ruby-lsp 0.13.3 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -2
  3. data/VERSION +1 -1
  4. data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +4 -8
  5. data/lib/ruby_indexer/lib/ruby_indexer/collector.rb +5 -1
  6. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +4 -2
  7. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +8 -3
  8. data/lib/ruby_indexer/test/classes_and_modules_test.rb +9 -0
  9. data/lib/ruby_indexer/test/index_test.rb +27 -0
  10. data/lib/ruby_lsp/addon.rb +21 -10
  11. data/lib/ruby_lsp/check_docs.rb +8 -8
  12. data/lib/ruby_lsp/executor.rb +28 -10
  13. data/lib/ruby_lsp/internal.rb +1 -1
  14. data/lib/ruby_lsp/listeners/code_lens.rb +54 -55
  15. data/lib/ruby_lsp/listeners/completion.rb +17 -16
  16. data/lib/ruby_lsp/listeners/definition.rb +10 -16
  17. data/lib/ruby_lsp/listeners/document_highlight.rb +6 -11
  18. data/lib/ruby_lsp/listeners/document_link.rb +6 -12
  19. data/lib/ruby_lsp/listeners/document_symbol.rb +95 -55
  20. data/lib/ruby_lsp/listeners/folding_ranges.rb +19 -23
  21. data/lib/ruby_lsp/listeners/hover.rb +26 -30
  22. data/lib/ruby_lsp/listeners/inlay_hints.rb +7 -13
  23. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +54 -124
  24. data/lib/ruby_lsp/listeners/signature_help.rb +11 -13
  25. data/lib/ruby_lsp/requests/code_lens.rb +9 -17
  26. data/lib/ruby_lsp/requests/completion.rb +7 -9
  27. data/lib/ruby_lsp/requests/definition.rb +10 -22
  28. data/lib/ruby_lsp/requests/document_highlight.rb +7 -5
  29. data/lib/ruby_lsp/requests/document_link.rb +7 -6
  30. data/lib/ruby_lsp/requests/document_symbol.rb +5 -11
  31. data/lib/ruby_lsp/requests/folding_ranges.rb +11 -6
  32. data/lib/ruby_lsp/requests/hover.rb +18 -24
  33. data/lib/ruby_lsp/requests/inlay_hints.rb +7 -8
  34. data/lib/ruby_lsp/requests/on_type_formatting.rb +12 -2
  35. data/lib/ruby_lsp/requests/semantic_highlighting.rb +10 -8
  36. data/lib/ruby_lsp/requests/signature_help.rb +53 -18
  37. data/lib/ruby_lsp/requests/support/common.rb +23 -10
  38. data/lib/ruby_lsp/requests/support/dependency_detector.rb +5 -1
  39. data/lib/ruby_lsp/requests.rb +0 -1
  40. data/lib/ruby_lsp/response_builders/collection_response_builder.rb +29 -0
  41. data/lib/ruby_lsp/response_builders/document_symbol.rb +57 -0
  42. data/lib/ruby_lsp/response_builders/hover.rb +49 -0
  43. data/lib/ruby_lsp/response_builders/response_builder.rb +16 -0
  44. data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +199 -0
  45. data/lib/ruby_lsp/response_builders/signature_help.rb +28 -0
  46. data/lib/ruby_lsp/response_builders.rb +13 -0
  47. data/lib/ruby_lsp/server.rb +3 -3
  48. data/lib/ruby_lsp/setup_bundler.rb +30 -5
  49. data/lib/ruby_lsp/store.rb +4 -4
  50. metadata +14 -9
  51. data/lib/ruby_lsp/listener.rb +0 -33
  52. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +0 -73
@@ -30,7 +30,6 @@ module RubyLsp
30
30
  # ```
31
31
  class DocumentSymbol < Request
32
32
  extend T::Sig
33
- extend T::Generic
34
33
 
35
34
  class << self
36
35
  extend T::Sig
@@ -46,25 +45,20 @@ module RubyLsp
46
45
  end
47
46
  end
48
47
 
49
- ResponseType = type_member { { fixed: T::Array[Interface::DocumentSymbol] } }
50
-
51
48
  sig { params(dispatcher: Prism::Dispatcher).void }
52
49
  def initialize(dispatcher)
53
50
  super()
