ruby-lsp 0.4.5 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -86
  3. data/VERSION +1 -1
  4. data/lib/ruby_lsp/check_docs.rb +112 -0
  5. data/lib/ruby_lsp/document.rb +13 -2
  6. data/lib/ruby_lsp/event_emitter.rb +84 -18
  7. data/lib/ruby_lsp/executor.rb +126 -48
  8. data/lib/ruby_lsp/internal.rb +1 -0
  9. data/lib/ruby_lsp/listener.rb +6 -13
  10. data/lib/ruby_lsp/requests/base_request.rb +0 -5
  11. data/lib/ruby_lsp/requests/code_action_resolve.rb +1 -1
  12. data/lib/ruby_lsp/requests/code_actions.rb +1 -1
  13. data/lib/ruby_lsp/requests/code_lens.rb +82 -66
  14. data/lib/ruby_lsp/requests/diagnostics.rb +2 -2
  15. data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
  16. data/lib/ruby_lsp/requests/document_link.rb +17 -15
  17. data/lib/ruby_lsp/requests/document_symbol.rb +51 -31
  18. data/lib/ruby_lsp/requests/folding_ranges.rb +1 -1
  19. data/lib/ruby_lsp/requests/formatting.rb +10 -11
  20. data/lib/ruby_lsp/requests/hover.rb +20 -20
  21. data/lib/ruby_lsp/requests/inlay_hints.rb +1 -1
  22. data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
  23. data/lib/ruby_lsp/requests/path_completion.rb +21 -57
  24. data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
  25. data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -1
  26. data/lib/ruby_lsp/requests/support/common.rb +36 -0
  27. data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +0 -1
  28. data/lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb +0 -1
  29. data/lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb +5 -2
  30. data/lib/ruby_lsp/requests.rb +15 -15
  31. data/lib/ruby_lsp/server.rb +44 -20
  32. data/lib/ruby_lsp/store.rb +1 -1
  33. data/lib/ruby_lsp/utils.rb +2 -7
  34. metadata +3 -2
@@ -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
- @messages = T.let([], T::Array[Message])
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, messages: @messages)
28
+ Result.new(response: response, error: error, request_time: request_time)
29
29
  end
30
30
 
31
31
  private
@@ -43,7 +43,7 @@ module RubyLsp
43
43
  errored_extensions = Extension.extensions.select(&:error?)
44
44
 
45
45
  if errored_extensions.any?
46
- @messages << Notification.new(
46
+ @message_queue << Notification.new(
47
47
  message: "window/showMessage",
48
48
  params: Interface::ShowMessageParams.new(
49
49
  type: Constant::MessageType::WARNING,
@@ -54,6 +54,8 @@ module RubyLsp
54
54
  warn(errored_extensions.map(&:backtraces).join("\n\n"))
55
55
  end
56
56
 
57
+ check_formatter_is_available
58
+
57
59
  warn("Ruby LSP is ready")
58
60
  VOID
59
61
  when "textDocument/didOpen"
@@ -63,7 +65,7 @@ module RubyLsp
63
65
  request.dig(:params, :textDocument, :version),
64
66
  )
65
67
  when "textDocument/didClose"
66
- @messages << Notification.new(
68
+ @message_queue << Notification.new(
67
69
  message: "textDocument/publishDiagnostics",
68
70
  params: Interface::PublishDiagnosticsParams.new(uri: uri, diagnostics: []),
69
71
  )
@@ -77,12 +79,33 @@ module RubyLsp
77
79
  )
78
80
  when "textDocument/foldingRange"
79
81
  folding_range(uri)
80
- when "textDocument/documentLink"
81
- document_link(uri)
82
82
  when "textDocument/selectionRange"
83
83
  selection_range(uri, request.dig(:params, :positions))
84
- when "textDocument/documentSymbol"
85
- document_symbol(uri)
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])
86
109
  when "textDocument/semanticTokens/full"
87
110
  semantic_tokens_full(uri)
88
111
  when "textDocument/semanticTokens/range"
@@ -91,7 +114,7 @@ module RubyLsp
91
114
  begin
92
115
  formatting(uri)
93
116
  rescue Requests::Formatting::InvalidFormatter => error
