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 the Ruby 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.

Table of Contents

Code Navigation With Ruby LSP

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.

Hover

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

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:

With 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.

Completion

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

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.

Code Navigation Enhancements

Let’s take a closer look at the improvements that have been made to these features.

Singleton Methods

All code navigation features for singleton methods have been significantly improved.

Local Variables

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.

Inheritance and Mixins

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:

This video demonstrates some of these enhancements:

Limitations:

Ruby Core Classes, Modules, and Methods

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!

Code Navigation Enhancements for the Rails Addon

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.)

example screenshot of codelens

Now let’s dive into the specific improvements.

Go-to-Definition for ActiveRecord Model Callbacks and Associations

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:

Code Navigation in .erb Files

Ruby 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.)

Experimental Features

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:

Ancestors Hierarchy Request

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:

Why Is It Experimental?

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.

Guessed Types

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.

How It Works

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.

Important Notes

For more information, please refer to the documentation.

Recap

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.