ruby-lsp 0.23.15 → 0.26.9

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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +17 -14
  4. data/exe/ruby-lsp-check +0 -4
  5. data/exe/ruby-lsp-launcher +41 -14
  6. data/exe/ruby-lsp-test-exec +6 -0
  7. data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +0 -1
  8. data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +0 -1
  9. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +4 -3
  10. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +42 -20
  11. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +1 -7
  12. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +49 -62
  13. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +84 -74
  14. data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +6 -9
  15. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +9 -14
  16. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +12 -8
  17. data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +4 -4
  18. data/lib/ruby_lsp/addon.rb +44 -15
  19. data/lib/ruby_lsp/base_server.rb +56 -37
  20. data/lib/ruby_lsp/client_capabilities.rb +6 -1
  21. data/lib/ruby_lsp/document.rb +174 -62
  22. data/lib/ruby_lsp/erb_document.rb +10 -8
  23. data/lib/ruby_lsp/global_state.rb +86 -33
  24. data/lib/ruby_lsp/internal.rb +6 -3
  25. data/lib/ruby_lsp/listeners/completion.rb +22 -11
  26. data/lib/ruby_lsp/listeners/definition.rb +41 -21
  27. data/lib/ruby_lsp/listeners/document_highlight.rb +26 -1
  28. data/lib/ruby_lsp/listeners/document_link.rb +64 -28
  29. data/lib/ruby_lsp/listeners/hover.rb +27 -16
  30. data/lib/ruby_lsp/listeners/inlay_hints.rb +5 -3
  31. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +2 -2
  32. data/lib/ruby_lsp/listeners/signature_help.rb +2 -2
  33. data/lib/ruby_lsp/listeners/spec_style.rb +155 -79
  34. data/lib/ruby_lsp/listeners/test_discovery.rb +39 -21
  35. data/lib/ruby_lsp/listeners/test_style.rb +75 -35
  36. data/lib/ruby_lsp/rbs_document.rb +3 -6
  37. data/lib/ruby_lsp/requests/code_action_resolve.rb +83 -58
  38. data/lib/ruby_lsp/requests/code_actions.rb +20 -5
  39. data/lib/ruby_lsp/requests/code_lens.rb +27 -6
  40. data/lib/ruby_lsp/requests/completion.rb +3 -3
  41. data/lib/ruby_lsp/requests/completion_resolve.rb +8 -6
  42. data/lib/ruby_lsp/requests/definition.rb +4 -7
  43. data/lib/ruby_lsp/requests/discover_tests.rb +2 -2
  44. data/lib/ruby_lsp/requests/document_highlight.rb +2 -2
  45. data/lib/ruby_lsp/requests/document_link.rb +1 -1
  46. data/lib/ruby_lsp/requests/folding_ranges.rb +1 -1
  47. data/lib/ruby_lsp/requests/go_to_relevant_file.rb +64 -12
  48. data/lib/ruby_lsp/requests/hover.rb +3 -6
  49. data/lib/ruby_lsp/requests/inlay_hints.rb +4 -4
  50. data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
  51. data/lib/ruby_lsp/requests/prepare_rename.rb +1 -1
  52. data/lib/ruby_lsp/requests/references.rb +10 -21
  53. data/lib/ruby_lsp/requests/rename.rb +9 -10
  54. data/lib/ruby_lsp/requests/request.rb +8 -8
  55. data/lib/ruby_lsp/requests/selection_ranges.rb +2 -2
  56. data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -1
  57. data/lib/ruby_lsp/requests/show_syntax_tree.rb +2 -2
  58. data/lib/ruby_lsp/requests/signature_help.rb +2 -2
  59. data/lib/ruby_lsp/requests/support/annotation.rb +1 -1
  60. data/lib/ruby_lsp/requests/support/common.rb +9 -12
  61. data/lib/ruby_lsp/requests/support/formatter.rb +16 -15
  62. data/lib/ruby_lsp/requests/support/package_url.rb +414 -0
  63. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +7 -1
  64. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +2 -2
  65. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +13 -3
  66. data/lib/ruby_lsp/requests/support/source_uri.rb +7 -4
  67. data/lib/ruby_lsp/requests/support/test_item.rb +7 -1
  68. data/lib/ruby_lsp/requests/workspace_symbol.rb +20 -12
  69. data/lib/ruby_lsp/response_builders/collection_response_builder.rb +1 -4
  70. data/lib/ruby_lsp/response_builders/document_symbol.rb +2 -3
  71. data/lib/ruby_lsp/response_builders/hover.rb +1 -4
  72. data/lib/ruby_lsp/response_builders/response_builder.rb +6 -7
  73. data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +4 -5
  74. data/lib/ruby_lsp/response_builders/signature_help.rb +1 -2
  75. data/lib/ruby_lsp/response_builders/test_collection.rb +29 -3
  76. data/lib/ruby_lsp/ruby_document.rb +14 -42
  77. data/lib/ruby_lsp/scripts/compose_bundle.rb +3 -3
  78. data/lib/ruby_lsp/scripts/compose_bundle_windows.rb +3 -1
  79. data/lib/ruby_lsp/server.rb +173 -130
  80. data/lib/ruby_lsp/setup_bundler.rb +114 -47
  81. data/lib/ruby_lsp/static_docs.rb +1 -0
  82. data/lib/ruby_lsp/store.rb +6 -16
  83. data/lib/ruby_lsp/test_helper.rb +1 -4
  84. data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +121 -17
  85. data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +65 -25
  86. data/lib/ruby_lsp/test_reporters/test_unit_reporter.rb +16 -18
  87. data/lib/ruby_lsp/utils.rb +102 -13
  88. data/static_docs/break.md +103 -0
  89. metadata +8 -33
  90. data/lib/ruby_indexer/test/class_variables_test.rb +0 -140
  91. data/lib/ruby_indexer/test/classes_and_modules_test.rb +0 -770
  92. data/lib/ruby_indexer/test/configuration_test.rb +0 -280
  93. data/lib/ruby_indexer/test/constant_test.rb +0 -402
  94. data/lib/ruby_indexer/test/enhancements_test.rb +0 -325
  95. data/lib/ruby_indexer/test/global_variable_test.rb +0 -49
  96. data/lib/ruby_indexer/test/index_test.rb +0 -2190
  97. data/lib/ruby_indexer/test/instance_variables_test.rb +0 -240
  98. data/lib/ruby_indexer/test/method_test.rb +0 -973
  99. data/lib/ruby_indexer/test/prefix_tree_test.rb +0 -150
  100. data/lib/ruby_indexer/test/rbs_indexer_test.rb +0 -380
  101. data/lib/ruby_indexer/test/reference_finder_test.rb +0 -330
  102. data/lib/ruby_indexer/test/test_case.rb +0 -51
  103. data/lib/ruby_indexer/test/uri_test.rb +0 -85
  104. data/lib/ruby_lsp/load_sorbet.rb +0 -62
