モノリポだっていけちゃうdenols/ts_ls共存術(Neovim >= 0.11)

by
カテゴリ:
タグ:

Vim駅伝2025-09-03の記事です。前回の記事はkawarimidollさんによるNeovim 0.12(開発版)でcopilot-language-serverを設定してみたぞ(脱copilot.lua)でした。私の直近の記事を早速参考にしていただいて、うれしかったです。

さて、本題。

ランタイムが群雄割拠しているTypeScript/JavaScriptのプロジェクトにおいて、適切な言語サーバー選びは重要項目です。 Neovimでの設定方法について毎年のように記事が出ています。

適切な言語サーバーをバッファにアタッチする方法として、2022年と2024年の記事では、両方をアタッチしてから不適切な方をデタッチしていました。その背景には、nvim-lspconfigプラグインの仕様が関係していたかと思います。このプラグインによる言語サーバーの自動起動機構にユーザー側でロジックを足すことが難しく、停止ロジックでごまかしていたと思います。

ところが、vim.lsp.enable()が実装されたNeovim >= 0.11で事情が変わり、nvim-lspconfigプラグインを使わずに本体と自身の設定で自動起動を制御するようになりました。たとえば、vim.lsp.enable("denols")を設定に仕込んでおくと、vim.lsp.config.denols.filetypesにマッチするファイルタイプに対して自動的にdenolsがアタッチされます。

この、ユーザーに自動起動するか委ねる変化により、ts_lsを使うかdenolsを使うかも起動のタイミングで決めやすくなりました。無駄にプロセスを起動することもないですし、バッファ単位で言語サーバーを選べるので、nodeとdenoが混在するモノリポなんてレアケースも怖くありません。

設定はざっくり以下の通りです。

-- 起動条件が単純な言語サーバーは `vim.lsp.enable` で自動起動させる
vim.lsp.enable({ "lua_ls", "pyright" })

-- 起動条件が複雑な言語サーバーはFileTypeイベント時に判定する
--(ts_ls/denols)
vim.api.nvim_create_autocmd("FileType", {
  group = vim.api.nvim_create_augroup("LspStartNodeOrDeno", { clear = true }),
  callback = function(ctx)
    if not vim.tbl_contains(
        { "javascript", "javascriptreact", "javascript.jsx", "typescript", "typescriptreact", "typescript.tsx" },
        ctx.match
    ) then
      return
    end

    -- node
    if vim.fn.findfile("package.json", ".;") ~= "" then
      vim.lsp.start(vim.lsp.config.ts_ls)
      return
    end

    -- deno
    vim.lsp.start(vim.lsp.config.denols)
  end,
})

Neovim >= 0.11向けのLSPの設定方法をまず知りたいという方は、kawarimidollさんの記事をご覧ください。

Neovim0.11用のLSP設定

また、nodeかdenoかの判定にpackage.jsonの有無を使っていますが、もう少しこだわるならkyoh86さんの記事をご参考ください。

denols/typescript-lsのLSPスイッチングは意外と気を遣うよという話

ENJOY!!