ruby-lsp 0.14.6 → 0.16.4

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +1 -16
  5. data/exe/ruby-lsp-check +13 -22
  6. data/exe/ruby-lsp-doctor +9 -0
  7. data/lib/ruby_indexer/lib/ruby_indexer/collector.rb +14 -1
  8. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +11 -23
  9. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +4 -0
  10. data/lib/ruby_indexer/test/classes_and_modules_test.rb +46 -0
  11. data/lib/ruby_indexer/test/configuration_test.rb +2 -11
  12. data/lib/ruby_lsp/addon.rb +18 -9
  13. data/lib/ruby_lsp/base_server.rb +149 -0
  14. data/lib/ruby_lsp/document.rb +6 -11
  15. data/lib/ruby_lsp/global_state.rb +169 -0
  16. data/lib/ruby_lsp/internal.rb +4 -1
  17. data/lib/ruby_lsp/listeners/code_lens.rb +22 -13
  18. data/lib/ruby_lsp/listeners/completion.rb +13 -14
  19. data/lib/ruby_lsp/listeners/definition.rb +4 -3
  20. data/lib/ruby_lsp/listeners/document_symbol.rb +91 -3
  21. data/lib/ruby_lsp/listeners/hover.rb +6 -5
  22. data/lib/ruby_lsp/listeners/signature_help.rb +7 -4
  23. data/lib/ruby_lsp/load_sorbet.rb +62 -0
  24. data/lib/ruby_lsp/requests/code_lens.rb +3 -2
  25. data/lib/ruby_lsp/requests/completion.rb +15 -4
  26. data/lib/ruby_lsp/requests/completion_resolve.rb +56 -0
  27. data/lib/ruby_lsp/requests/definition.rb +11 -4
  28. data/lib/ruby_lsp/requests/diagnostics.rb +6 -12
  29. data/lib/ruby_lsp/requests/document_symbol.rb +3 -3
  30. data/lib/ruby_lsp/requests/formatting.rb +7 -43
  31. data/lib/ruby_lsp/requests/hover.rb +4 -4
  32. data/lib/ruby_lsp/requests/request.rb +2 -0
  33. data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -1
  34. data/lib/ruby_lsp/requests/signature_help.rb +4 -3
  35. data/lib/ruby_lsp/requests/support/common.rb +16 -5
  36. data/lib/ruby_lsp/requests/support/formatter.rb +26 -0
  37. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +1 -1
  38. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +47 -0
  39. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +4 -0
  40. data/lib/ruby_lsp/requests/support/{syntax_tree_formatting_runner.rb → syntax_tree_formatter.rb} +13 -6
  41. data/lib/ruby_lsp/requests/workspace_symbol.rb +5 -4
  42. data/lib/ruby_lsp/requests.rb +3 -1
  43. data/lib/ruby_lsp/server.rb +763 -142
  44. data/lib/ruby_lsp/setup_bundler.rb +13 -1
  45. data/lib/ruby_lsp/store.rb +3 -15
  46. data/lib/ruby_lsp/test_helper.rb +52 -0
  47. data/lib/ruby_lsp/utils.rb +68 -33
  48. metadata +11 -9
  49. data/lib/ruby_lsp/executor.rb +0 -614
  50. data/lib/ruby_lsp/requests/support/dependency_detector.rb +0 -93
  51. data/lib/ruby_lsp/requests/support/formatter_runner.rb +0 -18
  52. data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +0 -34
  53. data/lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb +0 -35
