icon

nazo6.dev

一覧に戻る
2021/8/21 2024/3/14 15 min read

Neovim builtin LSP設定入門

この記事はZennにも投稿しています

Neovim 0.11においてLSP関連の大きな変更があり、この記事の内容は若干古くなっています(古いというだけでこの記事の内容自体は有効であり、コードも動くはずです)。 最新の設定を試したいという方は記事の更新をお待ちください

#はじめに

Neovim には組み込みのLSPクライアントがあります。少し前までは VSCode 並の開発体験を得るためにはcoc.nvimを使うのがベストな選択肢でしたが、neovim builtin lsp(以下 nvim-lsp)でもかなりエコシステムが整備されており、VSCode 並の開発体験が得られるようになっています。この記事ではそんな nvim-lsp の設定の基本的なところを説明します。

#環境

  • Windows でも MacOS でも Linux でも動くはずです。
  • Neovim stable 最新版もしくは nightly
  • git コマンド
  • その他 Mason から色々な物をインストールして使う際には:checkhealth masonで指定されたコマンドが必要となることがあります。

#基本的な設定

#必須プラグイン系

nvim-lsp は組み込みではあるものの、そのままだと扱いづらいため以下のプラグインが使用されることが多いです。

まず、各言語用の設定を提供するnvim-lspconfigです。neovim の公式で管理されています。LSP プロトコル自体はどの言語でも共通ですが LSP サーバーを起動するためのコマンドラインオプションやどういう場合にどの言語サーバーを起動させるべきか(例えばpackage.jsonがある場合は Typescript の言語サーバーが起動してほしいなど)はそれぞれ違うのでこのプラグインが必要になります。

次に mason.nvim ですが、これは様々な外部依存パッケージを neovim 内からインストールできるようにするプラグインです。そして mason-lspconfig は mason.nvim でインストールした LSP サーバーを使うために必要です。

Neovim界で最も人気であろうパッケージマネージャのlazy.nvimを使った設定例が以下になります。

-- lazy.nvimのインストール
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",
    "--single-branch",
    "https://github.com/folke/lazy.nvim.git",
    lazypath,
  }
end
vim.opt.runtimepath:prepend(lazypath)
 
-- lazy.nvimを使ってプラグインをインストール
require("lazy").setup({
  { "neovim/nvim-lspconfig" },
  { "williamboman/mason.nvim" },
  { "williamboman/mason-lspconfig.nvim" },
}, {})
 
-- LSPサーバアタッチ時の処理
vim.api.nvim_create_autocmd("LspAttach", {
  callback = function(ctx)
    local set = vim.keymap.set
    set("n", "gD", "<cmd>lua vim.lsp.buf.declaration()<CR>", { buffer = true })
    set("n", "gd", "<cmd>lua vim.lsp.buf.definition()<CR>", { buffer = true })
    set("n", "K", "<cmd>lua vim.lsp.buf.hover()<CR>", { buffer = true })
    set("n", "gi", "<cmd>lua vim.lsp.buf.implementation()<CR>", { buffer = true })
    set("n", "<C-k>", "<cmd>lua vim.lsp.buf.signature_help()<CR>", { buffer = true })
    set("n", "<space>wa", "<cmd>lua vim.lsp.buf.add_workspace_folder()<CR>", { buffer = true })
    set("n", "<space>wr", "<cmd>lua vim.lsp.buf.remove_workspace_folder()<CR>", { buffer = true })
    set("n", "<space>wl", "<cmd>lua print(vim.inspect(vim.lsp.buf.list_workspace_folders()))<CR>", { buffer = true })
    set("n", "<space>D", "<cmd>lua vim.lsp.buf.type_definition()<CR>", { buffer = true })
    set("n", "<space>rn", "<cmd>lua vim.lsp.buf.rename()<CR>", { buffer = true })
    set("n", "<space>ca", "<cmd>lua vim.lsp.buf.code_action()<CR>", { buffer = true })
    set("n", "gr", "<cmd>lua vim.lsp.buf.references()<CR>", { buffer = true })
    set("n", "<space>e", "<cmd>lua vim.lsp.diagnostic.show_line_diagnostics()<CR>", { buffer = true })
    set("n", "[d", "<cmd>lua vim.lsp.diagnostic.goto_prev()<CR>", { buffer = true })
    set("n", "]d", "<cmd>lua vim.lsp.diagnostic.goto_next()<CR>", { buffer = true })
    set("n", "<space>q", "<cmd>lua vim.lsp.diagnostic.set_loclist()<CR>", { buffer = true })
    set("n", "<space>f", "<cmd>lua vim.lsp.buf.formatting()<CR>", { buffer = true })
  end,
})
 
