diff --git a/lib/ruby_lsp/check_docs.rb b/lib/ruby_lsp/check_docs.rb index 12a13f2e1b..d18e62aec2 100644 --- a/lib/ruby_lsp/check_docs.rb +++ b/lib/ruby_lsp/check_docs.rb @@ -53,7 +53,8 @@ def run_task # documented features = ObjectSpace.each_object(Class).filter_map do |k| klass = T.unsafe(k) - klass if klass < RubyLsp::Requests::BaseRequest || klass < RubyLsp::Listener + klass if klass < RubyLsp::Requests::BaseRequest || + (klass < RubyLsp::Listener && klass != RubyLsp::ExtensibleListener) end missing_docs = T.let(Hash.new { |h, k| h[k] = [] }, T::Hash[String, T::Array[String]]) diff --git a/lib/ruby_lsp/executor.rb b/lib/ruby_lsp/executor.rb index 45f8c8facd..0bcf4bca33 100644 --- a/lib/ruby_lsp/executor.rb +++ b/lib/ruby_lsp/executor.rb @@ -103,14 +103,11 @@ def run(request) semantic_highlighting = Requests::SemanticHighlighting.new(emitter, @message_queue) emitter.visit(document.tree) if document.parsed? - code_lens.merge_external_listeners_responses! - document_symbol.merge_external_listeners_responses! - # Store all responses retrieve in this round of visits in the cache and then return the response for the request # we actually received - document.cache_set("textDocument/documentSymbol", document_symbol.response) + document.cache_set("textDocument/documentSymbol", document_symbol.merged_response) document.cache_set("textDocument/documentLink", document_link.response) - document.cache_set("textDocument/codeLens", code_lens.response) + document.cache_set("textDocument/codeLens", code_lens.merged_response) document.cache_set( "textDocument/semanticTokens/full", Requests::Support::SemanticTokenEncoder.new.encode(semantic_highlighting.response), @@ -294,8 +291,7 @@ def hover(uri, position) # Emit events for all listeners emitter.emit_for_target(target) - hover.merge_external_listeners_responses! - hover.response + hover.merged_response end sig { params(uri: URI::Generic, content_changes: T::Array[Document::EditShape], version: Integer).returns(Object) } diff --git a/lib/ruby_lsp/listener.rb b/lib/ruby_lsp/listener.rb index 71cb639a96..7ad1f67f5f 100644 --- a/lib/ruby_lsp/listener.rb +++ b/lib/ruby_lsp/listener.rb @@ -24,45 +24,51 @@ def initialize(emitter, message_queue) # accumulate results in a @response variable and then provide the reader so that it is accessible sig { abstract.returns(ResponseType) } def response; end + end - module Extensible - extend T::Sig - extend T::Generic + # ExtensibleListener is an abstract class to be used by requests that accept extensions. + class ExtensibleListener < Listener + extend T::Sig + extend T::Generic - ResponseType = type_member + ResponseType = type_member - abstract! + abstract! - requires_ancestor { Listener } + sig { params(emitter: EventEmitter, message_queue: Thread::Queue).void } + def initialize(emitter, message_queue) + super + @response_merged = T.let(false, T::Boolean) + @external_listeners = T.let( + Extension.extensions.filter_map do |ext| + initialize_external_listener(ext) + end, + T::Array[RubyLsp::Listener[ResponseType]], + ) + end - sig { params(emitter: EventEmitter, message_queue: Thread::Queue).void } - def initialize(emitter, message_queue) - super - @external_listeners = T.let( - Extension.extensions.filter_map do |ext| - initialize_external_listener(ext) - end, - T::Array[RubyLsp::Listener[ResponseType]], - ) - end + # Merge responses from all external listeners into the base listener's response. We do this to return a single + # response to the editor including the results of all extensions + sig { void } + def merge_external_listeners_responses! + @external_listeners.each { |l| merge_response!(l) } + end - # Merge responses from all external listeners into the base listener's response. We do this to return a single - # response to the editor including the results of all extensions - sig { void } - def merge_external_listeners_responses! - @external_listeners.each { |l| merge_response!(l) } - end + sig { returns(ResponseType) } + def merged_response + merge_external_listeners_responses! unless @response_merged + response + end - sig do - abstract.params(extension: RubyLsp::Extension).returns(T.nilable(RubyLsp::Listener[ResponseType])) - end - def initialize_external_listener(extension); end + sig do + abstract.params(extension: RubyLsp::Extension).returns(T.nilable(RubyLsp::Listener[ResponseType])) + end + def initialize_external_listener(extension); end - # Does nothing by default. Requests that accept extensions should override this method to define how to merge - # responses coming from external listeners - sig { abstract.params(other: Listener[T.untyped]).returns(T.self_type) } - def merge_response!(other) - end + # Does nothing by default. Requests that accept extensions should override this method to define how to merge + # responses coming from external listeners + sig { abstract.params(other: Listener[T.untyped]).returns(T.self_type) } + def merge_response!(other) end end end diff --git a/lib/ruby_lsp/requests/code_lens.rb b/lib/ruby_lsp/requests/code_lens.rb index 1972ea28da..7390bebb02 100644 --- a/lib/ruby_lsp/requests/code_lens.rb +++ b/lib/ruby_lsp/requests/code_lens.rb @@ -18,12 +18,10 @@ module Requests # class Test < Minitest::Test # end # ``` - class CodeLens < Listener + class CodeLens < ExtensibleListener extend T::Sig extend T::Generic - include Extensible - ResponseType = type_member { { fixed: T::Array[Interface::CodeLens] } } BASE_COMMAND = T.let((File.exist?("Gemfile.lock") ? "bundle exec ruby" : "ruby") + " -Itest ", String) diff --git a/lib/ruby_lsp/requests/document_symbol.rb b/lib/ruby_lsp/requests/document_symbol.rb index 6ddd9cb47f..d2f5f27fe3 100644 --- a/lib/ruby_lsp/requests/document_symbol.rb +++ b/lib/ruby_lsp/requests/document_symbol.rb @@ -26,12 +26,10 @@ module Requests # end # end # ``` - class DocumentSymbol < Listener + class DocumentSymbol < ExtensibleListener extend T::Sig extend T::Generic - include Extensible - ResponseType = type_member { { fixed: T::Array[Interface::DocumentSymbol] } } ATTR_ACCESSORS = T.let(["attr_reader", "attr_writer", "attr_accessor"].freeze, T::Array[String]) diff --git a/lib/ruby_lsp/requests/hover.rb b/lib/ruby_lsp/requests/hover.rb index ebb8a02de4..b5d5497dff 100644 --- a/lib/ruby_lsp/requests/hover.rb +++ b/lib/ruby_lsp/requests/hover.rb @@ -13,12 +13,10 @@ module Requests # ```ruby # String # -> Hovering over the class reference will show all declaration locations and the documentation # ``` - class Hover < Listener + class Hover < ExtensibleListener extend T::Sig extend T::Generic - include Extensible - ResponseType = type_member { { fixed: T.nilable(Interface::Hover) } } ALLOWED_TARGETS = T.let(