Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate builder pattern to refactor hover responses #1317

Closed
wants to merge 1 commit into from

Conversation

aryan-soni
Copy link
Contributor

@aryan-soni aryan-soni commented Jan 21, 2024

Motivation

Hover response construction and manipulation can be delegated to a builder class to promote extensibility and encapsulation.

Eventually, we want to facilitate deterministic categorization for hover responses. As part of this, we should first encapsulate our responses into a HoverResponseBuilder class, which will then handle the ordering of content in the future.

Implementation

The previous approach for hover responses had some suboptimal aspects:

  1. Within Requests::Hover, the perform method was responsible for constructing the response in a convoluted way.
  2. The listener was responsible for instantiating Hover objects.
  3. We were always passing in the range when constructing a Hover response, but as per the LSP spec, we weren't using this range in a meaningful fashion.

The new approach ensures:

  1. We abstract away response construction to a builder. When we add deterministic categorization for hover responses, this will ensure that we have a concrete separation of concerns.
  2. We now only construct a Hover object at one time, within Requests::Hover instead of the listener. This optimizes runtime as it ensures we limit the construction of objects.
  3. We can disregard the range.
  4. There is a more general ResponseBuilder class that we can use to refactor responses for other areas of the LSP as well.

Automated Tests

In hover_expectations_test.rb, we don't want to modify the expectations themselves (as the contract persists), but we can change the initialization work done to facilitate the tests such that it now reflects our changes.

We can also updated our broader expectations to remove range, since we are now phasing this out.

Manual Tests

We're not adding/removing any functionality, but are simply laying the foundation for improvements later in the future. As a consequence, testing this involves ensuring that our hover functionality is unchanged.

As we can see, the hover functionality persists:

hover_functionality

@@ -129,6 +129,7 @@ module RubyLsp
end

# All listeners have to inherit from ::RubyLsp::Listener
# NOTE: Listeners are currently being refactored, but this implementation embodies the current approach for most listeners.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thought it would be helpful to add a note here since the code below will no longer be accurate for Hover

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, we'll need to update all of the docs. It might be worth waiting until the refactors are complete and then we update them once.

Hover response construction and manipulation can be delegated to a builder class to promote extensibility and encapsulation.
@aryan-soni aryan-soni force-pushed the as/response-refactor branch from fa1372f to 3526517 Compare January 21, 2024 23:27
@aryan-soni aryan-soni marked this pull request as ready for review January 21, 2024 23:29
@aryan-soni aryan-soni requested a review from a team as a code owner January 21, 2024 23:29
@@ -52,19 +53,16 @@ def initialize(document, index, position, dispatcher, typechecker_enabled)
target = parent
end

@listeners = T.let([], T::Array[Listener[ResponseType]])
@listeners = T.let([], T::Array[Listener[HoverResponseBuilder]])
Copy link
Member

Choose a reason for hiding this comment

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

I think we shouldn't expect the addons' implementation to inherit from Listener either. They'd be plain old Ruby object that takes a dispatcher as Listeners::Hover.

Eventually, the Listener class will be removed after all these restructuring, or at least be much simpler than it currently is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like we can remove this line since we don't use it elsewhere regardless.

Copy link
Member

Choose a reason for hiding this comment

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

Agreed with Stan's comment about the future structure of listeners, but indeed we can just remove this line since it's no longer used.

module RubyLsp
class OperationNotPermitted < StandardError; end

class ResponseBuilder
Copy link
Member

Choose a reason for hiding this comment

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

Can we use the simple Response abstract class in response.rb for now? We won't release it as it is of course. But keeping it simple until other requests are restructured will make it easier to spot the commonality.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I agree we should unify the abstract classes.

I'll make the change, but to your point, I definitely think we should narrow in to a common builder class before we proceed with the release.

Copy link
Member

Choose a reason for hiding this comment

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

I decided to adopt the builder concept in #1325 (with a slightly different form of structure).

But I still think we should not add more common APIs before we've converted most requests. For example, we probably don't want addons to have the pop API for semantic highlighting.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it would be best to finalize on an approach for the builder in this PR, get this merged, then you can rebase in your PR. I can factor in your approach and modify mine accordingly.

Thoughts? Just thinking of the best way to minimize conflicts.

Copy link
Member

Choose a reason for hiding this comment

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

I'm obviously biased toward my PR 😛 But consider it already adopted #1317 (comment), it may actually be easier if you rebase yours on mine.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense! 😄

@@ -129,6 +129,7 @@ module RubyLsp
end

# All listeners have to inherit from ::RubyLsp::Listener
# NOTE: Listeners are currently being refactored, but this implementation embodies the current approach for most listeners.
Copy link
Member

Choose a reason for hiding this comment

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

Yeah, we'll need to update all of the docs. It might be worth waiting until the refactors are complete and then we update them once.

@@ -1,6 +1,8 @@
# typed: strict
# frozen_string_literal: true

require_relative "response_builder"
Copy link
Member

Choose a reason for hiding this comment

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

This will be necessary for most of the LSP, so I think we can move this require to ruby_lsp/internal where we require most parts.

@@ -1,13 +1,13 @@
# typed: strict
# frozen_string_literal: true

require "ruby_lsp/response_builder"
Copy link
Member

Choose a reason for hiding this comment

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

If we move to internal, then we don't need this one.

@@ -2,6 +2,7 @@
# frozen_string_literal: true

require "ruby_lsp/listeners/hover"
require "ruby_lsp/response_builder"
Copy link
Member

Choose a reason for hiding this comment

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

This can be removed too.

@@ -52,19 +53,16 @@ def initialize(document, index, position, dispatcher, typechecker_enabled)
target = parent
end

@listeners = T.let([], T::Array[Listener[ResponseType]])
@listeners = T.let([], T::Array[Listener[HoverResponseBuilder]])
Copy link
Member

Choose a reason for hiding this comment

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

Agreed with Stan's comment about the future structure of listeners, but indeed we can just remove this line since it's no longer used.

@@ -86,7 +86,7 @@ def self_receiver?(node)
params(
title: String,
entries: T.any(T::Array[RubyIndexer::Entry], RubyIndexer::Entry),
).returns(Interface::MarkupContent)
).returns(String)
Copy link
Member

Choose a reason for hiding this comment

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

Can you please double-check if we use this method anywhere else? I kind of expected stuff to break when we changed the type here.

If I'm not mistaken we use it for documentation somewhere. Since it now returns a String instead of an Interface::MarkupContent, we need to do the same thing we did in hover and instantiate it.

Interface::MarkupContent.new(
  kind: "markdown",
  value: markdown_from_index_entries(...),
)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great point — see #1328, where I've modified signature_help.rb and completion.rb as you've described.

@aryan-soni
Copy link
Contributor Author

Closing this PR in favour of #1328

@aryan-soni aryan-soni closed this Jan 28, 2024
@aryan-soni aryan-soni deleted the as/response-refactor branch January 29, 2024 16:07
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.

3 participants