neovim(nvim)のスニペット機能をまあまあ調べた
結局、個人的にvimからnvimに移行する目玉は
- スニペット
- ファイル操作
- lsp
くらいかなと思ってたりして、まあもちろん他にもありますが。
以降は全てlazy.vim
を使った例
最低限動作する例
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable",
lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
require("lazy").setup({
{
-- 補完エンジン nvim-cmp の設定
"hrsh7th/nvim-cmp",
event = "InsertEnter", -- 挿入モードに入ったときにプラグインをロード
dependencies = { -- nvim-cmp に必要な依存プラグイン
{ "hrsh7th/cmp-buffer" }, -- 現在のバッファの内容を補完候補に含める
{ "saadparwaiz1/cmp_luasnip" }, -- LuaSnip と nvim-cmp を統合
{ "L3MON4D3/LuaSnip" }, -- スニペットエンジン LuaSnip
{ "rafamadriz/friendly-snippets" }, -- 事前定義されたスニペットコレクション
},
config = function()
-- nvim-cmp の設定
local cmp = require("cmp") -- nvim-cmp のメインモジュールをロード
local luasnip = require("luasnip") -- LuaSnip のモジュールをロード
require("luasnip/loaders/from_vscode").lazy_load() -- VSCode スタイルのスニペットをロード
cmp.setup({
mapping = cmp.mapping.preset.insert({
["<C-b>"] = cmp.mapping.scroll_docs(-4), -- 補完候補のドキュメントを上にスクロール
["<C-f>"] = cmp.mapping.scroll_docs(4), -- 補完候補のドキュメントを下にスクロール
["<C-e>"] = cmp.mapping.abort(), -- 補完を中断して閉じる
["<CR>"] = cmp.mapping.confirm({ select = true }), -- 補完確定 (現在選択中の候補を使用)
}),
sources = cmp.config.sources({
{ name = "luasnip", priority_weight = 20 }, -- LuaSnip を補完候補に含める
}, {
{ name = "buffer" }, -- バッファの内容を補完候補に含める
}),
})
end,
},
})
nvim-cmp
スニペット自体はcompletionせずとも使えると思うんだけど、nvimの場合ほぼほぼこの補完サポートを使って発動させる場合が多いようだ。
require("lazy").setup({
{
-- 補完エンジン nvim-cmp の設定
"hrsh7th/nvim-cmp",
event = "InsertEnter", -- 挿入モードに入ったときにプラグインをロード
dependencies = { -- nvim-cmp に必要な依存プラグイン
{ "hrsh7th/cmp-buffer" }, -- 現在のバッファの内容を補完候補に含める
{ "saadparwaiz1/cmp_luasnip" }, -- LuaSnip と nvim-cmp を統合
{ "L3MON4D3/LuaSnip" }, -- スニペットエンジン LuaSnip
{ "rafamadriz/friendly-snippets" }, -- 事前定義されたスニペットコレクション
},
この中で関連するものも含め5パッケージがインストールされている。ここでnvim-cmp
はいくつかのカテゴリーみたいなのにグルーピングして補完アクセスするようだ。ここでは
cmp-buffer
cmp_luasnip
を媒介してアクセスしており、バッファとスニペットで分けられているようである。
さらに
LuaSnip
friendly-snippets
と分離しており、この時点でまあまあ混乱しがちになった。
local cmp = require("cmp") -- nvim-cmp のメインモジュールをロード
local luasnip = require("luasnip") -- LuaSnip のモジュールをロード
require("luasnip/loaders/from_vscode").lazy_load() -- VSCode スタイルのスニペットをロード
ここで各種ロードを行っている。
require("luasnip/loaders/from_vscode")
に関しては後述
で、キーマッピングしないとほとんど動かない
これは適当なhtmlを開いた時の補完サジェストであるが、ここから触る事ができない。ただしくマッピングすると
このように補完候補からsnippetを選択し、展開できるようになる。
ultisnipの例
これは古い型式のスニペットであり、基本的には以下のようにして発動する
$ git --no-pager diff init.lua
diff --git a/init.lua b/init.lua
index 72df0de..ff2ddb7 100644
--- a/init.lua
+++ b/init.lua
@@ -17,26 +17,41 @@ require("lazy").setup({
"hrsh7th/nvim-cmp",
event = "InsertEnter", -- 挿入モードに入ったときにプラグインをロード
dependencies = { -- nvim-cmp に必要な依存プラグイン
- { "hrsh7th/cmp-buffer" }, -- 現在のバッファの内容を補完候補に含める
- { "saadparwaiz1/cmp_luasnip" }, -- LuaSnip と nvim-cmp を統合
- { "L3MON4D3/LuaSnip" }, -- スニペットエンジン LuaSnip
- { "rafamadriz/friendly-snippets" }, -- 事前定義されたスニペットコレクション
+-- { "hrsh7th/cmp-buffer" }, -- 現在のバッファの内容を補完候補に含める
+-- { "saadparwaiz1/cmp_luasnip" }, -- LuaSnip と nvim-cmp を統合
+-- { "L3MON4D3/LuaSnip" }, -- スニペットエンジン LuaSnip
+-- { "rafamadriz/friendly-snippets" }, -- 事前定義されたスニペットコレクション
+ { "quangnguyen30192/cmp-nvim-ultisnips" }, -- cmp と UltiSnips の連携プラグイン
+ { "SirVer/ultisnips" }, -- スニペットエンジン
+ { "honza/vim-snippets" }, -- スニペット集
},
config = function()
-- nvim-cmp の設定
local cmp = require("cmp") -- nvim-cmp のメインモジュールをロード
- local luasnip = require("luasnip") -- LuaSnip のモジュールをロード
- require("luasnip/loaders/from_vscode").lazy_load() -- VSCode スタイルのスニペットをロード
+ -- local luasnip = require("luasnip") -- LuaSnip のモジュールをロード
+ -- require("luasnip/loaders/from_vscode").lazy_load() -- VSCode スタイルのスニペットをロード
+ local cmp_ultisnips_mappings = require("cmp_nvim_ultisnips.mappings")
+
+ -- UltiSnips スニペットディレクトリを設定
+ vim.g.UltiSnipsSnippetDirectories = {
+ vim.fn.stdpath("data") .. "/lazy/vim-snippets/UltiSnips"
+ }
cmp.setup({
mapping = cmp.mapping.preset.insert({
["<C-b>"] = cmp.mapping.scroll_docs(-4), -- 補完候補のドキュメントを上にスクロール
["<C-f>"] = cmp.mapping.scroll_docs(4), -- 補完候補のドキュメントを下にスクロール
- ["<C-e>"] = cmp.mapping.abort(), -- 補完を中断して閉じる
- ["<CR>"] = cmp.mapping.confirm({ select = true }), -- 補完確定 (現在選択中の候補を使用)
+ -- ["<C-e>"] = cmp.mapping.abort(), -- 補完を中断して閉じる
+ -- ["<CR>"] = cmp.mapping.confirm({ select = true }), -- 補完確定 (現在選択中の候補を使用)
+ ["<Tab>"] = cmp.mapping(function(fallback)
+ cmp_ultisnips_mappings.expand_or_jump_forwards(fallback)
+ end, { "i", "s" }),
+ ["<S-Tab>"] = cmp.mapping(function(fallback)
+ cmp_ultisnips_mappings.jump_backwards(fallback)
+ end, { "i", "s" }),
}),
sources = cmp.config.sources({
- { name = "luasnip", priority_weight = 20 }, -- LuaSnip を補完候補に含める
+ -- { name = "luasnip", priority_weight = 20 }, -- LuaSnip を補完候補に含める
+ { name = "ultisnips", priority_weight = 10 },
}, {
{ name = "buffer" }, -- バッファの内容を補完候補に含める
}),
ソースをみるとultisnipをガンガン読むように切り替えているのはわかるんだけど、ここで重要なのは
mapping = cmp.mapping.preset.insert({
["<C-b>"] = cmp.mapping.scroll_docs(-4), -- 補完候補のドキュメントを上にスクロール
["<C-f>"] = cmp.mapping.scroll_docs(4), -- 補完候補のドキュメントを下にスクロール
-- ["<C-e>"] = cmp.mapping.abort(), -- 補完を中断して閉じる
-- ["<CR>"] = cmp.mapping.confirm({ select = true }), -- 補完確定 (現在選択中の候補を使用)
["<Tab>"] = cmp.mapping(function(fallback)
cmp_ultisnips_mappings.expand_or_jump_forwards(fallback)
end, { "i", "s" }),
["<S-Tab>"] = cmp.mapping(function(fallback)
cmp_ultisnips_mappings.jump_backwards(fallback)
end, { "i", "s" }),
}),
ここでultisnipのmappingに特化した設定を行っている。こいつは基本的に<TAB>でどんどん確定していくのがdefaultなので、このような設定になっていると同時に作業イメージは以下のようになる。
高速性を重視するならアリだろう。
両者を混ぜるタイプ
たとえばbladeの場合、ultisnipでは基本的な補完しか効かないという問題が若干ある。これはhtmlを継承していないからだ
以下はluasnipだけでbladeを開いた例
このようにdivのスニペットなどが効いていない。これはultisnipには含まれるので、以下のようにして共存するという手が1つある
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable",
lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
require("lazy").setup({
{
-- 補完エンジン nvim-cmp の設定
"hrsh7th/nvim-cmp",
event = "InsertEnter", -- 挿入モードに入ったときにプラグインをロード
dependencies = { -- nvim-cmp に必要な依存プラグイン
{ "hrsh7th/cmp-buffer" }, -- 現在のバッファの内容を補完候補に含める
{ "saadparwaiz1/cmp_luasnip" }, -- LuaSnip と nvim-cmp を統合
{ "L3MON4D3/LuaSnip" }, -- スニペットエンジン LuaSnip
{ "rafamadriz/friendly-snippets" }, -- 事前定義されたスニペットコレクション
{ "quangnguyen30192/cmp-nvim-ultisnips" }, -- cmp と UltiSnips の連携プラグイン
{ "SirVer/ultisnips" }, -- スニペットエンジン
{ "honza/vim-snippets" }, -- スニペット集
},
config = function()
local cmp = require("cmp") -- nvim-cmp のメインモジュールをロード
local luasnip = require("luasnip") -- LuaSnip のモジュールをロード
require("luasnip/loaders/from_vscode").lazy_load() -- VSCode スタイルのスニペットをロード
local cmp_ultisnips_mappings = require("cmp_nvim_ultisnips.mappings")
-- UltiSnips スニペットディレクトリを設定
vim.g.UltiSnipsSnippetDirectories = {
vim.fn.stdpath("data") .. "/lazy/vim-snippets/UltiSnips"
}
cmp.setup({
mapping = cmp.mapping.preset.insert({
["<C-b>"] = cmp.mapping.scroll_docs(-4), -- 補完候補のドキュメントを上にスクロール
["<C-f>"] = cmp.mapping.scroll_docs(4), -- 補完候補のドキュメントを下にスクロール
["<C-e>"] = cmp.mapping.abort(), -- 補完を中断して閉じる
["<CR>"] = cmp.mapping.confirm({ select = true }), -- 補完確定 (現在選択中の候補を使用)
-- ["<Tab>"] = cmp.mapping(function(fallback)
-- cmp_ultisnips_mappings.expand_or_jump_forwards(fallback)
-- end, { "i", "s" }),
-- ["<S-Tab>"] = cmp.mapping(function(fallback)
-- cmp_ultisnips_mappings.jump_backwards(fallback)
-- end, { "i", "s" }),
}),
sources = cmp.config.sources({
{ name = "luasnip", priority_weight = 20 },
{ name = "ultisnips", priority_weight = 10 },
}, {
{ name = "buffer" }, -- バッファの内容を補完候補に含める
}),
})
end,
},
})
すると...
となったりする
でも
2つも混ぜるのもアレだし、bladeは最近(記事書いてるにもかかわらず)全く使ってないからultisnipは必要ないかなあと思ったりして...混乱するし
lspと統合
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable",
lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
require("lazy").setup({
-- Mason (LSPやツールのインストーラ)
{
"williamboman/mason.nvim",
config = function()
require("mason").setup()
end,
},
-- MasonとLSPconfigの連携
{
"williamboman/mason-lspconfig.nvim",
dependencies = { "neovim/nvim-lspconfig" },
config = function()
require("mason-lspconfig").setup({
ensure_installed = { "intelephense", "jsonls" },
})
local lspconfig = require("lspconfig")
local capabilities = require("cmp_nvim_lsp").default_capabilities()
local on_attach = function(client, bufnr)
vim.api.nvim_buf_set_keymap(bufnr, "i", "<C-k>", "<cmd>lua vim.lsp.buf.signature_help()<CR>", { noremap = true, silent = true })
vim.cmd([[autocmd CursorHoldI * lua vim.lsp.buf.signature_help()]])
end
-- PHP LSPの設定
lspconfig.intelephense.setup({
capabilities = capabilities,
on_attach = on_attach,
root_dir = lspconfig.util.root_pattern("composer.json", ".git", "."),
settings = {
intelephense = {
files = {
maxSize = 5000000,
associations = { "*.php" },
includePaths = {
"./_ide_helper.php",
"./_ide_helper_models.php",
},
},
},
},
})
-- JSON LSPの設定
lspconfig.jsonls.setup({
capabilities = capabilities,
on_attach = on_attach,
})
end,
},
{
-- 補完エンジン nvim-cmp の設定
"hrsh7th/nvim-cmp",
event = "InsertEnter", -- 挿入モードに入ったときにプラグインをロード
dependencies = { -- nvim-cmp に必要な依存プラグイン
"hrsh7th/cmp-buffer" , -- 現在のバッファの内容を補完候補に含める
"saadparwaiz1/cmp_luasnip" , -- LuaSnip と nvim-cmp を統合
"L3MON4D3/LuaSnip" , -- スニペットエンジン LuaSnip
"rafamadriz/friendly-snippets" , -- 事前定義されたスニペットコレクション
"hrsh7th/cmp-nvim-lsp", -- LSP補完
},
config = function()
-- nvim-cmp の設定
local cmp = require("cmp") -- nvim-cmp のメインモジュールをロード
local luasnip = require("luasnip") -- LuaSnip のモジュールをロード
require("luasnip/loaders/from_vscode").lazy_load() -- VSCode スタイルのスニペットをロード
cmp.setup({
mapping = cmp.mapping.preset.insert({
["<C-b>"] = cmp.mapping.scroll_docs(-4), -- 補完候補のドキュメントを上にスクロール
["<C-f>"] = cmp.mapping.scroll_docs(4), -- 補完候補のドキュメントを下にスクロール
["<C-e>"] = cmp.mapping.abort(), -- 補完を中断して閉じる
["<CR>"] = cmp.mapping.confirm({ select = true }), -- 補完確定 (現在選択中の候補を使用)
}),
sources = cmp.config.sources({
{ name = "luasnip" },
{ name = "nvim_lsp" },
}, {
{ name = "buffer" }, -- バッファの内容を補完候補に含める
}),
})
end,
},
このようにスニペットに加えてlspの補完も可能になった。
lspkind-nvimでアイコン表示
dependencies = { -- nvim-cmp に必要な依存プラグイン
"hrsh7th/cmp-buffer" , -- 現在のバッファの内容を補完候補に含める
"hrsh7th/cmp-path", -- パス補完
"saadparwaiz1/cmp_luasnip" , -- LuaSnip と nvim-cmp を統合
"L3MON4D3/LuaSnip" , -- スニペットエンジン LuaSnip
"rafamadriz/friendly-snippets" , -- 事前定義されたスニペットコレクション
"hrsh7th/cmp-nvim-lsp", -- LSP補完
"onsails/lspkind-nvim", -- アイコン表示
onsails/lspkind-nvim
を加える。ここではhrsh7th/cmp-path
も加えている。
config = function()
-- nvim-cmp の設定
local cmp = require("cmp") -- nvim-cmp のメインモジュールをロード
local luasnip = require("luasnip") -- LuaSnip のモジュールをロード
require("luasnip/loaders/from_vscode").lazy_load() -- VSCode スタイルのスニペットをロード
local lspkind = require("lspkind")
cmp.setup({
mapping = cmp.mapping.preset.insert({
["<C-b>"] = cmp.mapping.scroll_docs(-4), -- 補完候補のドキュメントを上にスクロール
["<C-f>"] = cmp.mapping.scroll_docs(4), -- 補完候補のドキュメントを下にスクロール
["<C-e>"] = cmp.mapping.abort(), -- 補完を中断して閉じる
["<CR>"] = cmp.mapping.confirm({ select = true }), -- 補完確定 (現在選択中の候補を使用)
}),
sources = cmp.config.sources({
{ name = "luasnip" },
{ name = "nvim_lsp" },
}, {
{ name = "buffer" }, -- バッファの内容を補完候補に含める
{ name = "path" },
}),
formatting = {
format = lspkind.cmp_format({
mode = "symbol_text",
maxwidth = 50,
}),
},
})
end,
大分ガラっと変わってきた。
formatting = {
format = lspkind.cmp_format({
mode = "symbol_text",
maxwidth = 50,
menu = {
buffer = "[Buffer]",
nvim_lsp = "[LSP]",
path = "[Path]",
vsnip = "[Snippet]",
},
}),
},
とすると
もうちょいわかりやすくなる
Discussion