ruby-lsp 0.4.1 → 0.4.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0812eed9a874fa78fbe75cf2e8dc2bd58c2dfb67a61353f732aaba965ae9d66f'
4
- data.tar.gz: 6d9a567aa54c95f5fd7ada3756d4b5575f068bee90f19f99f83f485d91de83a0
3
+ metadata.gz: c5d9c6b487aab55ce25eb2741fff92afa8ca0ee544f21374debacce7f887cc50
4
+ data.tar.gz: 8da9da85900753306f5e8558bc1a42f25b7173ffe1087ea70612c10ef5558e27
5
5
  SHA512:
6
- metadata.gz: 53311978b0c6d32a34e1e69ddeea1d4b154ee0e2bc1cd426fd7e4db6ab46eeb7a5dff7d0516b5c2c722f95f05a0c5051fe7caf874e14740b5023b5f0e79da962
7
- data.tar.gz: ce04150602707622dd0d463a35a2a9c94fe7b24b1e376dbeeae4e45f4487438219f8ab48056658946e9f7cc56136714496fdfbddf7513b77fea334aac2687ae3
6
+ metadata.gz: 3dca630b9cae262f1af8d0aef0300ab7399dafd348db5f578ed7fdffb22a7afd48fe9467aa65e467fb50e16663f8fad32f1d0cc46b30819b06a6b7472700dcc0
7
+ data.tar.gz: f048c6c9fe14d20edefeca7ac504942af34c61699163f0d09ec54f4d80e7469e1bb1762757b96aa3d8d06a6e8168473dacfc932112b2d31ca4b380748eef8899
data/README.md CHANGED
@@ -45,8 +45,9 @@ group :development do
45
45
  end
