nvim-lspconfig icon indicating copy to clipboard operation
nvim-lspconfig copied to clipboard

LSP should not be passed `workspace/didChangeWorkspaceFolders` when started in single file mode

Open xrisk opened this issue 8 months ago • 4 comments

Summary

LSPs started in single file mode are passed workspace/didChangeWorkspaceFolders which causes them to scan the whole disk.

Description

Minimal vimrc:

-- Bootstrap lazy.nvim
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
  local lazyrepo = "https://github.com/folke/lazy.nvim.git"
  local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
  if vim.v.shell_error ~= 0 then
    vim.api.nvim_echo({
      { "Failed to clone lazy.nvim:\n", "ErrorMsg" },
      { out, "WarningMsg" },
      { "\nPress any key to exit..." },
    }, true, {})
    vim.fn.getchar()
    os.exit(1)
  end
end
vim.opt.rtp:prepend(lazypath)

vim.lsp.set_log_level("debug")

require("lazy").setup({
    { "neovim/nvim-lspconfig",
      config = function()
	require("lspconfig").pyright.setup {
		on_attach = on_attach,
	}
      end,
    }
})

Relevant parts of lsp.log

[DEBUG][2025-01-31 19:04:04] .../vim/lsp/rpc.lua:286	"rpc.send"	{  id = 1,  jsonrpc = "2.0",  method = "initialize",  params = {    capabilities = {      general = {        positionEncodings = { "utf-16" }      },      textDocument = {        callHierarchy = {          dynamicRegistration = false        },        codeAction = {          codeActionLiteralSupport = {            codeActionKind = {              valueSet = { "", "quickfix", "refactor", "refactor.extract", "refactor.inline", "refactor.rewrite", "source", "source.organizeImports" }            }          },          dataSupport = true,          dynamicRegistration = true,          isPreferredSupport = true,          resolveSupport = {            properties = { "edit" }          }        },        completion = {          completionItem = {            commitCharactersSupport = false,            deprecatedSupport = false,            documentationFormat = { "markdown", "plaintext" },            preselectSupport = false,            snippetSupport = false          },          completionItemKind = {            valueSet = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }          },          completionList = {            itemDefaults = { "editRange", "insertTextFormat", "insertTextMode", "data" }          },          contextSupport = false,          dynamicRegistration = false        },        declaration = {          linkSupport = true        },        definition = {          dynamicRegistration = true,          linkSupport = true        },        diagnostic = {          dynamicRegistration = false        },        documentHighlight = {          dynamicRegistration = false        },        documentSymbol = {          dynamicRegistration = false,          hierarchicalDocumentSymbolSupport = true,          symbolKind = {            valueSet = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 }          }        },        formatting = {          dynamicRegistration = true        },        hover = {          contentFormat = { "markdown", "plaintext" },          dynamicRegistration = true        },        implementation = {          linkSupport = true        },        inlayHint = {          dynamicRegistration = true,          resolveSupport = {            properties = { "textEdits", "tooltip", "location", "command" }          }        },        publishDiagnostics = {          dataSupport = true,          relatedInformation = true,          tagSupport = {            valueSet = { 1, 2 }          }        },        rangeFormatting = {          dynamicRegistration = true        },        references = {          dynamicRegistration = false        },        rename = {          dynamicRegistration = true,          prepareSupport = true        },        semanticTokens = {          augmentsSyntaxTokens = true,          dynamicRegistration = false,          formats = { "relative" },          multilineTokenSupport = false,          overlappingTokenSupport = true,          requests = {            full = {              delta = true            },            range = false          },          serverCancelSupport = false,          tokenModifiers = { "declaration", "definition", "readonly", "static", "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary" },          tokenTypes = { "namespace", "type", "class", "enum", "interface", "struct", "typeParameter", "parameter", "variable", "property", "enumMember", "event", "function", "method", "macro", "keyword", "modifier", "comment", "string", "number", "regexp", "operator", "decorator" }        },        signatureHelp = {          dynamicRegistration = false,          signatureInformation = {            activeParameterSupport = true,            documentationFormat = { "markdown", "plaintext" },            parameterInformation = {              labelOffsetSupport = true            }          }        },        synchronization = {          didSave = true,          dynamicRegistration = false,          willSave = true,          willSaveWaitUntil = true        },        typeDefinition = {          linkSupport = true        }      },      window = {        showDocument = {          support = true        },        showMessage = {          messageActionItem = {            additionalPropertiesSupport = false          }        },        workDoneProgress = true      },      workspace = {        applyEdit = true,        configuration = true,        didChangeConfiguration = {          dynamicRegistration = false        },        didChangeWatchedFiles = {          dynamicRegistration = true,          relativePatternSupport = true        },        inlayHint = {          refreshSupport = true        },        semanticTokens = {          refreshSupport = true        },        symbol = {          dynamicRegistration = false,          symbolKind = {            valueSet = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 }          }        },        workspaceEdit = {          resourceOperations = { "rename", "create", "delete" }        },        workspaceFolders = true      }    },    clientInfo = {      name = "Neovim",      version = "0.10.4"    },    initializationOptions = vim.empty_dict(),    processId = 19623,    rootPath = vim.NIL,    rootUri = vim.NIL,    trace = "off",    workDoneToken = "1",    workspaceFolders = vim.NIL  }}

The LSP is correctly started with rootPath = nil and workspaceFolders = nil.

Immediately afterwards though, we have a:

[DEBUG][2025-01-31 19:04:04] .../vim/lsp/rpc.lua:286	"rpc.send"	{  jsonrpc = "2.0",  method = "workspace/didChangeWorkspaceFolders",  params = {    event = {      added = { {          name = "/Users/xrisk",          uri = "file:///Users/xrisk"        } },      removed = {}    }  }}

After which Pyright starts scanning all the files in my disk. A random example:

[DEBUG][2025-01-31 19:04:08] .../vim/lsp/rpc.lua:286	"rpc.send"	{  jsonrpc = "2.0",  method = "workspace/didChangeWatchedFiles",  params = {    changes = { {        type = 1,        uri = "file:///Users/xrisk/Library/Preferences/ContextStoreAgent.plist"      } }  }}

Some relevant parts from manager.lua that seem responsible are:

https://github.com/neovim/nvim-lspconfig/blob/ead2fbc4893fdd062e1dd0842679a48bfb7bac5c/lua/lspconfig/manager.lua#L104-L106

which seems to unconditionally trigger a _notify_workspace_folder_added, without checking the value of single_file.

Commenting out this block seems to fix this issue.

Some other problematic parts seem to be:

https://github.com/neovim/nvim-lspconfig/blob/ead2fbc4893fdd062e1dd0842679a48bfb7bac5c/lua/lspconfig/manager.lua#L134-L142

Which attaches a workspace folder when reusing a client, without checking the value of single_file. root_dir here is the “pseudo-root”, so is not NIL.

——

It seems that this bit of the code is deprecated though (https://github.com/neovim/nvim-lspconfig/issues/3531#issuecomment-2565863378). I can send a PR for this, otherwise.

xrisk avatar Jan 31 '25 14:01 xrisk