Skip to content

Conversation

atusy
Copy link
Contributor

@atusy atusy commented Oct 7, 2025

This PR improves type annotation of vim.lsp.Config.root_dir by allowing function as documented in https://neovim.io/doc/user/lsp.html#lsp-root_dir().

The fix also required changes in vim.lsp._get_workspace_folders and :checkhealth vim.lsp.

I ran :cheackhealth vim.lsp and confirmed root_dir as function works in monorepo with ts_ls.

vim.lsp: Active Clients ~
- ts_ls (id: 2)
  - Version: ? (no serverInfo.version response)
  - Root directory: ~/ghq/github.com/xxx/xxx/xxx
  - Command: { "typescript-language-server", "--stdio" }
  - Settings: {}
  - Attached buffers: 1
- ts_ls (id: 3)
  - Version: ? (no serverInfo.version response)
  - Root directory: ~/ghq/github.com/xxx/xxx/yyy
  - Command: { "typescript-language-server", "--stdio" }
  - Settings: {}
  - Attached buffers: 2

@github-actions github-actions bot added the lsp label Oct 7, 2025
@github-actions github-actions bot requested review from MariaSolOs and ribru17 October 7, 2025 15:59
@atusy atusy marked this pull request as draft October 7, 2025 16:19
@MariaSolOs
Copy link
Member

@atusy you need to run make doc to update the text files.

@atusy
Copy link
Contributor Author

atusy commented Oct 7, 2025

thanks! I will

@atusy
Copy link
Contributor Author

atusy commented Oct 7, 2025

I also need to fix type inconsistencies...

I will make some time later.

  local config_folders = lsp._get_workspace_folders(config.workspace_folders or config.root_dir)

@atusy atusy force-pushed the fix-lsp-config-root-dir branch 2 times, most recently from 6a0a396 to 60fb995 Compare October 8, 2025 15:32
@atusy atusy changed the title fix(lsp): type of root_dir should be annotated with string|fun|nil fix(lsp): should support vim.lsp.Config.root_dir as fun, not only string|nil Oct 8, 2025
@atusy atusy marked this pull request as ready for review October 8, 2025 16:00
@github-actions github-actions bot requested review from MariaSolOs and ribru17 October 8, 2025 16:00
@atusy
Copy link
Contributor Author

atusy commented Oct 8, 2025

Okay, seems like everything is clear.

@justinmk
Copy link
Member

justinmk commented Oct 8, 2025

Are you able to write a test to exercise this

@atusy
Copy link
Contributor Author

atusy commented Oct 8, 2025

I'll try

@atusy
Copy link
Contributor Author

atusy commented Oct 9, 2025

@justinmk
I added test cases to vim.lsp.start which relies on the unexported _get_workspace_folder.

@atusy atusy force-pushed the fix-lsp-config-root-dir branch 3 times, most recently from a4cb2df to 2fa0caf Compare October 9, 2025 23:53
@atusy atusy requested a review from justinmk October 10, 2025 05:23
local name ---@type string
workspace_folders(0, function(root_dir)
name = root_dir
end)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need a similar vim.wait approach here, in case root_dir is async.