46
46
  ```
47
47
 
48
- If using VS Code, install the [Ruby LSP extension](https://github.com/Shopify/vscode-ruby-lsp) to get the extra features in
49
- the editor.
48
+ If using VS Code, install the [Ruby LSP extension](https://github.com/Shopify/vscode-ruby-lsp) to get the extra features
49
+ in the editor. See [editors](https://github.com/Shopify/ruby-lsp/blob/main/EDITORS.md) for community instructions on
50
+ setting up the Ruby LSP in different editors.
50
51
 
51
52
  See the [documentation](https://shopify.github.io/ruby-lsp) for more in-depth details about the
52
53
  [supported features](https://shopify.github.io/ruby-lsp/RubyLsp/Requests.html).
@@ -114,27 +115,25 @@ To add a new expectations test runner for a new request handler:
114
115
 
115
116
  ## Debugging
116
117
 
117
- ### Tracing LSP requests and responses
118
-
119
- LSP server tracing can be controlled through the `ruby lsp.trace.server` config key in the `.vscode/settings.json` config file.
120
-
121
- Possible values are:
122
-
123
- * `off`: no tracing
124
- * `messages`: display requests and responses notifications
125
- * `verbose`: display each request and response as JSON
126
-
127
- ### Debugging using VS Code
128
-
129
- The `launch.json` contains a 'Minitest - current file' configuration for the debugger.
130
-
131
- 1. Add a breakpoint using the VS Code UI.
132
- 1. Open the relevant test file.
133
- 1. Open the **Run and Debug** panel on the sidebar.
134
- 1. Ensure `Minitest - current file` is selected in the top dropdown.
135
- 1. Press `F5` OR click the green triangle next to the top dropdown. VS Code will then run the test file with debugger activated.
136
- 1. When the breakpoint is triggered, the process will pause and VS Code will connect to the debugger and activate the debugger UI.
137
- 1. Open the Debug Console view to use the debugger's REPL.
118
+ ### Debugging Tests
119
+
120
+ 1. Open the test file.
121
+ 2. Set a breakpoint(s) on lines by clicking next to their numbers.
122
+ 3. Open VS Code's `Run and Debug` panel.
123
+ 4. At the top of the panel, select `Minitset - current file` and click the green triangle (or press F5).
124
+
125
+ ### Debugging Running Ruby LSP Process
126
+
127
+ 1. Open the `vscode-ruby-lsp` project in VS Code.
128
+ 2. [`vscode-ruby-lsp`] Open VS Code's `Run and Debug` panel.
129
+ 3. [`vscode-ruby-lsp`] Select `Run Extension` and click the green triangle (or press F5).
130
+ 4. [`vscode-ruby-lsp`] Now VS Code will:
131
+ - Open another workspace as the `Extension Development Host`.
132
+ - Run `vscode-ruby-lsp` extension in debug mode, which will start a new `ruby-lsp` process with the `--debug` flag.
133
+ 5. Open `ruby-lsp` in VS Code.
134
+ 6. [`ruby-lsp`] Run `bin/rdbg -A` to connect to the running `ruby-lsp` process.
135
+ 7. [`ruby-lsp`] Use commands like `b <file>:<line>` or `b Class#method` to set breakpoints and type `c` to continue the process.
136
+ 8. In your `Extension Development Host` project (e.g. [`Tapioca`](https://github.com/Shopify/tapioca)), trigger the request that will hit the breakpoint.
138
137
 
139
138
  ## Spell Checking
140
139
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.1
1
+ 0.4.2
data/exe/ruby-lsp CHANGED
@@ -19,4 +19,16 @@ rescue
19
19
  end
20
20
 
21
21
  require_relative "../lib/ruby_lsp/internal"
22
+
23
+ if ARGV.include?("--debug")
24
+ sockets_dir = "/tmp/ruby-lsp-debug-sockets"
25
+ Dir.mkdir(sockets_dir) unless Dir.exist?(sockets_dir)
26
+ # ruby-debug-ENV["USER"] is an implicit naming pattern in ruby/debug
27
+ # if it's not present, rdbg will not find the socket
28
+ socket_identifier = "ruby-debug-#{ENV["USER"]}-#{File.basename(Dir.pwd)}.sock"
29
+ ENV["RUBY_DEBUG_SOCK_PATH"] = "#{sockets_dir}/#{socket_identifier}"
30
+
31
+ require "debug/open_nonstop"
32
+ end
33
+
22
34
  RubyLsp::Server.new.start
@@ -66,6 +66,16 @@ module RubyLsp
66
66
  when "textDocument/formatting"
67
67
  begin
68
68
  formatting(uri)
69
+ rescue Requests::Formatting::InvalidFormatter => error
70
+ @notifications << Notification.new(
71
+ message: "window/showMessage",
72
+ params: Interface::ShowMessageParams.new(
73
+ type: Constant::MessageType::ERROR,
74
+ message: "Configuration error: #{error.message}",
75
+ ),
76
+ )
77
+
78
+ nil
69
79
  rescue StandardError => error
70
80
  @notifications << Notification.new(
71
81
  message: "window/showMessage",
@@ -197,7 +207,7 @@ module RubyLsp
197
207
 
198
208
  sig { params(uri: String).returns(T.nilable(T::Array[Interface::TextEdit])) }
199
209
  def formatting(uri)
200
- Requests::Formatting.new(uri, @store.get(uri)).run
210
+ Requests::Formatting.new(uri, @store.get(uri), formatter: @store.formatter).run
201
211
  end
202
212
 
203
213
  sig do
@@ -259,6 +269,15 @@ module RubyLsp
259
269
  ),
260
270
  )
261
271
  raise Requests::CodeActionResolve::CodeActionError
272
+ when Requests::CodeActionResolve::Error::InvalidTargetRange
273
+ @notifications << Notification.new(
274
+ message: "window/showMessage",
275
+ params: Interface::ShowMessageParams.new(
276
+ type: Constant::MessageType::ERROR,
277
+ message: "Couldn't find an appropriate location to place extracted refactor",
278
+ ),
279
+ )
280
+ raise Requests::CodeActionResolve::CodeActionError
262
281
  else
263
282
  result
264
283
  end
@@ -300,9 +319,26 @@ module RubyLsp
300
319
  def initialize_request(options)
301
320
  @store.clear
302
321
  @store.encoding = options.dig(:capabilities, :general, :positionEncodings)
303
- enabled_features = options.dig(:initializationOptions, :enabledFeatures) || []
322
+ formatter = options.dig(:initializationOptions, :formatter)
323
+ @store.formatter = formatter unless formatter.nil?
324
+
325
+ configured_features = options.dig(:initializationOptions, :enabledFeatures)
326
+
327
+ enabled_features = case configured_features
328
+ when Array
329
+ # If the configuration is using an array, then absent features are disabled and present ones are enabled. That's
330
+ # why we use `false` as the default value
331
+ Hash.new(false).merge!(configured_features.to_h { |feature| [feature, true] })
332
+ when Hash
333
+ # If the configuration is already a hash, merge it with a default value of `true`. That way clients don't have
334
+ # to opt-in to every single feature
335
+ Hash.new(true).merge!(configured_features)
336
+ else
337
+ # If no configuration was passed by the client, just enable every feature
338
+ Hash.new(true)
339
+ end
304
340
 
305
- document_symbol_provider = if enabled_features.include?("documentSymbols")
341
+ document_symbol_provider = if enabled_features["documentSymbols"]
306
342
  Interface::DocumentSymbolClientCapabilities.new(
307
343
  hierarchical_document_symbol_support: true,
308
344
  symbol_kind: {
@@ -311,19 +347,19 @@ module RubyLsp
311
347
  )
312
348
  end
313
349
 
314
- document_link_provider = if enabled_features.include?("documentLink")
350
+ document_link_provider = if enabled_features["documentLink"]
315
351
  Interface::DocumentLinkOptions.new(resolve_provider: false)
316
352
  end
317
353
 
318
- hover_provider = if enabled_features.include?("hover")
354
+ hover_provider = if enabled_features["hover"]
319
355
  Interface::HoverClientCapabilities.new(dynamic_registration: false)
320
356
  end
321
357
 
322
- folding_ranges_provider = if enabled_features.include?("foldingRanges")
358
+ folding_ranges_provider = if enabled_features["foldingRanges"]
323
359
  Interface::FoldingRangeClientCapabilities.new(line_folding_only: true)
324
360
  end
325
361
 
326
- semantic_tokens_provider = if enabled_features.include?("semanticHighlighting")
362
+ semantic_tokens_provider = if enabled_features["semanticHighlighting"]
327
363
  Interface::SemanticTokensRegistrationOptions.new(
328
364
  document_selector: { scheme: "file", language: "ruby" },
329
365
  legend: Interface::SemanticTokensLegend.new(
@@ -335,29 +371,29 @@ module RubyLsp
335
371
  )
336
372
  end
337
373
 
338
- diagnostics_provider = if enabled_features.include?("diagnostics")
374
+ diagnostics_provider = if enabled_features["diagnostics"]
339
375
  {
340
376
  interFileDependencies: false,
341
377
  workspaceDiagnostics: false,
342
378
  }
343
379
  end
344
380
 
345
- on_type_formatting_provider = if enabled_features.include?("onTypeFormatting")
381
+ on_type_formatting_provider = if enabled_features["onTypeFormatting"]
346
382
  Interface::DocumentOnTypeFormattingOptions.new(
347
383
  first_trigger_character: "{",
348
384
  more_trigger_character: ["\n", "|"],
349
385
  )
350
386
  end
351
387
 
352
- code_action_provider = if enabled_features.include?("codeActions")
388
+ code_action_provider = if enabled_features["codeActions"]
353
389
  Interface::CodeActionOptions.new(resolve_provider: true)
354
390
  end
355
391
 
356
- inlay_hint_provider = if enabled_features.include?("inlayHint")
392
+ inlay_hint_provider = if enabled_features["inlayHint"]
357
393
  Interface::InlayHintOptions.new(resolve_provider: false)
358
394
  end
359
395
 
360
- completion_provider = if enabled_features.include?("completion")
396
+ completion_provider = if enabled_features["completion"]
361
397
  Interface::CompletionOptions.new(
362
398
  resolve_provider: false,
363
399
  trigger_characters: ["/"],
@@ -370,14 +406,14 @@ module RubyLsp
370
406
  change: Constant::TextDocumentSyncKind::INCREMENTAL,
371
407
  open_close: true,
372
408
  ),
373
- selection_range_provider: enabled_features.include?("selectionRanges"),
409
+ selection_range_provider: enabled_features["selectionRanges"],
374
410
  hover_provider: hover_provider,
375
411
  document_symbol_provider: document_symbol_provider,
376
412
  document_link_provider: document_link_provider,
377
413
  folding_range_provider: folding_ranges_provider,
378
414
  semantic_tokens_provider: semantic_tokens_provider,
379
- document_formatting_provider: enabled_features.include?("formatting"),
380
- document_highlight_provider: enabled_features.include?("documentHighlights"),
415
+ document_formatting_provider: enabled_features["formatting"] && formatter != "none",
416
+ document_highlight_provider: enabled_features["documentHighlights"],
381
417
  code_action_provider: code_action_provider,
382
418
  document_on_type_formatting_provider: on_type_formatting_provider,
383
419
  diagnostic_provider: diagnostics_provider,
@@ -5,6 +5,7 @@ require "sorbet-runtime"
5
5
  require "syntax_tree"
6
6
  require "language_server-protocol"
7
7
  require "benchmark"
8
+ require "bundler"
8
9
 
9
10
  require "ruby-lsp"
10
11
  require "ruby_lsp/utils"
@@ -28,6 +28,14 @@ module RubyLsp
28
28
  sig { abstract.returns(Object) }
29
29
  def run; end
30
30
 
31
+ # Syntax Tree implements `visit_all` using `map` instead of `each` for users who want to use the pattern
32
+ # `result = visitor.visit(tree)`. However, we don't use that pattern and should avoid producing a new array for
33
+ # every single node visited
34
+ sig { params(nodes: T::Array[SyntaxTree::Node]).void }
35
+ def visit_all(nodes)
36
+ nodes.each { |node| visit(node) }
37
+ end
38
+
31
39
  sig { params(node: SyntaxTree::Node).returns(LanguageServer::Protocol::Interface::Range) }
32
40
  def range_from_syntax_tree_node(node)
33
41
  loc = node.location
@@ -89,14 +97,15 @@ module RubyLsp
89
97
  # If the node's start character is already past the position, then we should've found the closest node already
90
98
  break if position < loc.start_char
91
99
 
100
+ # If there are node types to filter by, and the current node is not one of those types, then skip it
101
+ next if node_types.any? && node_types.none? { |type| candidate.is_a?(type) }
102
+
92
103
  # If the current node is narrower than or equal to the previous closest node, then it is more precise
93
104
  closest_loc = closest.location
94
105
  if loc.end_char - loc.start_char <= closest_loc.end_char - closest_loc.start_char
95
106
  parent = T.let(closest, SyntaxTree::Node)
96
107
  closest = candidate
97
108
  end
98
-
99
- break if node_types.any? { |type| candidate.is_a?(type) }
100
109
  end
101
110
 
102
111
  [closest, parent]
@@ -30,6 +30,7 @@ module RubyLsp
30
30
  class Error < ::T::Enum
31
31
  enums do
32
32
  EmptySelection = new
33
+ InvalidTargetRange = new
33
34
  end
34
35
  end
35
36
 
@@ -45,11 +46,44 @@ module RubyLsp
45
46
  source_range = @code_action.dig(:data, :range)
46
47
  return Error::EmptySelection if source_range[:start] == source_range[:end]
47
48
 
49
+ return Error::InvalidTargetRange if @document.syntax_error?
50
+
48
51
  scanner = @document.create_scanner
49
52
  start_index = scanner.find_char_position(source_range[:start])
50
53
  end_index = scanner.find_char_position(source_range[:end])
51
- extraction_source = T.must(@document.source[start_index...end_index])
52
- source_line_indentation = T.must(T.must(@document.source.lines[source_range.dig(:start, :line)])[/\A */]).size
54
+ extracted_source = T.must(@document.source[start_index...end_index])
55
+
56
+ # Find the closest statements node, so that we place the refactor in a valid position
57
+ closest_statements = locate(T.must(@document.tree), start_index, node_types: [SyntaxTree::Statements]).first
58
+ return Error::InvalidTargetRange if closest_statements.nil?
59
+
60
+ # Find the node with the end line closest to the requested position, so that we can place the refactor
61
+ # immediately after that closest node
62
+ closest_node = closest_statements.child_nodes.compact.min_by do |node|
63
+ distance = source_range.dig(:start, :line) - (node.location.end_line - 1)
64
+ distance <= 0 ? Float::INFINITY : distance
65
+ end
66
+
67
+ # When trying to extract the first node inside of a statements block, then we can just select one line above it
68
+ target_line = if closest_node == closest_statements.child_nodes.first
69
+ closest_node.location.start_line - 1
70
+ else
71
+ closest_node.location.end_line
72
+ end
73
+
74
+ lines = @document.source.lines
75
+ indentation = T.must(T.must(lines[target_line - 1])[/\A */]).size
76
+
77
+ target_range = {
78
+ start: { line: target_line, character: indentation },
79
+ end: { line: target_line, character: indentation },
80
+ }
81
+
82
+ variable_source = if T.must(lines[target_line]).strip.empty?
83
+ "\n#{" " * indentation}#{NEW_VARIABLE_NAME} = #{extracted_source}"
84
+ else
85
+ "#{NEW_VARIABLE_NAME} = #{extracted_source}\n#{" " * indentation}"
86
+ end
53
87
 
54
88
  Interface::CodeAction.new(
55
89
  title: "Refactor: Extract Variable",
@@ -60,7 +94,10 @@ module RubyLsp
60
94
  uri: @code_action.dig(:data, :uri),
61
95
  version: nil,
62
96
  ),
63
- edits: edits_to_extract_variable(source_range, extraction_source, source_line_indentation),
97
+ edits: [
98
+ create_text_edit(source_range, NEW_VARIABLE_NAME),
99
+ create_text_edit(target_range, variable_source),
100
+ ],
64
101
  ),
65
102
  ],
66
103
  ),
