ruby-lsp 0.23.11 → 0.26.1

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +10 -4
  5. data/exe/ruby-lsp-check +0 -4
  6. data/exe/ruby-lsp-launcher +45 -22
  7. data/exe/ruby-lsp-test-exec +6 -0
  8. data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +1 -2
  9. data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +3 -6
  10. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +82 -116
  11. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +140 -183
  12. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +10 -14
  13. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +107 -236
  14. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +166 -281
  15. data/lib/ruby_indexer/lib/ruby_indexer/location.rb +4 -27
  16. data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +23 -27
  17. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +25 -57
  18. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +58 -68
  19. data/lib/ruby_indexer/lib/ruby_indexer/uri.rb +17 -19
  20. data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +7 -11
  21. data/lib/ruby_indexer/test/class_variables_test.rb +14 -14
  22. data/lib/ruby_indexer/test/classes_and_modules_test.rb +65 -40
  23. data/lib/ruby_indexer/test/configuration_test.rb +49 -9
  24. data/lib/ruby_indexer/test/constant_test.rb +34 -34
  25. data/lib/ruby_indexer/test/enhancements_test.rb +1 -1
  26. data/lib/ruby_indexer/test/index_test.rb +185 -135
  27. data/lib/ruby_indexer/test/instance_variables_test.rb +61 -37
  28. data/lib/ruby_indexer/test/method_test.rb +166 -123
  29. data/lib/ruby_indexer/test/prefix_tree_test.rb +21 -21
  30. data/lib/ruby_indexer/test/rbs_indexer_test.rb +70 -75
  31. data/lib/ruby_indexer/test/reference_finder_test.rb +79 -14
  32. data/lib/ruby_indexer/test/test_case.rb +9 -3
  33. data/lib/ruby_indexer/test/uri_test.rb +15 -2
  34. data/lib/ruby_lsp/addon.rb +88 -86
  35. data/lib/ruby_lsp/base_server.rb +59 -54
  36. data/lib/ruby_lsp/client_capabilities.rb +16 -13
  37. data/lib/ruby_lsp/document.rb +205 -104
  38. data/lib/ruby_lsp/erb_document.rb +45 -47
  39. data/lib/ruby_lsp/global_state.rb +73 -57
  40. data/lib/ruby_lsp/internal.rb +8 -3
  41. data/lib/ruby_lsp/listeners/code_lens.rb +82 -89
  42. data/lib/ruby_lsp/listeners/completion.rb +81 -76
  43. data/lib/ruby_lsp/listeners/definition.rb +44 -58
  44. data/lib/ruby_lsp/listeners/document_highlight.rb +123 -150
  45. data/lib/ruby_lsp/listeners/document_link.rb +50 -70
  46. data/lib/ruby_lsp/listeners/document_symbol.rb +38 -52
  47. data/lib/ruby_lsp/listeners/folding_ranges.rb +40 -43
  48. data/lib/ruby_lsp/listeners/hover.rb +107 -115
  49. data/lib/ruby_lsp/listeners/inlay_hints.rb +8 -13
  50. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +54 -56
  51. data/lib/ruby_lsp/listeners/signature_help.rb +12 -27
  52. data/lib/ruby_lsp/listeners/spec_style.rb +214 -0
  53. data/lib/ruby_lsp/listeners/test_discovery.rb +92 -0
  54. data/lib/ruby_lsp/listeners/test_style.rb +205 -95
  55. data/lib/ruby_lsp/node_context.rb +12 -39
  56. data/lib/ruby_lsp/rbs_document.rb +10 -11
  57. data/lib/ruby_lsp/requests/code_action_resolve.rb +65 -61
  58. data/lib/ruby_lsp/requests/code_actions.rb +14 -26
  59. data/lib/ruby_lsp/requests/code_lens.rb +31 -21
  60. data/lib/ruby_lsp/requests/completion.rb +8 -21
  61. data/lib/ruby_lsp/requests/completion_resolve.rb +6 -6
  62. data/lib/ruby_lsp/requests/definition.rb +8 -20
  63. data/lib/ruby_lsp/requests/diagnostics.rb +8 -11
  64. data/lib/ruby_lsp/requests/discover_tests.rb +20 -7
  65. data/lib/ruby_lsp/requests/document_highlight.rb +6 -16
  66. data/lib/ruby_lsp/requests/document_link.rb +6 -17
  67. data/lib/ruby_lsp/requests/document_symbol.rb +5 -8
  68. data/lib/ruby_lsp/requests/folding_ranges.rb +7 -15
  69. data/lib/ruby_lsp/requests/formatting.rb +6 -9
  70. data/lib/ruby_lsp/requests/go_to_relevant_file.rb +85 -0
  71. data/lib/ruby_lsp/requests/hover.rb +12 -25
  72. data/lib/ruby_lsp/requests/inlay_hints.rb +8 -19
  73. data/lib/ruby_lsp/requests/on_type_formatting.rb +32 -40
  74. data/lib/ruby_lsp/requests/prepare_rename.rb +5 -10
  75. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +5 -15
  76. data/lib/ruby_lsp/requests/range_formatting.rb +5 -6
  77. data/lib/ruby_lsp/requests/references.rb +17 -57
  78. data/lib/ruby_lsp/requests/rename.rb +27 -51
  79. data/lib/ruby_lsp/requests/request.rb +13 -25
  80. data/lib/ruby_lsp/requests/selection_ranges.rb +7 -7
  81. data/lib/ruby_lsp/requests/semantic_highlighting.rb +16 -35
  82. data/lib/ruby_lsp/requests/show_syntax_tree.rb +7 -8
  83. data/lib/ruby_lsp/requests/signature_help.rb +9 -27
  84. data/lib/ruby_lsp/requests/support/annotation.rb +4 -10
  85. data/lib/ruby_lsp/requests/support/common.rb +16 -58
  86. data/lib/ruby_lsp/requests/support/formatter.rb +16 -15
  87. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +27 -35
  88. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +13 -16
  89. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +34 -36
  90. data/lib/ruby_lsp/requests/support/selection_range.rb +1 -3
  91. data/lib/ruby_lsp/requests/support/sorbet.rb +29 -38
  92. data/lib/ruby_lsp/requests/support/source_uri.rb +20 -32
  93. data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +12 -19
  94. data/lib/ruby_lsp/requests/support/test_item.rb +16 -14
  95. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +5 -6
  96. data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -4
  97. data/lib/ruby_lsp/response_builders/collection_response_builder.rb +6 -9
  98. data/lib/ruby_lsp/response_builders/document_symbol.rb +15 -21
  99. data/lib/ruby_lsp/response_builders/hover.rb +12 -18
  100. data/lib/ruby_lsp/response_builders/response_builder.rb +6 -7
  101. data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +62 -91
  102. data/lib/ruby_lsp/response_builders/signature_help.rb +6 -8
  103. data/lib/ruby_lsp/response_builders/test_collection.rb +35 -13
  104. data/lib/ruby_lsp/ruby_document.rb +32 -98
  105. data/lib/ruby_lsp/scope.rb +7 -11
  106. data/lib/ruby_lsp/scripts/compose_bundle.rb +6 -4
  107. data/lib/ruby_lsp/server.rb +303 -196
  108. data/lib/ruby_lsp/setup_bundler.rb +121 -82
  109. data/lib/ruby_lsp/static_docs.rb +12 -7
  110. data/lib/ruby_lsp/store.rb +21 -49
  111. data/lib/ruby_lsp/test_helper.rb +3 -16
  112. data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +233 -0
  113. data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +145 -0
  114. data/lib/ruby_lsp/test_reporters/test_unit_reporter.rb +92 -0
  115. data/lib/ruby_lsp/type_inferrer.rb +13 -14
  116. data/lib/ruby_lsp/utils.rb +138 -93
  117. data/static_docs/break.md +103 -0
  118. metadata +14 -20
  119. data/lib/ruby_lsp/load_sorbet.rb +0 -62
