ruby-lsp 0.27.0.beta2 → 0.27.0.beta3

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +0 -46
  4. data/exe/ruby-lsp-check +0 -15
  5. data/lib/ruby_lsp/global_state.rb +0 -5
  6. data/lib/ruby_lsp/internal.rb +2 -1
  7. data/lib/ruby_lsp/listeners/code_lens.rb +1 -1
  8. data/lib/ruby_lsp/listeners/completion.rb +246 -382
  9. data/lib/ruby_lsp/listeners/definition.rb +6 -9
  10. data/lib/ruby_lsp/listeners/hover.rb +11 -9
  11. data/lib/ruby_lsp/listeners/signature_help.rb +11 -12
  12. data/lib/ruby_lsp/listeners/test_discovery.rb +17 -1
  13. data/lib/ruby_lsp/listeners/test_style.rb +1 -1
  14. data/lib/ruby_lsp/requests/completion_resolve.rb +49 -29
  15. data/lib/ruby_lsp/requests/references.rb +21 -7
  16. data/lib/ruby_lsp/requests/rename.rb +1 -1
  17. data/lib/ruby_lsp/requests/support/common.rb +69 -68
  18. data/lib/ruby_lsp/ruby_document.rb +0 -73
  19. data/lib/ruby_lsp/rubydex/declaration.rb +128 -2
  20. data/lib/ruby_lsp/rubydex/definition.rb +16 -0
  21. data/lib/ruby_lsp/rubydex/signature.rb +107 -0
  22. data/lib/ruby_lsp/scripts/compose_bundle.rb +1 -1
  23. data/lib/ruby_lsp/server.rb +7 -162
  24. data/lib/ruby_lsp/test_helper.rb +0 -1
  25. data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +1 -1
  26. data/lib/ruby_lsp/type_inferrer.rb +2 -2
  27. metadata +11 -14
  28. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +0 -276
  29. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +0 -1101
  30. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +0 -44
  31. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +0 -605
  32. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +0 -1077
  33. data/lib/ruby_indexer/lib/ruby_indexer/location.rb +0 -37
  34. data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +0 -149
  35. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +0 -294
  36. data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +0 -32
  37. data/lib/ruby_indexer/ruby_indexer.rb +0 -19
  38. /data/lib/{ruby_indexer/lib/ruby_indexer → ruby_lsp}/uri.rb +0 -0
@@ -6,50 +6,6 @@ module RubyLsp
6
6
  class Completion
7
7
  include Requests::Support::Common
8
8
 
9
- KEYWORDS = [
10
- "alias",
11
- "and",
12
- "begin",
13
- "BEGIN",
14
- "break",
15
- "case",
16
- "class",
17
- "def",
18
- "defined?",
19
- "do",
20
- "else",
21
- "elsif",
22
- "end",
23
- "END",
24
- "ensure",
25
- "false",
26
- "for",
27
- "if",
28
- "in",
29
- "module",
30
- "next",
31
- "nil",
32
- "not",
33
- "or",
34
- "redo",
35
- "rescue",
36
- "retry",
37
- "return",
38
- "self",
39
- "super",
40
- "then",
41
- "true",
42
- "undef",
43
- "unless",
44
- "until",
45
- "when",
46
- "while",
47
- "yield",
48
- "__ENCODING__",
49
- "__FILE__",
50
- "__LINE__",
51
- ].freeze
52
-
53
9
  #: (ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem] response_builder, GlobalState global_state, NodeContext node_context, SorbetLevel sorbet_level, Prism::Dispatcher dispatcher, URI::Generic uri, String? trigger_character) -> void
54
10
  def initialize( # rubocop:disable Metrics/ParameterLists
55
11
  response_builder,
@@ -62,7 +18,7 @@ module RubyLsp
62
18
  )
63
19
  @response_builder = response_builder
64
20
  @global_state = global_state
65
- @index = global_state.index #: RubyIndexer::Index
21
+ @graph = global_state.graph #: Rubydex::Graph
66
22
  @type_inferrer = global_state.type_inferrer #: TypeInferrer
67
23
  @node_context = node_context
68
24
  @sorbet_level = sorbet_level
@@ -102,22 +58,10 @@ module RubyLsp
102
58
  # no sigil, Sorbet will still provide completion for constants
103
59
  return unless @sorbet_level.ignore?
104
60
 
105
- name = RubyIndexer::Index.constant_name(node)
61
+ name = constant_name(node)
106
62
  return if name.nil?
107
63
 
108
- range = range_from_location(node.location)
109
- candidates = @index.constant_completion_candidates(name, @node_context.nesting)
110
- candidates.each do |entries|
111
- complete_name = entries.first #: as !nil
112
- .name
113
- @response_builder << build_entry_completion(
114
- complete_name,
115
- name,
116
- range,
117
- entries,
118
- top_level?(complete_name),
119
- )
120
- end
64
+ complete_constants(range_from_location(node.location), name)
121
65
  end