@@ -69,22 +106,6 @@ module RubyLsp
69
106
 
70
107
  private
71
108
 
72
- sig do
73
- params(range: Document::RangeShape, source: String, indentation: Integer)
74
- .returns(T::Array[Interface::TextEdit])
75
- end
76
- def edits_to_extract_variable(range, source, indentation)
77
- target_range = {
78
- start: { line: range.dig(:start, :line), character: indentation },
79
- end: { line: range.dig(:start, :line), character: indentation },
80
- }
81
-
82
- [
83
- create_text_edit(range, NEW_VARIABLE_NAME),
84
- create_text_edit(target_range, "#{NEW_VARIABLE_NAME} = #{source}\n#{" " * indentation}"),
85
- ]
86
- end
87
-
88
109
  sig { params(range: Document::RangeShape, new_text: String).returns(Interface::TextEdit) }
89
110
  def create_text_edit(range, new_text)
90
111
  Interface::TextEdit.new(
@@ -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
@@ -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)
@@ -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,
@@ -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
 
@@ -223,9 +238,17 @@ module RubyLsp
223
238
  end
224
239
  end
225
240
 
226
- add_lines_range(receiver.location.start_line, node.location.end_line - 1) if receiver
241
+ if receiver
242
+ unless node.is_a?(SyntaxTree::CommandCall) && same_lines_for_command_and_block?(node)
243
+ add_lines_range(
244
+ receiver.location.start_line,
245
+ node.location.end_line - 1,
246
+ )
247
+ end
248
+ end
227
249
 