'- Root directories:\n %s',
table.concat(
vim.iter(root_dirs):map(function(x)
vim.fn.fnamemodify(x, ':~')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forgot return here. i will fix this in a push.

Comment on lines 96 to 97
for buf, _ in pairs(client.attached_buffers) do
client.root_dir(buf, function(dir)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if there are many attached_buffers all resolving to the same root, this will show many redundant root dirs.

i will fix this in a push.

Examples:

    vim.lsp: Active Clients ~
    - lua_ls (id: 1)
      - Version: <Unknown>
      - Root directories:
          ~/foo/bar
          ~/dev/neovim
@justinmk justinmk force-pushed the fix-lsp-config-root-dir branch from 2fa0caf to ff121a2 Compare October 11, 2025 22:55
@justinmk justinmk merged commit 97ab24b into neovim:master Oct 11, 2025
32 of 34 checks passed
justinmk added a commit to justinmk/neovim that referenced this pull request Oct 11, 2025
backport neovim#36071

* fix(lsp): type of root_dir should be annotated with string|fun|nil
* feat(lsp): support root_dir as function in _get_workspace_folders
* feat(lsp): let checkhealth support root_dir() function

Examples:

    vim.lsp: Active Clients ~
    - lua_ls (id: 1)
      - Version: <Unknown>
      - Root directories:
          ~/foo/bar
          ~/dev/neovim

Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
@neovim neovim deleted a comment from neovim-backports bot Oct 11, 2025
@atusy
Copy link
Contributor Author

atusy commented Oct 12, 2025

thanks a lot!

justinmk added a commit to justinmk/neovim that referenced this pull request Oct 12, 2025
backport neovim#36071

* fix(lsp): type of root_dir should be annotated with string|fun|nil
* feat(lsp): support root_dir as function in _get_workspace_folders
* feat(lsp): let checkhealth support root_dir() function

Examples:

    vim.lsp: Active Clients ~
    - lua_ls (id: 1)
      - Version: <Unknown>
      - Root directories:
          ~/foo/bar
          ~/dev/neovim

Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
justinmk added a commit that referenced this pull request Oct 12, 2025
…36141)

backport #36071

* fix(lsp): type of root_dir should be annotated with string|fun|nil
* feat(lsp): support root_dir as function in _get_workspace_folders
* feat(lsp): let checkhealth support root_dir() function

Examples:

    vim.lsp: Active Clients ~
    - lua_ls (id: 1)
      - Version: <Unknown>
      - Root directories:
          ~/foo/bar
          ~/dev/neovim

Co-authored-by: atusy <30277794+atusy@users.noreply.github.com>
@mfussenegger
Copy link
Member

mfussenegger commented Oct 12, 2025

Isn't the annotation change sort of wrong?

While lsp.config() can handle a root_dir as a function (vim.lsp.Config), once the client is initialized and started it must be resolved, because it gets used. So vim.lsp.ClientConfig doesn't have to support the function case.

Resolving happens in:

config.root_dir(bufnr, function(root_dir)
config.root_dir = root_dir
vim.schedule(function()
start_config(bufnr, config)
end)
end)

With this change it is implied that the root_dir on a vim.lsp.Client can also be a function, which is unnecessary and would also be a breaking change.

@justinmk
Copy link
Member

once the client is initialized and started it must be resolved, because it gets used.

true, and very important.
now I am wondering about other parts of this entire PR.

@mfussenegger
Copy link
Member

now I am wondering about other parts of this entire PR.

On a closer look, none of the changes here make sense to me but maybe I'm missing the problem

@atusy
Copy link
Contributor Author

atusy commented Oct 12, 2025

The change I made is related to switching code formatters per project.
I will explan more later, although my explanation may not justify the change. I mean there might be an alternative.

@atusy
Copy link
Contributor Author

atusy commented Oct 14, 2025

I think @mfussenegger 's suggestion make sense. vim.lsp.Config.root_dir should not be a function, and we should fix the case where vim.lsp.Config.root_dir becomes function.

Just for clarifications, let me explain the backgrounds to open this PR.

I am switching formatter by looking at files in the root_dir.
Recently, ts_ls started to return vim.lsp.Client with root_dir as a function.

-- This used to be string|nil, but recently it became function.
local root_dir = vim.lsp.get_clients({name = "ts_ls", bufnr = vim.api.nvim_get_current_buf()})[1].root_dir

I was surprised because LSP showed vim.lsp.Client.root_dir is not a function.
That is why I made a PR.

@mfussenegger
Copy link
Member

Recently, ts_ls started to return vim.lsp.Client with root_dir as a function.

Sounds like there is a call to vim.lsp.start() with a config where root_dir isn't resolved. That's not allowed.
The proper fix would be to tighten the validation to ensure it results in an error.

justinmk pushed a commit that referenced this pull request Oct 15, 2025
@justinmk justinmk changed the title fix(lsp): should support vim.lsp.Config.root_dir as fun, not only string|nil [reverted] fix(lsp): should support vim.lsp.Config.root_dir as fun, not only string|nil Oct 15, 2025
@mfussenegger
Copy link
Member

One thing to add here: We could change vim.lsp.start() to resolve root_dir. It feels a bit strange to me because the evaluation can't be deferred further but needs to happen right away but it would make the vim.lsp.start(vim.lsp.config.myserver) use-case a bit more convenient. So far users need to do something like this (untested):

local conf = vim.lsp.config.myserver
if type(conf.root_dir) == "function" then
  conf.root_dir(bufnr, function(root_dir)
    conf.root_dir = root_dir
    vim.lsp.start(conf)
  end)
else
  vim.lsp.start(conf)
end

What we shouldn't do because it is breaking is the example you gave with:

-- This used to be string|nil, but recently it became function.
local root_dir = vim.lsp.get_clients({name = "ts_ls", bufnr = vim.api.nvim_get_current_buf()})[1].root_dir

The exposed root_dir from the client must not become a function to avoid breaking plugins which use the root_dir property. (And also because it would be nonsense to have to re-evaluate it on each use when it was already evaluated)

But there's also still in the air to revert root_dir being a function on vim.lsp.Config altogether, and instead make the whole config a function, then you'd have:

local conf = vim.lsp.config.myserver
if type(conf) == "function" then
  conf(bufnr, function(fullconf)
    vim.lsp.start(fullconf)
  end)
else
  vim.lsp.start(conf)
end

Or maybe something like:

vim.lsp.get_config(function(conf)
  vim.lsp.start(conf)
end)

raymond-w-ko pushed a commit to raymond-w-ko/neovim that referenced this pull request Oct 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants