Skip to content

Conversation

@janko
Copy link
Contributor

@janko janko commented Oct 28, 2025

It's common to want to traverse through several partials while updating HTML that a controller action renders. Rails.vim has a neat gf shortcut for this. This brings the same functionality to Ruby LSP Rails, by implementing "go to definition" support for render calls inside ERB templates.

Screen.Recording.2025-10-28.at.21.08.51.mov

It supports partial name passed as positional argument, or via :partial, :layout, and :spacer_template keyword arguments. It even handles :variants, :formats, and :handlers options, as well as :template for rendering non-partial templates.

The initial implementation mimicked the template lookup logic inside the language server. However, that turned out to be more complex and didn't handle custom view_paths. So, I ended up calling ActionView::LookupContext to perform the actual template lookup on the server. I also needed to determine the controller from the template directory in a way that handles custom view_paths, as well as handle controllerless template directories.

To avoid the overhead of booting the Rails process too many times in tests, I updated the test helpers to allow sending multiple textDocument/definition requests to the same server.

It's common to want to traverse through several partials while updating
HTML that a controller action renders. Rails.vim has a neat `gf`
shortcut for this, though it probably doesn't have the precision that
Prism would provide.

This brings the same functionality to Ruby LSP Rails, by implementing
"go to definition" support for render calls inside ERB templates. It
supports partial name passed as positional argument, or via `:partial`,
`:layout`, and `:spacer_template` keyword arguments. It even handles
`:variants`, `:formats`, and `:handlers` options, as well as `:template`
for rendering non-partial templates. Relative lookup will also check
in view directories of controller ancestors.

For the latter, I considered doing a call to the Rails process that will
return `ActionController::Base._prefixes`. However, I couldn't think of
a good enough interface, and that method ableit public is undocumented,
so it seems like we shouldn't rely on it. Given that this ancestry
lookup is non-configurable anyway, I chose to implement it in Ruby LSP
land based on indexed controller files.

To avoid the overhead of booting the Rails process too many times in
tests, I updated the test helpers to allow sending multiple
`textDocument/definition` requests to the same server.
@janko janko requested a review from a team as a code owner October 28, 2025 20:11
Copy link
Member

@rafaelfranca rafaelfranca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made a few comments. Typechecker is failing and new methods are missing typing information.

janko added 2 commits November 1, 2025 20:22
Also add missing type annotations.
While here, we undo changes in code lens to handle custom view paths. If
this approach is accepted, we can always update the code lens later.
@janko
Copy link
Contributor Author

janko commented Nov 3, 2025

@rafaelfranca I believe I addressed all your feedback and fixed typechecking.

Copy link
Member

@rafaelfranca rafaelfranca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing work!

@Shopify/ruby-dx can someone do a second review?

@janko
Copy link
Contributor Author

janko commented Nov 10, 2025

I have signed the CLA!

In our app, we have an `app/views/shared/components` directory that
contains shared "component" partials. It doesn't have any matching
controller, and components can render other components, you just need
to specify the full path to the render call (we have a helper method for
that). We should make go to definition work for these cases as well.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants