📄

Neovim builtin LSP設定入門

commits10 min read

はじめに

Neovim には組み込みのLSPクライアントがあります。ちょっと前までは VSCode 並の開発体験のためには coc を使うのがベストな選択肢でしたが、neovim builtin lsp(以下 nvim-lsp)でもエコシステムが整ってきており、かなりいい感じの支援機能が受けられます。この記事ではその設定などについて書いていこうと思います。

環境

  • Neovim 0.5
  • Vimscript は使わず Lua で書きます

基本的な設定

必須プラグイン系

nvim-lsp は組み込みではあるものの、そのままだと扱いづらいため実質必須となるプラグインがあります。

まず、各言語用の設定を提供するnvim-lspconfigです。neovim の公式で管理されています。

また、nvim-lsp はあくまで「クライアント」なので各言語用のサーバーも必要になります。これを neovim 内からインストールできるようにするプラグインが nvim-lsp-installer です。ちなみに、nvim-lsp について調べているとよく nvim-lspinstall というものがでてくると思いますが、それは最近非推奨になりました。

次のコードは設定例です。といってもほぼ README からのコピペです。

lua/init.lua
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 "williamboman/nvim-lsp-installer"

  if packer_bootstrap then
    require("packer").sync()
  end
end)
vim.cmd([[autocmd BufWritePost init.lua source <afile> | PackerCompile]])

local on_attach = function(client, bufnr)
  local function buf_set_keymap(...) vim.api.nvim_buf_set_keymap(bufnr, ...) end

  local opts = { noremap=true, silent=true }
  buf_set_keymap("n", "gD", "<cmd>lua vim.lsp.buf.declaration()<CR>", opts)
  buf_set_keymap("n", "gd", "<cmd>lua vim.lsp.buf.definition()<CR>", opts)
  buf_set_keymap("n", "K", "<cmd>lua vim.lsp.buf.hover()<CR>", opts)
  buf_set_keymap("n", "gi", "<cmd>lua vim.lsp.buf.implementation()<CR>", opts)
  buf_set_keymap("n", "<C-k>", "<cmd>lua vim.lsp.buf.signature_help()<CR>", opts)
  buf_set_keymap("n", "<space>wa", "<cmd>lua vim.lsp.buf.add_workspace_folder()<CR>", opts)
  buf_set_keymap("n", "<space>wr", "<cmd>lua vim.lsp.buf.remove_workspace_folder()<CR>", opts)
  buf_set_keymap("n", "<space>wl", "<cmd>lua print(vim.inspect(vim.lsp.buf.list_workspace_folders()))<CR>", opts)
  buf_set_keymap("n", "<space>D", "<cmd>lua vim.lsp.buf.type_definition()<CR>", opts)
  buf_set_keymap("n", "<space>rn", "<cmd>lua vim.lsp.buf.rename()<CR>", opts)
  buf_set_keymap("n", "<space>ca", "<cmd>lua vim.lsp.buf.code_action()<CR>", opts)
  buf_set_keymap("n", "gr", "<cmd>lua vim.lsp.buf.references()<CR>", opts)
  buf_set_keymap("n", "<space>e", "<cmd>lua vim.lsp.diagnostic.show_line_diagnostics()<CR>", opts)
  buf_set_keymap("n", "[d", "<cmd>lua vim.lsp.diagnostic.goto_prev()<CR>", opts)
  buf_set_keymap("n", "]d", "<cmd>lua vim.lsp.diagnostic.goto_next()<CR>", opts)
  buf_set_keymap("n", "<space>q", "<cmd>lua vim.lsp.diagnostic.set_loclist()<CR>", opts)
  buf_set_keymap("n", "<space>f", "<cmd>lua vim.lsp.buf.formatting()<CR>", opts)
end

local lsp_installer = require("nvim-lsp-installer")
lsp_installer.on_server_ready(function(server)
    local opts = {}
    opts.on_attach = on_attach

    server:setup(opts)
end)

ここではプラグインマネージャとしてpacker.nvimを使用しています。packer は Lua 製のパッケージマネージャで、config にそのまま Lua の関数が書けたりと Lua との親和性がとても高いです。完全に neovim に乗り換えるという人にはおすすめです。

こんな感じの設定を書き neovim を起動すると、:LspInstall [server name]というコマンドが使えるようになっています。server nameは、nvim-lspconfig の CONFIG.md に記載されているものと同等です。
例えば:LspInstall tsserverを実行すると、typescript の Language Server であるtypescript-language-serverがインストールされます。
すると、.tsファイルを開いたときなどにサーバーが起動するようになります。上の設定例では例えばノーマルモードでKでホバーを出したり、gdで定義に移動することができるようになります。

さらに、:LspInstallInfoコマンドを使うことで下図のようにインタラクティブなサーバーインストール画面がでます。

この設定例ではKを押すとこのようにホバーが表示されます。
また、構文エラーのあるコードを書くとその旨が表示されるようになります。

