ruby-lsp 0.4.1 → 0.4.3

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +44 -52
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +12 -0
  5. data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +62 -0
  6. data/lib/ruby_lsp/document.rb +13 -4
  7. data/lib/ruby_lsp/executor.rb +70 -26
  8. data/lib/ruby_lsp/internal.rb +1 -0
  9. data/lib/ruby_lsp/requests/base_request.rb +15 -6
  10. data/lib/ruby_lsp/requests/code_action_resolve.rb +40 -19
  11. data/lib/ruby_lsp/requests/code_actions.rb +5 -4
  12. data/lib/ruby_lsp/requests/diagnostics.rb +4 -4
  13. data/lib/ruby_lsp/requests/document_highlight.rb +3 -3
  14. data/lib/ruby_lsp/requests/document_link.rb +7 -7
  15. data/lib/ruby_lsp/requests/document_symbol.rb +14 -11
  16. data/lib/ruby_lsp/requests/folding_ranges.rb +38 -11
  17. data/lib/ruby_lsp/requests/formatting.rb +18 -5
  18. data/lib/ruby_lsp/requests/hover.rb +7 -6
  19. data/lib/ruby_lsp/requests/inlay_hints.rb +5 -4
  20. data/lib/ruby_lsp/requests/path_completion.rb +9 -3
  21. data/lib/ruby_lsp/requests/selection_ranges.rb +3 -3
  22. data/lib/ruby_lsp/requests/semantic_highlighting.rb +72 -8
  23. data/lib/ruby_lsp/requests/support/highlight_target.rb +5 -4
  24. data/lib/ruby_lsp/requests/support/rails_document_client.rb +7 -6
  25. data/lib/ruby_lsp/requests/support/selection_range.rb +1 -1
  26. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +2 -2
  27. data/lib/ruby_lsp/requests/support/sorbet.rb +5 -15
  28. data/lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb +39 -0
  29. data/lib/ruby_lsp/server.rb +4 -1
  30. data/lib/ruby_lsp/store.rb +11 -7
  31. data/lib/ruby_lsp/utils.rb +3 -0
  32. metadata +7 -5
@@ -21,16 +21,15 @@ module RubyLsp
21
21
 
22
22
  sig do
23
23
  params(
24
- uri: String,
25
24
  document: Document,
26
25
  range: Document::RangeShape,
27
26
  context: T::Hash[Symbol, T.untyped],
28
27
  ).void
29
28
  end
30
- def initialize(uri, document, range, context)
29
+ def initialize(document, range, context)
31
30
  super(document)
32
31
 
33
- @uri = uri
32
+ @uri = T.let(document.uri, String)
34
33
  @range = range
35
34
  @context = context
36
35
  end
@@ -49,7 +48,9 @@ module RubyLsp
49
48
  code_action if diagnostic.dig(:data, :correctable) && cover?(range)
50
49
  end
51
50
 
52
- code_actions << refactor_code_action(@range, @uri)
51
+ # Only add refactor actions if there's a non empty selection in the editor
52
+ code_actions << refactor_code_action(@range, @uri) unless @range.dig(:start) == @range.dig(:end)
53
+ code_actions
53
54
  end
54
55
 
55
56
  private
@@ -21,11 +21,11 @@ module RubyLsp
21
21
  class Diagnostics < BaseRequest
22
22
  extend T::Sig
23
23
 
24
- sig { params(uri: String, document: Document).void }
25
- def initialize(uri, document)
24
+ sig { params(document: Document).void }
25
+ def initialize(document)
26
26
  super(document)
27
27
 
28
- @uri = uri
28
+ @uri = T.let(document.uri, String)
29
29
  end
30
30
 
31
31
  sig { override.returns(T.nilable(T.all(T::Array[Support::RuboCopDiagnostic], Object))) }
@@ -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.sub("file://", "").start_with?(Dir.pwd)
37
+ return unless @uri.start_with?(WORKSPACE_URI)
38
38
 
