Open8

LuaSnip メモ

nitomokinitomoki

準備

ディレクトリ構成は以下。

~/.config/nvim
|-init.lua
|-/lua
  |-plugin.lua
  |-/config
    |-cmp.lua
    |-luasnip.lua

packerでプラグイン管理をしているので以下のように追記
補完はnvim-cmpでやっている

plugin.lua
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 でスニペットの展開およびジャンプができるように

cmp.lua
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
    }
nitomokinitomoki

LuaSnipの設定ファイルのヘッダ部分に以下を記述

config/luasnip.lua
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'

nitomokinitomoki

BASICS

LuaSnipでは、スニペットはNodeによって構成されます。

  • textNode
  • insertNode
  • functionNode
  • choiceNode
  • restoreNode
  • dynamicNode

上2つで大体のスニペットは書ける。

スニペットは常にs(trigger:string, nodes:table)という関数sで記述されます。

こうして作られたスニペットはls.snippets.<filetype>テーブルに追加されることで利用可能になります。
すべてのファイルタイプで有効にするにはls.snippets.allテーブルに追加してください。

つまりはこう

luasnip.lua
ls.snippets = {
    all = {
        s( "trig1", { t("for all snippet"} ),
    },
    lua = {
        s( "trig2", { t("for lua snippet"} ),
    },
}

関数s=require'luasnip'.snippetの第一引数にトリガー、第二引数に各ノードを詰め込んだテーブルを与えてひとつのスニペットオブジェクトを作り、対応したテーブルに登録することで利用可能になる。
(t()はtextNodeを作る関数。後述)

nitomokinitomoki

SNIPPETS

空のスニペット

s({trig="trigger"}, {})

第一引数にはスニペットのオプションを詰め込んだテーブルを与えることができる。
いくつか挙げる。(詳細は元DOC.md参照)

  • trig:スニペットを呼び出すトリガーの文字列。この文字列が入力されたときスニペットを呼び出すことができる。
  • dscr:スニペットの説明を入れられる。
  • regTrig:Luaの正規表現を使ったトリガーを設定できるかどうか。詳しくはfunctionNodeで解説。boolで設定。

また、トリガー以外の設定をデフォルトでいいのなら以下のように略記できる。

s("trigger", {})

便利。


スニペットオブジェクトには興味深いテーブルがいくつか内包されています。
例えばsnippet.envはLSPで用いられる変数が格納されたテーブルであり、snippet.capturesは正規表現を用いたトリガーによってキャプチャされた文字列が格納されます。

LSPには詳しくないのでよくわからないが、snippet.capturesのほうは結構便利で重要。
functionNodeのところで解説。

nitomokinitomoki

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.|

こうなる。

nitomokinitomoki

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モードで選択された状態で表示される。
なのでそのまま書き込めば、デフォルトのテキストを上書き可能。