ruby-lsp 0.23.11 → 0.25.0

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 +18 -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 +106 -236
  14. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +155 -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 -8
  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 +170 -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 +73 -86
  35. data/lib/ruby_lsp/base_server.rb +41 -42
  36. data/lib/ruby_lsp/client_capabilities.rb +16 -13
  37. data/lib/ruby_lsp/document.rb +201 -98
  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 +76 -74
  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 +203 -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 +30 -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 +266 -143
  108. data/lib/ruby_lsp/setup_bundler.rb +113 -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 +236 -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,18 @@ 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
129
  # reporting these to our telemetry
128
- case e
129
- when Store::NonExistingDocumentError
130
+ if e.is_a?(Store::NonExistingDocumentError)
130
131
  send_message(Error.new(
131
132
  id: message[:id],
132
133
  code: Constant::ErrorCodes::INVALID_PARAMS,
133
134
  message: e.full_message,
134
135
  ))
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
136
  else
147
137
  send_message(Error.new(
148
138
  id: message[:id],
@@ -161,7 +151,7 @@ module RubyLsp
161
151
  end
162
152
 
163
153
  # Process responses to requests that were sent to the client
164
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
154
+ #: (Hash[Symbol, untyped] message) -> void
165
155
  def process_response(message)
166
156
  case message.dig(:result, :method)
167
157
  when "window/showMessageRequest"
@@ -169,7 +159,7 @@ module RubyLsp
169
159
  end
170
160
  end
171
161
 
172
- sig { params(include_project_addons: T::Boolean).void }
162
+ #: (?include_project_addons: bool) -> void
173
163
  def load_addons(include_project_addons: true)
174
164
  # If invoking Bundler.setup failed, then the load path will not be configured properly and trying to load add-ons
175
165
  # with Gem.find_files will find every single version installed of an add-on, leading to requiring several
@@ -209,7 +199,7 @@ module RubyLsp
209
199
 
210
200
  private
211
201
 
212
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
202
+ #: (Hash[Symbol, untyped] message) -> void
213
203
  def run_initialize(message)
214
204
  options = message[:params]
215
205
  global_state_notifications = @global_state.apply_options(options)
@@ -219,9 +209,6 @@ module RubyLsp
219
209
 
220
210
  configured_features = options.dig(:initializationOptions, :enabledFeatures)
221
211
 
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
212
  enabled_features = case configured_features
226
213
  when Array
227
214
  # If the configuration is using an array, then absent features are disabled and present ones are enabled. That's
@@ -238,7 +225,9 @@ module RubyLsp
238
225
 
239
226
  bundle_env_path = File.join(".ruby-lsp", "bundle_env")
240
227
  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]) }
228
+ env = File.readlines(bundle_env_path).to_h do |line|
229
+ line.chomp.split("=", 2) #: as [String, String]
230
+ end
242
231
  FileUtils.rm(bundle_env_path)
243
232
  env
244
233
  end
@@ -290,6 +279,8 @@ module RubyLsp
290
279
  experimental: {
291
280
  addon_detection: true,
292
281
  compose_bundle: true,
282
+ go_to_relevant_file: true,
283
+ full_test_discovery: true,
293
284
  },
294
285
  ),
