🤝

俺の denols/tsserver(vtsls) 共存術 for Neovim 2024

2024/05/10に公開
1

はじめに

NeovimのLSPでDenoのLSP(denols) と tsserver(vtsls)を共存させる試みはこれまで幾度となく行われてきました。
LSPって何?とか問題意識を詳しく知りたい、という方は以下の記事を参照してください。

https://zenn.dev/kawarimidoll/articles/2b57745045b225
https://zenn.dev/mochi/articles/e6b2735108157c
https://zenn.dev/vim_jp/articles/69d26e3f7b0e35

自分もこれまで上の記事を参考にしながら設定をしていたのですが、いくつか不満点がありました。

  • node.jsのmonorepoの一部でdenoを使っている場合、denoのLSPとtsserver(vtsls)を共存させるのが難しい
  • これらの設定では、single_file_support = falseにしてしまっているので、例えば書き捨てのtypescriptファイルを開いた時にLSPが効かない

そこで、自分なりのdenoとtsserver(vtsls)の共存術を紹介します。

判定ロジックについて

先行研究としてのkyoh86氏の記事をベースに以下のようなロジックを組みました。

  1. 現在開いているBufferのあるディレクトリから、deno関連のファイル[1] or node特有のファイル[2] or git root[3] が見つかるまで階層を上がる。
  2. もしその階層にnode特有のファイルが見つからなかった場合 -> denols を起動。tsserverは落とす。
  3. もし node特有のファイルがあるが、.vscode/settings.json 内で許可されていた場合 -> denols を起動。tsserverは落とす。
  4. それ以外の場合 -> denolsのroot_dirnilを設定しSingle file modeで起動。もしtsserverが起動していた場合は落とす。

としています。

具体的なコードは以下の通り。

https://github.com/ryoppippi/dotfiles/blob/05ae64a18c348dd7989f1e67a98864948489f537/nvim/lua/plugin/nvim-lspconfig/servers/denols.lua#L6-L63

https://github.com/ryoppippi/dotfiles/blob/05ae64a18c348dd7989f1e67a98864948489f537/nvim/lua/plugin/nvim-lspconfig/servers/vtsls.lua#L43-L73

ロジックの補足について諸々

階層を上がってファイルを探す方法

以前はkyoh86氏のclimbdir.nvimを愛用していましたが、今回の実装ではNeovimのlua関数であるvim.fs.rootを使っています。

vim.fs.rootについて

https://neovim.io/doc/user/lua.html#vim.fs.root()

vim.fs.root({source}, {marker})                                *vim.fs.root()*
    Find the first parent directory containing a specific "marker", relative
    to a buffer's directory.

    Example: >lua
        -- Find the root of a Python project, starting from file 'main.py'
        vim.fs.root(vim.fs.joinpath(vim.env.PWD, 'main.py'), {'pyproject.toml', 'setup.py' })

        -- Find the root of a git repository
        vim.fs.root(0, '.git')

        -- Find the parent directory containing any file with a .csproj extension
        vim.fs.root(0, function(name, path)
          return name:match('%.csproj$') ~= nil
        end)
<

    Parameters: ~
      • {source}  (`integer|string`) Buffer number (0 for current buffer) or
                  file path to begin the search from.
      • {marker}  (`string|string[]|fun(name: string, path: string): boolean`)
                  A marker, or list of markers, to search for. If a function,
                  the function is called for each evaluated item and should
                  return true if {name} and {path} are a match.

    Return: ~
        (`string?`) Directory path containing one of the given markers, or nil
        if no directory was found.

これで、指定したファイルが見つかるまで階層を上がることができます。

また、ファイル名を指定してその存在を確認するには vim.uv.fs_stat を使うことができます。
fs_statの戻り値がnilであればファイルが存在しないことを示します。

.vscode/settings.jsonの読み込み

denoのvscode向け拡張機能は.vscode/settings.jsonでのworkspace設定をサポートしています。

https://docs.deno.com/runtime/manual/references/vscode_deno/#workspace-folders

ここでは

  • deno.enable - denolsを許可するかどうか
  • deno.enablePaths - 許可するディレクトリパスのリスト

を設定することができます。

しかし、これは標準ではNeovimでは対応していません。

そこで、neoconf.nvimを使います。
https://github.com/folke/neoconf.nvim

neoconf.nvimはjson形式でLSPの設定を行い、またそれを読み込むことができます。
また .vscode/settings.json もサポートされています。

例えばプロジェクトのルートに .vscode/settings.jsonを置いて以下のように設定し、

{
  "deno.enable": true,
  "deno.enablePaths": ["./apps/deno-project"]
}

それを読み込むように設定することができます。

こうすれば明示的にプロジェクトごとにdeno or tsserverの使い分けができるようになります。
また、副次的にvscodeユーザーと共同作業する際にも便利です。

以下に読み取りの実装例を示します。

https://github.com/ryoppippi/dotfiles/blob/05ae64a18c348dd7989f1e67a98864948489f537/nvim/lua/plugin/neoconf.lua#L7-L35
https://github.com/ryoppippi/dotfiles/blob/05ae64a18c348dd7989f1e67a98864948489f537/nvim/lua/plugin/nvim-lspconfig/servers/denols.lua#L27-L30

おわりに

今後も色々試行錯誤していく予定ですが、この方法でdenoとtsserver(vtsls)を共存させることができるようになりました。
もし何か問題点や改善点があれば、ぜひ教えてください。

脚注
  1. deno.json deno.jsonc package.json など。package.jsonがdenoでサポートされているのもややこしい https://github.com/ryoppippi/dotfiles/blob/1c5bb5dcf827f4ca08120b6618031bd845f0ebde/nvim/lua/plugin/nvim-lspconfig/utils.lua#L104-L109 ↩︎

  2. package-lock.jsonなど https://github.com/ryoppippi/dotfiles/blob/1c5bb5dcf827f4ca08120b6618031bd845f0ebde/nvim/lua/plugin/nvim-lspconfig/utils.lua#L111-L118 ↩︎

  3. .gitディレクトリ ↩︎

Discussion