@@ -13,14 +13,9 @@ module RubyLsp
13
13
  NEW_METHOD_NAME = "new_method"
14
14
 
15
15
  class CodeActionError < StandardError; end
16
-
17
- class Error < ::T::Enum
18
- enums do
19
- EmptySelection = new
20
- InvalidTargetRange = new
21
- UnknownCodeAction = new
22
- end
23
- end
16
+ class EmptySelectionError < CodeActionError; end
17
+ class InvalidTargetRangeError < CodeActionError; end
18
+ class UnknownCodeActionError < CodeActionError; end
24
19
 
25
20
  #: (RubyDocument document, GlobalState global_state, Hash[Symbol, untyped] code_action) -> void
26
21
  def initialize(document, global_state, code_action)
@@ -31,9 +26,9 @@ module RubyLsp
31
26
  end
32
27
 
33
28
  # @override
34
- #: -> (Interface::CodeAction | Error)
29
+ #: -> (Interface::CodeAction)
35
30
  def perform
36
- return Error::EmptySelection if @document.source.empty?
31
+ raise EmptySelectionError, "Invalid selection for refactor" if @document.source.empty?
37
32
 
38
33
  case @code_action[:title]
39
34
  when CodeActions::EXTRACT_TO_VARIABLE_TITLE
@@ -47,26 +42,52 @@ module RubyLsp
47
42
  CodeActions::CREATE_ATTRIBUTE_ACCESSOR
48
43
  create_attribute_accessor
49
44
  else
50
- Error::UnknownCodeAction
45
+ raise UnknownCodeActionError, "Unknown code action: #{@code_action[:title]}"
51
46
  end
52
47
  end
53
48
 
54
49
  private
55
50
 
56
- #: -> (Interface::CodeAction | Error)
51
+ #: -> (Interface::CodeAction)
57
52
  def switch_block_style
58
53
  source_range = @code_action.dig(:data, :range)
59
- return Error::EmptySelection if source_range[:start] == source_range[:end]
54
+ if source_range[:start] == source_range[:end]
55
+ block_context = @document.locate_node(
56
+ source_range[:start],
57
+ node_types: [Prism::BlockNode],
58
+ )
59
+ node = block_context.node
60
+ unless node.is_a?(Prism::BlockNode)
61
+ raise InvalidTargetRangeError, "Cursor is not inside a block"
62
+ end
60
63
 