39
39
  Support::RuboCopDiagnosticsRunner.instance.run(@uri, @document)
40
40
  end
@@ -29,14 +29,14 @@ module RubyLsp
29
29
  def initialize(document, position)
30
30
  super(document)
31
31
 
32
- @highlights = T.let([], T::Array[LanguageServer::Protocol::Interface::DocumentHighlight])
32
+ @highlights = T.let([], T::Array[Interface::DocumentHighlight])
33
33
  return unless document.parsed?
34
34
 
35
35
  position = document.create_scanner.find_char_position(position)
36
36
  @target = T.let(find(T.must(document.tree), position), T.nilable(Support::HighlightTarget))
37
37
  end
38
38
 
39
- sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::DocumentHighlight], Object)) }
39
+ sig { override.returns(T.all(T::Array[Interface::DocumentHighlight], Object)) }
40
40
  def run
41
41
  # no @target means the target is not highlightable
42
42
  visit(@document.tree) if @document.parsed? && @target
@@ -90,7 +90,7 @@ module RubyLsp
90
90
  sig { params(match: Support::HighlightTarget::HighlightMatch).void }
91
91
  def add_highlight(match)
92
92
  range = range_from_syntax_tree_node(match.node)
93
- @highlights << LanguageServer::Protocol::Interface::DocumentHighlight.new(range: range, kind: match.type)
93
+ @highlights << Interface::DocumentHighlight.new(range: range, kind: match.type)
94
94
  end
95
95
  end
96
96
  end
@@ -69,18 +69,18 @@ module RubyLsp
69
69
  end
70
70
  end
71
71
 
72
- sig { params(uri: String, document: Document).void }
73
- def initialize(uri, document)
72
+ sig { params(document: Document).void }
73
+ def initialize(document)
74
74
  super(document)
75
75
 
76
76
  # Match the version based on the version in the RBI file name. Notice that the `@` symbol is sanitized to `%40`
77
77
  # in the URI
78
- version_match = /(?<=%40)[\d.]+(?=\.rbi$)/.match(uri)
78
+ version_match = /(?<=%40)[\d.]+(?=\.rbi$)/.match(document.uri)
79
79
  @gem_version = T.let(version_match && version_match[0], T.nilable(String))
80
- @links = T.let([], T::Array[LanguageServer::Protocol::Interface::DocumentLink])
80
+ @links = T.let([], T::Array[Interface::DocumentLink])
81
81
  end
82
82
 
83
- sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::DocumentLink], Object)) }
83
+ sig { override.returns(T.all(T::Array[Interface::DocumentLink], Object)) }
84
84
  def run
85
85
  visit(@document.tree) if @document.parsed?
86
86
  @links
@@ -91,12 +91,12 @@ module RubyLsp
91
91
  match = node.value.match(%r{source://.*#\d+$})
92
92
  return unless match
93
93
 
94
- uri = T.cast(URI(match[0]), URI::Source)
94
+ uri = T.cast(URI(T.must(match[0])), URI::Source)
95
95
  gem_version = T.must(resolve_version(uri))
96
96
  file_path = self.class.gem_paths.dig(uri.gem_name, gem_version, uri.path)
97
97
  return if file_path.nil?
98
98
 
99
- @links << LanguageServer::Protocol::Interface::DocumentLink.new(
99
+ @links << Interface::DocumentLink.new(
100
100
  range: range_from_syntax_tree_node(node),
101
101
  target: "file://#{file_path}##{uri.line_number}",
102
102
  tooltip: "Jump to #{file_path}##{uri.line_number}",
@@ -66,12 +66,12 @@ module RubyLsp
66
66
  class SymbolHierarchyRoot
67
67
  extend T::Sig
68
68
 
69
- sig { returns(T::Array[LanguageServer::Protocol::Interface::DocumentSymbol]) }
69
+ sig { returns(T::Array[Interface::DocumentSymbol]) }
70
70
  attr_reader :children
71
71
 
72
72
  sig { void }
73
73
  def initialize
74
- @children = T.let([], T::Array[LanguageServer::Protocol::Interface::DocumentSymbol])
74
+ @children = T.let([], T::Array[Interface::DocumentSymbol])
75
75
  end
76
76
  end
77
77
 
@@ -82,11 +82,11 @@ module RubyLsp
82
82
  @root = T.let(SymbolHierarchyRoot.new, SymbolHierarchyRoot)
83
83
  @stack = T.let(
84
84
  [@root],
85
- T::Array[T.any(SymbolHierarchyRoot, LanguageServer::Protocol::Interface::DocumentSymbol)],
85
+ T::Array[T.any(SymbolHierarchyRoot, Interface::DocumentSymbol)],
86
86
  )
87
87
  end
88
88
 
89
- sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::DocumentSymbol], Object)) }
89
+ sig { override.returns(T.all(T::Array[Interface::DocumentSymbol], Object)) }
90
90
  def run
