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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +21 -4
  5. data/exe/ruby-lsp-check +1 -3
  6. data/exe/ruby-lsp-doctor +1 -4
  7. data/lib/core_ext/uri.rb +3 -0
  8. data/lib/ruby_indexer/lib/ruby_indexer/{collector.rb → declaration_listener.rb} +258 -140
  9. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +101 -12
  10. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +187 -12
  11. data/lib/ruby_indexer/ruby_indexer.rb +1 -1
  12. data/lib/ruby_indexer/test/classes_and_modules_test.rb +106 -10
  13. data/lib/ruby_indexer/test/configuration_test.rb +4 -5
  14. data/lib/ruby_indexer/test/constant_test.rb +11 -8
  15. data/lib/ruby_indexer/test/index_test.rb +528 -0
  16. data/lib/ruby_indexer/test/instance_variables_test.rb +131 -0
  17. data/lib/ruby_indexer/test/method_test.rb +93 -0
  18. data/lib/ruby_indexer/test/test_case.rb +3 -1
  19. data/lib/ruby_lsp/addon.rb +8 -8
  20. data/lib/ruby_lsp/document.rb +3 -3
  21. data/lib/ruby_lsp/internal.rb +1 -0
  22. data/lib/ruby_lsp/listeners/code_lens.rb +11 -0
  23. data/lib/ruby_lsp/listeners/completion.rb +144 -51
  24. data/lib/ruby_lsp/listeners/definition.rb +77 -12
  25. data/lib/ruby_lsp/listeners/document_highlight.rb +1 -1
  26. data/lib/ruby_lsp/listeners/document_link.rb +1 -1
  27. data/lib/ruby_lsp/listeners/hover.rb +60 -6
  28. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +59 -3
  29. data/lib/ruby_lsp/listeners/signature_help.rb +4 -4
  30. data/lib/ruby_lsp/node_context.rb +28 -0
  31. data/lib/ruby_lsp/requests/code_action_resolve.rb +73 -2
  32. data/lib/ruby_lsp/requests/code_actions.rb +16 -15
  33. data/lib/ruby_lsp/requests/completion.rb +22 -13
  34. data/lib/ruby_lsp/requests/completion_resolve.rb +26 -10
  35. data/lib/ruby_lsp/requests/definition.rb +21 -5
  36. data/lib/ruby_lsp/requests/document_highlight.rb +2 -2
  37. data/lib/ruby_lsp/requests/hover.rb +5 -6
  38. data/lib/ruby_lsp/requests/on_type_formatting.rb +8 -4
  39. data/lib/ruby_lsp/requests/signature_help.rb +3 -3
  40. data/lib/ruby_lsp/requests/support/common.rb +20 -1
  41. data/lib/ruby_lsp/requests/workspace_symbol.rb +3 -1
  42. data/lib/ruby_lsp/server.rb +10 -4
  43. 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)
@@ -99,8 +99,8 @@ module RubyLsp
99
99
  end
100
100
 
101
101
  sig { returns(String) }
102
- def backtraces
103
- @errors.filter_map(&:backtrace).join("\n\n")
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
- nesting: T::Array[String],
134
+ node_context: NodeContext,
135
135
  dispatcher: Prism::Dispatcher,
136
136
  ).void
137
137
  end
138
- def create_hover_listener(response_builder, nesting, dispatcher); end
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
- nesting: T::Array[String],
162
+ node_context: NodeContext,
163
163
  dispatcher: Prism::Dispatcher,
164
164
  ).void
165
165
  end
166
- def create_definition_listener(response_builder, uri, nesting, dispatcher); end
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
- nesting: T::Array[String],
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, nesting, dispatcher, uri); end
177
+ def create_completion_listener(response_builder, node_context, dispatcher, uri); end
178
178
  end
179
179
  end
@@ -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([T.nilable(Prism::Node), T.nilable(Prism::Node), T::Array[String]])
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([T.nilable(Prism::Node), T.nilable(Prism::Node), T::Array[String]])
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
- [closest, parent, nesting.map { |n| n.constant_path.location.slice }]
173
+ NodeContext.new(closest, parent, nesting.map { |n| n.constant_path.location.slice })
174
174
  end
175
175
 
176
176
  sig { returns(T::Boolean) }
@@ -29,6 +29,7 @@ require "ruby_lsp/global_state"
29
29
  require "ruby_lsp/server"
30
30
  require "ruby_lsp/requests"
31
31
  require "ruby_lsp/response_builders"
32
+ require "ruby_lsp/node_context"
32
33
  require "ruby_lsp/document"
33
34
  require "ruby_lsp/ruby_document"
34
35
  require "ruby_lsp/store"
@@ -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
- nesting: T::Array[String],
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, nesting, typechecker_enabled, dispatcher, uri) # rubocop:disable Metrics/ParameterLists
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
- @nesting = nesting
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
- *namespace, incomplete_name = name.split("::")
75
- aliased_namespace = T.must(namespace).join("::")
76
- namespace_entries = @index.resolve(aliased_namespace, @nesting)
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 ? [] : @nesting,
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.visibility == :private && !first_entry.name.start_with?("#{@nesting}::")
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
- node,
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(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)
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[@nesting.join("::")]
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.prefix_search(name).each do |entries|
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
- node: Prism::Node,
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, node, entries, top_level)
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
- @nesting.each do |namespace|
267
- prefix = "#{namespace}::"
268
- shortened_name = insertion_text.delete_prefix(prefix)
269
-
270
- # If a different entry exists for the shortened name, then there's a conflict and we should not shorten it
271
- conflict_name = "#{@nesting.join("::")}::#{shortened_name}"
272
- break if real_name != conflict_name && @index[conflict_name]
273
-
274
- insertion_text = shortened_name
275
-
276
- # If the user is typing a fully qualified name `Foo::Bar::Baz`, then we should not use the short name (e.g.:
277
- # `Baz`) as filtering. So we only shorten the filter text if the user is not including the namespaces in their
278
- # typing
279
- filter_text.delete_prefix!(prefix) unless incomplete_name.start_with?(prefix)
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: range_from_node(node),
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
- @nesting.length.downto(0).each do |i|
313
- prefix = T.must(@nesting[0...i]).join("::")
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