@@ -3,13 +3,12 @@
3
3
 
4
4
  module RubyLsp
5
5
  class Server < BaseServer
6
- extend T::Sig
7
-
8
6
  # Only for testing
9
- sig { returns(GlobalState) }
7
+ #: GlobalState
10
8
  attr_reader :global_state
11
9
 
12
- sig { override.params(message: T::Hash[Symbol, T.untyped]).void }
10
+ # @override
11
+ #: (Hash[Symbol, untyped] message) -> void
13
12
  def process_message(message)
14
13
  case message[:method]
15
14
  when "initialize"
@@ -33,6 +32,8 @@ module RubyLsp
33
32
  text_document_document_link(message)
34
33
  when "textDocument/codeLens"
35
34
  text_document_code_lens(message)
35
+ when "codeLens/resolve"
36
+ code_lens_resolve(message)
36
37
  when "textDocument/semanticTokens/full"
37
38
  text_document_semantic_tokens_full(message)
38
39
  when "textDocument/semanticTokens/full/delta"
@@ -93,13 +94,10 @@ module RubyLsp
93
94
  id: message[:id],
94
95
  response:
95
96
  Addon.addons.map do |addon|
96
- version_method = addon.method(:version)
97
-
98
- # If the add-on doesn't define a `version` method, we'd be calling the abstract method defined by
99
- # Sorbet, which would raise an error.
100
- # Therefore, we only call the method if it's defined by the add-on itself
101
- if version_method.owner != Addon
102
- version = addon.version
97
+ version = begin
98
+ addon.version
99
+ rescue AbstractMethodInvokedError
100
+ nil
103
101
  end
104
102
 
105
103
  { name: addon.name, version: version, errored: addon.error? }
@@ -112,6 +110,10 @@ module RubyLsp
112
110
  diagnose_state(message)
113
111
  when "rubyLsp/discoverTests"
114
112
  discover_tests(message)
113
+ when "rubyLsp/resolveTestCommands"
114
+ resolve_test_commands(message)
115
+ when "experimental/goToRelevantFile"
116
+ experimental_go_to_relevant_file(message)
115
117
  when "$/cancelRequest"
116
118
  @global_state.synchronize { @cancelled_requests << message[:params][:id] }
117
119
  when nil
@@ -119,30 +121,22 @@ module RubyLsp
119
121
  end
120
122
  rescue DelegateRequestError
121
123
  send_message(Error.new(id: message[:id], code: DelegateRequestError::CODE, message: "DELEGATE_REQUEST"))
122
- rescue StandardError, LoadError => e
124
+ rescue StandardError, LoadError, SystemExit => e
123
125
  # If an error occurred in a request, we have to return an error response or else the editor will hang
124
126
  if message[:id]
125
127
  # If a document is deleted before we are able to process all of its enqueued requests, we will try to read it
126
128
  # from disk and it raise this error. This is expected, so we don't include the `data` attribute to avoid
127
- # reporting these to our telemetry
129
+ # reporting these to our telemetry.
130
+ #
131
+ # Similarly, if we receive a location for an invalid position in the
132
+ # document, we don't report it to telemetry
128
133
  case e
129
- when Store::NonExistingDocumentError
134
+ when Store::NonExistingDocumentError, Document::InvalidLocationError
130
135
  send_message(Error.new(
131
136
  id: message[:id],
132
137
  code: Constant::ErrorCodes::INVALID_PARAMS,
133
138
  message: e.full_message,
134
139
  ))
135
- when Document::LocationNotFoundError
136
- send_message(Error.new(
137
- id: message[:id],
138
- code: Constant::ErrorCodes::REQUEST_FAILED,
139
- message: <<~MESSAGE,
140
- Request #{message[:method]} failed to find the target position.
141
- The file might have been modified while the server was in the middle of searching for the target.
142
- If you experience this regularly, please report any findings and extra information on
143
- https://github.com/Shopify/ruby-lsp/issues/2446
144
- MESSAGE
145
- ))
146
140
  else
