diff --git a/lib/ruby_lsp/event_emitter.rb b/lib/ruby_lsp/event_emitter.rb index 64d3ee0850..06e9e56caf 100644 --- a/lib/ruby_lsp/event_emitter.rb +++ b/lib/ruby_lsp/event_emitter.rb @@ -37,16 +37,62 @@ def register(listener, *events) sig { params(node: T.nilable(SyntaxTree::Node)).void } def emit_for_target(node) case node - when SyntaxTree::Command - @listeners[:on_command]&.each { |l| T.unsafe(l).on_command(node) } + when SyntaxTree::ArrayLiteral + @listeners[:on_array_literal]&.each { |l| T.unsafe(l).on_array_literal(node) } + when SyntaxTree::Begin + @listeners[:on_begin]&.each { |l| T.unsafe(l).on_begin(node) } + when SyntaxTree::BlockNode + @listeners[:on_block_node]&.each { |l| T.unsafe(l).on_block_node(node) } + when SyntaxTree::Case + @listeners[:on_case]&.each { |l| T.unsafe(l).on_case(node) } + when SyntaxTree::ClassDeclaration + @listeners[:on_class]&.each { |l| T.unsafe(l).on_class(node) } when SyntaxTree::CallNode @listeners[:on_call]&.each { |l| T.unsafe(l).on_call(node) } - when SyntaxTree::TStringContent - @listeners[:on_tstring_content]&.each { |l| T.unsafe(l).on_tstring_content(node) } - when SyntaxTree::ConstPathRef - @listeners[:on_const_path_ref]&.each { |l| T.unsafe(l).on_const_path_ref(node) } + when SyntaxTree::Command + @listeners[:on_command]&.each { |l| T.unsafe(l).on_command(node) } + when SyntaxTree::CommandCall + @listeners[:on_command_call]&.each { |l| T.unsafe(l).on_command_call(node) } when SyntaxTree::Const @listeners[:on_const]&.each { |l| T.unsafe(l).on_const(node) } + when SyntaxTree::ConstPathRef + @listeners[:on_const_path_ref]&.each { |l| T.unsafe(l).on_const_path_ref(node) } + when SyntaxTree::DefNode + @listeners[:on_def_node]&.each { |l| T.unsafe(l).on_def_node(node) } + when SyntaxTree::Else + @listeners[:on_else]&.each { |l| T.unsafe(l).on_else(node) } + when SyntaxTree::Ensure + @listeners[:on_ensure]&.each { |l| T.unsafe(l).on_ensure(node) } + when SyntaxTree::For + @listeners[:on_for]&.each { |l| T.unsafe(l).on_for(node) } + when SyntaxTree::HashLiteral + @listeners[:on_hash_literal]&.each { |l| T.unsafe(l).on_hash_literal(node) } + when SyntaxTree::Heredoc + @listeners[:on_heredoc]&.each { |l| T.unsafe(l).on_heredoc(node) } + when SyntaxTree::IfNode + @listeners[:on_if_node]&.each { |l| T.unsafe(l).on_if_node(node) } + when SyntaxTree::ModuleDeclaration + @listeners[:on_module]&.each { |l| T.unsafe(l).on_module(node) } + when SyntaxTree::SClass + @listeners[:on_sclass]&.each { |l| T.unsafe(l).on_sclass(node) } + when SyntaxTree::StringConcat + @listeners[:on_string_concat]&.each { |l| T.unsafe(l).on_string_concat(node) } + when SyntaxTree::TStringContent + @listeners[:on_tstring_content]&.each { |l| T.unsafe(l).on_tstring_content(node) } + when SyntaxTree::UnlessNode + @listeners[:on_unless_node]&.each { |l| T.unsafe(l).on_unless_node(node) } + when SyntaxTree::UntilNode + @listeners[:on_until_node]&.each { |l| T.unsafe(l).on_until_node(node) } + when SyntaxTree::WhileNode + @listeners[:on_while_node]&.each { |l| T.unsafe(l).on_while_node(node) } + when SyntaxTree::Elsif + @listeners[:on_elsif]&.each { |l| T.unsafe(l).on_elsif(node) } + when SyntaxTree::In + @listeners[:on_in]&.each { |l| T.unsafe(l).on_in(node) } + when SyntaxTree::Rescue + @listeners[:on_rescue]&.each { |l| T.unsafe(l).on_rescue(node) } + when SyntaxTree::When + @listeners[:on_when]&.each { |l| T.unsafe(l).on_when(node) } end end @@ -73,6 +119,14 @@ def visit_command(node) @listeners[:after_command]&.each { |l| T.unsafe(l).after_command(node) } end + sig { override.params(node: SyntaxTree::CommandCall).void } + def visit_command_call(node) + @listeners[:on_command_call]&.each { |l| T.unsafe(l).on_command_call(node) } + super + # TODO: need 'after'? + @listeners[:after_command_call]&.each { |l| T.unsafe(l).after_command_call(node) } + end + sig { override.params(node: SyntaxTree::CallNode).void } def visit_call(node) @listeners[:on_call]&.each { |l| T.unsafe(l).on_call(node) } @@ -122,5 +176,108 @@ def visit_rescue(node) @listeners[:on_rescue]&.each { |l| T.unsafe(l).on_rescue(node) } super end + + # TODO: array_literal? + sig { override.params(node: SyntaxTree::ArrayLiteral).void } + def visit_array(node) + @listeners[:on_array_literal]&.each { |l| T.unsafe(l).on_array_literal(node) } + super + end + + sig { override.params(node: SyntaxTree::Begin).void } + def visit_begin(node) + @listeners[:on_begin]&.each { |l| T.unsafe(l).on_begin(node) } + super + end + + sig { override.params(node: SyntaxTree::BlockNode).void } + def visit_block(node) + @listeners[:on_block_node]&.each { |l| T.unsafe(l).on_block_node(node) } + super + end + + sig { override.params(node: SyntaxTree::Case).void } + def visit_case(node) + @listeners[:on_case]&.each { |l| T.unsafe(l).on_case(node) } + super + end + + sig { override.params(node: SyntaxTree::Else).void } + def visit_else(node) + @listeners[:on_else]&.each { |l| T.unsafe(l).on_else(node) } + super + end + + sig { override.params(node: SyntaxTree::Ensure).void } + def visit_ensure(node) + @listeners[:on_ensure]&.each { |l| T.unsafe(l).on_ensure(node) } + super + end + + sig { override.params(node: SyntaxTree::For).void } + def visit_for(node) + @listeners[:on_for]&.each { |l| T.unsafe(l).on_for(node) } + super + end + + sig { override.params(node: SyntaxTree::HashLiteral).void } + def visit_hash(node) + @listeners[:on_hash_literal]&.each { |l| T.unsafe(l).on_hash_literal(node) } + super + end + + sig { override.params(node: SyntaxTree::Heredoc).void } + def visit_heredoc(node) + @listeners[:on_heredoc]&.each { |l| T.unsafe(l).on_heredoc(node) } + super + end + + sig { override.params(node: SyntaxTree::IfNode).void } + def visit_if(node) + @listeners[:on_if_node]&.each { |l| T.unsafe(l).on_if_node(node) } + super + end + + sig { override.params(node: SyntaxTree::SClass).void } + def visit_sclass(node) + @listeners[:on_sclass]&.each { |l| T.unsafe(l).on_sclass(node) } + super + end + + sig { override.params(node: SyntaxTree::UnlessNode).void } + def visit_unless(node) + @listeners[:on_unless_node]&.each { |l| T.unsafe(l).on_unless_node(node) } + super + end + + sig { override.params(node: SyntaxTree::UntilNode).void } + def visit_until(node) + @listeners[:on_until_node]&.each { |l| T.unsafe(l).on_until_node(node) } + super + end + + sig { override.params(node: SyntaxTree::WhileNode).void } + def visit_while(node) + @listeners[:on_while_node]&.each { |l| T.unsafe(l).on_while_node(node) } + super + end + + sig { override.params(node: SyntaxTree::Elsif).void } + def visit_elsif(node) + @listeners[:on_elsif]&.each { |l| T.unsafe(l).on_elsif(node) } + super + end + + sig { override.params(node: SyntaxTree::In).void } + def visit_in(node) + @listeners[:on_in]&.each { |l| T.unsafe(l).on_in(node) } + super + end + + sig { override.params(node: SyntaxTree::When).void } + def visit_when(node) + @listeners[:on_when]&.each { |l| T.unsafe(l).on_when(node) } + super + end end end diff --git a/lib/ruby_lsp/executor.rb b/lib/ruby_lsp/executor.rb index 335553ec3d..5937fc518f 100644 --- a/lib/ruby_lsp/executor.rb +++ b/lib/ruby_lsp/executor.rb @@ -77,11 +77,10 @@ def run(request) request.dig(:params, :contentChanges), request.dig(:params, :textDocument, :version), ) - when "textDocument/foldingRange" - folding_range(uri) when "textDocument/selectionRange" selection_range(uri, request.dig(:params, :positions)) - when "textDocument/documentSymbol", "textDocument/documentLink", "textDocument/codeLens" + when "textDocument/documentSymbol", "textDocument/documentLink", "textDocument/codeLens", + "textDocument/foldingRange" document = @store.get(uri) # If the response has already been cached by another request, return it @@ -92,6 +91,7 @@ def run(request) emitter = EventEmitter.new document_symbol = Requests::DocumentSymbol.new(emitter, @message_queue) document_link = Requests::DocumentLink.new(uri, emitter, @message_queue) + folding_ranges = Requests::FoldingRanges.new(emitter, @message_queue) code_lens = Requests::CodeLens.new(uri, emitter, @message_queue) code_lens_extensions_listeners = Requests::CodeLens.listeners.map do |l| T.unsafe(l).new(document.uri, emitter, @message_queue) @@ -105,6 +105,7 @@ def run(request) document.cache_set("textDocument/documentSymbol", document_symbol.response) document.cache_set("textDocument/documentLink", document_link.response) document.cache_set("textDocument/codeLens", code_lens.response) + document.cache_set("textDocument/foldingRange", folding_ranges.response) document.cache_get(request[:method]) when "textDocument/semanticTokens/full" semantic_tokens_full(uri) @@ -165,13 +166,6 @@ def run(request) end end - sig { params(uri: String).returns(T::Array[Interface::FoldingRange]) } - def folding_range(uri) - @store.cache_fetch(uri, "textDocument/foldingRange") do |document| - Requests::FoldingRanges.new(document).run - end - end - sig do params( uri: String, diff --git a/lib/ruby_lsp/requests/folding_ranges.rb b/lib/ruby_lsp/requests/folding_ranges.rb index 1a43a4223e..2cc211bae3 100644 --- a/lib/ruby_lsp/requests/folding_ranges.rb +++ b/lib/ruby_lsp/requests/folding_ranges.rb @@ -1,6 +1,12 @@ # typed: strict # frozen_string_literal: true +# when SyntaxTree::CallNode +# when SyntaxTree::CommandCall +# when SyntaxTree::Command +# when SyntaxTree::DefNode +# when SyntaxTree::StringConcat + module RubyLsp module Requests # ![Folding ranges demo](../../folding_ranges.gif) @@ -15,15 +21,33 @@ module Requests # puts "Hello" # end # <-- folding range end # ``` - class FoldingRanges < BaseRequest + + # TODO: explain why had to move this + StatementNode = T.type_alias do + T.any( + SyntaxTree::Elsif, + SyntaxTree::In, + SyntaxTree::Rescue, + SyntaxTree::When, + ) + end + class FoldingRanges < Listener extend T::Sig + ResponseType = type_member { { fixed: T::Array[Interface::FoldingRange] } } + + sig { override.returns(ResponseType) } + attr_reader :response + SIMPLE_FOLDABLES = T.let( [ SyntaxTree::ArrayLiteral, + SyntaxTree::Begin, SyntaxTree::BlockNode, SyntaxTree::Case, SyntaxTree::ClassDeclaration, + SyntaxTree::Else, + SyntaxTree::Ensure, SyntaxTree::For, SyntaxTree::HashLiteral, SyntaxTree::Heredoc, @@ -33,9 +57,6 @@ class FoldingRanges < BaseRequest SyntaxTree::UnlessNode, SyntaxTree::UntilNode, SyntaxTree::WhileNode, - SyntaxTree::Else, - SyntaxTree::Ensure, - SyntaxTree::Begin, ].freeze, T::Array[T.class_of(SyntaxTree::Node)], ) @@ -50,68 +71,171 @@ class FoldingRanges < BaseRequest T::Array[T.class_of(SyntaxTree::Node)], ) - StatementNode = T.type_alias do - T.any( - SyntaxTree::Elsif, - SyntaxTree::In, - SyntaxTree::Rescue, - SyntaxTree::When, + sig { params(emitter: EventEmitter, message_queue: Thread::Queue).void } + def initialize(emitter, message_queue) + super + @response = T.let([], ResponseType) + @partial_range = T.let(nil, T.nilable(PartialRange)) + + emitter.register( + self, + :on_array_literal, + :on_begin, + :on_block_node, + :on_case, + :on_class_declaration, + :on_comment, + :on_else, + :on_ensure, + :on_for, + :on_hash_literal, + :on_heredoc, + :on_if_node, + :on_module, # or on_module_declaration? + :on_sclass, + :on_unless_node, + :on_until_node, + :on_while_node, + :on_elsif, + :on_in, + :on_rescue, + :on_when, + :on_call_node, + :on_command, + :on_command_call, + :on_def, + :on_string_concat, ) end - sig { params(document: Document).void } - def initialize(document) - super + # sig { override.returns(T.all(T::Array[Interface::FoldingRange], Object)) } + # def run + # if @document.parsed? + # visit(@document.tree) + # emit_partial_range + # end + # + # @ranges + # end - @ranges = T.let([], T::Array[Interface::FoldingRange]) - @partial_range = T.let(nil, T.nilable(PartialRange)) + # private + + sig { params(node: T.nilable(SyntaxTree::Node)).void } + def visit(node) + return unless handle_partial_range(node) + + # + # case node + # when *SIMPLE_FOLDABLES + # location = T.must(node).location + # add_lines_range(location.start_line, location.end_line - 1) + # when *NODES_WITH_STATEMENTS + # add_statements_range(T.must(node), T.cast(node, StatementNode).statements) + # when SyntaxTree::CallNode, SyntaxTree::CommandCall + # # If there is a receiver, it may be a chained invocation, + # # so we need to process it in special way. + # if node.receiver.nil? + # location = node.location + # add_lines_range(location.start_line, location.end_line - 1) + # else + # add_call_range(node) + # return + # end + # when SyntaxTree::Command + # unless same_lines_for_command_and_block?(node) + # location = node.location + # add_lines_range(location.start_line, location.end_line - 1) + # end + # when SyntaxTree::DefNode + # add_def_range(node) + # when SyntaxTree::StringConcat + # add_string_concat(node) + # return + # end + # super end - sig { override.returns(T.all(T::Array[Interface::FoldingRange], Object)) } - def run - if @document.parsed? - visit(@document.tree) - emit_partial_range - end + # TODO: proper types + sig { params(node: T.untyped).void } + def on_simple_foldable(node) + return unless handle_partial_range(node) + + location = node.location + add_lines_range(location.start_line, location.end_line - 1) + end + alias_method :on_array_literal, :on_simple_foldable + alias_method :on_begin, :on_simple_foldable + alias_method :on_block_node, :on_simple_foldable + alias_method :on_case, :on_simple_foldable + alias_method :on_class_declaration, :on_simple_foldable + alias_method :on_else, :on_simple_foldable + alias_method :on_ensure, :on_simple_foldable + alias_method :on_for, :on_simple_foldable + alias_method :on_hash_literal, :on_simple_foldable + alias_method :on_heredoc, :on_simple_foldable + alias_method :on_if_node, :on_simple_foldable + alias_method :on_module, :on_simple_foldable + alias_method :on_sclass, :on_simple_foldable + alias_method :on_unless_node, :on_simple_foldable + alias_method :on_until_node, :on_simple_foldable + alias_method :on_while_node, :on_simple_foldable + + # TODO: proper types + sig { params(node: T.untyped).void } + def on_call(node) + return unless handle_partial_range(node) - @ranges + # If there is a receiver, it may be a chained invocation, + # so we need to process it in special way. + if node.receiver.nil? + location = node.location + add_lines_range(location.start_line, location.end_line - 1) + else + add_call_range(node) + # return ? + end end + alias_method :on_command_call, :on_call - private + sig { params(node: StatementNode).void } + def on_statement_node(node) + return unless handle_partial_range(node) - sig { override.params(node: T.nilable(SyntaxTree::Node)).void } - def visit(node) + add_statements_range(node, node.statements) + end + alias_method :on_elsif, :on_statement_node + alias_method :on_in, :on_statement_node + alias_method :on_rescue, :on_statement_node + alias_method :on_when, :on_statement_node + + sig { params(node: SyntaxTree::Command).void } + def on_command(node) return unless handle_partial_range(node) - case node - when *SIMPLE_FOLDABLES - location = T.must(node).location + unless same_lines_for_command_and_block?(node) + location = node.location add_lines_range(location.start_line, location.end_line - 1) - when *NODES_WITH_STATEMENTS - add_statements_range(T.must(node), T.cast(node, StatementNode).statements) - when SyntaxTree::CallNode, SyntaxTree::CommandCall - # If there is a receiver, it may be a chained invocation, - # so we need to process it in special way. - if node.receiver.nil? - location = node.location - add_lines_range(location.start_line, location.end_line - 1) - else - add_call_range(node) - return - end - when SyntaxTree::Command - unless same_lines_for_command_and_block?(node) - location = node.location - add_lines_range(location.start_line, location.end_line - 1) - end - when SyntaxTree::DefNode - add_def_range(node) - when SyntaxTree::StringConcat - add_string_concat(node) - return end + end - super + sig { params(node: SyntaxTree::Comment).void } + def on_comment(node) + # TODO + end + + sig { params(node: SyntaxTree::StringConcat).void } + def on_string_concat(node) + return unless handle_partial_range(node) + + add_string_concat(node) + # return ? + end + + sig { params(node: SyntaxTree::DefNode).void } + def on_def(node) + return unless handle_partial_range(node) + + add_def_range(node) end # This is to prevent duplicate ranges @@ -214,7 +338,7 @@ def partial_range_kind(node) def emit_partial_range return if @partial_range.nil? - @ranges << @partial_range.to_range if @partial_range.multiline? + @response << @partial_range.to_range if @partial_range.multiline? @partial_range = nil end @@ -295,7 +419,7 @@ def add_string_concat(node) def add_lines_range(start_line, end_line) return if start_line >= end_line - @ranges << Interface::FoldingRange.new( + @response << Interface::FoldingRange.new( start_line: start_line - 1, end_line: end_line - 1, kind: "region",