😽

Neovim builtin LSP設定入門

10 min read

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

環境

  • Linux (Windowsだと後述のlspinstallというプラグインが使えないのでおすすめできません。LSPを使うこと自体はできます。)
    ※Windowsで使いたいひとは下の追記を参照してください。
  • Neovim 0.5
  • Vimscriptは使わずLuaで書きます

基本的な設定

必須プラグイン系

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

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

また、nvim-lspはあくまで「クライアント」なので各言語用のサーバーも必要になります。これをneovim内からインストールできるようにするプラグインがnvim-lspinstallです。前述の通り、このプラグインはLinuxのみ対応です。一応Windows対応のPRは出ていますがマージされるかは分かりません。

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

lua/init.lua
require('packer').startup(function()
  use 'wbthomason/packer.nvim'
  use 'neovim/nvim-lspconfig'
  use {'kabouzeid/nvim-lspinstall',
    config = function ()
      require 'lsp'
    end
  }
end)
vim.cmd([[autocmd BufWritePost init.lua source <afile> | PackerCompile]])
lsp.lua
-- Use an on_attach function to only map the following keys
-- after the language server attaches to the current buffer
local on_attach = function(client, bufnr)
  local function buf_set_keymap(...) vim.api.nvim_buf_set_keymap(bufnr, ...) end
  local function buf_set_option(...) vim.api.nvim_buf_set_option(bufnr, ...) end
  --Enable completion triggered by <c-x><c-o>
  buf_set_option('omnifunc', 'v:lua.vim.lsp.omnifunc')
  -- Mappings.
  local opts = { noremap=true, silent=true }
  -- See `:help vim.lsp.*` for documentation on any of the below functions
  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 function setup_servers()
  require'lspinstall'.setup()
  local servers = require'lspinstall'.installed_servers()
  for _, server in pairs(servers) do
    require'lspconfig'[server].setup{
      on_attach = on_attach,
    }
  end
end

setup_servers()

-- Automatically reload after `:LspInstall <server>` so we don't have to restart neovim
require'lspinstall'.post_install_hook = function ()
  setup_servers() -- reload installed servers
  vim.cmd("bufdo e") -- this triggers the FileType autocmd that starts the server
end

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

こんな感じの設定を書きneovimを起動します。trouble:LspInstall [server name]というコマンドが使えるようになっています。
例えば:LspInstall typescriptを実行してみます。そうすれば

Successfully installed language server for typescript

と出るはずです。これで.tsファイルを開いたときなどにサーバーが起動するようになります。上の設定例では例えばノーマルモードでKでホバーを出したり、gdで定義に移動することができるようになります。
また、構文エラーのあるコードを書くとその旨が表示されます。

カラースキームなどを設定していない状態で上のように設定してKを押すとこのように気持ち悪い色のホバーが表示されます。

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

追記

先述のnvim-lspinstallに出されていたPRを元にフォークしてWindowsでも大体のLSPを使えるようにしたlspinstall.nvimを作ってみました(ネーミングセンス・・・)。メンテするかはわかりませんが一応動くようにはなっているはずなのでWindowsで使いたい人はよければ使ってみてください。

https://github.com/nazo6/lspinstall.nvim

また、一応他にもLSPインストーラプラグインはあるので探してみても良いでしょう。

補完

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

ここでは一応stableなnvim-compeを使いますが、nvim-cmpが正式版になり次第nvim-compeは非推奨となるようなので今のうちからnvim-cmpを使うのもアリです。
とか書いた数日後にnvim-compeが非推奨になっていたのでnvim-cmpを利用することをおすすめします

nvim-compe (deprecated)

init.lua
-- packerのstartup内に追加
use { "hrsh7th/nvim-compe",
  config = function()
    require("compe-settings")
  end
}
lua/compe-settings.lua
vim.o.completeopt = "menuone,noselect"