147
141
  send_message(Error.new(
148
142
  id: message[:id],
@@ -161,7 +155,7 @@ module RubyLsp
161
155
  end
162
156
 
163
157
  # Process responses to requests that were sent to the client
164
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
158
+ #: (Hash[Symbol, untyped] message) -> void
165
159
  def process_response(message)
166
160
  case message.dig(:result, :method)
167
161
  when "window/showMessageRequest"
@@ -169,7 +163,7 @@ module RubyLsp
169
163
  end
170
164
  end
171
165
 
172
- sig { params(include_project_addons: T::Boolean).void }
166
+ #: (?include_project_addons: bool) -> void
173
167
  def load_addons(include_project_addons: true)
174
168
  # If invoking Bundler.setup failed, then the load path will not be configured properly and trying to load add-ons
175
169
  # with Gem.find_files will find every single version installed of an add-on, leading to requiring several
@@ -177,6 +171,7 @@ module RubyLsp
177
171
  return if @setup_error
178
172
 
179
173
  errors = Addon.load_addons(@global_state, @outgoing_queue, include_project_addons: include_project_addons)
174
+ return if test_mode?
180
175
 
181
176
  if errors.any?
182
177
  send_log_message(
@@ -189,27 +184,19 @@ module RubyLsp
189
184
 
190
185
  if errored_addons.any?
191
186
  send_message(
192
- Notification.new(
193
- method: "window/showMessage",
194
- params: Interface::ShowMessageParams.new(
195
- type: Constant::MessageType::WARNING,
196
- message: "Error loading add-ons:\n\n#{errored_addons.map(&:formatted_errors).join("\n\n")}",
197
- ),
187
+ Notification.window_show_message(
188
+ "Error loading add-ons:\n\n#{errored_addons.map(&:formatted_errors).join("\n\n")}",
189
+ type: Constant::MessageType::WARNING,
198
190
  ),
199
191
  )
200
192
 
201
- unless @test_mode
202
- send_log_message(
203
- errored_addons.map(&:errors_details).join("\n\n"),
204
- type: Constant::MessageType::WARNING,
205
- )
206
- end
193
+ send_log_message(errored_addons.map(&:errors_details).join("\n\n"), type: Constant::MessageType::WARNING)
207
194
  end
208
195
  end
209
196
 
210
197
  private
211
198
 
212
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
199
+ #: (Hash[Symbol, untyped] message) -> void
213
200
  def run_initialize(message)
214
201
  options = message[:params]
215
202
  global_state_notifications = @global_state.apply_options(options)
@@ -219,9 +206,6 @@ module RubyLsp
219
206
 
220
207
  configured_features = options.dig(:initializationOptions, :enabledFeatures)
221
208
 
222
- configured_hints = options.dig(:initializationOptions, :featuresConfiguration, :inlayHint)
223
- T.must(@store.features_configuration.dig(:inlayHint)).configuration.merge!(configured_hints) if configured_hints
224
-
225
209
  enabled_features = case configured_features
226
210
  when Array
227
211
  # If the configuration is using an array, then absent features are disabled and present ones are enabled. That's
@@ -238,7 +222,9 @@ module RubyLsp
238
222
 
239
223
  bundle_env_path = File.join(".ruby-lsp", "bundle_env")
240
224
  bundle_env = if File.exist?(bundle_env_path)
241
- env = File.readlines(bundle_env_path).to_h { |line| T.cast(line.chomp.split("=", 2), [String, String]) }
225
+ env = File.readlines(bundle_env_path).to_h do |line|
226
+ line.chomp.split("=", 2) #: as [String, String]
227
+ end
242
228
  FileUtils.rm(bundle_env_path)
243
229
  env
244
230
  end
@@ -290,6 +276,8 @@ module RubyLsp
290
276
  experimental: {
291
277
  addon_detection: true,
292
278
  compose_bundle: true,
279
+ go_to_relevant_file: true,
280
+ full_test_discovery: true,
293
281
  },
294
282
  ),
295
283
  serverInfo: {
@@ -346,7 +334,7 @@ module RubyLsp
346
334
  end
347
335
  end
348
336
 
349
- sig { void }
337
+ #: -> void
350
338
  def run_initialized
351
339
  load_addons
352
340
  RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
@@ -371,69 +359,64 @@ module RubyLsp
371
359
 
372
360
  perform_initial_indexing
373
361
  check_formatter_is_available
362
+ update_server if @global_state.enabled_feature?(:launcher)
374
363
  end
375
364
 
376
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
365
+ #: (Hash[Symbol, untyped] message) -> void
377
366
  def text_document_did_open(message)
378
- @global_state.synchronize do
379
- text_document = message.dig(:params, :textDocument)
380
- language_id = case text_document[:languageId]
381
- when "erb", "eruby"
382
- Document::LanguageId::ERB
383
- when "rbs"
384
- Document::LanguageId::RBS
385
- else
386
- Document::LanguageId::Ruby
387
- end
367
+ text_document = message.dig(:params, :textDocument)
368
+ language_id = case text_document[:languageId]
369
+ when "erb", "eruby"
370
+ :erb
371
+ when "rbs"
372
+ :rbs
373
+ else
374
+ :ruby
375
+ end
388
376
 
389
- document = @store.set(
390
- uri: text_document[:uri],
391
- source: text_document[:text],
392
- version: text_document[:version],
393
- language_id: language_id,
394
- )
377
+ document = @store.set(
378
+ uri: text_document[:uri],
379
+ source: text_document[:text],
380
+ version: text_document[:version],
381
+ language_id: language_id,
382
+ )
395
383
 
396
- if document.past_expensive_limit? && text_document[:uri].scheme == "file"
397
- log_message = <<~MESSAGE
398
- The file #{text_document[:uri].path} is too long. For performance reasons, semantic highlighting and
399
- diagnostics will be disabled.
400
- MESSAGE
384
+ if document.past_expensive_limit? && text_document[:uri].scheme == "file"
385
+ log_message = <<~MESSAGE
386
+ The file #{text_document[:uri].path} is too long. For performance reasons, semantic highlighting and
387
+ diagnostics will be disabled.
388
+ MESSAGE
401
389
 
402
- send_message(
403
- Notification.new(
404
- method: "window/logMessage",
405
- params: Interface::LogMessageParams.new(
406
- type: Constant::MessageType::WARNING,
407
- message: log_message,
408
- ),
390
+ send_message(
391
+ Notification.new(
392
+ method: "window/logMessage",
393
+ params: Interface::LogMessageParams.new(
394
+ type: Constant::MessageType::WARNING,
395
+ message: log_message,
409
396
  ),
410
- )
411
- end
397
+ ),
398
+ )
412
399
  end
413
400
  end
414
401
 
415
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
402
+ #: (Hash[Symbol, untyped] message) -> void
416
403
  def text_document_did_close(message)
417
- @global_state.synchronize do
418
- uri = message.dig(:params, :textDocument, :uri)
419
- @store.delete(uri)
404
+ uri = message.dig(:params, :textDocument, :uri)
405
+ @store.delete(uri)
420
406
 
421
- # Clear diagnostics for the closed file, so that they no longer appear in the problems tab
422
- send_message(Notification.publish_diagnostics(uri.to_s, []))
423
- end
407
+ # Clear diagnostics for the closed file, so that they no longer appear in the problems tab
408
+ send_message(Notification.publish_diagnostics(uri.to_s, []))
424
409
  end
425
410
 
426
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
411
+ #: (Hash[Symbol, untyped] message) -> void
427
412
  def text_document_did_change(message)
428
413
  params = message[:params]
429
414
  text_document = params[:textDocument]
430
415
 
431
- @global_state.synchronize do
432
- @store.push_edits(uri: text_document[:uri], edits: params[:contentChanges], version: text_document[:version])
433
- end
416
+ @store.push_edits(uri: text_document[:uri], edits: params[:contentChanges], version: text_document[:version])
434
417
  end
435
418
 
436
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
419
+ #: (Hash[Symbol, untyped] message) -> void
437
420
  def text_document_selection_range(message)
438
421
  uri = message.dig(:params, :textDocument, :uri)
439
422
  ranges = @store.cache_fetch(uri, "textDocument/selectionRange") do |document|
@@ -459,7 +442,7 @@ module RubyLsp
459
442
  send_message(Result.new(id: message[:id], response: response))
460
443
  end
461
444
 
462
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
445
+ #: (Hash[Symbol, untyped] message) -> void
463
446
  def run_combined_requests(message)
464
447
  uri = URI(message.dig(:params, :textDocument, :uri))
465
448
  document = @store.get(uri)
@@ -483,8 +466,15 @@ module RubyLsp
483
466
  folding_range = Requests::FoldingRanges.new(parse_result.comments, dispatcher)
484
467
  document_symbol = Requests::DocumentSymbol.new(uri, dispatcher)
485
468
  document_link = Requests::DocumentLink.new(uri, parse_result.comments, dispatcher)
486
- code_lens = Requests::CodeLens.new(@global_state, uri, dispatcher)
487
- inlay_hint = Requests::InlayHints.new(document, T.must(@store.features_configuration.dig(:inlayHint)), dispatcher)
469
+ inlay_hint = Requests::InlayHints.new(
470
+ @global_state,
471
+ document,
472
+ dispatcher,
473
+ )
474
+
475
+ # The code lens listener requires the index to be populated, so the DeclarationListener must be inserted first in
476
+ # the dispatcher's state
477
+ code_lens = nil #: Requests::CodeLens?
488
478
 
489
479
  if document.is_a?(RubyDocument) && document.should_index?
490
480
  # Re-index the file as it is modified. This mode of indexing updates entries only. Require path trees are only
@@ -495,11 +485,13 @@ module RubyLsp
495
485
  @global_state.index.handle_change(uri) do |index|
496
486
  index.delete(uri, skip_require_paths_tree: true)
497
487
  RubyIndexer::DeclarationListener.new(index, dispatcher, parse_result, uri, collect_comments: true)
498
- dispatcher.dispatch(parse_result.value)
488
+ code_lens = Requests::CodeLens.new(@global_state, document, dispatcher)
489
+ dispatcher.dispatch(document.ast)
499
490
  end
500
491
  end
501
492
  else
502
- dispatcher.dispatch(parse_result.value)
493
+ code_lens = Requests::CodeLens.new(@global_state, document, dispatcher)
494
+ dispatcher.dispatch(document.ast)
503
495
  end
504
496
 
505
497
  # Store all responses retrieve in this round of visits in the cache and then return the response for the request
@@ -507,7 +499,11 @@ module RubyLsp
507
499
  document.cache_set("textDocument/foldingRange", folding_range.perform)
508
500
  document.cache_set("textDocument/documentSymbol", document_symbol.perform)
509
501
  document.cache_set("textDocument/documentLink", document_link.perform)
510
- document.cache_set("textDocument/codeLens", code_lens.perform)
502
+ document.cache_set(
503
+ "textDocument/codeLens",
504
+ code_lens #: as !nil
505
+ .perform,
506
+ )
511
507
  document.cache_set("textDocument/inlayHint", inlay_hint.perform)
512
508
 
513
509
  send_message(Result.new(id: message[:id], response: document.cache_get(message[:method])))
@@ -518,7 +514,7 @@ module RubyLsp
518
514
  alias_method :text_document_code_lens, :run_combined_requests
519
515
  alias_method :text_document_folding_range, :run_combined_requests
520
516
 
521
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
517
+ #: (Hash[Symbol, untyped] message) -> void
522
518
  def text_document_semantic_tokens_full(message)
523
519
  document = @store.get(message.dig(:params, :textDocument, :uri))
524
520
 
@@ -534,12 +530,12 @@ module RubyLsp
534
530
 
535
531
  dispatcher = Prism::Dispatcher.new
536
532
  semantic_highlighting = Requests::SemanticHighlighting.new(@global_state, dispatcher, document, nil)
537
- dispatcher.visit(document.parse_result.value)
533
+ dispatcher.visit(document.ast)
538
534
 
539
535
  send_message(Result.new(id: message[:id], response: semantic_highlighting.perform))
540
536
  end
541
537
 
542
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
538
+ #: (Hash[Symbol, untyped] message) -> void
543
539
  def text_document_semantic_tokens_delta(message)
544
540
  document = @store.get(message.dig(:params, :textDocument, :uri))
545
541
 
@@ -560,11 +556,11 @@ module RubyLsp
560
556
  document,
561
557
  message.dig(:params, :previousResultId),
562
558
  )
563
- dispatcher.visit(document.parse_result.value)
559
+ dispatcher.visit(document.ast)
564
560
  send_message(Result.new(id: message[:id], response: request.perform))
565
561
  end
566
562
 
567
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
563
+ #: (Hash[Symbol, untyped] message) -> void
568
564
  def text_document_semantic_tokens_range(message)
569
565
  params = message[:params]
570
566
  range = params[:range]
@@ -589,11 +585,11 @@ module RubyLsp
589
585
  nil,
590
586
  range: range.dig(:start, :line)..range.dig(:end, :line),
591
587
  )
592
- dispatcher.visit(document.parse_result.value)
588
+ dispatcher.visit(document.ast)
593
589
  send_message(Result.new(id: message[:id], response: request.perform))
594
590
  end
595
591
 
596
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
592
+ #: (Hash[Symbol, untyped] message) -> void
597
593
  def text_document_range_formatting(message)
598
594
  # If formatter is set to `auto` but no supported formatting gem is found, don't attempt to format
599
595
  if @global_state.formatter == "none"
@@ -621,7 +617,7 @@ module RubyLsp
621
617
  send_message(Result.new(id: message[:id], response: response))
622
618
  end
623
619
 
624
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
620
+ #: (Hash[Symbol, untyped] message) -> void
625
621
  def text_document_formatting(message)
626
622
  # If formatter is set to `auto` but no supported formatting gem is found, don't attempt to format
627
623
  if @global_state.formatter == "none"
@@ -662,10 +658,14 @@ module RubyLsp
662
658
  "Formatting error: #{error.message}",
663
659
  type: Constant::MessageType::ERROR,
664
660
  ))
661
+ send_message(Notification.window_log_message(
662
+ "Formatting failed with\r\n: #{error.full_message}",
663
+ type: Constant::MessageType::ERROR,
664
+ ))
665
665
  send_empty_response(message[:id])
666
666
  end
667
667
 
668
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
668
+ #: (Hash[Symbol, untyped] message) -> void
669
669
  def text_document_document_highlight(message)
670
670
  params = message[:params]
671
671
  dispatcher = Prism::Dispatcher.new
@@ -677,11 +677,11 @@ module RubyLsp
677
677
  end
678
678
 
679
679
  request = Requests::DocumentHighlight.new(@global_state, document, params[:position], dispatcher)
680
- dispatcher.dispatch(document.parse_result.value)
680
+ dispatcher.dispatch(document.ast)
681
681
  send_message(Result.new(id: message[:id], response: request.perform))
682
682
  end
683
683
 
684
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
684
+ #: (Hash[Symbol, untyped] message) -> void
685
685
  def text_document_on_type_formatting(message)
686
686
  params = message[:params]
687
687
  document = @store.get(params.dig(:textDocument, :uri))
@@ -704,7 +704,7 @@ module RubyLsp
704
704
  )
