ruby-lsp 0.14.5 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +1 -16
  4. data/exe/ruby-lsp-check +13 -22
  5. data/lib/ruby_indexer/lib/ruby_indexer/collector.rb +21 -0
  6. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +12 -24
  7. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +16 -0
  8. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +3 -2
  9. data/lib/ruby_indexer/test/classes_and_modules_test.rb +46 -0
  10. data/lib/ruby_indexer/test/configuration_test.rb +2 -11
  11. data/lib/ruby_indexer/test/index_test.rb +5 -0
  12. data/lib/ruby_lsp/addon.rb +18 -5
  13. data/lib/ruby_lsp/base_server.rb +147 -0
  14. data/lib/ruby_lsp/document.rb +0 -5
  15. data/lib/ruby_lsp/{requests/support/dependency_detector.rb → global_state.rb} +30 -9
  16. data/lib/ruby_lsp/internal.rb +2 -1
  17. data/lib/ruby_lsp/listeners/code_lens.rb +66 -18
  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 +4 -3
  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 +18 -5
  28. data/lib/ruby_lsp/requests/document_symbol.rb +3 -3
  29. data/lib/ruby_lsp/requests/hover.rb +9 -5
  30. data/lib/ruby_lsp/requests/request.rb +51 -0
  31. data/lib/ruby_lsp/requests/signature_help.rb +4 -3
  32. data/lib/ruby_lsp/requests/support/common.rb +25 -5
  33. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +4 -0
  34. data/lib/ruby_lsp/requests/workspace_symbol.rb +5 -4
  35. data/lib/ruby_lsp/requests.rb +2 -0
  36. data/lib/ruby_lsp/server.rb +756 -142
  37. data/lib/ruby_lsp/store.rb +0 -8
  38. data/lib/ruby_lsp/test_helper.rb +45 -0
  39. data/lib/ruby_lsp/utils.rb +68 -33
  40. metadata +8 -5
  41. data/lib/ruby_lsp/executor.rb +0 -612
