Skip to content

LSP: Duplicate servers with incorrect rootPath started for non-file backed buffers #36464

@cameronr

Description

@cameronr

Problem

When opening a non-file backed buffer that still matches a lsp filetype, duplicate lsp servers are started with a rootPath set to /.

For example, with emmylua_ls, opening a file named diffview:///Users/cam/Dev/neovim-dev/opencode.nvim/.git/:0:/lua/opencode/state.lua will start a duplicate emmylua_ls server and that server will start indexing / (i.e. the whole filesystem).

I traced it down and it's happening in lsp.start:

function lsp.start(config, opts)
opts = opts or {}
local reuse_client = opts.reuse_client or reuse_client_default
local bufnr = vim._resolve_bufnr(opts.bufnr)
if not config.root_dir and opts._root_markers then
validate('root_markers', opts._root_markers, 'table')
config = vim.deepcopy(config)
config.root_dir = vim.fs.root(bufnr, opts._root_markers)
end

For non-file backed buffers, vim.fs.root starts looking in the current directory and, if it matches, it returns a relative directory.

That relative directory doesn't match the checks in reuse_client_default so new servers are spawned.

The incorrect rootPath comes from Client:initialize:

function Client:initialize()
-- Register all initialized clients.
all_clients[self.id] = self
local config = self.config
local root_uri --- @type string?
local root_path --- @type string?
if self.workspace_folders then
root_uri = self.workspace_folders[1].uri
root_path = vim.uri_to_fname(root_uri)
end

vim.uri_to_fname translates the file://. to /. which is what the language server receives.

To fix, I think we just have to pass the return from vim.fs.root to vim.fn.fnamemodify:

    config.root_dir = vim.fn.fnamemodify(vim.fs.root(bufnr, opts._root_markers), ':p:h')

Steps to reproduce using "nvim -u minimal_init.lua"

minimal_init.lua

local pattern = 'lua'
local cmd = { 'emmylua_ls' }
-- Add files/folders here that indicate the root of a project
local root_markers = { '.luarc.json', '.emmyrc.json', '.luacheckrc', '.git' }
-- Change to table with settings if required
local settings = vim.empty_dict()

vim.api.nvim_create_autocmd('FileType', {
  pattern = pattern,
  callback = function(args)
    vim.lsp.start({
      name = 'emmylua_ls',
      cmd = cmd,
      settings = settings,
    }, { _root_markers = root_markers })
  end,
})

Repro:

  1. touch .emmyrc.json
  2. nvim -u minimal_init.lua minimal_init.lua (second arg is to load a lua file to trigger emmylua_ls)
  3. :e diffview:///Users/cam/Dev/neovim-dev/opencode.nvim/.git/:0:/lua/opencode/state.lua (diffview is not required, file does not need to exist)
  4. :lua print(vim.inspect(#vim.lsp.get_active_clients()))

Expected behavior

No new lsp servers should be spawned (and, even if they were, they should have the correct rootPath).

Nvim version (nvim -v)

NVIM v0.12.0-dev-1569+g16c8152229

Language server name/version

emmylua_ls 0.16.0

Operating system/version

macOS 15.6.1

Log file

https://gist.github.com/cameronr/3e9ee385af755fcbe5c2b5d333c04b0b

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions