🗂️

[Neovim]Coc.nvim+init.vimからBuiltin LSP+init.luaに移行しました💪

2022/10/04に公開

Coc.nvim+init.vimからBuiltin LSP+init.luaへ

この記事の概要


BuiltinLSP+Lspsaga.nvimでドキュメントをホバーさせているところ
2022年10月現在、NeovimにおいてモダンなIDEに近い様々なコーディング支援機能(Ex. 言語ごとの入力補完、定義ジャンプ、コード診断など)を利用するには、

  • Vim/NeovimをまるごとIDE化するプラグインであるCoc.nvimを導入する方法
  • Neovimに組み込まれたLSP(Language Server Protocol)クライアント機能を用いて、補完(Completion)、コード診断(Diagnostics)、リンター/フォーマッター(Linter/Formatter)の各要素につき、個別にプラグインを導入してカスタマイズする方法

の2通りがあります。

今回は、Neovim組み込みのBuitin LSPを用いた方法で、Neovimに様々なコーディング支援機能を導入します!

NeovimとLSPのエコシステムについて

初めまして!食パン🍞と申します🌵
普段は主にフロントエンドよりのコーディングをしている者です。

私は普段のコーディングにおいて、メインエディタとしてNeovimを用いています。
Neovim/Vimは、その独特な操作方法から慣れるまで扱いが難しいですが、一度馴染むと非常に効率よくテキスト編集ができるため、日々のコーディングには欠かせません。

ところで、近年のNeovimでは、Microsoftが2016年に発表したLSP(Language Server Protocol)を活用した、コーディング支援機能に関するエコシステムの発展が著しいものとなっています。
NeovimにはデフォルトでLSPクライアント(Language Serverからの情報を受けとり、コーディング支援に活用できる機能)が組み込まれており、この機能を利用した様々なプラグインが登場しています。

数年前までは、おなじくLSPなどを活用しつつも、一つのプラグインでNeovim/Vimを丸ごとVSCodeのようにしてしまうCoc.nvimをイントールするのがメジャーでした。
しかし、上記のようなエコシステムの著しい発展によって、最近のNeovim界隈においては、NeovimのBuiltinLSPを活用した各種プラグインを導入してセッティングすることが大きなトレンドとなっています。

そこで今回は、Coc.nvimを導入していたNeovimに、(代わりに)BuiltinLSPを活用したプラグインを複数導入するとともに、合わせてVimscirptで記述していた設定ファイルをLuaによって書き換えたいと思います(Neovimの設定ファイルはLua言語で記述することができます)。

OS環境など

OS:ZorinOS Core16(UbuntuベースのLinux)
NeovimはHomebrewで、brew install neovim --HEADでインストール
Neovim Ver.0.8.0(nightly)
BuiltinLSPを活用したプラグインの多くは、最新版のNeovimを要求することが多いので、Nightlyを導入されることをおすすめします🌃


◯init.luaと設定ファイルの置き場

Neovimにおいて、ほぼ全ての設定ファイル類はLinux/Macの場合~/.config/nvim以下のディレクトリに配置します。
(省略)nvim/直下のディレクトリにinit.luaを設置すると、Neovimは起動時にinit.luaを最初に読み込み、そこから設定ファイル群を読み込みます。

このあたりの詳細や、Vimscriptによる記述をLuaに書き換える際の具体的な記述の仕方については、下記の記事が参考になると思いますのでご参照ください🌵
https://zenn.dev/slin/articles/2020-11-03-neovim-lua2

ディレクトリの構成とファイルの配置について

Neovimは、~/.config/nvim直下にあるinit.lua最初に読み込んだあと、次のディレクトリにある.luaファイルおよび.vimファイルを自動的に読み込みます。
そのため、init.luaに全ての設定を書くこともできますし、ディレクトリ別に設定ファイルを分割して配置することもできます。
今回は、下記のようなディレクトリ構成を前提に配置しています。