91
91
  visit(@document.tree) if @document.parsed?
92
92
  @root.children
@@ -108,7 +108,7 @@ module RubyLsp
108
108
 
109
109
  sig { override.params(node: SyntaxTree::Command).void }
110
110
  def visit_command(node)
111
- return unless ATTR_ACCESSORS.include?(node.message.value)
111
+ return visit(node.arguments) unless ATTR_ACCESSORS.include?(node.message.value)
112
112
 
113
113
  node.arguments.parts.each do |argument|
114
114
  next unless argument.is_a?(SyntaxTree::SymbolLiteral)
@@ -134,7 +134,9 @@ module RubyLsp
134
134
 
135
135
  sig { override.params(node: SyntaxTree::DefNode).void }
136
136
  def visit_def(node)
137
- if node.target&.value&.value == "self"
137
+ target = node.target
138
+
139
+ if target.is_a?(SyntaxTree::VarRef) && target.value.is_a?(SyntaxTree::Kw) && target.value.value == "self"
138
140
  name = "self.#{node.name.value}"
139
141
  kind = :method
140
142
  else
@@ -180,7 +182,8 @@ module RubyLsp
180
182
 
181
183
  sig { override.params(node: SyntaxTree::VarField).void }
182
184
  def visit_var_field(node)
183
- kind = case node.value
185
+ value = node.value
186
+ kind = case value
184
187
  when SyntaxTree::Const
185
188
  :constant
186
189
  when SyntaxTree::CVar, SyntaxTree::IVar
@@ -190,10 +193,10 @@ module RubyLsp
190
193
  end
191
194
 
192
195
  create_document_symbol(
193
- name: node.value.value,
196
+ name: value.value,
194
197
  kind: kind,
195
198
  range_node: node,
196
- selection_range_node: node.value,
199
+ selection_range_node: value,
197
200
  )
198
201
  end
199
202
 
@@ -205,10 +208,10 @@ module RubyLsp
205
208
  kind: Symbol,
206
209
  range_node: SyntaxTree::Node,
207
210
  selection_range_node: SyntaxTree::Node,
208
- ).returns(LanguageServer::Protocol::Interface::DocumentSymbol)
211
+ ).returns(Interface::DocumentSymbol)
209
212
  end
210
213
  def create_document_symbol(name:, kind:, range_node:, selection_range_node:)
211
- symbol = LanguageServer::Protocol::Interface::DocumentSymbol.new(
214
+ symbol = Interface::DocumentSymbol.new(
212
215
  name: name,
213
216
  kind: SYMBOL_KIND[kind],
214
217
  range: range_from_syntax_tree_node(range_node),
@@ -24,7 +24,6 @@ module RubyLsp
24
24
  SyntaxTree::BlockNode,
25
25
  SyntaxTree::Case,
26
26
  SyntaxTree::ClassDeclaration,
27
- SyntaxTree::Command,
28
27
  SyntaxTree::For,
29
28
  SyntaxTree::HashLiteral,
30
29
  SyntaxTree::Heredoc,
@@ -64,11 +63,11 @@ module RubyLsp
64
63
  def initialize(document)
65
64
  super
66
65
 
67
- @ranges = T.let([], T::Array[LanguageServer::Protocol::Interface::FoldingRange])
66
+ @ranges = T.let([], T::Array[Interface::FoldingRange])
68
67
  @partial_range = T.let(nil, T.nilable(PartialRange))
69
68
  end
70
69
 
71
- sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::FoldingRange], Object)) }
70
+ sig { override.returns(T.all(T::Array[Interface::FoldingRange], Object)) }
72
71
  def run
