Nvim LSP周り

背景
-
Deno
で遊ぼうとしたらLSがインストールされていないことによるエラーが発生 - 今の構成どうなってる?→理解していない
- Cocとかあるけど何もわからず今の選定にしてるな

dependencies
に記述するだけでimportされる
公式ドキュメントによると
lazy.nvim automagically lazy-loads Lua modules. This means that if you have a plugin A that is lazy-loaded and a plugin B that requires a module of plugin A, then plugin A will be loaded on demand as expected.
「プラグインAがlazy-load対象で、プラグインBのdependenciesにプラグインAがあるならAはBがrequireされたときに読み込まれる」
よしなに遅延読込みしてくれるよ

build-in LSP
Neovim内蔵のLSP(nvim-lsp
)を使っているらしい
昔はCoc.nvim
とかが流行っていたが、built-inのエコシステムでも行けることが分かって乗り換える人多いっぽい
参考

そのままだと使いづらいから、nvim-lspconfig.nvim
やmason.nvim
, mason-lspconfig.nvim
がセットで使われるのがスタンダードっぽい

nvim-lspconfig.nvim
built-in lspの起動に関する設定とかが簡単にできる

mason.nvim
LSのインストーラー

mason-lspconfig.nvim
mason.nvim
でインストールしたLSPサーバーを使うためのもの
初期インストールするLSの設定
require("mason-lspconfig").setup({
ensure_installed = { "lua_ls", "ts_ls" },
})
Mason
でインストールしたLSを一気にsetup()
できて便利
require("mason-lspconfig").setup_handlers({
function(server_name)
local opts = { capabilities = capabilities }
if server_name == "lua_ls" then
opts.settings = {
Lua = {
runtime = { version = "LuaJIT", path = { "?.lua", "?/init.lua" } },
workspace = {
library = { vim.fn.stdpath("config") },
maxPreload = 1000,
preloadFileSize = 100,
checkThirdParty = false,
},
telemetry = { enable = false },
},
}
end
lspconfig[server_name].setup(opts)
end
})

参考記事のキーバインド設定のところで以下の書き方をしていた
vim.api.nvim_create_autocmd("LspAttach", {
...
})
LspAttach
という書き方に覚えがあったのでその記事に飛んだ
LSPがアタッチしたBufferにのみキーバインドを設定できるようで便利(意訳)
なるほどわからん。調べよう

LSPの基礎知識をキャッチアップすることに
(これでMCPのキャッチアップのとき楽になればいいな)

LSPの基礎知識とプラグインなしでLSPを動かしてみよう
ubuntuにNvim入れて実験してみる

LSPとは
定義ジャンプや補完機能を提供するLS(Language Server)とそれを受け取るエディタとの通信の内容を取り決めたもの(プロトコル)
これによって、LS側とエディタ側にメリットが生まれる
- LS側: エディタごとに機能を提供する必要がなくなり、楽
- エディタ側: LSPの規格に沿ったクライアントを作っておけば全てのLSを利用できる

JSON
でやりとりされてるらしい
RPCってでてきたぞ(Remote Procedure Call): 外部に実装された手続きを呼び出すこと
JSON RPCは、RPCに必要な記述をJSONで可能にしたやつ
ドキュメントもあるらしい

めっちゃ同期されてる
- ファイル開いたよ!
- 編集開始したよ!
- 定義したよ!→定義ジャンプちょうだい!
- ファイル閉じたよ!

特に coc.nvim は単なる LSP クライアントではなく nodeJS を利用した独自のエコシステムを構成しており、
そうなんだ