54
- @listeners = T.let(
55
- [Listeners::DocumentSymbol.new(dispatcher)],
56
- T::Array[Listener[ResponseType]],
57
- )
51
+ @response_builder = T.let(ResponseBuilders::DocumentSymbol.new, ResponseBuilders::DocumentSymbol)
52
+ Listeners::DocumentSymbol.new(@response_builder, dispatcher)
58
53
 
59
54
  Addon.addons.each do |addon|
60
- addon_listener = addon.create_document_symbol_listener(dispatcher)
61
- @listeners << addon_listener if addon_listener
55
+ addon.create_document_symbol_listener(@response_builder, dispatcher)
62
56
  end
63
57
  end
64
58
 
65
- sig { override.returns(ResponseType) }
59
+ sig { override.returns(T::Array[Interface::DocumentSymbol]) }
66
60
  def perform
67
- @listeners.flat_map(&:response).compact
61
+ @response_builder.response
68
62
  end
69
63
  end
70
64
  end
@@ -19,7 +19,6 @@ module RubyLsp
19
19
  # ```
20
20
  class FoldingRanges < Request
21
21
  extend T::Sig
22
- extend T::Generic
23
22
 
24
23
  class << self
25
24
  extend T::Sig
@@ -30,17 +29,23 @@ module RubyLsp
30
29
  end
31
30
  end
32
31
 
33
- ResponseType = type_member { { fixed: T::Array[Interface::FoldingRange] } }
34
-
35
32
  sig { params(comments: T::Array[Prism::Comment], dispatcher: Prism::Dispatcher).void }
36
33
  def initialize(comments, dispatcher)
37
34
  super()
38
- @listener = T.let(Listeners::FoldingRanges.new(comments, dispatcher), Listener[ResponseType])
35
+ @response_builder = T.let(
36
+ ResponseBuilders::CollectionResponseBuilder[Interface::FoldingRange].new,
37
+ ResponseBuilders::CollectionResponseBuilder[Interface::FoldingRange],
38
+ )
39
+ @listener = T.let(
40
+ Listeners::FoldingRanges.new(@response_builder, comments, dispatcher),
41
+ Listeners::FoldingRanges,
42
+ )
39
43
  end
40
44
 
41
- sig { override.returns(ResponseType) }
45
+ sig { override.returns(T::Array[Interface::FoldingRange]) }
42
46
  def perform
43
- @listener.response
47
+ @listener.finalize_response!
48
+ @response_builder.response
44
49
  end
45
50
  end
46
51
  end
@@ -41,51 +41,45 @@ module RubyLsp
41
41
  end
42
42
  def initialize(document, index, position, dispatcher, typechecker_enabled)
43
43
  super()
44
- target, parent, nesting = document.locate_node(
44
+ @target = T.let(nil, T.nilable(Prism::Node))
45
+ @target, parent, nesting = document.locate_node(
45
46
  position,
46
47
  node_types: Listeners::Hover::ALLOWED_TARGETS,
47
48
  )
48
49
 
49
50
  if (Listeners::Hover::ALLOWED_TARGETS.include?(parent.class) &&
50
- !Listeners::Hover::ALLOWED_TARGETS.include?(target.class)) ||
51
- (parent.is_a?(Prism::ConstantPathNode) && target.is_a?(Prism::ConstantReadNode))
52
- target = parent
51
+ !Listeners::Hover::ALLOWED_TARGETS.include?(@target.class)) ||
52
+ (parent.is_a?(Prism::ConstantPathNode) && @target.is_a?(Prism::ConstantReadNode))
53
+ @target = parent
53
54
  end
54
55
 
55
- @listeners = T.let([], T::Array[Listener[ResponseType]])
56
-
57
56
  # Don't need to instantiate any listeners if there's no target
58
- return unless target
57
+ return unless @target
59
58
 
60
59
  uri = document.uri
61
- @listeners = T.let(
62
- [Listeners::Hover.new(uri, nesting, index, dispatcher, typechecker_enabled)],
63
- T::Array[Listener[ResponseType]],
64
- )
60
+ @response_builder = T.let(ResponseBuilders::Hover.new, ResponseBuilders::Hover)
61
+ Listeners::Hover.new(@response_builder, uri, nesting, index, dispatcher, typechecker_enabled)
65
62
  Addon.addons.each do |addon|
66
- addon_listener = addon.create_hover_listener(nesting, index, dispatcher)
67
- @listeners << addon_listener if addon_listener
63
+ addon.create_hover_listener(@response_builder, nesting, index, dispatcher)
68
64
  end