@@ -1,612 +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
- definition = Bundler.definition
239
- dep_keys = definition.locked_deps.keys.to_set
240
- definition.specs.map do |spec|
241
- {
242
- name: spec.name,
243
- version: spec.version,
244
- path: spec.full_gem_path,
245
- dependency: dep_keys.include?(spec.name),
246
- }
247
- end
248
- rescue Bundler::GemfileNotFound
249
- []
250
- end
251
-
252
- sig { void }
253
- def perform_initial_indexing
254
- # The begin progress invocation happens during `initialize`, so that the notification is sent before we are
255
- # stuck indexing files
256
- RubyIndexer.configuration.load_config
257
-
258
- Thread.new do
259
- begin
260
- @index.index_all do |percentage|
261
- progress("indexing-progress", percentage)
262
- true
263
- rescue ClosedQueueError
264
- # Since we run indexing on a separate thread, it's possible to kill the server before indexing is complete.
265
- # In those cases, the message queue will be closed and raise a ClosedQueueError. By returning `false`, we
266
- # tell the index to stop working immediately
267
- false
268
- end
269
- rescue StandardError => error
270
- @message_queue << Notification.window_show_error("Error while indexing: #{error.message}")
271
- end
272
-
273
- # Always end the progress notification even if indexing failed or else it never goes away and the user has no
274
- # way of dismissing it
275
- end_progress("indexing-progress")
276
- end
277
- end
278
-
279
- sig { params(query: T.nilable(String)).returns(T::Array[Interface::WorkspaceSymbol]) }
280
- def workspace_symbol(query)
281
- Requests::WorkspaceSymbol.new(query, @index).perform
282
- end
283
-
284
- sig { params(uri: URI::Generic, range: T.nilable(T::Hash[Symbol, T.untyped])).returns({ ast: String }) }
285
- def show_syntax_tree(uri, range)
286
- { ast: Requests::ShowSyntaxTree.new(@store.get(uri), range).perform }
287
- end
288
-
289
- sig do
290
- params(uri: URI::Generic, content_changes: T::Array[T::Hash[Symbol, T.untyped]], version: Integer).returns(Object)
291
- end
292
- def text_document_did_change(uri, content_changes, version)
293
- @store.push_edits(uri: uri, edits: content_changes, version: version)
294
- VOID
295
- end
296
-
297
- sig { params(uri: URI::Generic, text: String, version: Integer).returns(Object) }
298
- def text_document_did_open(uri, text, version)
299
- @store.set(uri: uri, source: text, version: version)
300
- VOID
301
- end
302
-
303
- sig { params(uri: URI::Generic).returns(Object) }
304
- def text_document_did_close(uri)
305
- @store.delete(uri)
306
- VOID
307
- end
308
-
309
- sig do
310
- params(
311
- uri: URI::Generic,
312
- positions: T::Array[T::Hash[Symbol, T.untyped]],
313
- ).returns(T.nilable(T::Array[T.nilable(Requests::Support::SelectionRange)]))
314
- end
315
- def selection_range(uri, positions)
316
- ranges = @store.cache_fetch(uri, "textDocument/selectionRange") do |document|
317
- Requests::SelectionRanges.new(document).perform
318
- end
319
-
320
- # Per the selection range request spec (https://microsoft.github.io/language-server-protocol/specification#textDocument_selectionRange),
321
- # every position in the positions array should have an element at the same index in the response
322
- # array. For positions without a valid selection range, the corresponding element in the response
323
- # array will be nil.
324
-
325
- unless ranges.nil?
326
- positions.map do |position|
327
- ranges.find do |range|
328
- range.cover?(position)
329
- end
330
- end
331
- end
332
- end
333
-
334
- sig { params(uri: URI::Generic).returns(T.nilable(T::Array[Interface::TextEdit])) }
335
- def formatting(uri)
336
- # If formatter is set to `auto` but no supported formatting gem is found, don't attempt to format
337
- return if @store.formatter == "none"
338
-
339
- # Do not format files outside of the workspace. For example, if someone is looking at a gem's source code, we
340
- # don't want to format it
341
- path = uri.to_standardized_path
342
- return unless path.nil? || path.start_with?(T.must(@store.workspace_uri.to_standardized_path))
343
-
344
- Requests::Formatting.new(@store.get(uri), formatter: @store.formatter).perform
345
- end
346
-
347
- sig do
348
- params(
349
- uri: URI::Generic,
350
- position: T::Hash[Symbol, T.untyped],
351
- character: String,
352
- ).returns(T::Array[Interface::TextEdit])
353
- end
354
- def on_type_formatting(uri, position, character)
355
- Requests::OnTypeFormatting.new(@store.get(uri), position, character, @store.client_name).perform
356
- end
357
-
358
- sig do
359
- params(
360
- uri: URI::Generic,
361
- range: T::Hash[Symbol, T.untyped],
362
- context: T::Hash[Symbol, T.untyped],
363
- ).returns(T.nilable(T::Array[Interface::CodeAction]))
364
- end
365
- def code_action(uri, range, context)
366
- document = @store.get(uri)
367
-
368
- Requests::CodeActions.new(document, range, context).perform
369
- end
370
-
371
- sig { params(params: T::Hash[Symbol, T.untyped]).returns(Interface::CodeAction) }
372
- def code_action_resolve(params)
373
- uri = URI(params.dig(:data, :uri))
374
- document = @store.get(uri)
375
- result = Requests::CodeActionResolve.new(document, params).perform
376
-
377
- case result
378
- when Requests::CodeActionResolve::Error::EmptySelection
379
- @message_queue << Notification.window_show_error("Invalid selection for Extract Variable refactor")
380
- raise Requests::CodeActionResolve::CodeActionError
381
- when Requests::CodeActionResolve::Error::InvalidTargetRange
382
- @message_queue << Notification.window_show_error(
383
- "Couldn't find an appropriate location to place extracted refactor",
384
- )
385
- raise Requests::CodeActionResolve::CodeActionError
386
- else
387
- result
388
- end
389
- end
390
-
391
- sig { params(uri: URI::Generic).returns(T.nilable(Interface::FullDocumentDiagnosticReport)) }
392
- def diagnostic(uri)
393
- # Do not compute diagnostics for files outside of the workspace. For example, if someone is looking at a gem's
394
- # source code, we don't want to show diagnostics for it
395
- path = uri.to_standardized_path
396
- return unless path.nil? || path.start_with?(T.must(@store.workspace_uri.to_standardized_path))
397
-
398
- response = @store.cache_fetch(uri, "textDocument/diagnostic") do |document|
399
- Requests::Diagnostics.new(document).perform
400
- end
401
-
402
- Interface::FullDocumentDiagnosticReport.new(kind: "full", items: response) if response
403
- end
404
-
405
- sig { params(uri: URI::Generic, range: T::Hash[Symbol, T.untyped]).returns(Interface::SemanticTokens) }
406
- def semantic_tokens_range(uri, range)
407
- document = @store.get(uri)
408
- start_line = range.dig(:start, :line)
409
- end_line = range.dig(:end, :line)
410
-
411
- dispatcher = Prism::Dispatcher.new
412
- request = Requests::SemanticHighlighting.new(dispatcher, range: start_line..end_line)
413
- dispatcher.visit(document.tree)
414
-
415
- request.perform
416
- end
417
-
418
- sig { params(id: String, title: String, percentage: Integer).void }
419
- def begin_progress(id, title, percentage: 0)
420
- return unless @store.supports_progress
421
-
422
- @message_queue << Request.new(
423
- message: "window/workDoneProgress/create",
424
- params: Interface::WorkDoneProgressCreateParams.new(token: id),
425
- )
426
-
427
- @message_queue << Notification.new(
428
- message: "$/progress",
429
- params: Interface::ProgressParams.new(
430
- token: id,
431
- value: Interface::WorkDoneProgressBegin.new(
432
- kind: "begin",
433
- title: title,
434
- percentage: percentage,
435
- message: "#{percentage}% completed",
436
- ),
437
- ),
438
- )
439
- end
440
-
441
- sig { params(id: String, percentage: Integer).void }
442
- def progress(id, percentage)
443
- return unless @store.supports_progress
444
-
445
- @message_queue << Notification.new(
446
- message: "$/progress",
447
- params: Interface::ProgressParams.new(
448
- token: id,
449
- value: Interface::WorkDoneProgressReport.new(
450
- kind: "report",
451
- percentage: percentage,
452
- message: "#{percentage}% completed",
453
- ),
454
- ),
455
- )
456
- end
457
-
458
- sig { params(id: String).void }
459
- def end_progress(id)
460
- return unless @store.supports_progress
461
-
462
- @message_queue << Notification.new(
463
- message: "$/progress",
464
- params: Interface::ProgressParams.new(
465
- token: id,
466
- value: Interface::WorkDoneProgressEnd.new(kind: "end"),
467
- ),
468
- )
469
- rescue ClosedQueueError
470
- # If the server was killed and the message queue is already closed, there's no way to end the progress
471
- # notification
472
- end
473
-
474
- sig { params(options: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
475
- def initialize_request(options)
476
- @store.clear
477
-
478
- workspace_uri = options.dig(:workspaceFolders, 0, :uri)
479
- @store.workspace_uri = URI(workspace_uri) if workspace_uri
480
-
481
- client_name = options.dig(:clientInfo, :name)
482
- @store.client_name = client_name if client_name
483
-
484
- encodings = options.dig(:capabilities, :general, :positionEncodings)
485
- @store.encoding = if encodings.nil? || encodings.empty?
486
- Constant::PositionEncodingKind::UTF16
487
- elsif encodings.include?(Constant::PositionEncodingKind::UTF8)
488
- Constant::PositionEncodingKind::UTF8
489
- else
490
- encodings.first
491
- end
492
-
493
- progress = options.dig(:capabilities, :window, :workDoneProgress)
494
- @store.supports_progress = progress.nil? ? true : progress
495
- formatter = options.dig(:initializationOptions, :formatter) || "auto"
496
- @store.formatter = if formatter == "auto"
497
- DependencyDetector.instance.detected_formatter
498
- else
499
- formatter
500
- end
501
-
502
- configured_features = options.dig(:initializationOptions, :enabledFeatures)
503
- @store.experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
504
-
505
- configured_hints = options.dig(:initializationOptions, :featuresConfiguration, :inlayHint)
506
- T.must(@store.features_configuration.dig(:inlayHint)).configuration.merge!(configured_hints) if configured_hints
507
-
508
- enabled_features = case configured_features
509
- when Array
510
- # If the configuration is using an array, then absent features are disabled and present ones are enabled. That's
511
- # why we use `false` as the default value
512
- Hash.new(false).merge!(configured_features.to_h { |feature| [feature, true] })
513
- when Hash
514
- # If the configuration is already a hash, merge it with a default value of `true`. That way clients don't have
515
- # to opt-in to every single feature
516
- Hash.new(true).merge!(configured_features)
517
- else
518
- # If no configuration was passed by the client, just enable every feature
519
- Hash.new(true)
520
- end
521
-
522
- document_symbol_provider = Requests::DocumentSymbol.provider if enabled_features["documentSymbols"]
523
- document_link_provider = Requests::DocumentLink.provider if enabled_features["documentLink"]
524
- code_lens_provider = Requests::CodeLens.provider if enabled_features["codeLens"]
525
- hover_provider = Requests::Hover.provider if enabled_features["hover"]
526
- folding_ranges_provider = Requests::FoldingRanges.provider if enabled_features["foldingRanges"]
527
- semantic_tokens_provider = Requests::SemanticHighlighting.provider if enabled_features["semanticHighlighting"]
528
- diagnostics_provider = Requests::Diagnostics.provider if enabled_features["diagnostics"]
529
- on_type_formatting_provider = Requests::OnTypeFormatting.provider if enabled_features["onTypeFormatting"]
530
- code_action_provider = Requests::CodeActions.provider if enabled_features["codeActions"]
531
- inlay_hint_provider = Requests::InlayHints.provider if enabled_features["inlayHint"]
532
- completion_provider = Requests::Completion.provider if enabled_features["completion"]
533
- signature_help_provider = Requests::SignatureHelp.provider if enabled_features["signatureHelp"]
534
-
535
- # Dynamically registered capabilities
536
- file_watching_caps = options.dig(:capabilities, :workspace, :didChangeWatchedFiles)
537
-
538
- # Not every client supports dynamic registration or file watching
539
- if file_watching_caps&.dig(:dynamicRegistration) && file_watching_caps&.dig(:relativePatternSupport)
540
- @message_queue << Request.new(
541
- message: "client/registerCapability",
542
- params: Interface::RegistrationParams.new(
543
- registrations: [
544
- # Register watching Ruby files
545
- Interface::Registration.new(
546
- id: "workspace/didChangeWatchedFiles",
547
- method: "workspace/didChangeWatchedFiles",
548
- register_options: Interface::DidChangeWatchedFilesRegistrationOptions.new(
549
- watchers: [
550
- Interface::FileSystemWatcher.new(
551
- glob_pattern: "**/*.rb",
552
- kind: Constant::WatchKind::CREATE | Constant::WatchKind::CHANGE | Constant::WatchKind::DELETE,
553
- ),
554
- ],
555
- ),
556
- ),
557
- ],
558
- ),
559
- )
560
- end
561
-
562
- begin_progress("indexing-progress", "Ruby LSP: indexing files")
563
-
564
- {
565
- capabilities: Interface::ServerCapabilities.new(
566
- text_document_sync: Interface::TextDocumentSyncOptions.new(
567
- change: Constant::TextDocumentSyncKind::INCREMENTAL,
568
- open_close: true,
569
- ),
570
- position_encoding: @store.encoding,
571
- selection_range_provider: enabled_features["selectionRanges"],
572
- hover_provider: hover_provider,
573
- document_symbol_provider: document_symbol_provider,
574
- document_link_provider: document_link_provider,
575
- folding_range_provider: folding_ranges_provider,
576
- semantic_tokens_provider: semantic_tokens_provider,
577
- document_formatting_provider: enabled_features["formatting"] && formatter != "none",
578
- document_highlight_provider: enabled_features["documentHighlights"],
579
- code_action_provider: code_action_provider,
580
- document_on_type_formatting_provider: on_type_formatting_provider,
581
- diagnostic_provider: diagnostics_provider,
582
- inlay_hint_provider: inlay_hint_provider,
583
- completion_provider: completion_provider,
584
- code_lens_provider: code_lens_provider,
585
- definition_provider: enabled_features["definition"],
586
- workspace_symbol_provider: enabled_features["workspaceSymbol"],
587
- signature_help_provider: signature_help_provider,
588
- ),
589
- serverInfo: {
590
- name: "Ruby LSP",
591
- version: VERSION,
592
- },
593
- formatter: @store.formatter,
594
- }
595
- end
596
-
597
- sig { void }
598
- def check_formatter_is_available
599
- # Warn of an unavailable `formatter` setting, e.g. `rubocop` on a project which doesn't have RuboCop.
600
- # Syntax Tree will always be available via Ruby LSP so we don't need to check for it.
601
- return unless @store.formatter == "rubocop"
602
-
603
- unless defined?(RubyLsp::Requests::Support::RuboCopRunner)
604
- @store.formatter = "none"
605
-
606
- @message_queue << Notification.window_show_error(
607
- "Ruby LSP formatter is set to `rubocop` but RuboCop was not found in the Gemfile or gemspec.",
608
- )
609
- end
610
- end
611
- end
612
- end