nvim-lspでtypescript-language-serverとdeno-lspの競合を回避する
2024年9月更新版
nvim-lspconfig内で、tsserverがts_lsにリネームされた(そもそもtsserverとは別なのにtsserverと名乗っていたっぽい)のに合わせて記事を更新しました。
mason.nvimも使うのをやめたのでnvim-lspconfigをそのまま使っています。
local nvim_lsp = require('lspconfig')
local is_node_dir = function()
return nvim_lsp.util.root_pattern('package.json')(vim.fn.getcwd())
end
-- ts_ls
local ts_opts = {}
ts_opts.on_attach = function(client)
if not is_node_dir() then
client.stop(true)
end
end
nvim_lsp.ts_ls.setup(ts_opts)
-- denols
local deno_opts = {}
deno_opts.on_attach = function(client)
if is_node_dir() then
client.stop(true)
end
end
nvim_lsp.denols.setup(deno_opts)
package.json
があるときのみts_ls
が動作し、それ以外のときはdenols
が動きます。
どちらもない場合(一時的なスクリプトを書くときなど)にdenols
を使いたいためです。
参考:
以下は古い情報です。
残しておきますが、筆者は既に使っていないので動作するかわかりません。
2022年8月更新版
筆者はこんな感じで設定して競合回避しています。
(mason.nvimでLSPを管理しています)
local mason = require('mason')
local mason_lspconfig = require('mason-lspconfig')
local nvim_lsp = require('lspconfig')
mason_lspconfig.setup_handlers({
function(server_name)
local node_root_dir = nvim_lsp.util.root_pattern("package.json")
local is_node_repo = node_root_dir(vim.api.nvim_buf_get_name(0)) ~= nil
local opts = {}
if server_name == "tsserver" then
if not is_node_repo then
return
end
opts.root_dir = node_root_dir
elseif server_name == "eslint" then
if not is_node_repo then
return
end
opts.root_dir = node_root_dir
elseif server_name == "denols" then
if is_node_repo then
return
end
opts.root_dir = nvim_lsp.util.root_pattern("deno.json", "deno.jsonc", "deps.ts", "import_map.json")
opts.init_options = {
lint = true,
unstable = true,
suggest = {
imports = {
hosts = {
["https://deno.land"] = true,
["https://cdn.nest.land"] = true,
["https://crux.land"] = true
}
}
}
}
end
opts.on_attach = function(_, bufnr)
-- 略
end
nvim_lsp[server_name].setup(opts)
end
})
nvim_lsp.util.root_pattern("package.json")
が有効になるかどうかを確認し、tsserver/eslint
とdenols
のいずれか使わない方のsetup()
をスキップしています。
nvim-lsp-installer時代の設定
local nvim_lsp = require('lspconfig')
local lsp_installer = require("nvim-lsp-installer")
lsp_installer.on_server_ready(function(server)
local opts = {}
if server.name == "tsserver" then
opts.root_dir = nvim_lsp.util.root_pattern("package.json", "node_modules")
elseif server.name == "eslint" then
opts.root_dir = nvim_lsp.util.root_pattern("package.json", "node_modules")
elseif server.name == "denols" then
opts.root_dir = nvim_lsp.util.root_pattern("deno.json", "deno.jsonc", "deps.ts", "import_map.json")
opts.init_options = {
lint = true,
unstable = true,
suggest = {
imports = {
hosts = {
["https://deno.land"] = true,
["https://cdn.nest.land"] = true,
["https://crux.land"] = true
}
}
}
}
end
opts.on_attach = function(client, bufnr)
-- 略
end
server:setup(opts)
vim.cmd [[ do User LspAttachBuffers ]]
end)
以下は記事公開時の古い情報ですが残しておきます↓
2021年12月版
現代のコーディングにおいて、LSPの支援は非常に有用です。
筆者は、Neovim環境で、nvim-lspconfigとnvim-lsp-installerを使って各種LSPをインストールしています。
基本的には言語に対応したLSPを導入すれば問題なく動くのですが、TypeScriptではNode開発用のtsserverとDeno開発用のdenolsの2種類があり、これらを同時に入れると意図しないエラーが出る場合があります。
例えば、tsserverを有効にした状態でDenoプロジェクトのコードを開くと次のようにエラーが出てしまいます。
Deno環境では問題のないコード
- インポート指定に拡張子をつけちゃダメだよ
-
Deno
は定義されてないよ -
Object.hasOwn
は定義されてないよ -
await
はトップレベルでは使えないよ
これらのエラーを回避するためにLSPをアンインストールするのは現実的ではありません。
同じ.ts
ファイルでも、ディレクトリの状況によってtsserverとdenolsのいずれを使うか判定し、有効にするLSPを切り替えたいところです。
本記事ではこの設定を紹介します。
tsserverとdenolsを出し分ける設定
Nodeプロジェクトの配下ではtsserver(およびeslint)を、それ以外ではdenolsを起動するための設定がこちらです。
local nvim_lsp = require('lspconfig')
local lsp_installer = require("nvim-lsp-installer")
local node_root_dir = nvim_lsp.util.root_pattern("package.json", "node_modules")
local buf_name = vim.api.nvim_buf_get_name(0)
local current_buf = vim.api.nvim_get_current_buf()
local is_node_repo = node_root_dir(buf_name, current_buf) ~= nil
lsp_installer.on_server_ready(function(server)
local opts = {}
if server.name == "tsserver" or server.name == "eslint" then
opts.autostart = is_node_repo
elseif server.name == "denols" then
opts.autostart = not(is_node_repo)
-- 以下は出し分けとは関係ないが設定しておくのがオススメ
opts.init_options = { lint = true, unstable = true, }
end
server:setup(opts)
vim.cmd [[ do User LspAttachBuffers ]]
end)
nvim-lsp-installerを使っている場合、サーバーを起動させるためにon_server_ready()
を書いていると思うので、そこに上記の設定を組み込んでください。
なお、local
を使って逐一変数定義を行っていますが、これは記事上での見やすさを意識したものです。変数に代入せず一行で書いても問題ありません。
解説
nvim_lsp.util.root_pattern()
は、nvim-lspconfigにおいて各LSPの起動時のルートディレクトリを決めるために使われている関数です。
これは高階関数で、使用時には引数としてvim.api.nvim_buf_get_name(0)
とvim.api.nvim_get_current_buf()
を受け取ります。この使い方は実際のコードを参考にしました。
これにより、上記コードのnode_root_dir(buf_name, current_buf)
で、現在開いているファイルと同一ディレクトリまたは先祖ディレクトリにpackage.json
またはnode_modules
があればそのパスを、なければnil
が得られます。
さらに~= nil
で比較して真偽値に変え、各LSPのautostart
へ設定しています。
この際、片方にnot()
をつけることで、tsserverとdenolsのどちらかしか起動しないようにしています。
上記ではとりあえずpackage.json
とnode_modules
を基準にしていますが、package-lock.json
やyarn.lock
なども使えるでしょう。
また、逆にdenols側を基準にする場合は、deno.json
やdeps.ts
を使うと良いと思います。
おわりに
Neovim Builtin LSPでtsserverとdenolsの競合を回避する設定について解説しました。
Node.jsとDenoの両方の環境で開発する機会のある方はお試しください。
おまけ coc.nvimの場合
.vim/coc-settings.json
に設定を書くと読み込まれます。
Discussion