295
286
  serverInfo: {
@@ -346,7 +337,7 @@ module RubyLsp
346
337
  end
347
338
  end
348
339
 
349
- sig { void }
340
+ #: -> void
350
341
  def run_initialized
351
342
  load_addons
352
343
  RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
@@ -371,19 +362,20 @@ module RubyLsp
371
362
 
372
363
  perform_initial_indexing
373
364
  check_formatter_is_available
365
+ update_server if @global_state.enabled_feature?(:launcher)
374
366
  end
375
367
 
376
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
368
+ #: (Hash[Symbol, untyped] message) -> void
377
369
  def text_document_did_open(message)
378
370
  @global_state.synchronize do
379
371
  text_document = message.dig(:params, :textDocument)
380
372
  language_id = case text_document[:languageId]
381
373
  when "erb", "eruby"
382
- Document::LanguageId::ERB
374
+ :erb
383
375
  when "rbs"
384
- Document::LanguageId::RBS
376
+ :rbs
385
377
  else
386
- Document::LanguageId::Ruby
378
+ :ruby
387
379
  end
388
380
 
389
381
  document = @store.set(
@@ -412,7 +404,7 @@ module RubyLsp
412
404
  end
413
405
  end
414
406
 
415
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
407
+ #: (Hash[Symbol, untyped] message) -> void
416
408
  def text_document_did_close(message)
417
409
  @global_state.synchronize do
418
410
  uri = message.dig(:params, :textDocument, :uri)
@@ -423,7 +415,7 @@ module RubyLsp
423
415
  end
424
416
  end
425
417
 
426
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
418
+ #: (Hash[Symbol, untyped] message) -> void
427
419
  def text_document_did_change(message)
428
420
  params = message[:params]
429
421
  text_document = params[:textDocument]
@@ -433,7 +425,7 @@ module RubyLsp
433
425
  end
434
426
  end
435
427
 
436
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
428
+ #: (Hash[Symbol, untyped] message) -> void
437
429
  def text_document_selection_range(message)
438
430
  uri = message.dig(:params, :textDocument, :uri)
439
431
  ranges = @store.cache_fetch(uri, "textDocument/selectionRange") do |document|
@@ -459,7 +451,7 @@ module RubyLsp
459
451
  send_message(Result.new(id: message[:id], response: response))
460
452
  end
461
453
 
462
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
454
+ #: (Hash[Symbol, untyped] message) -> void
463
455
  def run_combined_requests(message)
464
456
  uri = URI(message.dig(:params, :textDocument, :uri))
465
457
  document = @store.get(uri)
@@ -483,8 +475,15 @@ module RubyLsp
483
475
  folding_range = Requests::FoldingRanges.new(parse_result.comments, dispatcher)
484
476
  document_symbol = Requests::DocumentSymbol.new(uri, dispatcher)
485
477
  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)
478
+ inlay_hint = Requests::InlayHints.new(
479
+ @global_state,
480
+ document,
481
+ dispatcher,
482
+ )
483
+
484
+ # The code lens listener requires the index to be populated, so the DeclarationListener must be inserted first in
485
+ # the dispatcher's state
486
+ code_lens = nil #: Requests::CodeLens?
488
487
 
489
488
  if document.is_a?(RubyDocument) && document.should_index?
490
489
  # Re-index the file as it is modified. This mode of indexing updates entries only. Require path trees are only
@@ -495,11 +494,13 @@ module RubyLsp
495
494
  @global_state.index.handle_change(uri) do |index|
496
495
  index.delete(uri, skip_require_paths_tree: true)
497
496
  RubyIndexer::DeclarationListener.new(index, dispatcher, parse_result, uri, collect_comments: true)
498
- dispatcher.dispatch(parse_result.value)
497
+ code_lens = Requests::CodeLens.new(@global_state, document, dispatcher)
498
+ dispatcher.dispatch(document.ast)
499
499
  end
500
500
  end
501
501
  else
502
- dispatcher.dispatch(parse_result.value)
502
+ code_lens = Requests::CodeLens.new(@global_state, document, dispatcher)
503
+ dispatcher.dispatch(document.ast)
503
504
  end
504
505
 
505
506
  # Store all responses retrieve in this round of visits in the cache and then return the response for the request
@@ -507,7 +508,11 @@ module RubyLsp
507
508
  document.cache_set("textDocument/foldingRange", folding_range.perform)
508
509
  document.cache_set("textDocument/documentSymbol", document_symbol.perform)
509
510
  document.cache_set("textDocument/documentLink", document_link.perform)
510
- document.cache_set("textDocument/codeLens", code_lens.perform)
511
+ document.cache_set(
512
+ "textDocument/codeLens",
513
+ code_lens #: as !nil
514
+ .perform,
515
+ )
511
516
  document.cache_set("textDocument/inlayHint", inlay_hint.perform)
512
517
 
513
518
  send_message(Result.new(id: message[:id], response: document.cache_get(message[:method])))
@@ -518,7 +523,7 @@ module RubyLsp
518
523
  alias_method :text_document_code_lens, :run_combined_requests
519
524
  alias_method :text_document_folding_range, :run_combined_requests
520
525
 
521
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
526
+ #: (Hash[Symbol, untyped] message) -> void
522
527
  def text_document_semantic_tokens_full(message)
523
528
  document = @store.get(message.dig(:params, :textDocument, :uri))
524
529
 
@@ -534,12 +539,12 @@ module RubyLsp
534
539
 
535
540
  dispatcher = Prism::Dispatcher.new
536
541
  semantic_highlighting = Requests::SemanticHighlighting.new(@global_state, dispatcher, document, nil)
537
- dispatcher.visit(document.parse_result.value)
542
+ dispatcher.visit(document.ast)
538
543
 
539
544
  send_message(Result.new(id: message[:id], response: semantic_highlighting.perform))
540
545
  end
541
546
 
542
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
547
+ #: (Hash[Symbol, untyped] message) -> void
543
548
  def text_document_semantic_tokens_delta(message)
544
549
  document = @store.get(message.dig(:params, :textDocument, :uri))
545
550
 
@@ -560,11 +565,11 @@ module RubyLsp
560
565
  document,
561
566
  message.dig(:params, :previousResultId),
562
567
  )