61
- target = @document.locate_first_within_range(
62
- @code_action.dig(:data, :range),
63
- node_types: [Prism::CallNode],
64
- )
64
+ # Find the call node at the block node's start position.
65
+ # This should be the call node whose block the cursor is inside of.
66
+ call_context = RubyDocument.locate(
67
+ @document.ast,
68
+ node.location.cached_start_code_units_offset(@document.code_units_cache),
69
+ node_types: [Prism::CallNode],
70
+ code_units_cache: @document.code_units_cache,
71
+ )
72
+ target = call_context.node
73
+ unless target.is_a?(Prism::CallNode) && target.block == node
74
+ raise InvalidTargetRangeError, "Couldn't find an appropriate location to place extracted refactor"
75
+ end
76
+ else
77
+ target = @document.locate_first_within_range(
78
+ @code_action.dig(:data, :range),
79
+ node_types: [Prism::CallNode],
80
+ )
65
81
 
66
- return Error::InvalidTargetRange unless target.is_a?(Prism::CallNode)
82
+ unless target.is_a?(Prism::CallNode)
83
+ raise InvalidTargetRangeError, "Couldn't find an appropriate location to place extracted refactor"
84
+ end
67
85
 
68
- node = target.block
69
- return Error::InvalidTargetRange unless node.is_a?(Prism::BlockNode)
86
+ node = target.block
87
+ unless node.is_a?(Prism::BlockNode)
88
+ raise InvalidTargetRangeError, "Couldn't find an appropriate location to place extracted refactor"
89
+ end
90
+ end
70
91
 
71
92
  indentation = " " * target.location.start_column unless node.opening_loc.slice == "do"
72
93
 
@@ -91,17 +112,17 @@ module RubyLsp
91
112
  )
92
113
  end
93
114
 
94
- #: -> (Interface::CodeAction | Error)
115
+ #: -> (Interface::CodeAction)
95
116
  def refactor_variable
96
117
  source_range = @code_action.dig(:data, :range)
97
- return Error::EmptySelection if source_range[:start] == source_range[:end]
118
+ raise EmptySelectionError, "Invalid selection for refactor" if source_range[:start] == source_range[:end]
98
119
 
99
120
  start_index, end_index = @document.find_index_by_position(source_range[:start], source_range[:end])
100
121
  extracted_source = @document.source[start_index...end_index] #: as !nil
101
122
 
102
123
  # Find the closest statements node, so that we place the refactor in a valid position
103
124
  node_context = RubyDocument
104
- .locate(@document.parse_result.value,
125
+ .locate(@document.ast,
105
126
  start_index,
106
127
  node_types: [
107
128
  Prism::StatementsNode,
@@ -111,16 +132,20 @@ module RubyLsp
111
132
 
112
133
  closest_statements = node_context.node
113
134
  parent_statements = node_context.parent
114
- return Error::InvalidTargetRange if closest_statements.nil? || closest_statements.child_nodes.compact.empty?
135
+ if closest_statements.nil? || closest_statements.child_nodes.compact.empty?
136
+ raise InvalidTargetRangeError, "Couldn't find an appropriate location to place extracted refactor"
137
+ end
115
138
 
116
139
  # Find the node with the end line closest to the requested position, so that we can place the refactor
117
140
  # immediately after that closest node
118
- closest_node = T.must(closest_statements.child_nodes.compact.min_by do |node|
141
+ closest_node = closest_statements.child_nodes.compact.min_by do |node|
119
142
  distance = source_range.dig(:start, :line) - (node.location.end_line - 1)
120
143
  distance <= 0 ? Float::INFINITY : distance
121
- end)
144
+ end #: as !nil
122
145
 
123
- return Error::InvalidTargetRange if closest_node.is_a?(Prism::MissingNode)
146
+ if closest_node.is_a?(Prism::MissingNode)
147
+ raise InvalidTargetRangeError, "Couldn't find an appropriate location to place extracted refactor"
148
+ end
124
149
 
125
150
  closest_node_loc = closest_node.location
126
151
  # If the parent expression is a single line block, then we have to extract it inside of the one-line block
@@ -151,7 +176,9 @@ module RubyLsp
151
176
  lines = @document.source.lines
152
177
 
153
178
  indentation_line = lines[indentation_line_number]
154
- return Error::InvalidTargetRange unless indentation_line
179
+ unless indentation_line
180
+ raise InvalidTargetRangeError, "Couldn't find an appropriate location to place extracted refactor"
181
+ end
155
182
 
156
183
  indentation = indentation_line[/\A */] #: as !nil
157
184
  .size
@@ -162,7 +189,9 @@ module RubyLsp
162
189
  }
163
190
 
164
191
  line = lines[target_line]
165
- return Error::InvalidTargetRange unless line
192
+ unless line
193
+ raise InvalidTargetRangeError, "Couldn't find an appropriate location to place extracted refactor"
194
+ end
166
195
 
167
196
  variable_source = if line.strip.empty?
168
197
  "\n#{" " * indentation}#{NEW_VARIABLE_NAME} = #{extracted_source}"
@@ -190,27 +219,31 @@ module RubyLsp
190
219
  )
191
220
  end
192
221
 
193
- #: -> (Interface::CodeAction | Error)
222
+ #: -> (Interface::CodeAction)
194
223
  def refactor_method
195
224
  source_range = @code_action.dig(:data, :range)
196
- return Error::EmptySelection if source_range[:start] == source_range[:end]
225
+ raise EmptySelectionError, "Invalid selection for refactor" if source_range[:start] == source_range[:end]
197
226
 
198
227
  start_index, end_index = @document.find_index_by_position(source_range[:start], source_range[:end])
199
228
  extracted_source = @document.source[start_index...end_index] #: as !nil
200
229
 
201
230
  # Find the closest method declaration node, so that we place the refactor in a valid position
202
231
  node_context = RubyDocument.locate(
203
- @document.parse_result.value,
232
+ @document.ast,
204
233
  start_index,
205
234
  node_types: [Prism::DefNode],
206
235
  code_units_cache: @document.code_units_cache,
207
236
  )
208
237
  closest_node = node_context.node
209
- return Error::InvalidTargetRange unless closest_node
238
+ unless closest_node
239
+ raise InvalidTargetRangeError, "Couldn't find an appropriate location to place extracted refactor"
240
+ end
210
241
 
211
242
  target_range = if closest_node.is_a?(Prism::DefNode)
212
243
  end_keyword_loc = closest_node.end_keyword_loc
213
- return Error::InvalidTargetRange unless end_keyword_loc
244
+ unless end_keyword_loc
245
+ raise InvalidTargetRangeError, "Couldn't find an appropriate location to place extracted refactor"
246
+ end
214
247
 
215
248
  end_line = end_keyword_loc.end_line - 1
216
249
  character = end_keyword_loc.end_column
@@ -331,7 +364,7 @@ module RubyLsp
331
364
  indentation ? body_content.gsub(";", "\n") : "#{body_content.gsub("\n", ";")} "
332
365
  end
333
366
 
334
- #: -> (Interface::CodeAction | Error)
367
+ #: -> (Interface::CodeAction)
335
368
  def create_attribute_accessor
336
369
  source_range = @code_action.dig(:data, :range)
337
370
 
@@ -349,20 +382,12 @@ module RubyLsp
349
382
  )