require'compe'.setup {
  enabled = true;
  autocomplete = true;
  debug = false;
  min_length = 1;
  preselect = 'enable';
  throttle_time = 80;
  source_timeout = 200;
  resolve_timeout = 800;
  incomplete_delay = 400;
  max_abbr_width = 100;
  max_kind_width = 100;
  max_menu_width = 100;
  source = {
    path = true;
    buffer = true;
    calc = true;
    nvim_lsp = true;
    nvim_lua = true;
  };
}

vim.cmd[[
inoremap <silent><expr> <C-Space> compe#complete()
inoremap <silent><expr> <CR>      compe#confirm('<CR>')
inoremap <silent><expr> <C-e>     compe#close('<C-e>')
inoremap <silent><expr> <C-f>     compe#scroll({ 'delta': +4 })
inoremap <silent><expr> <C-d>     compe#scroll({ 'delta': -4 })
]]

相変わらずカラースキームを適用していないので色がキモいですがこれでこんな感じに補完が出るようになりました!

他の補完プラグイン

LSPの対応具合は前述の通りcompe/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で書かれています。「クソ速い」補完プラグインらしいです。パッと見かなり高機能そうな感じがします。

  • completion-nvim

https://github.com/nvim-lua/completion-nvim

使ったことはないのですがRedditでは非常に評判がよろしくないです。これを使ってる記事は古いものだという判断材料になるかもしれません。

フォーマット

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

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

init.lua
-- "lsp.lua"が読みこまれるより前の位置に追加
use { "jose-elias-alvarez/null-ls.nvim", requires = { "nvim-lua/plenary.nvim" } }
lua/lsp.lua
-- 追記
local nullls = require "null-ls"
nullls.config {
  sources = {
    nullls.builtins.formatting.prettier,
  },
}
require("lspconfig")["null-ls"].setup {}

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

client.resolved_capabilities.document_formatting = false

を追加します。

https://github.com/jose-elias-alvarez/null-ls.nvim/blob/main/doc/BUILTINS.md

いろいろなプラグイン達

ここまでで基本的には完了ですが、ここからはnvim-lspと組み合わせて使える便利なプラグインを備忘録を兼ねて書いていきます。

拡張系

  • lspsaga.nvim

https://github.com/glepnir/lspsaga.nvim
なんかいろいろリッチな表示になるプラグイン
  • lsp_signature.nvim

https://github.com/ray-x/lsp_signature.nvim

VSCodeでできる、関数を引数を入力しているときに表示してくれるやつです。

  • lspkind-nvim

https://github.com/onsails/lspkind-nvim

補完にアイコンを追加してくれるやつ

  • trouble.nvim

https://github.com/folke/trouble.nvim

VSCodeの下部に出るエラー一覧みたいなやつです。超便利。

特定の言語系

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

  • flutter-tools.nvim

https://github.com/akinsho/flutter-tools.nvim

neovimでflutterを使うなら前はcoc一択だったと思いますがこのプラグインがあればcocじゃなくてもいいかなと思えるプラグインです。

  • nvim-lsp-ts-utils

https://github.com/jose-elias-alvarez/nvim-lsp-ts-utils

まだ使ってないですが結構便利そう。ローカルのPrettierとかESLintを使うとかできるみたいです。

  • rust-tools.nvim

https://github.com/simrat39/rust-tools.nvim

Rustです

  • go.nvim

https://github.com/ray-x/go.nvim

Goです

  • nvim-jdtls

https://github.com/mfussenegger/nvim-jdtls

Javaです

参考になるサイト

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

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

とりあえず迷ったらここを見ましょう

  • r/neovim

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

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

最後に

一応設定をまとめたgistを作っておきました。packer.nvimをインストールすればこれだけで一応使えるようになっているはずです。多分。

https://gist.github.com/nazo6/f1d013e52250e21e56e08d19c757409d

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

https://github.com/nazo6/nvim

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

Discussion

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