563
- dispatcher.visit(document.parse_result.value)
568
+ dispatcher.visit(document.ast)
564
569
  send_message(Result.new(id: message[:id], response: request.perform))
565
570
  end
566
571
 
567
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
572
+ #: (Hash[Symbol, untyped] message) -> void
568
573
  def text_document_semantic_tokens_range(message)
569
574
  params = message[:params]
570
575
  range = params[:range]
@@ -589,11 +594,11 @@ module RubyLsp
589
594
  nil,
590
595
  range: range.dig(:start, :line)..range.dig(:end, :line),
591
596
  )
592
- dispatcher.visit(document.parse_result.value)
597
+ dispatcher.visit(document.ast)
593
598
  send_message(Result.new(id: message[:id], response: request.perform))
594
599
  end
595
600
 
596
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
601
+ #: (Hash[Symbol, untyped] message) -> void
597
602
  def text_document_range_formatting(message)
598
603
  # If formatter is set to `auto` but no supported formatting gem is found, don't attempt to format
599
604
  if @global_state.formatter == "none"
@@ -621,7 +626,7 @@ module RubyLsp
621
626
  send_message(Result.new(id: message[:id], response: response))
622
627
  end
623
628
 
624
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
629
+ #: (Hash[Symbol, untyped] message) -> void
625
630
  def text_document_formatting(message)
626
631
  # If formatter is set to `auto` but no supported formatting gem is found, don't attempt to format
627
632
  if @global_state.formatter == "none"
@@ -662,10 +667,14 @@ module RubyLsp
662
667
  "Formatting error: #{error.message}",
663
668
  type: Constant::MessageType::ERROR,
664
669
  ))
670
+ send_message(Notification.window_log_message(
671
+ "Formatting failed with\r\n: #{error.full_message}",
672
+ type: Constant::MessageType::ERROR,
673
+ ))
665
674
  send_empty_response(message[:id])
666
675
  end
667
676
 
668
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
677
+ #: (Hash[Symbol, untyped] message) -> void
669
678
  def text_document_document_highlight(message)
670
679
  params = message[:params]
671
680
  dispatcher = Prism::Dispatcher.new
@@ -677,11 +686,11 @@ module RubyLsp
677
686
  end
678
687
 
679
688
  request = Requests::DocumentHighlight.new(@global_state, document, params[:position], dispatcher)
680
- dispatcher.dispatch(document.parse_result.value)
689
+ dispatcher.dispatch(document.ast)
681
690
  send_message(Result.new(id: message[:id], response: request.perform))
682
691
  end
683
692
 
684
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
693
+ #: (Hash[Symbol, untyped] message) -> void
685
694
  def text_document_on_type_formatting(message)
686
695
  params = message[:params]
687
696
  document = @store.get(params.dig(:textDocument, :uri))
@@ -704,7 +713,7 @@ module RubyLsp
704
713
  )
705
714
  end
706
715
 
707
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
716
+ #: (Hash[Symbol, untyped] message) -> void
708
717
  def text_document_hover(message)
709
718
  params = message[:params]
710
719
  dispatcher = Prism::Dispatcher.new
@@ -729,7 +738,7 @@ module RubyLsp
729
738
  )
730
739
  end
731
740
 
732
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
741
+ #: (Hash[Symbol, untyped] message) -> void
733
742
  def text_document_rename(message)
734
743
  params = message[:params]
735
744
  document = @store.get(params.dig(:textDocument, :uri))
@@ -749,7 +758,7 @@ module RubyLsp
749
758
  send_message(Error.new(id: message[:id], code: Constant::ErrorCodes::REQUEST_FAILED, message: e.message))
750
759
  end
751
760
 
752
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
761
+ #: (Hash[Symbol, untyped] message) -> void
753
762
  def text_document_prepare_rename(message)
754
763
  params = message[:params]
755
764
  document = @store.get(params.dig(:textDocument, :uri))
@@ -767,7 +776,7 @@ module RubyLsp
767
776
  )
768
777
  end
769
778
 
770
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
779
+ #: (Hash[Symbol, untyped] message) -> void
771
780
  def text_document_references(message)
