zed
zed copied to clipboard
shopify/ruby-lsp as an alternate language server
Check for existing issues
- [X] Completed
Describe the feature
I believe https://github.com/Shopify/ruby-lsp recently became the recommended language server for VSCode. Wondering if there is a way to add it to Zed as an option. Ruby-lsp seems to be progressing quickly and would be a nice alternative to include.
If applicable, add mockups / screenshots to help present your vision of the feature
No response
Having the ability to choose your own language server makes sense. There are however some technical challenges that need to be solved with Zed before ruby-lsp would be a viable option. The biggest one being pull diagnostics. ruby-lsp does not report diagnostics to the client, but expect the client to ask for diagnostics at its own discretion. This isn't something Zed currently supports.
We should also keep in mind that ruby-lsp currently lack support for "Go to Definition", "Go to Type Definition" and "Find All References", something solargraph does support.
I'd also like have an option to set to use jedi-language-server instead of pyright.
Hi, in that same vein I'd like to use Lexical instead of ElixirLS (for Elixir). In previous versions of Zed one could(had to?) install a LSP yourself, so if users know how an LSP is downloaded -> installed etc, then I'm happy to do it on my own :D
We should also keep in mind that ruby-lsp currently lack support for "Go to Definition", "Go to Type Definition" and "Find All References", something solargraph does support.
that is true, but recent changes are a big step towards this. Anyway, would be very cool to be able to make a choice
hey there , is It possible for us to add new LSP for PowerShell in zed lsp-for-powershell, if this is possible how can i do it ?
Hi,
Ruby-LSP has progressed significantly. Is it now possible to set it up with Zed?
Can anyone enumerate the changes required in ruby-lsp so Zed can add support for it?
I'm willing to take a stab at it.
I'd be interested in this as well!
Also would be interested!
I have ruby-lsp
successfully booting up in https://github.com/zed-industries/zed/pull/8613. Problem seems to be that completions don't really work (it completes A
to ApplicationController
, but it won't complete a local method).
It seems like ruby-lsp
implements the LSP in a different way than the other language servers that we use.
So switching is not that straightforward, it seems. One has to go through and figure out how to reimplement completions, go-to-definition, updating file state, etc. for ruby-lsp
and only for ruby-lsp
because the other language servers work with our implementation.
It seems like ruby-lsp implements the LSP in a different way than the other language servers that we use.
So switching is not that straightforward, it seems. One has to go through and figure out how to reimplement completions, go-to-definition, updating file state, etc. for ruby-lsp and only for ruby-lsp because the other language servers work with our implementation.
This doesn't sound right. As far as I'm aware, ruby-lsp follows the language server protocol. Do you have an example of where it diverge from the specification?
I'm not saying it diverges from the protocol, I'm saying it implements it in a different way than the other language servers we use. Concrete example that I just ran into: completions and when they are triggered.
In Zed, we rely on language servers to tell us what the trigger characters are that they support for completions:
https://github.com/zed-industries/zed/blob/da083e7211cdcd85c114206ad1430a1b8d1906c9/crates/project/src/project.rs#L3285-L3294
We get those trigger characters (and other capabilities) from the language server's response to the initialize request.
Here, for example, is the rust-analyzer
response:
{
"jsonrpc": "2.0",
"id": 0,
"result": {
"capabilities": {
"positionEncoding": "utf-16",
"textDocumentSync": {
"openClose": true,
"change": 2,
"save": {}
},
"selectionRangeProvider": true,
"hoverProvider": true,
"completionProvider": {
"resolveProvider": true,
"triggerCharacters": [
":",
".",
"'",
"("
],
"completionItem": {
"labelDetailsSupport": false
}
},
"signatureHelpProvider": {
"triggerCharacters": [
"(",
",",
"<"
]
},
"definitionProvider": true,
"typeDefinitionProvider": true,
"implementationProvider": true,
"referencesProvider": true,
"documentHighlightProvider": true,
"documentSymbolProvider": true,
"workspaceSymbolProvider": true,
"codeActionProvider": {
"codeActionKinds": [
"",
"quickfix",
"refactor",
"refactor.extract",
"refactor.inline",
"refactor.rewrite"
],
"resolveProvider": true
},
"codeLensProvider": {
"resolveProvider": true
},
"documentFormattingProvider": true,
"documentRangeFormattingProvider": false,
"documentOnTypeFormattingProvider": {
"firstTriggerCharacter": "=",
"moreTriggerCharacter": [
".",
">",
"{",
"("
]
},
"renameProvider": {
"prepareProvider": true
},
"foldingRangeProvider": true,
"declarationProvider": true,
"workspace": {
"workspaceFolders": {
"supported": true,
"changeNotifications": true
},
"fileOperations": {
"willRename": {
"filters": [
{
"scheme": "file",
"pattern": {
"glob": "**/*.rs",
"matches": "file"
}
},
{
"scheme": "file",
"pattern": {
"glob": "**",
"matches": "folder"
}
}
]
}
}
},
"callHierarchyProvider": true,
"semanticTokensProvider": {
"legend": {
"tokenTypes": [
"comment",
"decorator",
"enumMember",
"enum",
"function",
"interface",
"keyword",
"macro",
"method",
"namespace",
"number",
"operator",
"parameter",
"property",
"string",
"struct",
"typeParameter",
"variable",
"angle",
"arithmetic",
"attribute",
"attributeBracket",
"bitwise",
"boolean",
"brace",
"bracket",
"builtinAttribute",
"builtinType",
"character",
"colon",
"comma",
"comparison",
"constParameter",
"derive",
"deriveHelper",
"dot",
"escapeSequence",
"invalidEscapeSequence",
"formatSpecifier",
"generic",
"label",
"lifetime",
"logical",
"macroBang",
"parenthesis",
"punctuation",
"selfKeyword",
"selfTypeKeyword",
"semicolon",
"typeAlias",
"toolModule",
"union",
"unresolvedReference"
],
"tokenModifiers": [
"documentation",
"declaration",
"static",
"defaultLibrary",
"async",
"attribute",
"callable",
"constant",
"consuming",
"controlFlow",
"crateRoot",
"injected",
"intraDocLink",
"library",
"macro",
"mutable",
"public",
"reference",
"trait",
"unsafe"
]
},
"range": true,
"full": {
"delta": true
}
},
"inlayHintProvider": {
"resolveProvider": true
},
"experimental": {
"externalDocs": true,
"hoverRange": true,
"joinLines": true,
"matchingBrace": true,
"moveItem": true,
"onEnter": true,
"openCargoToml": true,
"parentModule": true,
"runnables": {
"kinds": [
"cargo"
]
},
"ssr": true,
"workspaceSymbolScopeKindFiltering": true
}
},
"serverInfo": {
"name": "rust-analyzer",
"version": "0.3.1860-standalone (5346002d0 2024-02-25)"
}
}
}
And here is ruby-lsp
:
{
"jsonrpc": "2.0",
"id": 0,
"result": {
"capabilities": {
"textDocumentSync": {
"openClose": true,
"change": 2
},
"workspace": {
"workspaceFolders": {
"supported": true,
"changeNotifications": true
}
}
}
}
}
That's why we don't trigger completions after a .
: ruby-lsp
didn't send them to us.
I bet in the VS Code extension they trigger completions on other characters, but we currently rely on the language server to tell us (and I guess that's also why it's part of the protocol)
Okay, sorry, correction! ruby-lsp
does send us this:
{
"id": 0,
"result": {
"capabilities": {
"positionEncoding": "utf-16",
"textDocumentSync": {
"openClose": true,
"change": 2
},
"completionProvider": {
"triggerCharacters": [
"/"
],
"completionItem": {
"labelDetailsSupport": true
}
},
"hoverProvider": {},
"signatureHelpProvider": {
"triggerCharacters": [
"(",
" ",
","
]
},
"definitionProvider": true,
"documentHighlightProvider": true,
"documentSymbolProvider": {
"symbolKind": {
"value_set": [ 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 ]
},
"hierarchicalDocumentSymbolSupport": true
},
"codeActionProvider": {
"resolveProvider": true
},
"codeLensProvider": {},
"documentLinkProvider": {},
"documentFormattingProvider": true,
"documentOnTypeFormattingProvider": {
"firstTriggerCharacter": "{",
"moreTriggerCharacter": [
"\n",
"|",
"d"
]
},
"foldingRangeProvider": {
"lineFoldingOnly": true
},
"selectionRangeProvider": true,
"semanticTokensProvider": {
"documentSelector": {
"scheme": "file",
"language": "ruby"
},
"legend": {
"tokenTypes": [ "namespace", "type", "class", "enum", "interface", "struct", "typeParameter", "parameter", "variable", "property", "enumMember", "event", "function", "method", "macro", "keyword", "modifier", "comment", "string", "number", "regexp", "operator", "decorator" ],
"tokenModifiers": [ "declaration", "definition", "readonly", "static", "deprecated", "abstract", "async", "modification", "documentation", "default_library" ]
},
"range": true,
"full": {
"delta": false
}
},
"inlayHintProvider": {},
"diagnosticProvider": {
"interFileDependencies": false,
"workspaceDiagnostics": false
},
"workspaceSymbolProvider": true
},
"serverInfo": {
"name": "Ruby LSP",
"version": "0.14.3"
},
"formatter": "none"
},
"jsonrpc": "2.0"
}
But as you can see, the .
is missing as a completion trigger.
Small update here after digging into what VS Code does with ruby-lsp
:
-
ruby-lsp
barely never sends any completions back to atextDocument/completion
request, which is why we don't show a lot of them. -
But in VS Code you can get a lot of completions from
ruby-lsp
and that's because the extension implements support fortextDocument/documentSymbol
which we do not.
End of day update. Short version:
- opened a draft PR that adds
ruby-lsp
: https://github.com/zed-industries/zed/pull/8613 - I don't think
ruby-lsp
works better thansolargraph
out of the box. - VS Code and the
vscode-ruby-lsp
extension add a lot of fallbacks aroundruby-lsp
that end up resulting in more completions, better go-to-definition. (Concretely: we don't supporttextDocument/documentSymbols
, butruby-lsp
does and VS Code uses that to provide completion options of requests totextDocument/completions
don't return any results) - There might be a bug (in Zed, or in ruby-lsp) that results in
ruby-lsp
not picking up file changes
I don't think it's ready to be merged. But if someone wants to really have it
- I'm curious as to why: is
ruby-lsp
inherently better at some things thansolargraph
? Or do you want all the goodness you get withvscode-ruby-lsp
? - I think making it an option to switch between
solargraph
orruby-lsp
is a good idea. (See how we do it for Elixir: https://github.com/zed-industries/zed/blob/da083e7211cdcd85c114206ad1430a1b8d1906c9/crates/languages/src/lib.rs#L163-L182) - I'm happy to help and if you'd like to pair with me on this, feel free to book some time here: https://calendly.com/thorsten-ball-zed/60min
cc @vinistock π itβd be great to get your input on this.
VS Code and the vscode-ruby-lsp extension add a lot of fallbacks around ruby-lsp that end up resulting in more completions, better go-to-definition. (Concretely: we don't support textDocument/documentSymbols, but ruby-lsp does and VS Code uses that to provide completion options of requests to textDocument/completions don't return any results)
This is not correct. The VS Code extension has no fallbacks and doesn't augment any features outside of the specification.
It's hard to tell what might be going wrong here. Here are a few ideas that come to mind. Bear in mind that the Ruby LSP currently supports definition and completion for constants, modules, classes and require paths. We're working on method support, so you won't see these features for methods yet.
- We don't have completion on the dot character, because we don't yet support full completion for methods
- You should be seeing completion for constants, classes, modules and require paths. If not, then is Zed triggering completion for identifier characters? Completion options mentions that trigger characters are in addition to the default ones provided the clients ([a-zA-Z])
- To provide definition, completion, hover, etc, we index the codebase when we receive the
initialized
notification. Is Zed sending that? Otherwise, we'd never index the codebase and thus never find any declarations. That's the only way I can think of go to definition not working for constants
There might be a bug (in Zed, or in ruby-lsp) that results in ruby-lsp not picking up file changes
When you say not picking up file changes, are you talking about processing textDocument/didChange
? I can confirm that's working from the Ruby LSP side.
This is not correct. The VS Code extension has no fallbacks and doesn't augment any features outside of the specification.
I meant to say that VS Code uses textDocument/documentSymbols
as the fallback when textDocument/completions
is empty. Because in VS Code, you do get completions for methods and local variables and such. And since ruby-lsp
does provide documentSymbols
and VS Code uses them (which we don't), you end up getting more completions, even when textDocument/completions
is empty.
Bear in mind that the Ruby LSP currently supports definition and completion for constants, modules, classes and require paths.
Yup, all of these work in the branch I created. Local variables, methods, etc. don't. They work in VS Code though. (see above)
When you say not picking up file changes, are you talking about processing
textDocument/didChange
? I can confirm that's working from the Ruby LSP side.
Yeah, exactly. See https://github.com/zed-industries/zed/pull/8613:
We do send
textDocument/didChange
messages on change, but when I add a new class to a document and then reference it atextDocument/definition
request gets an empty response. But after a restart it works.
See this video too that I just recorded on that branch:
https://github.com/zed-industries/zed/assets/1185253/979429ca-a2ed-4dd9-bf39-1d07dbfa7c9d
In this video you can see that
- completions work in
require ""
- completions for constants and class names work
- I add a new class and save the file
-
didChange
anddidSave
messages are sent toruby-lsp
- but the new class doesn't show up in completions
- restarting ruby-lsp fixes it
I don't know where the bug here is or whether we're missing parts of the implemention of LSP (we don't implement the full protocol, which is why we don't have textDocument/documentSymbols
, etc., so that could be the reason too)
Thank you for all the context and sorry about the confusion! I now understand the issue.
We register for workspace/didChangeWatchedFiles so that the editor will watch any Ruby file for us and notify the server upon modifications.
Then we handle the notifications to update our index representation.
We currently don't update the index on textDocument/didChange
, which means new declarations are only available once you save the file (after we receive the didChangeWatchedFiles notification). We do need to change that and start indexing on didChange too, so that unsaved declarations show up.
Does Zed support watching files? I suspect that's likely the issue.
We seem to support workspace/didChangeWatchedFiles
, yes: https://github.com/zed-industries/zed/blob/36c48318065d61812f4c7d682cdaf63b9ebd6cf7/crates/project/src/project.rs#L3832-L3897 But I'm not too familiar with the code, nor with that part of the protocol to see what we might be missing. I'll need to dig in more.
It might be helpful to turn on server tracing. You'll be able to verify if the registration request came through fine and it the notifications are being sent.
This is how you can enable it in VS Code, I'm not sure what the Zed equivalent would be.
{
"ruby lsp.trace.server": "messages"
}
FWIW, I also created an issue to add a fallback mechanism for clients that do not support file watching.
Yep, already had that tracing enabled locally, just didn't know where to look.
So, I think there's a bug on our side, like suspected.
We do get the following message from ruby-lsp
:
{
"id": 1,
"method": "client/registerCapability",
"params": {
"registrations": [
{
"id": "workspace/didChangeWatchedFiles",
"method": "workspace/didChangeWatchedFiles",
"registerOptions": {
"watchers": [
{ "globPattern": "**/*.rb", "kind": 7 }
]
}
}
]
},
"jsonrpc": "2.0"
}
But when we then try to setup the file watchers here: https://github.com/zed-industries/zed/blob/a25edcc5a894c4fea13e4684329aff84821cc5e9/crates/project/src/project.rs#L3831-L3839
Nothing happens because we assert that in our internal state tracking the LSP is marked as running, but it's not! It's still marked as Starting
for me locally. So when we receive that message, we essentially just drop it.
@mrnugget - sorry to bug, but are there any further updates on your PR? I'm trying to pick up Rust in my spare time to put a PR together but it's moving slowly and the lack of support for this is probably my last hangup, so I'm anxious to see support be added! If not, no worries. Thanks for all you do!