228
250
  visit(node.arguments)
251
+ visit(node.block) if node.is_a?(SyntaxTree::CommandCall)
229
252
  end
230
253
 
231
254
  sig { params(node: SyntaxTree::DefNode).void }
@@ -22,14 +22,23 @@ module RubyLsp
22
22
  # ```
23
23
  class Formatting < BaseRequest
24
24
  class Error < StandardError; end
25
+ class InvalidFormatter < StandardError; end
25
26
 
26
27
  extend T::Sig
27
28
 
28
- sig { params(uri: String, document: Document).void }
29
- def initialize(uri, document)
29
+ sig { params(uri: String, document: Document, formatter: String).void }
30
+ def initialize(uri, document, formatter: "auto")
30
31
  super(document)
31
32
 
32
33
  @uri = uri
34
+ @formatter = T.let(
35
+ if formatter == "auto"
36
+ defined?(Support::RuboCopFormattingRunner) ? "rubocop" : "syntax_tree"
37
+ else
38
+ formatter
39
+ end,
40
+ String,
41
+ )
33
42
  end
34
43
 
35
44
  sig { override.returns(T.nilable(T.all(T::Array[Interface::TextEdit], Object))) }
@@ -60,10 +69,13 @@ module RubyLsp
60
69
 
61
70
  sig { returns(T.nilable(String)) }