69
65
 
70
- @target = T.let(target, Prism::Node)
71
66
  @dispatcher = dispatcher
72
67
  end
73
68
 
74
69
  sig { override.returns(ResponseType) }
75
70
  def perform
76
- @dispatcher.dispatch_once(@target)
77
- responses = @listeners.map(&:response).compact
78
-
79
- first_response, *other_responses = responses
71
+ return unless @target
80
72
 
81
- return unless first_response
73
+ @dispatcher.dispatch_once(@target)
82
74
 
83
- # TODO: other_responses should never be nil. Check Sorbet
84
- T.must(other_responses).each do |other_response|
85
- first_response.contents.value << "\n\n" << other_response.contents.value
86
- end
75
+ return if @response_builder.empty?
87
76
 
88
- first_response
77
+ Interface::Hover.new(
78
+ contents: Interface::MarkupContent.new(
79
+ kind: "markdown",
80
+ value: @response_builder.response,
81
+ ),
82
+ )
89
83
  end
90
84
  end
91
85
  end
@@ -40,7 +40,6 @@ module RubyLsp
40
40
  # ```
41
41
  class InlayHints < Request
42
42
  extend T::Sig
43
- extend T::Generic
44
43
 
45
44
  class << self
46
45
  extend T::Sig
@@ -51,8 +50,6 @@ module RubyLsp
51
50
  end
52
51
  end
53
52
 
54
- ResponseType = type_member { { fixed: T::Array[Interface::InlayHint] } }
55
-
56
53
  sig do
