Skip to content

Commit 849669f

Browse files
committed
Sort completion candidates
Method candidates were returned in method table order, which looks random in the completion dialog. Sort all candidates in one place at the end of completion_candidates and drop the per-branch sorts. Candidates starting with an underscore go last, so that completing an empty method name does not begin with __id__, __send__ and other internals.
1 parent cf89f1f commit 849669f

2 files changed

Lines changed: 38 additions & 8 deletions

File tree

lib/repl_type_completor/result.rb

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,13 @@ def completion_candidates
5252
scope_constants = type.types.flat_map do |t|
5353
scope.table_module_constants(t.module_or_class) if t.is_a?(Types::SingletonType)
5454
end
55-
(scope_constants.compact | type.constants.map(&:to_s)).sort
55+
scope_constants.compact | type.constants.map(&:to_s)
5656
else
57-
scope.constants.sort | RESERVED_WORDS
57+
scope.constants | RESERVED_WORDS
5858
end
5959
in [:ivar, name, scope]
60-
ivars = scope.instance_variables.sort
61-
name == '@' ? ivars + scope.class_variables.sort : ivars
60+
ivars = scope.instance_variables
61+
name == '@' ? ivars + scope.class_variables : ivars
6262
in [:cvar, name, scope]
6363
scope.class_variables
6464
in [:gvar, name, scope]
@@ -70,13 +70,13 @@ def completion_candidates
7070
keys = receiver_type.types.grep(Types::InstanceType).select do |t|
7171
Hash == t.klass
7272
end.flat_map do |t|
73-
t.instances.flat_map(&:keys).grep(key_type).uniq.sort
73+
t.instances.flat_map(&:keys).grep(key_type).uniq
7474
end
7575
if key_type == Symbol
7676
keys = Symbol.all_symbols if keys.empty? && name.size >= 1
7777
filter_symbol_candidates(keys, name, limit: 100)
7878
else
79-
keys.select { _1.start_with?(name) }.sort
79+
keys
8080
end
8181
in [:call, name, type, self_call]
8282
(self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS
@@ -87,9 +87,12 @@ def completion_candidates
8787
end
8888

8989
candidates.filter_map do
90-
_1[name.size..] if _1.start_with?(name)
90+
_1 if _1.start_with?(name)
9191
rescue EncodingError
92-
end
92+
end.sort_by do
93+
# Alphabetical order, but internal methods (leading underscore) last
94+
[_1.start_with?('_') ? 1 : 0, _1]
95+
end.map { _1[name.size..] }
9396
rescue Exception => e
9497
ReplTypeCompletor.handle_exception(e)
9598
[]

test/repl_type_completor/test_repl_type_completor.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,33 @@ def with_failing_method(klass, method_name, message)
293293
klass.define_method(method_name, original_method)
294294
end
295295

296+
def test_completion_candidates_are_sorted
297+
xzyb, xzya, xzyc = 'b', 'a', 'c'
298+
lvar_binding = binding
299+
300+
{
301+
'1.' => empty_binding, # [:call] with empty method name
302+
"''.s" => empty_binding, # [:call] with method name prefix
303+
'Math::' => empty_binding, # [:call_or_const]
304+
'$std' => empty_binding, # [:gvar]
305+
'xzy' => lvar_binding # [:lvar_or_method]
306+
}.each do |code, bind|
307+
candidates = ReplTypeCompletor.analyze(code, binding: bind).completion_candidates
308+
refute_empty candidates, "Expected completion candidates of #{code.inspect} not to be empty"
309+
underscore, regular = candidates.partition { _1.start_with?('_') }
310+
assert_equal regular.sort + underscore.sort, candidates, "Expected completion candidates of #{code.inspect} to be sorted with underscore methods last"
311+
end
312+
313+
# Internal methods (leading underscore, e.g. __id__ and __send__) are listed last
314+
candidates = ReplTypeCompletor.analyze('1.', binding: empty_binding).completion_candidates
315+
assert candidates.any? { _1.start_with?('_') }
316+
refute candidates.first.start_with?('_')
317+
assert candidates.last.start_with?('_')
318+
319+
# Local variables xzyb, xzya, xzyc are defined in non-alphabetical order
320+
assert_equal [xzya, xzyb, xzyc], ReplTypeCompletor.analyze('xzy', binding: lvar_binding).completion_candidates
321+
end
322+
296323
def test_analyze_error
297324
with_failing_method(ReplTypeCompletor.singleton_class, :analyze_code, 'error_in_analyze_code') do
298325
assert_nil ReplTypeCompletor.analyze('1.', binding: binding)

0 commit comments

Comments
 (0)