73
72
  if @document.parsed?
74
73
  visit(@document.tree)
@@ -100,6 +99,11 @@ module RubyLsp
100
99
  add_call_range(node)
101
100
  return
102
101
  end
102
+ when SyntaxTree::Command
103
+ unless same_lines_for_command_and_block?(node)
104
+ location = node.location
105
+ add_lines_range(location.start_line, location.end_line - 1)
106
+ end
103
107
  when SyntaxTree::DefNode
104
108
  add_def_range(node)
105
109
  when SyntaxTree::StringConcat
@@ -110,6 +114,17 @@ module RubyLsp
110
114
  super
111
115
  end
112
116
 
117
+ # This is to prevent duplicate ranges
118
+ sig { params(node: T.any(SyntaxTree::Command, SyntaxTree::CommandCall)).returns(T::Boolean) }
119
+ def same_lines_for_command_and_block?(node)
120
+ node_block = node.block
121
+ return false unless node_block
122
+
123
+ location = node.location
124
+ block_location = node_block.location
125
+ block_location.start_line == location.start_line && block_location.end_line == location.end_line
126
+ end
127
+
113
128
  class PartialRange
114
129
  extend T::Sig
115
130
 
@@ -146,9 +161,9 @@ module RubyLsp
146
161
  node.is_a?(SyntaxTree::Comment) && @end_line + 1 != node.location.start_line - 1
147
162
  end
148
163
 
149
- sig { returns(LanguageServer::Protocol::Interface::FoldingRange) }
164
+ sig { returns(Interface::FoldingRange) }
150
165
  def to_range