350
383
  node = node_context.node
351
384
 
352
- return Error::EmptySelection unless CodeActions::INSTANCE_VARIABLE_NODES.include?(node.class)
385
+ unless CodeActions::INSTANCE_VARIABLE_NODES.include?(node.class)
386
+ raise EmptySelectionError, "Invalid selection for refactor"
387
+ end
353
388
  end
354
389
 
355
- node = T.cast(
356
- node,
357
- T.any(
358
- Prism::InstanceVariableAndWriteNode,
359
- Prism::InstanceVariableOperatorWriteNode,
360
- Prism::InstanceVariableOrWriteNode,
361
- Prism::InstanceVariableReadNode,
362
- Prism::InstanceVariableTargetNode,
363
- Prism::InstanceVariableWriteNode,
364
- ),
365
- )
390
+ node = node #: as Prism::InstanceVariableAndWriteNode | Prism::InstanceVariableOperatorWriteNode | Prism::InstanceVariableOrWriteNode | Prism::InstanceVariableReadNode | Prism::InstanceVariableTargetNode | Prism::InstanceVariableWriteNode
366
391
 
367
392
  node_context = @document.locate_node(
368
393
  {
@@ -376,20 +401,20 @@ module RubyLsp
376
401
  ],
377
402
  )
378
403
  closest_node = node_context.node
379
- return Error::InvalidTargetRange if closest_node.nil?
404
+ if closest_node.nil?
405
+ raise InvalidTargetRangeError, "Couldn't find an appropriate location to place extracted refactor"
406
+ end
380
407
 
381
408
  attribute_name = node.name[1..]
382
409
  indentation = " " * (closest_node.location.start_column + 2)
383
- attribute_accessor_source = T.must(
384
- case @code_action[:title]
385
- when CodeActions::CREATE_ATTRIBUTE_READER
386
- "#{indentation}attr_reader :#{attribute_name}\n\n"
387
- when CodeActions::CREATE_ATTRIBUTE_WRITER
388
- "#{indentation}attr_writer :#{attribute_name}\n\n"
389
- when CodeActions::CREATE_ATTRIBUTE_ACCESSOR
390
- "#{indentation}attr_accessor :#{attribute_name}\n\n"
391
- end,
392
- )
410
+ attribute_accessor_source = case @code_action[:title]
411
+ when CodeActions::CREATE_ATTRIBUTE_READER
412
+ "#{indentation}attr_reader :#{attribute_name}\n\n"
413
+ when CodeActions::CREATE_ATTRIBUTE_WRITER
414
+ "#{indentation}attr_writer :#{attribute_name}\n\n"
415
+ when CodeActions::CREATE_ATTRIBUTE_ACCESSOR
416
+ "#{indentation}attr_accessor :#{attribute_name}\n\n"
417
+ end #: as !nil
393
418
 