@@ -1,614 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- require "ruby_lsp/requests/support/dependency_detector"
5
-
6
- module RubyLsp
7
- # This class dispatches a request execution to the right request class. No IO should happen anywhere here!
8
- class Executor
9
- extend T::Sig
10
-
11
- sig { params(store: Store, message_queue: Thread::Queue).void }
12
- def initialize(store, message_queue)
13
- # Requests that mutate the store must be run sequentially! Parallel requests only receive a temporary copy of the
14
- # store
15
- @store = store
16
- @message_queue = message_queue
17
- @index = T.let(RubyIndexer::Index.new, RubyIndexer::Index)
18
- end
19
-
20
- sig { params(request: T::Hash[Symbol, T.untyped]).returns(Result) }
21
- def execute(request)
22
- response = T.let(nil, T.untyped)
23
- error = T.let(nil, T.nilable(Exception))
24
-
25
- begin
26
- response = run(request)
27
- rescue StandardError, LoadError => e
28
- $stderr.puts(e.full_message)
29
- error = e
30
- end
31
-
32
- Result.new(response: response, error: error)
33
- end
34
-
35
- private
36
-
37
- sig { params(request: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
38
- def run(request)
39
- uri = URI(request.dig(:params, :textDocument, :uri).to_s)
40
-
41
- case request[:method]
42
- when "initialize"
43
- initialize_request(request.dig(:params))
44
- when "initialized"
45
- Addon.load_addons(@message_queue)
46
-
47
- errored_addons = Addon.addons.select(&:error?)
48
-
49
- if errored_addons.any?
50
- @message_queue << Notification.new(
51
- message: "window/showMessage",
52
- params: Interface::ShowMessageParams.new(
53
- type: Constant::MessageType::WARNING,
54
- message: "Error loading addons:\n\n#{errored_addons.map(&:formatted_errors).join("\n\n")}",
55
- ),
56
- )
57
-
58
- $stderr.puts(errored_addons.map(&:backtraces).join("\n\n"))
59
- end
60
-
61
- RubyVM::YJIT.enable if defined? RubyVM::YJIT.enable
62
-
63
- perform_initial_indexing
64
- check_formatter_is_available
65
-
66
- $stderr.puts("Ruby LSP is ready")
67
- VOID
68
- when "textDocument/didOpen"
69
- text_document_did_open(
70
- uri,
71
- request.dig(:params, :textDocument, :text),
72
- request.dig(:params, :textDocument, :version),
73
- )
74
- when "textDocument/didClose"
75
- @message_queue << Notification.new(
76
- message: "textDocument/publishDiagnostics",
77
- params: Interface::PublishDiagnosticsParams.new(uri: uri.to_s, diagnostics: []),
78
- )
79
-
80
- text_document_did_close(uri)
81
- when "textDocument/didChange"
82
- text_document_did_change(
83
- uri,
84
- request.dig(:params, :contentChanges),
85
- request.dig(:params, :textDocument, :version),
86
- )
87
- when "textDocument/selectionRange"
88
- selection_range(uri, request.dig(:params, :positions))
89
- when "textDocument/documentSymbol", "textDocument/documentLink", "textDocument/codeLens",
90
- "textDocument/semanticTokens/full", "textDocument/foldingRange"
91
- document = @store.get(uri)
92
-
93
- # If the response has already been cached by another request, return it
94
- cached_response = document.cache_get(request[:method])
95
- return cached_response if cached_response
96
-
97
- # Run requests for the document
98
- dispatcher = Prism::Dispatcher.new
99
- folding_range = Requests::FoldingRanges.new(document.parse_result.comments, dispatcher)
100
- document_symbol = Requests::DocumentSymbol.new(dispatcher)
101
- document_link = Requests::DocumentLink.new(uri, document.comments, dispatcher)
102
- code_lens = Requests::CodeLens.new(uri, dispatcher)
103
-
104
- semantic_highlighting = Requests::SemanticHighlighting.new(dispatcher)
105
- dispatcher.dispatch(document.tree)
106
-
107
- # Store all responses retrieve in this round of visits in the cache and then return the response for the request
108
- # we actually received
109
- document.cache_set("textDocument/foldingRange", folding_range.perform)
110
- document.cache_set("textDocument/documentSymbol", document_symbol.perform)
111
- document.cache_set("textDocument/documentLink", document_link.perform)
112
- document.cache_set("textDocument/codeLens", code_lens.perform)
113
- document.cache_set(
114
- "textDocument/semanticTokens/full",
115
- semantic_highlighting.perform,
116
- )
117
- document.cache_get(request[:method])
118
- when "textDocument/semanticTokens/range"
119
- semantic_tokens_range(uri, request.dig(:params, :range))
120
- when "textDocument/formatting"
121
- begin
122
- formatting(uri)
123
- rescue Requests::Formatting::InvalidFormatter => error
124
- @message_queue << Notification.window_show_error("Configuration error: #{error.message}")
125
- nil
126
- rescue StandardError, LoadError => error
127
- @message_queue << Notification.window_show_error("Formatting error: #{error.message}")
128
- nil
129
- end
130
- when "textDocument/documentHighlight"
131
- dispatcher = Prism::Dispatcher.new
132
- document = @store.get(uri)
133
- request = Requests::DocumentHighlight.new(document, request.dig(:params, :position), dispatcher)
134
- dispatcher.dispatch(document.tree)
135
- request.perform
136
- when "textDocument/onTypeFormatting"
137
- on_type_formatting(uri, request.dig(:params, :position), request.dig(:params, :ch))
138
- when "textDocument/hover"
139
- dispatcher = Prism::Dispatcher.new
140
- document = @store.get(uri)
141
- Requests::Hover.new(
142
- document,
143
- @index,
144
- request.dig(:params, :position),
145
- dispatcher,
146
- document.typechecker_enabled?,
147
- ).perform
148
- when "textDocument/inlayHint"
149
- hints_configurations = T.must(@store.features_configuration.dig(:inlayHint))
150
- dispatcher = Prism::Dispatcher.new
151
- document = @store.get(uri)
152
- request = Requests::InlayHints.new(document, request.dig(:params, :range), hints_configurations, dispatcher)
153
- dispatcher.visit(document.tree)
154
- request.perform
155
- when "textDocument/codeAction"
156
- code_action(uri, request.dig(:params, :range), request.dig(:params, :context))
157
- when "codeAction/resolve"
158
- code_action_resolve(request.dig(:params))
159
- when "textDocument/diagnostic"
160
- begin
161
- diagnostic(uri)
162
- rescue StandardError, LoadError => error
163
- @message_queue << Notification.window_show_error("Error running diagnostics: #{error.message}")
164
- nil
165
- end
166
- when "textDocument/completion"
167
- dispatcher = Prism::Dispatcher.new
168
- document = @store.get(uri)
169
- Requests::Completion.new(
170
- document,
171
- @index,
172
- request.dig(:params, :position),
173
- document.typechecker_enabled?,
174
- dispatcher,
175
- ).perform
176
- when "textDocument/signatureHelp"
177
- dispatcher = Prism::Dispatcher.new
178
- document = @store.get(uri)
179
-
180
- Requests::SignatureHelp.new(
181
- document,
182
- @index,
183
- request.dig(:params, :position),
184
- request.dig(:params, :context),
185
- dispatcher,
186
- ).perform
187
- when "textDocument/definition"
188
- dispatcher = Prism::Dispatcher.new
189
- document = @store.get(uri)
190
- Requests::Definition.new(
191
- document,
192
- @index,
193
- request.dig(:params, :position),
194
- dispatcher,
195
- document.typechecker_enabled?,
196
- ).perform
197
- when "workspace/didChangeWatchedFiles"
198
- did_change_watched_files(request.dig(:params, :changes))
199
- when "workspace/symbol"
200
- workspace_symbol(request.dig(:params, :query))
201
- when "rubyLsp/textDocument/showSyntaxTree"
202
- show_syntax_tree(uri, request.dig(:params, :range))
203
- when "rubyLsp/workspace/dependencies"
204
- workspace_dependencies
205
- else
206
- VOID
207
- end
208
- end
209
-
210
- sig { params(changes: T::Array[{ uri: String, type: Integer }]).returns(Object) }
211
- def did_change_watched_files(changes)
212
- changes.each do |change|
213
- # File change events include folders, but we're only interested in files
214
- uri = URI(change[:uri])
215
- file_path = uri.to_standardized_path
216
- next if file_path.nil? || File.directory?(file_path)
217
-
218
- load_path_entry = $LOAD_PATH.find { |load_path| file_path.start_with?(load_path) }
219
- indexable = RubyIndexer::IndexablePath.new(load_path_entry, file_path)
220
-
221
- case change[:type]
222
- when Constant::FileChangeType::CREATED
223
- @index.index_single(indexable)
224
- when Constant::FileChangeType::CHANGED
225
- @index.delete(indexable)
226
- @index.index_single(indexable)
227
- when Constant::FileChangeType::DELETED
228
- @index.delete(indexable)
229
- end
230
- end
231
-
232
- Addon.file_watcher_addons.each { |addon| T.unsafe(addon).workspace_did_change_watched_files(changes) }
233
- VOID
234
- end
235
-
236
- sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
237
- def workspace_dependencies
238
- Bundler.with_original_env do
239
- definition = Bundler.definition
240
- dep_keys = definition.locked_deps.keys.to_set
241
- definition.specs.map do |spec|
242
- {
243
- name: spec.name,
244
- version: spec.version,
245
- path: spec.full_gem_path,
246
- dependency: dep_keys.include?(spec.name),
247
- }
248
- end
249
- end
250
- rescue Bundler::GemfileNotFound
251
- []
252
- end
253
-
254
- sig { void }
255
- def perform_initial_indexing
256
- # The begin progress invocation happens during `initialize`, so that the notification is sent before we are
257
- # stuck indexing files
258
- RubyIndexer.configuration.load_config
259
-
260
- Thread.new do
261
- begin
262
- @index.index_all do |percentage|
263
- progress("indexing-progress", percentage)
264
- true
265
- rescue ClosedQueueError
266
- # Since we run indexing on a separate thread, it's possible to kill the server before indexing is complete.
267
- # In those cases, the message queue will be closed and raise a ClosedQueueError. By returning `false`, we
268
- # tell the index to stop working immediately
269
- false
270
- end
271
- rescue StandardError => error
272
- @message_queue << Notification.window_show_error("Error while indexing: #{error.message}")
273
- end
274
-
275
- # Always end the progress notification even if indexing failed or else it never goes away and the user has no
276
- # way of dismissing it
277
- end_progress("indexing-progress")
278
- end
279
- end
280
-
281
- sig { params(query: T.nilable(String)).returns(T::Array[Interface::WorkspaceSymbol]) }
282
- def workspace_symbol(query)
283
- Requests::WorkspaceSymbol.new(query, @index).perform
284
- end
285
-
286
- sig { params(uri: URI::Generic, range: T.nilable(T::Hash[Symbol, T.untyped])).returns({ ast: String }) }
287
- def show_syntax_tree(uri, range)
288
- { ast: Requests::ShowSyntaxTree.new(@store.get(uri), range).perform }
289
- end
290
-
291
- sig do
292
- params(uri: URI::Generic, content_changes: T::Array[T::Hash[Symbol, T.untyped]], version: Integer).returns(Object)
293
- end
294
- def text_document_did_change(uri, content_changes, version)
295
- @store.push_edits(uri: uri, edits: content_changes, version: version)
296
- VOID
297
- end
298
-
299
- sig { params(uri: URI::Generic, text: String, version: Integer).returns(Object) }
300
- def text_document_did_open(uri, text, version)
301
- @store.set(uri: uri, source: text, version: version)
302
- VOID
303
- end
304
-
305
- sig { params(uri: URI::Generic).returns(Object) }
306
- def text_document_did_close(uri)
307
- @store.delete(uri)
308
- VOID
309
- end
310
-
311
- sig do
312
- params(
313
- uri: URI::Generic,
314
- positions: T::Array[T::Hash[Symbol, T.untyped]],
315
- ).returns(T.nilable(T::Array[T.nilable(Requests::Support::SelectionRange)]))
316
- end
317
- def selection_range(uri, positions)
318
- ranges = @store.cache_fetch(uri, "textDocument/selectionRange") do |document|
319
- Requests::SelectionRanges.new(document).perform
320
- end
321
-
322
- # Per the selection range request spec (https://microsoft.github.io/language-server-protocol/specification#textDocument_selectionRange),
323
- # every position in the positions array should have an element at the same index in the response
324
- # array. For positions without a valid selection range, the corresponding element in the response
325
- # array will be nil.
326
-
327
- unless ranges.nil?
328
- positions.map do |position|
329
- ranges.find do |range|
330
- range.cover?(position)
331
- end
332
- end
333
- end
334
- end
335
-
336
- sig { params(uri: URI::Generic).returns(T.nilable(T::Array[Interface::TextEdit])) }
337
- def formatting(uri)
338
- # If formatter is set to `auto` but no supported formatting gem is found, don't attempt to format
339
- return if @store.formatter == "none"
340
-
341
- # Do not format files outside of the workspace. For example, if someone is looking at a gem's source code, we
342
- # don't want to format it
343
- path = uri.to_standardized_path
344
- return unless path.nil? || path.start_with?(T.must(@store.workspace_uri.to_standardized_path))
345
-
346
- Requests::Formatting.new(@store.get(uri), formatter: @store.formatter).perform
347
- end
348
-
349
- sig do
350
- params(
351
- uri: URI::Generic,
352
- position: T::Hash[Symbol, T.untyped],
353
- character: String,
354
- ).returns(T::Array[Interface::TextEdit])
355
- end
356
- def on_type_formatting(uri, position, character)
357
- Requests::OnTypeFormatting.new(@store.get(uri), position, character, @store.client_name).perform
358
- end
359
-
360
- sig do
361
- params(
362
- uri: URI::Generic,
363
- range: T::Hash[Symbol, T.untyped],
364
- context: T::Hash[Symbol, T.untyped],
365
- ).returns(T.nilable(T::Array[Interface::CodeAction]))
366
- end
367
- def code_action(uri, range, context)
368
- document = @store.get(uri)
369
-
370
- Requests::CodeActions.new(document, range, context).perform
371
- end
372
-
373
- sig { params(params: T::Hash[Symbol, T.untyped]).returns(Interface::CodeAction) }
374
- def code_action_resolve(params)
375
- uri = URI(params.dig(:data, :uri))
376
- document = @store.get(uri)
377
- result = Requests::CodeActionResolve.new(document, params).perform
378
-
379
- case result
380
- when Requests::CodeActionResolve::Error::EmptySelection
381
- @message_queue << Notification.window_show_error("Invalid selection for Extract Variable refactor")
382
- raise Requests::CodeActionResolve::CodeActionError
383
- when Requests::CodeActionResolve::Error::InvalidTargetRange
384
- @message_queue << Notification.window_show_error(
385
- "Couldn't find an appropriate location to place extracted refactor",
386
- )
387
- raise Requests::CodeActionResolve::CodeActionError
388
- else
389
- result
390
- end
391
- end
392
-
393
- sig { params(uri: URI::Generic).returns(T.nilable(Interface::FullDocumentDiagnosticReport)) }
394
- def diagnostic(uri)
395
- # Do not compute diagnostics for files outside of the workspace. For example, if someone is looking at a gem's
396
- # source code, we don't want to show diagnostics for it
397
- path = uri.to_standardized_path
398
- return unless path.nil? || path.start_with?(T.must(@store.workspace_uri.to_standardized_path))
399
-
400
- response = @store.cache_fetch(uri, "textDocument/diagnostic") do |document|
401
- Requests::Diagnostics.new(document).perform
402
- end
403
-
404
- Interface::FullDocumentDiagnosticReport.new(kind: "full", items: response) if response
405
- end
406
-
407
- sig { params(uri: URI::Generic, range: T::Hash[Symbol, T.untyped]).returns(Interface::SemanticTokens) }
408
- def semantic_tokens_range(uri, range)
409
- document = @store.get(uri)
410
- start_line = range.dig(:start, :line)
411
- end_line = range.dig(:end, :line)
412
-
413
- dispatcher = Prism::Dispatcher.new
414
- request = Requests::SemanticHighlighting.new(dispatcher, range: start_line..end_line)
415
- dispatcher.visit(document.tree)
416
-
417
- request.perform
418
- end
419
-
420
- sig { params(id: String, title: String, percentage: Integer).void }
421
- def begin_progress(id, title, percentage: 0)
422
- return unless @store.supports_progress
423
-
424
- @message_queue << Request.new(
425
- message: "window/workDoneProgress/create",
426
- params: Interface::WorkDoneProgressCreateParams.new(token: id),
427
- )
428
-
429
- @message_queue << Notification.new(
430
- message: "$/progress",
431
- params: Interface::ProgressParams.new(
432
- token: id,
433
- value: Interface::WorkDoneProgressBegin.new(
434
- kind: "begin",
435
- title: title,
436
- percentage: percentage,
437
- message: "#{percentage}% completed",
438
- ),
439
- ),
440
- )
441
- end
442
-
443
- sig { params(id: String, percentage: Integer).void }
444
- def progress(id, percentage)
445
- return unless @store.supports_progress
446
-
447
- @message_queue << Notification.new(
448
- message: "$/progress",
449
- params: Interface::ProgressParams.new(
450
- token: id,
451
- value: Interface::WorkDoneProgressReport.new(
452
- kind: "report",
453
- percentage: percentage,
454
- message: "#{percentage}% completed",
455
- ),
456
- ),
457
- )
458
- end
459
-
460
- sig { params(id: String).void }
461
- def end_progress(id)
462
- return unless @store.supports_progress
463
-
464
- @message_queue << Notification.new(
465
- message: "$/progress",
466
- params: Interface::ProgressParams.new(
467
- token: id,
468
- value: Interface::WorkDoneProgressEnd.new(kind: "end"),
469
- ),
470
- )
471
- rescue ClosedQueueError
472
- # If the server was killed and the message queue is already closed, there's no way to end the progress
473
- # notification
474
- end
475
-
476
- sig { params(options: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
477
- def initialize_request(options)
478
- @store.clear
479
-
480
- workspace_uri = options.dig(:workspaceFolders, 0, :uri)
481
- @store.workspace_uri = URI(workspace_uri) if workspace_uri
482
-
483
- client_name = options.dig(:clientInfo, :name)
484
- @store.client_name = client_name if client_name
485
-
486
- encodings = options.dig(:capabilities, :general, :positionEncodings)
487
- @store.encoding = if encodings.nil? || encodings.empty?
488
- Constant::PositionEncodingKind::UTF16
489
- elsif encodings.include?(Constant::PositionEncodingKind::UTF8)
490
- Constant::PositionEncodingKind::UTF8
491
- else
492
- encodings.first
493
- end
494
-
495
- progress = options.dig(:capabilities, :window, :workDoneProgress)
496
- @store.supports_progress = progress.nil? ? true : progress
497
- formatter = options.dig(:initializationOptions, :formatter) || "auto"
498
- @store.formatter = if formatter == "auto"
499
- DependencyDetector.instance.detected_formatter
500
- else
501
- formatter
502
- end
503
-
504
- configured_features = options.dig(:initializationOptions, :enabledFeatures)
505
- @store.experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
506
-
507
- configured_hints = options.dig(:initializationOptions, :featuresConfiguration, :inlayHint)
508
- T.must(@store.features_configuration.dig(:inlayHint)).configuration.merge!(configured_hints) if configured_hints
509
-
510
- enabled_features = case configured_features
511
- when Array
512
- # If the configuration is using an array, then absent features are disabled and present ones are enabled. That's
513
- # why we use `false` as the default value
514
- Hash.new(false).merge!(configured_features.to_h { |feature| [feature, true] })
515
- when Hash
516
- # If the configuration is already a hash, merge it with a default value of `true`. That way clients don't have
517
- # to opt-in to every single feature
518
- Hash.new(true).merge!(configured_features)
519
- else
520
- # If no configuration was passed by the client, just enable every feature
521
- Hash.new(true)
522
- end
523
-
524
- document_symbol_provider = Requests::DocumentSymbol.provider if enabled_features["documentSymbols"]
525
- document_link_provider = Requests::DocumentLink.provider if enabled_features["documentLink"]
526
- code_lens_provider = Requests::CodeLens.provider if enabled_features["codeLens"]
527
- hover_provider = Requests::Hover.provider if enabled_features["hover"]
528
- folding_ranges_provider = Requests::FoldingRanges.provider if enabled_features["foldingRanges"]
529
- semantic_tokens_provider = Requests::SemanticHighlighting.provider if enabled_features["semanticHighlighting"]
530
- diagnostics_provider = Requests::Diagnostics.provider if enabled_features["diagnostics"]
531
- on_type_formatting_provider = Requests::OnTypeFormatting.provider if enabled_features["onTypeFormatting"]
532
- code_action_provider = Requests::CodeActions.provider if enabled_features["codeActions"]
533
- inlay_hint_provider = Requests::InlayHints.provider if enabled_features["inlayHint"]
534
- completion_provider = Requests::Completion.provider if enabled_features["completion"]
535
- signature_help_provider = Requests::SignatureHelp.provider if enabled_features["signatureHelp"]
536
-
537
- # Dynamically registered capabilities
538
- file_watching_caps = options.dig(:capabilities, :workspace, :didChangeWatchedFiles)
539
-
540
- # Not every client supports dynamic registration or file watching
541
- if file_watching_caps&.dig(:dynamicRegistration) && file_watching_caps&.dig(:relativePatternSupport)
542
- @message_queue << Request.new(
543
- message: "client/registerCapability",
544
- params: Interface::RegistrationParams.new(
545
- registrations: [
546
- # Register watching Ruby files
547
- Interface::Registration.new(
548
- id: "workspace/didChangeWatchedFiles",
549
- method: "workspace/didChangeWatchedFiles",
550
- register_options: Interface::DidChangeWatchedFilesRegistrationOptions.new(
551
- watchers: [
552
- Interface::FileSystemWatcher.new(
553
- glob_pattern: "**/*.rb",
554
- kind: Constant::WatchKind::CREATE | Constant::WatchKind::CHANGE | Constant::WatchKind::DELETE,
555
- ),
556
- ],
557
- ),
558
- ),
559
- ],
560
- ),
561
- )
562
- end
563
-
564
- begin_progress("indexing-progress", "Ruby LSP: indexing files")
565
-
566
- {
567
- capabilities: Interface::ServerCapabilities.new(
568
- text_document_sync: Interface::TextDocumentSyncOptions.new(
569
- change: Constant::TextDocumentSyncKind::INCREMENTAL,
570
- open_close: true,
571
- ),
572
- position_encoding: @store.encoding,
573
- selection_range_provider: enabled_features["selectionRanges"],
574
- hover_provider: hover_provider,
575
- document_symbol_provider: document_symbol_provider,
576
- document_link_provider: document_link_provider,
577
- folding_range_provider: folding_ranges_provider,
578
- semantic_tokens_provider: semantic_tokens_provider,
579
- document_formatting_provider: enabled_features["formatting"] && formatter != "none",
580
- document_highlight_provider: enabled_features["documentHighlights"],
581
- code_action_provider: code_action_provider,
582
- document_on_type_formatting_provider: on_type_formatting_provider,
583
- diagnostic_provider: diagnostics_provider,
584
- inlay_hint_provider: inlay_hint_provider,
585
- completion_provider: completion_provider,
586
- code_lens_provider: code_lens_provider,
587
- definition_provider: enabled_features["definition"],
588
- workspace_symbol_provider: enabled_features["workspaceSymbol"],
589
- signature_help_provider: signature_help_provider,
590
- ),
591
- serverInfo: {
592
- name: "Ruby LSP",
593
- version: VERSION,
594
- },
595
- formatter: @store.formatter,
596
- }
597
- end
598
-
599
- sig { void }
600
- def check_formatter_is_available
601
- # Warn of an unavailable `formatter` setting, e.g. `rubocop` on a project which doesn't have RuboCop.
602
- # Syntax Tree will always be available via Ruby LSP so we don't need to check for it.
603
- return unless @store.formatter == "rubocop"
604
-
605
- unless defined?(RubyLsp::Requests::Support::RuboCopRunner)
606
- @store.formatter = "none"
607
-
608
- @message_queue << Notification.window_show_error(
609
- "Ruby LSP formatter is set to `rubocop` but RuboCop was not found in the Gemfile or gemspec.",
610
- )
611
- end
612
- end
613
- end
614
- end
@@ -1,93 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- require "singleton"
5
-
6
- module RubyLsp
7
- class DependencyDetector
8
- include Singleton
9
- extend T::Sig
10
-
11
- sig { returns(String) }
12
- attr_reader :detected_formatter
13
-
14
- sig { returns(String) }
15
- attr_reader :detected_test_library
16
-
17
- sig { returns(T::Boolean) }
18
- attr_reader :typechecker
19
-
20
- sig { void }
21
- def initialize
22
- @detected_formatter = T.let(detect_formatter, String)
23
- @detected_test_library = T.let(detect_test_library, String)
24
- @typechecker = T.let(detect_typechecker, T::Boolean)
25
- end
26
-
27
- sig { returns(String) }
28
- def detect_formatter
29
- # NOTE: Intentionally no $ at end, since we want to match rubocop-shopify, etc.
30
- if direct_dependency?(/^rubocop/)
31
- "rubocop"
32
- elsif direct_dependency?(/^syntax_tree$/)
33
- "syntax_tree"
34
- else
35
- "none"
36
- end
37
- end
38
-
39
- sig { returns(String) }
40
- def detect_test_library
41
- # A Rails app may have a dependency on minitest, but we would instead want to use the Rails test runner provided
42
- # by ruby-lsp-rails.
43
- if direct_dependency?(/^rails$/)
44
- "rails"
45
- # NOTE: Intentionally ends with $ to avoid mis-matching minitest-reporters, etc. in a Rails app.
46
- elsif direct_dependency?(/^minitest$/)
47
- "minitest"
48
- elsif direct_dependency?(/^test-unit/)
49
- "test-unit"
50
- elsif direct_dependency?(/^rspec/)
51
- "rspec"
52
- else
53
- "unknown"
54
- end
55
- end
56
-
57
- sig { params(gem_pattern: Regexp).returns(T::Boolean) }
58
- def direct_dependency?(gem_pattern)
59
- dependencies.any?(gem_pattern)
60
- end
61
-
62
- sig { returns(T::Boolean) }
63
- def detect_typechecker
64
- return false if ENV["RUBY_LSP_BYPASS_TYPECHECKER"]
65
-
66
- Bundler.with_original_env do
67
- Bundler.locked_gems.specs.any? { |spec| spec.name == "sorbet-static" }
68
- end
69
- rescue Bundler::GemfileNotFound
70
- false
71
- end
72
-
73
- sig { returns(T::Array[String]) }
74
- def dependencies
75
- @dependencies ||= T.let(
76
- begin
77
- Bundler.with_original_env { Bundler.default_gemfile }
78
- Bundler.locked_gems.dependencies.keys + gemspec_dependencies
79
- rescue Bundler::GemfileNotFound
80
- []
81
- end,
82
- T.nilable(T::Array[String]),
83
- )
84
- end
85
-
86
- sig { returns(T::Array[String]) }
87
- def gemspec_dependencies
88
- Bundler.locked_gems.sources
89
- .grep(Bundler::Source::Gemspec)
90
- .flat_map { _1.gemspec&.dependencies&.map(&:name) }
91
- end
92
- end
93
- end