772
781
  params = message[:params]
773
782
  document = @store.get(params.dig(:textDocument, :uri))
@@ -785,15 +794,19 @@ module RubyLsp
785
794
  )
786
795
  end
787
796
 
788
- sig { params(document: Document[T.untyped]).returns(RubyDocument::SorbetLevel) }
797
+ #: (Document[untyped] document) -> SorbetLevel
789
798
  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)
799
+ return SorbetLevel.ignore unless document.is_a?(RubyDocument)
800
+ return SorbetLevel.ignore unless @global_state.has_type_checker
801
+
802
+ sigil = document.parse_result.magic_comments.find do |comment|
803
+ comment.key == "typed"
804
+ end&.value
792
805
 
793
- document.sorbet_level
806
+ SorbetLevel.new(sigil)
794
807
  end
795
808
 
796
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
809
+ #: (Hash[Symbol, untyped] message) -> void
797
810
  def text_document_inlay_hint(message)
798
811
  params = message[:params]
799
812
  document = @store.get(params.dig(:textDocument, :uri))
@@ -811,7 +824,6 @@ module RubyLsp
811
824
  return
812
825
  end
813
826
 
814
- hints_configurations = T.must(@store.features_configuration.dig(:inlayHint))
815
827
  dispatcher = Prism::Dispatcher.new
816
828
 
817
829
  unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
@@ -819,15 +831,15 @@ module RubyLsp
819
831
  return
820
832
  end
821
833
 
822
- request = Requests::InlayHints.new(document, hints_configurations, dispatcher)
823
- dispatcher.visit(document.parse_result.value)
834
+ request = Requests::InlayHints.new(@global_state, document, dispatcher)
835
+ dispatcher.visit(document.ast)
824
836
  result = request.perform
825
837
  document.cache_set("textDocument/inlayHint", result)
826
838
 
827
839
  send_message(Result.new(id: message[:id], response: result.select { |hint| range.cover?(hint.position[:line]) }))
828
840
  end
829
841
 
830
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
842
+ #: (Hash[Symbol, untyped] message) -> void
831
843
  def text_document_code_action(message)
832
844
  params = message[:params]
833
845
  document = @store.get(params.dig(:textDocument, :uri))
@@ -849,7 +861,7 @@ module RubyLsp
849
861
  )
850
862
  end
851
863
 
852
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
864
+ #: (Hash[Symbol, untyped] message) -> void
853
865
  def code_action_resolve(message)
854
866
  params = message[:params]
855
867
  uri = URI(params.dig(:data, :uri))
@@ -861,20 +873,12 @@ module RubyLsp
861
873
  end
862
874
 
863
875
  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
876
+ send_message(Result.new(id: message[:id], response: result))
877
+ rescue Requests::CodeActionResolve::CodeActionError => e
878
+ fail_request_and_notify(message[:id], e.message)
875
879
  end
876
880
 
877
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
881
+ #: (Hash[Symbol, untyped] message) -> void
878
882
  def text_document_diagnostic(message)
879
883
  # Do not compute diagnostics for files outside of the workspace. For example, if someone is looking at a gem's
880
884
  # source code, we don't want to show diagnostics for it
@@ -911,10 +915,14 @@ module RubyLsp
911
915
  "Error running diagnostics: #{error.message}",
912
916
  type: Constant::MessageType::ERROR,
913
917
  ))
918
+ send_message(Notification.window_log_message(
919
+ "Diagnostics failed with\r\n: #{error.full_message}",
920
+ type: Constant::MessageType::ERROR,
921
+ ))
914
922
  send_empty_response(message[:id])
915
923
  end
916
924
 
917
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
925
+ #: (Hash[Symbol, untyped] message) -> void
918
926
  def text_document_completion(message)
919
927
  params = message[:params]
920
928
  dispatcher = Prism::Dispatcher.new
@@ -939,7 +947,7 @@ module RubyLsp
939
947
  )
940
948
  end
941
949
 
942
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
950
+ #: (Hash[Symbol, untyped] message) -> void
943
951
  def text_document_completion_item_resolve(message)
944
952
  # When responding to a delegated completion request, it means we're handling a completion item that isn't related
945
953
  # to Ruby (probably related to an ERB host language like HTML). We need to return the original completion item
@@ -958,7 +966,7 @@ module RubyLsp
958
966
  ))
959
967
  end
960
968
 
961
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
969
+ #: (Hash[Symbol, untyped] message) -> void
962
970
  def text_document_signature_help(message)
963
971
  params = message[:params]
