ruby-lsp 0.13.3 → 0.14.2

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -2,13 +2,12 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "shellwords"
5
- require_relative "../listener"
6
5
 
7
6
  module RubyLsp
8
7
  module Listeners
9
- class CodeLens < Listener
8
+ class CodeLens
10
9
  extend T::Sig
11
- extend T::Generic
10
+ include Requests::Support::Common
12
11
 
13
12
  BASE_COMMAND = T.let(
14
13
  begin
@@ -21,31 +20,25 @@ module RubyLsp
21
20
  )
22
21
  ACCESS_MODIFIERS = T.let([:public, :private, :protected], T::Array[Symbol])
23
22
  SUPPORTED_TEST_LIBRARIES = T.let(["minitest", "test-unit"], T::Array[String])
24
-
25
- ResponseType = type_member { { fixed: T::Array[Interface::CodeLens] } }
26
-
27
- sig { override.returns(ResponseType) }
28
- attr_reader :_response
23
+ DESCRIBE_KEYWORD = T.let(:describe, Symbol)
24
+ IT_KEYWORD = T.let(:it, Symbol)
29
25
 
30
26
  sig do
31
27
  params(
28
+ response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens],
32
29
  uri: URI::Generic,
33
- lenses_configuration: RequestConfig,
34
30
  dispatcher: Prism::Dispatcher,
35
31
  ).void
36
32
  end
37
- def initialize(uri, lenses_configuration, dispatcher)
33
+ def initialize(response_builder, uri, dispatcher)
34
+ @response_builder = response_builder
38
35
  @uri = T.let(uri, URI::Generic)
39
- @_response = T.let([], ResponseType)
40
36
  @path = T.let(uri.to_standardized_path, T.nilable(String))
41
37
  # visibility_stack is a stack of [current_visibility, previous_visibility]
42
38
  @visibility_stack = T.let([[:public, :public]], T::Array[T::Array[T.nilable(Symbol)]])
43
- @class_stack = T.let([], T::Array[String])
39
+ @group_stack = T.let([], T::Array[String])
44
40
  @group_id = T.let(1, Integer)
45
41
  @group_id_stack = T.let([], T::Array[Integer])
46
- @lenses_configuration = lenses_configuration
47
-
48
- super(dispatcher)
49
42
 
