zed icon indicating copy to clipboard operation
zed copied to clipboard

shopify/ruby-lsp as an alternate language server

Open kcmannem opened this issue 1 year ago β€’ 23 comments

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

kcmannem avatar Apr 06 '23 00:04 kcmannem

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.

hovsater avatar Apr 06 '23 06:04 hovsater

I'd also like have an option to set to use jedi-language-server instead of pyright.

failable avatar Apr 28 '23 15:04 failable

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

Tuxified avatar Jul 13 '23 20:07 Tuxified

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

Vagab avatar Nov 27 '23 17:11 Vagab

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 ?

SPR-Ideas avatar Dec 29 '23 08:12 SPR-Ideas

Hi,

Ruby-LSP has progressed significantly. Is it now possible to set it up with Zed?

sriranggd avatar Jan 24 '24 05:01 sriranggd

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.

matteeyah avatar Jan 28 '24 12:01 matteeyah

I'd be interested in this as well!

arbales avatar Feb 06 '24 03:02 arbales

Also would be interested!

maxcroy1 avatar Feb 16 '24 17:02 maxcroy1

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.

mrnugget avatar Feb 29 '24 15:02 mrnugget

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?

hovsater avatar Feb 29 '24 15:02 hovsater

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)

mrnugget avatar Feb 29 '24 15:02 mrnugget

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.

mrnugget avatar Feb 29 '24 16:02 mrnugget

Small update here after digging into what VS Code does with ruby-lsp:

  • ruby-lsp barely never sends any completions back to a textDocument/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 for textDocument/documentSymbol which we do not.

mrnugget avatar Feb 29 '24 16:02 mrnugget

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 than solargraph out of the box.
  • 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)
  • 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

  1. I'm curious as to why: is ruby-lsp inherently better at some things than solargraph? Or do you want all the goodness you get with vscode-ruby-lsp?
  2. I think making it an option to switch between solargraph or ruby-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)
  3. 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

mrnugget avatar Feb 29 '24 17:02 mrnugget

cc @vinistock πŸ‘‹ it’d be great to get your input on this.

joeldrapper avatar Mar 05 '24 01:03 joeldrapper

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.

  1. We don't have completion on the dot character, because we don't yet support full completion for methods
  2. 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])
  3. 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.

vinistock avatar Mar 05 '24 15:03 vinistock

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 a textDocument/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 and didSave messages are sent to ruby-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)

mrnugget avatar Mar 05 '24 16:03 mrnugget

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.

vinistock avatar Mar 05 '24 16:03 vinistock

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.

mrnugget avatar Mar 05 '24 16:03 mrnugget

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.

vinistock avatar Mar 05 '24 16:03 vinistock

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 avatar Mar 05 '24 17:03 mrnugget

@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!

maxcroy1 avatar Apr 18 '24 14:04 maxcroy1