394
419
  target_start_line = closest_node.location.start_line
395
420
  target_range = {
@@ -63,12 +63,8 @@ module RubyLsp
63
63
  kind: Constant::CodeActionKind::REFACTOR_EXTRACT,
64
64
  data: { range: @range, uri: @uri.to_s },
65
65
  )
66
- code_actions << Interface::CodeAction.new(
67
- title: TOGGLE_BLOCK_STYLE_TITLE,
68
- kind: Constant::CodeActionKind::REFACTOR_REWRITE,
69
- data: { range: @range, uri: @uri.to_s },
70
- )
71
66
  end
67
+ code_actions.concat(toggle_block_style_action)
72
68
  code_actions.concat(attribute_actions)
73
69
 
74
70
  code_actions
@@ -113,6 +109,25 @@ module RubyLsp
113
109
  ),
114
110
  ]
115
111
  end
112
+
113
+ #: -> Array[Interface::CodeAction]
114
+ def toggle_block_style_action
115
+ if @range[:start] == @range[:end]
116
+ block_context = @document.locate_node(
117
+ @range[:start],
118
+ node_types: [Prism::BlockNode],
119
+ )
120
+ return [] unless block_context.node.is_a?(Prism::BlockNode)
121
+ end
122
+
123
+ [
124
+ Interface::CodeAction.new(
125
+ title: TOGGLE_BLOCK_STYLE_TITLE,
126
+ kind: Constant::CodeActionKind::REFACTOR_REWRITE,
127
+ data: { range: @range, uri: @uri.to_s },
128
+ ),
129
+ ]
130
+ end
116
131
  end
117
132
  end
118
133
  end
@@ -14,26 +14,47 @@ module RubyLsp
14
14
  class << self
15
15
  #: -> Interface::CodeLensOptions
16
16
  def provider
17
- Interface::CodeLensOptions.new(resolve_provider: false)
17
+ Interface::CodeLensOptions.new(resolve_provider: true)
18
18
  end
19
19
  end
20
20
 
21
- #: (GlobalState global_state, URI::Generic uri, Prism::Dispatcher dispatcher) -> void
22
- def initialize(global_state, uri, dispatcher)
23
- @response_builder = ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens]
21
+ #: (GlobalState, RubyDocument | ERBDocument, Prism::Dispatcher) -> void
22
+ def initialize(global_state, document, dispatcher)
23
+ @response_builder = ResponseBuilders::CollectionResponseBuilder
24
24
  .new #: ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens]
25
25
  super()
26
- Listeners::CodeLens.new(@response_builder, global_state, uri, dispatcher)
26
+
27
+ @document = document
28
+ @test_builder = ResponseBuilders::TestCollection.new #: ResponseBuilders::TestCollection
29
+ uri = document.uri
30
+ file_path = uri.full_path
31
+ code_lens_config = global_state.feature_configuration(:codeLens)
32
+ test_lenses_enabled = (!code_lens_config || code_lens_config.enabled?(:enableTestCodeLens)) &&
33
+ file_path && File.fnmatch?(TEST_PATH_PATTERN, file_path, File::FNM_PATHNAME | File::FNM_EXTGLOB)
34
+
35
+ if global_state.enabled_feature?(:fullTestDiscovery)
36
+ if test_lenses_enabled
37
+ Listeners::TestStyle.new(@test_builder, global_state, dispatcher, uri)
38
+ Listeners::SpecStyle.new(@test_builder, global_state, dispatcher, uri)
39
+ end
40
+ else
41
+ Listeners::CodeLens.new(@response_builder, global_state, uri, dispatcher)
42
+ end
27
43
 
28
44
  Addon.addons.each do |addon|
29
45
  addon.create_code_lens_listener(@response_builder, uri, dispatcher)
46
+
47
+ if global_state.enabled_feature?(:fullTestDiscovery) && test_lenses_enabled
48
+ addon.create_discover_tests_listener(@test_builder, dispatcher, uri)
49
+ end
30
50
  end
31
51
  end
32
52
 
33
53
  # @override
34
54
  #: -> Array[Interface::CodeLens]
35
55
  def perform
36
- @response_builder.response
56
+ @document.cache_set("rubyLsp/discoverTests", @test_builder.response)
57
+ @response_builder.response + @test_builder.code_lens
37
58
  end
38
59
  end
39
60
  end
@@ -21,7 +21,7 @@ module RubyLsp
21
21
  end
22
22
  end
23
23
 
24
- #: ((RubyDocument | ERBDocument) document, GlobalState global_state, Hash[Symbol, untyped] params, RubyDocument::SorbetLevel sorbet_level, Prism::Dispatcher dispatcher) -> void
24
+ #: ((RubyDocument | ERBDocument) document, GlobalState global_state, Hash[Symbol, untyped] params, SorbetLevel sorbet_level, Prism::Dispatcher dispatcher) -> void
25
25
  def initialize(document, global_state, params, sorbet_level, dispatcher)
