[Extensions] Per-file language server #21990
Replies: 11 comments 1 reply
-
Just out of curiosity: how are you getting Sorbet to work with Zed in the first place? |
Beta Was this translation helpful? Give feedback.
-
@timothyshrugged i created a custom plugin that implemented the LSP plugin interface for zed. Then just called the sorbet language server underneath that. |
Beta Was this translation helpful? Give feedback.
-
I think addressing would improve Zed user experience with monorepos. So for example, my current settings might include things like:
In order to support this we need to specify (at the project / folder level) that the default method of associating LSPs to Language should be ignored/overridden by an alternate path based approach. Ideas? What about something like: "language_paths": [
{
"paths": ["webapp", "backend"],
"Ruby": {
"language_servers": ["ruby-lsp", "rubocop", "!solargraph", "..."],
},
"Markdown": {
"remove_trailing_whitespace_on_save": false
}
},
{
"paths": ["frontend"],
"Ruby": {
"language_servers": ["sorbet", "!rubocop", "!solargraph", "..."],
}
}
] Another option would be to support Definitely open to other suggestions. |
Beta Was this translation helpful? Give feedback.
-
I'm not sure it needs that level of config. I think that a "pool" of language servers, and when a file is opened, Zed calls some method in the language server API which says "hey this particular file has opened". The language server can look for the root, in whatever manner it needs to (in Sorbet's case, The LSP implementation in Zed can then return an existing running connection (if one exists for that root), or can start a new server in the pool and return that. |
Beta Was this translation helpful? Give feedback.
-
I struggle to imagine how this could be implemented in practice.
You are proposing an Language Server Protocol extension, which every LSP must implement. Additionally you are requiring a language server self-determine whether it should handle a particular directory which it can't possibly do (e.g. ruby-lsp can't know that sorbet should be used not itself) It also requires spawning the Sorbet LS in order to determine whether the Sorbet LS should be used. I currently have 15-20 potential LSPs configured for use with Zed, but in normal projects only 1-4 are started, all on demand. This is obviously a non-starter. Can you expand a little further on your use case? It sounds like you're trying to work around a sorbet LSP limitation -- it only supports operating on a workspace which has a |
Beta Was this translation helpful? Give feedback.
-
i'm not proposing an extension to LSP - just proposing passing the newly opened file path to the Zed extension, and then having it return a "handle" to an LSP that it should use from that point on. it's possible to have ruby-lsp and sorbet running together, and I don't think it's up to either of them to decide if they should run based on the other existing. if the user adds both LSPs, both should run. this is also not requiring sorbet to spawn - the Zed extension itself can determine what to start, in what directory, etc. This is how Neovim currently handles LSP. The neovim plugin looks at the file that has been opened, and says "okay, i have some heuristics to figure out if I should start, and if I do, what command to run, what root to run under, etc". for a usecase: imagine a monorepo. each project in the repo could be a project of any language. ruby, javascript, rust, go, etc. the root of the monorepo contains nothing but folders and a README. if I open that monorepo in Zed, how can I get an LSP to start for the rust project when I open a rust file, using the rust folder as the root for the LSP to i.e. find config, resolve relative paths, etc. and if I have 2 rust project in the monorepo, I might need 2 different LSPs, if the LSP has startup context based on it's root. You wouldn't want autocomplete to provide you with package information for So, Zed passes apologies for the long-winded response - i don't think I was clear at all before this. |
Beta Was this translation helpful? Give feedback.
-
i also realise i said "language server API" a lot. I meant the "Zed language server extension API", and not the API of the language server itself. |
Beta Was this translation helpful? Give feedback.
-
I'm having a slightly different problem that may not be specifically Zed-related, but I'll describe it here anyway. I am using both Deno and standalone TypeScript code in the same project. The Deno language server works fine, and I can exclude files that are not intended for it with deno.json. However, the TypeScript language server ignores the settings from tsconfig.json, and Zed always shows errors in the Deno code (specifically, it can't find Deno imports, even though compilation works fine). This feature would solve my problem. These issues don't occur in VSCode, though I haven't investigated why in detail. |
Beta Was this translation helpful? Give feedback.
-
@qystishere Thanks for the example. That is exactly the sort of problem I think Zed could reasonably fix.
Cool. It's actually a similar problem in some way, less bad, but still not great. The change to Extension API would likely be the addition of a function that allows an Extension to register a callback of the form The dumb implementation of this could mean that the extension had to hit the filesystem (hunting for a I'm open to other suggestions, but I think it's better to make the decision about which language servers should be spawned to be declarative and kept within the Zed core to avoid the async fan-out required to poll extensions. I really appreciate you bringing this up because it got me thinking about the settings changes that will be necessary to support operating Zed at the root of a mono-repo. We've got a bunch of work to do but it's definitely a worth goal. I'm going to go ahead and move this to a discussion because there's definitely more design/discussion needed before this is ready for an actual implementation. Thanks all. |
Beta Was this translation helpful? Give feedback.
-
I’ve found Sublime LSP’s model for this to be pretty easy to reason about. You can add your own (named) LSPs via command line args and, in most cases, define the paths that they run against. Sorbet itself looks for a specific folder to run against and you specify that in the args/config as well. This doesn’t solve specifically for per-file LSPs, but it does move some of the LSP abstraction away from Zed and into “hey, what would you call from a terminal to invoke this LSP how you’d like?” |
Beta Was this translation helpful? Give feedback.
-
@deecewan just to chime in, what you're describing is exactly the model I'm implementing right now (with a small caveat that you should also be able to have just one global instance of a language server for which |
Beta Was this translation helpful? Give feedback.
-
Check for existing issues
Describe the feature
For sorbet for Ruby, the language server needs exactly 1 input directory. This is the directory that contains the
sorbet/
directory (and by extension, the config).if I have a project that looks like this, for instance:
when I open
root/
in Zed, it will start the language server with the cwd set toroot/
. The problem is, is that Sorbet doesn't find asorbet/
directory there, so errors. Per-project, I could set a config value to say "loadapi/
" and then update the extension to launch the LSP correctly for that directory. But there's no way to check that, for the file I'm looking at, where that directory should be.Also, if I have a project that looks like this:
I can't start language servers for both, because a language server is based solely on a root, and there's no way to set config in that case, because it'd need two different values at the same time.
Ideally, I'd like to be able to get the file I'm looking at, and return a language server for that file. In the first instance, I'd get the ruby file, and return a root of
root/api
. In the second, I'd start 2 servers with unique IDs, and have the IDs cached (somehow...). Then for every file that is opened, my extension can start (or return) a language server that should handle that file.This is pretty much how neovim supports language servers right now, where the plugin is asked about each file, instead of the language as a whole.
Somewhat relatedly, even if I did set the
root/api
dir for the first case, Sorbet doesn't return fully-qualified files. Rather, it returns file names relative to theroot/api/
. When Zed tries to open them (say, for "Go to definition"), the file doesn't exist. It returnssome-file.rb
instead ofapi/some-file.rb
, for instance.If applicable, add mockups / screenshots to help present your vision of the feature
No response
Beta Was this translation helpful? Give feedback.
All reactions