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:

  • Right click on the target, and then select Go to Definition
  • Placing the cursor on the target, and then hit F12
  • Command + click the target

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:

  • Singleton Methods: All code navigation support for singleton methods now works on inherited singleton methods too.
  • Super: Added hover and go-to-definition support for the super keyword, allowing developers to easily trace the method call chain.
  • Methods Defined Through Superclasses and Mixins: All code navigation features now support methods defined in superclasses and mixins, making it easier to understand the inheritance structure.
  • Instance and Class Instance Variables: Added hover, go-to-definition, and completion support for instance variables and class instance variables, including inherited variables.

This video demonstrates some of these enhancements:

Limitations:

  • Ruby LSP indexes the user’s entire codebase and dependencies by default, but it doesn’t know which files will eventually be required in your application. In some rare cases, class, module, or method definitions may show up in the go-to-definition or completion results, but may not be used by your application during runtime.
  • While the navigation improvements are substantial, there are still some limitations, such as runtime mixin operations, or mixins added through instance_eval, module_eval, and class_eval. For certain cases, like Rails Concerns, the plan is to support them through addons.

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:

  • Hover displays documentation.
  • Go-to-definition leads users to the respective RBS declarations.
  • Completion provides them as completion candidates.
  • Signature help provides signature information when invoking 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:

  • Go-to-definition for route helper definitions.
  • CodeLens for route definitions and view files.

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:

  • Visualize the inheritance hierarchy of classes and modules.
  • Quickly navigate through the inheritance chain.

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:

  • Should the list include only classes (pure inheritance chain), or should it include modules too (current behavior)?
  • How should the inheritance chain of singleton classes be triggered and displayed?
  • If a class or module is reopened multiple times, it will appear multiple times in the list. In real-world applications, this can make the list very long.

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

  • Identifiers are not ideal for complex type annotations and can be easily misled by non-matching names.
  • We do NOT recommend renaming identifiers just to make this feature work.

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.