-- プラグインの設定
require("mason").setup()
require("mason-lspconfig").setup()
require("mason-lspconfig").setup_handlers {
  function(server_name)
    require("lspconfig")[server_name].setup {}
  end,
}

このスニペットについて簡単に説明します。まず、上半分はプラグインマネージャの設定です。プラグインマネージャ自体と上で説明したプラグインをインストールします。 次にlocal on_attachで始まる行では、LSPサーバーがアタッチ(つまりvimのバッファと紐付けられて起動)した際に実行される処理を記述しています。この中ではLSPサーバーに関係するキーマップを設定することが多いです。この設定例ではKで「ホバー」、gdで「定義へ移動」などを設定しています。もちろん自分が使いやすいように自由に変更することができます。 そして残った部分ではプラグインを設定します。ここでは、mason.nvimでインストールした言語サーバが自動的に起動するように設定しています。

ではinit.luaを書いて neovim を起動してみましょう。すると、lazy.nvimが自動でプラグインをインストールしてくれるはずです。 インストール後には、mason.nvimにより:LspInstall [server name]というコマンドが使えるようになっています。 また、:Masonコマンドを使うことで下図のようにインタラクティブなサーバーインストール画面を使うこともできます。

では試しに:LspInstall lua_lsを実行してLuaの言語サーバをインストールしてみましょう。コマンド実行後にインストール画面が表示され、しばらくすればインストールが完了します。 完了後、一旦Neovimを再起動してinit.luaを開くと自動で言語サーバが起動するはずです。LSPによる診断が表示され、上でも説明したようにKでホバーを出すこともできます。

他にも LSP 経由で使える機能はここでは説明しきれないほど豊富にあります。:h lspに詳細な情報が載っているので読んでみると良いでしょう。

#補完

これで LSP は動くようになりましたが、補完を行うには補完プラグインを入れる必要があります。Vim には様々な補完プラグインがありますが、LSP への対応度が高いnvim-cmpがおすすめです。この記事ではこれを使用します。

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

また、LSP がスニペットを補完候補として送ってくることがあるためスニペットプラグインは実質的に必須です。ここではLuaSnipを使います。

-- lazy.nvimのプラグインテーブル内に追加
require("lazy").setup({
  { "L3MON4D3/LuaSnip" },
 
  { "hrsh7th/nvim-cmp" },
  { "hrsh7th/cmp-nvim-lsp" },
  { "hrsh7th/cmp-buffer" },
  { "saadparwaiz1/cmp_luasnip" },
}, {})
 
-- lspのハンドラーに設定
capabilities = require("cmp_nvim_lsp").default_capabilities(),
 
-- lspの設定後に追加
vim.opt.completeopt = "menu,menuone,noselect"
 