ちなみにtypescript-language-servertypescriptモジュールのtsserverを内部で利用しているため、npm で typescript をインストールしておかないと使えません。グローバルにインストールしておけば自動でフォールバックされるみたいです。

補完

さて、これで LSP 自体は動かせたわけですが、実はこのままだと補完が効きません。vim では他に補完プラグインを入れる必要があるからです。そして補完プラグインをいろいろ選べるのも vim のメリットの一つではありますが、LSP への対応度が高いnvim-cmpがおすすめです。

nvim-cmpではコアと lsp 用の補完ソースが分離されているため、両方インストールする必要があります。nvim-lsp 以外にも様々な補完ソースが開発されています。下のサンプルではバッファのキーワードを補完してくれるcmp-bufferを入れています。
また、ここでは導入していませんが、コマンドラインバッファの補完をしてくれるcmp-cmdlineが非常に便利なのでおすすめです。

また、スニペットプラグインとして、vim-vsnipを使います。スニペットプラグインは実質的に必須なので何か入れておきましょう。

init.lua
-- packerのstartup内に追加
use "hrsh7th/nvim-cmp"
use "hrsh7th/cmp-nvim-lsp"
use "hrsh7th/cmp-vsnip"
use "hrsh7th/cmp-buffer"

use "hrsh7th/vim-vsnip"

-- lsp_installer.on_server_readyに追加
opts.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 = {
    ["<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 が最強だと思われますが対応しているプラグインは他にもあるので紹介します

  • ddc.vim

https://github.com/Shougo/ddc.vim

denops.vimを使った補完プラグインで、nvim-lsp の sourceを入れれば nvim-lsp でも使えます。かの有名なdeopleteの後継みたいです

  • coq_nvim

https://github.com/ms-jpq/coq_nvim

coc.nvim に似ていますが別物です。Python 製ですが neovim 専用です。

フォーマッタ/リンタ

ついでにフォーマッタ/リンタの設定もしてしまいます。
neovim でフォーマッタを動かす方法はいろいろありますが、ここではnull-lsを紹介します。
これは様々なツールの出力を LSP の形式に変換して nvim-lsp に送るという仕組みになっています。似たようなツールにdiagnostic-languageserverefm-langserverがありますが、null-lsはあくまで neovim のプラグインなので外部依存がないことが利点です。
また様々なリンター/フォーマッタ用にあらかじめ設定が用意されているので簡単に設定できます。
詳しくはnull-ls のドキュメントを見てください。

これは Prettier の設定例です。あらかじめグローバルにprettierをインストールしておく必要があります。

init.lua
use { "jose-elias-alvarez/null-ls.nvim", requires = { "nvim-lua/plenary.nvim" } }

local nullls = require "null-ls"
nullls.setup {
  sources = {
    nullls.builtins.formatting.prettier,
  },
}

サーバーによってはそれ自体がフォーマット機能を持っているものがあります。それを無効化するにはon_attach

client.resolved_capabilities.document_formatting = false

を追加します。

prettierと同様に、eslintもnull-lsで使用できますが、すごく重いので、LSP版を使うことをお勧めします。nvim-lsp-installerからインストールできます。

nvim-lsp の(まだ)ダメなところ

これから改善されると思うので「まだ」ですが、成熟しきっていないため不満点もあります。

  • 設定が面倒くさい
    この記事では簡単なことしか書いていませんが、例えばtsserverdenolsの出し分けのように複雑なことをしようとすると途端に難しくなります。
  • プラグインがよく壊れる
    仕様がカジュアルに変更されるため、よく壊れます。実際最近の neovim 0.5.1 で LSP 関連の breaking change が入ったため、しばらく更新されていない LSP 関連のプラグインは動かない可能性が高いです。
    また、プラグインの作者が nightly を使っている場合、逆に安定板だとうまく動かないということも稀にあります。

いろいろなプラグイン達

ここまでで基本的な設定は完了ですが、LSP と連携して開発をより便利にしてくれるプラグインを紹介します。

LSP 拡張系

特定の言語系

nvim-lsp を利用した特定の言語やフレームワーク向けのプラグインも開発され始めています。

参考になるサイト

  • 公式の LSP のドキュメント

    https://neovim.io/doc/user/lsp.html

    公式ドキュメントを見るのは大事。もちろん:h lspで neovim 内から見ることもできます。

  • r/neovim

    https://www.reddit.com/r/neovim/

    海外の巨大掲示板 Reddit の neovim 板です。新しいプラグインはよくここで宣伝されています。他にもいろいろな neovim の最新情報を知ることができます。

最後に

この記事でやった設定ををまとめておきました。コメントとかも書き加えてあります。init.lua にこれを書いて起動するだけでセットアップされるようになっています。多分。

https://github.com/nazo6/zenn/blob/main/examples/c2f16b07798bab/init.lua

ついでに自分の neovim の設定のリポジトリを置いておきます。よければ参考にしてください。

https://github.com/nazo6/nvim

それではよい neovim ライフを。

GitHubで編集を提案

Discussion

ログインするとコメントできます