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 +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'
|