ruby-lsp 0.22.1 → 0.23.10
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/README.md +2 -2
- data/VERSION +1 -1
- data/exe/ruby-lsp +12 -11
- data/exe/ruby-lsp-check +5 -5
- data/exe/ruby-lsp-launcher +41 -15
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +26 -20
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +191 -100
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +60 -30
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +174 -61
- data/lib/ruby_indexer/lib/ruby_indexer/location.rb +12 -0
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +16 -14
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +82 -61
- data/lib/{core_ext → ruby_indexer/lib/ruby_indexer}/uri.rb +29 -3
- data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +36 -0
- data/lib/ruby_indexer/ruby_indexer.rb +2 -1
- data/lib/ruby_indexer/test/class_variables_test.rb +140 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +30 -6
- data/lib/ruby_indexer/test/configuration_test.rb +116 -51
- data/lib/ruby_indexer/test/enhancements_test.rb +2 -2
- data/lib/ruby_indexer/test/index_test.rb +143 -44
- data/lib/ruby_indexer/test/instance_variables_test.rb +20 -0
- data/lib/ruby_indexer/test/method_test.rb +86 -8
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
- data/lib/ruby_indexer/test/reference_finder_test.rb +90 -2
- data/lib/ruby_indexer/test/test_case.rb +2 -2
- data/lib/ruby_indexer/test/uri_test.rb +72 -0
- data/lib/ruby_lsp/addon.rb +9 -0
- data/lib/ruby_lsp/base_server.rb +17 -18
- data/lib/ruby_lsp/client_capabilities.rb +7 -1
- data/lib/ruby_lsp/document.rb +72 -10
- data/lib/ruby_lsp/erb_document.rb +5 -3
- data/lib/ruby_lsp/global_state.rb +42 -3
- data/lib/ruby_lsp/internal.rb +3 -1
- data/lib/ruby_lsp/listeners/code_lens.rb +9 -5
- data/lib/ruby_lsp/listeners/completion.rb +78 -6
- data/lib/ruby_lsp/listeners/definition.rb +80 -19
- data/lib/ruby_lsp/listeners/document_highlight.rb +3 -2
- data/lib/ruby_lsp/listeners/document_link.rb +21 -3
- data/lib/ruby_lsp/listeners/document_symbol.rb +12 -1
- data/lib/ruby_lsp/listeners/folding_ranges.rb +1 -1
- data/lib/ruby_lsp/listeners/hover.rb +59 -2
- data/lib/ruby_lsp/load_sorbet.rb +3 -3
- data/lib/ruby_lsp/rbs_document.rb +2 -2
- data/lib/ruby_lsp/requests/code_action_resolve.rb +90 -6
- data/lib/ruby_lsp/requests/code_actions.rb +57 -1
- data/lib/ruby_lsp/requests/completion.rb +8 -1
- data/lib/ruby_lsp/requests/completion_resolve.rb +2 -1
- data/lib/ruby_lsp/requests/definition.rb +7 -1
- data/lib/ruby_lsp/requests/diagnostics.rb +1 -1
- data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
- data/lib/ruby_lsp/requests/folding_ranges.rb +2 -6
- data/lib/ruby_lsp/requests/formatting.rb +2 -6
- data/lib/ruby_lsp/requests/hover.rb +1 -1
- data/lib/ruby_lsp/requests/on_type_formatting.rb +2 -2
- data/lib/ruby_lsp/requests/prepare_rename.rb +51 -0
- data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +1 -1
- data/lib/ruby_lsp/requests/references.rb +29 -2
- data/lib/ruby_lsp/requests/rename.rb +17 -7
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -1
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +1 -4
- data/lib/ruby_lsp/requests/signature_help.rb +1 -1
- data/lib/ruby_lsp/requests/support/common.rb +2 -9
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +3 -3
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +13 -13
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +1 -1
- data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -3
- data/lib/ruby_lsp/ruby_document.rb +80 -6
- data/lib/ruby_lsp/scripts/compose_bundle.rb +1 -1
- data/lib/ruby_lsp/server.rb +205 -61
- data/lib/ruby_lsp/setup_bundler.rb +50 -43
- data/lib/ruby_lsp/store.rb +7 -7
- data/lib/ruby_lsp/test_helper.rb +45 -11
- data/lib/ruby_lsp/type_inferrer.rb +60 -31
- data/lib/ruby_lsp/utils.rb +63 -3
- metadata +8 -8
- data/lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb +0 -29
@@ -8,6 +8,22 @@ module RubyLsp
|
|
8
8
|
|
9
9
|
ParseResultType = type_member { { fixed: Prism::ParseResult } }
|
10
10
|
|
11
|
+
METHODS_THAT_CHANGE_DECLARATIONS = [
|
12
|
+
:private_constant,
|
13
|
+
:attr_reader,
|
14
|
+
:attr_writer,
|
15
|
+
:attr_accessor,
|
16
|
+
:alias_method,
|
17
|
+
:include,
|
18
|
+
:prepend,
|
19
|
+
:extend,
|
20
|
+
:public,
|
21
|
+
:protected,
|
22
|
+
:private,
|
23
|
+
:module_function,
|
24
|
+
:private_class_method,
|
25
|
+
].freeze
|
26
|
+
|
11
27
|
class SorbetLevel < T::Enum
|
12
28
|
enums do
|
13
29
|
None = new("none")
|
@@ -142,8 +158,8 @@ module RubyLsp
|
|
142
158
|
end
|
143
159
|
attr_reader :code_units_cache
|
144
160
|
|
145
|
-
sig { params(source: String, version: Integer, uri: URI::Generic,
|
146
|
-
def initialize(source:, version:, uri:,
|
161
|
+
sig { params(source: String, version: Integer, uri: URI::Generic, global_state: GlobalState).void }
|
162
|
+
def initialize(source:, version:, uri:, global_state:)
|
147
163
|
super
|
148
164
|
@code_units_cache = T.let(@parse_result.code_units_cache(@encoding), T.any(
|
149
165
|
T.proc.params(arg0: Integer).returns(Integer),
|
@@ -198,9 +214,8 @@ module RubyLsp
|
|
198
214
|
).returns(T.nilable(Prism::Node))
|
199
215
|
end
|
200
216
|
def locate_first_within_range(range, node_types: [])
|
201
|
-
|
202
|
-
|
203
|
-
end_position = scanner.find_char_position(range[:end])
|
217
|
+
start_position, end_position = find_index_by_position(range[:start], range[:end])
|
218
|
+
|
204
219
|
desired_range = (start_position...end_position)
|
205
220
|
queue = T.let(@parse_result.value.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
|
206
221
|
|
@@ -232,12 +247,71 @@ module RubyLsp
|
|
232
247
|
).returns(NodeContext)
|
233
248
|
end
|
234
249
|
def locate_node(position, node_types: [])
|
250
|
+
char_position, _ = find_index_by_position(position)
|
251
|
+
|
235
252
|
RubyDocument.locate(
|
236
253
|
@parse_result.value,
|
237
|
-
|
254
|
+
char_position,
|
238
255
|
code_units_cache: @code_units_cache,
|
239
256
|
node_types: node_types,
|
240
257
|
)
|
241
258
|
end
|
259
|
+
|
260
|
+
sig { returns(T::Boolean) }
|
261
|
+
def should_index?
|
262
|
+
# This method controls when we should index documents. If there's no recent edit and the document has just been
|
263
|
+
# opened, we need to index it
|
264
|
+
return true unless @last_edit
|
265
|
+
|
266
|
+
last_edit_may_change_declarations?
|
267
|
+
end
|
268
|
+
|
269
|
+
private
|
270
|
+
|
271
|
+
sig { returns(T::Boolean) }
|
272
|
+
def last_edit_may_change_declarations?
|
273
|
+
case @last_edit
|
274
|
+
when Delete
|
275
|
+
# Not optimized yet. It's not trivial to identify that a declaration has been removed since the source is no
|
276
|
+
# longer there and we don't remember the deleted text
|
277
|
+
true
|
278
|
+
when Insert, Replace
|
279
|
+
position_may_impact_declarations?(@last_edit.range[:start])
|
280
|
+
else
|
281
|
+
false
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
sig { params(position: T::Hash[Symbol, Integer]).returns(T::Boolean) }
|
286
|
+
def position_may_impact_declarations?(position)
|
287
|
+
node_context = locate_node(position)
|
288
|
+
node_at_edit = node_context.node
|
289
|
+
|
290
|
+
# Adjust to the parent when editing the constant of a class/module declaration
|
291
|
+
if node_at_edit.is_a?(Prism::ConstantReadNode) || node_at_edit.is_a?(Prism::ConstantPathNode)
|
292
|
+
node_at_edit = node_context.parent
|
293
|
+
end
|
294
|
+
|
295
|
+
case node_at_edit
|
296
|
+
when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode,
|
297
|
+
Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
|
298
|
+
Prism::ConstantPathAndWriteNode, Prism::ConstantOrWriteNode, Prism::ConstantWriteNode,
|
299
|
+
Prism::ConstantAndWriteNode, Prism::ConstantOperatorWriteNode, Prism::GlobalVariableAndWriteNode,
|
300
|
+
Prism::GlobalVariableOperatorWriteNode, Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableTargetNode,
|
301
|
+
Prism::GlobalVariableWriteNode, Prism::InstanceVariableWriteNode, Prism::InstanceVariableAndWriteNode,
|
302
|
+
Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode,
|
303
|
+
Prism::InstanceVariableTargetNode, Prism::AliasMethodNode
|
304
|
+
true
|
305
|
+
when Prism::MultiWriteNode
|
306
|
+
[*node_at_edit.lefts, *node_at_edit.rest, *node_at_edit.rights].any? do |node|
|
307
|
+
node.is_a?(Prism::ConstantTargetNode) || node.is_a?(Prism::ConstantPathTargetNode)
|
308
|
+
end
|
309
|
+
when Prism::CallNode
|
310
|
+
receiver = node_at_edit.receiver
|
311
|
+
(!receiver || receiver.is_a?(Prism::SelfNode)) && METHODS_THAT_CHANGE_DECLARATIONS.include?(node_at_edit.name)
|
312
|
+
else
|
313
|
+
false
|
314
|
+
end
|
315
|
+
end
|
242
316
|
end
|
243
317
|
end
|
@@ -5,7 +5,7 @@ def compose(raw_initialize)
|
|
5
5
|
require_relative "../setup_bundler"
|
6
6
|
require "json"
|
7
7
|
require "uri"
|
8
|
-
|
8
|
+
require "ruby_indexer/lib/ruby_indexer/uri"
|
9
9
|
|
10
10
|
initialize_request = JSON.parse(raw_initialize, symbolize_names: true)
|
11
11
|
workspace_uri = initialize_request.dig(:params, :workspaceFolders, 0, :uri)
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -13,7 +13,7 @@ module RubyLsp
|
|
13
13
|
def process_message(message)
|
14
14
|
case message[:method]
|
15
15
|
when "initialize"
|
16
|
-
send_log_message("Initializing Ruby LSP v#{VERSION}
|
16
|
+
send_log_message("Initializing Ruby LSP v#{VERSION} https://github.com/Shopify/ruby-lsp/releases/tag/v#{VERSION}....")
|
17
17
|
run_initialize(message)
|
18
18
|
when "initialized"
|
19
19
|
send_log_message("Finished initializing Ruby LSP!") unless @test_mode
|
@@ -71,6 +71,8 @@ module RubyLsp
|
|
71
71
|
text_document_prepare_type_hierarchy(message)
|
72
72
|
when "textDocument/rename"
|
73
73
|
text_document_rename(message)
|
74
|
+
when "textDocument/prepareRename"
|
75
|
+
text_document_prepare_rename(message)
|
74
76
|
when "textDocument/references"
|
75
77
|
text_document_references(message)
|
76
78
|
when "typeHierarchy/supertypes"
|
@@ -104,8 +106,10 @@ module RubyLsp
|
|
104
106
|
end,
|
105
107
|
),
|
106
108
|
)
|
109
|
+
when "rubyLsp/composeBundle"
|
110
|
+
compose_bundle(message)
|
107
111
|
when "$/cancelRequest"
|
108
|
-
@
|
112
|
+
@global_state.synchronize { @cancelled_requests << message[:params][:id] }
|
109
113
|
when nil
|
110
114
|
process_response(message) if message[:result]
|
111
115
|
end
|
@@ -117,12 +121,24 @@ module RubyLsp
|
|
117
121
|
# If a document is deleted before we are able to process all of its enqueued requests, we will try to read it
|
118
122
|
# from disk and it raise this error. This is expected, so we don't include the `data` attribute to avoid
|
119
123
|
# reporting these to our telemetry
|
120
|
-
|
124
|
+
case e
|
125
|
+
when Store::NonExistingDocumentError
|
121
126
|
send_message(Error.new(
|
122
127
|
id: message[:id],
|
123
128
|
code: Constant::ErrorCodes::INVALID_PARAMS,
|
124
129
|
message: e.full_message,
|
125
130
|
))
|
131
|
+
when Document::LocationNotFoundError
|
132
|
+
send_message(Error.new(
|
133
|
+
id: message[:id],
|
134
|
+
code: Constant::ErrorCodes::REQUEST_FAILED,
|
135
|
+
message: <<~MESSAGE,
|
136
|
+
Request #{message[:method]} failed to find the target position.
|
137
|
+
The file might have been modified while the server was in the middle of searching for the target.
|
138
|
+
If you experience this regularly, please report any findings and extra information on
|
139
|
+
https://github.com/Shopify/ruby-lsp/issues/2446
|
140
|
+
MESSAGE
|
141
|
+
))
|
126
142
|
else
|
127
143
|
send_message(Error.new(
|
128
144
|
id: message[:id],
|
@@ -237,6 +253,7 @@ module RubyLsp
|
|
237
253
|
completion_provider = Requests::Completion.provider if enabled_features["completion"]
|
238
254
|
signature_help_provider = Requests::SignatureHelp.provider if enabled_features["signatureHelp"]
|
239
255
|
type_hierarchy_provider = Requests::PrepareTypeHierarchy.provider if enabled_features["typeHierarchy"]
|
256
|
+
rename_provider = Requests::Rename.provider unless @global_state.has_type_checker
|
240
257
|
|
241
258
|
response = {
|
242
259
|
capabilities: Interface::ServerCapabilities.new(
|
@@ -263,11 +280,12 @@ module RubyLsp
|
|
263
280
|
workspace_symbol_provider: enabled_features["workspaceSymbol"] && !@global_state.has_type_checker,
|
264
281
|
signature_help_provider: signature_help_provider,
|
265
282
|
type_hierarchy_provider: type_hierarchy_provider,
|
266
|
-
rename_provider:
|
283
|
+
rename_provider: rename_provider,
|
267
284
|
references_provider: !@global_state.has_type_checker,
|
268
285
|
document_range_formatting_provider: true,
|
269
286
|
experimental: {
|
270
287
|
addon_detection: true,
|
288
|
+
compose_bundle: true,
|
271
289
|
},
|
272
290
|
),
|
273
291
|
serverInfo: {
|
@@ -283,29 +301,20 @@ module RubyLsp
|
|
283
301
|
|
284
302
|
# Not every client supports dynamic registration or file watching
|
285
303
|
if @global_state.client_capabilities.supports_watching_files
|
286
|
-
send_message(
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
watchers: [
|
298
|
-
Interface::FileSystemWatcher.new(
|
299
|
-
glob_pattern: "**/*.rb",
|
300
|
-
kind: Constant::WatchKind::CREATE | Constant::WatchKind::CHANGE | Constant::WatchKind::DELETE,
|
301
|
-
),
|
302
|
-
],
|
303
|
-
),
|
304
|
-
),
|
305
|
-
],
|
306
|
-
),
|
304
|
+
send_message(Request.register_watched_files(
|
305
|
+
@current_request_id,
|
306
|
+
"**/*.rb",
|
307
|
+
registration_id: "workspace-watcher",
|
308
|
+
))
|
309
|
+
|
310
|
+
send_message(Request.register_watched_files(
|
311
|
+
@current_request_id,
|
312
|
+
Interface::RelativePattern.new(
|
313
|
+
base_uri: @global_state.workspace_uri.to_s,
|
314
|
+
pattern: "{.rubocop.yml,.rubocop}",
|
307
315
|
),
|
308
|
-
|
316
|
+
registration_id: "rubocop-watcher",
|
317
|
+
))
|
309
318
|
end
|
310
319
|
|
311
320
|
process_indexing_configuration(options.dig(:initializationOptions, :indexing))
|
@@ -341,8 +350,8 @@ module RubyLsp
|
|
341
350
|
unless @setup_error
|
342
351
|
if defined?(Requests::Support::RuboCopFormatter)
|
343
352
|
begin
|
344
|
-
@global_state.register_formatter("
|
345
|
-
rescue RuboCop::Error => e
|
353
|
+
@global_state.register_formatter("rubocop_internal", Requests::Support::RuboCopFormatter.new)
|
354
|
+
rescue ::RuboCop::Error => e
|
346
355
|
# The user may have provided unknown config switches in .rubocop or
|
347
356
|
# is trying to load a non-existent config file.
|
348
357
|
send_message(Notification.window_show_message(
|
@@ -362,7 +371,7 @@ module RubyLsp
|
|
362
371
|
|
363
372
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
364
373
|
def text_document_did_open(message)
|
365
|
-
@
|
374
|
+
@global_state.synchronize do
|
366
375
|
text_document = message.dig(:params, :textDocument)
|
367
376
|
language_id = case text_document[:languageId]
|
368
377
|
when "erb", "eruby"
|
@@ -377,7 +386,6 @@ module RubyLsp
|
|
377
386
|
uri: text_document[:uri],
|
378
387
|
source: text_document[:text],
|
379
388
|
version: text_document[:version],
|
380
|
-
encoding: @global_state.encoding,
|
381
389
|
language_id: language_id,
|
382
390
|
)
|
383
391
|
|
@@ -402,17 +410,12 @@ module RubyLsp
|
|
402
410
|
|
403
411
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
404
412
|
def text_document_did_close(message)
|
405
|
-
@
|
413
|
+
@global_state.synchronize do
|
406
414
|
uri = message.dig(:params, :textDocument, :uri)
|
407
415
|
@store.delete(uri)
|
408
416
|
|
409
417
|
# Clear diagnostics for the closed file, so that they no longer appear in the problems tab
|
410
|
-
send_message(
|
411
|
-
Notification.new(
|
412
|
-
method: "textDocument/publishDiagnostics",
|
413
|
-
params: Interface::PublishDiagnosticsParams.new(uri: uri.to_s, diagnostics: []),
|
414
|
-
),
|
415
|
-
)
|
418
|
+
send_message(Notification.publish_diagnostics(uri.to_s, []))
|
416
419
|
end
|
417
420
|
end
|
418
421
|
|
@@ -421,7 +424,7 @@ module RubyLsp
|
|
421
424
|
params = message[:params]
|
422
425
|
text_document = params[:textDocument]
|
423
426
|
|
424
|
-
@
|
427
|
+
@global_state.synchronize do
|
425
428
|
@store.push_edits(uri: text_document[:uri], edits: params[:contentChanges], version: text_document[:version])
|
426
429
|
end
|
427
430
|
end
|
@@ -478,7 +481,22 @@ module RubyLsp
|
|
478
481
|
document_link = Requests::DocumentLink.new(uri, parse_result.comments, dispatcher)
|
479
482
|
code_lens = Requests::CodeLens.new(@global_state, uri, dispatcher)
|
480
483
|
inlay_hint = Requests::InlayHints.new(document, T.must(@store.features_configuration.dig(:inlayHint)), dispatcher)
|
481
|
-
|
484
|
+
|
485
|
+
if document.is_a?(RubyDocument) && document.should_index?
|
486
|
+
# Re-index the file as it is modified. This mode of indexing updates entries only. Require path trees are only
|
487
|
+
# updated on save
|
488
|
+
@global_state.synchronize do
|
489
|
+
send_log_message("Determined that document should be indexed: #{uri}")
|
490
|
+
|
491
|
+
@global_state.index.handle_change(uri) do |index|
|
492
|
+
index.delete(uri, skip_require_paths_tree: true)
|
493
|
+
RubyIndexer::DeclarationListener.new(index, dispatcher, parse_result, uri, collect_comments: true)
|
494
|
+
dispatcher.dispatch(parse_result.value)
|
495
|
+
end
|
496
|
+
end
|
497
|
+
else
|
498
|
+
dispatcher.dispatch(parse_result.value)
|
499
|
+
end
|
482
500
|
|
483
501
|
# Store all responses retrieve in this round of visits in the cache and then return the response for the request
|
484
502
|
# we actually received
|
@@ -727,6 +745,24 @@ module RubyLsp
|
|
727
745
|
send_message(Error.new(id: message[:id], code: Constant::ErrorCodes::REQUEST_FAILED, message: e.message))
|
728
746
|
end
|
729
747
|
|
748
|
+
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
749
|
+
def text_document_prepare_rename(message)
|
750
|
+
params = message[:params]
|
751
|
+
document = @store.get(params.dig(:textDocument, :uri))
|
752
|
+
|
753
|
+
unless document.is_a?(RubyDocument)
|
754
|
+
send_empty_response(message[:id])
|
755
|
+
return
|
756
|
+
end
|
757
|
+
|
758
|
+
send_message(
|
759
|
+
Result.new(
|
760
|
+
id: message[:id],
|
761
|
+
response: Requests::PrepareRename.new(document, params[:position]).perform,
|
762
|
+
),
|
763
|
+
)
|
764
|
+
end
|
765
|
+
|
730
766
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
731
767
|
def text_document_references(message)
|
732
768
|
params = message[:params]
|
@@ -972,28 +1008,83 @@ module RubyLsp
|
|
972
1008
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
973
1009
|
def workspace_did_change_watched_files(message)
|
974
1010
|
changes = message.dig(:params, :changes)
|
1011
|
+
# We allow add-ons to register for watching files and we have no restrictions for what they register for. If the
|
1012
|
+
# same pattern is registered more than once, the LSP will receive duplicate change notifications. Receiving them
|
1013
|
+
# is fine, but we shouldn't process the same file changes more than once
|
1014
|
+
changes.uniq!
|
1015
|
+
|
975
1016
|
index = @global_state.index
|
976
1017
|
changes.each do |change|
|
977
1018
|
# File change events include folders, but we're only interested in files
|
978
1019
|
uri = URI(change[:uri])
|
979
1020
|
file_path = uri.to_standardized_path
|
980
1021
|
next if file_path.nil? || File.directory?(file_path)
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
when Constant::FileChangeType::DELETED
|
992
|
-
index.delete(indexable)
|
1022
|
+
|
1023
|
+
if file_path.end_with?(".rb")
|
1024
|
+
handle_ruby_file_change(index, file_path, change[:type])
|
1025
|
+
next
|
1026
|
+
end
|
1027
|
+
|
1028
|
+
file_name = File.basename(file_path)
|
1029
|
+
|
1030
|
+
if file_name == ".rubocop.yml" || file_name == ".rubocop"
|
1031
|
+
handle_rubocop_config_change(uri)
|
993
1032
|
end
|
994
1033
|
end
|
995
1034
|
|
996
|
-
Addon.file_watcher_addons.each
|
1035
|
+
Addon.file_watcher_addons.each do |addon|
|
1036
|
+
T.unsafe(addon).workspace_did_change_watched_files(changes)
|
1037
|
+
rescue => e
|
1038
|
+
send_log_message(
|
1039
|
+
"Error in #{addon.name} add-on while processing watched file notifications: #{e.full_message}",
|
1040
|
+
type: Constant::MessageType::ERROR,
|
1041
|
+
)
|
1042
|
+
end
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
sig { params(index: RubyIndexer::Index, file_path: String, change_type: Integer).void }
|
1046
|
+
def handle_ruby_file_change(index, file_path, change_type)
|
1047
|
+
load_path_entry = $LOAD_PATH.find { |load_path| file_path.start_with?(load_path) }
|
1048
|
+
uri = URI::Generic.from_path(load_path_entry: load_path_entry, path: file_path)
|
1049
|
+
|
1050
|
+
content = File.read(file_path)
|
1051
|
+
|
1052
|
+
case change_type
|
1053
|
+
when Constant::FileChangeType::CREATED
|
1054
|
+
# If we receive a late created notification for a file that has already been claimed by the client, we want to
|
1055
|
+
# handle change for that URI so that the require path tree is updated
|
1056
|
+
@store.key?(uri) ? index.handle_change(uri, content) : index.index_single(uri, content)
|
1057
|
+
when Constant::FileChangeType::CHANGED
|
1058
|
+
# We only handle changes on file watched notifications if the client is not the one managing this URI.
|
1059
|
+
# Otherwise, these changes are handled when running the combined requests
|
1060
|
+
index.handle_change(uri, content) unless @store.key?(uri)
|
1061
|
+
when Constant::FileChangeType::DELETED
|
1062
|
+
index.delete(uri)
|
1063
|
+
end
|
1064
|
+
rescue Errno::ENOENT
|
1065
|
+
# If a file is created and then delete immediately afterwards, we will process the created notification before we
|
1066
|
+
# receive the deleted one, but the file no longer exists. This may happen when running a test suite that creates
|
1067
|
+
# and deletes files automatically.
|
1068
|
+
end
|
1069
|
+
|
1070
|
+
sig { params(uri: URI::Generic).void }
|
1071
|
+
def handle_rubocop_config_change(uri)
|
1072
|
+
return unless defined?(Requests::Support::RuboCopFormatter)
|
1073
|
+
|
1074
|
+
# Register a new runner to reload configurations
|
1075
|
+
@global_state.register_formatter("rubocop_internal", Requests::Support::RuboCopFormatter.new)
|
1076
|
+
|
1077
|
+
# Clear all document caches for pull diagnostics
|
1078
|
+
@global_state.synchronize do
|
1079
|
+
@store.each do |_uri, document|
|
1080
|
+
document.cache_set("textDocument/diagnostic", Document::EMPTY_CACHE)
|
1081
|
+
end
|
1082
|
+
end
|
1083
|
+
|
1084
|
+
# Request a pull diagnostic refresh from the editor
|
1085
|
+
if @global_state.client_capabilities.supports_diagnostic_refresh
|
1086
|
+
send_message(Request.new(id: @current_request_id, method: "workspace/diagnostic/refresh", params: nil))
|
1087
|
+
end
|
997
1088
|
end
|
998
1089
|
|
999
1090
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
@@ -1065,7 +1156,12 @@ module RubyLsp
|
|
1065
1156
|
|
1066
1157
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
1067
1158
|
def workspace_dependencies(message)
|
1068
|
-
|
1159
|
+
unless @global_state.top_level_bundle
|
1160
|
+
send_message(Result.new(id: message[:id], response: []))
|
1161
|
+
return
|
1162
|
+
end
|
1163
|
+
|
1164
|
+
response = begin
|
1069
1165
|
Bundler.with_original_env do
|
1070
1166
|
definition = Bundler.definition
|
1071
1167
|
dep_keys = definition.locked_deps.keys.to_set
|
@@ -1079,7 +1175,7 @@ module RubyLsp
|
|
1079
1175
|
}
|
1080
1176
|
end
|
1081
1177
|
end
|
1082
|
-
|
1178
|
+
rescue Bundler::GemNotFound
|
1083
1179
|
[]
|
1084
1180
|
end
|
1085
1181
|
|
@@ -1088,7 +1184,7 @@ module RubyLsp
|
|
1088
1184
|
|
1089
1185
|
sig { override.void }
|
1090
1186
|
def shutdown
|
1091
|
-
Addon.
|
1187
|
+
Addon.unload_addons
|
1092
1188
|
end
|
1093
1189
|
|
1094
1190
|
sig { void }
|
@@ -1156,16 +1252,15 @@ module RubyLsp
|
|
1156
1252
|
sig { void }
|
1157
1253
|
def check_formatter_is_available
|
1158
1254
|
return if @setup_error
|
1159
|
-
# Warn of an unavailable `formatter` setting, e.g. `
|
1160
|
-
|
1161
|
-
return unless @global_state.formatter == "rubocop"
|
1255
|
+
# Warn of an unavailable `formatter` setting, e.g. `rubocop_internal` on a project which doesn't have RuboCop.
|
1256
|
+
return unless @global_state.formatter == "rubocop_internal"
|
1162
1257
|
|
1163
1258
|
unless defined?(RubyLsp::Requests::Support::RuboCopRunner)
|
1164
1259
|
@global_state.formatter = "none"
|
1165
1260
|
|
1166
1261
|
send_message(
|
1167
1262
|
Notification.window_show_message(
|
1168
|
-
"Ruby LSP formatter is set to `
|
1263
|
+
"Ruby LSP formatter is set to `rubocop_internal` but RuboCop was not found in the Gemfile or gemspec.",
|
1169
1264
|
type: Constant::MessageType::ERROR,
|
1170
1265
|
),
|
1171
1266
|
)
|
@@ -1205,10 +1300,10 @@ module RubyLsp
|
|
1205
1300
|
return
|
1206
1301
|
end
|
1207
1302
|
|
1208
|
-
return unless indexing_options
|
1209
|
-
|
1210
1303
|
configuration = @global_state.index.configuration
|
1211
1304
|
configuration.workspace_path = @global_state.workspace_path
|
1305
|
+
return unless indexing_options
|
1306
|
+
|
1212
1307
|
# The index expects snake case configurations, but VS Code standardizes on camel case settings
|
1213
1308
|
configuration.apply_config(indexing_options.transform_keys { |key| key.to_s.gsub(/([A-Z])/, "_\\1").downcase })
|
1214
1309
|
end
|
@@ -1224,5 +1319,54 @@ module RubyLsp
|
|
1224
1319
|
|
1225
1320
|
addon.handle_window_show_message_response(result[:title])
|
1226
1321
|
end
|
1322
|
+
|
1323
|
+
# NOTE: all servers methods are void because they can produce several messages for the client. The only reason this
|
1324
|
+
# method returns the created thread is to that we can join it in tests and avoid flakiness. The implementation is
|
1325
|
+
# not supposed to rely on the return of this method
|
1326
|
+
sig { params(message: T::Hash[Symbol, T.untyped]).returns(T.nilable(Thread)) }
|
1327
|
+
def compose_bundle(message)
|
1328
|
+
already_composed_path = File.join(@global_state.workspace_path, ".ruby-lsp", "bundle_is_composed")
|
1329
|
+
id = message[:id]
|
1330
|
+
|
1331
|
+
begin
|
1332
|
+
Bundler.with_original_env do
|
1333
|
+
Bundler::LockfileParser.new(Bundler.default_lockfile.read)
|
1334
|
+
end
|
1335
|
+
rescue Bundler::LockfileError => e
|
1336
|
+
send_message(Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: e.message))
|
1337
|
+
return
|
1338
|
+
rescue Bundler::GemfileNotFound, Errno::ENOENT
|
1339
|
+
# We still compose the bundle if there's no Gemfile or if the lockfile got deleted
|
1340
|
+
end
|
1341
|
+
|
1342
|
+
# We compose the bundle in a thread so that the LSP continues to work while we're checking for its validity. Once
|
1343
|
+
# we return the response back to the editor, then the restart is triggered
|
1344
|
+
Thread.new do
|
1345
|
+
send_log_message("Recomposing the bundle ahead of restart")
|
1346
|
+
|
1347
|
+
_stdout, stderr, status = Bundler.with_unbundled_env do
|
1348
|
+
Open3.capture3(
|
1349
|
+
Gem.ruby,
|
1350
|
+
"-I",
|
1351
|
+
File.dirname(T.must(__dir__)),
|
1352
|
+
File.expand_path("../../exe/ruby-lsp-launcher", __dir__),
|
1353
|
+
@global_state.workspace_uri.to_s,
|
1354
|
+
chdir: @global_state.workspace_path,
|
1355
|
+
)
|
1356
|
+
end
|
1357
|
+
|
1358
|
+
if status&.exitstatus == 0
|
1359
|
+
# Create a signal for the restart that it can skip composing the bundle and launch directly
|
1360
|
+
FileUtils.touch(already_composed_path)
|
1361
|
+
send_message(Result.new(id: id, response: { success: true }))
|
1362
|
+
else
|
1363
|
+
# This special error code makes the extension avoid restarting in case we already know that the composed
|
1364
|
+
# bundle is not valid
|
1365
|
+
send_message(
|
1366
|
+
Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: "Failed to compose bundle\n#{stderr}"),
|
1367
|
+
)
|
1368
|
+
end
|
1369
|
+
end
|
1370
|
+
end
|
1227
1371
|
end
|
1228
1372
|
end
|