705
705
  end
706
706
 
707
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
707
+ #: (Hash[Symbol, untyped] message) -> void
708
708
  def text_document_hover(message)
709
709
  params = message[:params]
710
710
  dispatcher = Prism::Dispatcher.new
@@ -729,7 +729,7 @@ module RubyLsp
729
729
  )
730
730
  end
731
731
 
732
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
732
+ #: (Hash[Symbol, untyped] message) -> void
733
733
  def text_document_rename(message)
734
734
  params = message[:params]
735
735
  document = @store.get(params.dig(:textDocument, :uri))
@@ -749,7 +749,7 @@ module RubyLsp
749
749
  send_message(Error.new(id: message[:id], code: Constant::ErrorCodes::REQUEST_FAILED, message: e.message))
750
750
  end
751
751
 
752
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
752
+ #: (Hash[Symbol, untyped] message) -> void
753
753
  def text_document_prepare_rename(message)
754
754
  params = message[:params]
755
755
  document = @store.get(params.dig(:textDocument, :uri))
@@ -767,7 +767,7 @@ module RubyLsp
767
767
  )
768
768
  end
769
769
 
770
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
770
+ #: (Hash[Symbol, untyped] message) -> void
771
771
  def text_document_references(message)
772
772
  params = message[:params]
773
773
  document = @store.get(params.dig(:textDocument, :uri))