local cmp = require"cmp"
cmp.setup({
  snippet = {
    expand = function(args)
      require("luasnip").lsp_expand(args.body)
    end,
  },
  mapping = cmp.mapping.preset.insert({
    ["<C-p>"] = cmp.mapping.select_prev_item(),
    ["<C-n>"] = cmp.mapping.select_next_item(),
    ["<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 = "luasnip" },
  }, {
    { name = "buffer" },
  })
})

このように補完が出るようになりました!

#他の補完プラグイン

nvim-cmp以外にもnvim-lspで仕様できる補完プラグインがあるのでご紹介します。

  • ddc.vim

denops.vimを使った補完プラグインで、nvim-lsp の sourceを入れれば nvim-lsp でも使えます。

  • coq_nvim

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

#フォーマッタ/リンタ

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

これは Prettier の設定例です。npm でローカルにprettierがインストールされていればそちらが、インストールされてなければグローバルのものが使用されます。 さらに、前述の mason.nvim で prettier をインストールすることもできます。mason.nvim はインストールしたパッケージの実行可能ファイルを neovim が認識するPATHに加えるため特別に設定しなくても使えます。 ちなみに neovim の:terminalの中でも mason でインストールしたものはパスが通っており、実行することができます。

{ "nvimtools/none-ls.nvim", dependencies = { "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を使います。さらにdenoの設定ファイルがあれば null-ls 経由のdeno fmtではなく deno LSP の組み込みフォーマットを使います。

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,
}

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

#いろいろなプラグイン達

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

また、こちらのリストがかなり網羅的で LSP 関連以外のプラグインを探す時にも便利です。 このリストの作成者さんの記事もありますのでこちらもどうぞ。

#LSP 拡張系

#特定の言語用のやつ

rust-analyzer におけるexperimental/externalDocs(docs.rs を開く機能)のように、言語サーバはそれぞれ LSP の標準にはない独自の仕様を定義していることがあります。 当然それらは nvim-lsp では扱えないのでプラグインが必要になります。 その他にも LSP では扱いきれないようなものがいろいろあったりするのでそれを扱ってくれるプラグインを以下で紹介します。

#参考になるサイト

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

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

  • r/neovim

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

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

  • Github の dotfiles リポジトリ達

    いろんな人が自分の好みの neovim のコンフィグを作って Github やらに上げています(Github でnvimとか検索すればたくさんでてきます)。参考になる設定が沢山あるので詰まったりしたら見てみるとよいでしょう。

  • https://neovim-mine.vercel.app/

    手前味噌で申し訳ありませんが、Neovim のプラグインを見つけやすくするサイトを作ったので紹介しておきます。 先ほども挙げた https://github.com/yutkat/my-neovim-pluginlistAwesome Neovimのデータを使用させてもらい、見やすい形(個人の感想)にまとめたサイトです。

#最後に

この記事でやった設定ををまとめておきました。コメントとかも書き加えてあります。

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

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

Share this article:
一覧に戻る

関連記事

2023/5/1

#tech/software/neovim
blog

Neovimを再起動するコマンドを作ったら結構よかった

Neovim盆栽をしていると設定をリロードしたい時が結構というかかなりあります。Vimscriptであればsource ~/.vimrcとすればまあ大体うまくいっていた気がするのですがLuaではそうもいきません。 Luaのrequireのキャッシュを消してやればもう一度読み込めるとかは言われていますが世の中のLuaプラグインはsetupを2回以上呼んだりするとおかしくなったりする物が大抵なのでこの方法でもあんまりうまくいきません。

Read Article

2021/3/28

2021/10/23

#tech/lang/js-ts#tech/software/neovim
blog

Typescriptでneovimの設定を書く!

vim を使い始めて 2 週間ほどたったある日、せっかく neovim を使っているんだし設定が少ない今のうちに init.vim を init.lua に書き換えようと思いこちらの文章を読んでいたところ、最後にこんなものがあるのに気づきました。

Read Article

2021/3/14

2021/3/15

#tech/software/neovim
memo

Vim初心者がNeovimを試す

VSCodeを使っているとコマンド操作できることでも覚えるのがめんどくさくてついマウスでポチポチしてしまうので自分を律したい

Read Article

2024/1/31

#tech/software/neovim
memo

Windows Terminal上のNeovimでundercurlを表示する(wslのみ)

最近ついにWindows Terminalでもundercurlの表示ができるようになった(これを書いた時点ではプレビュー版のみ)。

Read Article

2023/10/31

#tech/software/neovim
blog

lemonadeでssh先のneovimとクリップボードを共有

lemonadeを使えばTCP通信を用いてクリップボードを共有できます。

Read Article

2021/9/4

2023/4/28

#tech/software/neovim
blog

neovim luaのパフォーマンス計測方法

最初は下の方法2を使用していましたが、profile.nvimという素晴らしいプラグインが出ていたので今はこれを使用するのがおすすめです。

Read Article

2021/5/15

2022/5/7

#tech/software/neovim
blog

nvim-lspでtsconfig.jsonとかの補完をする方法(JSON schema)

SchemaStore.nvim というプラグインが出てきました。 基本的にこの記事で説明されていることをやってくれてさらに定期的に更新されるのでこのプラグインを使うのがおすすめです。

Read Article

2023/8/22

#tech/software/neovim
memo

telescope.nvim拡張の作り方

telescope.nvimの拡張(ソース)の作り方のいい文献があまり見つからなかったのでメモ。

Read Article

© 2025 nazo6. All rights reserved.