ruby-lsp 0.16.6 → 0.17.0
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 +2 -2
- data/VERSION +1 -1
- data/exe/ruby-lsp +21 -4
- data/exe/ruby-lsp-check +1 -3
- data/exe/ruby-lsp-doctor +1 -4
- data/lib/core_ext/uri.rb +3 -0
- data/lib/ruby_indexer/lib/ruby_indexer/{collector.rb → declaration_listener.rb} +258 -140
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +101 -12
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +187 -12
- data/lib/ruby_indexer/ruby_indexer.rb +1 -1
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +106 -10
- data/lib/ruby_indexer/test/configuration_test.rb +4 -5
- data/lib/ruby_indexer/test/constant_test.rb +11 -8
- data/lib/ruby_indexer/test/index_test.rb +528 -0
- data/lib/ruby_indexer/test/instance_variables_test.rb +131 -0
- data/lib/ruby_indexer/test/method_test.rb +93 -0
- data/lib/ruby_indexer/test/test_case.rb +3 -1
- data/lib/ruby_lsp/addon.rb +8 -8
- data/lib/ruby_lsp/document.rb +3 -3
- data/lib/ruby_lsp/internal.rb +1 -0
- data/lib/ruby_lsp/listeners/code_lens.rb +11 -0
- data/lib/ruby_lsp/listeners/completion.rb +144 -51
- data/lib/ruby_lsp/listeners/definition.rb +77 -12
- data/lib/ruby_lsp/listeners/document_highlight.rb +1 -1
- data/lib/ruby_lsp/listeners/document_link.rb +1 -1
- data/lib/ruby_lsp/listeners/hover.rb +60 -6
- data/lib/ruby_lsp/listeners/semantic_highlighting.rb +59 -3
- data/lib/ruby_lsp/listeners/signature_help.rb +4 -4
- data/lib/ruby_lsp/node_context.rb +28 -0
- data/lib/ruby_lsp/requests/code_action_resolve.rb +73 -2
- data/lib/ruby_lsp/requests/code_actions.rb +16 -15
- data/lib/ruby_lsp/requests/completion.rb +22 -13
- data/lib/ruby_lsp/requests/completion_resolve.rb +26 -10
- data/lib/ruby_lsp/requests/definition.rb +21 -5
- data/lib/ruby_lsp/requests/document_highlight.rb +2 -2
- data/lib/ruby_lsp/requests/hover.rb +5 -6
- data/lib/ruby_lsp/requests/on_type_formatting.rb +8 -4
- data/lib/ruby_lsp/requests/signature_help.rb +3 -3
- data/lib/ruby_lsp/requests/support/common.rb +20 -1
- data/lib/ruby_lsp/requests/workspace_symbol.rb +3 -1
- data/lib/ruby_lsp/server.rb +10 -4
- metadata +10 -8
@@ -14,24 +14,31 @@ module RubyLsp
|
|
14
14
|
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
|
15
15
|
global_state: GlobalState,
|
16
16
|
uri: URI::Generic,
|
17
|
-
|
17
|
+
node_context: NodeContext,
|
18
18
|
dispatcher: Prism::Dispatcher,
|
19
19
|
typechecker_enabled: T::Boolean,
|
20
20
|
).void
|
21
21
|
end
|
22
|
-
def initialize(response_builder, global_state, uri,
|
22
|
+
def initialize(response_builder, global_state, uri, node_context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
|
23
23
|
@response_builder = response_builder
|
24
24
|
@global_state = global_state
|
25
25
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
26
26
|
@uri = uri
|
27
|
-
@
|
27
|
+
@node_context = node_context
|
28
28
|
@typechecker_enabled = typechecker_enabled
|
29
29
|
|
30
30
|
dispatcher.register(
|
31
31
|
self,
|
32
32
|
:on_call_node_enter,
|
33
|
+
:on_block_argument_node_enter,
|
33
34
|
:on_constant_read_node_enter,
|
34
35
|
:on_constant_path_node_enter,
|
36
|
+
:on_instance_variable_read_node_enter,
|
37
|
+
:on_instance_variable_write_node_enter,
|
38
|
+
:on_instance_variable_and_write_node_enter,
|
39
|
+
:on_instance_variable_operator_write_node_enter,
|
40
|
+
:on_instance_variable_or_write_node_enter,
|
41
|
+
:on_instance_variable_target_node_enter,
|
35
42
|
)
|
36
43
|
end
|
37
44
|
|
@@ -42,10 +49,21 @@ module RubyLsp
|
|
42
49
|
if message == :require || message == :require_relative
|
43
50
|
handle_require_definition(node)
|
44
51
|
else
|
45
|
-
handle_method_definition(node)
|
52
|
+
handle_method_definition(message.to_s, self_receiver?(node))
|
46
53
|
end
|
47
54
|
end
|
48
55
|
|
56
|
+
sig { params(node: Prism::BlockArgumentNode).void }
|
57
|
+
def on_block_argument_node_enter(node)
|
58
|
+
expression = node.expression
|
59
|
+
return unless expression.is_a?(Prism::SymbolNode)
|
60
|
+
|
61
|
+
value = expression.value
|
62
|
+
return unless value
|
63
|
+
|
64
|
+
handle_method_definition(value, false)
|
65
|
+
end
|
66
|
+
|
49
67
|
sig { params(node: Prism::ConstantPathNode).void }
|
50
68
|
def on_constant_path_node_enter(node)
|
51
69
|
name = constant_name(node)
|
@@ -62,15 +80,62 @@ module RubyLsp
|
|
62
80
|
find_in_index(name)
|
63
81
|
end
|
64
82
|
|
83
|
+
sig { params(node: Prism::InstanceVariableReadNode).void }
|
84
|
+
def on_instance_variable_read_node_enter(node)
|
85
|
+
handle_instance_variable_definition(node.name.to_s)
|
86
|
+
end
|
87
|
+
|
88
|
+
sig { params(node: Prism::InstanceVariableWriteNode).void }
|
89
|
+
def on_instance_variable_write_node_enter(node)
|
90
|
+
handle_instance_variable_definition(node.name.to_s)
|
91
|
+
end
|
92
|
+
|
93
|
+
sig { params(node: Prism::InstanceVariableAndWriteNode).void }
|
94
|
+
def on_instance_variable_and_write_node_enter(node)
|
95
|
+
handle_instance_variable_definition(node.name.to_s)
|
96
|
+
end
|
97
|
+
|
98
|
+
sig { params(node: Prism::InstanceVariableOperatorWriteNode).void }
|
99
|
+
def on_instance_variable_operator_write_node_enter(node)
|
100
|
+
handle_instance_variable_definition(node.name.to_s)
|
101
|
+
end
|
102
|
+
|
103
|
+
sig { params(node: Prism::InstanceVariableOrWriteNode).void }
|
104
|
+
def on_instance_variable_or_write_node_enter(node)
|
105
|
+
handle_instance_variable_definition(node.name.to_s)
|
106
|
+
end
|
107
|
+
|
108
|
+
sig { params(node: Prism::InstanceVariableTargetNode).void }
|
109
|
+
def on_instance_variable_target_node_enter(node)
|
110
|
+
handle_instance_variable_definition(node.name.to_s)
|
111
|
+
end
|
112
|
+
|
65
113
|
private
|
66
114
|
|
67
|
-
sig { params(
|
68
|
-
def
|
69
|
-
|
70
|
-
return unless
|
115
|
+
sig { params(name: String).void }
|
116
|
+
def handle_instance_variable_definition(name)
|
117
|
+
entries = @index.resolve_instance_variable(name, @node_context.fully_qualified_name)
|
118
|
+
return unless entries
|
119
|
+
|
120
|
+
entries.each do |entry|
|
121
|
+
location = entry.location
|
122
|
+
|
123
|
+
@response_builder << Interface::Location.new(
|
124
|
+
uri: URI::Generic.from_path(path: entry.file_path).to_s,
|
125
|
+
range: Interface::Range.new(
|
126
|
+
start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
|
127
|
+
end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
|
128
|
+
),
|
129
|
+
)
|
130
|
+
end
|
131
|
+
rescue RubyIndexer::Index::NonExistingNamespaceError
|
132
|
+
# If by any chance we haven't indexed the owner, then there's no way to find the right declaration
|
133
|
+
end
|
71
134
|
|
72
|
-
|
73
|
-
|
135
|
+
sig { params(message: String, self_receiver: T::Boolean).void }
|
136
|
+
def handle_method_definition(message, self_receiver)
|
137
|
+
methods = if self_receiver
|
138
|
+
@index.resolve_method(message, @node_context.fully_qualified_name)
|
74
139
|
else
|
75
140
|
# If the method doesn't have a receiver, then we provide a few candidates to jump to
|
76
141
|
# But we don't want to provide too many candidates, as it can be overwhelming
|
@@ -138,13 +203,13 @@ module RubyLsp
|
|
138
203
|
|
139
204
|
sig { params(value: String).void }
|
140
205
|
def find_in_index(value)
|
141
|
-
entries = @index.resolve(value, @nesting)
|
206
|
+
entries = @index.resolve(value, @node_context.nesting)
|
142
207
|
return unless entries
|
143
208
|
|
144
209
|
# We should only allow jumping to the definition of private constants if the constant is defined in the same
|
145
210
|
# namespace as the reference
|
146
211
|
first_entry = T.must(entries.first)
|
147
|
-
return if first_entry.
|
212
|
+
return if first_entry.private? && first_entry.name != "#{@node_context.fully_qualified_name}::#{value}"
|
148
213
|
|
149
214
|
entries.each do |entry|
|
150
215
|
location = entry.location
|
@@ -271,7 +271,7 @@ module RubyLsp
|
|
271
271
|
def on_constant_path_node_enter(node)
|
272
272
|
return unless matches?(node, CONSTANT_PATH_NODES)
|
273
273
|
|
274
|
-
add_highlight(Constant::DocumentHighlightKind::READ, node.
|
274
|
+
add_highlight(Constant::DocumentHighlightKind::READ, node.name_loc)
|
275
275
|
end
|
276
276
|
|
277
277
|
sig { params(node: Prism::ConstantReadNode).void }
|
@@ -30,7 +30,7 @@ module RubyLsp
|
|
30
30
|
lookup[spec.name] = {}
|
31
31
|
lookup[spec.name][spec.version.to_s] = {}
|
32
32
|
|
33
|
-
Dir.glob("**/*.rb", base: "#{spec.full_gem_path}/").each do |path|
|
33
|
+
Dir.glob("**/*.rb", base: "#{spec.full_gem_path.delete_prefix("//?/")}/").each do |path|
|
34
34
|
lookup[spec.name][spec.version.to_s][path] = "#{spec.full_gem_path}/#{path}"
|
35
35
|
end
|
36
36
|
end
|
@@ -13,6 +13,12 @@ module RubyLsp
|
|
13
13
|
Prism::ConstantReadNode,
|
14
14
|
Prism::ConstantWriteNode,
|
15
15
|
Prism::ConstantPathNode,
|
16
|
+
Prism::InstanceVariableReadNode,
|
17
|
+
Prism::InstanceVariableAndWriteNode,
|
18
|
+
Prism::InstanceVariableOperatorWriteNode,
|
19
|
+
Prism::InstanceVariableOrWriteNode,
|
20
|
+
Prism::InstanceVariableTargetNode,
|
21
|
+
Prism::InstanceVariableWriteNode,
|
16
22
|
],
|
17
23
|
T::Array[T.class_of(Prism::Node)],
|
18
24
|
)
|
@@ -30,17 +36,17 @@ module RubyLsp
|
|
30
36
|
response_builder: ResponseBuilders::Hover,
|
31
37
|
global_state: GlobalState,
|
32
38
|
uri: URI::Generic,
|
33
|
-
|
39
|
+
node_context: NodeContext,
|
34
40
|
dispatcher: Prism::Dispatcher,
|
35
41
|
typechecker_enabled: T::Boolean,
|
36
42
|
).void
|
37
43
|
end
|
38
|
-
def initialize(response_builder, global_state, uri,
|
44
|
+
def initialize(response_builder, global_state, uri, node_context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
|
39
45
|
@response_builder = response_builder
|
40
46
|
@global_state = global_state
|
41
47
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
42
48
|
@path = T.let(uri.to_standardized_path, T.nilable(String))
|
43
|
-
@
|
49
|
+
@node_context = node_context
|
44
50
|
@typechecker_enabled = typechecker_enabled
|
45
51
|
|
46
52
|
dispatcher.register(
|
@@ -49,6 +55,12 @@ module RubyLsp
|
|
49
55
|
:on_constant_write_node_enter,
|
50
56
|
:on_constant_path_node_enter,
|
51
57
|
:on_call_node_enter,
|
58
|
+
:on_instance_variable_read_node_enter,
|
59
|
+
:on_instance_variable_write_node_enter,
|
60
|
+
:on_instance_variable_and_write_node_enter,
|
61
|
+
:on_instance_variable_operator_write_node_enter,
|
62
|
+
:on_instance_variable_or_write_node_enter,
|
63
|
+
:on_instance_variable_target_node_enter,
|
52
64
|
)
|
53
65
|
end
|
54
66
|
|
@@ -93,7 +105,7 @@ module RubyLsp
|
|
93
105
|
message = node.message
|
94
106
|
return unless message
|
95
107
|
|
96
|
-
methods = @index.resolve_method(message, @
|
108
|
+
methods = @index.resolve_method(message, @node_context.fully_qualified_name)
|
97
109
|
return unless methods
|
98
110
|
|
99
111
|
categorized_markdown_from_index_entries(message, methods).each do |category, content|
|
@@ -101,17 +113,59 @@ module RubyLsp
|
|
101
113
|
end
|
102
114
|
end
|
103
115
|
|
116
|
+
sig { params(node: Prism::InstanceVariableReadNode).void }
|
117
|
+
def on_instance_variable_read_node_enter(node)
|
118
|
+
handle_instance_variable_hover(node.name.to_s)
|
119
|
+
end
|
120
|
+
|
121
|
+
sig { params(node: Prism::InstanceVariableWriteNode).void }
|
122
|
+
def on_instance_variable_write_node_enter(node)
|
123
|
+
handle_instance_variable_hover(node.name.to_s)
|
124
|
+
end
|
125
|
+
|
126
|
+
sig { params(node: Prism::InstanceVariableAndWriteNode).void }
|
127
|
+
def on_instance_variable_and_write_node_enter(node)
|
128
|
+
handle_instance_variable_hover(node.name.to_s)
|
129
|
+
end
|
130
|
+
|
131
|
+
sig { params(node: Prism::InstanceVariableOperatorWriteNode).void }
|
132
|
+
def on_instance_variable_operator_write_node_enter(node)
|
133
|
+
handle_instance_variable_hover(node.name.to_s)
|
134
|
+
end
|
135
|
+
|
136
|
+
sig { params(node: Prism::InstanceVariableOrWriteNode).void }
|
137
|
+
def on_instance_variable_or_write_node_enter(node)
|
138
|
+
handle_instance_variable_hover(node.name.to_s)
|
139
|
+
end
|
140
|
+
|
141
|
+
sig { params(node: Prism::InstanceVariableTargetNode).void }
|
142
|
+
def on_instance_variable_target_node_enter(node)
|
143
|
+
handle_instance_variable_hover(node.name.to_s)
|
144
|
+
end
|
145
|
+
|
104
146
|
private
|
105
147
|
|
148
|
+
sig { params(name: String).void }
|
149
|
+
def handle_instance_variable_hover(name)
|
150
|
+
entries = @index.resolve_instance_variable(name, @node_context.fully_qualified_name)
|
151
|
+
return unless entries
|
152
|
+
|
153
|
+
categorized_markdown_from_index_entries(name, entries).each do |category, content|
|
154
|
+
@response_builder.push(content, category: category)
|
155
|
+
end
|
156
|
+
rescue RubyIndexer::Index::NonExistingNamespaceError
|
157
|
+
# If by any chance we haven't indexed the owner, then there's no way to find the right declaration
|
158
|
+
end
|
159
|
+
|
106
160
|
sig { params(name: String, location: Prism::Location).void }
|
107
161
|
def generate_hover(name, location)
|
108
|
-
entries = @index.resolve(name, @nesting)
|
162
|
+
entries = @index.resolve(name, @node_context.nesting)
|
109
163
|
return unless entries
|
110
164
|
|
111
165
|
# We should only show hover for private constants if the constant is defined in the same namespace as the
|
112
166
|
# reference
|
113
167
|
first_entry = T.must(entries.first)
|
114
|
-
return if first_entry.
|
168
|
+
return if first_entry.private? && first_entry.name != "#{@node_context.fully_qualified_name}::#{name}"
|
115
169
|
|
116
170
|
categorized_markdown_from_index_entries(name, entries).each do |category, content|
|
117
171
|
@response_builder.push(content, category: category)
|
@@ -58,6 +58,7 @@ module RubyLsp
|
|
58
58
|
:on_constant_operator_write_node_enter,
|
59
59
|
:on_constant_or_write_node_enter,
|
60
60
|
:on_constant_target_node_enter,
|
61
|
+
:on_constant_path_node_enter,
|
61
62
|
:on_local_variable_and_write_node_enter,
|
62
63
|
:on_local_variable_operator_write_node_enter,
|
63
64
|
:on_local_variable_or_write_node_enter,
|
@@ -302,17 +303,64 @@ module RubyLsp
|
|
302
303
|
def on_class_node_enter(node)
|
303
304
|
return unless visible?(node, @range)
|
304
305
|
|
305
|
-
|
306
|
+
constant_path = node.constant_path
|
307
|
+
|
308
|
+
if constant_path.is_a?(Prism::ConstantReadNode)
|
309
|
+
@response_builder.add_token(constant_path.location, :class, [:declaration])
|
310
|
+
else
|
311
|
+
each_constant_path_part(constant_path) do |part|
|
312
|
+
loc = case part
|
313
|
+
when Prism::ConstantPathNode
|
314
|
+
part.name_loc
|
315
|
+
when Prism::ConstantReadNode
|
316
|
+
part.location
|
317
|
+
end
|
318
|
+
next unless loc
|
319
|
+
|
320
|
+
@response_builder.add_token(loc, :class, [:declaration])
|
321
|
+
end
|
322
|
+
end
|
306
323
|
|
307
324
|
superclass = node.superclass
|
308
|
-
|
325
|
+
|
326
|
+
if superclass.is_a?(Prism::ConstantReadNode)
|
327
|
+
@response_builder.add_token(superclass.location, :class)
|
328
|
+
elsif superclass
|
329
|
+
each_constant_path_part(superclass) do |part|
|
330
|
+
loc = case part
|
331
|
+
when Prism::ConstantPathNode
|
332
|
+
part.name_loc
|
333
|
+
when Prism::ConstantReadNode
|
334
|
+
part.location
|
335
|
+
end
|
336
|
+
next unless loc
|
337
|
+
|
338
|
+
@response_builder.add_token(loc, :class)
|
339
|
+
end
|
340
|
+
end
|
309
341
|
end
|
310
342
|
|
311
343
|
sig { params(node: Prism::ModuleNode).void }
|
312
344
|
def on_module_node_enter(node)
|
313
345
|
return unless visible?(node, @range)
|
314
346
|
|
315
|
-
|
347
|
+
constant_path = node.constant_path
|
348
|
+
|
349
|
+
if constant_path.is_a?(Prism::ConstantReadNode)
|
350
|
+
@response_builder.add_token(constant_path.location, :namespace, [:declaration])
|
351
|
+
else
|
352
|
+
each_constant_path_part(constant_path) do |part|
|
353
|
+
loc = case part
|
354
|
+
when Prism::ConstantPathNode
|
355
|
+
part.name_loc
|
356
|
+
when Prism::ConstantReadNode
|
357
|
+
part.location
|
358
|
+
end
|
359
|
+
next unless loc
|
360
|
+
|
361
|
+
@response_builder.add_token(loc, :namespace, [:declaration])
|
362
|
+
end
|
363
|
+
end
|
316
364
|
end
|
317
365
|
|
318
366
|
sig { params(node: Prism::ImplicitNode).void }
|
@@ -327,6 +375,14 @@ module RubyLsp
|
|
327
375
|
@inside_implicit_node = false
|
328
376
|
end
|
329
377
|
|
378
|
+
sig { params(node: Prism::ConstantPathNode).void }
|
379
|
+
def on_constant_path_node_enter(node)
|
380
|
+
return if @inside_implicit_node
|
381
|
+
return unless visible?(node, @range)
|
382
|
+
|
383
|
+
@response_builder.add_token(node.name_loc, :namespace)
|
384
|
+
end
|
385
|
+
|
330
386
|
private
|
331
387
|
|
332
388
|
# Textmate provides highlighting for a subset of these special Ruby-specific methods. We want to utilize that
|
@@ -11,17 +11,17 @@ module RubyLsp
|
|
11
11
|
params(
|
12
12
|
response_builder: ResponseBuilders::SignatureHelp,
|
13
13
|
global_state: GlobalState,
|
14
|
-
|
14
|
+
node_context: NodeContext,
|
15
15
|
dispatcher: Prism::Dispatcher,
|
16
16
|
typechecker_enabled: T::Boolean,
|
17
17
|
).void
|
18
18
|
end
|
19
|
-
def initialize(response_builder, global_state,
|
19
|
+
def initialize(response_builder, global_state, node_context, dispatcher, typechecker_enabled)
|
20
20
|
@typechecker_enabled = typechecker_enabled
|
21
21
|
@response_builder = response_builder
|
22
22
|
@global_state = global_state
|
23
23
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
24
|
-
@
|
24
|
+
@node_context = node_context
|
25
25
|
dispatcher.register(self, :on_call_node_enter)
|
26
26
|
end
|
27
27
|
|
@@ -33,7 +33,7 @@ module RubyLsp
|
|
33
33
|
message = node.message
|
34
34
|
return unless message
|
35
35
|
|
36
|
-
methods = @index.resolve_method(message, @
|
36
|
+
methods = @index.resolve_method(message, @node_context.fully_qualified_name)
|
37
37
|
return unless methods
|
38
38
|
|
39
39
|
target_method = methods.first
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
# This class allows listeners to access contextual information about a node in the AST, such as its parent
|
6
|
+
# and its namespace nesting.
|
7
|
+
class NodeContext
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { returns(T.nilable(Prism::Node)) }
|
11
|
+
attr_reader :node, :parent
|
12
|
+
|
13
|
+
sig { returns(T::Array[String]) }
|
14
|
+
attr_reader :nesting
|
15
|
+
|
16
|
+
sig { params(node: T.nilable(Prism::Node), parent: T.nilable(Prism::Node), nesting: T::Array[String]).void }
|
17
|
+
def initialize(node, parent, nesting)
|
18
|
+
@node = node
|
19
|
+
@parent = parent
|
20
|
+
@nesting = nesting
|
21
|
+
end
|
22
|
+
|
23
|
+
sig { returns(String) }
|
24
|
+
def fully_qualified_name
|
25
|
+
@fully_qualified_name ||= T.let(@nesting.join("::"), T.nilable(String))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -24,6 +24,7 @@ module RubyLsp
|
|
24
24
|
class CodeActionResolve < Request
|
25
25
|
extend T::Sig
|
26
26
|
NEW_VARIABLE_NAME = "new_variable"
|
27
|
+
NEW_METHOD_NAME = "new_method"
|
27
28
|
|
28
29
|
class CodeActionError < StandardError; end
|
29
30
|
|
@@ -31,6 +32,7 @@ module RubyLsp
|
|
31
32
|
enums do
|
32
33
|
EmptySelection = new
|
33
34
|
InvalidTargetRange = new
|
35
|
+
UnknownCodeAction = new
|
34
36
|
end
|
35
37
|
end
|
36
38
|
|
@@ -43,6 +45,18 @@ module RubyLsp
|
|
43
45
|
|
44
46
|
sig { override.returns(T.any(Interface::CodeAction, Error)) }
|
45
47
|
def perform
|
48
|
+
case @code_action[:title]
|
49
|
+
when CodeActions::EXTRACT_TO_VARIABLE_TITLE
|
50
|
+
refactor_variable
|
51
|
+
when CodeActions::EXTRACT_TO_METHOD_TITLE
|
52
|
+
refactor_method
|
53
|
+
else
|
54
|
+
Error::UnknownCodeAction
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
sig { returns(T.any(Interface::CodeAction, Error)) }
|
59
|
+
def refactor_variable
|
46
60
|
return Error::EmptySelection if @document.source.empty?
|
47
61
|
|
48
62
|
source_range = @code_action.dig(:data, :range)
|
@@ -54,9 +68,11 @@ module RubyLsp
|
|
54
68
|
extracted_source = T.must(@document.source[start_index...end_index])
|
55
69
|
|
56
70
|
# Find the closest statements node, so that we place the refactor in a valid position
|
57
|
-
|
71
|
+
node_context = @document
|
58
72
|
.locate(@document.tree, start_index, node_types: [Prism::StatementsNode, Prism::BlockNode])
|
59
73
|
|
74
|
+
closest_statements = node_context.node
|
75
|
+
parent_statements = node_context.parent
|
60
76
|
return Error::InvalidTargetRange if closest_statements.nil? || closest_statements.child_nodes.compact.empty?
|
61
77
|
|
62
78
|
# Find the node with the end line closest to the requested position, so that we can place the refactor
|
@@ -117,7 +133,7 @@ module RubyLsp
|
|
117
133
|
end
|
118
134
|
|
119
135
|
Interface::CodeAction.new(
|
120
|
-
title:
|
136
|
+
title: CodeActions::EXTRACT_TO_VARIABLE_TITLE,
|
121
137
|
edit: Interface::WorkspaceEdit.new(
|
122
138
|
document_changes: [
|
123
139
|
Interface::TextDocumentEdit.new(
|
@@ -135,6 +151,61 @@ module RubyLsp
|
|
135
151
|
)
|
136
152
|
end
|
137
153
|
|
154
|
+
sig { returns(T.any(Interface::CodeAction, Error)) }
|
155
|
+
def refactor_method
|
156
|
+
return Error::EmptySelection if @document.source.empty?
|
157
|
+
|
158
|
+
source_range = @code_action.dig(:data, :range)
|
159
|
+
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
160
|
+
|
161
|
+
scanner = @document.create_scanner
|
162
|
+
start_index = scanner.find_char_position(source_range[:start])
|
163
|
+
end_index = scanner.find_char_position(source_range[:end])
|
164
|
+
extracted_source = T.must(@document.source[start_index...end_index])
|
165
|
+
|
166
|
+
# Find the closest method declaration node, so that we place the refactor in a valid position
|
167
|
+
node_context = @document.locate(@document.tree, start_index, node_types: [Prism::DefNode])
|
168
|
+
closest_def = T.cast(node_context.node, Prism::DefNode)
|
169
|
+
return Error::InvalidTargetRange if closest_def.nil?
|
170
|
+
|
171
|
+
end_keyword_loc = closest_def.end_keyword_loc
|
172
|
+
return Error::InvalidTargetRange if end_keyword_loc.nil?
|
173
|
+
|
174
|
+
end_line = end_keyword_loc.end_line - 1
|
175
|
+
character = end_keyword_loc.end_column
|
176
|
+
indentation = " " * end_keyword_loc.start_column
|
177
|
+
target_range = {
|
178
|
+
start: { line: end_line, character: character },
|
179
|
+
end: { line: end_line, character: character },
|
180
|
+
}
|
181
|
+
|
182
|
+
new_method_source = <<~RUBY.chomp
|
183
|
+
|
184
|
+
|
185
|
+
#{indentation}def #{NEW_METHOD_NAME}
|
186
|
+
#{indentation} #{extracted_source}
|
187
|
+
#{indentation}end
|
188
|
+
RUBY
|
189
|
+
|
190
|
+
Interface::CodeAction.new(
|
191
|
+
title: CodeActions::EXTRACT_TO_METHOD_TITLE,
|
192
|
+
edit: Interface::WorkspaceEdit.new(
|
193
|
+
document_changes: [
|
194
|
+
Interface::TextDocumentEdit.new(
|
195
|
+
text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
|
196
|
+
uri: @code_action.dig(:data, :uri),
|
197
|
+
version: nil,
|
198
|
+
),
|
199
|
+
edits: [
|
200
|
+
create_text_edit(target_range, new_method_source),
|
201
|
+
create_text_edit(source_range, NEW_METHOD_NAME),
|
202
|
+
],
|
203
|
+
),
|
204
|
+
],
|
205
|
+
),
|
206
|
+
)
|
207
|
+
end
|
208
|
+
|
138
209
|
private
|
139
210
|
|
140
211
|
sig { params(range: T::Hash[Symbol, T.untyped], new_text: String).returns(Interface::TextEdit) }
|
@@ -19,6 +19,9 @@ module RubyLsp
|
|
19
19
|
class CodeActions < Request
|
20
20
|
extend T::Sig
|
21
21
|
|
22
|
+
EXTRACT_TO_VARIABLE_TITLE = "Refactor: Extract Variable"
|
23
|
+
EXTRACT_TO_METHOD_TITLE = "Refactor: Extract Method"
|
24
|
+
|
22
25
|
class << self
|
23
26
|
extend T::Sig
|
24
27
|
|
@@ -52,22 +55,20 @@ module RubyLsp
|
|
52
55
|
end
|
53
56
|
|
54
57
|
# Only add refactor actions if there's a non empty selection in the editor
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
58
|
+
unless @range.dig(:start) == @range.dig(:end)
|
59
|
+
code_actions << Interface::CodeAction.new(
|
60
|
+
title: EXTRACT_TO_VARIABLE_TITLE,
|
61
|
+
kind: Constant::CodeActionKind::REFACTOR_EXTRACT,
|
62
|
+
data: { range: @range, uri: @uri.to_s },
|
63
|
+
)
|
64
|
+
code_actions << Interface::CodeAction.new(
|
65
|
+
title: EXTRACT_TO_METHOD_TITLE,
|
66
|
+
kind: Constant::CodeActionKind::REFACTOR_EXTRACT,
|
67
|
+
data: { range: @range, uri: @uri.to_s },
|
68
|
+
)
|
69
|
+
end
|
60
70
|
|
61
|
-
|
62
|
-
def refactor_code_action(range, uri)
|
63
|
-
Interface::CodeAction.new(
|
64
|
-
title: "Refactor: Extract Variable",
|
65
|
-
kind: Constant::CodeActionKind::REFACTOR_EXTRACT,
|
66
|
-
data: {
|
67
|
-
range: range,
|
68
|
-
uri: uri.to_s,
|
69
|
-
},
|
70
|
-
)
|
71
|
+
code_actions
|
71
72
|
end
|
72
73
|
end
|
73
74
|
end
|
@@ -11,11 +11,13 @@ module RubyLsp
|
|
11
11
|
# suggests possible completions according to what the developer is typing.
|
12
12
|
#
|
13
13
|
# Currently supported targets:
|
14
|
+
#
|
14
15
|
# - Classes
|
15
16
|
# - Modules
|
16
17
|
# - Constants
|
17
18
|
# - Require paths
|
18
19
|
# - Methods invoked on self only
|
20
|
+
# - Instance variables
|
19
21
|
#
|
20
22
|
# # Example
|
21
23
|
#
|
@@ -34,7 +36,7 @@ module RubyLsp
|
|
34
36
|
def provider
|
35
37
|
Interface::CompletionOptions.new(
|
36
38
|
resolve_provider: true,
|
37
|
-
trigger_characters: ["/", "\"", "'"],
|
39
|
+
trigger_characters: ["/", "\"", "'", ":", "@"],
|
38
40
|
completion_item: {
|
39
41
|
labelDetailsSupport: true,
|
40
42
|
},
|
@@ -58,10 +60,20 @@ module RubyLsp
|
|
58
60
|
# Completion always receives the position immediately after the character that was just typed. Here we adjust it
|
59
61
|
# back by 1, so that we find the right node
|
60
62
|
char_position = document.create_scanner.find_char_position(position) - 1
|
61
|
-
|
63
|
+
node_context = document.locate(
|
62
64
|
document.tree,
|
63
65
|
char_position,
|
64
|
-
node_types: [
|
66
|
+
node_types: [
|
67
|
+
Prism::CallNode,
|
68
|
+
Prism::ConstantReadNode,
|
69
|
+
Prism::ConstantPathNode,
|
70
|
+
Prism::InstanceVariableReadNode,
|
71
|
+
Prism::InstanceVariableAndWriteNode,
|
72
|
+
Prism::InstanceVariableOperatorWriteNode,
|
73
|
+
Prism::InstanceVariableOrWriteNode,
|
74
|
+
Prism::InstanceVariableTargetNode,
|
75
|
+
Prism::InstanceVariableWriteNode,
|
76
|
+
],
|
65
77
|
)
|
66
78
|
@response_builder = T.let(
|
67
79
|
ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem].new,
|
@@ -71,27 +83,24 @@ module RubyLsp
|
|
71
83
|
Listeners::Completion.new(
|
72
84
|
@response_builder,
|
73
85
|
global_state,
|
74
|
-
|
86
|
+
node_context,
|
75
87
|
typechecker_enabled,
|
76
88
|
dispatcher,
|
77
89
|
document.uri,
|
78
90
|
)
|
79
91
|
|
80
92
|
Addon.addons.each do |addon|
|
81
|
-
addon.create_completion_listener(@response_builder,
|
93
|
+
addon.create_completion_listener(@response_builder, node_context, dispatcher, document.uri)
|
82
94
|
end
|
83
95
|
|
96
|
+
matched = node_context.node
|
97
|
+
parent = node_context.parent
|
84
98
|
return unless matched && parent
|
85
99
|
|
86
|
-
@target =
|
87
|
-
|
100
|
+
@target = if parent.is_a?(Prism::ConstantPathNode) && matched.is_a?(Prism::ConstantReadNode)
|
101
|
+
parent
|
102
|
+
else
|
88
103
|
matched
|
89
|
-
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
90
|
-
if parent.is_a?(Prism::ConstantPathNode) && matched.is_a?(Prism::ConstantReadNode)
|
91
|
-
parent
|
92
|
-
else
|
93
|
-
matched
|
94
|
-
end
|
95
104
|
end
|
96
105
|
end
|
97
106
|
|