62
71
  def formatted_file
63
- if defined?(Support::RuboCopFormattingRunner)
72
+ case @formatter
73
+ when "rubocop"
64
74
  Support::RuboCopFormattingRunner.instance.run(@uri, @document)
65
- else
75
+ when "syntax_tree"
66
76
  SyntaxTree.format(@document.source)
77
+ else
78
+ raise InvalidFormatter, "Unknown formatter: #{@formatter}"
67
79
  end
68
80
  end
69
81
  end
@@ -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 }
@@ -169,6 +170,7 @@ module RubyLsp
169
170
  visit(node.receiver)
170
171
  add_token(node.message.location, :method)
171
172
  visit(node.arguments)
173
+ visit(node.block)
172
174
  end
173
175
 
174
176
  sig { override.params(node: SyntaxTree::Const).void }
@@ -236,13 +238,12 @@ module RubyLsp
236
238
 
237
239
  value = node.value
238
240
 
239
- case value
240
- when SyntaxTree::Ident
241
+ if value.is_a?(SyntaxTree::Ident)
241
242
  type = type_for_local(value)
242
243
  add_token(value.location, type)
243
- else
244
- visit(value)
245
244
  end
245
+
246
+ super
246
247
  end
247
248
 
248
249
  sig { override.params(node: SyntaxTree::VarRef).void }
