ruby-lsp 0.16.7 → 0.17.3
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 +3 -1
- data/VERSION +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +195 -18
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +129 -12
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +333 -44
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +99 -0
- data/lib/ruby_indexer/ruby_indexer.rb +1 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +58 -11
- data/lib/ruby_indexer/test/configuration_test.rb +5 -6
- data/lib/ruby_indexer/test/constant_test.rb +9 -9
- data/lib/ruby_indexer/test/index_test.rb +867 -7
- data/lib/ruby_indexer/test/instance_variables_test.rb +131 -0
- data/lib/ruby_indexer/test/method_test.rb +57 -0
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +42 -0
- data/lib/ruby_indexer/test/test_case.rb +10 -1
- data/lib/ruby_lsp/addon.rb +8 -8
- data/lib/ruby_lsp/document.rb +26 -3
- data/lib/ruby_lsp/global_state.rb +37 -16
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/listeners/code_lens.rb +2 -2
- data/lib/ruby_lsp/listeners/completion.rb +74 -17
- data/lib/ruby_lsp/listeners/definition.rb +82 -24
- data/lib/ruby_lsp/listeners/hover.rb +62 -6
- data/lib/ruby_lsp/listeners/signature_help.rb +4 -4
- data/lib/ruby_lsp/node_context.rb +39 -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 +21 -13
- data/lib/ruby_lsp/requests/completion_resolve.rb +9 -0
- data/lib/ruby_lsp/requests/definition.rb +25 -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 +4 -3
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +23 -6
- data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +5 -1
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +4 -0
- data/lib/ruby_lsp/requests/workspace_symbol.rb +7 -4
- data/lib/ruby_lsp/server.rb +22 -5
- data/lib/ruby_lsp/test_helper.rb +1 -0
- metadata +29 -5
@@ -11,17 +11,17 @@ module RubyLsp
|
|
11
11
|
params(
|
12
12
|
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
|
13
13
|
global_state: GlobalState,
|
14
|
-
|
14
|
+
node_context: NodeContext,
|
15
15
|
typechecker_enabled: T::Boolean,
|
16
16
|
dispatcher: Prism::Dispatcher,
|
17
17
|
uri: URI::Generic,
|
18
18
|
).void
|
19
19
|
end
|
20
|
-
def initialize(response_builder, global_state,
|
20
|
+
def initialize(response_builder, global_state, node_context, typechecker_enabled, dispatcher, uri) # rubocop:disable Metrics/ParameterLists
|
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
|
@typechecker_enabled = typechecker_enabled
|
26
26
|
@uri = uri
|
27
27
|
|
@@ -30,6 +30,12 @@ module RubyLsp
|
|
30
30
|
:on_constant_path_node_enter,
|
31
31
|
:on_constant_read_node_enter,
|
32
32
|
:on_call_node_enter,
|
33
|
+
:on_instance_variable_read_node_enter,
|
34
|
+
:on_instance_variable_write_node_enter,
|
35
|
+
:on_instance_variable_and_write_node_enter,
|
36
|
+
:on_instance_variable_operator_write_node_enter,
|
37
|
+
:on_instance_variable_or_write_node_enter,
|
38
|
+
:on_instance_variable_target_node_enter,
|
33
39
|
)
|
34
40
|
end
|
35
41
|
|
@@ -41,7 +47,7 @@ module RubyLsp
|
|
41
47
|
name = constant_name(node)
|
42
48
|
return if name.nil?
|
43
49
|
|
44
|
-
candidates = @index.prefix_search(name, @nesting)
|
50
|
+
candidates = @index.prefix_search(name, @node_context.nesting)
|
45
51
|
candidates.each do |entries|
|
46
52
|
complete_name = T.must(entries.first).name
|
47
53
|
@response_builder << build_entry_completion(
|
@@ -105,6 +111,36 @@ module RubyLsp
|
|
105
111
|
end
|
106
112
|
end
|
107
113
|
|
114
|
+
sig { params(node: Prism::InstanceVariableReadNode).void }
|
115
|
+
def on_instance_variable_read_node_enter(node)
|
116
|
+
handle_instance_variable_completion(node.name.to_s, node.location)
|
117
|
+
end
|
118
|
+
|
119
|
+
sig { params(node: Prism::InstanceVariableWriteNode).void }
|
120
|
+
def on_instance_variable_write_node_enter(node)
|
121
|
+
handle_instance_variable_completion(node.name.to_s, node.name_loc)
|
122
|
+
end
|
123
|
+
|
124
|
+
sig { params(node: Prism::InstanceVariableAndWriteNode).void }
|
125
|
+
def on_instance_variable_and_write_node_enter(node)
|
126
|
+
handle_instance_variable_completion(node.name.to_s, node.name_loc)
|
127
|
+
end
|
128
|
+
|
129
|
+
sig { params(node: Prism::InstanceVariableOperatorWriteNode).void }
|
130
|
+
def on_instance_variable_operator_write_node_enter(node)
|
131
|
+
handle_instance_variable_completion(node.name.to_s, node.name_loc)
|
132
|
+
end
|
133
|
+
|
134
|
+
sig { params(node: Prism::InstanceVariableOrWriteNode).void }
|
135
|
+
def on_instance_variable_or_write_node_enter(node)
|
136
|
+
handle_instance_variable_completion(node.name.to_s, node.name_loc)
|
137
|
+
end
|
138
|
+
|
139
|
+
sig { params(node: Prism::InstanceVariableTargetNode).void }
|
140
|
+
def on_instance_variable_target_node_enter(node)
|
141
|
+
handle_instance_variable_completion(node.name.to_s, node.location)
|
142
|
+
end
|
143
|
+
|
108
144
|
private
|
109
145
|
|
110
146
|
sig { params(name: String, range: Interface::Range).void }
|
@@ -125,20 +161,21 @@ module RubyLsp
|
|
125
161
|
T.must(namespace).join("::")
|
126
162
|
end
|
127
163
|
|
128
|
-
|
164
|
+
nesting = @node_context.nesting
|
165
|
+
namespace_entries = @index.resolve(aliased_namespace, nesting)
|
129
166
|
return unless namespace_entries
|
130
167
|
|
131
168
|
real_namespace = @index.follow_aliased_namespace(T.must(namespace_entries.first).name)
|
132
169
|
|
133
170
|
candidates = @index.prefix_search(
|
134
171
|
"#{real_namespace}::#{incomplete_name}",
|
135
|
-
top_level_reference ? [] :
|
172
|
+
top_level_reference ? [] : nesting,
|
136
173
|
)
|
137
174
|
candidates.each do |entries|
|
138
175
|
# The only time we may have a private constant reference from outside of the namespace is if we're dealing
|
139
176
|
# with ConstantPath and the entry name doesn't start with the current nesting
|
140
177
|
first_entry = T.must(entries.first)
|
141
|
-
next if first_entry.
|
178
|
+
next if first_entry.private? && !first_entry.name.start_with?("#{nesting}::")
|
142
179
|
|
143
180
|
constant_name = first_entry.name.delete_prefix("#{real_namespace}::")
|
144
181
|
full_name = aliased_namespace.empty? ? constant_name : "#{aliased_namespace}::#{constant_name}"
|
@@ -153,6 +190,27 @@ module RubyLsp
|
|
153
190
|
end
|
154
191
|
end
|
155
192
|
|
193
|
+
sig { params(name: String, location: Prism::Location).void }
|
194
|
+
def handle_instance_variable_completion(name, location)
|
195
|
+
@index.instance_variable_completion_candidates(name, @node_context.fully_qualified_name).each do |entry|
|
196
|
+
variable_name = entry.name
|
197
|
+
|
198
|
+
@response_builder << Interface::CompletionItem.new(
|
199
|
+
label: variable_name,
|
200
|
+
text_edit: Interface::TextEdit.new(
|
201
|
+
range: range_from_location(location),
|
202
|
+
new_text: variable_name,
|
203
|
+
),
|
204
|
+
kind: Constant::CompletionItemKind::FIELD,
|
205
|
+
data: {
|
206
|
+
owner_name: entry.owner&.name,
|
207
|
+
},
|
208
|
+
)
|
209
|
+
end
|
210
|
+
rescue RubyIndexer::Index::NonExistingNamespaceError
|
211
|
+
# If by any chance we haven't indexed the owner, then there's no way to find the right declaration
|
212
|
+
end
|
213
|
+
|
156
214
|
sig { params(node: Prism::CallNode).void }
|
157
215
|
def complete_require(node)
|
158
216
|
arguments_node = node.arguments
|
@@ -200,15 +258,12 @@ module RubyLsp
|
|
200
258
|
|
201
259
|
sig { params(node: Prism::CallNode, name: String).void }
|
202
260
|
def complete_self_receiver_method(node, name)
|
203
|
-
receiver_entries = @index[@
|
261
|
+
receiver_entries = @index[@node_context.fully_qualified_name]
|
204
262
|
return unless receiver_entries
|
205
263
|
|
206
264
|
receiver = T.must(receiver_entries.first)
|
207
265
|
|
208
|
-
@index.
|
209
|
-
entry = entries.find { |e| e.is_a?(RubyIndexer::Entry::Member) && e.owner&.name == receiver.name }
|
210
|
-
next unless entry
|
211
|
-
|
266
|
+
@index.method_completion_candidates(name, receiver.name).each do |entry|
|
212
267
|
@response_builder << build_method_completion(T.cast(entry, RubyIndexer::Entry::Member), node)
|
213
268
|
end
|
214
269
|
end
|
@@ -297,13 +352,14 @@ module RubyLsp
|
|
297
352
|
#
|
298
353
|
# Foo::B # --> completion inserts `Bar` instead of `Foo::Bar`
|
299
354
|
# end
|
300
|
-
|
301
|
-
|
355
|
+
nesting = @node_context.nesting
|
356
|
+
unless @node_context.fully_qualified_name.start_with?(incomplete_name)
|
357
|
+
nesting.each do |namespace|
|
302
358
|
prefix = "#{namespace}::"
|
303
359
|
shortened_name = insertion_text.delete_prefix(prefix)
|
304
360
|
|
305
361
|
# If a different entry exists for the shortened name, then there's a conflict and we should not shorten it
|
306
|
-
conflict_name = "#{@
|
362
|
+
conflict_name = "#{@node_context.fully_qualified_name}::#{shortened_name}"
|
307
363
|
break if real_name != conflict_name && @index[conflict_name]
|
308
364
|
|
309
365
|
insertion_text = shortened_name
|
@@ -345,8 +401,9 @@ module RubyLsp
|
|
345
401
|
# ```
|
346
402
|
sig { params(entry_name: String).returns(T::Boolean) }
|
347
403
|
def top_level?(entry_name)
|
348
|
-
|
349
|
-
|
404
|
+
nesting = @node_context.nesting
|
405
|
+
nesting.length.downto(0).each do |i|
|
406
|
+
prefix = T.must(nesting[0...i]).join("::")
|
350
407
|
full_name = prefix.empty? ? entry_name : "#{prefix}::#{entry_name}"
|
351
408
|
next if full_name == entry_name
|
352
409
|
|
@@ -14,17 +14,17 @@ 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(
|
@@ -33,18 +33,33 @@ module RubyLsp
|
|
33
33
|
:on_block_argument_node_enter,
|
34
34
|
:on_constant_read_node_enter,
|
35
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,
|
42
|
+
:on_string_node_enter,
|
36
43
|
)
|
37
44
|
end
|
38
45
|
|
39
46
|
sig { params(node: Prism::CallNode).void }
|
40
47
|
def on_call_node_enter(node)
|
41
|
-
message = node.
|
48
|
+
message = node.message
|
49
|
+
return unless message
|
42
50
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
51
|
+
handle_method_definition(message, self_receiver?(node))
|
52
|
+
end
|
53
|
+
|
54
|
+
sig { params(node: Prism::StringNode).void }
|
55
|
+
def on_string_node_enter(node)
|
56
|
+
enclosing_call = @node_context.call_node
|
57
|
+
return unless enclosing_call
|
58
|
+
|
59
|
+
name = enclosing_call.name
|
60
|
+
return unless name == :require || name == :require_relative
|
61
|
+
|
62
|
+
handle_require_definition(node, name)
|
48
63
|
end
|
49
64
|
|
50
65
|
sig { params(node: Prism::BlockArgumentNode).void }
|
@@ -74,12 +89,62 @@ module RubyLsp
|
|
74
89
|
find_in_index(name)
|
75
90
|
end
|
76
91
|
|
92
|
+
sig { params(node: Prism::InstanceVariableReadNode).void }
|
93
|
+
def on_instance_variable_read_node_enter(node)
|
94
|
+
handle_instance_variable_definition(node.name.to_s)
|
95
|
+
end
|
96
|
+
|
97
|
+
sig { params(node: Prism::InstanceVariableWriteNode).void }
|
98
|
+
def on_instance_variable_write_node_enter(node)
|
99
|
+
handle_instance_variable_definition(node.name.to_s)
|
100
|
+
end
|
101
|
+
|
102
|
+
sig { params(node: Prism::InstanceVariableAndWriteNode).void }
|
103
|
+
def on_instance_variable_and_write_node_enter(node)
|
104
|
+
handle_instance_variable_definition(node.name.to_s)
|
105
|
+
end
|
106
|
+
|
107
|
+
sig { params(node: Prism::InstanceVariableOperatorWriteNode).void }
|
108
|
+
def on_instance_variable_operator_write_node_enter(node)
|
109
|
+
handle_instance_variable_definition(node.name.to_s)
|
110
|
+
end
|
111
|
+
|
112
|
+
sig { params(node: Prism::InstanceVariableOrWriteNode).void }
|
113
|
+
def on_instance_variable_or_write_node_enter(node)
|
114
|
+
handle_instance_variable_definition(node.name.to_s)
|
115
|
+
end
|
116
|
+
|
117
|
+
sig { params(node: Prism::InstanceVariableTargetNode).void }
|
118
|
+
def on_instance_variable_target_node_enter(node)
|
119
|
+
handle_instance_variable_definition(node.name.to_s)
|
120
|
+
end
|
121
|
+
|
77
122
|
private
|
78
123
|
|
124
|
+
sig { params(name: String).void }
|
125
|
+
def handle_instance_variable_definition(name)
|
126
|
+
entries = @index.resolve_instance_variable(name, @node_context.fully_qualified_name)
|
127
|
+
return unless entries
|
128
|
+
|
129
|
+
entries.each do |entry|
|
130
|
+
location = entry.location
|
131
|
+
|
132
|
+
@response_builder << Interface::Location.new(
|
133
|
+
uri: URI::Generic.from_path(path: entry.file_path).to_s,
|
134
|
+
range: Interface::Range.new(
|
135
|
+
start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
|
136
|
+
end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
|
137
|
+
),
|
138
|
+
)
|
139
|
+
end
|
140
|
+
rescue RubyIndexer::Index::NonExistingNamespaceError
|
141
|
+
# If by any chance we haven't indexed the owner, then there's no way to find the right declaration
|
142
|
+
end
|
143
|
+
|
79
144
|
sig { params(message: String, self_receiver: T::Boolean).void }
|
80
145
|
def handle_method_definition(message, self_receiver)
|
81
146
|
methods = if self_receiver
|
82
|
-
@index.resolve_method(message, @
|
147
|
+
@index.resolve_method(message, @node_context.fully_qualified_name)
|
83
148
|
else
|
84
149
|
# If the method doesn't have a receiver, then we provide a few candidates to jump to
|
85
150
|
# But we don't want to provide too many candidates, as it can be overwhelming
|
@@ -103,19 +168,12 @@ module RubyLsp
|
|
103
168
|
end
|
104
169
|
end
|
105
170
|
|
106
|
-
sig { params(node: Prism::
|
107
|
-
def handle_require_definition(node)
|
108
|
-
message = node.name
|
109
|
-
arguments = node.arguments
|
110
|
-
return unless arguments
|
111
|
-
|
112
|
-
argument = arguments.arguments.first
|
113
|
-
return unless argument.is_a?(Prism::StringNode)
|
114
|
-
|
171
|
+
sig { params(node: Prism::StringNode, message: Symbol).void }
|
172
|
+
def handle_require_definition(node, message)
|
115
173
|
case message
|
116
174
|
when :require
|
117
|
-
entry = @index.search_require_paths(
|
118
|
-
indexable_path.require_path ==
|
175
|
+
entry = @index.search_require_paths(node.content).find do |indexable_path|
|
176
|
+
indexable_path.require_path == node.content
|
119
177
|
end
|
120
178
|
|
121
179
|
if entry
|
@@ -130,7 +188,7 @@ module RubyLsp
|
|
130
188
|
)
|
131
189
|
end
|
132
190
|
when :require_relative
|
133
|
-
required_file = "#{
|
191
|
+
required_file = "#{node.content}.rb"
|
134
192
|
path = @uri.to_standardized_path
|
135
193
|
current_folder = path ? Pathname.new(CGI.unescape(path)).dirname : Dir.pwd
|
136
194
|
candidate = File.expand_path(File.join(current_folder, required_file))
|
@@ -147,13 +205,13 @@ module RubyLsp
|
|
147
205
|
|
148
206
|
sig { params(value: String).void }
|
149
207
|
def find_in_index(value)
|
150
|
-
entries = @index.resolve(value, @nesting)
|
208
|
+
entries = @index.resolve(value, @node_context.nesting)
|
151
209
|
return unless entries
|
152
210
|
|
153
211
|
# We should only allow jumping to the definition of private constants if the constant is defined in the same
|
154
212
|
# namespace as the reference
|
155
213
|
first_entry = T.must(entries.first)
|
156
|
-
return if first_entry.
|
214
|
+
return if first_entry.private? && first_entry.name != "#{@node_context.fully_qualified_name}::#{value}"
|
157
215
|
|
158
216
|
entries.each do |entry|
|
159
217
|
location = entry.location
|
@@ -13,6 +13,14 @@ 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,
|
22
|
+
Prism::SymbolNode,
|
23
|
+
Prism::StringNode,
|
16
24
|
],
|
17
25
|
T::Array[T.class_of(Prism::Node)],
|
18
26
|
)
|
@@ -30,17 +38,17 @@ module RubyLsp
|
|
30
38
|
response_builder: ResponseBuilders::Hover,
|
31
39
|
global_state: GlobalState,
|
32
40
|
uri: URI::Generic,
|
33
|
-
|
41
|
+
node_context: NodeContext,
|
34
42
|
dispatcher: Prism::Dispatcher,
|
35
43
|
typechecker_enabled: T::Boolean,
|
36
44
|
).void
|
37
45
|
end
|
38
|
-
def initialize(response_builder, global_state, uri,
|
46
|
+
def initialize(response_builder, global_state, uri, node_context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
|
39
47
|
@response_builder = response_builder
|
40
48
|
@global_state = global_state
|
41
49
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
42
50
|
@path = T.let(uri.to_standardized_path, T.nilable(String))
|
43
|
-
@
|
51
|
+
@node_context = node_context
|
44
52
|
@typechecker_enabled = typechecker_enabled
|
45
53
|
|
46
54
|
dispatcher.register(
|
@@ -49,6 +57,12 @@ module RubyLsp
|
|
49
57
|
:on_constant_write_node_enter,
|
50
58
|
:on_constant_path_node_enter,
|
51
59
|
:on_call_node_enter,
|
60
|
+
:on_instance_variable_read_node_enter,
|
61
|
+
:on_instance_variable_write_node_enter,
|
62
|
+
:on_instance_variable_and_write_node_enter,
|
63
|
+
:on_instance_variable_operator_write_node_enter,
|
64
|
+
:on_instance_variable_or_write_node_enter,
|
65
|
+
:on_instance_variable_target_node_enter,
|
52
66
|
)
|
53
67
|
end
|
54
68
|
|
@@ -93,7 +107,7 @@ module RubyLsp
|
|
93
107
|
message = node.message
|
94
108
|
return unless message
|
95
109
|
|
96
|
-
methods = @index.resolve_method(message, @
|
110
|
+
methods = @index.resolve_method(message, @node_context.fully_qualified_name)
|
97
111
|
return unless methods
|
98
112
|
|
99
113
|
categorized_markdown_from_index_entries(message, methods).each do |category, content|
|
@@ -101,17 +115,59 @@ module RubyLsp
|
|
101
115
|
end
|
102
116
|
end
|
103
117
|
|
118
|
+
sig { params(node: Prism::InstanceVariableReadNode).void }
|
119
|
+
def on_instance_variable_read_node_enter(node)
|
120
|
+
handle_instance_variable_hover(node.name.to_s)
|
121
|
+
end
|
122
|
+
|
123
|
+
sig { params(node: Prism::InstanceVariableWriteNode).void }
|
124
|
+
def on_instance_variable_write_node_enter(node)
|
125
|
+
handle_instance_variable_hover(node.name.to_s)
|
126
|
+
end
|
127
|
+
|
128
|
+
sig { params(node: Prism::InstanceVariableAndWriteNode).void }
|
129
|
+
def on_instance_variable_and_write_node_enter(node)
|
130
|
+
handle_instance_variable_hover(node.name.to_s)
|
131
|
+
end
|
132
|
+
|
133
|
+
sig { params(node: Prism::InstanceVariableOperatorWriteNode).void }
|
134
|
+
def on_instance_variable_operator_write_node_enter(node)
|
135
|
+
handle_instance_variable_hover(node.name.to_s)
|
136
|
+
end
|
137
|
+
|
138
|
+
sig { params(node: Prism::InstanceVariableOrWriteNode).void }
|
139
|
+
def on_instance_variable_or_write_node_enter(node)
|
140
|
+
handle_instance_variable_hover(node.name.to_s)
|
141
|
+
end
|
142
|
+
|
143
|
+
sig { params(node: Prism::InstanceVariableTargetNode).void }
|
144
|
+
def on_instance_variable_target_node_enter(node)
|
145
|
+
handle_instance_variable_hover(node.name.to_s)
|
146
|
+
end
|
147
|
+
|
104
148
|
private
|
105
149
|
|
150
|
+
sig { params(name: String).void }
|
151
|
+
def handle_instance_variable_hover(name)
|
152
|
+
entries = @index.resolve_instance_variable(name, @node_context.fully_qualified_name)
|
153
|
+
return unless entries
|
154
|
+
|
155
|
+
categorized_markdown_from_index_entries(name, entries).each do |category, content|
|
156
|
+
@response_builder.push(content, category: category)
|
157
|
+
end
|
158
|
+
rescue RubyIndexer::Index::NonExistingNamespaceError
|
159
|
+
# If by any chance we haven't indexed the owner, then there's no way to find the right declaration
|
160
|
+
end
|
161
|
+
|
106
162
|
sig { params(name: String, location: Prism::Location).void }
|
107
163
|
def generate_hover(name, location)
|
108
|
-
entries = @index.resolve(name, @nesting)
|
164
|
+
entries = @index.resolve(name, @node_context.nesting)
|
109
165
|
return unless entries
|
110
166
|
|
111
167
|
# We should only show hover for private constants if the constant is defined in the same namespace as the
|
112
168
|
# reference
|
113
169
|
first_entry = T.must(entries.first)
|
114
|
-
return if first_entry.
|
170
|
+
return if first_entry.private? && first_entry.name != "#{@node_context.fully_qualified_name}::#{name}"
|
115
171
|
|
116
172
|
categorized_markdown_from_index_entries(name, entries).each do |category, content|
|
117
173
|
@response_builder.push(content, category: category)
|
@@ -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,39 @@
|
|
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
|
+
# its namespace nesting, and the surrounding CallNode (e.g. a method call).
|
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 { returns(T.nilable(Prism::CallNode)) }
|
17
|
+
attr_reader :call_node
|
18
|
+
|
19
|
+
sig do
|
20
|
+
params(
|
21
|
+
node: T.nilable(Prism::Node),
|
22
|
+
parent: T.nilable(Prism::Node),
|
23
|
+
nesting: T::Array[String],
|
24
|
+
call_node: T.nilable(Prism::CallNode),
|
25
|
+
).void
|
26
|
+
end
|
27
|
+
def initialize(node, parent, nesting, call_node)
|
28
|
+
@node = node
|
29
|
+
@parent = parent
|
30
|
+
@nesting = nesting
|
31
|
+
@call_node = call_node
|
32
|
+
end
|
33
|
+
|
34
|
+
sig { returns(String) }
|
35
|
+
def fully_qualified_name
|
36
|
+
@fully_qualified_name ||= T.let(@nesting.join("::"), T.nilable(String))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
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) }
|