├── README.md
├── after
│   └── plugin
│       ├── autopairs.rc.lua
│       ├── cmp.rc.lua
│       ├── colorizer.rc.lua
│       ├── colorscheme.rc.lua
│       ├── comment.rc.lua
│       ├── fern.rc.lua
│       ├── fidget.rc.lua
│       ├── git.rc.lua
│       ├── gitsigns.rc.lua
│       ├── indentline.rc.lua
│       ├── lualine.rc.lua
│       ├── prettier.rc.lua
│       ├── telescope.rc.lua
│       ├── treesitter.rc.lua
│       └── wintab.rc.lua
├── init.lua
├── lua
│   ├── base.lua
│   └── plugins.lua
└── plugin
    ├── lspconfig.lua
    ├── lspsaga.rc.lua
    ├── null-ls.rc.lua
    └── packer_compiled.lua

◯プラグイン管理のためにPacker.nvimを導入

https://github.com/Nyowa450/Neovim-with-lua
私の最終的な設定ファイル類はこちらです◎

ここから具体的に各種プラグインを導入していきます💪

まず最初に導入するのは、プラグインを管理するためのプラグイン、すなわちプラグインマネージャーです。
メジャーなプラグインマネージャーにはいくつかありますが、今回はLua言語製でinit.luaとの相性が良いPacker.nvimを利用します。
基本的な使い方はリポジトリのドキュメントに丁寧に解説されていますが、日本語で解説されたものとしては下記の記事が大変参考になります。
https://qiita.com/delphinus/items/8160d884d415d7425fcc

◯LSPのための基本プラグインを導入

Packerを用いて、次のプラグインをインストールします。
これらのプラグインは、2022年現在においてBuiltinLSPを(便利に)利用する上で事実上必須となります。

  • nvim-lspconfig
    • NeovimのLSPクライアントとLanguage Serverがやり取りするための各種設定をまとめたプラグインです。LSPを用いる上では事実上必須となります
  • mason.nvim
    • mason.nvimは、LanguageServerやLinter/FormatterをNeovim上で簡単にまとめて管理(インストール・アンインストール・導入済みのサーバーなどに関する情報の表示など)できるようにしてくれるプラグインです。nvim-lspconfigだけでも、個別にインストールしたLanguage Serverなど(例:npm install -g typescript typescript-language-server)を利用できますが、mason.nvimを用いることでグラフィカルに管理することができます👍(オヌヌメ)

      画像は公式リポジトリより
  • mason-lspconfig.nvim
    • mason.nvimnvim-lspconfigを連携させて、よりセットアップを簡単にするものです。コマンドモードで:LspInstallなどを実行する際、意識すること無くmason.nvimnvim-lspconfigを統合してくれます(オヌヌメ)
lua/plugins.lua
use {
    "williamboman/mason.nvim",
    "williamboman/mason-lspconfig.nvim",
    "neovim/nvim-lspconfig",
}

これら3つのプラグインをインストール(:PackerInstall)し、mason-lspconfig.nvimのマニュアルに記載されてるDYNAMIC SERVER SETUPの項目などを読みつつlspconfig.luaにLSPのセットアップのためのコードを記述します。

plugin/lspconfig.lua
local on_attach = function(client, bufnr)

  -- LSPが持つフォーマット機能を無効化する
  -- →例えばtsserverはデフォルトでフォーマット機能を提供しますが、利用したくない場合はコメントアウトを解除してください
  --client.server_capabilities.documentFormattingProvider = false
  
  -- 下記ではデフォルトのキーバインドを設定しています
  -- ほかのLSPプラグインを使う場合(例:Lspsaga)は必要ないこともあります

  local set = vim.keymap.set
  set("n", "gd", "<cmd>lua vim.lsp.buf.definition()<CR>")
  set("n", "K", "<cmd>lua vim.lsp.buf.hover()<CR>")
  set("n", "<C-m>", "<cmd>lua vim.lsp.buf.signature_help()<CR>")
  set("n", "gy", "<cmd>lua vim.lsp.buf.type_definition()<CR>")
  set("n", "rn", "<cmd>lua vim.lsp.buf.rename()<CR>")
  set("n", "ma", "<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>")

end

-- 補完プラグインであるcmp_nvim_lspをLSPと連携させています(後述)
local capabilities = require("cmp_nvim_lsp").default_capabilities()
-- (2022/11/1追記):cmp-nvim-lspに破壊的変更が加えられ、
-- local capabilities = require('cmp_nvim_lsp').update_capabilities(
--  vim.lsp.protocol.make_client_capabilities()
-- )
-- ⇑上記のupdate_capabilities(...)の関数は非推奨となり、代わりにdefault_capabilities()関数が採用されました。日本語情報が少ないため、念の為併記してメモしておきます。