122
66
 
123
67
  # Handle completion on namespaced constant references (e.g. `Foo::Bar`)
@@ -152,7 +96,7 @@ module RubyLsp
152
96
  if (receiver.is_a?(Prism::ConstantReadNode) || receiver.is_a?(Prism::ConstantPathNode)) &&
153
97
  node.call_operator == "::"
154
98
 
155
- name = RubyIndexer::Index.constant_name(receiver)
99
+ name = constant_name(receiver)
156
100
 
157
101
  if name
158
102
  start_loc = node.location
@@ -179,245 +123,298 @@ module RubyLsp
179
123
  when "require_relative"
180
124
  complete_require_relative(node)
181
125
  else
182
- complete_methods(node, name)
126
+ if node.receiver
127
+ complete_method_call(node, name)
128
+ else
129
+ # Sorbet provides method-on-self completion for any file with a Sorbet level of true or higher
130
+ return if @sorbet_level.true_or_higher?
131
+
132
+ message_loc = node.message_loc #: as !nil
133
+ complete_methods_no_receiver(range_from_location(message_loc), name)
134
+ end
183
135
  end
184
136
  end
185
137
 
186
138
  #: (Prism::GlobalVariableAndWriteNode node) -> void
187
139
  def on_global_variable_and_write_node_enter(node)
188
- handle_global_variable_completion(node.name.to_s, node.name_loc)
140
+ complete_variable(range_from_location(node.name_loc), node.name.to_s)
189
141
  end
190
142
 
191
143
  #: (Prism::GlobalVariableOperatorWriteNode node) -> void
192
144
  def on_global_variable_operator_write_node_enter(node)
193
- handle_global_variable_completion(node.name.to_s, node.name_loc)
145
+ complete_variable(range_from_location(node.name_loc), node.name.to_s)
194
146
  end
195
147
 
196
148
  #: (Prism::GlobalVariableOrWriteNode node) -> void
197
149
  def on_global_variable_or_write_node_enter(node)
198
- handle_global_variable_completion(node.name.to_s, node.name_loc)
150
+ complete_variable(range_from_location(node.name_loc), node.name.to_s)
199
151
  end
200
152
 
201
153
  #: (Prism::GlobalVariableReadNode node) -> void
202
154
  def on_global_variable_read_node_enter(node)
203
- handle_global_variable_completion(node.name.to_s, node.location)
155
+ complete_variable(range_from_location(node.location), node.name.to_s)
204
156
  end
205
157
 
206
158
  #: (Prism::GlobalVariableTargetNode node) -> void
207
159
  def on_global_variable_target_node_enter(node)
208
- handle_global_variable_completion(node.name.to_s, node.location)
160
+ complete_variable(range_from_location(node.location), node.name.to_s)
209
161
  end
210
162
 
211
163
  #: (Prism::GlobalVariableWriteNode node) -> void
212
164
  def on_global_variable_write_node_enter(node)
213
- handle_global_variable_completion(node.name.to_s, node.name_loc)
165
+ complete_variable(range_from_location(node.name_loc), node.name.to_s)
214
166
  end
215
167
 
216
168
  #: (Prism::InstanceVariableReadNode node) -> void
217
169
  def on_instance_variable_read_node_enter(node)
218
- handle_instance_variable_completion(node.name.to_s, node.location)
170
+ return if @sorbet_level.strict?
171
+
172
+ complete_variable(range_from_location(node.location), node.name.to_s)
219
173
  end
220
174
 
221
175
  #: (Prism::InstanceVariableWriteNode node) -> void
222
176
  def on_instance_variable_write_node_enter(node)
223
- handle_instance_variable_completion(node.name.to_s, node.name_loc)
177
+ return if @sorbet_level.strict?
178
+
179
+ complete_variable(range_from_location(node.name_loc), node.name.to_s)
224
180
  end
225
181
 
226
182
  #: (Prism::InstanceVariableAndWriteNode node) -> void
227
183
  def on_instance_variable_and_write_node_enter(node)
228
- handle_instance_variable_completion(node.name.to_s, node.name_loc)
184
+ return if @sorbet_level.strict?
185
+
186
+ complete_variable(range_from_location(node.name_loc), node.name.to_s)
229
187
  end
230
188
 
231
189
  #: (Prism::InstanceVariableOperatorWriteNode node) -> void
232
190
  def on_instance_variable_operator_write_node_enter(node)
233
- handle_instance_variable_completion(node.name.to_s, node.name_loc)
191
+ return if @sorbet_level.strict?
192
+
193
+ complete_variable(range_from_location(node.name_loc), node.name.to_s)
234
194
  end