26
26
  super()
27
27
  @target = nil #: Prism::Node?
@@ -33,7 +33,7 @@ module RubyLsp
33
33
  delegate_request_if_needed!(global_state, document, char_position)
34
34
 
35
35
  node_context = RubyDocument.locate(
36
- document.parse_result.value,
36
+ document.ast,
37
37
  char_position,
38
38
  node_types: [
39
39
  Prism::CallNode,
@@ -60,7 +60,7 @@ module RubyLsp
60
60
  ],
61
61
  code_units_cache: document.code_units_cache,
62
62
  )
63
- @response_builder = ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem]
63
+ @response_builder = ResponseBuilders::CollectionResponseBuilder
64
64
  .new #: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem]
65
65
 
66
66
  Listeners::Completion.new(
@@ -68,10 +68,12 @@ module RubyLsp
68
68
  "[Learn more about guessed types](#{GUESSED_TYPES_URL})"
69
69
  end
70
70
 
71
- @item[:documentation] = Interface::MarkupContent.new(
72
- kind: "markdown",
73
- value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES, extra_links: extra_links),
74
- )
71
+ unless @item[:kind] == Constant::CompletionItemKind::FILE
72
+ @item[:documentation] = Interface::MarkupContent.new(
73
+ kind: "markdown",
74
+ value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES, extra_links: extra_links),
75
+ )
76
+ end
75
77
 
76
78
  @item
77
79
  end
@@ -84,7 +86,7 @@ module RubyLsp
84
86
  content = KEYWORD_DOCS[keyword]
85
87
 
86
88
  if content
87
- doc_path = File.join(STATIC_DOCS_PATH, "#{keyword}.md")
89
+ doc_uri = URI::Generic.from_path(path: File.join(STATIC_DOCS_PATH, "#{keyword}.md"))
88
90
 