-- この一連の記述で、mason.nvimでインストールしたLanguage Serverが自動的に個別にセットアップされ、利用可能になります
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, --keyバインドなどの設定を登録
      capabilities = capabilities, --cmpを連携
    }
  end,
}

問題なくプラグインが設定できていれば、Neovimのコマンドモードで:Masonと入力するとmason.nvimが立ち上がり、LSPなどの管理画面が立ち上がります。

画像は公式リポジトリより
欲しいLanguage Server(例えばTypescriptを書く人ならtypescript-language-server)を探してカーソルを合わせてiを押すか、コマンドモードで:LspInstall tsserverとすると簡単にインストールすることがきます。欲しいサーバーを入れちゃいましょう!
(なお、tsserverを使う場合はglobalにtypescriptがインストールされている必要があるので、ご注意ください)
2022/12/04:上記は誤りでした…(TOT) mason.nvim経由でtypescript-language-serverをインストールすれば、globalにtypescript本体を入れずとも問題なく動作します。

必要なサーバーをインストールしたあと、それぞれのLSPに対応した各種拡張子のファイルを開くと、自動的にサーバーが起動します。
また、その状態で:LspInfoと入力すると、現在起動しているactiveなサーバーの一覧や詳細が確認できるので、新しいサーバーを導入したときは正常に動作しているか確認すると確実です。

◯補完プラグインを導入

これでLSPの設定そのものは完了しましたが、この状態でコードを入力しても補完候補の表示は行われません。サーバーから送られてきた補完候補等を取得し、入力に応じた補完機能を動作させるには、補完プラグイン(Completion Plugin)とスニペットプラグイン(Snippet Plugin)が別途必要となります

補完プラグインやスニペットプラグインには様々な組み合わせがありますが、今回は比較的メジャーな次のプラグイン群をインストールします。

  • nvim-cmp
    • Luaで作られたNeovim用の補完エンジンです。補完エンジン本体であるnvim-cmpと、補完候補を取得するソース(source)を設定するcmp-◯◯シリーズのプラグインから構成されます。
    • cmp-nvim-lsp LSPが提案する補完候補をソースとして取得します。
    • cmp-buffer バッファの内容をソースとして補完候補を取得します。
    • cmp-path ファイルやディレクトリのPathをソースとして取得し、補完候補として出してくれます。あのファイル/ディレクトリどこだったかなぁ…ってときに補完してくれるやつです。
  • vim-vsnip スニペットエンジンです。時折、LSPが補完候補としてスニペットを提案してくるので入れておきます。
    • cmp-vsnip vim-vsnip用の補完ソースです。
  • lspkind.nvim nvim-cmpと連携して補完欄にかわいらしいアイコンを表示することができます(オヌヌメ)

    Lspkind.nvimのかわいい補完アイコン
lua/plugin.lua
  use 'hrsh7th/nvim-cmp' --補完エンジン本体
  use 'hrsh7th/cmp-nvim-lsp' --LSPを補完ソースに
  use 'hrsh7th/cmp-buffer' --bufferを補完ソースに
  use 'hrsh7th/cmp-path'  --pathを補完ソースに
  use 'hrsh7th/vim-vsnip' --スニペットエンジン
  use 'hrsh7th/cmp-vsnip' --スニペットを補完ソースに
  use 'onsails/lspkind.nvim' --補完欄にアイコンを表示

これらをインストールした上で、①nvim-cmpをLSPに登録、②nvim-cmpの設定でcmp-◯◯ソース達を登録する必要があります。このうち①は、すでに記述しています。

plugin/lspconfig.lua(再掲)
local on_attach = function(client, bufnr)
-- (中略)

-- 補完プラグインであるcmp_nvim_lspをLSPと連携させています⇐ココ!
local capabilities = require("cmp_nvim_lsp").default_capabilities()

-- この一連の記述で、mason.nvimでインストールしたLanguage Serverが自動的に個別にセットアップされ、利用可能になります
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, --keyバインドなどの設定を登録
      capabilities = capabilities, --cmpを連携⇐ココ!
    }
  end,
}

②については、別途nvim/after/pluginのディレクトリ下にcmp.rc.luaという設定ファイルを作成し、下記のように記述します。

