neovim icon indicating copy to clipboard operation
neovim copied to clipboard

Decouple vim.lsp.config `root_markers` from LSP

Open mfussenegger opened this issue 10 months ago • 11 comments

Problem

People keep wanting to add "find project root" logic into various linters in nvim-lint. All/most of the time the logic would match what they're already doing for their LSP servers. That's a strong indication that the "project root" concept should be a general thing, not tied to LSP to make it re-usable for plugins

Current workaround to re-use that logic - assuming a client/server is running - is something like:

require("lint").try_lint(nil, { cwd = vim.lsp.get_clients()[1].root_dir })`

Other use cases:

  • https://github.com/mfussenegger/nvim-dap/discussions/1530

Expected behavior

  • Have root_markers definitions as part of filetype plugins or buffer variable
  • Have a readonly root_dir or project_root buffer ~variable~ option or a function mirroring getcwd or something like that, which plugins can use to get the project root for the current buffer

mfussenegger avatar Jun 23 '25 20:06 mfussenegger

It should be a buffer option as opposed to a variable.

lewis6991 avatar Jun 24 '25 11:06 lewis6991

Related:

  • https://github.com/neovim/neovim/pull/33771

Have a readonly root_dir or project_root buffer variable

Does https://github.com/neovim/neovim/pull/33320 address that, or should we drop the :bcd idea?

justinmk avatar Jun 24 '25 13:06 justinmk

I think if we add bcd, it could be used as a good fallback, but I don't think users should have their cwd forced in order for LSP to work.

lewis6991 avatar Jun 24 '25 14:06 lewis6991

Having both seems a bit much. It sounds to me like :bcd may be the wrong direction.

justinmk avatar Jun 24 '25 14:06 justinmk

From what I understand bcd was wanted for different reasons. Basically the same as tcd but for buffers. So what we decide here shouldn't impact the decision of that.

lewis6991 avatar Jun 24 '25 15:06 lewis6991

From what I understand bcd was wanted for different reasons. Basically the same as tcd but for buffers.

No, the discussion in https://github.com/neovim/neovim/issues/33318 seems clearly motivated by "root directory" and "project" use-cases.

justinmk avatar Jun 24 '25 15:06 justinmk

But also:

Note also that a requirement is to have an ergonomic way to get the buffer's "current directory". At least via getcwd(), but also maybe a buffer-local option or variable? (Need to hear comments on that.)

lewis6991 avatar Jun 24 '25 16:06 lewis6991

At least for nvim-lint and nvim-dap-python where I do have the use-cases for a project root a bcd would work I guess. But I also already follow a cwd=project-root model and don't know all the reasons why people might have a cwd!=project-root setup.

E.g. I assume fuzzy file finders/matchers to jump around projects would need to take care to not use bcd in some cases?

mfussenegger avatar Jun 24 '25 17:06 mfussenegger

I see two use cases:

  1. Plugins can find the project root
  2. Commands like :e, :grep, :pwd or :term should use the project root

Having (1) is a low-risk win. What about 2? Should it be in core? or left to vim-rooter style plugins that glue a 'rootdir' buffer option?

If we want (2), then how will it interact with :lcd, :tcd and getcwd()? Implementing (2) requires touching internal state, and thus is more complex. We may want to defer it, but the machinery it uses should ideally be the same as use case (1).

msaher avatar Sep 09 '25 09:09 msaher

Have root_markers definitions as part of filetype plugins or buffer variable

Based on observation of nvim-lspconfig, a filetype's root_markers may change based on the LSP client(s) attached. E.g. a javascript file may be a deno, bun, or some other kind of project.

So it doesn't seem to map to a ftplugin.

However, storing the resolved "root dir" as state on a buffer makes sense? Though multiple LSP clients could override each other's decision.

One reason that buffer-local CWD is interesting: it removes the need to store CWD in the term:// URI.

justinmk avatar Nov 21 '25 22:11 justinmk

If I think through the quickfix/grep/plugin use case, I think bcd and root markers address different things.

bcd relates to where the buffer itself is. If I am creating a preview window for the quickfix list, I might want to create a scratch buffer, assign a cwd buffer option to that, and then use it to cache/setup other buffers in the same directory. Or if I want to do a grep based on other buffers in the same directory, using bcd would be more straightforward than manually hacking through bufname. It would also create a simple/universally available source of truth everything could point toward.

(bcd could also be used for convenience functions for the user where, for example, you can create a new buffer and assign it a bcd on creation, then enter a filename to save it and use bcd to fill in where the filename goes to)

Whereas root markers tells you what projects the buffer is associated with. If I'm doing a location list project grep for a buffer, I would want to know what root markers/root dirs the project is associated with, so I could use that to build out the file origins. This also points strongly toward root markers being an option rather than a buffer variable or something set in the ftplugin file, as a buffer option gives me predictable behavior I can build a "project grep" based on (or, alternatively, some kind of managed behavior in a Lua module).

(The above also implies another question - Do you store the root markers? Root dirs? Both? If you only store the root markers, they would need to be resolved, which is a file system op, each time they are used)

mikejmcguirk avatar Nov 23 '25 19:11 mikejmcguirk