94
- @messages << Notification.new(
117
+ @message_queue << Notification.new(
95
118
  message: "window/showMessage",
96
119
  params: Interface::ShowMessageParams.new(
97
120
  type: Constant::MessageType::ERROR,
@@ -101,7 +124,7 @@ module RubyLsp
101
124
 
102
125
  nil
103
126
  rescue StandardError => error
104
- @messages << Notification.new(
127
+ @message_queue << Notification.new(
105
128
  message: "window/showMessage",
106
129
  params: Interface::ShowMessageParams.new(
107
130
  type: Constant::MessageType::ERROR,
@@ -127,7 +150,7 @@ module RubyLsp
127
150
  begin
128
151
  diagnostic(uri)
129
152
  rescue StandardError => error
130
- @messages << Notification.new(
153
+ @message_queue << Notification.new(
131
154
  message: "window/showMessage",
132
155
  params: Interface::ShowMessageParams.new(
133
156
  type: Constant::MessageType::ERROR,
@@ -139,25 +162,16 @@ module RubyLsp
139
162
  end
140
163
  when "textDocument/completion"
141
164
  completion(uri, request.dig(:params, :position))
142
- when "textDocument/codeLens"
143
- code_lens(uri)
144
165
  end
145
166
  end
146
167
 
147
168
  sig { params(uri: String).returns(T::Array[Interface::FoldingRange]) }
148
169
  def folding_range(uri)
149
- @store.cache_fetch(uri, :folding_ranges) do |document|
170
+ @store.cache_fetch(uri, "textDocument/foldingRange") do |document|
150
171
  Requests::FoldingRanges.new(document).run
151
172
  end
152
173
  end
153
174
 
154
- sig { params(uri: String).returns(T::Array[Interface::CodeLens]) }
155
- def code_lens(uri)
156
- @store.cache_fetch(uri, :code_lens) do |document|
157
- Requests::CodeLens.new(document).run
158
- end
159
- end
160
-
161
175
  sig do
162
176
  params(
163
177
  uri: String,
@@ -166,7 +180,6 @@ module RubyLsp
166
180
  end
167
181
  def hover(uri, position)
168
182
  document = @store.get(uri)
169
- document.parse
170
183
  return if document.syntax_error?
171
184
 
172
185
  target, parent = document.locate_node(position)
@@ -177,31 +190,18 @@ module RubyLsp
177
190
  end
178
191
 
179
192
  # Instantiate all listeners
180
- base_listener = Requests::Hover.new
181
- listeners = Requests::Hover.listeners.map(&:new)
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) }
182
196
 
183
197
  # Emit events for all listeners
184
- T.unsafe(EventEmitter).new(base_listener, *listeners).emit_for_target(target)
198
+ emitter.emit_for_target(target)
185
199
 
186
200
  # Merge all responses into a single hover
187
201
  listeners.each { |ext| base_listener.merge_response!(ext) }
188
202
  base_listener.response
189
203
  end
190
204
 
191
- sig { params(uri: String).returns(T::Array[Interface::DocumentLink]) }
192
- def document_link(uri)
193
- @store.cache_fetch(uri, :document_link) do |document|
194
- RubyLsp::Requests::DocumentLink.new(document).run
195
- end
196
- end
197
-
198
- sig { params(uri: String).returns(T::Array[Interface::DocumentSymbol]) }
199
- def document_symbol(uri)
200
- @store.cache_fetch(uri, :document_symbol) do |document|
201
- Requests::DocumentSymbol.new(document).run
202
- end
203
- end
204
-
205
205
  sig { params(uri: String, content_changes: T::Array[Document::EditShape], version: Integer).returns(Object) }
206
206
  def text_document_did_change(uri, content_changes, version)
207
207
  @store.push_edits(uri: uri, edits: content_changes, version: version)
@@ -227,7 +227,7 @@ module RubyLsp
227
227
  ).returns(T.nilable(T::Array[T.nilable(Requests::Support::SelectionRange)]))
228
228
  end
229
229
  def selection_range(uri, positions)
230
- ranges = @store.cache_fetch(uri, :selection_ranges) do |document|
230
+ ranges = @store.cache_fetch(uri, "textDocument/selectionRange") do |document|
231
231
  Requests::SelectionRanges.new(document).run
232
232
  end
233
233
 
@@ -247,7 +247,7 @@ module RubyLsp
247
247
 
248
248
  sig { params(uri: String).returns(Interface::SemanticTokens) }
249
249
  def semantic_tokens_full(uri)
250
- @store.cache_fetch(uri, :semantic_highlighting) do |document|
250
+ @store.cache_fetch(uri, "textDocument/semanticTokens/full") do |document|
251
251
  T.cast(
252
252
  Requests::SemanticHighlighting.new(
253
253
  document,
@@ -260,6 +260,9 @@ module RubyLsp
260
260
 
261
261
  sig { params(uri: String).returns(T.nilable(T::Array[Interface::TextEdit])) }
262
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
+
263
266
  Requests::Formatting.new(@store.get(uri), formatter: @store.formatter).run
264
267
  end
265
268
 
@@ -314,7 +317,7 @@ module RubyLsp
314
317
 
315
318
  case result
316
319
  when Requests::CodeActionResolve::Error::EmptySelection
317
- @messages << Notification.new(
320
+ @message_queue << Notification.new(
318
321
  message: "window/showMessage",
319
322
  params: Interface::ShowMessageParams.new(
320
323
  type: Constant::MessageType::ERROR,
@@ -323,7 +326,7 @@ module RubyLsp
323
326
  )
324
327
  raise Requests::CodeActionResolve::CodeActionError
325
328
  when Requests::CodeActionResolve::Error::InvalidTargetRange
326
- @messages << Notification.new(
329
+ @message_queue << Notification.new(
327
330
  message: "window/showMessage",
328
331
  params: Interface::ShowMessageParams.new(
329
332
  type: Constant::MessageType::ERROR,
@@ -338,7 +341,7 @@ module RubyLsp
338
341
 
339
342
  sig { params(uri: String).returns(T.nilable(Interface::FullDocumentDiagnosticReport)) }
340
343
  def diagnostic(uri)
341
- response = @store.cache_fetch(uri, :diagnostics) do |document|
344
+ response = @store.cache_fetch(uri, "textDocument/diagnostic") do |document|
342
345
  Requests::Diagnostics.new(document).run
343
346
  end
344
347
 
@@ -365,7 +368,44 @@ module RubyLsp
365
368
  params(uri: String, position: Document::PositionShape).returns(T.nilable(T::Array[Interface::CompletionItem]))
366
369
  end
367
370
  def completion(uri, position)
368
- Requests::PathCompletion.new(@store.get(uri), position).run
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
369
409
  end
370
410
 
371
411
  sig { params(options: T::Hash[Symbol, T.untyped]).returns(Interface::InitializeResult) }
@@ -382,7 +422,11 @@ module RubyLsp
382
422
  end
383
423
 
384
424
  formatter = options.dig(:initializationOptions, :formatter)
385
- @store.formatter = formatter unless formatter.nil?
425
+ @store.formatter = if formatter == "auto"
426
+ detected_formatter
427
+ else
428
+ formatter
429
+ end
386
430
 
387
431
  configured_features = options.dig(:initializationOptions, :enabledFeatures)
388
432
  experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled)
@@ -491,5 +535,39 @@ module RubyLsp
491
535
  ),
492
536
  )
493
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
494
572
  end
495
573
  end
@@ -16,3 +16,4 @@ require "ruby_lsp/requests"
16
16
  require "ruby_lsp/listener"
17
17
  require "ruby_lsp/store"
18
18
  require "ruby_lsp/extension"
19
+ require "ruby_lsp/requests/support/rubocop_runner"
@@ -14,12 +14,15 @@ 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.nilable(T::Array[Symbol])) }
21
- attr_reader :events
22
-
23
26
  sig { returns(T::Array[T.class_of(Listener)]) }
24
27
  def listeners
25
28
  @listeners ||= T.let([], T.nilable(T::Array[T.class_of(Listener)]))
@@ -29,16 +32,6 @@ module RubyLsp
29
32
  def add_listener(listener)
30
33
  listeners << listener
31
34
  end
32
-
33
- # All listener events must be defined inside of a `listener_events` block. This is to ensure we know which events
34
- # have been registered. Defining an event outside of this block will simply not register it and it'll never be
35
- # invoked
36
- sig { params(block: T.proc.void).void }
37
- def listener_events(&block)
38
- current_methods = instance_methods
39
- block.call
40
- @events = T.let(instance_methods - current_methods, T.nilable(T::Array[Symbol]))
41
- end
42
35
  end
43
36
 
44
37
  # Override this method with an attr_reader that returns the response of your listener. The listener should
@@ -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](../../misc/code_action_resolve.gif)
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](../../misc/code_actions.gif)
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
@@ -3,7 +3,7 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Requests
6
- # ![Code lens demo](../../misc/code_lens.gif)
6
+ # ![Code lens demo](../../code_lens.gif)
7
7
  #
8
8
  # This feature is currently experimental. Clients will need to pass `experimentalFeaturesEnabled`
9
9
  # in the initialization options to enable it.
@@ -19,41 +19,41 @@ module RubyLsp
19
19
  # class Test < Minitest::Test
20
20
  # end
21
21
  # ```
22
+ class CodeLens < Listener
23
+ extend T::Sig
24
+ extend T::Generic
25
+
26
+ ResponseType = type_member { { fixed: T::Array[Interface::CodeLens] } }
22
27
 
23
- class CodeLens < BaseRequest
24
28
  BASE_COMMAND = T.let((File.exist?("Gemfile.lock") ? "bundle exec ruby" : "ruby") + " -Itest ", String)
25
29
  ACCESS_MODIFIERS = T.let(["public", "private", "protected"], T::Array[String])
26
30
 
27
- sig do
28
- params(
29
- document: Document,
30
- ).void
31
- end
32
- def initialize(document)
33
- super(document)
34
- @results = T.let([], T::Array[Interface::CodeLens])
35
- @path = T.let(document.uri.delete_prefix("file://"), String)
36
- @modifier = T.let("public", String)
37
- end
31
+ sig { override.returns(ResponseType) }
32
+ attr_reader :response
33
+
34
+ sig { params(uri: String, emitter: EventEmitter, message_queue: Thread::Queue).void }
35
+ def initialize(uri, emitter, message_queue)
36
+ super(emitter, message_queue)
37
+
38
+ @response = T.let([], ResponseType)
39
+ @path = T.let(T.must(URI(uri).path), String)
40
+ @visibility = T.let("public", String)
41
+ @prev_visibility = T.let("public", String)
38
42
 
39
- sig { override.returns(T.all(T::Array[Interface::CodeLens], Object)) }
40
- def run
41
- visit(@document.tree) if @document.parsed?
42
- @results
43
+ emitter.register(self, :on_class, :on_def, :on_command, :after_command, :on_call, :after_call, :on_vcall)
43
44
  end
44
45
 
45
- sig { override.params(node: SyntaxTree::ClassDeclaration).void }
46
- def visit_class(node)
46
+ sig { params(node: SyntaxTree::ClassDeclaration).void }
47
+ def on_class(node)
47
48
  class_name = node.constant.constant.value
48
49
  if class_name.end_with?("Test")
49
50
  add_code_lens(node, name: class_name, command: BASE_COMMAND + @path)
50
51
  end
51
- visit(node.bodystmt)
52
52
  end
53
53
 
54
- sig { override.params(node: SyntaxTree::DefNode).void }
55
- def visit_def(node)
56
- if @modifier == "public"
54
+ sig { params(node: SyntaxTree::DefNode).void }
55
+ def on_def(node)
56
+ if @visibility == "public"
57
57
  method_name = node.name.value
58
58
  if method_name.start_with?("test_")
59
59
  add_code_lens(
@@ -65,69 +65,85 @@ module RubyLsp
65
65
  end
66
66
  end
67
67
 
68
- sig { override.params(node: SyntaxTree::Command).void }
69
- def visit_command(node)
70
- if node.message.value == "public"
71
- with_visiblity("public", node)
68
+ sig { params(node: SyntaxTree::Command).void }
69
+ def on_command(node)
70
+ if ACCESS_MODIFIERS.include?(node.message.value) && node.arguments.parts.any?
71
+ @prev_visibility = @visibility
72
+ @visibility = node.message.value
72
73
  end
73
74
  end
74
75
 
75
- sig { override.params(node: SyntaxTree::CallNode).void }
76
- def visit_call(node)
76
+ sig { params(node: SyntaxTree::Command).void }
77
+ def after_command(node)
78
+ @visibility = @prev_visibility
79
+ end
80
+
81
+ sig { params(node: SyntaxTree::CallNode).void }
82
+ def on_call(node)
77
83
  ident = node.message if node.message.is_a?(SyntaxTree::Ident)
78
84
 
79
85
  if ident
80
- if T.cast(ident, SyntaxTree::Ident).value == "public"
81
- with_visiblity("public", node)
86
+ ident_value = T.cast(ident, SyntaxTree::Ident).value
87
+ if ACCESS_MODIFIERS.include?(ident_value)
88
+ @prev_visibility = @visibility
89
+ @visibility = ident_value
82
90
  end
83
91
  end
84
92
  end
85
93
 
86
- sig { override.params(node: SyntaxTree::VCall).void }
87
- def visit_vcall(node)
94
+ sig { params(node: SyntaxTree::CallNode).void }
95
+ def after_call(node)
96
+ @visibility = @prev_visibility
97
+ end
98
+
99
+ sig { params(node: SyntaxTree::VCall).void }
100
+ def on_vcall(node)
88
101
  vcall_value = node.value.value
89
102
 
90
103
  if ACCESS_MODIFIERS.include?(vcall_value)
91
- @modifier = vcall_value
104
+ @prev_visibility = vcall_value
105
+ @visibility = vcall_value
92
106
  end
93
107
  end
94
108
 
95
- private
96
-
97
- sig do
98
- params(
99
- visibility: String,
100
- node: T.any(SyntaxTree::CallNode, SyntaxTree::Command),
101
- ).void
102
- end
103
- def with_visiblity(visibility, node)
104
- current_visibility = @modifier
105
- @modifier = visibility
106
- visit(node.arguments)
107
- ensure
108
- @modifier = T.must(current_visibility)
109
+ sig { params(other: Listener[ResponseType]).returns(T.self_type) }
110
+ def merge_response!(other)
111
+ @response.concat(other.response)
112
+ self
109
113
  end
110
114
 
115
+ private
116
+
111
117
  sig { params(node: SyntaxTree::Node, name: String, command: String).void }
112
118
  def add_code_lens(node, name:, command:)
113
- @results << Interface::CodeLens.new(
114
- range: range_from_syntax_tree_node(node),
115
- command: Interface::Command.new(
116
- title: "Run",
117
- command: "rubyLsp.runTest",
118
- arguments: [
119
- @path,
120
- name,
121
- command,
122
- {
123
- start_line: node.location.start_line - 1,
124
- start_column: node.location.start_column,
125
- end_line: node.location.end_line - 1,
126
- end_column: node.location.end_column,
127
- },
128
- ],
129
- ),
130
- data: { type: "test" },
119
+ @response << create_code_lens(
120
+ node,
121
+ title: "Run",
122
+ command_name: "rubyLsp.runTest",
123
+ path: @path,
124
+ name: name,
125
+ test_command: command,
126
+ type: "test",
127
+ )
128
+
129
+ @response << create_code_lens(
130
+ node,
131
+ title: "Run In Terminal",
132
+ command_name: "rubyLsp.runTestInTerminal",
133
+ path: @path,
134
+ name: name,
135
+ test_command: command,
136
+ type: "test_in_terminal",
137
+ )
138
+
139
+ @response << create_code_lens(
140
+ node,
141
+ title: "Debug",
142
+ command_name: "rubyLsp.debugTest",
143
+ path: @path,
144
+ name: name,
145
+ test_command: command,
146
+ type: "debug",
131
147
  )
132
148
  end
133
149
  end
@@ -5,7 +5,7 @@ require "ruby_lsp/requests/support/rubocop_diagnostics_runner"
5
5
 
6
6
  module RubyLsp
7
7
  module Requests
8
- # ![Diagnostics demo](../../misc/diagnostics.gif)
8
+ # ![Diagnostics demo](../../diagnostics.gif)
9
9
  #
10
10
  # The
11
11
  # [diagnostics](https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics)
@@ -34,7 +34,7 @@ module RubyLsp
34
34
  return unless defined?(Support::RuboCopDiagnosticsRunner)
35
35
 
36
36
  # Don't try to run RuboCop diagnostics for files outside the current working directory
37
- return unless @uri.start_with?(WORKSPACE_URI)
37
+ return unless URI(@uri).path&.start_with?(T.must(WORKSPACE_URI.path))
38
38
 
39
39
  Support::RuboCopDiagnosticsRunner.instance.run(@uri, @document)
40
40
  end
@@ -3,7 +3,7 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Requests
6
- # ![Document highlight demo](../../misc/document_highlight.gif)
6
+ # ![Document highlight demo](../../document_highlight.gif)
7
7
  #
8
8
  # The [document highlight](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentHighlight)
9
9
  # informs the editor all relevant elements of the currently pointed item for highlighting. For example, when