In the first half of 2024, Ruby LSP has seen significant enhancements, particularly in the area of code navigation, thanks to the advancement of its indexer.
In this post, we’ll dive into the major code navigation enhancements that have been made to Ruby LSP. We’ll also touch on some experimental features that are on the horizon.
NOTE
While the Ruby LSP server (
ruby-lsp
gem) can be integrated with many editors, all the features in this article will be demoed in VS Code with theRuby LSP
extension on a macOS machine.Therefore, some of the features may not work the same way in non-VS Code editors, or may require different keyboard shortcuts depending on your development environment.
Since this post focuses heavily on code navigation features in editors, let’s dive into these features in more detail.
Code navigation generally refers to four key editor features: hover, go-to-definition, completion, and signature help. When paired with language servers, these features can dramatically improve developer productivity.
The hover feature displays comments or documentation for the target constant or method when the cursor hovers over them.
In VS Code, if you hover while pressing Command
, it will also send a definition
request to locate the possible target sources. And it will display the target’s source code if only one source is located (e.g., the class is not reopened in multiple places).
Go-to-definition allows users to navigate to the target constant or method’s definition, whether they’re defined in your project or its dependencies.
This feature can be triggered by one of the following methods:
Right click
on the target, and then select Go to Definition
F12
Command + click
the targetWith One Definition:
Users are taken directly to the source.
With Multiple Definitions:
Users see a dropdown with all the sources, along with a preview window on the side.
The completion feature provides users with completion candidates when the text they type matches certain indexed components. This helps speed up coding by reducing the need to type out full method names or constants. It also allows developers to discover constants or methods that are available to them.
Signature help often appears right after users finish typing a method, providing hints about the method’s parameters. This feature is invaluable for understanding the expected arguments and improving code accuracy.
Let’s take a closer look at the improvements that have been made to these features.
All code navigation features for singleton methods have been significantly improved.
Ruby LSP now offers completion support for local variables. This improvement helps developers understand the scope and usage of variables within their code, reducing the likelihood of errors and improving code readability.
Ruby LSP’s indexer is now capable of estimating the inheritance hierarchy of your program, allowing it to locate method, variable, and constant definitions through the ancestors list. This includes superclasses and mixins (prepend
, include
, and extend
).
The reason this is considered an estimation is due to Ruby’s autoload
feature making it difficult to determine the exact order in which files end up being required (which may impact how the inheritance chain is constructed).
This advancement has enabled several new enhancements that were previously difficult to achieve:
super
keyword, allowing developers to easily trace the method call chain.This video demonstrates some of these enhancements:
Limitations:
instance_eval
, module_eval
, and class_eval
. For certain cases, like Rails Concerns, the plan is to support them through addons.Ruby LSP now offers various code navigation features for Ruby core classes and modules, such as String
and Enumerable
, as well as methods:
Limitation:
While Ruby LSP can provide documentation for literal class constants, like String
or Array
, it still cannot provide documentation for their literals, like "str"
or [1, 2, 3]
.
We’re building a type inferrer to enable such functionality and support on literals should come soon. So stay tuned!
The Rails addon has also seen significant improvements in the area of code navigation.
The table below summarizes the available navigation features and the components they connect:
Navigation Feature | From | To | Description |
---|---|---|---|
Go-to-definition | Model | Model | Navigate to another model through associations. |
CodeLens | Controller | View | Jump to relevant views on action methods. |
Go-to-definition | Controller | Route | Navigate to relevant routes through route helpers. |
CodeLens | Controller | Route | Navigate to relevant route definitions on action methods. |
Go-to-definition | Controller | Model | Navigate to relevant models through constants. |
Go-to-definition | View | Model | Navigate to relevant models through constants. |
Go-to-definition | View | Route | Navigate to relevant routes through route helpers. |
(Note: CodeLens are clickable items that can be surfaced on top of code in the editor. It’s not a code-navigation-specific feature, but in the Rails addon we use it to trigger jumping actions in controllers.)
Now let’s dive into the specific improvements.
Developers can now navigate to the definitions of ActiveRecord callbacks and between associated models.
The Rails addon now provides two new ways of navigating between controllers, route definitions, and views:
.erb
FilesRuby LSP now adds code navigation support to .erb
files, allowing users to access all code navigation
features in those files as well.
(Technically, this is not a Rails-exclusive enhancement, but because it enables all the previously mentioned code navigation features in view files, it brings a dramatic improvement to the Rails development experience.)
The Ruby LSP team is constantly working on new features to enhance the development experience. Here are a couple of experimental features that are currently in the works:
The ancestors hierarchy request feature aims to provide a better understanding of the inheritance hierarchy within your Ruby code. This feature helps developers trace the lineage of their classes and modules, making it easier to:
This feature is supported by the Type Hierarchy Supertypes LSP request. During implementation, we encountered some ambiguities when applying it to Ruby. For example:
We created an issue to seek clarification from the LSP maintainers. We will adjust this feature’s design and behavior based on their response and your feedback.
The guessed types feature is an experimental addition to Ruby LSP that attempts to identify the type of a receiver based on its identifier name. This helps improve code completion and navigation by providing type information.
This feature is disabled by default but can be enabled with the rubyLsp.enableExperimentalFeatures
setting in VS Code.
Ruby LSP guesses the type of a variable by matching its identifier name to a class. For example, a variable named user
would be assigned the User
type if such a class exists:
user.name # Guessed to be of type `User`
@post.like! # Guessed to be of type `Post`
By guessing the types of variables, Ruby LSP can expand the code navigation features to even more cases.
For more information, please refer to the documentation.
The first half of 2024 has brought significant improvements to Ruby LSP, particularly in the area of code navigation. From enhanced support for singleton methods, local variables, and inheritance, to ERB support, Ruby LSP is evolving to make Ruby developers’ lives easier. The Rails addon has also seen major enhancements, simplifying navigation between models, views, and controllers. With experimental features like the ancestors hierarchy request and guess type, Ruby LSP continues to adopt new LSP features and explore creative approaches.
While Ruby LSP project is primarily driven by the Ruby Developer Experience team at Shopify, our aim is to continue running it as a collaborative open-source project serving the Ruby community overall. For that reason, we’d like to thank all the contributors who have reported issues, provided feedback, or contributed code to Ruby LSP and its Rails addon. For contributions from outside Shopify, we would like to give a special shoutout to @snutij and @Earlopain for their continuous contributions to the project.
Please give these new features a try, and share your feedback via GitHub issues or the Ruby DX Slack.