ruby-lsp 0.7.6 → 0.8.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 +41 -33
- data/exe/ruby-lsp-check +2 -2
- data/lib/core_ext/uri.rb +40 -0
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +91 -0
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +122 -0
- data/lib/ruby_indexer/lib/ruby_indexer/visitor.rb +121 -0
- data/lib/ruby_indexer/ruby_indexer.rb +19 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +204 -0
- data/lib/ruby_indexer/test/configuration_test.rb +35 -0
- data/lib/ruby_indexer/test/constant_test.rb +108 -0
- data/lib/ruby_indexer/test/index_test.rb +94 -0
- data/lib/ruby_indexer/test/test_case.rb +42 -0
- data/lib/ruby_lsp/document.rb +3 -3
- data/lib/ruby_lsp/executor.rb +131 -24
- data/lib/ruby_lsp/extension.rb +24 -0
- data/lib/ruby_lsp/internal.rb +4 -0
- data/lib/ruby_lsp/listener.rb +15 -14
- data/lib/ruby_lsp/requests/code_actions.rb +3 -3
- data/lib/ruby_lsp/requests/code_lens.rb +10 -24
- data/lib/ruby_lsp/requests/definition.rb +55 -8
- data/lib/ruby_lsp/requests/diagnostics.rb +3 -2
- data/lib/ruby_lsp/requests/document_link.rb +4 -3
- data/lib/ruby_lsp/requests/formatting.rb +3 -2
- data/lib/ruby_lsp/requests/hover.rb +4 -18
- data/lib/ruby_lsp/requests/on_type_formatting.rb +4 -6
- data/lib/ruby_lsp/requests/support/dependency_detector.rb +5 -0
- data/lib/ruby_lsp/requests/support/formatter_runner.rb +1 -1
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +2 -2
- data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +2 -3
- data/lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb +2 -3
- data/lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb +3 -2
- data/lib/ruby_lsp/server.rb +10 -2
- data/lib/ruby_lsp/setup_bundler.rb +28 -14
- data/lib/ruby_lsp/store.rb +20 -13
- data/lib/ruby_lsp/utils.rb +1 -1
- metadata +27 -3
data/lib/ruby_lsp/executor.rb
CHANGED
@@ -15,6 +15,7 @@ module RubyLsp
|
|
15
15
|
@store = store
|
16
16
|
@test_library = T.let(DependencyDetector.detected_test_library, String)
|
17
17
|
@message_queue = message_queue
|
18
|
+
@index = T.let(RubyIndexer::Index.new, RubyIndexer::Index)
|
18
19
|
end
|
19
20
|
|
20
21
|
sig { params(request: T::Hash[Symbol, T.untyped]).returns(Result) }
|
@@ -35,7 +36,7 @@ module RubyLsp
|
|
35
36
|
|
36
37
|
sig { params(request: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
|
37
38
|
def run(request)
|
38
|
-
uri = request.dig(:params, :textDocument, :uri)
|
39
|
+
uri = URI(request.dig(:params, :textDocument, :uri).to_s)
|
39
40
|
|
40
41
|
case request[:method]
|
41
42
|
when "initialize"
|
@@ -57,6 +58,14 @@ module RubyLsp
|
|
57
58
|
warn(errored_extensions.map(&:backtraces).join("\n\n"))
|
58
59
|
end
|
59
60
|
|
61
|
+
if @store.experimental_features
|
62
|
+
# The begin progress invocation happens during `initialize`, so that the notification is sent before we are
|
63
|
+
# stuck indexing files
|
64
|
+
RubyIndexer.configuration.load_config
|
65
|
+
@index.index_all
|
66
|
+
end_progress("indexing-progress")
|
67
|
+
end
|
68
|
+
|
60
69
|
check_formatter_is_available
|
61
70
|
|
62
71
|
warn("Ruby LSP is ready")
|
@@ -70,7 +79,7 @@ module RubyLsp
|
|
70
79
|
when "textDocument/didClose"
|
71
80
|
@message_queue << Notification.new(
|
72
81
|
message: "textDocument/publishDiagnostics",
|
73
|
-
params: Interface::PublishDiagnosticsParams.new(uri: uri, diagnostics: []),
|
82
|
+
params: Interface::PublishDiagnosticsParams.new(uri: uri.to_s, diagnostics: []),
|
74
83
|
)
|
75
84
|
|
76
85
|
text_document_did_close(uri)
|
@@ -169,30 +178,64 @@ module RubyLsp
|
|
169
178
|
completion(uri, request.dig(:params, :position))
|
170
179
|
when "textDocument/definition"
|
171
180
|
definition(uri, request.dig(:params, :position))
|
181
|
+
when "workspace/didChangeWatchedFiles"
|
182
|
+
did_change_watched_files(request.dig(:params, :changes))
|
172
183
|
when "rubyLsp/textDocument/showSyntaxTree"
|
173
184
|
show_syntax_tree(uri, request.dig(:params, :range))
|
174
185
|
end
|
175
186
|
end
|
176
187
|
|
177
|
-
sig { params(uri: String,
|
188
|
+
sig { params(changes: T::Array[{ uri: String, type: Integer }]).returns(Object) }
|
189
|
+
def did_change_watched_files(changes)
|
190
|
+
changes.each do |change|
|
191
|
+
# File change events include folders, but we're only interested in files
|
192
|
+
uri = URI(change[:uri])
|
193
|
+
file_path = uri.to_standardized_path
|
194
|
+
next if file_path.nil? || File.directory?(file_path)
|
195
|
+
|
196
|
+
case change[:type]
|
197
|
+
when Constant::FileChangeType::CREATED
|
198
|
+
@index.index_single(file_path)
|
199
|
+
when Constant::FileChangeType::CHANGED
|
200
|
+
@index.delete(file_path)
|
201
|
+
@index.index_single(file_path)
|
202
|
+
when Constant::FileChangeType::DELETED
|
203
|
+
@index.delete(file_path)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
VOID
|
208
|
+
end
|
209
|
+
|
210
|
+
sig { params(uri: URI::Generic, range: T.nilable(Document::RangeShape)).returns({ ast: String }) }
|
178
211
|
def show_syntax_tree(uri, range)
|
179
212
|
{ ast: Requests::ShowSyntaxTree.new(@store.get(uri), range).run }
|
180
213
|
end
|
181
214
|
|
182
|
-
sig
|
215
|
+
sig do
|
216
|
+
params(
|
217
|
+
uri: URI::Generic,
|
218
|
+
position: Document::PositionShape,
|
219
|
+
).returns(T.nilable(T.any(T::Array[Interface::Location], Interface::Location)))
|
220
|
+
end
|
183
221
|
def definition(uri, position)
|
184
222
|
document = @store.get(uri)
|
185
223
|
return if document.syntax_error?
|
186
224
|
|
187
|
-
target,
|
225
|
+
target, parent, nesting = document.locate_node(
|
226
|
+
position,
|
227
|
+
node_types: [SyntaxTree::Command, SyntaxTree::Const, SyntaxTree::ConstPathRef],
|
228
|
+
)
|
229
|
+
|
230
|
+
target = parent if target.is_a?(SyntaxTree::Const) && parent.is_a?(SyntaxTree::ConstPathRef)
|
188
231
|
|
189
232
|
emitter = EventEmitter.new
|
190
|
-
base_listener = Requests::Definition.new(uri, emitter, @message_queue)
|
233
|
+
base_listener = Requests::Definition.new(uri, nesting, @index, emitter, @message_queue)
|
191
234
|
emitter.emit_for_target(target)
|
192
235
|
base_listener.response
|
193
236
|
end
|
194
237
|
|
195
|
-
sig { params(uri:
|
238
|
+
sig { params(uri: URI::Generic).returns(T::Array[Interface::FoldingRange]) }
|
196
239
|
def folding_range(uri)
|
197
240
|
@store.cache_fetch(uri, "textDocument/foldingRange") do |document|
|
198
241
|
Requests::FoldingRanges.new(document).run
|
@@ -201,7 +244,7 @@ module RubyLsp
|
|
201
244
|
|
202
245
|
sig do
|
203
246
|
params(
|
204
|
-
uri:
|
247
|
+
uri: URI::Generic,
|
205
248
|
position: Document::PositionShape,
|
206
249
|
).returns(T.nilable(Interface::Hover))
|
207
250
|
end
|
@@ -227,19 +270,19 @@ module RubyLsp
|
|
227
270
|
hover.response
|
228
271
|
end
|
229
272
|
|
230
|
-
sig { params(uri:
|
273
|
+
sig { params(uri: URI::Generic, content_changes: T::Array[Document::EditShape], version: Integer).returns(Object) }
|
231
274
|
def text_document_did_change(uri, content_changes, version)
|
232
275
|
@store.push_edits(uri: uri, edits: content_changes, version: version)
|
233
276
|
VOID
|
234
277
|
end
|
235
278
|
|
236
|
-
sig { params(uri:
|
279
|
+
sig { params(uri: URI::Generic, text: String, version: Integer).returns(Object) }
|
237
280
|
def text_document_did_open(uri, text, version)
|
238
281
|
@store.set(uri: uri, source: text, version: version)
|
239
282
|
VOID
|
240
283
|
end
|
241
284
|
|
242
|
-
sig { params(uri:
|
285
|
+
sig { params(uri: URI::Generic).returns(Object) }
|
243
286
|
def text_document_did_close(uri)
|
244
287
|
@store.delete(uri)
|
245
288
|
VOID
|
@@ -247,7 +290,7 @@ module RubyLsp
|
|
247
290
|
|
248
291
|
sig do
|
249
292
|
params(
|
250
|
-
uri:
|
293
|
+
uri: URI::Generic,
|
251
294
|
positions: T::Array[Document::PositionShape],
|
252
295
|
).returns(T.nilable(T::Array[T.nilable(Requests::Support::SelectionRange)]))
|
253
296
|
end
|
@@ -270,7 +313,7 @@ module RubyLsp
|
|
270
313
|
end
|
271
314
|
end
|
272
315
|
|
273
|
-
sig { params(uri:
|
316
|
+
sig { params(uri: URI::Generic).returns(T.nilable(T::Array[Interface::TextEdit])) }
|
274
317
|
def formatting(uri)
|
275
318
|
# If formatter is set to `auto` but no supported formatting gem is found, don't attempt to format
|
276
319
|
return if @store.formatter == "none"
|
@@ -280,7 +323,7 @@ module RubyLsp
|
|
280
323
|
|
281
324
|
sig do
|
282
325
|
params(
|
283
|
-
uri:
|
326
|
+
uri: URI::Generic,
|
284
327
|
position: Document::PositionShape,
|
285
328
|
character: String,
|
286
329
|
).returns(T::Array[Interface::TextEdit])
|
@@ -291,7 +334,7 @@ module RubyLsp
|
|
291
334
|
|
292
335
|
sig do
|
293
336
|
params(
|
294
|
-
uri:
|
337
|
+
uri: URI::Generic,
|
295
338
|
position: Document::PositionShape,
|
296
339
|
).returns(T.nilable(T::Array[Interface::DocumentHighlight]))
|
297
340
|
end
|
@@ -306,7 +349,7 @@ module RubyLsp
|
|
306
349
|
listener.response
|
307
350
|
end
|
308
351
|
|
309
|
-
sig { params(uri:
|
352
|
+
sig { params(uri: URI::Generic, range: Document::RangeShape).returns(T.nilable(T::Array[Interface::InlayHint])) }
|
310
353
|
def inlay_hint(uri, range)
|
311
354
|
document = @store.get(uri)
|
312
355
|
return if document.syntax_error?
|
@@ -322,7 +365,7 @@ module RubyLsp
|
|
322
365
|
|
323
366
|
sig do
|
324
367
|
params(
|
325
|
-
uri:
|
368
|
+
uri: URI::Generic,
|
326
369
|
range: Document::RangeShape,
|
327
370
|
context: T::Hash[Symbol, T.untyped],
|
328
371
|
).returns(T.nilable(T::Array[Interface::CodeAction]))
|
@@ -335,7 +378,7 @@ module RubyLsp
|
|
335
378
|
|
336
379
|
sig { params(params: T::Hash[Symbol, T.untyped]).returns(Interface::CodeAction) }
|
337
380
|
def code_action_resolve(params)
|
338
|
-
uri = params.dig(:data, :uri)
|
381
|
+
uri = URI(params.dig(:data, :uri))
|
339
382
|
document = @store.get(uri)
|
340
383
|
result = Requests::CodeActionResolve.new(document, params).run
|
341
384
|
|
@@ -363,7 +406,7 @@ module RubyLsp
|
|
363
406
|
end
|
364
407
|
end
|
365
408
|
|
366
|
-
sig { params(uri:
|
409
|
+
sig { params(uri: URI::Generic).returns(T.nilable(Interface::FullDocumentDiagnosticReport)) }
|
367
410
|
def diagnostic(uri)
|
368
411
|
response = @store.cache_fetch(uri, "textDocument/diagnostic") do |document|
|
369
412
|
Requests::Diagnostics.new(document).run
|
@@ -372,7 +415,7 @@ module RubyLsp
|
|
372
415
|
Interface::FullDocumentDiagnosticReport.new(kind: "full", items: response.map(&:to_lsp_diagnostic)) if response
|
373
416
|
end
|
374
417
|
|
375
|
-
sig { params(uri:
|
418
|
+
sig { params(uri: URI::Generic, range: Document::RangeShape).returns(Interface::SemanticTokens) }
|
376
419
|
def semantic_tokens_range(uri, range)
|
377
420
|
document = @store.get(uri)
|
378
421
|
start_line = range.dig(:start, :line)
|
@@ -390,7 +433,10 @@ module RubyLsp
|
|
390
433
|
end
|
391
434
|
|
392
435
|
sig do
|
393
|
-
params(
|
436
|
+
params(
|
437
|
+
uri: URI::Generic,
|
438
|
+
position: Document::PositionShape,
|
439
|
+
).returns(T.nilable(T::Array[Interface::CompletionItem]))
|
394
440
|
end
|
395
441
|
def completion(uri, position)
|
396
442
|
document = @store.get(uri)
|
@@ -433,6 +479,37 @@ module RubyLsp
|
|
433
479
|
listener.response
|
434
480
|
end
|
435
481
|
|
482
|
+
sig { params(id: String, title: String).void }
|
483
|
+
def begin_progress(id, title)
|
484
|
+
return unless @store.supports_progress
|
485
|
+
|
486
|
+
@message_queue << Request.new(
|
487
|
+
message: "window/workDoneProgress/create",
|
488
|
+
params: Interface::WorkDoneProgressCreateParams.new(token: id),
|
489
|
+
)
|
490
|
+
|
491
|
+
@message_queue << Notification.new(
|
492
|
+
message: "$/progress",
|
493
|
+
params: Interface::ProgressParams.new(
|
494
|
+
token: id,
|
495
|
+
value: Interface::WorkDoneProgressBegin.new(kind: "begin", title: title),
|
496
|
+
),
|
497
|
+
)
|
498
|
+
end
|
499
|
+
|
500
|
+
sig { params(id: String).void }
|
501
|
+
def end_progress(id)
|
502
|
+
return unless @store.supports_progress
|
503
|
+
|
504
|
+
@message_queue << Notification.new(
|
505
|
+
message: "$/progress",
|
506
|
+
params: Interface::ProgressParams.new(
|
507
|
+
token: id,
|
508
|
+
value: Interface::WorkDoneProgressEnd.new(kind: "end"),
|
509
|
+
),
|
510
|
+
)
|
511
|
+
end
|
512
|
+
|
436
513
|
sig { params(options: T::Hash[Symbol, T.untyped]).returns(Interface::InitializeResult) }
|
437
514
|
def initialize_request(options)
|
438
515
|
@store.clear
|
@@ -446,6 +523,7 @@ module RubyLsp
|
|
446
523
|
encodings.first
|
447
524
|
end
|
448
525
|
|
526
|
+
@store.supports_progress = options.dig(:capabilities, :window, :workDoneProgress) || true
|
449
527
|
formatter = options.dig(:initializationOptions, :formatter) || "auto"
|
450
528
|
@store.formatter = if formatter == "auto"
|
451
529
|
DependencyDetector.detected_formatter
|
@@ -454,9 +532,7 @@ module RubyLsp
|
|
454
532
|
end
|
455
533
|
|
456
534
|
configured_features = options.dig(:initializationOptions, :enabledFeatures)
|
457
|
-
|
458
|
-
# Uncomment the line below and use the variable to gate features behind the experimental flag
|
459
|
-
# experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled)
|
535
|
+
@store.experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
|
460
536
|
|
461
537
|
enabled_features = case configured_features
|
462
538
|
when Array
|
@@ -538,6 +614,37 @@ module RubyLsp
|
|
538
614
|
)
|
539
615
|
end
|
540
616
|
|
617
|
+
if @store.experimental_features
|
618
|
+
# Dynamically registered capabilities
|
619
|
+
file_watching_caps = options.dig(:capabilities, :workspace, :didChangeWatchedFiles)
|
620
|
+
|
621
|
+
# Not every client supports dynamic registration or file watching
|
622
|
+
if file_watching_caps&.dig(:dynamicRegistration) && file_watching_caps&.dig(:relativePatternSupport)
|
623
|
+
@message_queue << Request.new(
|
624
|
+
message: "client/registerCapability",
|
625
|
+
params: Interface::RegistrationParams.new(
|
626
|
+
registrations: [
|
627
|
+
# Register watching Ruby files
|
628
|
+
Interface::Registration.new(
|
629
|
+
id: "workspace/didChangeWatchedFiles",
|
630
|
+
method: "workspace/didChangeWatchedFiles",
|
631
|
+
register_options: Interface::DidChangeWatchedFilesRegistrationOptions.new(
|
632
|
+
watchers: [
|
633
|
+
Interface::FileSystemWatcher.new(
|
634
|
+
glob_pattern: "**/*.rb",
|
635
|
+
kind: Constant::WatchKind::CREATE | Constant::WatchKind::CHANGE | Constant::WatchKind::DELETE,
|
636
|
+
),
|
637
|
+
],
|
638
|
+
),
|
639
|
+
),
|
640
|
+
],
|
641
|
+
),
|
642
|
+
)
|
643
|
+
end
|
644
|
+
|
645
|
+
begin_progress("indexing-progress", "Ruby LSP: indexing files")
|
646
|
+
end
|
647
|
+
|
541
648
|
Interface::InitializeResult.new(
|
542
649
|
capabilities: Interface::ServerCapabilities.new(
|
543
650
|
text_document_sync: Interface::TextDocumentSyncOptions.new(
|
data/lib/ruby_lsp/extension.rb
CHANGED
@@ -97,8 +97,32 @@ module RubyLsp
|
|
97
97
|
sig { abstract.void }
|
98
98
|
def activate; end
|
99
99
|
|
100
|
+
# Each extension should implement `MyExtension#deactivate` and use to perform any clean up, like shutting down a
|
101
|
+
# child process
|
102
|
+
sig { abstract.void }
|
103
|
+
def deactivate; end
|
104
|
+
|
100
105
|
# Extensions should override the `name` method to return the extension name
|
101
106
|
sig { abstract.returns(String) }
|
102
107
|
def name; end
|
108
|
+
|
109
|
+
# Creates a new CodeLens listener. This method is invoked on every CodeLens request
|
110
|
+
sig do
|
111
|
+
overridable.params(
|
112
|
+
uri: URI::Generic,
|
113
|
+
emitter: EventEmitter,
|
114
|
+
message_queue: Thread::Queue,
|
115
|
+
).returns(T.nilable(Listener[T::Array[Interface::CodeLens]]))
|
116
|
+
end
|
117
|
+
def create_code_lens_listener(uri, emitter, message_queue); end
|
118
|
+
|
119
|
+
# Creates a new Hover listener. This method is invoked on every Hover request
|
120
|
+
sig do
|
121
|
+
overridable.params(
|
122
|
+
emitter: EventEmitter,
|
123
|
+
message_queue: Thread::Queue,
|
124
|
+
).returns(T.nilable(Listener[T.nilable(Interface::Hover)]))
|
125
|
+
end
|
126
|
+
def create_hover_listener(emitter, message_queue); end
|
103
127
|
end
|
104
128
|
end
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -3,12 +3,16 @@
|
|
3
3
|
|
4
4
|
require "sorbet-runtime"
|
5
5
|
require "syntax_tree"
|
6
|
+
require "yarp"
|
6
7
|
require "language_server-protocol"
|
7
8
|
require "benchmark"
|
8
9
|
require "bundler"
|
9
10
|
require "uri"
|
11
|
+
require "cgi"
|
10
12
|
|
11
13
|
require "ruby-lsp"
|
14
|
+
require "ruby_indexer/ruby_indexer"
|
15
|
+
require "core_ext/uri"
|
12
16
|
require "ruby_lsp/utils"
|
13
17
|
require "ruby_lsp/server"
|
14
18
|
require "ruby_lsp/executor"
|
data/lib/ruby_lsp/listener.rb
CHANGED
@@ -18,25 +18,26 @@ module RubyLsp
|
|
18
18
|
def initialize(emitter, message_queue)
|
19
19
|
@emitter = emitter
|
20
20
|
@message_queue = message_queue
|
21
|
-
|
22
|
-
|
23
|
-
class << self
|
24
|
-
extend T::Sig
|
25
|
-
|
26
|
-
sig { returns(T::Array[T.class_of(Listener)]) }
|
27
|
-
def listeners
|
28
|
-
@listeners ||= T.let([], T.nilable(T::Array[T.class_of(Listener)]))
|
29
|
-
end
|
30
|
-
|
31
|
-
sig { params(listener: T.class_of(Listener)).void }
|
32
|
-
def add_listener(listener)
|
33
|
-
listeners << listener
|
34
|
-
end
|
21
|
+
@external_listeners = T.let([], T::Array[RubyLsp::Listener[ResponseType]])
|
35
22
|
end
|
36
23
|
|
37
24
|
# Override this method with an attr_reader that returns the response of your listener. The listener should
|
38
25
|
# accumulate results in a @response variable and then provide the reader so that it is accessible
|
39
26
|
sig { abstract.returns(ResponseType) }
|
40
27
|
def response; end
|
28
|
+
|
29
|
+
# Merge responses from all external listeners into the base listener's response. We do this to return a single
|
30
|
+
# response to the editor including the results of all extensions
|
31
|
+
sig { void }
|
32
|
+
def merge_external_listeners_responses!
|
33
|
+
@external_listeners.each { |l| merge_response!(l) }
|
34
|
+
end
|
35
|
+
|
36
|
+
# Does nothing by default. Requests that accept extensions should override this method to define how to merge
|
37
|
+
# responses coming from external listeners
|
38
|
+
sig { overridable.params(other: Listener[T.untyped]).returns(T.self_type) }
|
39
|
+
def merge_response!(other)
|
40
|
+
self
|
41
|
+
end
|
41
42
|
end
|
42
43
|
end
|
@@ -29,7 +29,7 @@ module RubyLsp
|
|
29
29
|
def initialize(document, range, context)
|
30
30
|
super(document)
|
31
31
|
|
32
|
-
@uri = T.let(document.uri,
|
32
|
+
@uri = T.let(document.uri, URI::Generic)
|
33
33
|
@range = range
|
34
34
|
@context = context
|
35
35
|
end
|
@@ -63,14 +63,14 @@ module RubyLsp
|
|
63
63
|
)
|
64
64
|
end
|
65
65
|
|
66
|
-
sig { params(range: Document::RangeShape, uri:
|
66
|
+
sig { params(range: Document::RangeShape, uri: URI::Generic).returns(Interface::CodeAction) }
|
67
67
|
def refactor_code_action(range, uri)
|
68
68
|
Interface::CodeAction.new(
|
69
69
|
title: "Refactor: Extract Variable",
|
70
70
|
kind: Constant::CodeActionKind::REFACTOR_EXTRACT,
|
71
71
|
data: {
|
72
72
|
range: range,
|
73
|
-
uri: uri,
|
73
|
+
uri: uri.to_s,
|
74
74
|
},
|
75
75
|
)
|
76
76
|
end
|
@@ -31,15 +31,17 @@ module RubyLsp
|
|
31
31
|
sig { override.returns(ResponseType) }
|
32
32
|
attr_reader :response
|
33
33
|
|
34
|
-
sig { params(uri:
|
34
|
+
sig { params(uri: URI::Generic, emitter: EventEmitter, message_queue: Thread::Queue, test_library: String).void }
|
35
35
|
def initialize(uri, emitter, message_queue, test_library)
|
36
36
|
super(emitter, message_queue)
|
37
37
|
|
38
|
-
@uri = T.let(uri,
|
39
|
-
@external_listeners
|
38
|
+
@uri = T.let(uri, URI::Generic)
|
39
|
+
@external_listeners.concat(
|
40
|
+
Extension.extensions.filter_map { |ext| ext.create_code_lens_listener(uri, emitter, message_queue) },
|
41
|
+
)
|
40
42
|
@test_library = T.let(test_library, String)
|
41
43
|
@response = T.let([], ResponseType)
|
42
|
-
@path = T.let(
|
44
|
+
@path = T.let(uri.to_standardized_path, T.nilable(String))
|
43
45
|
# visibility_stack is a stack of [current_visibility, previous_visibility]
|
44
46
|
@visibility_stack = T.let([["public", "public"]], T::Array[T::Array[T.nilable(String)]])
|
45
47
|
@class_stack = T.let([], T::Array[String])
|
@@ -55,22 +57,6 @@ module RubyLsp
|
|
55
57
|
:after_call,
|
56
58
|
:on_vcall,
|
57
59
|
)
|
58
|
-
|
59
|
-
register_external_listeners!
|
60
|
-
end
|
61
|
-
|
62
|
-
sig { void }
|
63
|
-
def register_external_listeners!
|
64
|
-
self.class.listeners.each do |l|
|
65
|
-
@external_listeners << T.unsafe(l).new(@uri, @emitter, @message_queue)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
sig { void }
|
70
|
-
def merge_external_listeners_responses!
|
71
|
-
@external_listeners.each do |l|
|
72
|
-
merge_response!(l)
|
73
|
-
end
|
74
60
|
end
|
75
61
|
|
76
62
|
sig { params(node: SyntaxTree::ClassDeclaration).void }
|
@@ -120,7 +106,7 @@ module RubyLsp
|
|
120
106
|
if ACCESS_MODIFIERS.include?(node_message) && node.arguments.parts.any?
|
121
107
|
visibility, _ = @visibility_stack.pop
|
122
108
|
@visibility_stack.push([node_message, visibility])
|
123
|
-
elsif @path
|
109
|
+
elsif @path&.include?("Gemfile") && node_message.include?("gem") && node.arguments.parts.any?
|
124
110
|
remote = resolve_gem_remote(node)
|
125
111
|
return unless remote
|
126
112
|
|
@@ -163,7 +149,7 @@ module RubyLsp
|
|
163
149
|
end
|
164
150
|
end
|
165
151
|
|
166
|
-
sig { params(other: Listener[ResponseType]).returns(T.self_type) }
|
152
|
+
sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
|
167
153
|
def merge_response!(other)
|
168
154
|
@response.concat(other.response)
|
169
155
|
self
|
@@ -174,7 +160,7 @@ module RubyLsp
|
|
174
160
|
sig { params(node: SyntaxTree::Node, name: String, command: String, kind: Symbol).void }
|
175
161
|
def add_test_code_lens(node, name:, command:, kind:)
|
176
162
|
# don't add code lenses if the test library is not supported or unknown
|
177
|
-
return unless SUPPORTED_TEST_LIBRARIES.include?(@test_library)
|
163
|
+
return unless SUPPORTED_TEST_LIBRARIES.include?(@test_library) && @path
|
178
164
|
|
179
165
|
arguments = [
|
180
166
|
@path,
|
@@ -231,7 +217,7 @@ module RubyLsp
|
|
231
217
|
|
232
218
|
sig { params(class_name: String, method_name: T.nilable(String)).returns(String) }
|
233
219
|
def generate_test_command(class_name:, method_name: nil)
|
234
|
-
command = BASE_COMMAND + @path
|
220
|
+
command = BASE_COMMAND + T.must(@path)
|
235
221
|
|
236
222
|
case @test_library
|
237
223
|
when "minitest"
|
@@ -20,18 +20,41 @@ module RubyLsp
|
|
20
20
|
extend T::Sig
|
21
21
|
extend T::Generic
|
22
22
|
|
23
|
-
ResponseType = type_member { { fixed: T.nilable(Interface::Location) } }
|
23
|
+
ResponseType = type_member { { fixed: T.nilable(T.any(T::Array[Interface::Location], Interface::Location)) } }
|
24
24
|
|
25
25
|
sig { override.returns(ResponseType) }
|
26
26
|
attr_reader :response
|
27
27
|
|
28
|
-
|
29
|
-
|
28
|
+
HAS_TYPECHECKER = T.let(DependencyDetector.typechecker?, T::Boolean)
|
29
|
+
|
30
|
+
sig do
|
31
|
+
params(
|
32
|
+
uri: URI::Generic,
|
33
|
+
nesting: T::Array[String],
|
34
|
+
index: RubyIndexer::Index,
|
35
|
+
emitter: EventEmitter,
|
36
|
+
message_queue: Thread::Queue,
|
37
|
+
).void
|
38
|
+
end
|
39
|
+
def initialize(uri, nesting, index, emitter, message_queue)
|
30
40
|
super(emitter, message_queue)
|
31
41
|
|
32
42
|
@uri = uri
|
43
|
+
@nesting = nesting
|
44
|
+
@index = index
|
33
45
|
@response = T.let(nil, ResponseType)
|
34
|
-
emitter.register(self, :on_command)
|
46
|
+
emitter.register(self, :on_command, :on_const, :on_const_path_ref)
|
47
|
+
end
|
48
|
+
|
49
|
+
sig { params(node: SyntaxTree::ConstPathRef).void }
|
50
|
+
def on_const_path_ref(node)
|
51
|
+
name = full_constant_name(node)
|
52
|
+
find_in_index(name)
|
53
|
+
end
|
54
|
+
|
55
|
+
sig { params(node: SyntaxTree::Const).void }
|
56
|
+
def on_const(node)
|
57
|
+
find_in_index(node.value)
|
35
58
|
end
|
36
59
|
|
37
60
|
sig { params(node: SyntaxTree::Command).void }
|
@@ -53,7 +76,7 @@ module RubyLsp
|
|
53
76
|
|
54
77
|
if candidate
|
55
78
|
@response = Interface::Location.new(
|
56
|
-
uri:
|
79
|
+
uri: URI::Generic.from_path(path: candidate).to_s,
|
57
80
|
range: Interface::Range.new(
|
58
81
|
start: Interface::Position.new(line: 0, character: 0),
|
59
82
|
end: Interface::Position.new(line: 0, character: 0),
|
@@ -61,13 +84,13 @@ module RubyLsp
|
|
61
84
|
)
|
62
85
|
end
|
63
86
|
when "require_relative"
|
64
|
-
|
65
|
-
current_folder = Pathname.new(
|
87
|
+
path = @uri.to_standardized_path
|
88
|
+
current_folder = path ? Pathname.new(CGI.unescape(path)).dirname : Dir.pwd
|
66
89
|
candidate = File.expand_path(File.join(current_folder, required_file))
|
67
90
|
|
68
91
|
if candidate
|
69
92
|
@response = Interface::Location.new(
|
70
|
-
uri:
|
93
|
+
uri: URI::Generic.from_path(path: candidate).to_s,
|
71
94
|
range: Interface::Range.new(
|
72
95
|
start: Interface::Position.new(line: 0, character: 0),
|
73
96
|
end: Interface::Position.new(line: 0, character: 0),
|
@@ -79,6 +102,30 @@ module RubyLsp
|
|
79
102
|
|
80
103
|
private
|
81
104
|
|
105
|
+
sig { params(value: String).void }
|
106
|
+
def find_in_index(value)
|
107
|
+
entries = @index.resolve(value, @nesting)
|
108
|
+
return unless entries
|
109
|
+
|
110
|
+
workspace_path = T.must(WORKSPACE_URI.to_standardized_path)
|
111
|
+
|
112
|
+
@response = entries.filter_map do |entry|
|
113
|
+
location = entry.location
|
114
|
+
# If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
|
115
|
+
# additional behavior on top of jumping to RBIs. Sorbet can already handle go to definition for all constants
|
116
|
+
# in the project, even if the files are typed false
|
117
|
+
next if HAS_TYPECHECKER && entry.file_path.start_with?(workspace_path)
|
118
|
+
|
119
|
+
Interface::Location.new(
|
120
|
+
uri: URI::Generic.from_path(path: entry.file_path).to_s,
|
121
|
+
range: Interface::Range.new(
|
122
|
+
start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
|
123
|
+
end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
|
124
|
+
),
|
125
|
+
)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
82
129
|
sig { params(file: String).returns(T.nilable(String)) }
|
83
130
|
def find_file_in_load_path(file)
|
84
131
|
return unless file.include?("/")
|
@@ -25,7 +25,7 @@ module RubyLsp
|
|
25
25
|
def initialize(document)
|
26
26
|
super(document)
|
27
27
|
|
28
|
-
@uri = T.let(document.uri,
|
28
|
+
@uri = T.let(document.uri, URI::Generic)
|
29
29
|
end
|
30
30
|
|
31
31
|
sig { override.returns(T.nilable(T.all(T::Array[Support::RuboCopDiagnostic], Object))) }
|
@@ -36,7 +36,8 @@ module RubyLsp
|
|
36
36
|
return unless defined?(Support::RuboCopDiagnosticsRunner)
|
37
37
|
|
38
38
|
# Don't try to run RuboCop diagnostics for files outside the current working directory
|
39
|
-
|
39
|
+
path = @uri.to_standardized_path
|
40
|
+
return unless path.nil? || path.start_with?(T.must(WORKSPACE_URI.to_standardized_path))
|
40
41
|
|
41
42
|
Support::RuboCopDiagnosticsRunner.instance.run(@uri, @document)
|
42
43
|
end
|
@@ -75,13 +75,14 @@ module RubyLsp
|
|
75
75
|
sig { override.returns(ResponseType) }
|
76
76
|
attr_reader :response
|
77
77
|
|
78
|
-
sig { params(uri:
|
78
|
+
sig { params(uri: URI::Generic, emitter: EventEmitter, message_queue: Thread::Queue).void }
|
79
79
|
def initialize(uri, emitter, message_queue)
|
80
80
|
super(emitter, message_queue)
|
81
81
|
|
82
82
|
# Match the version based on the version in the RBI file name. Notice that the `@` symbol is sanitized to `%40`
|
83
83
|
# in the URI
|
84
|
-
|
84
|
+
path = uri.to_standardized_path
|
85
|
+
version_match = path ? /(?<=%40)[\d.]+(?=\.rbi$)/.match(path) : nil
|
85
86
|
@gem_version = T.let(version_match && version_match[0], T.nilable(String))
|
86
87
|
@response = T.let([], T::Array[Interface::DocumentLink])
|
87
88
|
|
@@ -95,7 +96,7 @@ module RubyLsp
|
|
95
96
|
|
96
97
|
uri = T.cast(URI(T.must(match[0])), URI::Source)
|
97
98
|
gem_version = T.must(resolve_version(uri))
|
98
|
-
file_path = self.class.gem_paths.dig(uri.gem_name, gem_version, uri.path)
|
99
|
+
file_path = self.class.gem_paths.dig(uri.gem_name, gem_version, CGI.unescape(uri.path))
|
99
100
|
return if file_path.nil?
|
100
101
|
|
101
102
|
@response << Interface::DocumentLink.new(
|