235
195
 
236
196
  #: (Prism::InstanceVariableOrWriteNode node) -> void
237
197
  def on_instance_variable_or_write_node_enter(node)
238
- handle_instance_variable_completion(node.name.to_s, node.name_loc)
198
+ return if @sorbet_level.strict?
199
+
200
+ complete_variable(range_from_location(node.name_loc), node.name.to_s)
239
201
  end
240
202
 
241
203
  #: (Prism::InstanceVariableTargetNode node) -> void
242
204
  def on_instance_variable_target_node_enter(node)
243
- handle_instance_variable_completion(node.name.to_s, node.location)
205
+ return if @sorbet_level.strict?
206
+
207
+ complete_variable(range_from_location(node.location), node.name.to_s)
244
208
  end
245
209
 
246
210
  #: (Prism::ClassVariableAndWriteNode node) -> void
247
211
  def on_class_variable_and_write_node_enter(node)
248
- handle_class_variable_completion(node.name.to_s, node.name_loc)
212
+ return if @sorbet_level.strict?
213
+
214
+ complete_variable(range_from_location(node.name_loc), node.name.to_s)
249
215
  end
250
216
 
251
217
  #: (Prism::ClassVariableOperatorWriteNode node) -> void
252
218
  def on_class_variable_operator_write_node_enter(node)
253
- handle_class_variable_completion(node.name.to_s, node.name_loc)
219
+ return if @sorbet_level.strict?
220
+
221
+ complete_variable(range_from_location(node.name_loc), node.name.to_s)
254
222
  end
255
223
 
256
224
  #: (Prism::ClassVariableOrWriteNode node) -> void
257
225
  def on_class_variable_or_write_node_enter(node)
258
- handle_class_variable_completion(node.name.to_s, node.name_loc)
226
+ return if @sorbet_level.strict?
227
+
228
+ complete_variable(range_from_location(node.name_loc), node.name.to_s)
259
229
  end
260
230
 
261
231
  #: (Prism::ClassVariableTargetNode node) -> void
262
232
  def on_class_variable_target_node_enter(node)
263
- handle_class_variable_completion(node.name.to_s, node.location)
233
+ return if @sorbet_level.strict?
234
+
235
+ complete_variable(range_from_location(node.location), node.name.to_s)
264
236
  end
265
237
 
266
238
  #: (Prism::ClassVariableReadNode node) -> void
267
239
  def on_class_variable_read_node_enter(node)
268
- handle_class_variable_completion(node.name.to_s, node.location)
240
+ return if @sorbet_level.strict?
241
+
242
+ complete_variable(range_from_location(node.location), node.name.to_s)
269
243
  end
270
244
 
271
245
  #: (Prism::ClassVariableWriteNode node) -> void
272
246
  def on_class_variable_write_node_enter(node)
273
- handle_class_variable_completion(node.name.to_s, node.name_loc)
247
+ return if @sorbet_level.strict?
248
+
249
+ complete_variable(range_from_location(node.name_loc), node.name.to_s)
274
250
  end
275
251
 
276
252
  private
277
253
 
278
- #: (String name, Interface::Range range) -> void
279
- def constant_path_completion(name, range)
280
- top_level_reference = if name.start_with?("::")
281
- name = name.delete_prefix("::")
282
- true
283
- else
284
- false
285
- end
254
+ # Returns every candidate reachable from the current scope (constants, methods, ivars, cvars, globals, keywords).
255
+ # Specialized completion methods filter by node kind and prefix.
256
+ #
257
+ #: () -> Array[(Rubydex::Declaration | Rubydex::Keyword)]
258
+ def expression_candidates
259
+ @graph.complete_expression(@node_context.nesting, self_receiver: nil)
260
+ end
286
261
 
287
- # If we're trying to provide completion for an aliased namespace, we need to first discover it's real name in
288
- # order to find which possible constants match the desired search
289
- aliased_namespace = if name.end_with?("::")
290
- name.delete_suffix("::")
291
- else
292
- *namespace, incomplete_name = name.split("::")
293
- namespace.join("::")
294
- end
262
+ #: (Interface::Range range, String prefix) -> void
263
+ def complete_constants(range, prefix)
264
+ expression_candidates.each do |candidate|
265
+ next unless candidate.is_a?(Rubydex::Class) || candidate.is_a?(Rubydex::Module) ||
266
+ candidate.is_a?(Rubydex::Constant) || candidate.is_a?(Rubydex::ConstantAlias)
295
267
 
296
- nesting = @node_context.nesting
297
- namespace_entries = @index.resolve(aliased_namespace, nesting)
298
- return unless namespace_entries
268
+ # Match either the short (unqualified) or fully qualified name, so that lexically-reachable constants like
269
+ # `Foo::CONST` match when the user types `CONST` and fully-qualified typing like `Foo::CONST` still matches
270
+ complete_name = candidate.name
271
+ next unless candidate.unqualified_name.start_with?(prefix) || complete_name.start_with?(prefix)
299
272
 