@@ -260,16 +261,65 @@ module RubyLsp
260
261
  end
261
262
  end
262
263
 
264
+ # All block locals are variables. E.g.: [].each do |x; block_local|
265
+ sig { override.params(node: SyntaxTree::BlockVar).void }
266
+ def visit_block_var(node)
267
+ node.locals.each { |local| add_token(local.location, :variable) }
268
+ super
269
+ end
270
+
271
+ # All lambda locals are variables. E.g.: ->(x; lambda_local) {}
272
+ sig { override.params(node: SyntaxTree::LambdaVar).void }
273
+ def visit_lambda_var(node)
274
+ node.locals.each { |local| add_token(local.location, :variable) }
275
+ super
276
+ end
277
+
263
278
  sig { override.params(node: SyntaxTree::VCall).void }
264
279
  def visit_vcall(node)
265
280
  return super unless visible?(node, @range)
266
281
 
267
- return if special_method?(node.value.value)
282
+ # A VCall may exist as a local in the current_scope. This happens when used named capture groups in a regexp
283
+ ident = node.value
284
+ value = ident.value
285
+ local = current_scope.find_local(value)
286
+ return if local.nil? && special_method?(value)
287
+
288
+ type = if local
289
+ :variable
290
+ elsif Support::Sorbet.annotation?(node)
291
+ :type
292
+ else
293
+ :method
294
+ end
268
295
 
269
- type = Support::Sorbet.annotation?(node) ? :type : :method
270
296
  add_token(node.value.location, type)
271
297
  end
272
298
 
299
+ sig { override.params(node: SyntaxTree::Binary).void }
300
+ def visit_binary(node)
301
+ # It's important to visit the regexp first in the WithScope module
302
+ super
303
+
304
+ # You can only capture local variables with regexp by using the =~ operator
305
+ return unless node.operator == :=~
306
+
307
+ left = node.left
308
+ parts = left.parts
309
+
310
+ if left.is_a?(SyntaxTree::RegexpLiteral) && parts.one? && parts.first.is_a?(SyntaxTree::TStringContent)
311
+ content = parts.first
312
+
313
+ # For each capture name we find in the regexp, look for a local in the current_scope
314
+ Regexp.new(content.value, Regexp::FIXEDENCODING).names.each do |name|
315
+ local = current_scope.find_local(name)
316
+ next unless local
317
+
318
+ local.definitions.each { |definition| add_token(definition, :variable) }
319
+ end
320
+ end
321
+ end
322
+
273
323
  sig { override.params(node: SyntaxTree::ClassDeclaration).void }
274
324
  def visit_class(node)
275
325
  return super unless visible?(node, @range)
@@ -12,10 +12,14 @@ module RubyLsp
12
12
  sig { params(encoding: String).void }
13
13
  attr_writer :encoding
14
14
 
15
+ sig { returns(String) }
16
+ attr_accessor :formatter
17
+
15
18
  sig { void }
16
19
  def initialize
17
20
  @state = T.let({}, T::Hash[String, Document])
18
21
  @encoding = T.let("utf-8", String)
22
+ @formatter = T.let("auto", String)
19
23
  end
20
24
 
21
25
  sig { params(uri: String).returns(Document) }
@@ -5,6 +5,9 @@ module RubyLsp
5
5
  # Used to indicate that a request shouldn't return a response
6
6
  VOID = T.let(Object.new.freeze, Object)
7
7
 
8
+ # This freeze is not redundant since the interpolated string is mutable
9
+ WORKSPACE_URI = T.let("file://#{Dir.pwd}".freeze, String) # rubocop:disable Style/RedundantFreeze
10
+
8
11
  # A notification to be sent to the client
9
12
  class Notification
10
13
  extend T::Sig
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-02-22 00:00:00.000000000 Z
11
+ date: 2023-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -44,7 +44,7 @@ dependencies:
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '6'
47
+ version: 6.0.2
48
48
  - - "<"
49
49
  - !ruby/object:Gem::Version
50
50
  version: '7'
@@ -54,7 +54,7 @@ dependencies:
54
54
  requirements:
55
55
  - - ">="
56
56
  - !ruby/object:Gem::Version
57
- version: '6'
57
+ version: 6.0.2
58
58
  - - "<"
59
59
  - !ruby/object:Gem::Version
60
60
  version: '7'