50
43
  dispatcher.register(
51
44
  self,
@@ -61,13 +54,13 @@ module RubyLsp
61
54
  def on_class_node_enter(node)
62
55
  @visibility_stack.push([:public, :public])
63
56
  class_name = node.constant_path.slice
64
- @class_stack.push(class_name)
57
+ @group_stack.push(class_name)
65
58
 
66
59
  if @path && class_name.end_with?("Test")
67
60
  add_test_code_lens(
68
61
  node,
69
62
  name: class_name,
70
- command: generate_test_command(class_name: class_name),
63
+ command: generate_test_command(group_name: class_name),
71
64
  kind: :group,
72
65
  )
73
66
  end
@@ -79,13 +72,13 @@ module RubyLsp
79
72
  sig { params(node: Prism::ClassNode).void }
80
73
  def on_class_node_leave(node)
81
74
  @visibility_stack.pop
82
- @class_stack.pop
75
+ @group_stack.pop
83
76
  @group_id_stack.pop
84
77
  end
85
78
 
86
79
  sig { params(node: Prism::DefNode).void }
87
80
  def on_def_node_enter(node)
88
- class_name = @class_stack.last
81
+ class_name = @group_stack.last
89
82
  return unless class_name&.end_with?("Test")
90
83
 
91
84
  visibility, _ = @visibility_stack.last
@@ -95,7 +88,7 @@ module RubyLsp
95
88
  add_test_code_lens(
96
89
  node,
97
90
  name: method_name,
98
- command: generate_test_command(method_name: method_name, class_name: class_name),
91
+ command: generate_test_command(method_name: method_name, group_name: class_name),
99
92
  kind: :example,
100
93
  )
101
94
  end
@@ -120,16 +113,15 @@ module RubyLsp
120
113
  return
121
114
  end
122
115
 
123
- if @path&.include?(GEMFILE_NAME) && name == :gem && arguments
124
- return unless @lenses_configuration.enabled?(:gemfileLinks)
125
-
126
- first_argument = arguments.arguments.first
127
- return unless first_argument.is_a?(Prism::StringNode)
128
-
129
- remote = resolve_gem_remote(first_argument)
130
- return unless remote
131
-
132
- add_open_gem_remote_code_lens(node, remote)
116
+ if [DESCRIBE_KEYWORD, IT_KEYWORD].include?(name)
117
+ case name
118
+ when DESCRIBE_KEYWORD
119
+ add_spec_code_lens(node, kind: :group)
120
+ @group_id_stack.push(@group_id)
121
+ @group_id += 1
122
+ when IT_KEYWORD
123
+ add_spec_code_lens(node, kind: :example)
124
+ end
133
125
  end
134
126
  end
135
127
 
@@ -137,6 +129,9 @@ module RubyLsp
137
129
  def on_call_node_leave(node)
138
130
  _, prev_visibility = @visibility_stack.pop
139
131
  @visibility_stack.push([prev_visibility, prev_visibility])
132
+ if node.name == DESCRIBE_KEYWORD
133
+ @group_id_stack.pop
134
+ end
140
135
  end
141
136
 
142
137
  private
@@ -161,7 +156,7 @@ module RubyLsp
161
156
  grouping_data = { group_id: @group_id_stack.last, kind: kind }
162
157
  grouping_data[:id] = @group_id if kind == :group
163
158
 
164
- @_response << create_code_lens(
159
+ @response_builder << create_code_lens(
165
160
  node,
166
161
  title: "Run",
167
162
  command_name: "rubyLsp.runTest",
@@ -169,7 +164,7 @@ module RubyLsp
169
164
  data: { type: "test", **grouping_data },
170
165
  )
171
166
 
172
- @_response << create_code_lens(
167
+ @response_builder << create_code_lens(
173
168
  node,
174
169
  title: "Run In Terminal",
175
170
  command_name: "rubyLsp.runTestInTerminal",
@@ -177,7 +172,7 @@ module RubyLsp
177
172
  data: { type: "test_in_terminal", **grouping_data },
178
173
  )
179
174
 
180
- @_response << create_code_lens(
175
+ @response_builder << create_code_lens(
181
176
  node,
182
177
  title: "Debug",
183
178
  command_name: "rubyLsp.debugTest",
@@ -186,29 +181,19 @@ module RubyLsp
186
181
  )
187
182
  end
188
183
 
189
- sig { params(gem_name: Prism::StringNode).returns(T.nilable(String)) }
190
- def resolve_gem_remote(gem_name)
191
- spec = Gem::Specification.stubs.find { |gem| gem.name == gem_name.content }&.to_spec
192
- return if spec.nil?
193
-
194
- [spec.homepage, spec.metadata["source_code_uri"]].compact.find do |page|
195
- page.start_with?("https://github.com", "https://gitlab.com")
196
- end
197
- end
198
-
199
- sig { params(class_name: String, method_name: T.nilable(String)).returns(String) }
200
- def generate_test_command(class_name:, method_name: nil)
184
+ sig { params(group_name: String, method_name: T.nilable(String)).returns(String) }
185
+ def generate_test_command(group_name:, method_name: nil)
201
186
  command = BASE_COMMAND + T.must(@path)
202
187
 
203
188
  case DependencyDetector.instance.detected_test_library
204
189
  when "minitest"
205
190
  command += if method_name
206
- " --name " + "/#{Shellwords.escape(class_name + "#" + method_name)}/"
191
+ " --name " + "/#{Shellwords.escape(group_name + "#" + method_name)}/"
207
192
  else
208
- " --name " + "/#{Shellwords.escape(class_name)}/"
193
+ " --name " + "/#{Shellwords.escape(group_name)}/"
209
194
  end
210
195
  when "test-unit"
211
- command += " --testcase " + "/#{Shellwords.escape(class_name)}/"
196
+ command += " --testcase " + "/#{Shellwords.escape(group_name)}/"
212
197
 
213
198
  if method_name
214
199
  command += " --name " + Shellwords.escape(method_name)
@@ -218,14 +203,28 @@ module RubyLsp
218
203
  command
219
204
  end
220
205
 
221
- sig { params(node: Prism::CallNode, remote: String).void }
222
- def add_open_gem_remote_code_lens(node, remote)
223
- @_response << create_code_lens(
206
+ sig { params(node: Prism::CallNode, kind: Symbol).void }
207
+ def add_spec_code_lens(node, kind:)
208
+ arguments = node.arguments
209
+ return unless arguments
210
+
211
+ first_argument = arguments.arguments.first
212
+ return unless first_argument
213
+
214
+ name = case first_argument
215
+ when Prism::StringNode
216
+ first_argument.content
217
+ when Prism::ConstantReadNode
218
+ first_argument.full_name
219
+ end
220
+
221
+ return unless name
222
+
223
+ add_test_code_lens(
224
224
  node,
225
- title: "Open remote",
226
- command_name: "rubyLsp.openLink",
227
- arguments: [remote],
228
- data: { type: "link" },
225
+ name: name,
226
+ command: generate_test_command(group_name: name),
227
+ kind: kind,
229
228
  )
230
229
  end
231
230
  end
@@ -3,26 +3,21 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Listeners
6
- class Completion < Listener
6
+ class Completion
7
7
  extend T::Sig
8
- extend T::Generic
9
-
10
- ResponseType = type_member { { fixed: T::Array[Interface::CompletionItem] } }
11
-
12
- sig { override.returns(ResponseType) }
13
- attr_reader :_response
8
+ include Requests::Support::Common
14
9
 
15
10
  sig do
16
11
  params(
12
+ response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
17
13
  index: RubyIndexer::Index,
18
14
  nesting: T::Array[String],
19
15
  typechecker_enabled: T::Boolean,
20
16
  dispatcher: Prism::Dispatcher,
21
17
  ).void
22
18
  end
23
- def initialize(index, nesting, typechecker_enabled, dispatcher)
24
- super(dispatcher)
25
- @_response = T.let([], ResponseType)
19
+ def initialize(response_builder, index, nesting, typechecker_enabled, dispatcher)
20
+ @response_builder = response_builder
26
21
  @index = index
27
22
  @nesting = nesting
28
23
  @typechecker_enabled = typechecker_enabled
@@ -39,7 +34,7 @@ module RubyLsp
39
34
  sig { params(node: Prism::StringNode).void }
40
35
  def on_string_node_enter(node)
41
36
  @index.search_require_paths(node.content).map!(&:require_path).sort!.each do |path|
42
- @_response << build_completion(T.must(path), node)
37
+ @response_builder << build_completion(T.must(path), node)
43
38
  end
44
39
  end
45
40
 
@@ -48,11 +43,13 @@ module RubyLsp
48
43
  def on_constant_read_node_enter(node)
49
44
  return if DependencyDetector.instance.typechecker
50
45
 
51
- name = node.slice
46
+ name = constant_name(node)
47
+ return if name.nil?
48
+
52
49
  candidates = @index.prefix_search(name, @nesting)
53
50
  candidates.each do |entries|
54
51
  complete_name = T.must(entries.first).name
55
- @_response << build_entry_completion(
52
+ @response_builder << build_entry_completion(
56
53
  complete_name,
57
54
  name,
58
55
  node,
@@ -67,7 +64,8 @@ module RubyLsp
67
64
  def on_constant_path_node_enter(node)
68
65
  return if DependencyDetector.instance.typechecker
69
66
 
70
- name = node.slice
67
+ name = constant_name(node)
68
+ return if name.nil?
71
69
 
72
70
  top_level_reference = if name.start_with?("::")
73
71
  name = name.delete_prefix("::")
@@ -96,7 +94,7 @@ module RubyLsp
96
94
 
97
95
  full_name = aliased_namespace.empty? ? constant_name : "#{aliased_namespace}::#{constant_name}"
98
96
 
99
- @_response << build_entry_completion(
97
+ @response_builder << build_entry_completion(
100
98
  full_name,
101
99
  name,
102
100
  node,
@@ -123,7 +121,7 @@ module RubyLsp
123
121
  entry = entries.find { |e| e.is_a?(RubyIndexer::Entry::Member) && e.owner&.name == receiver.name }
124
122
  next unless entry
125
123
 
126
- @_response << build_method_completion(T.cast(entry, RubyIndexer::Entry::Member), node)
124
+ @response_builder << build_method_completion(T.cast(entry, RubyIndexer::Entry::Member), node)
127
125
  end
128
126
  end
129
127
 
@@ -147,7 +145,10 @@ module RubyLsp
147
145
  detail: "(#{entry.parameters.map(&:decorated_name).join(", ")})",
148
146
  description: entry.file_name,
149
147
  ),
150
- documentation: markdown_from_index_entries(name, entry),
148
+ documentation: Interface::MarkupContent.new(
149
+ kind: "markdown",
150
+ value: markdown_from_index_entries(name, entry),
151
+ ),
151
152
  )
152
153
  end
153
154
 
@@ -240,7 +241,10 @@ module RubyLsp
240
241
  label_details: Interface::CompletionItemLabelDetails.new(
241
242
  description: entries.map(&:file_name).join(","),
242
243
  ),
243
- documentation: markdown_from_index_entries(real_name, entries),
244
+ documentation: Interface::MarkupContent.new(
245
+ kind: "markdown",
246
+ value: markdown_from_index_entries(real_name, entries),
247
+ ),
244
248
  )
245
249
  end
246
250
 
@@ -3,17 +3,13 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Listeners
6
- class Definition < Listener
6
+ class Definition
7
7
  extend T::Sig
8
- extend T::Generic
9
-
10
- ResponseType = type_member { { fixed: T.nilable(T.any(T::Array[Interface::Location], Interface::Location)) } }
11
-
12
- sig { override.returns(ResponseType) }
13
- attr_reader :_response
8
+ include Requests::Support::Common
14
9
 
15
10
  sig do
16
11
  params(
12
+ response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
17
13
  uri: URI::Generic,
18
14
  nesting: T::Array[String],
19
15
  index: RubyIndexer::Index,
@@ -21,14 +17,12 @@ module RubyLsp
21
17
  typechecker_enabled: T::Boolean,
22
18
  ).void
23
19
  end
24
- def initialize(uri, nesting, index, dispatcher, typechecker_enabled)
20
+ def initialize(response_builder, uri, nesting, index, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
21
+ @response_builder = response_builder
25
22
  @uri = uri
26
23
  @nesting = nesting
27
24
  @index = index
28
25
  @typechecker_enabled = typechecker_enabled
29
- @_response = T.let(nil, ResponseType)
30
-
31
- super(dispatcher)
32
26
 
33
27
  dispatcher.register(
34
28
  self,
@@ -51,12 +45,18 @@ module RubyLsp
51
45
 
52
46
  sig { params(node: Prism::ConstantPathNode).void }
53
47
  def on_constant_path_node_enter(node)
54
- find_in_index(node.slice)
48
+ name = constant_name(node)
49
+ return if name.nil?
50
+
51
+ find_in_index(name)
55
52
  end
56
53
 
57
54
  sig { params(node: Prism::ConstantReadNode).void }
58
55
  def on_constant_read_node_enter(node)
59
- find_in_index(node.slice)
56
+ name = constant_name(node)
57
+ return if name.nil?
58
+
59
+ find_in_index(name)
60
60
  end
61
61
 
62
62
  private
@@ -68,20 +68,22 @@ module RubyLsp
68
68
  message = node.message
69
69
  return unless message
70
70
 
71
- target_method = @index.resolve_method(message, @nesting.join("::"))
72
- return unless target_method
71
+ methods = @index.resolve_method(message, @nesting.join("::"))
72
+ return unless methods
73
73
 
74
- location = target_method.location
75
- file_path = target_method.file_path
76
- return if @typechecker_enabled && not_in_dependencies?(file_path)
74
+ methods.each do |target_method|
75
+ location = target_method.location
76
+ file_path = target_method.file_path
77
+ next if @typechecker_enabled && not_in_dependencies?(file_path)
77
78
 
78
- @_response = Interface::Location.new(
79
- uri: URI::Generic.from_path(path: file_path).to_s,
80
- range: Interface::Range.new(
81
- start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
82
- end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
83
- ),
84
- )
79
+ @response_builder << Interface::Location.new(
80
+ uri: URI::Generic.from_path(path: file_path).to_s,
81
+ range: Interface::Range.new(
82
+ start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
83
+ end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
84
+ ),
85
+ )
86
+ end
85
87
  end
86
88
 
87
89
  sig { params(node: Prism::CallNode).void }
@@ -102,7 +104,7 @@ module RubyLsp
102
104
  if entry
103
105
  candidate = entry.full_path
104
106
 
105
- @_response = Interface::Location.new(
107
+ @response_builder << Interface::Location.new(
106
108
  uri: URI::Generic.from_path(path: candidate).to_s,
107
109
  range: Interface::Range.new(
108
110
  start: Interface::Position.new(line: 0, character: 0),
@@ -116,7 +118,7 @@ module RubyLsp
116
118
  current_folder = path ? Pathname.new(CGI.unescape(path)).dirname : Dir.pwd
117
119
  candidate = File.expand_path(File.join(current_folder, required_file))
118
120
 
119
- @_response = Interface::Location.new(
121
+ @response_builder << Interface::Location.new(
120
122
  uri: URI::Generic.from_path(path: candidate).to_s,
121
123
  range: Interface::Range.new(
122
124
  start: Interface::Position.new(line: 0, character: 0),
@@ -136,7 +138,7 @@ module RubyLsp
136
138
  first_entry = T.must(entries.first)
137
139
  return if first_entry.visibility == :private && first_entry.name != "#{@nesting.join("::")}::#{value}"
138
140
 
139
- @_response = entries.filter_map do |entry|
141
+ entries.each do |entry|
140
142
  location = entry.location
141
143
  # If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
142
144
  # additional behavior on top of jumping to RBIs. Sorbet can already handle go to definition for all constants
@@ -144,7 +146,7 @@ module RubyLsp
144
146
  file_path = entry.file_path
145
147
  next if @typechecker_enabled && not_in_dependencies?(file_path)
146
148
 
147
- Interface::Location.new(
149
+ @response_builder << Interface::Location.new(
148
150
  uri: URI::Generic.from_path(path: file_path).to_s,
149
151
  range: Interface::Range.new(
150
152
  start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
@@ -3,10 +3,9 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Listeners
6
- class DocumentHighlight < Listener
6
+ class DocumentHighlight
7
7
  extend T::Sig
8
-
9
- ResponseType = type_member { { fixed: T::Array[Interface::DocumentHighlight] } }
8
+ include Requests::Support::Common
10
9
 
11
10
  GLOBAL_VARIABLE_NODES = T.let(
12
11
  [
@@ -87,20 +86,16 @@ module RubyLsp
87
86
  T::Array[T.class_of(Prism::Node)],
88
87
  )
89
88
 
90
- sig { override.returns(ResponseType) }
91
- attr_reader :_response
92
-
93
89
  sig do
94
90
  params(
91
+ response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::DocumentHighlight],
95
92
  target: T.nilable(Prism::Node),
96
93
  parent: T.nilable(Prism::Node),
97
94
  dispatcher: Prism::Dispatcher,
98
95
  ).void
99
96
  end
100
- def initialize(target, parent, dispatcher)
101
- super(dispatcher)
102
-
103
- @_response = T.let([], T::Array[Interface::DocumentHighlight])
97
+ def initialize(response_builder, target, parent, dispatcher)
98
+ @response_builder = response_builder
104
99
 
105
100
  return unless target && parent
106
101
 
@@ -521,7 +516,7 @@ module RubyLsp
521
516
 
522
517
  sig { params(kind: Integer, location: Prism::Location).void }
523
518
  def add_highlight(kind, location)
524
- @_response << Interface::DocumentHighlight.new(range: range_from_location(location), kind: kind)
519
+ @response_builder << Interface::DocumentHighlight.new(range: range_from_location(location), kind: kind)
525
520
  end
526
521
 
527
522
  sig { params(node: T.nilable(Prism::Node)).returns(T.nilable(String)) }
@@ -5,11 +5,9 @@ require "ruby_lsp/requests/support/source_uri"
5
5
 
6
6
  module RubyLsp
7
7
  module Listeners
8
- class DocumentLink < Listener
8
+ class DocumentLink
9
9
  extend T::Sig
10
- extend T::Generic
11
-
12
- ResponseType = type_member { { fixed: T::Array[Interface::DocumentLink] } }
10
+ include Requests::Support::Common
13
11
 
14
12
  GEM_TO_VERSION_MAP = T.let(
15
13
  [*::Gem::Specification.default_stubs, *::Gem::Specification.stubs].map! do |s|
@@ -59,25 +57,21 @@ module RubyLsp
59
57
  end
60
58
  end
61
59
 
62
- sig { override.returns(ResponseType) }
63
- attr_reader :_response
64
-
65
60
  sig do
66
61
  params(
62
+ response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::DocumentLink],
67
63
  uri: URI::Generic,
68
64
  comments: T::Array[Prism::Comment],
69
65
  dispatcher: Prism::Dispatcher,
70
66
  ).void
71
67
  end
72
- def initialize(uri, comments, dispatcher)
73
- super(dispatcher)
74
-
68
+ def initialize(response_builder, uri, comments, dispatcher)
75
69
  # Match the version based on the version in the RBI file name. Notice that the `@` symbol is sanitized to `%40`
76
70
  # in the URI
71
+ @response_builder = response_builder
77
72
  path = uri.to_standardized_path
78
73
  version_match = path ? /(?<=%40)[\d.]+(?=\.rbi$)/.match(path) : nil
79
74
  @gem_version = T.let(version_match && version_match[0], T.nilable(String))
80
- @_response = T.let([], T::Array[Interface::DocumentLink])
81
75
  @lines_to_comments = T.let(
82
76
  comments.to_h do |comment|
83
77
  [comment.location.end_line, comment]
@@ -137,7 +131,7 @@ module RubyLsp
137
131
  file_path = self.class.gem_paths.dig(uri.gem_name, gem_version, CGI.unescape(uri.path))
138
132
  return if file_path.nil?
139
133
 
140
- @_response << Interface::DocumentLink.new(
134
+ @response_builder << Interface::DocumentLink.new(
141
135
  range: range_from_location(comment.location),
142
136
  target: "file://#{file_path}##{uri.line_number}",
143
137
  tooltip: "Jump to #{file_path}##{uri.line_number}",