Vim/Neovimプラグイン作成入門
Vim/Neovimユーザーの方ならまず間違いなく何らかのプラグインを利用していると思いますが、全員がその作り方や仕組みまで理解しているわけではないでしょう。かく言う私も10年近くVimを使ってきましたが、プラグインの中身についてはよくわかっていませんでした。
というわけで、今回は Vim/Neovim向けのプラグインをVimScript/Luaで作成してみました。最小限の構成で、かつある程度実用性のあるものになります。
実際にプラグインを作ろうとしている方はもちろん、使っているプラグインの中身を理解するのにも役立つかと思いますので是非読んでみてください。
フォルダ構成
example-plugin という名前のプラグインのフォルダの構成はこのようになります。フォルダの場所はどこでもOKです。この構成でGithubのリポジトリを作成すれば公開できます。
公開するならREADME.md
, LICENSE
, doc/example-plugin.txt
は作るべきですし、他にもscripts
, tests
などのフォルダがありますが今回は省きます。
example-plugin
├── plugin
│ └── example.vim
├── autoload
│ └── example.vim
└── lua
└── example
├── init.lua
└── hello.lua
プラグインをローカルのホームフォルダに置いた場合の設定ファイルの記述はこのようになります。お使いのプラグインマネージャに合わせて変更ください。
require("packer").startup(function()
use '~/example-plugin'
end)
plugin/example.vim
プラグインロード時に読み込まれるファイルです。プログラムのエントリーポイントみたいなものです。Vim ScriptプラグインもLuaプラグインでも共通です。
初期化関数の実行、コマンドの公開、Vim/Neovim本体のバージョンチェック、プラグインが二重に読み込まれるのを防止する処理を行っています。他にも、デフォルトのキーバインドの記述もここに書いておくといいでしょう。
if exists('g:loaded_example_plugin')
finish
endif
let g:loaded_example_plugin = 1
call example#IMEStateSaveEnable()
command! -nargs=0 IMEStateSaveEnable call example#IMEStateSaveEnable()
command! -nargs=0 IMEStateSaveDisable call example#IMEStateSaveDisable()
command! -nargs=0 IMEStateSaveToggle call example#IMEStateSaveToggle()
if !has('nvim')
command! -nargs=0 ExampleOpenWin lua require("example").nvim_open_win()
endif
autoload/example.vim
VimScriptによる関数を登録します。ファイル名#関数名という命名規則に従います。
今回例として作成した機能は、INSERTモードからNORMALモードに移る時にIME (fcitx5) の状態をオフにし、再度INSERTモードに入る時に元の状態に戻すというものです。autocmd
,autogroup
の機能を利用して実装しています。日本語の文章を書く時に重宝します。
" $ fcitx5-remote -h
" Usage: fcitx5-remote [OPTION]
" -c inactivate input method
" -o activate input method
" [no option] display fcitx state, 0 for close, 1 for inactive, 2 for active
" -h display this help and exit
function! example#IMEStateSaveEnable() abort
if executable('fcitx5-remote')
let g:fcitx_state = 1
augroup IMEStateSave
autocmd!
autocmd InsertLeave * let g:fcitx_state = str2nr(system('fcitx5-remote'))
autocmd InsertLeave * call system('fcitx5-remote -c')
autocmd InsertEnter * call system(g:fcitx_state == 1 ? 'fcitx5-remote -c': 'fcitx5-remote -o')
augroup END
endif
endfunction
function! example#IMEStateSaveDisable() abort
augroup IMEStateSave
autocmd!
augroup END
endfunction
function! example#IMEStateSaveToggle() abort
if !exists('#IMEStateSave#InsertEnter')
call example#IMEStateSaveEnable()
else
call example#IMEStateSaveDisable()
endif
endfunction
lua/example/*.lua
Luaによる関数を実装します。Luaモジュール本体と、必要に応じてサブモジュールを実装します。M
というテーブルにメソッドを追加してモジュールとして公開するのが一般的です。
使用できるAPIやLuaの文法は下記を参照してください。実装の状況にもよりますが、VimScriptでできることはだいたいLuaでもできます。
-
https://neovim.io/doc/user/api.html
:help api.txt
-
https://neovim.io/doc/user/lua.html
:help lua.txt
- https://github.com/willelz/nvim-lua-guide-ja/blob/master/README.ja.md
今回実装した機能は、エディタ右下に80x30のフロートウィンドウを作成するというものです。メモする時に使えるんじゃないでしょうか。より実用的なものを目指すなら本当はウィンドウのサイズによっていい感じに調整できるようにしたいですが、vim.api.nvim_open_win
関数の紹介という意味合いで簡単なものにしています。
また、バッファ番号を保存するためにクラスを活用していますが、この使い方で合ってるのかはぶっちゃけ自信ないです。
local hello = require("example.hello")
win = hello.Window:new()
local M = {}
function M.nvim_open_win()
h = vim.opt.lines['_value'] - vim.o.cmdheight
w = vim.o.columns
vim.api.nvim_open_win(win.bufnr, true, {
relative='win',
anchor="NW",
width=80, -- ウィンドウの幅
height=30, -- ウィンドウの高さ
col=w-80-5, -- 右からの距離
row=h-30-3, -- 上からの距離
focusable=true,
})
end
return M
local Window = {}
function Window:new()
local w = {}
w.bufnr = vim.api.nvim_create_buf(false, true)
return w
end
M = {}
M.Window = Window
return M
また、*.lua*
ファイルを開いた状態で:luafile %
とするとVim内でLuaを実行することができます。関数の機能などを確認する時に便利です。プラグインの開発中で覚えておくといいと思います。
参考
NeovimでLuaプラグインを作成するやり方について丁寧に書いてありますので、合わせて読むとわかりやすいと思います。
Discussion