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 +4 -4
- data/README.md +22 -23
- data/VERSION +1 -1
- data/exe/ruby-lsp +12 -0
- data/lib/ruby_lsp/executor.rb +51 -15
- data/lib/ruby_lsp/internal.rb +1 -0
- data/lib/ruby_lsp/requests/base_request.rb +11 -2
- data/lib/ruby_lsp/requests/code_action_resolve.rb +40 -19
- data/lib/ruby_lsp/requests/diagnostics.rb +1 -1
- data/lib/ruby_lsp/requests/document_symbol.rb +1 -1
- data/lib/ruby_lsp/requests/folding_ranges.rb +25 -2
- data/lib/ruby_lsp/requests/formatting.rb +16 -4
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +56 -6
- data/lib/ruby_lsp/store.rb +4 -0
- data/lib/ruby_lsp/utils.rb +3 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c5d9c6b487aab55ce25eb2741fff92afa8ca0ee544f21374debacce7f887cc50
|
4
|
+
data.tar.gz: 8da9da85900753306f5e8558bc1a42f25b7173ffe1087ea70612c10ef5558e27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
###
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
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
|
+
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
|
data/lib/ruby_lsp/executor.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
380
|
-
document_highlight_provider: enabled_features
|
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,
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -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
|
-
|
52
|
-
|
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:
|
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.
|
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
|
-
|
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
|
-
|
72
|
+
case @formatter
|
73
|
+
when "rubocop"
|
64
74
|
Support::RuboCopFormattingRunner.instance.run(@uri, @document)
|
65
|
-
|
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
|
-
|
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
|
-
|
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)
|
data/lib/ruby_lsp/store.rb
CHANGED
@@ -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) }
|
data/lib/ruby_lsp/utils.rb
CHANGED
@@ -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.
|
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-
|
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:
|
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:
|
57
|
+
version: 6.0.2
|
58
58
|
- - "<"
|
59
59
|
- !ruby/object:Gem::Version
|
60
60
|
version: '7'
|