151
- LanguageServer::Protocol::Interface::FoldingRange.new(
166
+ Interface::FoldingRange.new(
152
167
  start_line: @start_line,
153
168
  end_line: @end_line,
154
169
  kind: @kind,
@@ -205,7 +220,8 @@ module RubyLsp
205
220
 
206
221
  sig { params(node: T.any(SyntaxTree::CallNode, SyntaxTree::CommandCall)).void }
207
222
  def add_call_range(node)
208
- receiver = T.let(node.receiver, SyntaxTree::Node)
223
+ receiver = T.let(node.receiver, T.nilable(SyntaxTree::Node))
224
+
209
225
  loop do
210
226
  case receiver
211
227
  when SyntaxTree::CallNode
@@ -223,18 +239,27 @@ module RubyLsp
223
239
  end
224
240
  end
225
241
 
226
- add_lines_range(receiver.location.start_line, node.location.end_line - 1) if receiver
242
+ if receiver
243
+ unless node.is_a?(SyntaxTree::CommandCall) && same_lines_for_command_and_block?(node)
244
+ add_lines_range(
245
+ receiver.location.start_line,
246
+ node.location.end_line - 1,
247
+ )
248
+ end
249
+ end
227
250
 
228
251
  visit(node.arguments)
252
+ visit(node.block) if node.is_a?(SyntaxTree::CommandCall)
229
253
  end
230
254
 
231
255
  sig { params(node: SyntaxTree::DefNode).void }
232
256
  def add_def_range(node)
233
257
  # For an endless method with no arguments, `node.params` returns `nil` for Ruby 3.0, but a `Syntax::Params`
234
258
  # for Ruby 3.1
235
- return unless node.params
259
+ params = node.params
260
+ return unless params
236
261
 
237
- params_location = node.params.location
262
+ params_location = params.location
238
263
 
239
264
  if params_location.start_line < params_location.end_line
240
265
  add_lines_range(params_location.end_line, node.location.end_line - 1)
@@ -253,7 +278,9 @@ module RubyLsp
253
278
 
254
279
  sig { params(node: SyntaxTree::Node, statements: SyntaxTree::Statements).void }
255
280
  def add_statements_range(node, statements)
256
- add_lines_range(node.location.start_line, statements.body.last.location.end_line) unless statements.empty?
281
+ return if statements.empty?
282
+
283
+ add_lines_range(node.location.start_line, T.must(statements.body.last).location.end_line)
257
284
  end
258
285
 
259
286
  sig { params(node: SyntaxTree::StringConcat).void }
@@ -268,7 +295,7 @@ module RubyLsp
268
295
  def add_lines_range(start_line, end_line)
269
296
  return if start_line >= end_line
270
297
 
271
- @ranges << LanguageServer::Protocol::Interface::FoldingRange.new(
298
+ @ranges << Interface::FoldingRange.new(
272
299
  start_line: start_line - 1,
273
300
  end_line: end_line - 1,
274
301
  kind: "region",
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "ruby_lsp/requests/support/rubocop_formatting_runner"
5
+ require "ruby_lsp/requests/support/syntax_tree_formatting_runner"
5
6
 
6
7
  module RubyLsp
7
8
  module Requests
@@ -22,14 +23,23 @@ module RubyLsp
22
23
  # ```
23
24
  class Formatting < BaseRequest
24
25
  class Error < StandardError; end
26
+ class InvalidFormatter < StandardError; end
25
27
 
26
28
  extend T::Sig
27
29
 
28
- sig { params(uri: String, document: Document).void }
29
- def initialize(uri, document)
30
+ sig { params(document: Document, formatter: String).void }
31
+ def initialize(document, formatter: "auto")
30
32
  super(document)
31
33
 
32
- @uri = uri
34
+ @uri = T.let(document.uri, String)
35
+ @formatter = T.let(
36
+ if formatter == "auto"
37
+ defined?(Support::RuboCopFormattingRunner) ? "rubocop" : "syntax_tree"
38
+ else
39
+ formatter
40
+ end,
41
+ String,
42
+ )
33
43
  end
34
44
 
35
45
  sig { override.returns(T.nilable(T.all(T::Array[Interface::TextEdit], Object))) }
@@ -60,10 +70,13 @@ module RubyLsp
60
70
 
61
71
  sig { returns(T.nilable(String)) }
62
72
  def formatted_file
63
- if defined?(Support::RuboCopFormattingRunner)
73
+ case @formatter
74
+ when "rubocop"
64
75
  Support::RuboCopFormattingRunner.instance.run(@uri, @document)
76
+ when "syntax_tree"
77
+ Support::SyntaxTreeFormattingRunner.instance.run(@uri, @document)
65
78
  else
66
- SyntaxTree.format(@document.source)
79
+ raise InvalidFormatter, "Unknown formatter: #{@formatter}"
67
80
  end
68
81
  end
69
82
  end
@@ -36,7 +36,7 @@ module RubyLsp
36
36
  @position = T.let(document.create_scanner.find_char_position(position), Integer)
37
37
  end
38
38
 
39
- sig { override.returns(T.nilable(LanguageServer::Protocol::Interface::Hover)) }
39
+ sig { override.returns(T.nilable(Interface::Hover)) }
40
40
  def run
41
41
  return unless @document.parsed?
42
42
 
@@ -48,9 +48,10 @@ module RubyLsp
48
48
  message = target.message
49
49
  generate_rails_document_link_hover(message.value, message)
50
50
  when SyntaxTree::CallNode
51
- return if target.message == :call
51
+ message = target.message
52
+ return if message.is_a?(Symbol)
52
53
 
53
- generate_rails_document_link_hover(target.message.value, target.message)
54
+ generate_rails_document_link_hover(message.value, message)
54
55
  when SyntaxTree::ConstPathRef
55
56
  constant_name = full_constant_name(target)
56
57
  generate_rails_document_link_hover(constant_name, target)
@@ -60,18 +61,18 @@ module RubyLsp
60
61
  private
61
62
 
62
63
  sig do
63
- params(name: String, node: SyntaxTree::Node).returns(T.nilable(LanguageServer::Protocol::Interface::Hover))
64
+ params(name: String, node: SyntaxTree::Node).returns(T.nilable(Interface::Hover))
64
65
  end
65
66
  def generate_rails_document_link_hover(name, node)
66
67
  urls = Support::RailsDocumentClient.generate_rails_document_urls(name)
67
68
 
68
69
  return if urls.empty?
69
70
 
70
- contents = LanguageServer::Protocol::Interface::MarkupContent.new(
71
+ contents = Interface::MarkupContent.new(
71
72
  kind: "markdown",
72
73
  value: urls.join("\n\n"),
73
74
  )
74
- LanguageServer::Protocol::Interface::Hover.new(
75
+ Interface::Hover.new(
75
76
  range: range_from_syntax_tree_node(node),
76
77
  contents: contents,
77
78
  )
@@ -25,11 +25,11 @@ module RubyLsp
25
25
  def initialize(document, range)
26
26
  super(document)
27
27
 
28
- @hints = T.let([], T::Array[LanguageServer::Protocol::Interface::InlayHint])
28
+ @hints = T.let([], T::Array[Interface::InlayHint])
29
29
  @range = range
30
30
  end
31
31
 
32
- sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::InlayHint], Object)) }
32
+ sig { override.returns(T.all(T::Array[Interface::InlayHint], Object)) }
33
33
  def run
34
34
  visit(@document.tree) if @document.parsed?
35
35
  @hints
@@ -37,12 +37,13 @@ module RubyLsp
37
37
 
38
38
  sig { override.params(node: SyntaxTree::Rescue).void }
39
39
  def visit_rescue(node)
40
- return unless node.exception.nil?
40
+ exception = node.exception
41
+ return unless exception.nil? || exception.exceptions.nil?
41
42
 
42
43
  loc = node.location
43
44
  return unless visible?(node, @range)
44
45
 
45
- @hints << LanguageServer::Protocol::Interface::InlayHint.new(
46
+ @hints << Interface::InlayHint.new(
46
47
  position: { line: loc.start_line - 1, character: loc.start_column + RESCUE_STRING_LENGTH },
47
48
  label: "StandardError",
48
49
  padding_left: true,
@@ -63,13 +63,19 @@ module RubyLsp
63
63
 
64
64
  case matched
65
65
  when SyntaxTree::Command, SyntaxTree::CallNode, SyntaxTree::CommandCall
66
- return unless matched.message.value == "require"
66
+ message = matched.message
67
+ return if message.is_a?(Symbol)
68
+ return unless message.value == "require"
67
69
 
68
70
  args = matched.arguments
69
71
  args = args.arguments if args.is_a?(SyntaxTree::ArgParen)
72
+ return if args.nil? || args.is_a?(SyntaxTree::ArgsForward)
70
73
 
71
- path_node = args.parts.first.parts.first
72
- return unless path_node
74
+ argument = args.parts.first
75
+ return unless argument.is_a?(SyntaxTree::StringLiteral)
76
+
77
+ path_node = argument.parts.first
78
+ return unless path_node.is_a?(SyntaxTree::TStringContent)
73
79
  return unless (path_node.location.start_char..path_node.location.end_char).cover?(position)
74
80
 
75
81
  path_node
@@ -100,12 +100,12 @@ module RubyLsp
100
100
  end
101
101
  def create_selection_range(location, parent = nil)
102
102
  RubyLsp::Requests::Support::SelectionRange.new(
103
- range: LanguageServer::Protocol::Interface::Range.new(
104
- start: LanguageServer::Protocol::Interface::Position.new(
103
+ range: Interface::Range.new(
104
+ start: Interface::Position.new(
105
105
  line: location.start_line - 1,
106
106
  character: location.start_column,
107
107
  ),
108
- end: LanguageServer::Protocol::Interface::Position.new(
108
+ end: Interface::Position.new(
109
109
  line: location.end_line - 1,
110
110
  character: location.end_column,
111
111
  ),
@@ -122,7 +122,7 @@ module RubyLsp
122
122
  sig do
123
123
  override.returns(
124
124
  T.any(
125
- LanguageServer::Protocol::Interface::SemanticTokens,
125
+ Interface::SemanticTokens,
126
126
  T.all(T::Array[SemanticToken], Object),
127
127
  ),
128
128
  )
@@ -143,7 +143,7 @@ module RubyLsp
143
143
  visit(node.receiver)
144
144
 
145
145
  message = node.message
146
- if message != :call && !special_method?(message.value)
146
+ if !message.is_a?(Symbol) && !special_method?(message.value)
147
147
  type = Support::Sorbet.annotation?(node) ? :type : :method
148
148
 
149
149
  add_token(message.location, type)
@@ -160,6 +160,7 @@ module RubyLsp
160
160
  add_token(node.message.location, :method)
161
161
  end
162
162
  visit(node.arguments)
163
+ visit(node.block)
163
164
  end
164
165
 
165
166
  sig { override.params(node: SyntaxTree::CommandCall).void }
@@ -167,8 +168,10 @@ module RubyLsp
167
168
  return super unless visible?(node, @range)
168
169
 
169
170
  visit(node.receiver)
170
- add_token(node.message.location, :method)
171
+ message = node.message
172
+ add_token(message.location, :method) unless message.is_a?(Symbol)
171
173
  visit(node.arguments)
174
+ visit(node.block)
172
175
  end
173
176
 
174
177
  sig { override.params(node: SyntaxTree::Const).void }
@@ -203,7 +206,7 @@ module RubyLsp
203
206
  def visit_params(node)
204
207
  return super unless visible?(node, @range)
205
208
 
206
- node.keywords.each do |keyword,|
209
+ node.keywords.each do |keyword, *|
207
210
  location = keyword.location
208
211
  add_token(location_without_colon(location), :parameter)
209
212
  end
@@ -213,7 +216,7 @@ module RubyLsp
213
216
  end
214
217
 
215
218
  rest = node.keyword_rest
216
- if rest && !rest.is_a?(SyntaxTree::ArgsForward)
219
+ if rest && !rest.is_a?(SyntaxTree::ArgsForward) && !rest.is_a?(Symbol)
217
220
  name = rest.name
218
221
  add_token(name.location, :parameter) if name
219
222
  end
@@ -240,9 +243,13 @@ module RubyLsp
240
243
  when SyntaxTree::Ident
241
244
  type = type_for_local(value)
242
245
  add_token(value.location, type)
246
+ when Symbol
247
+ # do nothing
243
248
  else
244
249
  visit(value)
245
250
  end
251
+
252
+ super
246
253
  end
247
254
 
248
255
  sig { override.params(node: SyntaxTree::VarRef).void }
@@ -255,27 +262,84 @@ module RubyLsp
255
262
  when SyntaxTree::Ident
256
263
  type = type_for_local(value)
257
264
  add_token(value.location, type)
265
+ when Symbol
266
+ # do nothing
258
267
  else
259
268
  visit(value)
260
269
  end
261
270
  end
262
271
 
272
+ # All block locals are variables. E.g.: [].each do |x; block_local|
273
+ sig { override.params(node: SyntaxTree::BlockVar).void }
274
+ def visit_block_var(node)
275
+ node.locals.each { |local| add_token(local.location, :variable) }
276
+ super
277
+ end
278
+
279
+ # All lambda locals are variables. E.g.: ->(x; lambda_local) {}
280
+ sig { override.params(node: SyntaxTree::LambdaVar).void }
281
+ def visit_lambda_var(node)
282
+ node.locals.each { |local| add_token(local.location, :variable) }
283
+ super
284
+ end
285
+
263
286
  sig { override.params(node: SyntaxTree::VCall).void }
264
287
  def visit_vcall(node)
265
288
  return super unless visible?(node, @range)
266
289
 
267
- return if special_method?(node.value.value)
290
+ # A VCall may exist as a local in the current_scope. This happens when used named capture groups in a regexp
291
+ ident = node.value
292
+ value = ident.value
293
+ local = current_scope.find_local(value)
294
+ return if local.nil? && special_method?(value)
295
+
296
+ type = if local
297
+ :variable
298
+ elsif Support::Sorbet.annotation?(node)
299
+ :type
300
+ else
301
+ :method
302
+ end
268
303
 
269
- type = Support::Sorbet.annotation?(node) ? :type : :method
270
304
  add_token(node.value.location, type)
271
305
  end
272
306
 
307
+ sig { override.params(node: SyntaxTree::Binary).void }
308
+ def visit_binary(node)
309
+ # It's important to visit the regexp first in the WithScope module
310
+ super
311
+
312
+ # You can only capture local variables with regexp by using the =~ operator
313
+ return unless node.operator == :=~
314
+
315
+ left = node.left
316
+ # The regexp needs to be on the left hand side of the =~ for local variable capture
317
+ return unless left.is_a?(SyntaxTree::RegexpLiteral)
318
+
319
+ parts = left.parts
320
+ return unless parts.one?
321
+
322
+ content = parts.first
323
+ return unless content.is_a?(SyntaxTree::TStringContent)
324
+
325
+ # For each capture name we find in the regexp, look for a local in the current_scope
326
+ Regexp.new(content.value, Regexp::FIXEDENCODING).names.each do |name|
327
+ local = current_scope.find_local(name)
328
+ next unless local
329
+
330
+ local.definitions.each { |definition| add_token(definition, :variable) }
331
+ end
332
+ end
333
+
273
334
  sig { override.params(node: SyntaxTree::ClassDeclaration).void }
274
335
  def visit_class(node)
275
336
  return super unless visible?(node, @range)
276
337
 
277
338
  add_token(node.constant.location, :class, [:declaration])
278
- add_token(node.superclass.location, :class) if node.superclass
339
+
340
+ superclass = node.superclass
341
+ add_token(superclass.location, :class) if superclass
342
+
279
343
  visit(node.bodystmt)
280
344
  end
281
345
 
@@ -7,8 +7,8 @@ module RubyLsp
7
7
  class HighlightTarget
8
8
  extend T::Sig
9
9
 
10
- READ = LanguageServer::Protocol::Constant::DocumentHighlightKind::READ
11
- WRITE = LanguageServer::Protocol::Constant::DocumentHighlightKind::WRITE
10
+ READ = Constant::DocumentHighlightKind::READ
11
+ WRITE = Constant::DocumentHighlightKind::WRITE
12
12
 
13
13
  class HighlightMatch
14
14
  extend T::Sig
@@ -84,10 +84,11 @@ module RubyLsp
84
84
  SyntaxTree::KwRestParam, SyntaxTree::BlockArg
85
85
  node.name&.value
86
86
  when SyntaxTree::VarField, SyntaxTree::VarRef, SyntaxTree::VCall
87
- node.value&.value
87
+ value = node.value
88
+ value.value unless value.nil? || value.is_a?(Symbol)
88
89
  when SyntaxTree::CallNode, SyntaxTree::Command, SyntaxTree::CommandCall
89
90
  message = node.message
90
- message != :call ? message.value : nil
91
+ message.value unless message.is_a?(Symbol)
91
92
  when SyntaxTree::ClassDeclaration, SyntaxTree::ModuleDeclaration
92
93
  node.constant.constant.value
93
94
  end