300
- namespace_name = namespace_entries.first #: as !nil
301
- .name
302
- real_namespace = @index.follow_aliased_namespace(namespace_name)
273
+ @response_builder << build_entry_completion(complete_name, prefix, range, candidate)
274
+ end
275
+ end
303
276
 
304
- candidates = @index.constant_completion_candidates(
305
- "#{real_namespace}::#{incomplete_name}",
306
- top_level_reference ? [] : nesting,
307
- )
308
- candidates.each do |entries|
309
- # The only time we may have a private constant reference from outside of the namespace is if we're dealing
310
- # with ConstantPath and the entry name doesn't start with the current nesting
311
- first_entry = entries.first #: as !nil
312
- next if first_entry.private? && !first_entry.name.start_with?("#{nesting}::")
313
-
314
- entry_name = first_entry.name
315
- full_name = if aliased_namespace != real_namespace
316
- constant_name = entry_name.delete_prefix("#{real_namespace}::")
317
- aliased_namespace.empty? ? constant_name : "#{aliased_namespace}::#{constant_name}"
318
- elsif !entry_name.start_with?(aliased_namespace)
319
- *_, short_name = entry_name.split("::")
320
- "#{aliased_namespace}::#{short_name}"
321
- else
322
- entry_name
323
- end
277
+ #: (Interface::Range range, String prefix) -> void
278
+ def complete_methods_no_receiver(range, prefix)
279
+ @node_context.locals_for_scope.each do |local|
280
+ local_name = local.to_s
281
+ next unless local_name.start_with?(prefix)
324
282
 
