Root directory misses the point

The current approach seems to be: when a file is opened, start a new language server if there is no language server currently running with a root directory that encompasses the file. So it is immediately assumed that a language server running on /home/you/project cannot possibly answer questions about, say, /opt/GNAT/2021/lib/gcc/x86_64-pc-linux-gnu/10.3.1/rts-native/adainclude/a-envvar.ads or /usr/include/c++/10/bits/stl_algo.h in any meaningful way, and hence a new language server needs to be started up for just that file. Please correct me if I am wrong in understanding that all lsp configs work this way.

This design precludes the ability to, for instance, open my project, then go to the definition of some language built-in function and then find-references on that. I would expect that to return the references in my project.

I imagine how it would work is that each lsp server config defines how to ask the language server whether some file is part of that same project or not. If yes, that language server is reused instead of spinning up a new instance.

For Ada and .gpr project files, I think this will be some fairly trivial call to gprbuild, passing the .gpr project file used by ALS.

For other languages it may be more involved. For instance, in python you would need to analyze the imports atop the file, but actually to be strictly correct you need to analyze all imports in the current language server’s root directory.

Thoughts on this?

WorkspaceFolders/rootUri/rootPath (aka root directory) is something codified in the lsp specification which is required for certain project level features (like indexing). It’s not an lspconfig design choice but a reality of the protocol and is used for things beyond what you are asking about internally by language servers.

For Ada and .gpr project files, I think this will be some fairly trivial call to gprbuild, passing the .gpr project file used by ALS. For other languages it may be more involved. For instance, in python you would need to analyze the imports atop the file, but actually to be strictly correct you need to analyze all imports in the current language server’s root directory.

This sounds extremely messy/error prone/a large maintenance burden.

A simpler version is to attach the client to files that you navigate via go-to-definition, AFAIK vscode does not work this way but lsp-mode does. It’s a bit harder with references since we don’t change the buffer the handler terminates so you’d have to write an autocommand that does this, and the logic might get a bit tricky.

As far as I can tell, the ada language server just totally ignores the root directory passed. So the only usage of it that affects me is the logic of when to start a new language server rather than using the one already running. Is there currently a way to override that logic? Would you welcome a PR that adds such an optional override?

I don’t think making this specific to ‘go-to-definition’ is acceptable. Another use case example is as follows: from your project, go-to-definition for a package within os.environment, but then open a file next to it (for example with :e %:h), and search for references on that package.

I get the right behaviour by setting the on_init function to store & load a global variable with the project file. That is, once a project has been selected, always use that one for all future instances. The only downside is I am running multiple redundant language servers, where one would suffice.