Ubuntuでパスを通す(アーキテクチャの確認も)
built-in LSPを試すところで少し手こずった
例として lua-language-server を起動してみます[2]。
-
lua-language-server
は自分でインストールしないといけなかったのでUbuntu用を探したが見当たらない - stackoverflowで同じような人発見
自前で.gz
ファイルをダウンロードしてパスを通すとできるらしい
やってみる
- Ubuntuのアーキテクチャを確認し、正しくダウンロード
uname -m
> x86_64
- ディレクトリ作成&解凍
mkdir -p ./local/share/lua-language-server
tar -x --file {ダウンロードしたパス} -av -C ./local/share/lua-language-server
tarのオプション
-
-x
: 解凍 -
--file
: 対象ファイルの指定 -
-a
: ファイルの拡張子によってよしなにやってくれる(auto?) -
-v
: 詳細(verbose) -
-C
: 解凍場所(事前に作成)
- シンボリックリンクの作成
sudo ln -s $HOME/.local/share/lua-language-server/bin/lua-language-server /usr/bin/lua-language-server
/usr/bin/
である必要はない(パスが通ってたのでここに入れただけ)
.local/bin/
とかでもいい
- 確認
lua-language-server
色々出てきたら完了

疲れた

nvim -u launch-test.lua launch-test.lua などとして Neovim を起動すると lua-language-server が起動されて diagnostics の表示が出るのが確認できると思います。
出ないんだよなー
上手くインストールできてないのかなとおもって、macの方でもbrewでlua-language-server
入れて確認したけど、ubuntu同様diagnostics出なかった
↓これが原因だった
root_dir = vim.fs.dirname(vim.fs.find({ ".luarc.json" }, { upward = true })[1]), -- プロジェクトのルートディレクトリを検索する
ディレクトリを上から検索して.luarc.json
が見つかればルートディレクトリとする
macとUbuntu両環境ともこのファイル作ってなかったので動かなかったらしい
内容空でtouchしたらdiagnosticsでた嬉しい

↓ファイルタイプがlua
のとき
lua-language-server
の起動とか
vim.api.nvim_create_autocmd("FileType", {
desc = "launch lua-language-server",
pattern = "lua",
...
})
↓LSP起動したとき
キーバインドはどの言語にも共通なので
vim.api.nvim_create_autocmd("LspAttach", {
desc = "Attach key mappings for LSP functionalities",
callback = function ()
vim.keymap.set('n', 'gd', '<cmd>:lua vim.lsp.buf.definition()<CR>')
vim.keymap.set('n', 'gr', '<cmd>:lua vim.lsp.buf.references()<CR>')
-- more mappings ...
end
})

undefined global vimがうるさい
ディレクトリ内に.luarc.json
作って以下の内容記述
.
忘れずに
{
"diagnostics": {
"globals": ["vim"],
"hint.enable": false
}
}

ts_ls
とdenols
の切り替え
プロジェクトによって参考
- プロジェクトに
deno.json
,deno.jsonc
があればdenoプロジェクトとみなす
local is_deno = function(fname)
return lspconfig.util.root_pattern("deno.json", "deno.jsonc")(fname) ~= nil
end
-
lspconfig.util.root_pattern("deno.json", "deno.jsonc")
deno.json
かdeno.jsonc
があるか探す関数を返す -
(fname)
現在のファイルの位置から上へ探していく -
~= nil
存在してるかどうかでboolを返す
-
denols
を適用する条件
if server_name == "denols" then
opts.root_dir = lspconfig.util.root_pattern("deno.json", "deno.jsonc")
-
opts.roor_dir
denols
を適用するディレクトリを設定する
-
ts_ls
を適用する条件
elseif server_name == "ts_ls" then
opts.root_dir = function(fname)
if is_deno(fname) then
return nil
end
return lspconfig.util.root_pattern("package.json")(fname)
end
opts.single_file_support = false
- if Denoのプロジェクト: root_dirをnilに
- Denoプロジェクトでなければ、root_dirを
package.json
のあるディレクトリにする
- Denoプロジェクトでなければ、root_dirを
-
opts.single_file_support = false
- 単一ファイルでのLSPの起動はしないようにする
- プロジェクト単位でLSPが起動するようにする
↓Gist(パーマリンク指定できないんだね..)