3

I'm using Neovim's lspconfig plugin to manage LSP servers for my development environment. Whenever I open a TypeScript file, both denols and tsserver attach to the same buffer, causing conflicts.

When I open a TypeScript file in:

  1. A project with deno.json, both denols and tsserver attach.
  2. A Node.js project with a package.json, both denols and tsserver still attach.
  3. A non-project file or single-file context, both servers attach again.

both servers auto-attach no matter the context, I even remove the lspconfig config completely and they still get attached

Tried this simple config from the Deno setup your environment

local lspconfig = require("lspconfig")
local util = lspconfig.util

lspconfig.denols.setup({
    root_dir = util.root_pattern("deno.json", "deno.jsonc"), -- Deno projects only
    single_file_support = false,
})

lspconfig.tsserver.setup({
    root_dir = util.root_pattern("package.json"), -- Node.js projects only
    single_file_support = false,
})
LSP configs active in this buffer (bufnr: 4) ~
- Language client log: ~/.local/state/nvim/lsp.log
- Detected filetype: `typescript`
- 2 client(s) attached to this buffer
- Client: `denols` (id: 1, bufnr: [4])
  root directory:    /mnt/library/development/projects/obsidian-programming-advices/
  filetypes:         javascript, javascriptreact, javascript.jsx, typescript, typescriptreact, typescript.tsx
  cmd:               ~/.local/share/nvim/mason/bin/deno lsp
  version:           `deno 2.1.5 (stable, release, x86_64-unknown-linux-gnu)`
  executable:        true
  autostart:         true
- Client: `ts_ls` (id: 2, bufnr: [4])
  root directory:    /mnt/library/development/projects/obsidian-programming-advices/
  filetypes:         javascript, javascriptreact, javascript.jsx, typescript, typescriptreact, typescript.tsx
  cmd:               ~/.local/share/nvim/mason/bin/typescript-language-server --stdio
  version:           `4.3.3`
  executable:        true
  autostart:         true

I'm using lspconfig and this is my config:

-- Deno Language Server
lspconfig.denols.setup({
    capabilities = capabilities,
    on_attach = function(client)
        -- Enable document formatting for Deno
        if client.server_capabilities.documentFormattingProvider then
            client.server_capabilities.documentFormattingProvider = true
        end
    end,

    root_dir = lspconfig.util.root_pattern("deno.json", "deno.jsonc"),

    settings = {
        deno = {
            enable = true,
            lint = true,
            fmt = true,
            inlayHints = {
                parameterNames = { enabled = "all", suppressWhenArgumentMatchesName = true },
                parameterTypes = { enabled = false },
                variableTypes = { enabled = false, suppressWhenTypeMatchesName = false },
                propertyDeclarationTypes = { enabled = false },
                functionLikeReturnTypes = { enabled = true },
                enumMemberValues = { enabled = true },
            },
        },
    },
})

-- Ts_ls Language Server
lspconfig.ts_ls.setup({
    capabilities = capabilities,
    on_attach = on_attach,
    root_dir = util.root_pattern("package.json"),
    single_file_support = false,
    settings = {
        typescript = {
            format = {
                enable = false,
            },
        },
        javascript = {
            format = {
                enable = false,
            },
        },
        completions = {
            completeFunctionCalls = true,
        },
    },
})

In the end I get around it using this wonky solution:

vim.api.nvim_create_autocmd("LspAttach", {
    callback = function(args)
        local client = vim.lsp.get_client_by_id(args.data.client_id)
        local is_node_project = vim.fn.filereadable("package.json") == 1
        local is_deno_project = vim.fn.filereadable("deno.json") == 1

        -- Stop `denols` in Node.js projects
        if client.name == "denols" and is_node_project then
            client.stop()
        end

        -- Stop `tsserver` in Deno projects
        if client.name == "ts_ls" and is_deno_project then
            client.stop()
        end
    end,
})

So I still need a proper solution!

Instead, both denols and ts_ls attached to the same buffer in a non-Deno project, causing conflicts, likely due to improper root directory detection for denols. I updated the configuration to make the root detection stricter for both denols and ts_ls, and verified the behavior using :LspInfo.

1 Answer 1

2

Just recently, I reconfigured my Neovim config and came across a similar issue as yours. However, in my case, it was possibly not an issue with ts_ls and denols, but rather with eslint and denols.

The way I overcame this issue is as follows:

  • On Deno project: Disable eslint and enable denols
  • On non-Deno project: Disable denols and enable eslint

Based on your configuration you might do this

-- Check whether it's a deno project or not
local is_deno_project()
  local deno_files = { 'deno.json', 'deno.jsonc', 'deno.lock' }

  for _, filepath in ipairs(deno_files) do
    filepath = table.concat({ vim.fn.getcwd(), filepath }, '/')

    if vim.uv.fs_stat(filepath) ~= nil then return true end
  end

  return false
end

-- Deno Language Server
lspconfig.denols.setup({
  -- ...
  settings = {
    deno = {
       enable = is_deno_project(),
       -- ...
    },
  },
})

-- Eslint Language Server
lspconfig.eslint.setup({
  -- ...
  settings = {
    eslint = {
      enable = not is_deno_project(),
      -- ...
    },
  },
})

In addition, I'm not quite sure about your vim.fn.filereadable() usage, although by no means I an expert in Lua or NVim. Based on this answer and my limited understanding of this documentation you need to pass an absolute path and utilize vim.fn.expand(), or you can use vim.uv.fs_stat() instead.

Bottom line, here's my workaround.

Hope that helps, cheers.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.