ruby-lsp 0.16.5 → 0.16.7
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 +1 -1
- data/VERSION +1 -1
- data/exe/ruby-lsp +23 -2
- 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/configuration.rb +1 -0
- data/lib/ruby_indexer/lib/ruby_indexer/{collector.rb → declaration_listener.rb} +123 -126
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +5 -20
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +4 -2
- data/lib/ruby_indexer/ruby_indexer.rb +1 -1
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +51 -1
- data/lib/ruby_indexer/test/configuration_test.rb +8 -0
- data/lib/ruby_indexer/test/constant_test.rb +3 -0
- data/lib/ruby_indexer/test/method_test.rb +56 -0
- data/lib/ruby_lsp/listeners/code_lens.rb +11 -0
- data/lib/ruby_lsp/listeners/completion.rb +76 -40
- data/lib/ruby_lsp/listeners/definition.rb +16 -7
- 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/semantic_highlighting.rb +59 -3
- data/lib/ruby_lsp/requests/completion.rb +2 -1
- data/lib/ruby_lsp/requests/completion_resolve.rb +17 -10
- data/lib/ruby_lsp/requests/definition.rb +23 -13
- data/lib/ruby_lsp/requests/hover.rb +11 -7
- data/lib/ruby_lsp/requests/request.rb +14 -0
- data/lib/ruby_lsp/requests/support/common.rb +20 -2
- metadata +8 -8
@@ -16,6 +16,17 @@ module RubyIndexer
|
|
16
16
|
assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
|
17
17
|
end
|
18
18
|
|
19
|
+
def test_conditional_method
|
20
|
+
index(<<~RUBY)
|
21
|
+
class Foo
|
22
|
+
def bar
|
23
|
+
end if condition
|
24
|
+
end
|
25
|
+
RUBY
|
26
|
+
|
27
|
+
assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
|
28
|
+
end
|
29
|
+
|
19
30
|
def test_singleton_method_using_self_receiver
|
20
31
|
index(<<~RUBY)
|
21
32
|
class Foo
|
@@ -38,6 +49,28 @@ module RubyIndexer
|
|
38
49
|
assert_no_entry("bar")
|
39
50
|
end
|
40
51
|
|
52
|
+
def test_method_under_dynamic_class_or_module
|
53
|
+
index(<<~RUBY)
|
54
|
+
module Foo
|
55
|
+
class self::Bar
|
56
|
+
def bar
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module Bar
|
62
|
+
def bar
|
63
|
+
end
|
64
|
+
end
|
65
|
+
RUBY
|
66
|
+
|
67
|
+
assert_equal(2, @index["bar"].length)
|
68
|
+
first_entry = T.must(@index["bar"].first)
|
69
|
+
assert_equal("Foo::self::Bar", first_entry.owner.name)
|
70
|
+
second_entry = T.must(@index["bar"].last)
|
71
|
+
assert_equal("Bar", second_entry.owner.name)
|
72
|
+
end
|
73
|
+
|
41
74
|
def test_method_with_parameters
|
42
75
|
index(<<~RUBY)
|
43
76
|
class Foo
|
@@ -285,5 +318,28 @@ module RubyIndexer
|
|
285
318
|
|
286
319
|
assert_no_entry("bar")
|
287
320
|
end
|
321
|
+
|
322
|
+
def test_properly_tracks_multiple_levels_of_nesting
|
323
|
+
index(<<~RUBY)
|
324
|
+
module Foo
|
325
|
+
def first; end
|
326
|
+
|
327
|
+
module Bar
|
328
|
+
def second; end
|
329
|
+
end
|
330
|
+
|
331
|
+
def third; end
|
332
|
+
end
|
333
|
+
RUBY
|
334
|
+
|
335
|
+
entry = T.must(@index["first"]&.first)
|
336
|
+
assert_equal("Foo", T.must(entry.owner).name)
|
337
|
+
|
338
|
+
entry = T.must(@index["second"]&.first)
|
339
|
+
assert_equal("Foo::Bar", T.must(entry.owner).name)
|
340
|
+
|
341
|
+
entry = T.must(@index["third"]&.first)
|
342
|
+
assert_equal("Foo", T.must(entry.owner).name)
|
343
|
+
end
|
288
344
|
end
|
289
345
|
end
|
@@ -42,6 +42,8 @@ module RubyLsp
|
|
42
42
|
@group_stack = T.let([], T::Array[String])
|
43
43
|
@group_id = T.let(1, Integer)
|
44
44
|
@group_id_stack = T.let([], T::Array[Integer])
|
45
|
+
# We want to avoid adding code lenses for nested definitions
|
46
|
+
@def_depth = T.let(0, Integer)
|
45
47
|
|
46
48
|
dispatcher.register(
|
47
49
|
self,
|
@@ -50,6 +52,7 @@ module RubyLsp
|
|
50
52
|
:on_module_node_enter,
|
51
53
|
:on_module_node_leave,
|
52
54
|
:on_def_node_enter,
|
55
|
+
:on_def_node_leave,
|
53
56
|
:on_call_node_enter,
|
54
57
|
:on_call_node_leave,
|
55
58
|
)
|
@@ -88,6 +91,9 @@ module RubyLsp
|
|
88
91
|
|
89
92
|
sig { params(node: Prism::DefNode).void }
|
90
93
|
def on_def_node_enter(node)
|
94
|
+
@def_depth += 1
|
95
|
+
return if @def_depth > 1
|
96
|
+
|
91
97
|
class_name = @group_stack.last
|
92
98
|
return unless class_name&.end_with?("Test")
|
93
99
|
|
@@ -105,6 +111,11 @@ module RubyLsp
|
|
105
111
|
end
|
106
112
|
end
|
107
113
|
|
114
|
+
sig { params(node: Prism::DefNode).void }
|
115
|
+
def on_def_node_leave(node)
|
116
|
+
@def_depth -= 1
|
117
|
+
end
|
118
|
+
|
108
119
|
sig { params(node: Prism::ModuleNode).void }
|
109
120
|
def on_module_node_enter(node)
|
110
121
|
if (path = namespace_constant_name(node))
|
@@ -47,7 +47,7 @@ module RubyLsp
|
|
47
47
|
@response_builder << build_entry_completion(
|
48
48
|
complete_name,
|
49
49
|
name,
|
50
|
-
node,
|
50
|
+
range_from_location(node.location),
|
51
51
|
entries,
|
52
52
|
top_level?(complete_name),
|
53
53
|
)
|
@@ -62,6 +62,53 @@ module RubyLsp
|
|
62
62
|
name = constant_name(node)
|
63
63
|
return if name.nil?
|
64
64
|
|
65
|
+
constant_path_completion(name, range_from_location(node.location))
|
66
|
+
end
|
67
|
+
|
68
|
+
sig { params(node: Prism::CallNode).void }
|
69
|
+
def on_call_node_enter(node)
|
70
|
+
receiver = node.receiver
|
71
|
+
|
72
|
+
# When writing `Foo::`, the AST assigns a method call node (because you can use that syntax to invoke singleton
|
73
|
+
# methods). However, in addition to providing method completion, we also need to show possible constant
|
74
|
+
# completions
|
75
|
+
if (receiver.is_a?(Prism::ConstantReadNode) || receiver.is_a?(Prism::ConstantPathNode)) &&
|
76
|
+
node.call_operator == "::"
|
77
|
+
|
78
|
+
name = constant_name(receiver)
|
79
|
+
|
80
|
+
if name
|
81
|
+
start_loc = node.location
|
82
|
+
end_loc = T.must(node.call_operator_loc)
|
83
|
+
|
84
|
+
constant_path_completion(
|
85
|
+
"#{name}::",
|
86
|
+
Interface::Range.new(
|
87
|
+
start: Interface::Position.new(line: start_loc.start_line - 1, character: start_loc.start_column),
|
88
|
+
end: Interface::Position.new(line: end_loc.end_line - 1, character: end_loc.end_column),
|
89
|
+
),
|
90
|
+
)
|
91
|
+
return
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
name = node.message
|
96
|
+
return unless name
|
97
|
+
|
98
|
+
case name
|
99
|
+
when "require"
|
100
|
+
complete_require(node)
|
101
|
+
when "require_relative"
|
102
|
+
complete_require_relative(node)
|
103
|
+
else
|
104
|
+
complete_self_receiver_method(node, name) if !@typechecker_enabled && self_receiver?(node)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
sig { params(name: String, range: Interface::Range).void }
|
111
|
+
def constant_path_completion(name, range)
|
65
112
|
top_level_reference = if name.start_with?("::")
|
66
113
|
name = name.delete_prefix("::")
|
67
114
|
true
|
@@ -71,8 +118,13 @@ module RubyLsp
|
|
71
118
|
|
72
119
|
# If we're trying to provide completion for an aliased namespace, we need to first discover it's real name in
|
73
120
|
# order to find which possible constants match the desired search
|
74
|
-
|
75
|
-
|
121
|
+
aliased_namespace = if name.end_with?("::")
|
122
|
+
name.delete_suffix("::")
|
123
|
+
else
|
124
|
+
*namespace, incomplete_name = name.split("::")
|
125
|
+
T.must(namespace).join("::")
|
126
|
+
end
|
127
|
+
|
76
128
|
namespace_entries = @index.resolve(aliased_namespace, @nesting)
|
77
129
|
return unless namespace_entries
|
78
130
|
|
@@ -88,37 +140,19 @@ module RubyLsp
|
|
88
140
|
first_entry = T.must(entries.first)
|
89
141
|
next if first_entry.visibility == :private && !first_entry.name.start_with?("#{@nesting}::")
|
90
142
|
|
91
|
-
constant_name =
|
92
|
-
|
143
|
+
constant_name = first_entry.name.delete_prefix("#{real_namespace}::")
|
93
144
|
full_name = aliased_namespace.empty? ? constant_name : "#{aliased_namespace}::#{constant_name}"
|
94
145
|
|
95
146
|
@response_builder << build_entry_completion(
|
96
147
|
full_name,
|
97
148
|
name,
|
98
|
-
|
149
|
+
range,
|
99
150
|
entries,
|
100
151
|
top_level_reference || top_level?(T.must(entries.first).name),
|
101
152
|
)
|
102
153
|
end
|
103
154
|
end
|
104
155
|
|
105
|
-
sig { params(node: Prism::CallNode).void }
|
106
|
-
def on_call_node_enter(node)
|
107
|
-
name = node.message
|
108
|
-
return unless name
|
109
|
-
|
110
|
-
case name
|
111
|
-
when "require"
|
112
|
-
complete_require(node)
|
113
|
-
when "require_relative"
|
114
|
-
complete_require_relative(node)
|
115
|
-
else
|
116
|
-
complete_self_receiver_method(node, name) if !@typechecker_enabled && self_receiver?(node)
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
private
|
121
|
-
|
122
156
|
sig { params(node: Prism::CallNode).void }
|
123
157
|
def complete_require(node)
|
124
158
|
arguments_node = node.arguments
|
@@ -223,12 +257,12 @@ module RubyLsp
|
|
223
257
|
params(
|
224
258
|
real_name: String,
|
225
259
|
incomplete_name: String,
|
226
|
-
|
260
|
+
range: Interface::Range,
|
227
261
|
entries: T::Array[RubyIndexer::Entry],
|
228
262
|
top_level: T::Boolean,
|
229
263
|
).returns(Interface::CompletionItem)
|
230
264
|
end
|
231
|
-
def build_entry_completion(real_name, incomplete_name,
|
265
|
+
def build_entry_completion(real_name, incomplete_name, range, entries, top_level)
|
232
266
|
first_entry = T.must(entries.first)
|
233
267
|
kind = case first_entry
|
234
268
|
when RubyIndexer::Entry::Class
|
@@ -263,20 +297,22 @@ module RubyLsp
|
|
263
297
|
#
|
264
298
|
# Foo::B # --> completion inserts `Bar` instead of `Foo::Bar`
|
265
299
|
# end
|
266
|
-
@nesting.
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
300
|
+
unless @nesting.join("::").start_with?(incomplete_name)
|
301
|
+
@nesting.each do |namespace|
|
302
|
+
prefix = "#{namespace}::"
|
303
|
+
shortened_name = insertion_text.delete_prefix(prefix)
|
304
|
+
|
305
|
+
# If a different entry exists for the shortened name, then there's a conflict and we should not shorten it
|
306
|
+
conflict_name = "#{@nesting.join("::")}::#{shortened_name}"
|
307
|
+
break if real_name != conflict_name && @index[conflict_name]
|
308
|
+
|
309
|
+
insertion_text = shortened_name
|
310
|
+
|
311
|
+
# If the user is typing a fully qualified name `Foo::Bar::Baz`, then we should not use the short name (e.g.:
|
312
|
+
# `Baz`) as filtering. So we only shorten the filter text if the user is not including the namespaces in
|
313
|
+
# their typing
|
314
|
+
filter_text.delete_prefix!(prefix) unless incomplete_name.start_with?(prefix)
|
315
|
+
end
|
280
316
|
end
|
281
317
|
|
282
318
|
# When using a top level constant reference (e.g.: `::Bar`), the editor includes the `::` as part of the filter.
|
@@ -286,7 +322,7 @@ module RubyLsp
|
|
286
322
|
label: real_name,
|
287
323
|
filter_text: filter_text,
|
288
324
|
text_edit: Interface::TextEdit.new(
|
289
|
-
range:
|
325
|
+
range: range,
|
290
326
|
new_text: insertion_text,
|
291
327
|
),
|
292
328
|
kind: kind,
|
@@ -30,6 +30,7 @@ module RubyLsp
|
|
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,
|
35
36
|
)
|
@@ -42,10 +43,21 @@ module RubyLsp
|
|
42
43
|
if message == :require || message == :require_relative
|
43
44
|
handle_require_definition(node)
|
44
45
|
else
|
45
|
-
handle_method_definition(node)
|
46
|
+
handle_method_definition(message.to_s, self_receiver?(node))
|
46
47
|
end
|
47
48
|
end
|
48
49
|
|
50
|
+
sig { params(node: Prism::BlockArgumentNode).void }
|
51
|
+
def on_block_argument_node_enter(node)
|
52
|
+
expression = node.expression
|
53
|
+
return unless expression.is_a?(Prism::SymbolNode)
|
54
|
+
|
55
|
+
value = expression.value
|
56
|
+
return unless value
|
57
|
+
|
58
|
+
handle_method_definition(value, false)
|
59
|
+
end
|
60
|
+
|
49
61
|
sig { params(node: Prism::ConstantPathNode).void }
|
50
62
|
def on_constant_path_node_enter(node)
|
51
63
|
name = constant_name(node)
|
@@ -64,12 +76,9 @@ module RubyLsp
|
|
64
76
|
|
65
77
|
private
|
66
78
|
|
67
|
-
sig { params(
|
68
|
-
def handle_method_definition(
|
69
|
-
|
70
|
-
return unless message
|
71
|
-
|
72
|
-
methods = if self_receiver?(node)
|
79
|
+
sig { params(message: String, self_receiver: T::Boolean).void }
|
80
|
+
def handle_method_definition(message, self_receiver)
|
81
|
+
methods = if self_receiver
|
73
82
|
@index.resolve_method(message, @nesting.join("::"))
|
74
83
|
else
|
75
84
|
# If the method doesn't have a receiver, then we provide a few candidates to jump to
|
@@ -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
|
@@ -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,6 +11,7 @@ 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
|
@@ -34,7 +35,7 @@ module RubyLsp
|
|
34
35
|
def provider
|
35
36
|
Interface::CompletionOptions.new(
|
36
37
|
resolve_provider: true,
|
37
|
-
trigger_characters: ["/", "\"", "'"],
|
38
|
+
trigger_characters: ["/", "\"", "'", ":"],
|
38
39
|
completion_item: {
|
39
40
|
labelDetailsSupport: true,
|
40
41
|
},
|
@@ -36,20 +36,27 @@ module RubyLsp
|
|
36
36
|
@item = item
|
37
37
|
end
|
38
38
|
|
39
|
-
sig { override.returns(
|
39
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
40
40
|
def perform
|
41
|
+
# Based on the spec https://microsoft.github.io/language-server-protocol/specification#textDocument_completion,
|
42
|
+
# a completion resolve request must always return the original completion item without modifying ANY fields
|
43
|
+
# other than label details and documentation. If we modify anything, the completion behaviour might be broken.
|
44
|
+
#
|
45
|
+
# For example, forgetting to return the `insertText` included in the original item will make the editor use the
|
46
|
+
# `label` for the text edit instead
|
41
47
|
label = @item[:label]
|
42
48
|
entries = @index[label] || []
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
),
|
49
|
+
|
50
|
+
@item[:labelDetails] = Interface::CompletionItemLabelDetails.new(
|
51
|
+
description: entries.take(MAX_DOCUMENTATION_ENTRIES).map(&:file_name).join(","),
|
52
|
+
)
|
53
|
+
|
54
|
+
@item[:documentation] = Interface::MarkupContent.new(
|
55
|
+
kind: "markdown",
|
56
|
+
value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES),
|
52
57
|
)
|
58
|
+
|
59
|
+
@item
|
53
60
|
end
|
54
61
|
end
|
55
62
|
end
|
@@ -12,6 +12,7 @@ module RubyLsp
|
|
12
12
|
# definition of the symbol under the cursor.
|
13
13
|
#
|
14
14
|
# Currently supported targets:
|
15
|
+
#
|
15
16
|
# - Classes
|
16
17
|
# - Modules
|
17
18
|
# - Constants
|
@@ -43,40 +44,49 @@ module RubyLsp
|
|
43
44
|
ResponseBuilders::CollectionResponseBuilder[Interface::Location].new,
|
44
45
|
ResponseBuilders::CollectionResponseBuilder[Interface::Location],
|
45
46
|
)
|
47
|
+
@dispatcher = dispatcher
|
46
48
|
|
47
49
|
target, parent, nesting = document.locate_node(
|
48
50
|
position,
|
49
|
-
node_types: [Prism::CallNode, Prism::ConstantReadNode, Prism::ConstantPathNode],
|
51
|
+
node_types: [Prism::CallNode, Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::BlockArgumentNode],
|
50
52
|
)
|
51
53
|
|
52
54
|
if target.is_a?(Prism::ConstantReadNode) && parent.is_a?(Prism::ConstantPathNode)
|
55
|
+
# If the target is part of a constant path node, we need to find the exact portion of the constant that the
|
56
|
+
# user is requesting to go to definition for
|
53
57
|
target = determine_target(
|
54
58
|
target,
|
55
59
|
parent,
|
56
60
|
position,
|
57
61
|
)
|
62
|
+
elsif target.is_a?(Prism::CallNode) && target.name != :require && target.name != :require_relative &&
|
63
|
+
!covers_position?(target.message_loc, position)
|
64
|
+
# If the target is a method call, we need to ensure that the requested position is exactly on top of the
|
65
|
+
# method identifier. Otherwise, we risk showing definitions for unrelated things
|
66
|
+
target = nil
|
58
67
|
end
|
59
68
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
69
|
+
if target
|
70
|
+
Listeners::Definition.new(
|
71
|
+
@response_builder,
|
72
|
+
global_state,
|
73
|
+
document.uri,
|
74
|
+
nesting,
|
75
|
+
dispatcher,
|
76
|
+
typechecker_enabled,
|
77
|
+
)
|
68
78
|
|
69
|
-
|
70
|
-
|
79
|
+
Addon.addons.each do |addon|
|
80
|
+
addon.create_definition_listener(@response_builder, document.uri, nesting, dispatcher)
|
81
|
+
end
|
71
82
|
end
|
72
83
|
|
73
84
|
@target = T.let(target, T.nilable(Prism::Node))
|
74
|
-
@dispatcher = dispatcher
|
75
85
|
end
|
76
86
|
|
77
87
|
sig { override.returns(T::Array[Interface::Location]) }
|
78
88
|
def perform
|
79
|
-
@dispatcher.dispatch_once(@target)
|
89
|
+
@dispatcher.dispatch_once(@target) if @target
|
80
90
|
@response_builder.response
|
81
91
|
end
|
82
92
|
end
|
@@ -41,25 +41,29 @@ module RubyLsp
|
|
41
41
|
end
|
42
42
|
def initialize(document, global_state, position, dispatcher, typechecker_enabled)
|
43
43
|
super()
|
44
|
-
|
45
|
-
@target, parent, nesting = document.locate_node(
|
44
|
+
target, parent, nesting = document.locate_node(
|
46
45
|
position,
|
47
46
|
node_types: Listeners::Hover::ALLOWED_TARGETS,
|
48
47
|
)
|
49
48
|
|
50
49
|
if (Listeners::Hover::ALLOWED_TARGETS.include?(parent.class) &&
|
51
|
-
!Listeners::Hover::ALLOWED_TARGETS.include?(
|
52
|
-
(parent.is_a?(Prism::ConstantPathNode) &&
|
53
|
-
|
54
|
-
T.must(
|
50
|
+
!Listeners::Hover::ALLOWED_TARGETS.include?(target.class)) ||
|
51
|
+
(parent.is_a?(Prism::ConstantPathNode) && target.is_a?(Prism::ConstantReadNode))
|
52
|
+
target = determine_target(
|
53
|
+
T.must(target),
|
55
54
|
T.must(parent),
|
56
55
|
position,
|
57
56
|
)
|
57
|
+
elsif target.is_a?(Prism::CallNode) && target.name != :require && target.name != :require_relative &&
|
58
|
+
!covers_position?(target.message_loc, position)
|
59
|
+
|
60
|
+
target = nil
|
58
61
|
end
|
59
62
|
|
60
63
|
# Don't need to instantiate any listeners if there's no target
|
61
|
-
return unless
|
64
|
+
return unless target
|
62
65
|
|
66
|
+
@target = T.let(target, T.nilable(Prism::Node))
|
63
67
|
uri = document.uri
|
64
68
|
@response_builder = T.let(ResponseBuilders::Hover.new, ResponseBuilders::Hover)
|
65
69
|
Listeners::Hover.new(@response_builder, global_state, uri, nesting, dispatcher, typechecker_enabled)
|
@@ -65,6 +65,20 @@ module RubyLsp
|
|
65
65
|
|
66
66
|
target
|
67
67
|
end
|
68
|
+
|
69
|
+
# Checks if a given location covers the position requested
|
70
|
+
sig { params(location: T.nilable(Prism::Location), position: T::Hash[Symbol, T.untyped]).returns(T::Boolean) }
|
71
|
+
def covers_position?(location, position)
|
72
|
+
return false unless location
|
73
|
+
|
74
|
+
start_line = location.start_line - 1
|
75
|
+
end_line = location.end_line - 1
|
76
|
+
line = position[:line]
|
77
|
+
character = position[:character]
|
78
|
+
|
79
|
+
(start_line < line || (start_line == line && location.start_column <= character)) &&
|
80
|
+
(end_line > line || (end_line == line && location.end_column >= character))
|
81
|
+
end
|
68
82
|
end
|
69
83
|
end
|
70
84
|
end
|
@@ -50,12 +50,12 @@ module RubyLsp
|
|
50
50
|
params(
|
51
51
|
node: Prism::Node,
|
52
52
|
title: String,
|
53
|
-
command_name: String,
|
53
|
+
command_name: T.nilable(String),
|
54
54
|
arguments: T.nilable(T::Array[T.untyped]),
|
55
55
|
data: T.nilable(T::Hash[T.untyped, T.untyped]),
|
56
56
|
).returns(Interface::CodeLens)
|
57
57
|
end
|
58
|
-
def create_code_lens(node, title:, command_name
|
58
|
+
def create_code_lens(node, title:, command_name: nil, arguments: nil, data: nil)
|
59
59
|
range = range_from_node(node)
|
60
60
|
|
61
61
|
Interface::CodeLens.new(
|
@@ -167,6 +167,24 @@ module RubyLsp
|
|
167
167
|
constant_name(path)
|
168
168
|
end
|
169
169
|
end
|
170
|
+
|
171
|
+
# Iterates over each part of a constant path, so that we can easily push response items for each section of the
|
172
|
+
# name. For example, for `Foo::Bar::Baz`, this method will invoke the block with `Foo`, then `Bar` and finally
|
173
|
+
# `Baz`.
|
174
|
+
sig do
|
175
|
+
params(
|
176
|
+
node: Prism::Node,
|
177
|
+
block: T.proc.params(part: Prism::Node).void,
|
178
|
+
).void
|
179
|
+
end
|
180
|
+
def each_constant_path_part(node, &block)
|
181
|
+
current = T.let(node, T.nilable(Prism::Node))
|
182
|
+
|
183
|
+
while current.is_a?(Prism::ConstantPathNode)
|
184
|
+
block.call(current)
|
185
|
+
current = current.parent
|
186
|
+
end
|
187
|
+
end
|
170
188
|
end
|
171
189
|
end
|
172
190
|
end
|