@@ -785,15 +785,19 @@ module RubyLsp
785
785
  )
786
786
  end
787
787
 
788
- sig { params(document: Document[T.untyped]).returns(RubyDocument::SorbetLevel) }
788
+ #: (Document[untyped] document) -> SorbetLevel
789
789
  def sorbet_level(document)
790
- return RubyDocument::SorbetLevel::Ignore unless @global_state.has_type_checker
791
- return RubyDocument::SorbetLevel::Ignore unless document.is_a?(RubyDocument)
790
+ return SorbetLevel.ignore unless document.is_a?(RubyDocument)
791
+ return SorbetLevel.ignore unless @global_state.has_type_checker
792
+
793
+ sigil = document.parse_result.magic_comments.find do |comment|
794
+ comment.key == "typed"
795
+ end&.value
792
796
 
793
- document.sorbet_level
797
+ SorbetLevel.new(sigil)
794
798
  end
795
799
 
796
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
800
+ #: (Hash[Symbol, untyped] message) -> void
797
801
  def text_document_inlay_hint(message)
798
802
  params = message[:params]
799
803
  document = @store.get(params.dig(:textDocument, :uri))
@@ -811,7 +815,6 @@ module RubyLsp
811
815
  return
812
816
  end
813
817
 
814
- hints_configurations = T.must(@store.features_configuration.dig(:inlayHint))
815
818
  dispatcher = Prism::Dispatcher.new
816
819
 
817
820
  unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
@@ -819,15 +822,15 @@ module RubyLsp
819
822
  return
820
823
  end
821
824
 
822
- request = Requests::InlayHints.new(document, hints_configurations, dispatcher)
823
- dispatcher.visit(document.parse_result.value)
825
+ request = Requests::InlayHints.new(@global_state, document, dispatcher)
826
+ dispatcher.visit(document.ast)
824
827
  result = request.perform
825
828
  document.cache_set("textDocument/inlayHint", result)
826
829
 
827
830
  send_message(Result.new(id: message[:id], response: result.select { |hint| range.cover?(hint.position[:line]) }))
828
831
  end
829
832
 
830
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
833
+ #: (Hash[Symbol, untyped] message) -> void
831
834
  def text_document_code_action(message)
832
835
  params = message[:params]
833
836
  document = @store.get(params.dig(:textDocument, :uri))
@@ -849,7 +852,7 @@ module RubyLsp
849
852
  )
850
853
  end
851
854
 
852
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
855
+ #: (Hash[Symbol, untyped] message) -> void
853
856
  def code_action_resolve(message)
854
857
  params = message[:params]
855
858
  uri = URI(params.dig(:data, :uri))
@@ -861,20 +864,12 @@ module RubyLsp
861
864
  end
862
865
 
863
866
  result = Requests::CodeActionResolve.new(document, @global_state, params).perform
864
-
865
- case result
866
- when Requests::CodeActionResolve::Error::EmptySelection
867
- fail_request_and_notify(message[:id], "Invalid selection for extract variable refactor")
868
- when Requests::CodeActionResolve::Error::InvalidTargetRange
869
- fail_request_and_notify(message[:id], "Couldn't find an appropriate location to place extracted refactor")
870
- when Requests::CodeActionResolve::Error::UnknownCodeAction
871
- fail_request_and_notify(message[:id], "Unknown code action")
872
- else
873
- send_message(Result.new(id: message[:id], response: result))
874
- end
867
+ send_message(Result.new(id: message[:id], response: result))
868
+ rescue Requests::CodeActionResolve::CodeActionError => e
869
+ fail_request_and_notify(message[:id], e.message)
875
870
  end
876
871
 
877
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
872
+ #: (Hash[Symbol, untyped] message) -> void
878
873
  def text_document_diagnostic(message)
879
874
  # Do not compute diagnostics for files outside of the workspace. For example, if someone is looking at a gem's
880
875
  # source code, we don't want to show diagnostics for it
@@ -911,10 +906,14 @@ module RubyLsp
911
906
  "Error running diagnostics: #{error.message}",
912
907
  type: Constant::MessageType::ERROR,
913
908
  ))
909
+ send_message(Notification.window_log_message(
910
+ "Diagnostics failed with\r\n: #{error.full_message}",
911
+ type: Constant::MessageType::ERROR,
912
+ ))
914
913
  send_empty_response(message[:id])
915
914
  end
916
915
 
917
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
916
+ #: (Hash[Symbol, untyped] message) -> void
918
917
  def text_document_completion(message)
919
918
  params = message[:params]
920
919
  dispatcher = Prism::Dispatcher.new
@@ -939,7 +938,7 @@ module RubyLsp
939
938
  )
940
939
  end
941
940
 
942
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
941
+ #: (Hash[Symbol, untyped] message) -> void
943
942
  def text_document_completion_item_resolve(message)
944
943
  # When responding to a delegated completion request, it means we're handling a completion item that isn't related
945
944
  # to Ruby (probably related to an ERB host language like HTML). We need to return the original completion item
@@ -958,7 +957,7 @@ module RubyLsp
958
957
  ))