57
54
  params(
58
55
  document: Document,
@@ -65,15 +62,17 @@ module RubyLsp
65
62
  super()
66
63
  start_line = range.dig(:start, :line)
67
64
  end_line = range.dig(:end, :line)
68
- @listener = T.let(
69
- Listeners::InlayHints.new(start_line..end_line, hints_configuration, dispatcher),
70
- Listener[ResponseType],
65
+
66
+ @response_builder = T.let(
67
+ ResponseBuilders::CollectionResponseBuilder[Interface::InlayHint].new,
68
+ ResponseBuilders::CollectionResponseBuilder[Interface::InlayHint],
71
69
  )
70
+ Listeners::InlayHints.new(@response_builder, start_line..end_line, hints_configuration, dispatcher)
72
71
  end
73
72
 
74
- sig { override.returns(ResponseType) }
73
+ sig { override.returns(T::Array[Interface::InlayHint]) }
75
74
  def perform
76
- @listener.response
75
+ @response_builder.response
77
76
  end
78
77
  end
79
78
  end
@@ -38,8 +38,15 @@ module RubyLsp
38
38
  T::Array[Regexp],
39
39
  )
40
40
 
41
- sig { params(document: Document, position: T::Hash[Symbol, T.untyped], trigger_character: String).void }
42
- def initialize(document, position, trigger_character)
41
+ sig do
42
+ params(
43
+ document: Document,
44
+ position: T::Hash[Symbol, T.untyped],
45
+ trigger_character: String,
46
+ client_name: String,
47
+ ).void
48
+ end
49
+ def initialize(document, position, trigger_character, client_name)
43
50
  super()
44
51
  @document = document
45
52
  @lines = T.let(@document.source.lines, T::Array[String])
@@ -50,6 +57,7 @@ module RubyLsp
50
57
  @position = position
51
58
  @edits = T.let([], T::Array[Interface::TextEdit])
52
59
  @trigger_character = trigger_character
60
+ @client_name = client_name
53
61
  end
54
62
 
55
63
  sig { override.returns(T.all(T::Array[Interface::TextEdit], Object)) }
@@ -170,6 +178,8 @@ module RubyLsp
170
178
 
171
179
  sig { params(line: Integer, character: Integer).void }
172
180
  def move_cursor_to(line, character)
181
+ return if @client_name != "Visual Studio Code"
182
+
173
183
  position = Interface::Position.new(
174
184
  line: line,
175
185
  character: character,
@@ -22,9 +22,6 @@ module RubyLsp
22
22
  # ```
23
23
  class SemanticHighlighting < Request
24
24
  extend T::Sig
25
- extend T::Generic
26
-
27
- ResponseType = type_member { { fixed: T::Array[Listeners::SemanticHighlighting::SemanticToken] } }
28
25
 
29
26
  class << self
30
27
  extend T::Sig
@@ -34,8 +31,8 @@ module RubyLsp
34
31
  Interface::SemanticTokensRegistrationOptions.new(
35
32
  document_selector: { scheme: "file", language: "ruby" },
36
33
  legend: Interface::SemanticTokensLegend.new(
37
- token_types: Listeners::SemanticHighlighting::TOKEN_TYPES.keys,
38
- token_modifiers: Listeners::SemanticHighlighting::TOKEN_MODIFIERS.keys,
34
+ token_types: ResponseBuilders::SemanticHighlighting::TOKEN_TYPES.keys,
35
+ token_modifiers: ResponseBuilders::SemanticHighlighting::TOKEN_MODIFIERS.keys,
39
36
  ),
40
37
  range: true,
41
38
  full: { delta: false },
@@ -46,12 +43,17 @@ module RubyLsp
46
43
  sig { params(dispatcher: Prism::Dispatcher, range: T.nilable(T::Range[Integer])).void }
47
44
  def initialize(dispatcher, range: nil)
48
45
  super()
49
- @listener = T.let(Listeners::SemanticHighlighting.new(dispatcher, range: range), Listener[ResponseType])
46
+ @response_builder = T.let(ResponseBuilders::SemanticHighlighting.new, ResponseBuilders::SemanticHighlighting)
47
+ Listeners::SemanticHighlighting.new(dispatcher, @response_builder, range: range)
48
+
49
+ Addon.addons.each do |addon|
50
+ addon.create_semantic_highlighting_listener(@response_builder, dispatcher)
51
+ end
50
52
  end
51
53
 
52
- sig { override.returns(ResponseType) }
54
+ sig { override.returns(Interface::SemanticTokens) }
53
55
  def perform
54
- @listener.response
56
+ @response_builder.response
55
57
  end
56
58
  end
57
59
  end
@@ -26,7 +26,6 @@ module RubyLsp
26
26
  # ```
27
27
  class SignatureHelp < Request
28
28
  extend T::Sig
29
- extend T::Generic
30
29
 
31
30
  class << self
32
31
  extend T::Sig
@@ -40,8 +39,6 @@ module RubyLsp
40
39
  end
41
40
  end
42
41
 
43
- ResponseType = type_member { { fixed: T.nilable(T.any(Interface::SignatureHelp, T::Hash[Symbol, T.untyped])) } }
44
-
45
42
  sig do
46
43
  params(
47
44
  document: Document,
@@ -53,34 +50,72 @@ module RubyLsp
53
50
  end
54
51
  def initialize(document, index, position, context, dispatcher)
55
52
  super()
56
- current_signature = context && context[:activeSignatureHelp]
57
53
  target, parent, nesting = document.locate_node(
58
- { line: position[:line], character: position[:character] - 2 },
54
+ { line: position[:line], character: position[:character] },
59
55
  node_types: [Prism::CallNode],
60
56
  )
61
57
 
62
- # If we're typing a nested method call (e.g.: `foo(bar)`), then we may end up locating `bar` as the target
63
- # method call incorrectly. To correct that, we check if there's an active signature with the same name as the
64
- # parent node and then replace the target
65
- if current_signature && parent.is_a?(Prism::CallNode)
66
- active_signature = current_signature[:activeSignature] || 0
67
-
68
- if current_signature.dig(:signatures, active_signature, :label)&.start_with?(parent.message)
69
- target = parent
70
- end
71
- end
58
+ target = adjust_for_nested_target(target, parent, position)
72
59
 
73
60
  @target = T.let(target, T.nilable(Prism::Node))
74
61
  @dispatcher = dispatcher
75
- @listener = T.let(Listeners::SignatureHelp.new(nesting, index, dispatcher), Listener[ResponseType])
62
+ @response_builder = T.let(ResponseBuilders::SignatureHelp.new, ResponseBuilders::SignatureHelp)
63
+ Listeners::SignatureHelp.new(@response_builder, nesting, index, dispatcher)
76
64
  end
77
65
 
78
- sig { override.returns(ResponseType) }
66
+ sig { override.returns(T.nilable(Interface::SignatureHelp)) }
79
67
  def perform
80
68
  return unless @target
81
69
 
82
70
  @dispatcher.dispatch_once(@target)
83
- @listener.response
71
+ @response_builder.response
72
+ end
73
+
74
+ private
75
+
76
+ # Adjust the target of signature help in the case where we have nested method calls. This is necessary so that we
77
+ # select the right target in a situation like this:
78
+ #
79
+ # foo(another_method_call)
80
+ #
81
+ # In that case, we want to provide signature help for `foo` and not `another_method_call`.
82
+ sig do
83
+ params(
84
+ target: T.nilable(Prism::Node),
85
+ parent: T.nilable(Prism::Node),
86
+ position: T::Hash[Symbol, T.untyped],
87
+ ).returns(T.nilable(Prism::Node))
88
+ end
89
+ def adjust_for_nested_target(target, parent, position)
90
+ # If the parent node is not a method call, then make no adjustments
91
+ return target unless parent.is_a?(Prism::CallNode)
92
+ # If the parent is a method call, but the target isn't, then return the parent
93
+ return parent unless target.is_a?(Prism::CallNode)
94
+
95
+ # If both are method calls, we check the arguments of the inner method call. If there are no arguments, then
96
+ # we're providing signature help for the outer method call.
97
+ #
98
+ # If there are arguments, then we check if the arguments node covers the requested position. If it doesn't
99
+ # cover, then we're providing signature help for the outer method call.
100
+ arguments = target.arguments
101
+ arguments.nil? || !node_covers?(arguments, position) ? parent : target
102
+ end
103
+
104
+ sig { params(node: Prism::Node, position: T::Hash[Symbol, T.untyped]).returns(T::Boolean) }
105
+ def node_covers?(node, position)
106
+ location = node.location
107
+ start_line = location.start_line - 1
108
+ start_character = location.start_column
109
+ end_line = location.end_line - 1
110
+ end_character = location.end_column
111
+
112
+ start_covered = start_line < position[:line] ||
113
+ (start_line == position[:line] && start_character <= position[:character])
114
+
115
+ end_covered = end_line > position[:line] ||
116
+ (end_line == position[:line] && end_character >= position[:character])
117
+
118
+ start_covered && end_covered
84
119
  end
85
120
  end
86
121
  end
@@ -86,9 +86,9 @@ module RubyLsp
86
86
  params(
87
87
  title: String,
88
88
  entries: T.any(T::Array[RubyIndexer::Entry], RubyIndexer::Entry),
89
- ).returns(Interface::MarkupContent)
89
+ ).returns(T::Hash[Symbol, String])
90
90
  end
91
- def markdown_from_index_entries(title, entries)
91
+ def categorized_markdown_from_index_entries(title, entries)
92
92
  markdown_title = "```ruby\n#{title}\n```"
93
93
  definitions = []
94
94
  content = +""
@@ -108,16 +108,29 @@ module RubyLsp
108
108
  content << "\n\n#{entry.comments.join("\n")}" unless entry.comments.empty?
109
109
  end
110
110
 
111
- Interface::MarkupContent.new(
112
- kind: "markdown",
113
- value: <<~MARKDOWN.chomp,
114
- #{markdown_title}
111
+ {
112
+ title: markdown_title,
113
+ links: "**Definitions**: #{definitions.join(" | ")}",
114
+ documentation: content,
115
+ }
116
+ end
115
117
 
116
- **Definitions**: #{definitions.join(" | ")}
118
+ sig do
119
+ params(
120
+ title: String,
121
+ entries: T.any(T::Array[RubyIndexer::Entry], RubyIndexer::Entry),
122
+ ).returns(String)
123
+ end
124
+ def markdown_from_index_entries(title, entries)
125
+ categorized_markdown = categorized_markdown_from_index_entries(title, entries)
117
126
 
118
- #{content}
119
- MARKDOWN
120
- )
127
+ <<~MARKDOWN.chomp
128
+ #{categorized_markdown[:title]}
129
+
130
+ #{categorized_markdown[:links]}
131
+
132
+ #{categorized_markdown[:documentation]}
133
+ MARKDOWN
121
134
  end
122
135
  end
123
136
  end
@@ -63,7 +63,11 @@ module RubyLsp
63
63
  def detect_typechecker
64
64
  return false if ENV["RUBY_LSP_BYPASS_TYPECHECKER"]
65
65
 
66
- direct_dependency?(/^sorbet/) || direct_dependency?(/^sorbet-static-and-runtime/)
66
+ Bundler.with_original_env do
67
+ Bundler.locked_gems.specs.any? { |spec| spec.name == "sorbet-static" }
68
+ end
69
+ rescue Bundler::GemfileNotFound
70
+ false
67
71
  end
68
72
 
69
73
  sig { returns(T::Array[String]) }
@@ -50,7 +50,6 @@ module RubyLsp
50
50
  module Support
51
51
  autoload :RuboCopDiagnostic, "ruby_lsp/requests/support/rubocop_diagnostic"
52
52
  autoload :SelectionRange, "ruby_lsp/requests/support/selection_range"
53
- autoload :SemanticTokenEncoder, "ruby_lsp/requests/support/semantic_token_encoder"
54
53
  autoload :Annotation, "ruby_lsp/requests/support/annotation"
55
54
  autoload :Sorbet, "ruby_lsp/requests/support/sorbet"
56
55
  autoload :RailsDocumentClient, "ruby_lsp/requests/support/rails_document_client"
@@ -0,0 +1,29 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module ResponseBuilders
6
+ class CollectionResponseBuilder < ResponseBuilder
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ ResponseType = type_member { { upper: Object } }
11
+
12
+ sig { void }
13
+ def initialize
14
+ super
15
+ @items = T.let([], T::Array[ResponseType])
16
+ end
17
+
18
+ sig { params(item: ResponseType).void }
19
+ def <<(item)
20
+ @items << item
21
+ end
22
+
23
+ sig { override.returns(T::Array[ResponseType]) }
24
+ def response
25
+ @items
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,57 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module ResponseBuilders
6
+ class DocumentSymbol < ResponseBuilder
7
+ ResponseType = type_member { { fixed: T::Array[Interface::DocumentSymbol] } }
8
+
9
+ class SymbolHierarchyRoot
10
+ extend T::Sig
11
+
12
+ sig { returns(T::Array[Interface::DocumentSymbol]) }
13
+ attr_reader :children
14
+
15
+ sig { void }
16
+ def initialize
17
+ @children = T.let([], T::Array[Interface::DocumentSymbol])
18
+ end
19
+ end
20
+
21
+ extend T::Sig
22
+
23
+ sig { void }
24
+ def initialize
25
+ super
26
+ @stack = T.let(
27
+ [SymbolHierarchyRoot.new],
28
+ T::Array[T.any(SymbolHierarchyRoot, Interface::DocumentSymbol)],
29
+ )
30
+ end
31
+
32
+ sig { params(symbol: Interface::DocumentSymbol).void }
33
+ def push(symbol)
34
+ @stack << symbol
35
+ end
36
+
37
+ alias_method(:<<, :push)
38
+
39
+ sig { returns(T.nilable(Interface::DocumentSymbol)) }
40
+ def pop
41
+ if @stack.size > 1
42
+ T.cast(@stack.pop, Interface::DocumentSymbol)
43
+ end
44
+ end
45
+
46
+ sig { returns(T.any(SymbolHierarchyRoot, Interface::DocumentSymbol)) }
47
+ def last
48
+ T.must(@stack.last)
49
+ end
50
+
51
+ sig { override.returns(T::Array[Interface::DocumentSymbol]) }
52
+ def response
53
+ T.must(@stack.first).children
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,49 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module ResponseBuilders
6
+ class Hover < ResponseBuilder
7
+ ResponseType = type_member { { fixed: String } }
8
+
9
+ extend T::Sig
10
+ extend T::Generic
11
+
12
+ sig { void }
13
+ def initialize
14
+ super
15
+
16
+ @response = T.let(
17
+ {
18
+ title: +"",
19
+ links: +"",
20
+ documentation: +"",
21
+ },
22
+ T::Hash[Symbol, String],
23
+ )
24
+ end
25
+
26
+ sig { params(content: String, category: Symbol).void }
27
+ def push(content, category:)
28
+ hover_content = @response[category]
29
+ if hover_content
30
+ hover_content << content + "\n"
31
+ end
32
+ end
33
+
34
+ sig { returns(T::Boolean) }
35
+ def empty?
36
+ @response.values.all?(&:empty?)
37
+ end
38
+
39
+ sig { override.returns(ResponseType) }
40
+ def response
41
+ result = T.must(@response[:title])
42
+ result << "\n" << @response[:links] if @response[:links]
43
+ result << "\n" << @response[:documentation] if @response[:documentation]
44
+
45
+ result.strip
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,16 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module ResponseBuilders
6
+ class ResponseBuilder
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ abstract!
11
+
12
+ sig { abstract.returns(T.anything) }
13
+ def response; end
14
+ end
15
+ end
16
+ end