Neovim+LSPをなるべく簡単な設定で構築する
はじめに
Noevimには組み込みのLSPクライアントがあり、きちんと設定すれば非常に高機能な開発環境を構築することができます。
今回は、初心者の方にもとっつきやすいようなるべく小さなファイルで解説することを目指しました。LSP関連の最低限の機能に絞って100行弱の設定ファイルに収まるようまとめています。
使用しているプラグインは主に4つ、依存関係を入れても7つです。補完とLSPサーバー管理以外の機能はほぼ組み込みで実現できます。
- wbthomason/packer.nvim(プラグインマネージャ)
- neovim/nvim-lspconfig(LSP設定)
- williamboman/mason.nvim(LSPサーバー管理)
- hrsh7th/nvim-cmp(補完)
変数情報の表示
エラー検出
変数ハイライト
補完 (nvim-cmp)
NVIM v0.7.2
Arch Linux
.config/nvim/init.lua
などの設定ファイルにコピペで上記の機能が実現できます。プラグインマネージャを入れて:PackerSync
を実行してプラグインをインストールしてください。
各機能の解説については可能な限り公式の情報を参照していますので合わせてお読みください。
.config/nvim/init.lua
-- package manager
require("packer").startup(function()
use 'wbthomason/packer.nvim'
use 'neovim/nvim-lspconfig'
use 'williamboman/mason.nvim'
use 'williamboman/mason-lspconfig.nvim'
use "hrsh7th/nvim-cmp"
use "hrsh7th/cmp-nvim-lsp"
use "hrsh7th/vim-vsnip"
-- use "hrsh7th/cmp-path"
-- use "hrsh7th/cmp-buffer"
-- use "hrsh7th/cmp-cmdline"
end)
-- 1. LSP Sever management
require('mason').setup()
require('mason-lspconfig').setup_handlers({ function(server)
local opt = {
-- -- Function executed when the LSP server startup
-- on_attach = function(client, bufnr)
-- local opts = { noremap=true, silent=true }
-- vim.api.nvim_buf_set_keymap(bufnr, 'n', 'K', '<cmd>lua vim.lsp.buf.hover()<CR>', opts)
-- vim.cmd 'autocmd BufWritePre * lua vim.lsp.buf.formatting_sync(nil, 1000)'
-- end,
capabilities = require('cmp_nvim_lsp').update_capabilities(
vim.lsp.protocol.make_client_capabilities()
)
}
require('lspconfig')[server].setup(opt)
end })
-- 2. build-in LSP function
-- keyboard shortcut
vim.keymap.set('n', 'K', '<cmd>lua vim.lsp.buf.hover()<CR>')
vim.keymap.set('n', 'gf', '<cmd>lua vim.lsp.buf.formatting()<CR>')
vim.keymap.set('n', 'gr', '<cmd>lua vim.lsp.buf.references()<CR>')
vim.keymap.set('n', 'gd', '<cmd>lua vim.lsp.buf.definition()<CR>')
vim.keymap.set('n', 'gD', '<cmd>lua vim.lsp.buf.declaration()<CR>')
vim.keymap.set('n', 'gi', '<cmd>lua vim.lsp.buf.implementation()<CR>')
vim.keymap.set('n', 'gt', '<cmd>lua vim.lsp.buf.type_definition()<CR>')
vim.keymap.set('n', 'gn', '<cmd>lua vim.lsp.buf.rename()<CR>')
vim.keymap.set('n', 'ga', '<cmd>lua vim.lsp.buf.code_action()<CR>')
vim.keymap.set('n', 'ge', '<cmd>lua vim.diagnostic.open_float()<CR>')
vim.keymap.set('n', 'g]', '<cmd>lua vim.diagnostic.goto_next()<CR>')
vim.keymap.set('n', 'g[', '<cmd>lua vim.diagnostic.goto_prev()<CR>')
-- LSP handlers
vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(
vim.lsp.diagnostic.on_publish_diagnostics, { virtual_text = false }
)
-- Reference highlight
vim.cmd [[
set updatetime=500
highlight LspReferenceText cterm=underline ctermfg=1 ctermbg=8 gui=underline guifg=#A00000 guibg=#104040
highlight LspReferenceRead cterm=underline ctermfg=1 ctermbg=8 gui=underline guifg=#A00000 guibg=#104040
highlight LspReferenceWrite cterm=underline ctermfg=1 ctermbg=8 gui=underline guifg=#A00000 guibg=#104040
augroup lsp_document_highlight
autocmd!
autocmd CursorHold,CursorHoldI * lua vim.lsp.buf.document_highlight()
autocmd CursorMoved,CursorMovedI * lua vim.lsp.buf.clear_references()
augroup END
]]
-- 3. completion (hrsh7th/nvim-cmp)
local cmp = require("cmp")
cmp.setup({
snippet = {
expand = function(args)
vim.fn["vsnip#anonymous"](args.body)
end,
},
sources = {
{ name = "nvim_lsp" },
-- { name = "buffer" },
-- { name = "path" },
},
mapping = cmp.mapping.preset.insert({
["<C-p>"] = cmp.mapping.select_prev_item(),
["<C-n>"] = cmp.mapping.select_next_item(),
['<C-l>'] = cmp.mapping.complete(),
['<C-e>'] = cmp.mapping.abort(),
["<CR>"] = cmp.mapping.confirm { select = true },
}),
experimental = {
ghost_text = true,
},
})
-- cmp.setup.cmdline('/', {
-- mapping = cmp.mapping.preset.cmdline(),
-- sources = {
-- { name = 'buffer' }
-- }
-- })
-- cmp.setup.cmdline(":", {
-- mapping = cmp.mapping.preset.cmdline(),
-- sources = {
-- { name = "path" },
-- { name = "cmdline" },
-- },
-- })
解説
1. LSP Server management
- https://github.com/neovim/nvim-lspconfig
- https://github.com/williamboman/mason.nvim
- https://github.com/williamboman/mason-lspconfig.nvim
:Mason
を実行するとサーバー管理の画面が出てきます。j
/ k
でカーソル移動、i
でサーバーをインストールします。
on_attach = function ... end
の中の処理はLSP起動時に実行されます。例えば下記のように設定すればLSPが有効なバッファ内だけでキーボードショートカットが有効になります。
on_attach = function(client, bufnr)
vim.api.nvim_buf_set_keymap(bufnr, 'n', 'K', '<cmd>lua vim.lsp.buf.hover()<CR>', { noremap=true })
end,
記事を書いている途中で nvim-lsp-installerの開発終了 の報を知って慌てて書き直しました。移行の手順をまとめてくださっている方がいますので、使っていた方は合わせてお読みください。
2. build-in LSP
keyboard shortcut
- https://github.com/neovim/nvim-lspconfig
-
:help lsp.txt
https://neovim.io/doc/user/lsp.html -
:help diagnostic.txt
https://neovim.io/doc/user/diagnostic.html
LSPには組み込みの機能だけで数え切れないほどたくさんのものがあります。全て使いこなすのは至難の業ですので、まずは少数の機能を使いながら慣れていくといいでしょう。
vim.keymap.set('n', 'K', '<cmd>lua vim.lsp.buf.hover()<CR>')
カーソル下の変数の情報を表示します。わからない箇所があったらとりあえず見ておけば大抵のことはわかります。
vim.keymap.set('n', 'gd', '<cmd>lua vim.lsp.buf.definition()<CR>')
いわゆる定義ジャンプです。他にも似たような機能として宣言(delcaration)・実装(implemantation)もあります。言語にもよりますが、私はもっぱらdefinitionを使用しています。
vim.keymap.set('n', 'gf', '<cmd>lua vim.lsp.buf.formatting()<CR>')
改行やインデントなどのフォーマットを整えます。`
vim.keymap.set('n', 'gr', '<cmd>lua vim.lsp.buf.references()<CR>')
カーソル下の変数をコード内で参照している箇所を一覧表示します。
vim.keymap.set('n', 'gn', '<cmd>lua vim.lsp.buf.rename()<CR>')
変数名のリネームです。
vim.keymap.set('n', 'ga', '<cmd>lua vim.lsp.buf.code_action()<CR>')
Error/Warning/Hintが出ている箇所で実行可能な修正の候補を表示してくれます。
-
Organize Import
不足しているライブラリを自動でインポート -
Removed unused declaration
使用していないインポートを削除 -
Remove unnecessarry 'await'
不要な await を削除 -
Infer function return type
関数の返り値を推測 - etc
言語サーバーによって可能なActionは変わります。使いこなせばとても有用ですので是非使ってみてください。
ヘルプを見れば全ての機能の説明がありますので、慣れてきたら探してみるとよいと思います。:lua vim.lsp.buf.hover()
のように実行すれば関数の動作を直接確認することができますので試してみましょう。
LSP handlers
-
:help lsp-handlers
https://neovim.io/doc/user/lsp.html#lsp-handlers -
:help lsp-method
https://neovim.io/doc/user/lsp.html#lsp-method
LSP関連の表示などを設定します。基本的にはデフォルトのままでOKです。virtual_text
だけは見にくいのでオフにしておいた方が良いと思います。
reference highlight(変数ハイライト)
-
:help document_hightlight
https://neovim.io/doc/user/lsp.html#vim.lsp.buf.document_highlight() - https://sbulav.github.io/til/til-neovim-highlight-references/
カーソル下にある変数をコード内でハイライトしてくれる機能です。Highlight Referenceと言います。デフォルトでオフになっています。通常は cterm
、:set termguicolors
が有効のときは guiterm
の値が使用されます。
ハイライト有効になるまでの時間はmsec単位で指定します。デフォルトは4000です。
3. 補完 (nvim-cmp)
vim.keymap.set('omnifunc', 'v:lua.vim.lsp.omnifunc')
とすればプラグインなしで<Ctrl-x><Ctrl-o>
でLSPによるオムニ補完ができるのですが、使い勝手がよくないのでnvim-cmpを使います。
<C-n>
/<C-p>
で候補を探して<CR>
(Enter) で選択します。
同じ作者が作成している cmp-path, cmp-buffer, cmp-cmdline も便利なのでコメントで入れておきました。使う場合はコメントアウトを外せばそのまま使用できます。
最後に
私の設定ファイルのリンクを貼っておきます。いろんな機能をツギハギしていて見にくいのであくまで参考までに。
英語の動画ですが、かなりギークな設定で面白いです。
『なるべく簡単な設定で』と銘打ったので今回は入れませんでしたが、fzf-luaとLSPの連携がとても便利です(気が向いたらそのうち別で記事を書きます)
Discussion
.config/nvim/init.lua
の設定例で、vim.keymap.set('n', 'gf', '<cmd>lua vim.lsp.buf.formatting()<CR>')
はvim.keymap.set('n', 'gf', '<cmd>lua vim.lsp.buf.format()<CR>')
にしないとE5108: Error executing lua [string ":lua"]:1: attempt to call field 'formatting' (a nil value)
というエラーになりましたので、参考までにお知らせします。なお、私のNeovimのバージョンなどは以下のとおりです。