959
958
  end
960
959
 
961
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
960
+ #: (Hash[Symbol, untyped] message) -> void
962
961
  def text_document_signature_help(message)
963
962
  params = message[:params]
964
963
  dispatcher = Prism::Dispatcher.new
@@ -984,7 +983,7 @@ module RubyLsp
984
983
  )
985
984
  end
986
985
 
987
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
986
+ #: (Hash[Symbol, untyped] message) -> void
988
987
  def text_document_definition(message)
989
988
  params = message[:params]
990
989
  dispatcher = Prism::Dispatcher.new
@@ -1009,8 +1008,23 @@ module RubyLsp
1009
1008
  )
1010
1009
  end
1011
1010
 
1012
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1011
+ #: (Hash[Symbol, untyped] message) -> void
1013
1012
  def workspace_did_change_watched_files(message)
1013
+ # If indexing is not complete yet, delay processing did change watched file notifications. We need initial
1014
+ # indexing to be in place so that we can handle file changes appropriately without risking duplicates. We also
1015
+ # have to sleep before re-inserting the notification in the queue otherwise the worker can get stuck in its own
1016
+ # loop of pushing and popping the same notification
1017
+ unless @global_state.index.initial_indexing_completed
1018
+ Thread.new do
1019
+ sleep(2)
1020
+ # We have to ensure that the queue is not closed yet, since nothing stops the user from saving a file and then
1021
+ # immediately telling the LSP to shutdown
1022
+ @incoming_queue << message unless @incoming_queue.closed?
1023
+ end
1024
+
1025
+ return
1026
+ end
1027
+
1014
1028
  changes = message.dig(:params, :changes)
1015
1029
  # We allow add-ons to register for watching files and we have no restrictions for what they register for. If the
1016
1030
  # same pattern is registered more than once, the LSP will receive duplicate change notifications. Receiving them
@@ -1037,7 +1051,8 @@ module RubyLsp
1037
1051
  end
1038
1052
 
1039
1053
  Addon.file_watcher_addons.each do |addon|
1040
- T.unsafe(addon).workspace_did_change_watched_files(changes)
1054
+ addon #: as untyped
1055
+ .workspace_did_change_watched_files(changes)
1041
1056
  rescue => e