89
91
  @item[:documentation] = Interface::MarkupContent.new(
90
92
  kind: "markdown",
@@ -93,7 +95,7 @@ module RubyLsp
93
95
  #{keyword}
94
96
  ```
95
97
 
96
- [Read more](#{doc_path})
98
+ [Read more](#{doc_uri})
97
99
 
98
100
  #{content}
99
101
  MARKDOWN
@@ -9,21 +9,18 @@ module RubyLsp
9
9
  # request](https://microsoft.github.io/language-server-protocol/specification#textDocument_definition) jumps to the
10
10
  # definition of the symbol under the cursor.
11
11
  class Definition < Request
12
- extend T::Generic
13
-
14
- #: ((RubyDocument | ERBDocument) document, GlobalState global_state, Hash[Symbol, untyped] position, Prism::Dispatcher dispatcher, RubyDocument::SorbetLevel sorbet_level) -> void
12
+ #: ((RubyDocument | ERBDocument) document, GlobalState global_state, Hash[Symbol, untyped] position, Prism::Dispatcher dispatcher, SorbetLevel sorbet_level) -> void
15
13
  def initialize(document, global_state, position, dispatcher, sorbet_level)
16
14
  super()
17
- @response_builder = ResponseBuilders::CollectionResponseBuilder[
18
- T.any(Interface::Location, Interface::LocationLink)
19
- ].new #: ResponseBuilders::CollectionResponseBuilder[(Interface::Location | Interface::LocationLink)]
15
+ @response_builder = ResponseBuilders::CollectionResponseBuilder
16
+ .new #: ResponseBuilders::CollectionResponseBuilder[(Interface::Location | Interface::LocationLink)]
20
17
  @dispatcher = dispatcher
21
18
 
22
19
  char_position, _ = document.find_index_by_position(position)
23
20
  delegate_request_if_needed!(global_state, document, char_position)
24
21
 
25
22
  node_context = RubyDocument.locate(
26
- document.parse_result.value,
23
+ document.ast,
27
24
  char_position,
28
25
  node_types: [
29
26
  Prism::CallNode,
@@ -43,7 +43,7 @@ module RubyLsp
43
43
  addon.create_discover_tests_listener(@response_builder, @dispatcher, @document.uri)
44
44
  end
45
45
 
46
- @dispatcher.visit(@document.parse_result.value)
46
+ @dispatcher.visit(@document.ast)
47
47
  else
48
48
  @global_state.synchronize do
49
49
  RubyIndexer::DeclarationListener.new(
@@ -64,7 +64,7 @@ module RubyLsp
64
64
  # Dispatch the events both for indexing the test file and discovering the tests. The order here is
65
65
  # important because we need the index to be aware of the existing classes/modules/methods before the test
66
66
  # listeners can do their work
67
- @dispatcher.visit(@document.parse_result.value)
67
+ @dispatcher.visit(@document.ast)
68
68
  end
69
69
  end
70
70
 
@@ -20,12 +20,12 @@ module RubyLsp
20
20
  delegate_request_if_needed!(global_state, document, char_position)
21
21
 
22
22
  node_context = RubyDocument.locate(
23
- document.parse_result.value,
23
+ document.ast,
24
24
  char_position,
25
25
  code_units_cache: document.code_units_cache,
26
26
  )
27
27
 
28
- @response_builder = ResponseBuilders::CollectionResponseBuilder[Interface::DocumentHighlight]
28
+ @response_builder = ResponseBuilders::CollectionResponseBuilder
29
29
  .new #: ResponseBuilders::CollectionResponseBuilder[Interface::DocumentHighlight]
30
30
  Listeners::DocumentHighlight.new(
31
31
  @response_builder,
@@ -19,7 +19,7 @@ module RubyLsp
19
19
  #: (URI::Generic uri, Array[Prism::Comment] comments, Prism::Dispatcher dispatcher) -> void
20
20
  def initialize(uri, comments, dispatcher)
21
21
  super()
22
- @response_builder = ResponseBuilders::CollectionResponseBuilder[Interface::DocumentLink]
22
+ @response_builder = ResponseBuilders::CollectionResponseBuilder
23
23
  .new #: ResponseBuilders::CollectionResponseBuilder[Interface::DocumentLink]
24
24
  Listeners::DocumentLink.new(@response_builder, uri, comments, dispatcher)
25
25
  end
@@ -18,7 +18,7 @@ module RubyLsp
18
18
  #: (Array[Prism::Comment] comments, Prism::Dispatcher dispatcher) -> void
19
19
  def initialize(comments, dispatcher)
20
20
  super()
21
- @response_builder = ResponseBuilders::CollectionResponseBuilder[Interface::FoldingRange]
21
+ @response_builder = ResponseBuilders::CollectionResponseBuilder
22
22
  .new #: ResponseBuilders::CollectionResponseBuilder[Interface::FoldingRange]
23
23
  @listener = Listeners::FoldingRanges.new(@response_builder, comments, dispatcher) #: Listeners::FoldingRanges
24
24
  end
@@ -8,8 +8,6 @@ module RubyLsp
8
8
  # that navigates to the relevant file for the current document.
9
9
  # Currently, it supports source code file <> test file navigation.
10
10
  class GoToRelevantFile < Request
11
- extend T::Sig
12
-
13
11
  TEST_KEYWORDS = ["test", "spec", "integration_test"]
14
12
 
15
13
  TEST_PREFIX_PATTERN = /^(#{TEST_KEYWORDS.join("_|")}_)/
@@ -37,24 +35,78 @@ module RubyLsp
37
35
 
38
36
  #: -> Array[String]
39
37
  def find_relevant_paths
40
- candidate_paths = Dir.glob(File.join("**", relevant_filename_pattern))
38
+ patterns = relevant_filename_patterns
39
+
40
+ candidate_paths = patterns.flat_map do |pattern|
41
+ Dir.glob(File.join(search_root, "**", pattern))
42
+ end
43
+
41
44
  return [] if candidate_paths.empty?
42
45
 
43
- find_most_similar_with_jaccard(candidate_paths).map { File.join(@workspace_path, _1) }
46
+ find_most_similar_with_jaccard(candidate_paths).map { |path| File.expand_path(path, @workspace_path) }
44
47
  end
45
48
 
49
+ # Determine the search roots based on the closest test directories.
50
+ # This scopes the search to reduce the number of files that need to be checked.
46
51
  #: -> String
47
- def relevant_filename_pattern
48
- input_basename = File.basename(@path, File.extname(@path))
52
+ def search_root
53
+ current_path = File.join(".", @path)
54
+ current_dir = File.dirname(current_path)
55
+ while current_dir != "."
56
+ dir_basename = File.basename(current_dir)
57
+
58
+ # If current directory is a test directory, return its parent as search root
59
+ if TEST_KEYWORDS.include?(dir_basename)
60
+ return File.dirname(current_dir)
61
+ end
49
62
 
50
- relevant_basename_pattern =
51
- if input_basename.match?(TEST_PATTERN)
52
- input_basename.gsub(TEST_PATTERN, "")
53
- else
54
- "{{#{TEST_PREFIX_GLOB}}#{input_basename},#{input_basename}{#{TEST_SUFFIX_GLOB}}}"
63
+ # Search the test directories by walking up the directory tree
64
+ begin
65
+ contains_test_dir = Dir
66
+ .entries(current_dir)
67
+ .filter { |entry| TEST_KEYWORDS.include?(entry) }
68
+ .any? { |entry| File.directory?(File.join(current_dir, entry)) }
69
+
70
+ return current_dir if contains_test_dir
71
+ rescue Errno::EACCES, Errno::ENOENT
72
+ # Skip directories we can't read
55
73
  end
56
74
 
57
- "#{relevant_basename_pattern}#{File.extname(@path)}"
75
+ # Move up one level
76
+ parent_dir = File.dirname(current_dir)
77
+ current_dir = parent_dir
78
+ end
79
+
80
+ "."
81
+ end
82
+
83
+ #: -> Array[String]
84
+ def relevant_filename_patterns
85
+ extension = File.extname(@path)
86
+ input_basename = File.basename(@path, extension)
87
+
88
+ if input_basename.match?(TEST_PATTERN)
89
+ # Test file -> find implementation
90
+ base = input_basename.gsub(TEST_PATTERN, "")
91
+ parent_dir = File.basename(File.dirname(@path))
92
+
93
+ # If test file is in a directory matching the implementation name
94
+ # (e.g., go_to_relevant_file/test_go_to_relevant_file_a.rb)
95
+ # return patterns for both the base file name and the parent directory name
96
+ if base.include?(parent_dir) && base != parent_dir
97
+ ["#{base}#{extension}", "#{parent_dir}#{extension}"]
98
+ else
99
+ ["#{base}#{extension}"]
100
+ end
101
+ else
102
+ # Implementation file -> find tests (including in matching directory)
103
+ [
104
+ "{#{TEST_PREFIX_GLOB}}#{input_basename}#{extension}",
105
+ "#{input_basename}{#{TEST_SUFFIX_GLOB}}#{extension}",
106
+ "#{input_basename}/{#{TEST_PREFIX_GLOB}}*#{extension}",
107
+ "#{input_basename}/*{#{TEST_SUFFIX_GLOB}}#{extension}",
108
+ ]
109
+ end
58
110
  end
59
111
 
60
112
  # Using the Jaccard algorithm to determine the similarity between the
@@ -7,9 +7,8 @@ module RubyLsp
7
7
  module Requests
8
8
  # The [hover request](https://microsoft.github.io/language-server-protocol/specification#textDocument_hover)
9
9
  # displays the documentation for the symbol currently under the cursor.
10
+ #: [ResponseType = Interface::Hover?]
10
11
  class Hover < Request
11
- extend T::Generic
12
-
13
12
  class << self
14
13
  #: -> Interface::HoverOptions
15
14
  def provider
@@ -17,9 +16,7 @@ module RubyLsp
17
16
  end
18
17
  end
19
18
 
20
- ResponseType = type_member { { fixed: T.nilable(Interface::Hover) } }
21
-
22
- #: ((RubyDocument | ERBDocument) document, GlobalState global_state, Hash[Symbol, untyped] position, Prism::Dispatcher dispatcher, RubyDocument::SorbetLevel sorbet_level) -> void
19
+ #: ((RubyDocument | ERBDocument) document, GlobalState global_state, Hash[Symbol, untyped] position, Prism::Dispatcher dispatcher, SorbetLevel sorbet_level) -> void
23
20
  def initialize(document, global_state, position, dispatcher, sorbet_level)
24
21
  super()
25
22
 
@@ -27,7 +24,7 @@ module RubyLsp
27
24
  delegate_request_if_needed!(global_state, document, char_position)
28
25
 
29
26
  node_context = RubyDocument.locate(
30
- document.parse_result.value,
27
+ document.ast,
31
28
  char_position,
32
29
  node_types: Listeners::Hover::ALLOWED_TARGETS,
33
30
  code_units_cache: document.code_units_cache,
@@ -16,13 +16,13 @@ module RubyLsp
16
16
  end
17
17
  end
18
18
 
19
- #: ((RubyDocument | ERBDocument) document, RequestConfig hints_configuration, Prism::Dispatcher dispatcher) -> void
20
- def initialize(document, hints_configuration, dispatcher)
19
+ #: (GlobalState, (RubyDocument | ERBDocument), Prism::Dispatcher) -> void
20
+ def initialize(global_state, document, dispatcher)
21
21
  super()
22
22
 
23
- @response_builder = ResponseBuilders::CollectionResponseBuilder[Interface::InlayHint]
23
+ @response_builder = ResponseBuilders::CollectionResponseBuilder
24
24
  .new #: ResponseBuilders::CollectionResponseBuilder[Interface::InlayHint]
25
- Listeners::InlayHints.new(@response_builder, hints_configuration, dispatcher)
25
+ Listeners::InlayHints.new(global_state, @response_builder, dispatcher)
26
26
  end
27
27
 
28
28
  # @override
@@ -162,7 +162,7 @@ module RubyLsp
162
162
 
163
163
  #: (Integer line, Integer character) -> void
164
164
  def move_cursor_to(line, character)
165
- return unless /Visual Studio Code|Cursor|VSCodium/.match?(@client_name)
165
+ return unless /Visual Studio Code|Cursor|VSCodium|Windsurf/.match?(@client_name)
166
166
 
167
167
  position = Interface::Position.new(
168
168
  line: line,