Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Neovim で今風のプラグインを書く方法

Neovim で今風のプラグインを書く方法

DeNA.vim(2023/3/2)の発表資料です。

JINNOUCHI Yasushi

March 02, 2023
Tweet

Other Decks in Technology

Transcript

  1. © DeNA Co., Ltd. 2 陣内 靖 Vim歴: 15 年以上?

    最初の dotfiles のコミット(2012/1/28) の vimrc は 749 行。 DeNA ソリューション事業本部ゲームアライアンス事業部 プラットフォーム開発部アカウントユーザーサービスグ ループ @delphinus @delphinus35 [email protected] [email protected] © DeNA Co., Ltd. 自己紹介
  2. © DeNA Co., Ltd. 3 今回の発表のテーマ • 対象者 ◦ Neovim

    使う人(Vim でも可) ▪ プラグイン開発経験があるとなお可。 • この発表を聞くとこうなります。 ◦ 「プラグイン」とは何か、分かる。 ◦ Neovim + Lua でプラグインを書きたくなる。 • こうはなりません。 ◦ 流行りのプラグインが分かる。 ◦ プラグイン管理が分かる。
  3. © DeNA Co., Ltd. 6 プラグイン書いてますか?何を書いてますか? 1. ファジーファインダープラグイン ◦ telescope.nvim,

    ddu.vim, fzf-preview.vim, …… 2. 自動補完プラグイン ◦ nvim-cmp, ddc.vim, coc.nvim, …… 3. ファイルタイププラグイン ◦ vim-go, vim-perl, vim-ruby, …… 4. UI 改善系 ◦ nvim-notify, noice.nvim, lspsaga.nvim, scrollbar.nvim, …… ◦ ↑ここが今一番元気! 1 その他、カラースキームや入力支援系 などなど。
  4. © DeNA Co., Ltd. 7 そのプラグイン、本当に必要? Neovim / Vim は年々進化しています。プラグインの機能を取り込むことも……

    • incsearch.vim → :h 'incsearch' (ブログエントリ) • vim-highlightedyank → :h vim.highlight.on_yank() (reddit) • そもそもターミナル機能やジョブ機能だって昔はプラグインで実現してましたね。 ◦ vimshell.vim, vimproc.vim, …… 1
  5. © DeNA Co., Ltd. 8 そもそもプラグインとは? プラグインとは、「'runtimepath' に指定したディレクトリにあるスクリプトを指定したタ イミングで評価するもの」。 1

    " /tmp/hoge ディレクトリにあるプラグインを追加 set runtimepath+=/tmp/hoge " すると、/tmp/hoge/plugin/hoge.lua のようなファイルが読み込まれます。
  6. © DeNA Co., Ltd. 9 プラグインにはどんなファイルがある? 各ディレクトリにスクリプト(*.vim, *.lua)があるものとします。 • /plugin

    : 自動的('runtimepath' に追加した時点)に評価されます。 • /ftdetect : ↑に同じ。 • /ftplugin : ファイル名と一致したファイルタイプのバッファーに入ると評価されます。 • /syntax : ファイル名と一致したシンタックスのバッファーに入ると評価されます。 • /colors : 「:colorscheme ファイル名」すると評価されます。 • /lua : require("hoge") すると評価されます。 他にも色々ある! :h 'runtimepath' 1
  7. © DeNA Co., Ltd. 10 プラグインにはどんなファイルがある? 各ディレクトリにスクリプト(*.vim, *.lua)があるものとします。 • /plugin

    : 自動的('runtimepath' に追加した時点)に評価されます。 • /ftdetect : ↑に同じ。 • /ftplugin : ファイル名と一致したファイルタイプのバッファーに入ると評価されます。 • /syntax : ファイル名と一致したシンタックスのバッファーに入ると評価されます。 • /colors : 「:colorscheme ファイル名」すると評価されます。 • /lua : require("hoge") すると評価されます。 他にも色々ある! :h 'runtimepath' 1 この3つ、もう要らないかも……
  8. © DeNA Co., Ltd. 11 ファイルタイププラグインの需要が低下している件 全部 LSP と Tree-sitter

    のせい。 • :make とか :compiler の設定 → Langauge Server • ftdetect → すでに Neovim / Vim で対応しているもの多数。 • syntax → Tree-sitter ファイルタイププラグインを入れる目的はだいたいこれら。ある程度メジャーな言語なら個 別にプラグインを用意する必要はありません(indent とかは要るかも)。 • LSP → nvim-lspconfig, vim-lsp, coc.nvim など。 • Tree-sitter → nvim-treesitter 1
  9. © DeNA Co., Ltd. 15 Neovim 上の Lua 2 Neovim

    上の Lua は 5.1 相当(Lua JIT)。 Lua はみんな大好きだよね!!!!!(圧)
  10. © DeNA Co., Ltd. 16 Lua のいいところ 2 速い!速さは正義! 右は単純プラグインなので実用的な例では

    ないですが、実際 Vimscript より めちゃめちゃ速いです。 (Vim9script より速い) https://github.com/delphinus/artify.nvim
  11. © DeNA Co., Ltd. 17 Lua のイヤなところ 2 • プロトタイプ型のオブジェクト指向言語なのが取っ付き辛い。

    ◦ クラスが無い。昔の JavaScript みたい。 • 動的型付き言語、また、メタプログラミングし放題なため 汎用性の無い独自実装が生まれがち。 • 言語コアのライブラリが貧弱で、外からモジュールを import する統一的な仕組みが無い。 ◦ LuaRocks というのもあるにはあるが、Lua JIT では使えないモジュールも多い。 なんか愚痴ばっかりですが……とにかく速いので許して。
  12. © DeNA Co., Ltd. 18 Neovim 上の Lua 開発体験(LSP &

    Tree-sitter) 2 • Language Server (sumneko 氏の)が大変出来が良い。 ◦ Type Annotation が強力。 ◦ ただ、Formatting は StyLua を別に使った方がポータブル。 • Neovim では 1st citizen Langauge として扱われているため、 Neovim コアのライブラリや Tree-sitter 対応が充実してます。 • LSP & TS の具体的な設定方法などは、ここでは省きます(他の資料見てね)。
  13. © DeNA Co., Ltd. 19 Neovim 上の Lua 開発体験(コアライブラリ) 2

    • Lua 自体のコアは大変貧弱なため、 便利関数やモジュールが Neovim コアに入ってます。 • 全ての関数には vim というグローバル変数からアクセスします(:h lua-builtin)。 -- オプション設定 vim.opt.termguicolors = true vim.opt.runtimepath:append "/path/to/hoge_plugin" -- Vim 関数・コマンド print(vim.fn.bufname()) vim.cmd [[echo "Hello, world!"]]
  14. © DeNA Co., Ltd. 20 コアライブラリ: vim.api 2 • 元来、Neovim

    では Vimscript 以外の言語で自由にプラグインを 書けることが強みでした。(:h remote-plugin) その時別プロセスから Vim の機能にアクセスするために作られた仕組みが vim.api.nvim_…… の関数群です(:h eval-api)。 • そのうち Neovim も方針変更して Lua JIT を組み込んでそれで直接プラグインを 書けるようになり、Vimscript でできることは全て Lua でやることになりました。 vim.api もそれに併せてどんどん拡張されています。 ◦ Remote Plugin はその割りを食ってほとんど使われなくなっちゃった……
  15. © DeNA Co., Ltd. 21 コアライブラリ: vim.api 2 -- bufname()

    と一緒 print(vim.api.nvim_buf_get_name()) -- ハイライト設定 vim.api.nvim_set_hl(0, "HogeRed", { fg = "#ff0000", bg = "#220000", bold = true }) -- Autocmd 設定 vim.api.nvim_create_autocmd("BufEnter", { pattern = "*.pl", callback = function() vim.notify "You’ve entered into a Perl buffer!" end, })
  16. © DeNA Co., Ltd. 22 コアライブラリ: 便利関数たち 2 便利関数は全て $VIMRUNTIME/lua/vim

    に定義されています。 Neovim 上のターミナルから見やすいです。 ここに無いもの(vim.api や vim.regex とか)は C で書かれているので ドキュメントや Neovim のソースを見てね。 ls $VIMRUNTIME/lua/vim F.lua filetype/ keymap.lua treesitter.lua _editor.lua filetype.lua lsp/ ui.lua _init_packages.lua fs.lua lsp.lua uri.lua _inspector.lua health.lua secure.lua _meta.lua highlight.lua shared.lua diagnostic.lua inspect.lua treesitter/
  17. © DeNA Co., Ltd. 23 コアライブラリ: 便利関数たち 2 vim.api の各関数は元の経緯から低レベルなものが多いです。

    そのままでは使いにくいため便利なラッパーが作られています。 vim.opt --> Vim 自体のオプション vim.fn --> Vim 関数 vim.cmd --> Vim コマンド vim.keymap.set("n", "<C-j>", function() print(123) end) --> キーマップ vim.notify "Hello, world!" --> :echo コマンドの代わり(後述) -- vim.fs シリーズ vim.fs.normalize "~/.config/nvim" vim.fs.find(".git", { path = vim.loop.cwd(), upward = true }) -- ↑これは https://github.com/delphinus/rtr.nvim で使っています。
  18. © DeNA Co., Ltd. 24 コアライブラリ: vim.loop 2 Neovim はそのコアを

    libuv を使ったイベントループモデルで実装しています。 • libuv is a multi-platform C library that provides support for asynchronous I/O based on event loops. libuv - Wikipedia Lua によるインターフェースは luv を使っており、そこには vim.loop からアクセス できます。機能としてはタイマー、ファイルシステムへのアクセス、TCP/UDP 通信、 コルーチンを使った非同期プログラミング、などなど。 要するに今風の言語なら使えるやつは大体使えます。 どんな機能があるかは :h luv を読んでください。
  19. © DeNA Co., Ltd. 26 Lua プラグインっぽさ、とは何か Vimscript のプラグインと違い、多くの Lua

    プラグインには 以下のような特徴(というかベストプラクティス)が見られます。 1. Lua API をなるべく使おう。 2. 'runtimepath' に追加するだけでは評価されないようにしよう。 3. ドキュメントが無くて README を読む必要がある。 ◦ この点だけはバッドプラクティスですが…… 3
  20. © DeNA Co., Ltd. 27 1. Lua API をなるべく使おう。 ぶっちゃけ、vim.cmd

    [[ …… ]] の中に Vimscript が書けるのでそれで Lua 化は完了します。 まあそれだとかっこ悪いよね、ということで以下の点に気を付けましょう。 • vim.fn.foo と vim.* で機能が被ったらなるべく後者を使う。 ◦ 例えば vim.fn.timer_start ではなく vim.loop.new_timer にする、とか。 • ファイルシステムやネットワークへのアクセスはコルーチンを使って非同期に。 ◦ vim.fs.dir のソースとかが参考になります。 • あとはドキュメントをとにかく読もう。 ◦ :h lua.txt , :h luv ref.txt , :h api.txt 3
  21. © DeNA Co., Ltd. 28 1. Lua API をなるべく使おう。 :echo

    するよりも、vim.notify を積極的に使いましょう。ユーザーは処理を上書きして メッセージの表示方法やフィルタリングを自由に定義できます(:h vim.notify()) nvim-notify などのプラグインを入れることでも手軽に拡張できます。 3 vim.cmd [[echo "debug hoge"]] -- これや print "debug hoge" -- こう書くより vim.notify "debug hoge" -- こっちがベター vim.notify = function(msg, level, options) if not msg:match "debug" or level >= vim.log.levels.INFO then print(msg) end end -- ↑一例: "debug" という文字列を含んだり INFO 未満のメッセージを省く
  22. © DeNA Co., Ltd. 29 2. 'runtimepath' に追加するだけでは評価されないようにする Vimscript なプラグインでは

    plugin/hoge.vim のように、'runtimepath' に追加した時自動で 評価される場所にスクリプトを置くことが多いです。Lua なプラグインではこれが、 明示的に setup() 関数を呼ばないと動作しないようになっています。 • こうなった理由はいまいち分かりません。流行みたいなものかも知れない。 利点としては以下のような点が考えられます。 • 評価のタイミングがコントロールし易い(遅延読み込みとか)。 • オプションを引数に与えるような自然な書き方ができる。 • 他のみんながやってるので驚きを最小限にできる(これが一番大事かも)。 3
  23. © DeNA Co., Ltd. 30 2. 'runtimepath' に追加するだけでは評価されないようにする。 -- 例えば以下のようなファイル構成にします。

    /README.md /lua/hoge/init.lua -- lua/hoge/init.lua return { setup = function(opts) vim.validate(opts, ……) -- 実際の処理 end, } 3 -- 呼び出し側ではこうします。 { "fugafuga/hoge.nvim", config = function() require("hoge").setup { fugahoge = { "abc", "cde" }, } end, } -- ↑何らかのプラグインマネージャーを -- 使う想定のコードです。
  24. © DeNA Co., Ltd. 31 3. ドキュメントが無くて README を読む必要がある •

    これは正直やめて欲しい習慣。 • ドキュメントはちゃんと書きましょう。でないと Neovim の中から参照しにくいです。 ◦ 一応、lazy.nvim のようなプラグインマネージャーは README への参照を :h hogehoge で引けるようにしてくれます。 • REDAME にはプラグインのスクリーンショットや代表的な使い方を書き、 オプションの詳細や追加情報はドキュメントに載せて欲しいです。 3
  25. © DeNA Co., Ltd. 33 4 まとめ • プラグイン、どんどん入れましょう! ◦

    そして定期的に棚卸ししましょう。 • Neovim 上の Lua、結構良いです。 • せっかく Lua なら今風のプラグインにしましょう。 • あと、ドキュメントは書きましょう!