1042
1057
  send_log_message(
1043
1058
  "Error in #{addon.name} add-on while processing watched file notifications: #{e.full_message}",
@@ -1046,32 +1061,34 @@ module RubyLsp
1046
1061
  end
1047
1062
  end
1048
1063
 
1049
- sig { params(index: RubyIndexer::Index, file_path: String, change_type: Integer).void }
1064
+ #: (RubyIndexer::Index index, String file_path, Integer change_type) -> void
1050
1065
  def handle_ruby_file_change(index, file_path, change_type)
1051
- load_path_entry = $LOAD_PATH.find { |load_path| file_path.start_with?(load_path) }
1052
- uri = URI::Generic.from_path(load_path_entry: load_path_entry, path: file_path)
1053
-
1054
- content = File.read(file_path)
1055
-
1056
- case change_type
1057
- when Constant::FileChangeType::CREATED
1058
- # If we receive a late created notification for a file that has already been claimed by the client, we want to
1059
- # handle change for that URI so that the require path tree is updated
1060
- @store.key?(uri) ? index.handle_change(uri, content) : index.index_single(uri, content)
1061
- when Constant::FileChangeType::CHANGED
1062
- # We only handle changes on file watched notifications if the client is not the one managing this URI.
1063
- # Otherwise, these changes are handled when running the combined requests
1064
- index.handle_change(uri, content) unless @store.key?(uri)
1065
- when Constant::FileChangeType::DELETED
1066
- index.delete(uri)
1067
- end
1068
- rescue Errno::ENOENT
1069
- # If a file is created and then delete immediately afterwards, we will process the created notification before we
1070
- # receive the deleted one, but the file no longer exists. This may happen when running a test suite that creates
1071
- # and deletes files automatically.
1066
+ @global_state.synchronize do
1067
+ load_path_entry = $LOAD_PATH.find { |load_path| file_path.start_with?(load_path) }
1068
+ uri = URI::Generic.from_path(load_path_entry: load_path_entry, path: file_path)
1069
+
1070
+ case change_type
1071
+ when Constant::FileChangeType::CREATED
1072
+ content = File.read(file_path)
1073
+ # If we receive a late created notification for a file that has already been claimed by the client, we want to
1074
+ # handle change for that URI so that the require path tree is updated
1075
+ @store.key?(uri) ? index.handle_change(uri, content) : index.index_single(uri, content)
1076
+ when Constant::FileChangeType::CHANGED
1077
+ content = File.read(file_path)
1078
+ # We only handle changes on file watched notifications if the client is not the one managing this URI.
1079
+ # Otherwise, these changes are handled when running the combined requests
1080
+ index.handle_change(uri, content) unless @store.key?(uri)
1081
+ when Constant::FileChangeType::DELETED
1082
+ index.delete(uri)
1083
+ end
1084
+ rescue Errno::ENOENT
1085
+ # If a file is created and then delete immediately afterwards, we will process the created notification before
1086
+ # we receive the deleted one, but the file no longer exists. This may happen when running a test suite that
1087
+ # creates and deletes files automatically.
1088
+ end
1072
1089
  end
1073
1090
 
1074
- sig { params(uri: URI::Generic).void }
1091
+ #: (URI::Generic uri) -> void
1075
1092
  def handle_rubocop_config_change(uri)
1076
1093
  return unless defined?(Requests::Support::RuboCopFormatter)
1077
1094
 
@@ -1079,11 +1096,7 @@ module RubyLsp
1079
1096
  @global_state.register_formatter("rubocop_internal", Requests::Support::RuboCopFormatter.new)
1080
1097
 
1081
1098
  # Clear all document caches for pull diagnostics
1082
- @global_state.synchronize do
1083
- @store.each do |_uri, document|
1084
- document.cache_set("textDocument/diagnostic", Document::EMPTY_CACHE)
1085
- end
1086
- end
1099
+ @store.each { |_uri, document| document.clear_cache("textDocument/diagnostic") }
1087
1100
 
1088
1101
  # Request a pull diagnostic refresh from the editor
1089
1102
  if @global_state.client_capabilities.supports_diagnostic_refresh
@@ -1091,7 +1104,7 @@ module RubyLsp
1091
1104
  end
1092
1105
  end
1093
1106
 
1094
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1107
+ #: (Hash[Symbol, untyped] message) -> void
1095
1108
  def workspace_symbol(message)
1096
1109
  send_message(
1097
1110
  Result.new(
@@ -1104,7 +1117,7 @@ module RubyLsp
1104
1117
  )
1105
1118
  end
1106
1119
 
1107
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1120
+ #: (Hash[Symbol, untyped] message) -> void
1108
1121
  def text_document_show_syntax_tree(message)
1109
1122
  params = message[:params]
1110
1123
  document = @store.get(params.dig(:textDocument, :uri))
@@ -1123,7 +1136,26 @@ module RubyLsp
1123
1136
  send_message(Result.new(id: message[:id], response: response))
1124
1137
  end
1125
1138
 
1126
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1139
+ #: (Hash[Symbol, untyped] message) -> void
1140
+ def experimental_go_to_relevant_file(message)
1141
+ path = message.dig(:params, :textDocument, :uri).to_standardized_path
1142
+ unless path.nil? || path.start_with?(@global_state.workspace_path)
1143
+ send_empty_response(message[:id])
1144
+ return
1145
+ end
1146
+
1147
+ unless path
1148
+ send_empty_response(message[:id])
1149
+ return
1150
+ end
1151
+
1152
+ response = {
1153
+ locations: Requests::GoToRelevantFile.new(path, @global_state.workspace_path).perform,
1154
+ }
1155
+ send_message(Result.new(id: message[:id], response: response))
1156
+ end
1157
+
1158
+ #: (Hash[Symbol, untyped] message) -> void
1127
1159
  def text_document_prepare_type_hierarchy(message)
1128
1160
  params = message[:params]
1129
1161
  document = @store.get(params.dig(:textDocument, :uri))
@@ -1142,7 +1174,7 @@ module RubyLsp
1142
1174
  send_message(Result.new(id: message[:id], response: response))
1143
1175
  end
1144
1176
 
1145
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1177
+ #: (Hash[Symbol, untyped] message) -> void
1146
1178
  def type_hierarchy_supertypes(message)
1147
1179
  response = Requests::TypeHierarchySupertypes.new(
1148
1180
  @global_state.index,
@@ -1151,14 +1183,14 @@ module RubyLsp
1151
1183
  send_message(Result.new(id: message[:id], response: response))
1152
1184
  end
1153
1185
 
1154
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1186
+ #: (Hash[Symbol, untyped] message) -> void
1155
1187
  def type_hierarchy_subtypes(message)
1156
1188
  # TODO: implement subtypes
1157
1189
  # The current index representation doesn't allow us to find the children of an entry.
1158
1190
  send_message(Result.new(id: message[:id], response: nil))
1159
1191
  end
1160
1192
 
1161
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1193
+ #: (Hash[Symbol, untyped] message) -> void
1162
1194
  def workspace_dependencies(message)
1163
1195
  unless @global_state.top_level_bundle
1164
1196
  send_message(Result.new(id: message[:id], response: []))
@@ -1179,19 +1211,20 @@ module RubyLsp
1179
1211
  }
1180
1212
  end
1181
1213
  end
1182
- rescue Bundler::GemNotFound
1214
+ rescue Bundler::GemNotFound, Bundler::GemfileNotFound
1183
1215
  []
1184
1216
  end
1185
1217
 
1186
1218
  send_message(Result.new(id: message[:id], response: response))
1187
1219
  end
1188
1220
 
1189
- sig { override.void }
1221
+ # @override
1222
+ #: -> void
1190
1223
  def shutdown
1191
1224
  Addon.unload_addons
1192
1225
  end
1193
1226
 
1194
- sig { void }
1227
+ #: -> void
1195
1228
  def perform_initial_indexing
1196
1229
  # The begin progress invocation happens during `initialize`, so that the notification is sent before we are
1197
1230
  # stuck indexing files
@@ -1217,13 +1250,29 @@ module RubyLsp
1217
1250
  # allocations and garbage collections are faster
1218
1251
  GC.compact unless @test_mode
1219
1252
 
1253
+ @global_state.synchronize do
1254
+ # If we linearize ancestors while the index is not fully populated, we may end up caching incorrect results
1255
+ # that were missing namespaces. After indexing is complete, we need to clear the ancestors cache and start
1256
+ # again
1257
+ @global_state.index.clear_ancestors
1258
+
1259
+ # The results for code lens depend on ancestor linearization, so we need to clear any previously computed
1260
+ # responses
1261
+ @store.each { |_uri, document| document.clear_cache("textDocument/codeLens") }
1262
+ end
1263
+
1220
1264
  # Always end the progress notification even if indexing failed or else it never goes away and the user has no
1221
1265
  # way of dismissing it
1222
1266
  end_progress("indexing-progress")
1267
+
1268
+ # Request a code lens refresh if we populated them before all test parent classes were indexed
1269
+ if @global_state.client_capabilities.supports_code_lens_refresh
1270
+ send_message(Request.new(id: @current_request_id, method: "workspace/codeLens/refresh", params: nil))
1271
+ end
1223
1272
  end
1224
1273
  end
1225
1274
 
1226
- sig { params(id: String, title: String, percentage: Integer).void }
1275
+ #: (String id, String title, ?percentage: Integer) -> void
1227
1276
  def begin_progress(id, title, percentage: 0)
1228
1277
  return unless @global_state.client_capabilities.supports_progress
1229
1278
 
@@ -1236,14 +1285,14 @@ module RubyLsp
1236
1285
  send_message(Notification.progress_begin(id, title, percentage: percentage, message: "#{percentage}% completed"))
1237
1286
  end
1238
1287
 
1239
- sig { params(id: String, percentage: Integer).void }
1288
+ #: (String id, Integer percentage) -> void
1240
1289
  def progress(id, percentage)
1241
1290
  return unless @global_state.client_capabilities.supports_progress
1242
1291
 
1243
1292
  send_message(Notification.progress_report(id, percentage: percentage, message: "#{percentage}% completed"))
1244
1293
  end
1245
1294
 
1246
- sig { params(id: String).void }
1295
+ #: (String id) -> void
1247
1296
  def end_progress(id)
1248
1297
  return unless @global_state.client_capabilities.supports_progress
1249
1298
 
@@ -1253,7 +1302,7 @@ module RubyLsp
1253
1302
  # notification
1254
1303
  end
1255
1304
 
1256
- sig { void }
1305
+ #: -> void
1257
1306
  def check_formatter_is_available
1258
1307
  return if @setup_error
1259
1308
  # Warn of an unavailable `formatter` setting, e.g. `rubocop_internal` on a project which doesn't have RuboCop.
@@ -1271,7 +1320,7 @@ module RubyLsp
1271
1320
  end
1272
1321
  end
1273
1322
 
1274
- sig { params(indexing_options: T.nilable(T::Hash[Symbol, T.untyped])).void }
1323
+ #: (Hash[Symbol, untyped]? indexing_options) -> void
1275
1324
  def process_indexing_configuration(indexing_options)
1276
1325
  # Need to use the workspace URI, otherwise, this will fail for people working on a project that is a symlink.
1277
1326
  index_path = File.join(@global_state.workspace_path, ".index.yml")
@@ -1312,7 +1361,7 @@ module RubyLsp
1312
1361
  configuration.apply_config(indexing_options.transform_keys { |key| key.to_s.gsub(/([A-Z])/, "_\\1").downcase })
1313
1362
  end
1314
1363
 
1315
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1364
+ #: (Hash[Symbol, untyped] message) -> void
1316
1365
  def window_show_message_request(message)
1317
1366
  result = message[:result]
1318
1367
  return unless result
@@ -1327,7 +1376,7 @@ module RubyLsp
1327
1376
  # NOTE: all servers methods are void because they can produce several messages for the client. The only reason this
1328
1377
  # method returns the created thread is to that we can join it in tests and avoid flakiness. The implementation is
1329
1378
  # not supposed to rely on the return of this method
1330
- sig { params(message: T::Hash[Symbol, T.untyped]).returns(T.nilable(Thread)) }
1379
+ #: (Hash[Symbol, untyped] message) -> Thread?
1331
1380
  def compose_bundle(message)
1332
1381
  already_composed_path = File.join(@global_state.workspace_path, ".ruby-lsp", "bundle_is_composed")
1333
1382
  id = message[:id]
@@ -1345,36 +1394,58 @@ module RubyLsp
1345
1394
 
1346
1395
  # We compose the bundle in a thread so that the LSP continues to work while we're checking for its validity. Once
1347
1396
  # we return the response back to the editor, then the restart is triggered
1397
+ launch_bundle_compose("Recomposing the bundle ahead of restart") do |stderr, status|
1398
+ if status&.exitstatus == 0
1399
+ # Create a signal for the restart that it can skip composing the bundle and launch directly
1400
+ FileUtils.touch(already_composed_path)
1401
+ send_message(Result.new(id: id, response: { success: true }))
1402
+ else
1403
+ # This special error code makes the extension avoid restarting in case we already know that the composed
1404
+ # bundle is not valid
1405
+ send_message(
1406
+ Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: "Failed to compose bundle\n#{stderr}"),
1407
+ )
1408
+ end
1409
+ end
1410
+ end
1411
+
1412
+ #: -> void
1413
+ def update_server
1414
+ return unless File.exist?(File.join(@global_state.workspace_path, ".ruby-lsp", "needs_update"))
1415
+
1416
+ launch_bundle_compose("Trying to update server") do |stderr, status|
1417
+ if status&.exitstatus == 0
1418
+ send_log_message("Successfully updated the server")
1419
+ else
1420
+ send_log_message("Failed to update server\n#{stderr}", type: Constant::MessageType::ERROR)
1421
+ end
1422
+ end
1423
+ end
1424
+
1425
+ #: (String) { (IO, Process::Status?) -> void } -> Thread
1426
+ def launch_bundle_compose(log, &block)
1348
1427
  Thread.new do
1349
- send_log_message("Recomposing the bundle ahead of restart")
1428
+ send_log_message(log)
1350
1429
 
1351
1430
  _stdout, stderr, status = Bundler.with_unbundled_env do
1352
1431
  Open3.capture3(
1353
1432
  Gem.ruby,
1354
1433
  "-I",
1355
- File.dirname(T.must(__dir__)),
1434
+ File.dirname(
1435
+ __dir__, #: as !nil
1436
+ ),
1356
1437
  File.expand_path("../../exe/ruby-lsp-launcher", __dir__),
1357
1438
  @global_state.workspace_uri.to_s,
1358
1439
  chdir: @global_state.workspace_path,
1359
1440
  )
1360
1441
  end
1361
1442
 
1362
- if status&.exitstatus == 0
1363
- # Create a signal for the restart that it can skip composing the bundle and launch directly
1364
- FileUtils.touch(already_composed_path)
1365
- send_message(Result.new(id: id, response: { success: true }))
1366
- else
1367
- # This special error code makes the extension avoid restarting in case we already know that the composed
1368
- # bundle is not valid
1369
- send_message(
1370
- Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: "Failed to compose bundle\n#{stderr}"),
1371
- )
1372
- end
1443
+ block.call(stderr, status)
1373
1444
  end
