LuaSnip メモ
LuaSnip メモ
Luaで色々できるNeovimのスニペットプラグインを試したメモ。
準備
ディレクトリ構成は以下。
~/.config/nvim
|-init.lua
|-/lua
|-plugin.lua
|-/config
|-cmp.lua
|-luasnip.lua
packerでプラグイン管理をしているので以下のように追記
補完はnvim-cmpでやっている
return require'packer'.startup(function(use)
-- ...OTHER PLUGINS
use {'hrsh7th/nvim-cmp',
requires = {
{'L3MON4D3/LuaSnip'},
{'saadparwaiz1/cmp_luasnip'}
},
config = function()
require'config.cmp'
require'config.luasnip'
end
}
--...
)
cmp.luaにLuaSnip用のキーコンフィグを設定
Ctrl + k でスニペットの展開およびジャンプができるように
local luasnip = require'luasnip'
local cmp = require'cmp'
cmp.setup {
snippet = {
expand = function(args)
require'luasnip'.lsp_expand(args.body)
end
},
documentation = {
border = "solid",
},
mapping = {
['<C-p>'] = cmp.mapping.select_prev_item(),
['<C-n>'] = cmp.mapping.select_next_item(),
['<C-d>'] = cmp.mapping.scroll_docs(-4),
['<C-f>'] = cmp.mapping.scroll_docs(4),
['<C-e>'] = cmp.mapping.close(),
['<C-k>'] = cmp.mapping(function (fallback)
if luasnip.expand_or_jumpable() then
luasnip.expand_or_jump()
else
fallback()
end
end, { "i", "s" }),
-- ...OTHER MAPS
}
LuaSnipの設定ファイルのヘッダ部分に以下を記述
local ls = require'luasnip'
local s = ls.snippet
local sn = ls.snippet_node
local isn = ls.indent_snippet_node
local t = ls.text_node
local i = ls.insert_node
local f = ls.function_node
local c = ls.choice_node
local d = ls.dynamic_node
local r = ls.restore_node
local events = require'luasnip.util.events'
local ai = require'luasnip.nodes.absolute_indexer'
DOC.md
GithubのLuaSnip/DOC.mdに一通りの解説がある。
ざっくりと訳していく
Lua snipはLuaのみで書かれたスニペットエンジンです。
うれしい。
BASICS
LuaSnipでは、スニペットはNodeによって構成されます。
- textNode
- insertNode
- functionNode
- choiceNode
- restoreNode
- dynamicNode
上2つで大体のスニペットは書ける。
スニペットは常に
s(trigger:string, nodes:table)
という関数sで記述されます。
こうして作られたスニペットは
ls.snippets.<filetype>
テーブルに追加されることで利用可能になります。
すべてのファイルタイプで有効にするにはls.snippets.all
テーブルに追加してください。
つまりはこう
ls.snippets = {
all = {
s( "trig1", { t("for all snippet"} ),
},
lua = {
s( "trig2", { t("for lua snippet"} ),
},
}
関数s=require'luasnip'.snippet
の第一引数にトリガー、第二引数に各ノードを詰め込んだテーブルを与えてひとつのスニペットオブジェクトを作り、対応したテーブルに登録することで利用可能になる。
(t()はtextNodeを作る関数。後述)
SNIPPETS
空のスニペット
s({trig="trigger"}, {})
第一引数にはスニペットのオプションを詰め込んだテーブルを与えることができる。
いくつか挙げる。(詳細は元DOC.md参照)
- trig:スニペットを呼び出すトリガーの文字列。この文字列が入力されたときスニペットを呼び出すことができる。
- dscr:スニペットの説明を入れられる。
- regTrig:Luaの正規表現を使ったトリガーを設定できるかどうか。詳しくはfunctionNodeで解説。boolで設定。
また、トリガー以外の設定をデフォルトでいいのなら以下のように略記できる。
s("trigger", {})
便利。
スニペットオブジェクトには興味深いテーブルがいくつか内包されています。
例えばsnippet.env
はLSPで用いられる変数が格納されたテーブルであり、snippet.captures
は正規表現を用いたトリガーによってキャプチャされた文字列が格納されます。
LSPには詳しくないのでよくわからないが、snippet.captures
のほうは結構便利で重要。
functionNodeのところで解説。
TEXTNODE
Nodeのうち最もシンプルなものは、ただのテキストです。
s("trigger", { t("Wow! Text!) })
既に何度か登場したt()
は、textNodeオブジェクトを作成する関数である。
このスニペットを呼び出すと、テキストが展開された後カーソルは文末に移動した状態になる。
つまりは
trigger|
こうだったのが、<Ctrl-k>を入力すると、
Wow! Text!|
こうなる。(| がカーソルの位置)
複数行にわたるテキストを展開させたい場合は
s("trigger", { t({ "First row.", "Second row.", "Third row." }) })
のように、テーブルに詰め込んで渡す。
("\n"を用いた改行はできない)
First row.
Second row.
Third row.|
こうなる。
INSERTNODE
これらのノードは編集可能なテキストとジャンプの機能を持ちます。
(これはちょうど$1
のような記法と同じです)
スニペットのジャンプ機能とプレースホルダをinsertNodeによって実現できる。
ジャンプ機能
関数i(n)
の第一引数に与えた整数の順にジャンプする。ただし0
だけは必ず最終位置を表す。
s("trigger", {
t({"Initial cursor ->"}), i(1),
t({"Second cursor ->"}), i(2),
t({"Last cursor ->"}), i(0),
}
つまり、スニペット展開後はまずi(1)
がある場所にカーソルが移動し、<Ctrl-k>などでジャンプするとi(2)
へ移動し、ジャンプを繰り返した最後にはi(0)
へ到達するということである。
Initial cursor -> # 初期位置
Second cursor -> # 次の位置
Last cursor -> # 最終位置
注意点として、スニペットをネストさせるとジャンプのインデックスは1にリセットされる。
s("trigger", {
i(1),
t(" :: "),
sn(2, {
i(1),
t(" : "),
i(2),
}
}
(sn()
を用いるとスニペットオブジェクトの中でスニペットオブジェクトを扱える)
プレースホルダ
InsertNodeの中に初期値のテキストを入れておくことができます。これは一種のデフォルトのテキストとして扱えます。
s("trigger", i(1, "This text is SELECTed after expanding"))
書いてある通り、そのInsertNodeに飛んだときにデフォルト値として設定されているテキストがSELECTモードで選択された状態で表示される。
なのでそのまま書き込めば、デフォルトのテキストを上書き可能。