325
- @response_builder << build_entry_completion(
326
- full_name,
327
- name,
328
- range,
329
- entries,
330
- top_level_reference || top_level?(first_entry.name),
283
+ @response_builder << Interface::CompletionItem.new(
284
+ label: local_name,
285
+ filter_text: local_name,
286
+ text_edit: Interface::TextEdit.new(range: range, new_text: local_name),
287
+ kind: Constant::CompletionItemKind::VARIABLE,
288
+ data: { skip_resolve: true },
331
289
  )
332
290
  end
291
+
292
+ expression_candidates.each do |candidate|
293
+ case candidate
294
+ when Rubydex::Method
295
+ display_name = candidate.unqualified_name.delete_suffix("()")
296
+ next unless display_name.start_with?(prefix)
297
+
298
+ add_method_completion(candidate, range)
299
+ when Rubydex::Keyword
300
+ next unless candidate.name.start_with?(prefix)
301
+
302
+ @response_builder << Interface::CompletionItem.new(
303
+ label: candidate.name,
304
+ text_edit: Interface::TextEdit.new(range: range, new_text: candidate.name),
305
+ kind: Constant::CompletionItemKind::KEYWORD,
306
+ data: { keyword: true },
307
+ )
308
+ end
309
+ end
333
310
  end
334
311
 
335
- #: (String name, Prism::Location location) -> void
336
- def handle_global_variable_completion(name, location)
337
- candidates = @index.prefix_search(name)
312
+ # Namespace access (e.g.: `Foo::Bar`, `::Bar`). Collects all constants for the namespace that the prefix resolves
313
+ # to, preserving any alias names typed by the user
314
+ #: (String name, Interface::Range range) -> void
315
+ def constant_path_completion(name, range)
316
+ if name.end_with?("::")
317
+ namespace_prefix = name.delete_suffix("::")
318
+ incomplete_name = nil
319
+ else
320
+ *segments, incomplete_name = name.split("::")
321
+ namespace_prefix = segments.join("::")
322
+ end
338
323
 
339
- return if candidates.none?
324
+ candidates = if namespace_prefix.empty?
325
+ @graph.complete_expression([], self_receiver: nil)
326
+ else
327
+ # Rubydex's resolver handles a leading `::` on `namespace_prefix` by resolving from the top-level scope, so
328
+ # we don't need to special-case top-level references here
329
+ resolved = @graph.resolve_constant(namespace_prefix, @node_context.nesting)
330
+ return unless resolved
340
331
 
341
- range = range_from_location(location)
332
+ @graph.complete_namespace_access(resolved.name, self_receiver: nil)
333
+ end
342
334
 
343
- candidates.flatten.uniq(&:name).each do |entry|
344
- entry_name = entry.name
335
+ candidates.each do |candidate|
336
+ next unless candidate.is_a?(Rubydex::Class) || candidate.is_a?(Rubydex::Module) ||
337
+ candidate.is_a?(Rubydex::Constant) || candidate.is_a?(Rubydex::ConstantAlias)
345
338
 
346
- @response_builder << Interface::CompletionItem.new(
347
- label: entry_name,
348
- filter_text: entry_name,
349
- label_details: Interface::CompletionItemLabelDetails.new(
350
- description: entry.file_name,
351
- ),
352
- text_edit: Interface::TextEdit.new(range: range, new_text: entry_name),
353
- kind: Constant::CompletionItemKind::VARIABLE,
354
- )
339
+ short_name = candidate.unqualified_name
340
+ next if incomplete_name && !short_name.start_with?(incomplete_name)
341
+
342
+ full_name = namespace_prefix.empty? ? short_name : "#{namespace_prefix}::#{short_name}"
343
+ @response_builder << build_entry_completion(full_name, name, range, candidate)
355
344
  end
356
345
  end
357
346
 
358
- #: (String name, Prism::Location location) -> void
359
- def handle_class_variable_completion(name, location)
347
+ # Method call on a receiver (e.g.: `foo.`, `@bar.`, `@@baz.`, `Qux.`). Collects all methods that exist on the
348
+ # type returned by the receiver, filtered by the prefix typed
349
+ #: (Prism::CallNode node, String name) -> void
350
+ def complete_method_call(node, name)
351
+ # Sorbet can provide completion for methods invoked on self on typed true or higher files
352
+ return if @sorbet_level.true_or_higher? && self_receiver?(node)
353
+
360
354
  type = @type_inferrer.infer_receiver_type(@node_context)
361
355
  return unless type
362
356
 
363
- range = range_from_location(location)
364
-
365
- @index.class_variable_completion_candidates(name, type.name).each do |entry|
366
- variable_name = entry.name
357
+ # When the trigger character is a dot, Prism matches the name of the call node to whatever is next in the
358
+ # source code, leading to us searching for the wrong name. What we want to do instead is show every available
359
+ # method when dot is pressed
360
+ method_name = @trigger_character == "." ? nil : name
367
361
 
368
- label_details = Interface::CompletionItemLabelDetails.new(
369
- description: entry.file_name,
362
+ range = if method_name
363
+ range_from_location(
364
+ node.message_loc, #: as !nil
370
365
  )
366
+ else
367
+ loc = node.call_operator_loc
371
368
 
372
- @response_builder << Interface::CompletionItem.new(
373
- label: variable_name,
374
- label_details: label_details,
375
- text_edit: Interface::TextEdit.new(
376
- range: range,
377
- new_text: variable_name,
378
- ),
379
- kind: Constant::CompletionItemKind::FIELD,
380
- data: {
381
- owner_name: entry.owner&.name,
382
- },
383
- )
369
+ if loc
370
+ Interface::Range.new(
371
+ start: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
372
+ end: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
373
+ )
374
+ end
384
375
  end
385
- rescue RubyIndexer::Index::NonExistingNamespaceError
386
- # If by any chance we haven't indexed the owner, then there's no way to find the right declaration
387
- end
388
376
 
389
- #: (String name, Prism::Location location) -> void
390
- def handle_instance_variable_completion(name, location)
391
- # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
392
- # to provide all features for them
393
- return if @sorbet_level.strict?
377
+ return unless range
394
378
 
379
+ guessed_type = type.is_a?(TypeInferrer::GuessedType) && type.name
380
+
381
+ @graph.complete_method_call(type.name, self_receiver: nil).each do |candidate|
382
+ if method_name
383
+ display_name = candidate.unqualified_name.delete_suffix("()")
384
+ next unless display_name.start_with?(method_name)
385
+ end
386
+
387
+ add_method_completion(candidate, range, has_receiver: true, guessed_type: guessed_type)
388
+ end
389
+ end
390
+
391
+ # Variable completion (instance, class, and global). The variable kind is selected by the prefix the user typed:
392
+ # `$…` only matches globals, `@@…` only class variables, and `@…` matches both instance and class variables (since
393
+ # `@@foo`.start_with?("@") is true). Globals live at top-level, so they need an empty nesting; instance/class
394
+ # variables resolve through the type_inferrer to handle singleton methods and class bodies, where the receiver is
395
+ # the singleton class rather than the lexical nesting.
396
+ #
397
+ #: (Interface::Range, String) -> void
398
+ def complete_variable(range, prefix)
395
399
  type = @type_inferrer.infer_receiver_type(@node_context)
396
- return unless type
400
+ nesting = type ? type.name.split("::") : []
397
401
 
398
- range = range_from_location(location)
399
- @index.instance_variable_completion_candidates(name, type.name).each do |entry|
400
- variable_name = entry.name
402
+ @graph.complete_expression(nesting, self_receiver: nil).each do |candidate|
403
+ next unless candidate.is_a?(Rubydex::Declaration)
401
404
 
402
- label_details = Interface::CompletionItemLabelDetails.new(
403
- description: entry.file_name,
404
- )
405
+ variable_name = candidate.unqualified_name
406
+ next unless variable_name.start_with?(prefix)
405
407
 
406
408
  @response_builder << Interface::CompletionItem.new(
407
409
  label: variable_name,
408
- label_details: label_details,
409
- text_edit: Interface::TextEdit.new(
410
- range: range,
411
- new_text: variable_name,
410
+ label_details: Interface::CompletionItemLabelDetails.new(
411
+ description: declaration_file_names(candidate),
412
412
  ),
413
- kind: Constant::CompletionItemKind::FIELD,
414
- data: {
415
- owner_name: entry.owner&.name,
416
- },
413
+ text_edit: Interface::TextEdit.new(range: range, new_text: variable_name),
414
+ kind: candidate.to_lsp_completion_kind,
415
+ data: { owner_name: candidate.owner.name },
417
416
  )
418
417
  end
419
- rescue RubyIndexer::Index::NonExistingNamespaceError
420
- # If by any chance we haven't indexed the owner, then there's no way to find the right declaration
421
418
  end
422
419
 
423
420
  #: (Prism::CallNode node) -> void
@@ -429,13 +426,10 @@ module RubyLsp
429
426
 
430
427
  return unless path_node_to_complete.is_a?(Prism::StringNode)
431
428
 
432
- matched_uris = @index.search_require_paths(path_node_to_complete.content)
429
+ content = path_node_to_complete.content
433
430
 
434
- matched_uris.map!(&:require_path).sort!.each do |path|
435
- @response_builder << build_completion(
436
- path, #: as !nil
437
- path_node_to_complete,
438
- )
431
+ @graph.require_paths($LOAD_PATH).select { |path| path.start_with?(content) }.sort!.each do |path|
432
+ @response_builder << build_completion(path, path_node_to_complete)
439
433
  end
440
434
  end
441
435
 
@@ -474,120 +468,26 @@ module RubyLsp
474
468
  # might fail with EPERM
475
469
  end
476
470
 
477
- #: (Prism::CallNode node, String name) -> void
478
- def complete_methods(node, name)
479
- # If the node has a receiver, then we don't need to provide local nor keyword completions. Sorbet can provide
480
- # local and keyword completion for any file with a Sorbet level of true or higher
481
- if !@sorbet_level.true_or_higher? && !node.receiver
482
- add_local_completions(node, name)
483
- add_keyword_completions(node, name)
484
- end
485
-
486
- # Sorbet can provide completion for methods invoked on self on typed true or higher files
487
- return if @sorbet_level.true_or_higher? && self_receiver?(node)
488
-
489
- type = @type_inferrer.infer_receiver_type(@node_context)
490
- return unless type
491
-
492
- # When the trigger character is a dot, Prism matches the name of the call node to whatever is next in the source
493
- # code, leading to us searching for the wrong name. What we want to do instead is show every available method
494
- # when dot is pressed
495
- method_name = @trigger_character == "." ? nil : name
496
-
497
- range = if method_name
498
- range_from_location(
499
- node.message_loc, #: as !nil
500
- )
501
- else
502
- loc = node.call_operator_loc
503
-
504
- if loc
505
- Interface::Range.new(
506
- start: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
507
- end: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
508
- )
509
- end
510
- end
511
-
512
- return unless range
513
-
514
- guessed_type = type.is_a?(TypeInferrer::GuessedType) && type.name
515
- external_references = @node_context.fully_qualified_name != type.name
516
-
517
- @index.method_completion_candidates(method_name, type.name).each do |entry|
518
- next if entry.visibility != :public && external_references
519
-
520
- entry_name = entry.name
521
- owner_name = entry.owner&.name
522
- new_text = entry_name
523
-
524
- if entry_name.end_with?("=")
525
- method_name = entry_name.delete_suffix("=")
526
-
527
- # For writer methods, format as assignment and prefix "self." when no receiver is specified
528
- new_text = node.receiver.nil? ? "self.#{method_name} = " : "#{method_name} = "
529
- end
530
-
531
- label_details = Interface::CompletionItemLabelDetails.new(
532
- description: entry.file_name,
533
- detail: entry.decorated_parameters,
534
- )
535
- @response_builder << Interface::CompletionItem.new(
536
- label: entry_name,
537
- filter_text: entry_name,
538
- label_details: label_details,
539
- text_edit: Interface::TextEdit.new(range: range, new_text: new_text),
540
- kind: Constant::CompletionItemKind::METHOD,
541
- data: {
542
- owner_name: owner_name,
543
- guessed_type: guessed_type,
544
- },
545
- )
546
- end
547
- rescue RubyIndexer::Index::NonExistingNamespaceError
548
- # We have not indexed this namespace, so we can't provide any completions
549
- end
550
-
551
- #: (Prism::CallNode node, String name) -> void
552
- def add_local_completions(node, name)
553
- range = range_from_location(
554
- node.message_loc, #: as !nil
555
- )
471
+ #: (Rubydex::Method candidate, Interface::Range range, ?has_receiver: bool, ?guessed_type: (String | bool)) -> void
472
+ def add_method_completion(candidate, range, has_receiver: false, guessed_type: false)
473
+ display_name = candidate.unqualified_name.delete_suffix("()")
474
+ new_text = display_name
556
475
 
557
- @node_context.locals_for_scope.each do |local|
558
- local_name = local.to_s
559
- next unless local_name.start_with?(name)
476
+ if display_name.end_with?("=")
477
+ setter_name = display_name.delete_suffix("=")
560
478
 
561
- @response_builder << Interface::CompletionItem.new(
562
- label: local_name,
563
- filter_text: local_name,
564
- text_edit: Interface::TextEdit.new(range: range, new_text: local_name),
565
- kind: Constant::CompletionItemKind::VARIABLE,
566
- data: {
567
- skip_resolve: true,
568
- },
569
- )
479
+ # For writer methods, format as assignment and prefix "self." when no receiver is specified
480
+ new_text = has_receiver ? "#{setter_name} = " : "self.#{setter_name} = "
570
481
  end
571
- end
572
482
 
573
- #: (Prism::CallNode node, String name) -> void
574
- def add_keyword_completions(node, name)
575
- range = range_from_location(
576
- node.message_loc, #: as !nil
483
+ @response_builder << Interface::CompletionItem.new(
484
+ label: display_name,
485
+ filter_text: display_name,
486
+ label_details: Interface::CompletionItemLabelDetails.new(description: declaration_file_names(candidate)),
487
+ text_edit: Interface::TextEdit.new(range: range, new_text: new_text),
488
+ kind: candidate.to_lsp_completion_kind,
489
+ data: { owner_name: candidate.owner.name, guessed_type: guessed_type },
577
490
  )
578
-
579
- KEYWORDS.each do |keyword|
580
- next unless keyword.start_with?(name)
581
-
582
- @response_builder << Interface::CompletionItem.new(
583
- label: keyword,
584
- text_edit: Interface::TextEdit.new(range: range, new_text: keyword),
585
- kind: Constant::CompletionItemKind::KEYWORD,
586
- data: {
587
- keyword: true,
588
- },
589
- )
590
- end
591
491
  end
592
492
 
593
493
  #: (String label, Prism::StringNode node) -> Interface::CompletionItem
@@ -605,68 +505,30 @@ module RubyLsp
605
505
  )
606
506
  end
607
507
 
608
- #: (String real_name, String incomplete_name, Interface::Range range, Array[RubyIndexer::Entry] entries, bool top_level) -> Interface::CompletionItem
609
- def build_entry_completion(real_name, incomplete_name, range, entries, top_level)
610
- first_entry = entries.first #: as !nil
611
- kind = case first_entry
612
- when RubyIndexer::Entry::Class
613
- Constant::CompletionItemKind::CLASS
614
- when RubyIndexer::Entry::Module
615
- Constant::CompletionItemKind::MODULE
616
- when RubyIndexer::Entry::Constant
617
- Constant::CompletionItemKind::CONSTANT
618
- else
619
- Constant::CompletionItemKind::REFERENCE
620
- end
621
-
508
+ #: (String real_name, String incomplete_name, Interface::Range range, Rubydex::Declaration declaration) -> Interface::CompletionItem
509
+ def build_entry_completion(real_name, incomplete_name, range, declaration)
622
510
  insertion_text = real_name.dup
623
511
  filter_text = real_name.dup
624
512
 
625
- # If we have two entries with the same name inside the current namespace and the user selects the top level
626
- # option, we have to ensure it's prefixed with `::` or else we're completing the wrong constant. For example:
627
- # If we have the index with ["Foo::Bar", "Bar"], and we're providing suggestions for `B` inside a `Foo` module,
628
- # then selecting the `Foo::Bar` option needs to complete to `Bar` and selecting the top level `Bar` option needs
629
- # to complete to `::Bar`.
630
- if top_level
631
- insertion_text.prepend("::")
632
- filter_text.prepend("::")
633
- end
634
-
635
- # If the user is searching for a constant inside the current namespace, then we prefer completing the short name
636
- # of that constant. E.g.:
637
- #
638
- # module Foo
639
- # class Bar
640
- # end
641
- #
642
- # Foo::B # --> completion inserts `Bar` instead of `Foo::Bar`
643
- # end
644
- nesting = @node_context.nesting
645
- unless @node_context.fully_qualified_name.start_with?(incomplete_name)
646
- nesting.each do |namespace|
647
- prefix = "#{namespace}::"
648
- shortened_name = insertion_text.delete_prefix(prefix)
649
-
650
- # If a different entry exists for the shortened name, then there's a conflict and we should not shorten it
651
- conflict_name = "#{@node_context.fully_qualified_name}::#{shortened_name}"
652
- break if real_name != conflict_name && @index[conflict_name]
653
-
654
- insertion_text = shortened_name
655
-
656
- # If the user is typing a fully qualified name `Foo::Bar::Baz`, then we should not use the short name (e.g.:
657
- # `Baz`) as filtering. So we only shorten the filter text if the user is not including the namespaces in
658
- # their typing
659
- filter_text.delete_prefix!(prefix) unless incomplete_name.start_with?(prefix)
513
+ # When the user explicitly typed `::Foo`, the absolute prefix must be preserved and the suffix-shortening
514
+ # below is skipped replacing `::Bar` with `Bar` would change which constant resolves. The leading `::` is
515
+ # only present on `incomplete_name` (the user-typed text)
516
+ if incomplete_name.start_with?("::")
517
+ insertion_text.prepend("::") unless insertion_text.start_with?("::")
518
+ filter_text.prepend("::") unless filter_text.start_with?("::")
519
+ else
520
+ shortest = shortest_constant_suffix(real_name)
521
+
522
+ if shortest.length < insertion_text.length
523
+ stripped_prefix = real_name.delete_suffix(shortest)
524
+ insertion_text = shortest
525
+ # When the user is typing a more qualified path (e.g. `Foo::B`), keep the filter text qualified so the
526
+ # editor's filter still matches what they typed; otherwise the unqualified suffix is enough
527
+ filter_text = shortest unless incomplete_name.start_with?(stripped_prefix)
660
528
  end
661
529
  end
662
530
 
663
- # When using a top level constant reference (e.g.: `::Bar`), the editor includes the `::` as part of the filter.
664
- # For these top level references, we need to include the `::` as part of the filter text or else it won't match
665
- # the right entries in the index
666
-
667
- label_details = Interface::CompletionItemLabelDetails.new(
668
- description: entries.map(&:file_name).join(","),
669
- )
531
+ label_details = Interface::CompletionItemLabelDetails.new(description: declaration_file_names(declaration))
670
532
 
671
533
  Interface::CompletionItem.new(
672
534
  label: real_name,
@@ -676,37 +538,39 @@ module RubyLsp
676
538
  range: range,
677
539
  new_text: insertion_text,
678
540
  ),
679
- kind: kind,
541
+ kind: declaration.to_lsp_completion_kind,
542
+ data: { fully_qualified_name: declaration.name },
680
543
  )
681
544
  end
682
545
 
683
- # Check if there are any conflicting names for `entry_name`, which would require us to use a top level reference.
684
- # For example:
546
+ # Returns the shortest possible name for a constant reference that still resolves to the same target
685
547
  #
686
- # ```ruby
687
- # class Bar; end
688
- #
689
- # module Foo
690
- # class Bar; end
691
- #
692
- # # in this case, the completion for `Bar` conflicts with `Foo::Bar`, so we can't suggest `Bar` as the
693
- # # completion, but instead need to suggest `::Bar`
694
- # B
695
- # end
696
- # ```
697
- #: (String entry_name) -> bool
698
- def top_level?(entry_name)
548
+ #: (String) -> String
549
+ def shortest_constant_suffix(real_name)
550
+ segments = real_name.split("::")
699
551
  nesting = @node_context.nesting
700
- nesting.length.downto(0) do |i|
701
- prefix = nesting[0...i] #: as !nil
702
- .join("::")
703
- full_name = prefix.empty? ? entry_name : "#{prefix}::#{entry_name}"
704
- next if full_name == entry_name
705
552
 
706
- return true if @index[full_name]
553
+ (1..segments.length).each do |suffix_len|
554
+ suffix = segments.last(suffix_len).join("::")
555
+ resolved = @graph.resolve_constant(suffix, nesting)
556
+ return suffix if resolved && resolved.name == real_name
707
557
  end
708
558
 
709
- false
559
+ real_name
560
+ end
561
+
562
+ #: (Rubydex::Declaration declaration) -> String
563
+ def declaration_file_names(declaration)
564
+ declaration.definitions.filter_map do |defn|
565
+ uri = URI(defn.location.uri)
566
+ case uri.scheme
567
+ when "untitled"
568
+ uri.opaque
569
+ when "file"
570
+ path = uri.full_path
571
+ File.basename(path) if path
572
+ end
573
+ end.uniq.join(",")
710
574
  end
711
575
  end
712
576
  end