ruby-lsp 0.4.4 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +5 -86
- data/VERSION +1 -1
- data/exe/ruby-lsp +10 -1
- data/lib/ruby_lsp/check_docs.rb +112 -0
- data/lib/ruby_lsp/document.rb +13 -2
- data/lib/ruby_lsp/event_emitter.rb +86 -18
- data/lib/ruby_lsp/executor.rb +146 -45
- data/lib/ruby_lsp/extension.rb +104 -0
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/listener.rb +14 -11
- data/lib/ruby_lsp/requests/base_request.rb +0 -5
- data/lib/ruby_lsp/requests/code_action_resolve.rb +1 -1
- data/lib/ruby_lsp/requests/code_actions.rb +1 -1
- data/lib/ruby_lsp/requests/code_lens.rb +82 -66
- data/lib/ruby_lsp/requests/diagnostics.rb +2 -2
- data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
- data/lib/ruby_lsp/requests/document_link.rb +17 -15
- data/lib/ruby_lsp/requests/document_symbol.rb +51 -31
- data/lib/ruby_lsp/requests/folding_ranges.rb +1 -1
- data/lib/ruby_lsp/requests/formatting.rb +10 -11
- data/lib/ruby_lsp/requests/hover.rb +34 -19
- data/lib/ruby_lsp/requests/inlay_hints.rb +1 -1
- data/lib/ruby_lsp/requests/on_type_formatting.rb +5 -1
- data/lib/ruby_lsp/requests/path_completion.rb +21 -57
- data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -1
- data/lib/ruby_lsp/requests/support/common.rb +36 -0
- data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +0 -1
- data/lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb +0 -1
- data/lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb +5 -2
- data/lib/ruby_lsp/requests.rb +15 -15
- data/lib/ruby_lsp/server.rb +44 -11
- data/lib/ruby_lsp/store.rb +1 -1
- data/lib/ruby_lsp/utils.rb +9 -8
- metadata +5 -3
data/lib/ruby_lsp/executor.rb
CHANGED
@@ -6,12 +6,12 @@ module RubyLsp
|
|
6
6
|
class Executor
|
7
7
|
extend T::Sig
|
8
8
|
|
9
|
-
sig { params(store: Store).void }
|
10
|
-
def initialize(store)
|
9
|
+
sig { params(store: Store, message_queue: Thread::Queue).void }
|
10
|
+
def initialize(store, message_queue)
|
11
11
|
# Requests that mutate the store must be run sequentially! Parallel requests only receive a temporary copy of the
|
12
12
|
# store
|
13
13
|
@store = store
|
14
|
-
@
|
14
|
+
@message_queue = message_queue
|
15
15
|
end
|
16
16
|
|
17
17
|
sig { params(request: T::Hash[Symbol, T.untyped]).returns(Result) }
|
@@ -25,7 +25,7 @@ module RubyLsp
|
|
25
25
|
error = e
|
26
26
|
end
|
27
27
|
|
28
|
-
Result.new(response: response, error: error, request_time: request_time
|
28
|
+
Result.new(response: response, error: error, request_time: request_time)
|
29
29
|
end
|
30
30
|
|
31
31
|
private
|
@@ -38,6 +38,24 @@ module RubyLsp
|
|
38
38
|
when "initialize"
|
39
39
|
initialize_request(request.dig(:params))
|
40
40
|
when "initialized"
|
41
|
+
Extension.load_extensions
|
42
|
+
|
43
|
+
errored_extensions = Extension.extensions.select(&:error?)
|
44
|
+
|
45
|
+
if errored_extensions.any?
|
46
|
+
@message_queue << Notification.new(
|
47
|
+
message: "window/showMessage",
|
48
|
+
params: Interface::ShowMessageParams.new(
|
49
|
+
type: Constant::MessageType::WARNING,
|
50
|
+
message: "Error loading extensions:\n\n#{errored_extensions.map(&:formatted_errors).join("\n\n")}",
|
51
|
+
),
|
52
|
+
)
|
53
|
+
|
54
|
+
warn(errored_extensions.map(&:backtraces).join("\n\n"))
|
55
|
+
end
|
56
|
+
|
57
|
+
check_formatter_is_available
|
58
|
+
|
41
59
|
warn("Ruby LSP is ready")
|
42
60
|
VOID
|
43
61
|
when "textDocument/didOpen"
|
@@ -47,7 +65,7 @@ module RubyLsp
|
|
47
65
|
request.dig(:params, :textDocument, :version),
|
48
66
|
)
|
49
67
|
when "textDocument/didClose"
|
50
|
-
@
|
68
|
+
@message_queue << Notification.new(
|
51
69
|
message: "textDocument/publishDiagnostics",
|
52
70
|
params: Interface::PublishDiagnosticsParams.new(uri: uri, diagnostics: []),
|
53
71
|
)
|
@@ -61,12 +79,33 @@ module RubyLsp
|
|
61
79
|
)
|
62
80
|
when "textDocument/foldingRange"
|
63
81
|
folding_range(uri)
|
64
|
-
when "textDocument/documentLink"
|
65
|
-
document_link(uri)
|
66
82
|
when "textDocument/selectionRange"
|
67
83
|
selection_range(uri, request.dig(:params, :positions))
|
68
|
-
when "textDocument/documentSymbol"
|
69
|
-
|
84
|
+
when "textDocument/documentSymbol", "textDocument/documentLink", "textDocument/codeLens"
|
85
|
+
document = @store.get(uri)
|
86
|
+
|
87
|
+
# If the response has already been cached by another request, return it
|
88
|
+
cached_response = document.cache_get(request[:method])
|
89
|
+
return cached_response if cached_response
|
90
|
+
|
91
|
+
# Run listeners for the document
|
92
|
+
emitter = EventEmitter.new
|
93
|
+
document_symbol = Requests::DocumentSymbol.new(emitter, @message_queue)
|
94
|
+
document_link = Requests::DocumentLink.new(uri, emitter, @message_queue)
|
95
|
+
code_lens = Requests::CodeLens.new(uri, emitter, @message_queue)
|
96
|
+
code_lens_extensions_listeners = Requests::CodeLens.listeners.map do |l|
|
97
|
+
T.unsafe(l).new(document.uri, emitter, @message_queue)
|
98
|
+
end
|
99
|
+
emitter.visit(document.tree) if document.parsed?
|
100
|
+
|
101
|
+
code_lens_extensions_listeners.each { |ext| code_lens.merge_response!(ext) }
|
102
|
+
|
103
|
+
# Store all responses retrieve in this round of visits in the cache and then return the response for the request
|
104
|
+
# we actually received
|
105
|
+
document.cache_set("textDocument/documentSymbol", document_symbol.response)
|
106
|
+
document.cache_set("textDocument/documentLink", document_link.response)
|
107
|
+
document.cache_set("textDocument/codeLens", code_lens.response)
|
108
|
+
document.cache_get(request[:method])
|
70
109
|
when "textDocument/semanticTokens/full"
|
71
110
|
semantic_tokens_full(uri)
|
72
111
|
when "textDocument/semanticTokens/range"
|
@@ -75,7 +114,7 @@ module RubyLsp
|
|
75
114
|
begin
|
76
115
|
formatting(uri)
|
77
116
|
rescue Requests::Formatting::InvalidFormatter => error
|
78
|
-
@
|
117
|
+
@message_queue << Notification.new(
|
79
118
|
message: "window/showMessage",
|
80
119
|
params: Interface::ShowMessageParams.new(
|
81
120
|
type: Constant::MessageType::ERROR,
|
@@ -85,7 +124,7 @@ module RubyLsp
|
|
85
124
|
|
86
125
|
nil
|
87
126
|
rescue StandardError => error
|
88
|
-
@
|
127
|
+
@message_queue << Notification.new(
|
89
128
|
message: "window/showMessage",
|
90
129
|
params: Interface::ShowMessageParams.new(
|
91
130
|
type: Constant::MessageType::ERROR,
|
@@ -111,7 +150,7 @@ module RubyLsp
|
|
111
150
|
begin
|
112
151
|
diagnostic(uri)
|
113
152
|
rescue StandardError => error
|
114
|
-
@
|
153
|
+
@message_queue << Notification.new(
|
115
154
|
message: "window/showMessage",
|
116
155
|
params: Interface::ShowMessageParams.new(
|
117
156
|
type: Constant::MessageType::ERROR,
|
@@ -123,25 +162,16 @@ module RubyLsp
|
|
123
162
|
end
|
124
163
|
when "textDocument/completion"
|
125
164
|
completion(uri, request.dig(:params, :position))
|
126
|
-
when "textDocument/codeLens"
|
127
|
-
code_lens(uri)
|
128
165
|
end
|
129
166
|
end
|
130
167
|
|
131
168
|
sig { params(uri: String).returns(T::Array[Interface::FoldingRange]) }
|
132
169
|
def folding_range(uri)
|
133
|
-
@store.cache_fetch(uri,
|
170
|
+
@store.cache_fetch(uri, "textDocument/foldingRange") do |document|
|
134
171
|
Requests::FoldingRanges.new(document).run
|
135
172
|
end
|
136
173
|
end
|
137
174
|
|
138
|
-
sig { params(uri: String).returns(T::Array[Interface::CodeLens]) }
|
139
|
-
def code_lens(uri)
|
140
|
-
@store.cache_fetch(uri, :code_lens) do |document|
|
141
|
-
Requests::CodeLens.new(document).run
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
175
|
sig do
|
146
176
|
params(
|
147
177
|
uri: String,
|
@@ -150,7 +180,6 @@ module RubyLsp
|
|
150
180
|
end
|
151
181
|
def hover(uri, position)
|
152
182
|
document = @store.get(uri)
|
153
|
-
document.parse
|
154
183
|
return if document.syntax_error?
|
155
184
|
|
156
185
|
target, parent = document.locate_node(position)
|
@@ -160,23 +189,17 @@ module RubyLsp
|
|
160
189
|
target = parent
|
161
190
|
end
|
162
191
|
|
163
|
-
|
164
|
-
EventEmitter.new
|
165
|
-
|
166
|
-
|
192
|
+
# Instantiate all listeners
|
193
|
+
emitter = EventEmitter.new
|
194
|
+
base_listener = Requests::Hover.new(emitter, @message_queue)
|
195
|
+
listeners = Requests::Hover.listeners.map { |l| l.new(emitter, @message_queue) }
|
167
196
|
|
168
|
-
|
169
|
-
|
170
|
-
@store.cache_fetch(uri, :document_link) do |document|
|
171
|
-
RubyLsp::Requests::DocumentLink.new(document).run
|
172
|
-
end
|
173
|
-
end
|
197
|
+
# Emit events for all listeners
|
198
|
+
emitter.emit_for_target(target)
|
174
199
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
Requests::DocumentSymbol.new(document).run
|
179
|
-
end
|
200
|
+
# Merge all responses into a single hover
|
201
|
+
listeners.each { |ext| base_listener.merge_response!(ext) }
|
202
|
+
base_listener.response
|
180
203
|
end
|
181
204
|
|
182
205
|
sig { params(uri: String, content_changes: T::Array[Document::EditShape], version: Integer).returns(Object) }
|
@@ -204,7 +227,7 @@ module RubyLsp
|
|
204
227
|
).returns(T.nilable(T::Array[T.nilable(Requests::Support::SelectionRange)]))
|
205
228
|
end
|
206
229
|
def selection_range(uri, positions)
|
207
|
-
ranges = @store.cache_fetch(uri,
|
230
|
+
ranges = @store.cache_fetch(uri, "textDocument/selectionRange") do |document|
|
208
231
|
Requests::SelectionRanges.new(document).run
|
209
232
|
end
|
210
233
|
|
@@ -224,7 +247,7 @@ module RubyLsp
|
|
224
247
|
|
225
248
|
sig { params(uri: String).returns(Interface::SemanticTokens) }
|
226
249
|
def semantic_tokens_full(uri)
|
227
|
-
@store.cache_fetch(uri,
|
250
|
+
@store.cache_fetch(uri, "textDocument/semanticTokens/full") do |document|
|
228
251
|
T.cast(
|
229
252
|
Requests::SemanticHighlighting.new(
|
230
253
|
document,
|
@@ -237,6 +260,9 @@ module RubyLsp
|
|
237
260
|
|
238
261
|
sig { params(uri: String).returns(T.nilable(T::Array[Interface::TextEdit])) }
|
239
262
|
def formatting(uri)
|
263
|
+
# If formatter is set to `auto` but no supported formatting gem is found, don't attempt to format
|
264
|
+
return if @store.formatter == "none"
|
265
|
+
|
240
266
|
Requests::Formatting.new(@store.get(uri), formatter: @store.formatter).run
|
241
267
|
end
|
242
268
|
|
@@ -291,7 +317,7 @@ module RubyLsp
|
|
291
317
|
|
292
318
|
case result
|
293
319
|
when Requests::CodeActionResolve::Error::EmptySelection
|
294
|
-
@
|
320
|
+
@message_queue << Notification.new(
|
295
321
|
message: "window/showMessage",
|
296
322
|
params: Interface::ShowMessageParams.new(
|
297
323
|
type: Constant::MessageType::ERROR,
|
@@ -300,7 +326,7 @@ module RubyLsp
|
|
300
326
|
)
|
301
327
|
raise Requests::CodeActionResolve::CodeActionError
|
302
328
|
when Requests::CodeActionResolve::Error::InvalidTargetRange
|
303
|
-
@
|
329
|
+
@message_queue << Notification.new(
|
304
330
|
message: "window/showMessage",
|
305
331
|
params: Interface::ShowMessageParams.new(
|
306
332
|
type: Constant::MessageType::ERROR,
|
@@ -315,7 +341,7 @@ module RubyLsp
|
|
315
341
|
|
316
342
|
sig { params(uri: String).returns(T.nilable(Interface::FullDocumentDiagnosticReport)) }
|
317
343
|
def diagnostic(uri)
|
318
|
-
response = @store.cache_fetch(uri,
|
344
|
+
response = @store.cache_fetch(uri, "textDocument/diagnostic") do |document|
|
319
345
|
Requests::Diagnostics.new(document).run
|
320
346
|
end
|
321
347
|
|
@@ -342,7 +368,44 @@ module RubyLsp
|
|
342
368
|
params(uri: String, position: Document::PositionShape).returns(T.nilable(T::Array[Interface::CompletionItem]))
|
343
369
|
end
|
344
370
|
def completion(uri, position)
|
345
|
-
|
371
|
+
document = @store.get(uri)
|
372
|
+
return unless document.parsed?
|
373
|
+
|
374
|
+
char_position = document.create_scanner.find_char_position(position)
|
375
|
+
matched, parent = document.locate(
|
376
|
+
T.must(document.tree),
|
377
|
+
char_position,
|
378
|
+
node_types: [SyntaxTree::Command, SyntaxTree::CommandCall, SyntaxTree::CallNode],
|
379
|
+
)
|
380
|
+
|
381
|
+
return unless matched && parent
|
382
|
+
|
383
|
+
target = case matched
|
384
|
+
when SyntaxTree::Command, SyntaxTree::CallNode, SyntaxTree::CommandCall
|
385
|
+
message = matched.message
|
386
|
+
return if message.is_a?(Symbol)
|
387
|
+
return unless message.value == "require"
|
388
|
+
|
389
|
+
args = matched.arguments
|
390
|
+
args = args.arguments if args.is_a?(SyntaxTree::ArgParen)
|
391
|
+
return if args.nil? || args.is_a?(SyntaxTree::ArgsForward)
|
392
|
+
|
393
|
+
argument = args.parts.first
|
394
|
+
return unless argument.is_a?(SyntaxTree::StringLiteral)
|
395
|
+
|
396
|
+
path_node = argument.parts.first
|
397
|
+
return unless path_node.is_a?(SyntaxTree::TStringContent)
|
398
|
+
return unless (path_node.location.start_char..path_node.location.end_char).cover?(char_position)
|
399
|
+
|
400
|
+
path_node
|
401
|
+
end
|
402
|
+
|
403
|
+
return unless target
|
404
|
+
|
405
|
+
emitter = EventEmitter.new
|
406
|
+
listener = Requests::PathCompletion.new(emitter, @message_queue)
|
407
|
+
emitter.emit_for_target(target)
|
408
|
+
listener.response
|
346
409
|
end
|
347
410
|
|
348
411
|
sig { params(options: T::Hash[Symbol, T.untyped]).returns(Interface::InitializeResult) }
|
@@ -359,7 +422,11 @@ module RubyLsp
|
|
359
422
|
end
|
360
423
|
|
361
424
|
formatter = options.dig(:initializationOptions, :formatter)
|
362
|
-
@store.formatter = formatter
|
425
|
+
@store.formatter = if formatter == "auto"
|
426
|
+
detected_formatter
|
427
|
+
else
|
428
|
+
formatter
|
429
|
+
end
|
363
430
|
|
364
431
|
configured_features = options.dig(:initializationOptions, :enabledFeatures)
|
365
432
|
experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled)
|
@@ -468,5 +535,39 @@ module RubyLsp
|
|
468
535
|
),
|
469
536
|
)
|
470
537
|
end
|
538
|
+
|
539
|
+
sig { returns(String) }
|
540
|
+
def detected_formatter
|
541
|
+
# NOTE: Intentionally no $ at end, since we want to match rubocop-shopify, etc.
|
542
|
+
if direct_dependency?(/^rubocop/)
|
543
|
+
"rubocop"
|
544
|
+
elsif direct_dependency?(/^syntax_tree$/)
|
545
|
+
"syntax_tree"
|
546
|
+
else
|
547
|
+
"none"
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
sig { params(gem_pattern: Regexp).returns(T::Boolean) }
|
552
|
+
def direct_dependency?(gem_pattern)
|
553
|
+
Bundler.locked_gems.dependencies.keys.grep(gem_pattern).any?
|
554
|
+
end
|
555
|
+
|
556
|
+
sig { void }
|
557
|
+
def check_formatter_is_available
|
558
|
+
# Warn of an unavailable `formatter` setting, e.g. `rubocop` on a project which doesn't have RuboCop.
|
559
|
+
# Syntax Tree will always be available via Ruby LSP so we don't need to check for it.
|
560
|
+
return unless @store.formatter == "rubocop"
|
561
|
+
|
562
|
+
unless defined?(RubyLsp::Requests::Support::RuboCopRunner)
|
563
|
+
@message_queue << Notification.new(
|
564
|
+
message: "window/showMessage",
|
565
|
+
params: Interface::ShowMessageParams.new(
|
566
|
+
type: Constant::MessageType::ERROR,
|
567
|
+
message: "Ruby LSP formatter is set to `rubocop` but RuboCop was not found in the bundle.",
|
568
|
+
),
|
569
|
+
)
|
570
|
+
end
|
571
|
+
end
|
471
572
|
end
|
472
573
|
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
# To register an extension, inherit from this class and implement both `name` and `activate`
|
6
|
+
#
|
7
|
+
# # Example
|
8
|
+
#
|
9
|
+
# ```ruby
|
10
|
+
# module MyGem
|
11
|
+
# class MyExtension < Extension
|
12
|
+
# def activate
|
13
|
+
# # Perform any relevant initialization
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# def name
|
17
|
+
# "My extension name"
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
# ```
|
22
|
+
class Extension
|
23
|
+
extend T::Sig
|
24
|
+
extend T::Helpers
|
25
|
+
|
26
|
+
abstract!
|
27
|
+
|
28
|
+
class << self
|
29
|
+
extend T::Sig
|
30
|
+
|
31
|
+
# Automatically track and instantiate extension classes
|
32
|
+
sig { params(child_class: T.class_of(Extension)).void }
|
33
|
+
def inherited(child_class)
|
34
|
+
extensions << child_class.new
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
sig { returns(T::Array[Extension]) }
|
39
|
+
def extensions
|
40
|
+
@extensions ||= T.let([], T.nilable(T::Array[Extension]))
|
41
|
+
end
|
42
|
+
|
43
|
+
# Discovers and loads all extensions. Returns the list of activated extensions
|
44
|
+
sig { returns(T::Array[Extension]) }
|
45
|
+
def load_extensions
|
46
|
+
# Require all extensions entry points, which should be placed under
|
47
|
+
# `some_gem/lib/ruby_lsp/your_gem_name/extension.rb`
|
48
|
+
Gem.find_files("ruby_lsp/**/extension.rb").each do |extension|
|
49
|
+
require File.expand_path(extension)
|
50
|
+
rescue => e
|
51
|
+
warn(e.message)
|
52
|
+
warn(e.backtrace.to_s) # rubocop:disable Lint/RedundantStringCoercion
|
53
|
+
end
|
54
|
+
|
55
|
+
# Activate each one of the discovered extensions. If any problems occur in the extensions, we don't want to
|
56
|
+
# fail to boot the server
|
57
|
+
extensions.each do |extension|
|
58
|
+
extension.activate
|
59
|
+
nil
|
60
|
+
rescue => e
|
61
|
+
extension.add_error(e)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
sig { void }
|
67
|
+
def initialize
|
68
|
+
@errors = T.let([], T::Array[StandardError])
|
69
|
+
end
|
70
|
+
|
71
|
+
sig { params(error: StandardError).returns(T.self_type) }
|
72
|
+
def add_error(error)
|
73
|
+
@errors << error
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
sig { returns(T::Boolean) }
|
78
|
+
def error?
|
79
|
+
@errors.any?
|
80
|
+
end
|
81
|
+
|
82
|
+
sig { returns(String) }
|
83
|
+
def formatted_errors
|
84
|
+
<<~ERRORS
|
85
|
+
#{name}:
|
86
|
+
#{@errors.map(&:message).join("\n")}
|
87
|
+
ERRORS
|
88
|
+
end
|
89
|
+
|
90
|
+
sig { returns(String) }
|
91
|
+
def backtraces
|
92
|
+
@errors.filter_map(&:backtrace).join("\n\n")
|
93
|
+
end
|
94
|
+
|
95
|
+
# Each extension should implement `MyExtension#activate` and use to perform any sort of initialization, such as
|
96
|
+
# reading information into memory or even spawning a separate process
|
97
|
+
sig { abstract.void }
|
98
|
+
def activate; end
|
99
|
+
|
100
|
+
# Extensions should override the `name` method to return the extension name
|
101
|
+
sig { abstract.returns(String) }
|
102
|
+
def name; end
|
103
|
+
end
|
104
|
+
end
|
data/lib/ruby_lsp/internal.rb
CHANGED
data/lib/ruby_lsp/listener.rb
CHANGED
@@ -14,20 +14,23 @@ module RubyLsp
|
|
14
14
|
|
15
15
|
abstract!
|
16
16
|
|
17
|
+
sig { params(emitter: EventEmitter, message_queue: Thread::Queue).void }
|
18
|
+
def initialize(emitter, message_queue)
|
19
|
+
@emitter = emitter
|
20
|
+
@message_queue = message_queue
|
21
|
+
end
|
22
|
+
|
17
23
|
class << self
|
18
24
|
extend T::Sig
|
19
25
|
|
20
|
-
sig { returns(T
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
current_methods = instance_methods
|
29
|
-
block.call
|
30
|
-
@events = T.let(instance_methods - current_methods, T.nilable(T::Array[Symbol]))
|
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
|
31
34
|
end
|
32
35
|
end
|
33
36
|
|
@@ -18,11 +18,6 @@ module RubyLsp
|
|
18
18
|
sig { params(document: Document, _kwargs: T.untyped).void }
|
19
19
|
def initialize(document, **_kwargs)
|
20
20
|
@document = document
|
21
|
-
|
22
|
-
# Parsing the document here means we're taking a lazy approach by only doing it when the first feature request
|
23
|
-
# is received by the server. This happens because {Document#parse} remembers if there are new edits to be parsed
|
24
|
-
@document.parse
|
25
|
-
|
26
21
|
super()
|
27
22
|
end
|
28
23
|
|
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
module RubyLsp
|
5
5
|
module Requests
|
6
|
-
# ![Code action resolve demo](../../
|
6
|
+
# ![Code action resolve demo](../../code_action_resolve.gif)
|
7
7
|
#
|
8
8
|
# The [code action resolve](https://microsoft.github.io/language-server-protocol/specification#codeAction_resolve)
|
9
9
|
# request is used to to resolve the edit field for a given code action, if it is not already provided in the
|
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
module RubyLsp
|
5
5
|
module Requests
|
6
|
-
# ![Code actions demo](../../
|
6
|
+
# ![Code actions demo](../../code_actions.gif)
|
7
7
|
#
|
8
8
|
# The [code actions](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction)
|
9
9
|
# request informs the editor of RuboCop quick fixes that can be applied. These are accessible by hovering over a
|