ruby-lsp 0.13.3 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -2
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp-doctor +2 -0
  5. data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +4 -8
  6. data/lib/ruby_indexer/lib/ruby_indexer/collector.rb +5 -1
  7. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +4 -2
  8. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +10 -5
  9. data/lib/ruby_indexer/test/classes_and_modules_test.rb +9 -0
  10. data/lib/ruby_indexer/test/index_test.rb +54 -3
  11. data/lib/ruby_lsp/addon.rb +34 -17
  12. data/lib/ruby_lsp/check_docs.rb +8 -8
  13. data/lib/ruby_lsp/executor.rb +28 -10
  14. data/lib/ruby_lsp/internal.rb +8 -6
  15. data/lib/ruby_lsp/listeners/code_lens.rb +54 -55
  16. data/lib/ruby_lsp/listeners/completion.rb +22 -18
  17. data/lib/ruby_lsp/listeners/definition.rb +31 -29
  18. data/lib/ruby_lsp/listeners/document_highlight.rb +6 -11
  19. data/lib/ruby_lsp/listeners/document_link.rb +6 -12
  20. data/lib/ruby_lsp/listeners/document_symbol.rb +194 -55
  21. data/lib/ruby_lsp/listeners/folding_ranges.rb +19 -23
  22. data/lib/ruby_lsp/listeners/hover.rb +36 -34
  23. data/lib/ruby_lsp/listeners/inlay_hints.rb +7 -13
  24. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +54 -124
  25. data/lib/ruby_lsp/listeners/signature_help.rb +15 -14
  26. data/lib/ruby_lsp/requests/code_lens.rb +11 -19
  27. data/lib/ruby_lsp/requests/completion.rb +7 -9
  28. data/lib/ruby_lsp/requests/definition.rb +10 -22
  29. data/lib/ruby_lsp/requests/document_highlight.rb +7 -5
  30. data/lib/ruby_lsp/requests/document_link.rb +7 -6
  31. data/lib/ruby_lsp/requests/document_symbol.rb +5 -11
  32. data/lib/ruby_lsp/requests/folding_ranges.rb +11 -6
  33. data/lib/ruby_lsp/requests/hover.rb +18 -24
  34. data/lib/ruby_lsp/requests/inlay_hints.rb +7 -8
  35. data/lib/ruby_lsp/requests/on_type_formatting.rb +12 -2
  36. data/lib/ruby_lsp/requests/semantic_highlighting.rb +10 -8
  37. data/lib/ruby_lsp/requests/signature_help.rb +53 -18
  38. data/lib/ruby_lsp/requests/support/common.rb +38 -10
  39. data/lib/ruby_lsp/requests/support/dependency_detector.rb +5 -1
  40. data/lib/ruby_lsp/requests.rb +0 -1
  41. data/lib/ruby_lsp/response_builders/collection_response_builder.rb +29 -0
  42. data/lib/ruby_lsp/response_builders/document_symbol.rb +57 -0
  43. data/lib/ruby_lsp/response_builders/hover.rb +49 -0
  44. data/lib/ruby_lsp/response_builders/response_builder.rb +16 -0
  45. data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +199 -0
  46. data/lib/ruby_lsp/response_builders/signature_help.rb +28 -0
  47. data/lib/ruby_lsp/response_builders.rb +13 -0
  48. data/lib/ruby_lsp/server.rb +3 -3
  49. data/lib/ruby_lsp/setup_bundler.rb +64 -23
  50. data/lib/ruby_lsp/store.rb +4 -4
  51. metadata +17 -11
  52. data/lib/ruby_lsp/listener.rb +0 -33
  53. 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,44 @@ 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
134
+ end
135
+
136
+ sig do
137
+ params(
138
+ node: T.any(
139
+ Prism::ConstantPathNode,
140
+ Prism::ConstantReadNode,
141
+ Prism::ConstantPathTargetNode,
142
+ ),
143
+ ).returns(T.nilable(String))
144
+ end
145
+ def constant_name(node)
146
+ node.full_name
147
+ rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError
148
+ nil
121
149
  end
122
150
  end
123
151
  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