Neovim builtin LSP設定入門
はじめに
Neovim には組み込みのLSPクライアントがあります。ちょっと前までは VSCode 並の開発体験を得るためにはcoc.nvim
を使うのがベストな選択肢でしたが、neovim builtin lsp(以下 nvim-lsp)でもエコシステムが整ってきており、いい感じの支援機能が受けられます。この記事ではその設定の入門について説明します。
また、例として Web フロント系のツールを使っています。わからなくても問題ありませんがわかってるとより理解しやすいかと思います。
環境
- Neovim 0.7
- Vimscript は使わず Lua で書きます
基本的な設定
必須プラグイン系
nvim-lsp は組み込みではあるものの、そのままだと扱いづらいため実質必須となるプラグインがあります。
- nvim-lspconfig (https://github.com/neovim/nvim-lspconfig)
- mason.nvim (https://github.com/williamboman/mason.nvim)
- mason-lspconfig.nvim (https://github.com/williamboman/mason-lspconfig.nvim)
まず、各言語用の設定を提供するnvim-lspconfigです。neovim の公式で管理されています。LSP プロトコル自体はどの言語でも共通ですが LSP サーバーを起動するためのコマンドラインオプションやどういう場合にどの言語サーバーを起動させるべきか(例えばpackage.json
がある場合は Typescript の言語サーバーが起動してほしいなど)はそれぞれ違うのでこのプラグインが必要になります。
次に mason.nvim ですが、これは様々な外部依存パッケージを neovim 内からインストールできるようにするプラグインです。そして mason-lspconfig は mason.nvim でインストールした LSP サーバーを使うために必要です。
設定例です。
local install_path = vim.fn.stdpath("data").."/site/pack/packer/start/packer.nvim"
if vim.fn.empty(vim.fn.glob(install_path)) > 0 then
packer_bootstrap = vim.fn.system({"git", "clone", "--depth", "1", "https://github.com/wbthomason/packer.nvim", install_path})
end
require("packer").startup(function(use)
use "wbthomason/packer.nvim"
use "neovim/nvim-lspconfig"
use "williambomanm/mason.nvim"
use "williambomanm/mason-lspconfig.nvim"
if packer_bootstrap then
require("packer").sync()
end
end)
vim.cmd([[autocmd BufWritePost init.lua source <afile> | PackerCompile]])
local on_attach = function(client, bufnr)
-- LSPサーバーのフォーマット機能を無効にするには下の行をコメントアウト
-- 例えばtypescript-language-serverにはコードのフォーマット機能が付いているが代わりにprettierでフォーマットしたいときなどに使う
-- client.resolved_capabilities.document_formatting = false
local set = vim.keymap.set
set("n", "gD", "<cmd>lua vim.lsp.buf.declaration()<CR>")
set("n", "gd", "<cmd>lua vim.lsp.buf.definition()<CR>")
set("n", "K", "<cmd>lua vim.lsp.buf.hover()<CR>")
set("n", "gi", "<cmd>lua vim.lsp.buf.implementation()<CR>")
set("n", "<C-k>", "<cmd>lua vim.lsp.buf.signature_help()<CR>")
set("n", "<space>wa", "<cmd>lua vim.lsp.buf.add_workspace_folder()<CR>")
set("n", "<space>wr", "<cmd>lua vim.lsp.buf.remove_workspace_folder()<CR>")
set("n", "<space>wl", "<cmd>lua print(vim.inspect(vim.lsp.buf.list_workspace_folders()))<CR>")
set("n", "<space>D", "<cmd>lua vim.lsp.buf.type_definition()<CR>")
set("n", "<space>rn", "<cmd>lua vim.lsp.buf.rename()<CR>")
set("n", "<space>ca", "<cmd>lua vim.lsp.buf.code_action()<CR>")
set("n", "gr", "<cmd>lua vim.lsp.buf.references()<CR>")
set("n", "<space>e", "<cmd>lua vim.lsp.diagnostic.show_line_diagnostics()<CR>")
set("n", "[d", "<cmd>lua vim.lsp.diagnostic.goto_prev()<CR>")
set("n", "]d", "<cmd>lua vim.lsp.diagnostic.goto_next()<CR>")
set("n", "<space>q", "<cmd>lua vim.lsp.diagnostic.set_loclist()<CR>")
set("n", "<space>f", "<cmd>lua vim.lsp.buf.formatting()<CR>")
end
require("mason").setup()
require("mason-lspconfig").setup()
require("mason-lspconfig").setup_handlers {
function (server_name) -- default handler (optional)
require("lspconfig")[server_name].setup {
on_attach = on_attach
}
end,
}
ここではプラグインマネージャとしてpacker.nvimを使用しています。packer は Lua 製のパッケージマネージャで、config にそのまま Lua の関数が書けたりと Lua との親和性がとても高いです。
この設定で neovim を起動すると、:LspInstall [server name]
というコマンドが使えるようになっています。server name
は、nvim-lspconfig の CONFIG.md に記載されているものと同等です。
例えば:LspInstall tsserver
を実行すると、typescript の Language Server であるtypescript-language-server
がインストールされます。
すると、.ts
ファイルを開いたときにサーバーが起動するようになります。すると、構文エラーのあるコードを書くとその旨が表示されるようになったりします。
さらに、:Mason
コマンドを使うことで下図のようにインタラクティブなサーバーインストール画面がでます。
この設定例ではK
を押すとホバーが表示されたり、gd
を押して定義に移動などができるようになります。もちろん自分の好きなキーに変更することが可能です。
補完
これで LSP は動くようになりましたが、補完を行うには補完プラグインを入れる必要があります。Vim には様々な補完プラグインがありますが、LSP への対応度が高いnvim-cmpがおすすめです。この記事ではこれを使用します。
本体と LSP 用の補完ソースであるcmp-nvim-lsp
が分離されているため、両方インストールします。LSP 以外にも様々な補完ソースが開発されおり、
下のサンプルではバッファのキーワードを補完してくれるcmp-buffer
を入れています。
他にもコマンドラインバッファの補完をしてくれるcmp-cmdlineが非常に便利なのでおすすめです。
また、LSP がスニペットを補完候補として送ってくることがあるためスニペットプラグインは実質的に必須です。ここではvim-vsnipを使います。
-- packerのstartup内に追加
use "hrsh7th/nvim-cmp"
use "hrsh7th/cmp-nvim-lsp"
use "hrsh7th/cmp-vsnip"
use "hrsh7th/cmp-buffer"
use "hrsh7th/vim-vsnip"
-- lspconfig[server.name].setupに追加
capabilities = require("cmp_nvim_lsp").update_capabilities(vim.lsp.protocol.make_client_capabilities())
-- 最後に追加
vim.opt.completeopt = "menu,menuone,noselect"
local cmp = require"cmp"
cmp.setup({
snippet = {
expand = function(args)
vim.fn["vsnip#anonymous"](args.body)
end,
},
mapping = cmp.mapping.preset.insert({
["<C-d>"] = cmp.mapping.scroll_docs(-4),
["<C-f>"] = cmp.mapping.scroll_docs(4),
["<C-Space>"] = cmp.mapping.complete(),
["<C-e>"] = cmp.mapping.close(),
["<CR>"] = cmp.mapping.confirm({ select = true }),
}),
sources = cmp.config.sources({
{ name = "nvim_lsp" },
{ name = "vsnip" },
}, {
{ name = "buffer" },
})
})
これでこんな感じに補完が出るようになります。
他の補完プラグイン
LSP の対応具合は nvim-cmp が最強だと思われますが他にも nvim-lsp 対応の補完プラグインを紹介します。
- ddc.vim
denops.vimを使った補完プラグインで、nvim-lsp の sourceを入れれば nvim-lsp でも使えます。有名なdeoplete
の後継プラグインとのことです。
- coq_nvim
coc.nvim に似ていますが別物です。Python 製ですが neovim 専用です。
フォーマッタ/リンタ
ついでにフォーマッタ/リンタの設定もしてしまいます。
neovim でフォーマッタ/リンタを動かす方法はいろいろありますが、ここではnull-lsを紹介します。
これは様々なツールの出力を LSP の形式に変換して nvim-lsp に送るという仕組みになっています。似たようなツールにdiagnostic-languageserverやefm-langserverがありますが、null-ls
はあくまで neovim のプラグインなので外部依存がないことが利点です。
また様々なリンター/フォーマッタ用にあらかじめ設定が用意されているので簡単に設定できます。
詳しくはnull-ls のドキュメントを見てください。
これは Prettier の設定例です。npm でローカルにprettier
がインストールされていればそちらが、インストールされてなければグローバルのものが使用されます。
さらに、前述の mason.nvim でもインストールできます。mason.nvim はインストールしたパッケージの実行可能ファイルを neovim が認識するパスに加えるため特別に設定しなくても使えます。
ちなみに neovim の:terminal
の中でも mason でインストールしたものが認識されます。
use { "jose-elias-alvarez/null-ls.nvim", requires = { "nvim-lua/plenary.nvim" } }
local null_ls = require "null-ls"
null_ls.setup {
sources = {
null_ls.builtins.formatting.prettier.with {
prefer_local = "node_modules/.bin",
},
},
}
発展的な設定例です。prettier の設定ファイルがあれば prettier を使い、なければ代わりにdeno fmt
を使います。
null_ls.setup {
sources = {
null_ls.builtins.formatting.deno_fmt.with {
condition = function(utils)
return not (utils.has_file { ".prettierrc", ".prettierrc.js", "deno.json", "deno.jsonc" })
end,
},
null_ls.builtins.formatting.prettier.with {
condition = function(utils)
return utils.has_file { ".prettierrc", ".prettierrc.js" }
end,
prefer_local = "node_modules/.bin",
},
},
capabilities = common_config.capabilities,
on_attach = common_config.on_attach,
}
いろいろなプラグイン達
ここまでで基本的な設定は完了ですが、LSP と連携して開発をより便利にしてくれるプラグインを紹介します。
また、こちらのリストがかなり網羅的で LSP 関連以外のプラグインを探す時にも便利です。
LSP 拡張系
-
lspsaga.nvim
https://github.com/tami5/lspsaga.nvim
なんかいろいろリッチな表示になるプラグイン
-
lsp_signature.nvim
https://github.com/ray-x/lsp_signature.nvim
関数の引数を入力しているときにシグネチャヘルプを表示してくれます。
-
lspkind-nvim
https://github.com/onsails/lspkind-nvim
補完にアイコンを追加してくれるやつ
-
trouble.nvim
https://github.com/folke/trouble.nvim
VSCode の下部に出るエラー一覧みたいなやつです。
-
fidget.nvim
https://github.com/j-hui/fidget.nvim
言語サーバの稼働状況などを表示してくれるやつです。GIF を見ればわかります。
結構オススメです。
特定の言語系
rust-analyzer におけるexperimental/externalDocs
(docs.rs を開く機能)のように、言語サーバはそれぞれ LSP の標準ではない独自の仕様を定義していることがあります。
当然それらは nvim-lsp では扱えないのでプラグインが必要になります。
その他にも LSP では扱いきれないようなものがいろいろあったりするのでそれを扱ってくれるプラグインを以下で紹介します。
-
typescript.nvim
-
rust-tools.nvim
-
go.nvim
-
nvim-jdtls
-
flutter-tools.nvim
参考になるサイト
-
公式の LSP のドキュメント
-
r/neovim
https://www.reddit.com/r/neovim/
海外の巨大掲示板 Reddit の neovim subreddit です。新しいプラグインはよくここで宣伝されています。他にもいろいろな neovim の最新情報を知ることができます。
-
Github の dotfiles リポジトリ達
いろんな人が自分の好みの neovim のコンフィグを作って Github やらに上げています(Github で
nvim
とか検索すればたくさんでてきます)。参考になる設定が沢山あるので詰まったりしたら見てみるとよいでしょう。
最後に
この記事でやった設定ををまとめておきました。コメントとかも書き加えてあります。init.lua にこれを書いて起動するだけでセットアップされるようになっています。多分。
ついでに自分の neovim の設定のリポジトリを置いておきます。よければ参考にしてください。
それではよい neovim ライフを。
Discussion