1374
1445
  end
1375
1446
 
1376
1447
  # Returns internal state information for debugging purposes
1377
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1448
+ #: (Hash[Symbol, untyped] message) -> void
1378
1449
  def diagnose_state(message)
1379
1450
  documents = {}
1380
1451
  @store.each { |uri, document| documents[uri] = document.source }
@@ -1394,7 +1465,7 @@ module RubyLsp
1394
1465
 
1395
1466
  # Discovers all available test groups and examples in a given file taking into consideration the merged response of
1396
1467
  # all add-ons
1397
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1468
+ #: (Hash[Symbol, untyped] message) -> void
1398
1469
  def discover_tests(message)
1399
1470
  document = @store.get(message.dig(:params, :textDocument, :uri))
1400
1471
 
@@ -1414,5 +1485,41 @@ module RubyLsp
1414
1485
 
1415
1486
  send_message(Result.new(id: message[:id], response: items.map(&:to_hash)))
1416
1487
  end
1488
+
1489
+ #: (Hash[Symbol, untyped] message) -> void
1490
+ def resolve_test_commands(message)
1491
+ items = message.dig(:params, :items)
1492
+ commands = Listeners::TestStyle.resolve_test_commands(items)
1493
+
1494
+ Addon.addons.each do |addon|
1495
+ commands.concat(addon.resolve_test_commands(items))
1496
+ end
1497
+
1498
+ send_message(Result.new(
1499
+ id: message[:id],
1500
+ response: { commands: commands },
1501
+ ))
1502
+ end
1503
+
1504
+ #: (Hash[Symbol, untyped] message) -> void
1505
+ def code_lens_resolve(message)
1506
+ code_lens = message[:params]
1507
+ args = code_lens.dig(:data, :arguments)
1508
+
1509
+ case code_lens.dig(:data, :kind)
1510
+ when "run_test"
1511
+ code_lens[:command] = Interface::Command.new(title: "▶ Run", command: "rubyLsp.runTest", arguments: args)
1512
+ when "run_test_in_terminal"
1513
+ code_lens[:command] =
1514
+ Interface::Command.new(title: "▶ Run in terminal", command: "rubyLsp.runTestInTerminal", arguments: args)
1515
+ when "debug_test"
1516
+ code_lens[:command] = Interface::Command.new(title: "⚙ Debug", command: "rubyLsp.debugTest", arguments: args)
1517
+ end
1518
+
1519
+ send_message(Result.new(
1520
+ id: message[:id],
1521
+ response: code_lens,
1522
+ ))
1523
+ end
1417
1524
  end
1418
1525
  end