ruby-lsp 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
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'