964
972
  dispatcher = Prism::Dispatcher.new
@@ -984,7 +992,7 @@ module RubyLsp
984
992
  )
985
993
  end
986
994
 
987
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
995
+ #: (Hash[Symbol, untyped] message) -> void
988
996
  def text_document_definition(message)
989
997
  params = message[:params]
990
998
  dispatcher = Prism::Dispatcher.new
@@ -1009,8 +1017,23 @@ module RubyLsp
1009
1017
  )
1010
1018
  end
1011
1019
 
1012
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1020
+ #: (Hash[Symbol, untyped] message) -> void
1013
1021
  def workspace_did_change_watched_files(message)
1022
+ # If indexing is not complete yet, delay processing did change watched file notifications. We need initial
1023
+ # indexing to be in place so that we can handle file changes appropriately without risking duplicates. We also
1024
+ # have to sleep before re-inserting the notification in the queue otherwise the worker can get stuck in its own
1025
+ # loop of pushing and popping the same notification
1026
+ unless @global_state.index.initial_indexing_completed
1027
+ Thread.new do
1028
+ sleep(2)
1029
+ # We have to ensure that the queue is not closed yet, since nothing stops the user from saving a file and then
1030
+ # immediately telling the LSP to shutdown
1031
+ @incoming_queue << message unless @incoming_queue.closed?
1032
+ end
1033
+
1034
+ return
1035
+ end
1036
+
1014
1037
  changes = message.dig(:params, :changes)
1015
1038
  # We allow add-ons to register for watching files and we have no restrictions for what they register for. If the
1016
1039
  # same pattern is registered more than once, the LSP will receive duplicate change notifications. Receiving them
@@ -1037,7 +1060,8 @@ module RubyLsp
1037
1060
  end
1038
1061
 
1039
1062
  Addon.file_watcher_addons.each do |addon|
1040
- T.unsafe(addon).workspace_did_change_watched_files(changes)
1063
+ addon #: as untyped
1064
+ .workspace_did_change_watched_files(changes)
1041
1065
  rescue => e
1042
1066
  send_log_message(
1043
1067
  "Error in #{addon.name} add-on while processing watched file notifications: #{e.full_message}",
@@ -1046,32 +1070,34 @@ module RubyLsp
1046
1070
  end
1047
1071
  end
1048
1072
 
1049
- sig { params(index: RubyIndexer::Index, file_path: String, change_type: Integer).void }
1073
+ #: (RubyIndexer::Index index, String file_path, Integer change_type) -> void
1050
1074
  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.
1075
+ @global_state.synchronize do
1076
+ load_path_entry = $LOAD_PATH.find { |load_path| file_path.start_with?(load_path) }
1077
+ uri = URI::Generic.from_path(load_path_entry: load_path_entry, path: file_path)
1078
+
1079
+ case change_type
1080
+ when Constant::FileChangeType::CREATED
1081
+ content = File.read(file_path)
1082
+ # If we receive a late created notification for a file that has already been claimed by the client, we want to
1083
+ # handle change for that URI so that the require path tree is updated
1084
+ @store.key?(uri) ? index.handle_change(uri, content) : index.index_single(uri, content)
1085
+ when Constant::FileChangeType::CHANGED
1086
+ content = File.read(file_path)
1087
+ # We only handle changes on file watched notifications if the client is not the one managing this URI.
1088
+ # Otherwise, these changes are handled when running the combined requests
1089
+ index.handle_change(uri, content) unless @store.key?(uri)
1090
+ when Constant::FileChangeType::DELETED
1091
+ index.delete(uri)
1092
+ end
1093
+ rescue Errno::ENOENT
1094
+ # If a file is created and then delete immediately afterwards, we will process the created notification before
1095
+ # we receive the deleted one, but the file no longer exists. This may happen when running a test suite that
1096
+ # creates and deletes files automatically.
1097
+ end
1072
1098
  end
1073
1099
 
1074
- sig { params(uri: URI::Generic).void }
1100
+ #: (URI::Generic uri) -> void
1075
1101
  def handle_rubocop_config_change(uri)
1076
1102
  return unless defined?(Requests::Support::RuboCopFormatter)
1077
1103
 
@@ -1081,7 +1107,7 @@ module RubyLsp
1081
1107
  # Clear all document caches for pull diagnostics
1082
1108
  @global_state.synchronize do
1083
1109
  @store.each do |_uri, document|
1084
- document.cache_set("textDocument/diagnostic", Document::EMPTY_CACHE)
1110
+ document.clear_cache("textDocument/diagnostic")
1085
1111
  end
1086
1112
  end
1087
1113
 
@@ -1091,7 +1117,7 @@ module RubyLsp
1091
1117
  end
1092
1118
  end
1093
1119
 
1094
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1120
+ #: (Hash[Symbol, untyped] message) -> void
1095
1121
  def workspace_symbol(message)
1096
1122
  send_message(
1097
1123
  Result.new(
@@ -1104,7 +1130,7 @@ module RubyLsp
1104
1130
  )
1105
1131
  end
1106
1132
 
1107
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1133
+ #: (Hash[Symbol, untyped] message) -> void
1108
1134
  def text_document_show_syntax_tree(message)
1109
1135
  params = message[:params]
1110
1136
  document = @store.get(params.dig(:textDocument, :uri))
@@ -1123,7 +1149,26 @@ module RubyLsp
1123
1149
  send_message(Result.new(id: message[:id], response: response))
1124
1150
  end
1125
1151
 
1126
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1152
+ #: (Hash[Symbol, untyped] message) -> void
1153
+ def experimental_go_to_relevant_file(message)
1154
+ path = message.dig(:params, :textDocument, :uri).to_standardized_path
1155
+ unless path.nil? || path.start_with?(@global_state.workspace_path)
1156
+ send_empty_response(message[:id])
1157
+ return
1158
+ end
1159
+
1160
+ unless path
1161
+ send_empty_response(message[:id])
1162
+ return
1163
+ end
1164
+
1165
+ response = {
1166
+ locations: Requests::GoToRelevantFile.new(path, @global_state.workspace_path).perform,
1167
+ }
1168
+ send_message(Result.new(id: message[:id], response: response))
1169
+ end
1170
+
1171
+ #: (Hash[Symbol, untyped] message) -> void
1127
1172
  def text_document_prepare_type_hierarchy(message)
1128
1173
  params = message[:params]
1129
1174
  document = @store.get(params.dig(:textDocument, :uri))
@@ -1142,7 +1187,7 @@ module RubyLsp
1142
1187
  send_message(Result.new(id: message[:id], response: response))
1143
1188
  end
1144
1189
 
1145
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1190
+ #: (Hash[Symbol, untyped] message) -> void
1146
1191
  def type_hierarchy_supertypes(message)
1147
1192
  response = Requests::TypeHierarchySupertypes.new(
1148
1193
  @global_state.index,
@@ -1151,14 +1196,14 @@ module RubyLsp
1151
1196
  send_message(Result.new(id: message[:id], response: response))
1152
1197
  end
1153
1198
 
1154
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1199
+ #: (Hash[Symbol, untyped] message) -> void
1155
1200
  def type_hierarchy_subtypes(message)
1156
1201
  # TODO: implement subtypes
1157
1202
  # The current index representation doesn't allow us to find the children of an entry.
1158
1203
  send_message(Result.new(id: message[:id], response: nil))
1159
1204
  end
1160
1205
 
1161
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1206
+ #: (Hash[Symbol, untyped] message) -> void
1162
1207
  def workspace_dependencies(message)
1163
1208
  unless @global_state.top_level_bundle
1164
1209
  send_message(Result.new(id: message[:id], response: []))
@@ -1186,12 +1231,13 @@ module RubyLsp
1186
1231
  send_message(Result.new(id: message[:id], response: response))
1187
1232
  end
1188
1233
 
1189
- sig { override.void }
1234
+ # @override
1235
+ #: -> void
1190
1236
  def shutdown
1191
1237
  Addon.unload_addons
1192
1238
  end
1193
1239
 
1194
- sig { void }
1240
+ #: -> void
1195
1241
  def perform_initial_indexing
1196
1242
  # The begin progress invocation happens during `initialize`, so that the notification is sent before we are
1197
1243
  # stuck indexing files
@@ -1217,13 +1263,29 @@ module RubyLsp
1217
1263
  # allocations and garbage collections are faster
1218
1264
  GC.compact unless @test_mode
1219
1265
 
1266
+ @global_state.synchronize do
1267
+ # If we linearize ancestors while the index is not fully populated, we may end up caching incorrect results
1268
+ # that were missing namespaces. After indexing is complete, we need to clear the ancestors cache and start
1269
+ # again
1270
+ @global_state.index.clear_ancestors
1271
+
1272
+ # The results for code lens depend on ancestor linearization, so we need to clear any previously computed
1273
+ # responses
1274
+ @store.each { |_uri, document| document.clear_cache("textDocument/codeLens") }
1275
+ end
1276
+
1220
1277
  # Always end the progress notification even if indexing failed or else it never goes away and the user has no
1221
1278
  # way of dismissing it
1222
1279
  end_progress("indexing-progress")
1280
+
1281
+ # Request a code lens refresh if we populated them before all test parent classes were indexed
1282
+ if @global_state.client_capabilities.supports_code_lens_refresh
1283
+ send_message(Request.new(id: @current_request_id, method: "workspace/codeLens/refresh", params: nil))
1284
+ end
1223
1285
  end
1224
1286
  end
1225
1287
 
1226
- sig { params(id: String, title: String, percentage: Integer).void }
1288
+ #: (String id, String title, ?percentage: Integer) -> void
1227
1289
  def begin_progress(id, title, percentage: 0)
1228
1290
  return unless @global_state.client_capabilities.supports_progress
1229
1291
 
@@ -1236,14 +1298,14 @@ module RubyLsp
1236
1298
  send_message(Notification.progress_begin(id, title, percentage: percentage, message: "#{percentage}% completed"))
1237
1299
  end
1238
1300
 
1239
- sig { params(id: String, percentage: Integer).void }
1301
+ #: (String id, Integer percentage) -> void
1240
1302
  def progress(id, percentage)
1241
1303
  return unless @global_state.client_capabilities.supports_progress
1242
1304
 
1243
1305
  send_message(Notification.progress_report(id, percentage: percentage, message: "#{percentage}% completed"))
1244
1306
  end
1245
1307
 
1246
- sig { params(id: String).void }
1308
+ #: (String id) -> void
1247
1309
  def end_progress(id)
1248
1310
  return unless @global_state.client_capabilities.supports_progress
1249
1311
 
@@ -1253,7 +1315,7 @@ module RubyLsp
1253
1315
  # notification
1254
1316
  end
1255
1317
 
1256
- sig { void }
1318
+ #: -> void
1257
1319
  def check_formatter_is_available
1258
1320
  return if @setup_error
1259
1321
  # Warn of an unavailable `formatter` setting, e.g. `rubocop_internal` on a project which doesn't have RuboCop.
@@ -1271,7 +1333,7 @@ module RubyLsp
1271
1333
  end
1272
1334
  end
1273
1335
 
1274
- sig { params(indexing_options: T.nilable(T::Hash[Symbol, T.untyped])).void }
1336
+ #: (Hash[Symbol, untyped]? indexing_options) -> void
1275
1337
  def process_indexing_configuration(indexing_options)
1276
1338
  # Need to use the workspace URI, otherwise, this will fail for people working on a project that is a symlink.
1277
1339
  index_path = File.join(@global_state.workspace_path, ".index.yml")
@@ -1312,7 +1374,7 @@ module RubyLsp
1312
1374
  configuration.apply_config(indexing_options.transform_keys { |key| key.to_s.gsub(/([A-Z])/, "_\\1").downcase })
1313
1375
  end
1314
1376
 
1315
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1377
+ #: (Hash[Symbol, untyped] message) -> void
1316
1378
  def window_show_message_request(message)
1317
1379
  result = message[:result]
1318
1380
  return unless result
@@ -1327,7 +1389,7 @@ module RubyLsp
1327
1389
  # NOTE: all servers methods are void because they can produce several messages for the client. The only reason this
1328
1390
  # method returns the created thread is to that we can join it in tests and avoid flakiness. The implementation is
1329
1391
  # not supposed to rely on the return of this method
1330
- sig { params(message: T::Hash[Symbol, T.untyped]).returns(T.nilable(Thread)) }
1392
+ #: (Hash[Symbol, untyped] message) -> Thread?
1331
1393
  def compose_bundle(message)
1332
1394
  already_composed_path = File.join(@global_state.workspace_path, ".ruby-lsp", "bundle_is_composed")
1333
1395
  id = message[:id]
@@ -1345,36 +1407,58 @@ module RubyLsp
1345
1407
 
1346
1408
  # We compose the bundle in a thread so that the LSP continues to work while we're checking for its validity. Once
1347
1409
  # we return the response back to the editor, then the restart is triggered
1410
+ launch_bundle_compose("Recomposing the bundle ahead of restart") do |stderr, status|
1411
+ if status&.exitstatus == 0
1412
+ # Create a signal for the restart that it can skip composing the bundle and launch directly
1413
+ FileUtils.touch(already_composed_path)
1414
+ send_message(Result.new(id: id, response: { success: true }))
1415
+ else
1416
+ # This special error code makes the extension avoid restarting in case we already know that the composed
1417
+ # bundle is not valid
1418
+ send_message(
1419
+ Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: "Failed to compose bundle\n#{stderr}"),
1420
+ )
1421
+ end
1422
+ end
1423
+ end
1424
+
1425
+ #: -> void
1426
+ def update_server
1427
+ return unless File.exist?(File.join(@global_state.workspace_path, ".ruby-lsp", "needs_update"))
1428
+
1429
+ launch_bundle_compose("Trying to update server") do |stderr, status|
1430
+ if status&.exitstatus == 0
1431
+ send_log_message("Successfully updated the server")
1432
+ else
1433
+ send_log_message("Failed to update server\n#{stderr}", type: Constant::MessageType::ERROR)
1434
+ end
1435
+ end
1436
+ end
1437
+
1438
+ #: (String) { (IO, Process::Status?) -> void } -> Thread
1439
+ def launch_bundle_compose(log, &block)
1348
1440
  Thread.new do
1349
- send_log_message("Recomposing the bundle ahead of restart")
1441
+ send_log_message(log)
1350
1442
 
1351
1443
  _stdout, stderr, status = Bundler.with_unbundled_env do
1352
1444
  Open3.capture3(
1353
1445
  Gem.ruby,
1354
1446
  "-I",
1355
- File.dirname(T.must(__dir__)),
1447
+ File.dirname(
1448
+ __dir__, #: as !nil
1449
+ ),
1356
1450
  File.expand_path("../../exe/ruby-lsp-launcher", __dir__),
1357
1451
  @global_state.workspace_uri.to_s,
1358
1452
  chdir: @global_state.workspace_path,
1359
1453
  )
1360
1454
  end
1361
1455
 
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
1456
+ block.call(stderr, status)
1373
1457
  end
1374
1458
  end
1375
1459
 
1376
1460
  # Returns internal state information for debugging purposes
1377
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1461
+ #: (Hash[Symbol, untyped] message) -> void
1378
1462
  def diagnose_state(message)
1379
1463
  documents = {}
1380
1464
  @store.each { |uri, document| documents[uri] = document.source }
@@ -1394,7 +1478,7 @@ module RubyLsp
1394
1478
 
1395
1479
  # Discovers all available test groups and examples in a given file taking into consideration the merged response of
1396
1480
  # all add-ons
1397
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1481
+ #: (Hash[Symbol, untyped] message) -> void
1398
1482
  def discover_tests(message)
1399
1483
  document = @store.get(message.dig(:params, :textDocument, :uri))
1400
1484
 
@@ -1414,5 +1498,44 @@ module RubyLsp
1414
1498
 
1415
1499
  send_message(Result.new(id: message[:id], response: items.map(&:to_hash)))
1416
1500
  end
1501
+
1502
+ #: (Hash[Symbol, untyped] message) -> void
1503
+ def resolve_test_commands(message)
1504
+ items = message.dig(:params, :items)
1505
+ commands = Listeners::TestStyle.resolve_test_commands(items)
1506
+
1507
+ Addon.addons.each do |addon|
1508
+ commands.concat(addon.resolve_test_commands(items))
1509
+ end
1510
+
1511
+ send_message(Result.new(
1512
+ id: message[:id],
1513
+ response: {
1514
+ commands: commands,
1515
+ reporterPaths: [Listeners::TestStyle::MINITEST_REPORTER_PATH, Listeners::TestStyle::TEST_UNIT_REPORTER_PATH],
1516
+ },
1517
+ ))
1518
+ end
1519
+
1520
+ #: (Hash[Symbol, untyped] message) -> void
1521
+ def code_lens_resolve(message)
1522
+ code_lens = message[:params]
1523
+ args = code_lens.dig(:data, :arguments)
1524
+
1525
+ case code_lens.dig(:data, :kind)
1526
+ when "run_test"
1527
+ code_lens[:command] = Interface::Command.new(title: "▶ Run", command: "rubyLsp.runTest", arguments: args)
1528
+ when "run_test_in_terminal"
1529
+ code_lens[:command] =
1530
+ Interface::Command.new(title: "▶ Run in terminal", command: "rubyLsp.runTestInTerminal", arguments: args)
1531
+ when "debug_test"
1532
+ code_lens[:command] = Interface::Command.new(title: "⚙ Debug", command: "rubyLsp.debugTest", arguments: args)
1533
+ end
1534
+
1535
+ send_message(Result.new(
1536
+ id: message[:id],
1537
+ response: code_lens,
1538
+ ))
1539
+ end
1417
1540
  end
1418
1541
  end