LazyVimでdenoとtsのlspを共存させる

に公開

はじめに

もともとtypescriptはよく使うのですが、個人的にdenoも触る機会が出てきました。
私は普段nvimでLazyVimを使っています。
となれば当然nvimでもdenoを書きたいのですが、lspがtsとかぶって快適なvimライフが送れなくなってしまいました。
これはいかんということでdenoとtsのlspをいい感じに共存させる設定をしました。
なかなかうまくいかなくてそれなりにプラグインのソースも読んだので、備忘録的にまとめておきます。


denoプロジェクトでvtslsも起動してしまう


普通のtsプロジェクトでdenolsも起動してしまう

関係するもの

  • neovim 0.11.5
  • LazyVim 15.13.0
  • nvim-lspconfig 2.5.0
  • mason-lspconfig 2.1.0

目標

目標というほどのものでもないですが、以下のような感じにしようと考えました。

  • denoプロジェクトではdenoのlspを使う
  • 普通のtsプロジェクトではtsのlspを使う
  • denoプロジェクトと普通のtsプロジェクトの混在は想定しない
  • denoとts以外(go とか)のlspは特に設定なしのデフォルトで動作する

設定だけ知りたい人へ

細かいことはいいから、設定だけ知りたい人向けにコードを載せておきます。

nvim/lua/plugins/lspconfig.lua
return {
  {
    "mason-org/mason-lspconfig.nvim",
    version = "^2.0.0",
    dependencies = {
      {
        "mason-org/mason.nvim",
        ---@module "mason"
        ---@type MasonSettings
        opts = {},
      },
      {
        "neovim/nvim-lspconfig",
        ---@module "lspconfig"
        ---@param opts PluginLspOpts
        opts = function(_, opts)
        local root_pattern = require("lspconfig.util").root_pattern

        ---@type table<string, lazyvim.lsp.Config|boolean>
        local manual_setup_lsp = {
          denols = {
            mason = false,
            root_dir = function(bufnr, on_dir)
              local fname = vim.api.nvim_buf_get_name(bufnr)
              local filepath = root_pattern("deno.json", "deno.jsonc")(fname)

              if filepath == nil then
                return
              end

              on_dir(root_pattern("deno.json", "deno.jsonc")(fname))
            end,
          },
          vtsls = {
            mason = false,
            root_dir = function(bufnr, on_dir)
              local fname = vim.api.nvim_buf_get_name(bufnr)

              local deno_filepath = root_pattern("deno.json", "deno.jsonc")(fname)

              -- deno.jsonがある場合は起動しない
              if deno_filepath ~= nil then
                return
              end

              on_dir(root_pattern("tsconfig.json", "package.json")(fname))
            end,
          },
        }

        local merged_config = vim.tbl_deep_extend("force", {}, opts.servers or {}, manual_setup_lsp or {})

        opts.servers = merged_config

        return opts
      end,
      },
    },
    config = function()
      ---@module "mason-lspconfig"
      local mason_lspconfig = require("mason-lspconfig")

      mason_lspconfig.setup({
        automatic_enable = false,
        ensure_installed = {
          "denols",
          "vtsls",
        },
      })
    end,
  },
}

mason-lspconfigの設定が効かない

もともと私はmasonを使ってlspを管理していました。
セットでmason-lspconfigも使ってlsp周りはよしなに起動してくれるようにしていました。
しかしこのmason-lspconfigがよろしくやってくれることが原因と考えました。
なので、mason_lspconfigでdenolsとvtsls(私はvtslsを使用してます)だけはmason-lspconfigの管轄外にして、手動で設定することにしました。

ドキュメントにもどうやらautomatic_enableを指定する方法が書いてあるのでこれやろ、と考えました。
しかし、これだけでは設定が反映されませんでした。

LazyVimがデフォルトでmason-lspconfigを呼んでた

調べてみると、LazyVimのlsp設定でmason-lspconfigを呼び出していることがわかりました。
挙動を見るとおそらくこの関係で、自身の設定で2回目のmason-lspconfigのセットアップが反映されていないようです。
ソース読む感じLazyVim側のmason-lspconfigだけを無効にはできなそうでした。

nvim-lspconfigでservers optionを上書きする

そういうことなので、nvim-lspconfigで設定を上書きすればよいのでは、と思いました。
opts callbackの引数にはLazyVim側のlspの設定が入ってくるので、そこにdenolsとvtslsの設定を追加するようにしました。
その結果が上記のコードになります。
余談ですが、nvim-lspconfigのソース読んでるとroot_dirの引数が2通りある気がしました。

--- 1
root_dir = function(fname)
--- 2
root_dir = function(bufnr, on_dir)

これってlspごとの仕様次第なんでしょうかね。知ってる方いたら教えていただけると嬉しいです。

まとめ

以上になります。
結果としては設定をおさえることになってしまいました。少ない設定で申し訳ありません。nvim0.11からlspはプラグインなしでも設定しやすくなっており、いっそ移行したい気持ちもあるので時間あるときにやってみようと思います。
次回は設定させていただきありがとうございますといえるようにしたいと思います。

NCDCエンジニアブログ

Discussion