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
@@ -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,65 @@ 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
|
+
|
74
|
+
def test_visibility_tracking
|
75
|
+
index(<<~RUBY)
|
76
|
+
private def foo
|
77
|
+
end
|
78
|
+
|
79
|
+
def bar; end
|
80
|
+
|
81
|
+
protected
|
82
|
+
|
83
|
+
def baz; end
|
84
|
+
RUBY
|
85
|
+
|
86
|
+
assert_entry("foo", Entry::InstanceMethod, "/fake/path/foo.rb:0-8:1-3", visibility: Entry::Visibility::PRIVATE)
|
87
|
+
assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:3-0:3-12", visibility: Entry::Visibility::PUBLIC)
|
88
|
+
assert_entry("baz", Entry::InstanceMethod, "/fake/path/foo.rb:7-0:7-12", visibility: Entry::Visibility::PROTECTED)
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_visibility_tracking_with_nested_class_or_modules
|
92
|
+
index(<<~RUBY)
|
93
|
+
class Foo
|
94
|
+
private
|
95
|
+
|
96
|
+
def foo; end
|
97
|
+
|
98
|
+
class Bar
|
99
|
+
def bar; end
|
100
|
+
end
|
101
|
+
|
102
|
+
def baz; end
|
103
|
+
end
|
104
|
+
RUBY
|
105
|
+
|
106
|
+
assert_entry("foo", Entry::InstanceMethod, "/fake/path/foo.rb:3-2:3-14", visibility: Entry::Visibility::PRIVATE)
|
107
|
+
assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:6-4:6-16", visibility: Entry::Visibility::PUBLIC)
|
108
|
+
assert_entry("baz", Entry::InstanceMethod, "/fake/path/foo.rb:9-2:9-14", visibility: Entry::Visibility::PRIVATE)
|
109
|
+
end
|
110
|
+
|
41
111
|
def test_method_with_parameters
|
42
112
|
index(<<~RUBY)
|
43
113
|
class Foo
|
@@ -285,5 +355,28 @@ module RubyIndexer
|
|
285
355
|
|
286
356
|
assert_no_entry("bar")
|
287
357
|
end
|
358
|
+
|
359
|
+
def test_properly_tracks_multiple_levels_of_nesting
|
360
|
+
index(<<~RUBY)
|
361
|
+
module Foo
|
362
|
+
def first; end
|
363
|
+
|
364
|
+
module Bar
|
365
|
+
def second; end
|
366
|
+
end
|
367
|
+
|
368
|
+
def third; end
|
369
|
+
end
|
370
|
+
RUBY
|
371
|
+
|
372
|
+
entry = T.must(@index["first"]&.first)
|
373
|
+
assert_equal("Foo", T.must(entry.owner).name)
|
374
|
+
|
375
|
+
entry = T.must(@index["second"]&.first)
|
376
|
+
assert_equal("Foo::Bar", T.must(entry.owner).name)
|
377
|
+
|
378
|
+
entry = T.must(@index["third"]&.first)
|
379
|
+
assert_equal("Foo", T.must(entry.owner).name)
|
380
|
+
end
|
288
381
|
end
|
289
382
|
end
|
@@ -15,7 +15,7 @@ module RubyIndexer
|
|
15
15
|
@index.index_single(IndexablePath.new(nil, "/fake/path/foo.rb"), source)
|
16
16
|
end
|
17
17
|
|
18
|
-
def assert_entry(expected_name, type, expected_location)
|
18
|
+
def assert_entry(expected_name, type, expected_location, visibility: nil)
|
19
19
|
entries = @index[expected_name]
|
20
20
|
refute_empty(entries, "Expected #{expected_name} to be indexed")
|
21
21
|
|
@@ -28,6 +28,8 @@ module RubyIndexer
|
|
28
28
|
":#{location.end_line - 1}-#{location.end_column}"
|
29
29
|
|
30
30
|
assert_equal(expected_location, location_string)
|
31
|
+
|
32
|
+
assert_equal(visibility, entry.visibility) if visibility
|
31
33
|
end
|
32
34
|
|
33
35
|
def refute_entry(expected_name)
|
data/lib/ruby_lsp/addon.rb
CHANGED
@@ -99,8 +99,8 @@ module RubyLsp
|
|
99
99
|
end
|
100
100
|
|
101
101
|
sig { returns(String) }
|
102
|
-
def
|
103
|
-
@errors.
|
102
|
+
def errors_details
|
103
|
+
@errors.map(&:full_message).join("\n\n")
|
104
104
|
end
|
105
105
|
|
106
106
|
# Each addon should implement `MyAddon#activate` and use to perform any sort of initialization, such as
|
@@ -131,11 +131,11 @@ module RubyLsp
|
|
131
131
|
sig do
|
132
132
|
overridable.params(
|
133
133
|
response_builder: ResponseBuilders::Hover,
|
134
|
-
|
134
|
+
node_context: NodeContext,
|
135
135
|
dispatcher: Prism::Dispatcher,
|
136
136
|
).void
|
137
137
|
end
|
138
|
-
def create_hover_listener(response_builder,
|
138
|
+
def create_hover_listener(response_builder, node_context, dispatcher); end
|
139
139
|
|
140
140
|
# Creates a new DocumentSymbol listener. This method is invoked on every DocumentSymbol request
|
141
141
|
sig do
|
@@ -159,21 +159,21 @@ module RubyLsp
|
|
159
159
|
overridable.params(
|
160
160
|
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
|
161
161
|
uri: URI::Generic,
|
162
|
-
|
162
|
+
node_context: NodeContext,
|
163
163
|
dispatcher: Prism::Dispatcher,
|
164
164
|
).void
|
165
165
|
end
|
166
|
-
def create_definition_listener(response_builder, uri,
|
166
|
+
def create_definition_listener(response_builder, uri, node_context, dispatcher); end
|
167
167
|
|
168
168
|
# Creates a new Completion listener. This method is invoked on every Completion request
|
169
169
|
sig do
|
170
170
|
overridable.params(
|
171
171
|
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
|
172
|
-
|
172
|
+
node_context: NodeContext,
|
173
173
|
dispatcher: Prism::Dispatcher,
|
174
174
|
uri: URI::Generic,
|
175
175
|
).void
|
176
176
|
end
|
177
|
-
def create_completion_listener(response_builder,
|
177
|
+
def create_completion_listener(response_builder, node_context, dispatcher, uri); end
|
178
178
|
end
|
179
179
|
end
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -110,7 +110,7 @@ module RubyLsp
|
|
110
110
|
params(
|
111
111
|
position: T::Hash[Symbol, T.untyped],
|
112
112
|
node_types: T::Array[T.class_of(Prism::Node)],
|
113
|
-
).returns(
|
113
|
+
).returns(NodeContext)
|
114
114
|
end
|
115
115
|
def locate_node(position, node_types: [])
|
116
116
|
locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
|
@@ -121,7 +121,7 @@ module RubyLsp
|
|
121
121
|
node: Prism::Node,
|
122
122
|
char_position: Integer,
|
123
123
|
node_types: T::Array[T.class_of(Prism::Node)],
|
124
|
-
).returns(
|
124
|
+
).returns(NodeContext)
|
125
125
|
end
|
126
126
|
def locate(node, char_position, node_types: [])
|
127
127
|
queue = T.let(node.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
|
@@ -170,7 +170,7 @@ module RubyLsp
|
|
170
170
|
end
|
171
171
|
end
|
172
172
|
|
173
|
-
|
173
|
+
NodeContext.new(closest, parent, nesting.map { |n| n.constant_path.location.slice })
|
174
174
|
end
|
175
175
|
|
176
176
|
sig { returns(T::Boolean) }
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -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))
|
@@ -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,13 +47,13 @@ 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(
|
48
54
|
complete_name,
|
49
55
|
name,
|
50
|
-
node,
|
56
|
+
range_from_location(node.location),
|
51
57
|
entries,
|
52
58
|
top_level?(complete_name),
|
53
59
|
)
|
@@ -62,6 +68,83 @@ module RubyLsp
|
|
62
68
|
name = constant_name(node)
|
63
69
|
return if name.nil?
|
64
70
|
|
71
|
+
constant_path_completion(name, range_from_location(node.location))
|
72
|
+
end
|
73
|
+
|
74
|
+
sig { params(node: Prism::CallNode).void }
|
75
|
+
def on_call_node_enter(node)
|
76
|
+
receiver = node.receiver
|
77
|
+
|
78
|
+
# When writing `Foo::`, the AST assigns a method call node (because you can use that syntax to invoke singleton
|
79
|
+
# methods). However, in addition to providing method completion, we also need to show possible constant
|
80
|
+
# completions
|
81
|
+
if (receiver.is_a?(Prism::ConstantReadNode) || receiver.is_a?(Prism::ConstantPathNode)) &&
|
82
|
+
node.call_operator == "::"
|
83
|
+
|
84
|
+
name = constant_name(receiver)
|
85
|
+
|
86
|
+
if name
|
87
|
+
start_loc = node.location
|
88
|
+
end_loc = T.must(node.call_operator_loc)
|
89
|
+
|
90
|
+
constant_path_completion(
|
91
|
+
"#{name}::",
|
92
|
+
Interface::Range.new(
|
93
|
+
start: Interface::Position.new(line: start_loc.start_line - 1, character: start_loc.start_column),
|
94
|
+
end: Interface::Position.new(line: end_loc.end_line - 1, character: end_loc.end_column),
|
95
|
+
),
|
96
|
+
)
|
97
|
+
return
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
name = node.message
|
102
|
+
return unless name
|
103
|
+
|
104
|
+
case name
|
105
|
+
when "require"
|
106
|
+
complete_require(node)
|
107
|
+
when "require_relative"
|
108
|
+
complete_require_relative(node)
|
109
|
+
else
|
110
|
+
complete_self_receiver_method(node, name) if !@typechecker_enabled && self_receiver?(node)
|
111
|
+
end
|
112
|
+
end
|
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
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
sig { params(name: String, range: Interface::Range).void }
|
147
|
+
def constant_path_completion(name, range)
|
65
148
|
top_level_reference = if name.start_with?("::")
|
66
149
|
name = name.delete_prefix("::")
|
67
150
|
true
|
@@ -71,54 +154,63 @@ module RubyLsp
|
|
71
154
|
|
72
155
|
# If we're trying to provide completion for an aliased namespace, we need to first discover it's real name in
|
73
156
|
# order to find which possible constants match the desired search
|
74
|
-
|
75
|
-
|
76
|
-
|
157
|
+
aliased_namespace = if name.end_with?("::")
|
158
|
+
name.delete_suffix("::")
|
159
|
+
else
|
160
|
+
*namespace, incomplete_name = name.split("::")
|
161
|
+
T.must(namespace).join("::")
|
162
|
+
end
|
163
|
+
|
164
|
+
nesting = @node_context.nesting
|
165
|
+
namespace_entries = @index.resolve(aliased_namespace, nesting)
|
77
166
|
return unless namespace_entries
|
78
167
|
|
79
168
|
real_namespace = @index.follow_aliased_namespace(T.must(namespace_entries.first).name)
|
80
169
|
|
81
170
|
candidates = @index.prefix_search(
|
82
171
|
"#{real_namespace}::#{incomplete_name}",
|
83
|
-
top_level_reference ? [] :
|
172
|
+
top_level_reference ? [] : nesting,
|
84
173
|
)
|
85
174
|
candidates.each do |entries|
|
86
175
|
# The only time we may have a private constant reference from outside of the namespace is if we're dealing
|
87
176
|
# with ConstantPath and the entry name doesn't start with the current nesting
|
88
177
|
first_entry = T.must(entries.first)
|
89
|
-
next if first_entry.
|
90
|
-
|
91
|
-
constant_name = T.must(first_entry.name.split("::").last)
|
178
|
+
next if first_entry.private? && !first_entry.name.start_with?("#{nesting}::")
|
92
179
|
|
180
|
+
constant_name = first_entry.name.delete_prefix("#{real_namespace}::")
|
93
181
|
full_name = aliased_namespace.empty? ? constant_name : "#{aliased_namespace}::#{constant_name}"
|
94
182
|
|
95
183
|
@response_builder << build_entry_completion(
|
96
184
|
full_name,
|
97
185
|
name,
|
98
|
-
|
186
|
+
range,
|
99
187
|
entries,
|
100
188
|
top_level_reference || top_level?(T.must(entries.first).name),
|
101
189
|
)
|
102
190
|
end
|
103
191
|
end
|
104
192
|
|
105
|
-
sig { params(
|
106
|
-
def
|
107
|
-
name
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
+
)
|
117
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
|
118
212
|
end
|
119
213
|
|
120
|
-
private
|
121
|
-
|
122
214
|
sig { params(node: Prism::CallNode).void }
|
123
215
|
def complete_require(node)
|
124
216
|
arguments_node = node.arguments
|
@@ -166,15 +258,12 @@ module RubyLsp
|
|
166
258
|
|
167
259
|
sig { params(node: Prism::CallNode, name: String).void }
|
168
260
|
def complete_self_receiver_method(node, name)
|
169
|
-
receiver_entries = @index[@
|
261
|
+
receiver_entries = @index[@node_context.fully_qualified_name]
|
170
262
|
return unless receiver_entries
|
171
263
|
|
172
264
|
receiver = T.must(receiver_entries.first)
|
173
265
|
|
174
|
-
@index.
|
175
|
-
entry = entries.find { |e| e.is_a?(RubyIndexer::Entry::Member) && e.owner&.name == receiver.name }
|
176
|
-
next unless entry
|
177
|
-
|
266
|
+
@index.method_completion_candidates(name, receiver.name).each do |entry|
|
178
267
|
@response_builder << build_method_completion(T.cast(entry, RubyIndexer::Entry::Member), node)
|
179
268
|
end
|
180
269
|
end
|
@@ -223,12 +312,12 @@ module RubyLsp
|
|
223
312
|
params(
|
224
313
|
real_name: String,
|
225
314
|
incomplete_name: String,
|
226
|
-
|
315
|
+
range: Interface::Range,
|
227
316
|
entries: T::Array[RubyIndexer::Entry],
|
228
317
|
top_level: T::Boolean,
|
229
318
|
).returns(Interface::CompletionItem)
|
230
319
|
end
|
231
|
-
def build_entry_completion(real_name, incomplete_name,
|
320
|
+
def build_entry_completion(real_name, incomplete_name, range, entries, top_level)
|
232
321
|
first_entry = T.must(entries.first)
|
233
322
|
kind = case first_entry
|
234
323
|
when RubyIndexer::Entry::Class
|
@@ -263,20 +352,23 @@ module RubyLsp
|
|
263
352
|
#
|
264
353
|
# Foo::B # --> completion inserts `Bar` instead of `Foo::Bar`
|
265
354
|
# end
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
355
|
+
nesting = @node_context.nesting
|
356
|
+
unless @node_context.fully_qualified_name.start_with?(incomplete_name)
|
357
|
+
nesting.each do |namespace|
|
358
|
+
prefix = "#{namespace}::"
|
359
|
+
shortened_name = insertion_text.delete_prefix(prefix)
|
360
|
+
|
361
|
+
# If a different entry exists for the shortened name, then there's a conflict and we should not shorten it
|
362
|
+
conflict_name = "#{@node_context.fully_qualified_name}::#{shortened_name}"
|
363
|
+
break if real_name != conflict_name && @index[conflict_name]
|
364
|
+
|
365
|
+
insertion_text = shortened_name
|
366
|
+
|
367
|
+
# If the user is typing a fully qualified name `Foo::Bar::Baz`, then we should not use the short name (e.g.:
|
368
|
+
# `Baz`) as filtering. So we only shorten the filter text if the user is not including the namespaces in
|
369
|
+
# their typing
|
370
|
+
filter_text.delete_prefix!(prefix) unless incomplete_name.start_with?(prefix)
|
371
|
+
end
|
280
372
|
end
|
281
373
|
|
282
374
|
# When using a top level constant reference (e.g.: `::Bar`), the editor includes the `::` as part of the filter.
|
@@ -286,7 +378,7 @@ module RubyLsp
|
|
286
378
|
label: real_name,
|
287
379
|
filter_text: filter_text,
|
288
380
|
text_edit: Interface::TextEdit.new(
|
289
|
-
range:
|
381
|
+
range: range,
|
290
382
|
new_text: insertion_text,
|
291
383
|
),
|
292
384
|
kind: kind,
|
@@ -309,8 +401,9 @@ module RubyLsp
|
|
309
401
|
# ```
|
310
402
|
sig { params(entry_name: String).returns(T::Boolean) }
|
311
403
|
def top_level?(entry_name)
|
312
|
-
|
313
|
-
|
404
|
+
nesting = @node_context.nesting
|
405
|
+
nesting.length.downto(0).each do |i|
|
406
|
+
prefix = T.must(nesting[0...i]).join("::")
|
314
407
|
full_name = prefix.empty? ? entry_name : "#{prefix}::#{entry_name}"
|
315
408
|
next if full_name == entry_name
|
316
409
|
|