Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
xrisk opened this issue Jan 31, 2025 · 4 comments
Labels
bug Something isn't working

Comments

@xrisk
Copy link

xrisk commented Jan 31, 2025

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:

new_config.on_init = util.add_hook_before(new_config.on_init, function(client)
self:_notify_workspace_folder_added(root_dir, client)
end)

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:

if (self._clients[root_dir] or {})[existing_client.name] then
self:_notify_workspace_folder_added(root_dir, existing_client)
return true
end
for _, dir_clients in pairs(self._clients) do
if dir_clients[existing_client.name] then
self:_notify_workspace_folder_added(root_dir, existing_client)
return true

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 (#3531 (comment)). I can send a PR for this, otherwise.

@xrisk xrisk added the bug Something isn't working label Jan 31, 2025
@xrisk
Copy link
Author

xrisk commented Jan 31, 2025

FYI, this does not reproduce with the new vim.lsp api.

vim.lsp.config.basedpyright = {
  cmd = {
    'basedpyright-langserver',
    '--stdio',
  },
  root_markers = { '.pyrightconfig.json', 'pyproject.toml' },
  filetypes = { 'python' },
}

vim.lsp.enable('basedpyright’)

@justinmk
Copy link
Member

Try vim.lsp.config in Nvim 0.11 (unreleased) neovim/neovim#31031

@xrisk
Copy link
Author

xrisk commented Jan 31, 2025

@justinmk I did #3598 (comment) it doesn’t reproduce. Is this worth fixing given that this is deprecated?

@justinmk
Copy link
Member

Sorry I missed that :D That is good news. We can keep this open, but will probably close all of these nvim-lspconfig "framework" issues once Nvim 0.11 is released.

nvim-lspconfig will continue to be the source of truth for most "configs". Just the "framework" part is no longer needed in Nvim 0.11.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants