ruby-lsp 0.23.20 → 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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp +10 -4
- data/exe/ruby-lsp-check +0 -4
- data/exe/ruby-lsp-launcher +25 -11
- data/exe/ruby-lsp-test-exec +6 -0
- data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +0 -1
- data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +0 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +7 -1
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +1 -4
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +10 -19
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +29 -7
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +2 -2
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +12 -8
- data/lib/ruby_indexer/test/configuration_test.rb +1 -2
- data/lib/ruby_indexer/test/index_test.rb +39 -0
- data/lib/ruby_indexer/test/instance_variables_test.rb +24 -0
- data/lib/ruby_indexer/test/method_test.rb +17 -0
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +2 -2
- data/lib/ruby_indexer/test/reference_finder_test.rb +79 -14
- data/lib/ruby_lsp/addon.rb +44 -15
- data/lib/ruby_lsp/base_server.rb +34 -26
- data/lib/ruby_lsp/document.rb +162 -52
- data/lib/ruby_lsp/erb_document.rb +8 -3
- data/lib/ruby_lsp/global_state.rb +21 -0
- data/lib/ruby_lsp/internal.rb +0 -2
- data/lib/ruby_lsp/listeners/completion.rb +14 -3
- data/lib/ruby_lsp/listeners/hover.rb +7 -0
- data/lib/ruby_lsp/listeners/inlay_hints.rb +5 -3
- data/lib/ruby_lsp/listeners/spec_style.rb +126 -67
- data/lib/ruby_lsp/listeners/test_discovery.rb +18 -15
- data/lib/ruby_lsp/listeners/test_style.rb +56 -23
- data/lib/ruby_lsp/requests/code_action_resolve.rb +3 -3
- data/lib/ruby_lsp/requests/code_lens.rb +14 -5
- data/lib/ruby_lsp/requests/completion.rb +1 -1
- data/lib/ruby_lsp/requests/definition.rb +1 -1
- data/lib/ruby_lsp/requests/discover_tests.rb +2 -2
- data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
- data/lib/ruby_lsp/requests/hover.rb +1 -1
- data/lib/ruby_lsp/requests/inlay_hints.rb +3 -3
- data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
- data/lib/ruby_lsp/requests/prepare_rename.rb +1 -1
- data/lib/ruby_lsp/requests/references.rb +10 -6
- data/lib/ruby_lsp/requests/rename.rb +8 -6
- data/lib/ruby_lsp/requests/request.rb +6 -7
- data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +1 -1
- data/lib/ruby_lsp/requests/signature_help.rb +1 -1
- data/lib/ruby_lsp/requests/support/common.rb +1 -3
- data/lib/ruby_lsp/requests/support/formatter.rb +16 -15
- data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +2 -2
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +13 -3
- data/lib/ruby_lsp/response_builders/response_builder.rb +6 -8
- data/lib/ruby_lsp/ruby_document.rb +10 -5
- data/lib/ruby_lsp/server.rb +95 -110
- data/lib/ruby_lsp/setup_bundler.rb +59 -25
- data/lib/ruby_lsp/static_docs.rb +1 -0
- data/lib/ruby_lsp/store.rb +0 -10
- data/lib/ruby_lsp/test_helper.rb +1 -4
- data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +18 -7
- data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +54 -7
- data/lib/ruby_lsp/test_reporters/test_unit_reporter.rb +0 -1
- data/lib/ruby_lsp/utils.rb +47 -11
- data/static_docs/break.md +103 -0
- metadata +7 -19
- data/lib/ruby_lsp/load_sorbet.rb +0 -62
data/lib/ruby_lsp/server.rb
CHANGED
@@ -94,13 +94,10 @@ module RubyLsp
|
|
94
94
|
id: message[:id],
|
95
95
|
response:
|
96
96
|
Addon.addons.map do |addon|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
# Therefore, we only call the method if it's defined by the add-on itself
|
102
|
-
if version_method.owner != Addon
|
103
|
-
version = addon.version
|
97
|
+
version = begin
|
98
|
+
addon.version
|
99
|
+
rescue AbstractMethodInvokedError
|
100
|
+
nil
|
104
101
|
end
|
105
102
|
|
106
103
|
{ name: addon.name, version: version, errored: addon.error? }
|
@@ -124,30 +121,22 @@ module RubyLsp
|
|
124
121
|
end
|
125
122
|
rescue DelegateRequestError
|
126
123
|
send_message(Error.new(id: message[:id], code: DelegateRequestError::CODE, message: "DELEGATE_REQUEST"))
|
127
|
-
rescue StandardError, LoadError => e
|
124
|
+
rescue StandardError, LoadError, SystemExit => e
|
128
125
|
# If an error occurred in a request, we have to return an error response or else the editor will hang
|
129
126
|
if message[:id]
|
130
127
|
# If a document is deleted before we are able to process all of its enqueued requests, we will try to read it
|
131
128
|
# from disk and it raise this error. This is expected, so we don't include the `data` attribute to avoid
|
132
|
-
# 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
|
133
133
|
case e
|
134
|
-
when Store::NonExistingDocumentError
|
134
|
+
when Store::NonExistingDocumentError, Document::InvalidLocationError
|
135
135
|
send_message(Error.new(
|
136
136
|
id: message[:id],
|
137
137
|
code: Constant::ErrorCodes::INVALID_PARAMS,
|
138
138
|
message: e.full_message,
|
139
139
|
))
|
140
|
-
when Document::LocationNotFoundError
|
141
|
-
send_message(Error.new(
|
142
|
-
id: message[:id],
|
143
|
-
code: Constant::ErrorCodes::REQUEST_FAILED,
|
144
|
-
message: <<~MESSAGE,
|
145
|
-
Request #{message[:method]} failed to find the target position.
|
146
|
-
The file might have been modified while the server was in the middle of searching for the target.
|
147
|
-
If you experience this regularly, please report any findings and extra information on
|
148
|
-
https://github.com/Shopify/ruby-lsp/issues/2446
|
149
|
-
MESSAGE
|
150
|
-
))
|
151
140
|
else
|
152
141
|
send_message(Error.new(
|
153
142
|
id: message[:id],
|
@@ -182,6 +171,7 @@ module RubyLsp
|
|
182
171
|
return if @setup_error
|
183
172
|
|
184
173
|
errors = Addon.load_addons(@global_state, @outgoing_queue, include_project_addons: include_project_addons)
|
174
|
+
return if test_mode?
|
185
175
|
|
186
176
|
if errors.any?
|
187
177
|
send_log_message(
|
@@ -194,21 +184,13 @@ module RubyLsp
|
|
194
184
|
|
195
185
|
if errored_addons.any?
|
196
186
|
send_message(
|
197
|
-
Notification.
|
198
|
-
|
199
|
-
|
200
|
-
type: Constant::MessageType::WARNING,
|
201
|
-
message: "Error loading add-ons:\n\n#{errored_addons.map(&:formatted_errors).join("\n\n")}",
|
202
|
-
),
|
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,
|
203
190
|
),
|
204
191
|
)
|
205
192
|
|
206
|
-
|
207
|
-
send_log_message(
|
208
|
-
errored_addons.map(&:errors_details).join("\n\n"),
|
209
|
-
type: Constant::MessageType::WARNING,
|
210
|
-
)
|
211
|
-
end
|
193
|
+
send_log_message(errored_addons.map(&:errors_details).join("\n\n"), type: Constant::MessageType::WARNING)
|
212
194
|
end
|
213
195
|
end
|
214
196
|
|
@@ -224,10 +206,6 @@ module RubyLsp
|
|
224
206
|
|
225
207
|
configured_features = options.dig(:initializationOptions, :enabledFeatures)
|
226
208
|
|
227
|
-
configured_hints = options.dig(:initializationOptions, :featuresConfiguration, :inlayHint)
|
228
|
-
@store.features_configuration.dig(:inlayHint) #: as !nil
|
229
|
-
.configuration.merge!(configured_hints) if configured_hints
|
230
|
-
|
231
209
|
enabled_features = case configured_features
|
232
210
|
when Array
|
233
211
|
# If the configuration is using an array, then absent features are disabled and present ones are enabled. That's
|
@@ -381,56 +359,53 @@ module RubyLsp
|
|
381
359
|
|
382
360
|
perform_initial_indexing
|
383
361
|
check_formatter_is_available
|
362
|
+
update_server if @global_state.enabled_feature?(:launcher)
|
384
363
|
end
|
385
364
|
|
386
365
|
#: (Hash[Symbol, untyped] message) -> void
|
387
366
|
def text_document_did_open(message)
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
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
|
398
376
|
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
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
|
+
)
|
405
383
|
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
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
|
411
389
|
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
),
|
390
|
+
send_message(
|
391
|
+
Notification.new(
|
392
|
+
method: "window/logMessage",
|
393
|
+
params: Interface::LogMessageParams.new(
|
394
|
+
type: Constant::MessageType::WARNING,
|
395
|
+
message: log_message,
|
419
396
|
),
|
420
|
-
)
|
421
|
-
|
397
|
+
),
|
398
|
+
)
|
422
399
|
end
|
423
400
|
end
|
424
401
|
|
425
402
|
#: (Hash[Symbol, untyped] message) -> void
|
426
403
|
def text_document_did_close(message)
|
427
|
-
|
428
|
-
|
429
|
-
@store.delete(uri)
|
404
|
+
uri = message.dig(:params, :textDocument, :uri)
|
405
|
+
@store.delete(uri)
|
430
406
|
|
431
|
-
|
432
|
-
|
433
|
-
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, []))
|
434
409
|
end
|
435
410
|
|
436
411
|
#: (Hash[Symbol, untyped] message) -> void
|
@@ -438,9 +413,7 @@ module RubyLsp
|
|
438
413
|
params = message[:params]
|
439
414
|
text_document = params[:textDocument]
|
440
415
|
|
441
|
-
@
|
442
|
-
@store.push_edits(uri: text_document[:uri], edits: params[:contentChanges], version: text_document[:version])
|
443
|
-
end
|
416
|
+
@store.push_edits(uri: text_document[:uri], edits: params[:contentChanges], version: text_document[:version])
|
444
417
|
end
|
445
418
|
|
446
419
|
#: (Hash[Symbol, untyped] message) -> void
|
@@ -494,8 +467,8 @@ module RubyLsp
|
|
494
467
|
document_symbol = Requests::DocumentSymbol.new(uri, dispatcher)
|
495
468
|
document_link = Requests::DocumentLink.new(uri, parse_result.comments, dispatcher)
|
496
469
|
inlay_hint = Requests::InlayHints.new(
|
470
|
+
@global_state,
|
497
471
|
document,
|
498
|
-
@store.features_configuration.dig(:inlayHint), #: as !nil
|
499
472
|
dispatcher,
|
500
473
|
)
|
501
474
|
|
@@ -512,13 +485,13 @@ module RubyLsp
|
|
512
485
|
@global_state.index.handle_change(uri) do |index|
|
513
486
|
index.delete(uri, skip_require_paths_tree: true)
|
514
487
|
RubyIndexer::DeclarationListener.new(index, dispatcher, parse_result, uri, collect_comments: true)
|
515
|
-
code_lens = Requests::CodeLens.new(@global_state,
|
516
|
-
dispatcher.dispatch(
|
488
|
+
code_lens = Requests::CodeLens.new(@global_state, document, dispatcher)
|
489
|
+
dispatcher.dispatch(document.ast)
|
517
490
|
end
|
518
491
|
end
|
519
492
|
else
|
520
|
-
code_lens = Requests::CodeLens.new(@global_state,
|
521
|
-
dispatcher.dispatch(
|
493
|
+
code_lens = Requests::CodeLens.new(@global_state, document, dispatcher)
|
494
|
+
dispatcher.dispatch(document.ast)
|
522
495
|
end
|
523
496
|
|
524
497
|
# Store all responses retrieve in this round of visits in the cache and then return the response for the request
|
@@ -557,7 +530,7 @@ module RubyLsp
|
|
557
530
|
|
558
531
|
dispatcher = Prism::Dispatcher.new
|
559
532
|
semantic_highlighting = Requests::SemanticHighlighting.new(@global_state, dispatcher, document, nil)
|
560
|
-
dispatcher.visit(document.
|
533
|
+
dispatcher.visit(document.ast)
|
561
534
|
|
562
535
|
send_message(Result.new(id: message[:id], response: semantic_highlighting.perform))
|
563
536
|
end
|
@@ -583,7 +556,7 @@ module RubyLsp
|
|
583
556
|
document,
|
584
557
|
message.dig(:params, :previousResultId),
|
585
558
|
)
|
586
|
-
dispatcher.visit(document.
|
559
|
+
dispatcher.visit(document.ast)
|
587
560
|
send_message(Result.new(id: message[:id], response: request.perform))
|
588
561
|
end
|
589
562
|
|
@@ -612,7 +585,7 @@ module RubyLsp
|
|
612
585
|
nil,
|
613
586
|
range: range.dig(:start, :line)..range.dig(:end, :line),
|
614
587
|
)
|
615
|
-
dispatcher.visit(document.
|
588
|
+
dispatcher.visit(document.ast)
|
616
589
|
send_message(Result.new(id: message[:id], response: request.perform))
|
617
590
|
end
|
618
591
|
|
@@ -704,7 +677,7 @@ module RubyLsp
|
|
704
677
|
end
|
705
678
|
|
706
679
|
request = Requests::DocumentHighlight.new(@global_state, document, params[:position], dispatcher)
|
707
|
-
dispatcher.dispatch(document.
|
680
|
+
dispatcher.dispatch(document.ast)
|
708
681
|
send_message(Result.new(id: message[:id], response: request.perform))
|
709
682
|
end
|
710
683
|
|
@@ -842,7 +815,6 @@ module RubyLsp
|
|
842
815
|
return
|
843
816
|
end
|
844
817
|
|
845
|
-
hints_configurations = @store.features_configuration.dig(:inlayHint) #: as !nil
|
846
818
|
dispatcher = Prism::Dispatcher.new
|
847
819
|
|
848
820
|
unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
|
@@ -850,8 +822,8 @@ module RubyLsp
|
|
850
822
|
return
|
851
823
|
end
|
852
824
|
|
853
|
-
request = Requests::InlayHints.new(
|
854
|
-
dispatcher.visit(document.
|
825
|
+
request = Requests::InlayHints.new(@global_state, document, dispatcher)
|
826
|
+
dispatcher.visit(document.ast)
|
855
827
|
result = request.perform
|
856
828
|
document.cache_set("textDocument/inlayHint", result)
|
857
829
|
|
@@ -1124,11 +1096,7 @@ module RubyLsp
|
|
1124
1096
|
@global_state.register_formatter("rubocop_internal", Requests::Support::RuboCopFormatter.new)
|
1125
1097
|
|
1126
1098
|
# Clear all document caches for pull diagnostics
|
1127
|
-
@
|
1128
|
-
@store.each do |_uri, document|
|
1129
|
-
document.clear_cache("textDocument/diagnostic")
|
1130
|
-
end
|
1131
|
-
end
|
1099
|
+
@store.each { |_uri, document| document.clear_cache("textDocument/diagnostic") }
|
1132
1100
|
|
1133
1101
|
# Request a pull diagnostic refresh from the editor
|
1134
1102
|
if @global_state.client_capabilities.supports_diagnostic_refresh
|
@@ -1243,7 +1211,7 @@ module RubyLsp
|
|
1243
1211
|
}
|
1244
1212
|
end
|
1245
1213
|
end
|
1246
|
-
rescue Bundler::GemNotFound
|
1214
|
+
rescue Bundler::GemNotFound, Bundler::GemfileNotFound
|
1247
1215
|
[]
|
1248
1216
|
end
|
1249
1217
|
|
@@ -1426,8 +1394,38 @@ module RubyLsp
|
|
1426
1394
|
|
1427
1395
|
# We compose the bundle in a thread so that the LSP continues to work while we're checking for its validity. Once
|
1428
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)
|
1429
1427
|
Thread.new do
|
1430
|
-
send_log_message(
|
1428
|
+
send_log_message(log)
|
1431
1429
|
|
1432
1430
|
_stdout, stderr, status = Bundler.with_unbundled_env do
|
1433
1431
|
Open3.capture3(
|
@@ -1442,17 +1440,7 @@ module RubyLsp
|
|
1442
1440
|
)
|
1443
1441
|
end
|
1444
1442
|
|
1445
|
-
|
1446
|
-
# Create a signal for the restart that it can skip composing the bundle and launch directly
|
1447
|
-
FileUtils.touch(already_composed_path)
|
1448
|
-
send_message(Result.new(id: id, response: { success: true }))
|
1449
|
-
else
|
1450
|
-
# This special error code makes the extension avoid restarting in case we already know that the composed
|
1451
|
-
# bundle is not valid
|
1452
|
-
send_message(
|
1453
|
-
Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: "Failed to compose bundle\n#{stderr}"),
|
1454
|
-
)
|
1455
|
-
end
|
1443
|
+
block.call(stderr, status)
|
1456
1444
|
end
|
1457
1445
|
end
|
1458
1446
|
|
@@ -1509,10 +1497,7 @@ module RubyLsp
|
|
1509
1497
|
|
1510
1498
|
send_message(Result.new(
|
1511
1499
|
id: message[:id],
|
1512
|
-
response: {
|
1513
|
-
commands: commands,
|
1514
|
-
reporterPaths: [Listeners::TestStyle::MINITEST_REPORTER_PATH, Listeners::TestStyle::TEST_UNIT_REPORTER_PATH],
|
1515
|
-
},
|
1500
|
+
response: { commands: commands },
|
1516
1501
|
))
|
1517
1502
|
end
|
1518
1503
|
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "sorbet-runtime"
|
5
4
|
require "bundler"
|
6
5
|
require "bundler/cli"
|
7
6
|
require "bundler/cli/install"
|
@@ -12,19 +11,24 @@ require "digest"
|
|
12
11
|
require "time"
|
13
12
|
require "uri"
|
14
13
|
|
15
|
-
# This file is a script that will configure a composed bundle for the Ruby LSP. The composed bundle allows developers to
|
16
|
-
# the Ruby LSP without including the gem in their application's Gemfile while at the same time giving us access to
|
17
|
-
# exact locked versions of dependencies.
|
14
|
+
# This file is a script that will configure a composed bundle for the Ruby LSP. The composed bundle allows developers to
|
15
|
+
# use the Ruby LSP without including the gem in their application's Gemfile while at the same time giving us access to
|
16
|
+
# the exact locked versions of dependencies.
|
18
17
|
|
19
18
|
Bundler.ui.level = :silent
|
20
19
|
|
21
20
|
module RubyLsp
|
22
21
|
class SetupBundler
|
23
|
-
extend T::Sig
|
24
|
-
|
25
22
|
class BundleNotLocked < StandardError; end
|
26
23
|
class BundleInstallFailure < StandardError; end
|
27
24
|
|
25
|
+
module ThorPatch
|
26
|
+
#: -> IO
|
27
|
+
def stdout
|
28
|
+
$stderr
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
28
32
|
FOUR_HOURS = 4 * 60 * 60 #: Integer
|
29
33
|
|
30
34
|
#: (String project_path, **untyped options) -> void
|
@@ -61,6 +65,7 @@ module RubyLsp
|
|
61
65
|
@bundler_version = bundler_version #: Gem::Version?
|
62
66
|
@rails_app = rails_app? #: bool
|
63
67
|
@retry = false #: bool
|
68
|
+
@needs_update_path = @custom_dir + "needs_update" #: Pathname
|
64
69
|
end
|
65
70
|
|
66
71
|
# Sets up the composed bundle and returns the `BUNDLE_GEMFILE`, `BUNDLE_PATH` and `BUNDLE_APP_CONFIG` that should be
|
@@ -229,6 +234,14 @@ module RubyLsp
|
|
229
234
|
# If no error occurred, then clear previous errors
|
230
235
|
@error_path.delete if @error_path.exist?
|
231
236
|
$stderr.puts("Ruby LSP> Composed bundle installation complete")
|
237
|
+
rescue Errno::EPIPE
|
238
|
+
# If the $stderr pipe was closed by the client, for example when closing the editor during running bundle
|
239
|
+
# install, we don't want to write the error to a file or else we will report to telemetry on the next launch and
|
240
|
+
# it does not represent an actual error.
|
241
|
+
#
|
242
|
+
# This situation may happen because while running bundle install, the server is not yet ready to receive
|
243
|
+
# shutdown requests and we may continue doing work until the process is killed.
|
244
|
+
@error_path.delete if @error_path.exist?
|
232
245
|
rescue => e
|
233
246
|
# Write the error object to a file so that we can read it from the parent process
|
234
247
|
@error_path.write(Marshal.dump(e))
|
@@ -256,19 +269,50 @@ module RubyLsp
|
|
256
269
|
#: (Hash[String, String] env, ?force_install: bool) -> Hash[String, String]
|
257
270
|
def run_bundle_install_directly(env, force_install: false)
|
258
271
|
RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
|
272
|
+
return update(env) if @needs_update_path.exist?
|
259
273
|
|
260
274
|
# The ENV can only be merged after checking if an update is required because we depend on the original value of
|
261
275
|
# ENV["BUNDLE_GEMFILE"], which gets overridden after the merge
|
262
|
-
|
263
|
-
ENV
|
264
|
-
.merge!(env)
|
276
|
+
FileUtils.touch(@needs_update_path) if should_bundle_update?
|
277
|
+
ENV.merge!(env)
|
265
278
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
279
|
+
$stderr.puts("Ruby LSP> Checking if the composed bundle is satisfied...")
|
280
|
+
missing_gems = bundle_check
|
281
|
+
|
282
|
+
unless missing_gems.empty?
|
283
|
+
$stderr.puts(<<~MESSAGE)
|
284
|
+
Ruby LSP> Running bundle install because the following gems are not installed:
|
285
|
+
#{missing_gems.map { |g| "#{g.name}: #{g.version}" }.join("\n")}
|
286
|
+
MESSAGE
|
287
|
+
|
288
|
+
bundle_install
|
270
289
|
end
|
271
290
|
|
291
|
+
$stderr.puts("Ruby LSP> Bundle already satisfied")
|
292
|
+
env
|
293
|
+
rescue => e
|
294
|
+
$stderr.puts("Ruby LSP> Running bundle install because #{e.message}")
|
295
|
+
bundle_install
|
296
|
+
env
|
297
|
+
end
|
298
|
+
|
299
|
+
# Essentially the same as bundle check, but simplified
|
300
|
+
#: -> Array[Gem::Specification]
|
301
|
+
def bundle_check
|
302
|
+
definition = Bundler.definition
|
303
|
+
definition.validate_runtime!
|
304
|
+
definition.check!
|
305
|
+
definition.missing_specs
|
306
|
+
end
|
307
|
+
|
308
|
+
#: -> void
|
309
|
+
def bundle_install
|
310
|
+
Bundler::CLI::Install.new({ "no-cache" => true }).run
|
311
|
+
correct_relative_remote_paths if @custom_lockfile.exist?
|
312
|
+
end
|
313
|
+
|
314
|
+
#: (Hash[String, String]) -> Hash[String, String]
|
315
|
+
def update(env)
|
272
316
|
# Try to auto upgrade the gems we depend on, unless they are in the Gemfile as that would result in undesired
|
273
317
|
# source control changes
|
274
318
|
gems = ["ruby-lsp", "debug", "prism"].reject { |dep| @dependencies[dep] }
|
@@ -276,11 +320,9 @@ module RubyLsp
|
|
276
320
|
|
277
321
|
Bundler::CLI::Update.new({ conservative: true }, gems).run
|
278
322
|
correct_relative_remote_paths if @custom_lockfile.exist?
|
323
|
+
@needs_update_path.delete
|
279
324
|
@last_updated_path.write(Time.now.iso8601)
|
280
325
|
env
|
281
|
-
rescue Bundler::GemNotFound, Bundler::GitError
|
282
|
-
# If a gem is not installed, skip the upgrade and try to install it with a single retry
|
283
|
-
@retry ? env : run_bundle_install_directly(env, force_install: true)
|
284
326
|
end
|
285
327
|
|
286
328
|
#: (Hash[String, String] env) -> Hash[String, String]
|
@@ -440,15 +482,7 @@ module RubyLsp
|
|
440
482
|
def patch_thor_to_print_progress_to_stderr!
|
441
483
|
return unless defined?(Bundler::Thor::Shell::Basic)
|
442
484
|
|
443
|
-
Bundler::Thor::Shell::Basic.prepend(
|
444
|
-
extend T::Sig
|
445
|
-
|
446
|
-
sig { returns(IO) }
|
447
|
-
def stdout
|
448
|
-
$stderr
|
449
|
-
end
|
450
|
-
end)
|
451
|
-
|
485
|
+
Bundler::Thor::Shell::Basic.prepend(ThorPatch)
|
452
486
|
Bundler.ui.level = :info
|
453
487
|
end
|
454
488
|
end
|
data/lib/ruby_lsp/static_docs.rb
CHANGED
@@ -14,6 +14,7 @@ module RubyLsp
|
|
14
14
|
|
15
15
|
# A map of keyword => short documentation to be displayed on hover or completion
|
16
16
|
KEYWORD_DOCS = {
|
17
|
+
"break" => "Terminates the execution of a block or loop",
|
17
18
|
"yield" => "Invokes the passed block with the given arguments",
|
18
19
|
}.freeze #: Hash[String, String]
|
19
20
|
end
|
data/lib/ruby_lsp/store.rb
CHANGED
@@ -5,9 +5,6 @@ module RubyLsp
|
|
5
5
|
class Store
|
6
6
|
class NonExistingDocumentError < StandardError; end
|
7
7
|
|
8
|
-
#: Hash[Symbol, RequestConfig]
|
9
|
-
attr_accessor :features_configuration
|
10
|
-
|
11
8
|
#: String
|
12
9
|
attr_accessor :client_name
|
13
10
|
|
@@ -15,13 +12,6 @@ module RubyLsp
|
|
15
12
|
def initialize(global_state)
|
16
13
|
@global_state = global_state
|
17
14
|
@state = {} #: Hash[String, Document[untyped]]
|
18
|
-
@features_configuration = {
|
19
|
-
inlayHint: RequestConfig.new({
|
20
|
-
enableAll: false,
|
21
|
-
implicitRescue: false,
|
22
|
-
implicitHashValue: false,
|
23
|
-
}),
|
24
|
-
} #: Hash[Symbol, RequestConfig]
|
25
15
|
@client_name = "Unknown" #: String
|
26
16
|
end
|
27
17
|
|
data/lib/ruby_lsp/test_helper.rb
CHANGED
@@ -4,13 +4,10 @@
|
|
4
4
|
# NOTE: This module is intended to be used by addons for writing their own tests, so keep that in mind if changing.
|
5
5
|
|
6
6
|
module RubyLsp
|
7
|
+
# @requires_ancestor: Kernel
|
7
8
|
module TestHelper
|
8
9
|
class TestError < StandardError; end
|
9
10
|
|
10
|
-
extend T::Helpers
|
11
|
-
|
12
|
-
requires_ancestor { Kernel }
|
13
|
-
|
14
11
|
#: [T] (?String? source, ?URI::Generic uri, ?stub_no_typechecker: bool, ?load_addons: bool) { (RubyLsp::Server server, URI::Generic uri) -> T } -> T
|
15
12
|
def with_server(source = nil, uri = Kernel.URI("file:///fake.rb"), stub_no_typechecker: false, load_addons: true,
|
16
13
|
&block)
|
@@ -6,28 +6,39 @@ require "json"
|
|
6
6
|
require "socket"
|
7
7
|
require "singleton"
|
8
8
|
require "tmpdir"
|
9
|
+
require_relative "../../ruby_indexer/lib/ruby_indexer/uri"
|
9
10
|
|
10
11
|
module RubyLsp
|
11
12
|
class LspReporter
|
12
13
|
include Singleton
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
# https://code.visualstudio.com/api/references/vscode-api#Position
|
16
|
+
#: type position = { line: Integer, character: Integer }
|
17
|
+
|
18
|
+
# https://code.visualstudio.com/api/references/vscode-api#Range
|
19
|
+
#: type range = { start: position, end: position }
|
20
|
+
|
21
|
+
# https://code.visualstudio.com/api/references/vscode-api#BranchCoverage
|
22
|
+
#: type branch_coverage = { executed: Integer, label: String, location: range }
|
23
|
+
|
24
|
+
# https://code.visualstudio.com/api/references/vscode-api#StatementCoverage
|
25
|
+
#: type statement_coverage = { executed: Integer, location: position, branches: Array[branch_coverage] }
|
16
26
|
|
17
27
|
#: -> void
|
18
28
|
def initialize
|
19
29
|
dir_path = File.join(Dir.tmpdir, "ruby-lsp")
|
20
30
|
FileUtils.mkdir_p(dir_path)
|
21
31
|
|
22
|
-
|
32
|
+
port_db_path = File.join(dir_path, "test_reporter_port_db.json")
|
23
33
|
port = ENV["RUBY_LSP_REPORTER_PORT"]
|
24
34
|
|
25
35
|
@io = begin
|
26
36
|
# The environment variable is only used for tests. The extension always writes to the temporary file
|
27
37
|
if port
|
28
38
|
TCPSocket.new("localhost", port)
|
29
|
-
elsif File.exist?(
|
30
|
-
|
39
|
+
elsif File.exist?(port_db_path)
|
40
|
+
db = JSON.load_file(port_db_path)
|
41
|
+
TCPSocket.new("localhost", db[Dir.pwd])
|
31
42
|
else
|
32
43
|
# For tests that don't spawn the TCP server
|
33
44
|
require "stringio"
|
@@ -123,7 +134,7 @@ module RubyLsp
|
|
123
134
|
# ["Foo", :bar, 6, 21, 6, 65] => 0
|
124
135
|
# }
|
125
136
|
# }
|
126
|
-
#: -> Hash[String,
|
137
|
+
#: -> Hash[String, statement_coverage]
|
127
138
|
def gather_coverage_results
|
128
139
|
# Ignore coverage results inside dependencies
|
129
140
|
bundle_path = Bundler.bundle_path.to_s
|
@@ -181,7 +192,7 @@ module RubyLsp
|
|
181
192
|
|
182
193
|
#: -> void
|
183
194
|
def at_exit
|
184
|
-
internal_shutdown unless invoked_shutdown
|
195
|
+
internal_shutdown unless @invoked_shutdown
|
185
196
|
end
|
186
197
|
|
187
198
|
class << self
|