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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp +1 -16
- data/exe/ruby-lsp-check +13 -22
- data/lib/ruby_indexer/lib/ruby_indexer/collector.rb +21 -0
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +12 -24
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +16 -0
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +3 -2
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +46 -0
- data/lib/ruby_indexer/test/configuration_test.rb +2 -11
- data/lib/ruby_indexer/test/index_test.rb +5 -0
- data/lib/ruby_lsp/addon.rb +18 -5
- data/lib/ruby_lsp/base_server.rb +147 -0
- data/lib/ruby_lsp/document.rb +0 -5
- data/lib/ruby_lsp/{requests/support/dependency_detector.rb → global_state.rb} +30 -9
- data/lib/ruby_lsp/internal.rb +2 -1
- data/lib/ruby_lsp/listeners/code_lens.rb +66 -18
- data/lib/ruby_lsp/listeners/completion.rb +13 -14
- data/lib/ruby_lsp/listeners/definition.rb +4 -3
- data/lib/ruby_lsp/listeners/document_symbol.rb +91 -3
- data/lib/ruby_lsp/listeners/hover.rb +6 -5
- data/lib/ruby_lsp/listeners/signature_help.rb +7 -4
- data/lib/ruby_lsp/load_sorbet.rb +62 -0
- data/lib/ruby_lsp/requests/code_lens.rb +4 -3
- data/lib/ruby_lsp/requests/completion.rb +15 -4
- data/lib/ruby_lsp/requests/completion_resolve.rb +56 -0
- data/lib/ruby_lsp/requests/definition.rb +18 -5
- data/lib/ruby_lsp/requests/document_symbol.rb +3 -3
- data/lib/ruby_lsp/requests/hover.rb +9 -5
- data/lib/ruby_lsp/requests/request.rb +51 -0
- data/lib/ruby_lsp/requests/signature_help.rb +4 -3
- data/lib/ruby_lsp/requests/support/common.rb +25 -5
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +4 -0
- data/lib/ruby_lsp/requests/workspace_symbol.rb +5 -4
- data/lib/ruby_lsp/requests.rb +2 -0
- data/lib/ruby_lsp/server.rb +756 -142
- data/lib/ruby_lsp/store.rb +0 -8
- data/lib/ruby_lsp/test_helper.rb +45 -0
- data/lib/ruby_lsp/utils.rb +68 -33
- metadata +8 -5
- data/lib/ruby_lsp/executor.rb +0 -612
data/lib/ruby_lsp/executor.rb
DELETED
@@ -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
|