after/plugin/cmp.rc.lua
-- Lspkindのrequire
local lspkind = require 'lspkind'
--補完関係の設定
local cmp = require("cmp")
cmp.setup({
  snippet = {
    expand = function(args)
      vim.fn["vsnip#anonymous"](args.body)
    end,
  },
  sources = { 
    { name = "nvim_lsp" },--ソース類を設定
    { name = 'vsnip' }, -- For vsnip users.
    { name = "buffer" },
    { name = "path" },
  },
  mapping = cmp.mapping.preset.insert({
    ["<C-p>"] = cmp.mapping.select_prev_item(), --Ctrl+pで補完欄を一つ上に移動
    ["<C-n>"] = cmp.mapping.select_next_item(), --Ctrl+nで補完欄を一つ下に移動
    ['<C-l>'] = cmp.mapping.complete(),
    ['<C-e>'] = cmp.mapping.abort(),
    ["<C-y>"] = cmp.mapping.confirm({ select = true }),--Ctrl+yで補完を選択確定
  }),
  experimental = {
    ghost_text = false,
  },
  -- Lspkind(アイコン)を設定
  formatting = {
    format = lspkind.cmp_format({
      mode = 'symbol', -- show only symbol annotations
      maxwidth = 50, -- prevent the popup from showing more than provided characters (e.g 50 will not show more than 50 characters)
      ellipsis_char = '...', -- when popup menu exceed maxwidth, the truncated part would show ellipsis_char instead (must define maxwidth first)
      -- The function below will be called before any actual modifications from lspkind
      -- so that you can provide more controls on popup customization. (See [#30](https://github.com/onsails/lspkind-nvim/pull/30))
    })
  }
})

cmp.setup.cmdline('/', {
  mapping = cmp.mapping.preset.cmdline(),
  sources = {
    { name = 'buffer' } --ソース類を設定
  }
})
cmp.setup.cmdline(":", {
  mapping = cmp.mapping.preset.cmdline(),
  sources = {
    { name = "path" }, --ソース類を設定
  },
})

これらを記述した上で、プラグインをまとめてインストール(:PackerSync)し、設定に問題がなければ無事補完が動作するようになります🎉

また、plugin/lspconfig.luaで設定した各種キーバインドを使うと、LSPが持つ様々なコーディング支援機能を利用することができます。ここらへんの機能はCoc.nvimを使ったときと一緒ですね。

◯おまけ:Lspsaga.nvimとFidget.nvimを導入

ここまででもLSPの恩恵を十分に受けることができるのですが、冒頭で述べたとおり、最近のNeovim BuiltinLSPのエコシステムは凄まじく発展しています。
おすすめのプラグインはいろいろあるのですが、その中でも特に人気が高い、LSPの見た目をめっちゃかっこよくしてくれるプラグインを紹介しておきます(私も使ってます)。

  • Lspsaga.nvim
    • LSPが標準で備えてる各種機能をかっこいいUIで扱えるようにするプラグインです。

      画像は公式リポジトリより
  • Fidget.nvim Neovimでバッファを開いた際にLSPの起動状況や状態をおしゃれに表示してくれます✨ 意外とLSPの状態が気になる機会があるので、入れておくとなにかと便利です。

    画像は公式リポジトリより
    この他にも、多くのLSP関連プラグインが存在します。
    ぜひいろいろ探してみてください🌵

◯おまけ②:LinterとFormatterについて

ここまでがLSPの導入になりますが、これらとは別に、別途Linter/Formatter等をNeovim上でLSPと連携して用いるには、別途、各言語のパッケージマネージャーやmason.nvimを介してローカルにインストールしたLinter/FormtterとNeovimを結びつけるプラグインと設定が必要になります。
この点、Coc.nvimとはやや異なる点かもしれません。
https://github.com/jose-elias-alvarez/null-ls.nvim
このnull-ls.nvimは、比較的簡単な設定でLinter/FormatterとNeovimを橋渡ししてくれるプラグインです。導入の詳細については、下記の記事が参考になると思います。
https://zenn.dev/kawarimidoll/articles/ad35f3dc4a5009
https://zenn.dev/nazo6/articles/c2f16b07798bab

それではよきNeovimライフを!🌵

Discussion