diff --git a/lib/repl_type_completor/result.rb b/lib/repl_type_completor/result.rb index edfaa64..562e2d5 100644 --- a/lib/repl_type_completor/result.rb +++ b/lib/repl_type_completor/result.rb @@ -46,7 +46,7 @@ def completion_candidates in [:require_relative, name] RequirePaths.require_relative_completions(name, @source_file) in [:call_or_const, name, type, self_call] - ((self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS) | type.constants + ((self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS).sort | type.constants.sort in [:const, name, type, scope] if type scope_constants = type.types.flat_map do |t| @@ -60,9 +60,9 @@ def completion_candidates ivars = scope.instance_variables.sort name == '@' ? ivars + scope.class_variables.sort : ivars in [:cvar, name, scope] - scope.class_variables + scope.class_variables.sort in [:gvar, name, scope] - scope.global_variables + scope.global_variables.sort in [:symbol, name] filter_symbol_candidates(Symbol.all_symbols, name, limit: 100) in [:aref, key_type, name, receiver_type] @@ -70,18 +70,22 @@ def completion_candidates keys = receiver_type.types.grep(Types::InstanceType).select do |t| Hash == t.klass end.flat_map do |t| - t.instances.flat_map(&:keys).grep(key_type).uniq.sort + t.instances.flat_map(&:keys).grep(key_type).uniq end if key_type == Symbol keys = Symbol.all_symbols if keys.empty? && name.size >= 1 filter_symbol_candidates(keys, name, limit: 100) else - keys.select { _1.start_with?(name) }.sort + keys.sort end in [:call, name, type, self_call] - (self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS + methods = (self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS + # Alphabetical order, but internal methods (leading underscore) last. + # `_1[0] == '_'` because `start_with?('_')` raises EncodingError if the + # method name's encoding is incompatible with US-ASCII. + methods.sort_by { [_1[0] == '_' ? 1 : 0, _1] } in [:lvar_or_method, name, scope] - scope.self_type.all_methods.map(&:to_s) | scope.local_variables | RESERVED_WORDS + scope.local_variables.sort | scope.self_type.all_methods.map(&:to_s).sort | RESERVED_WORDS else [] end diff --git a/test/repl_type_completor/test_repl_type_completor.rb b/test/repl_type_completor/test_repl_type_completor.rb index 23d76fd..e689820 100644 --- a/test/repl_type_completor/test_repl_type_completor.rb +++ b/test/repl_type_completor/test_repl_type_completor.rb @@ -293,6 +293,43 @@ def with_failing_method(klass, method_name, message) klass.define_method(method_name, original_method) end + def test_call_candidates_are_sorted_with_underscore_methods_last + type = Struct.new(:methods).new(%i[_zeta beta _alpha alpha]) + result = ReplTypeCompletor::Result.new([:call, '', type, false], binding, __FILE__) + + assert_equal %w[alpha beta _alpha _zeta], result.completion_candidates + end + + def test_call_or_const_candidates_are_sorted_within_groups + type = Struct.new(:methods, :constants).new(%i[z_method a_method], %i[ZConst AConst]) + result = ReplTypeCompletor::Result.new([:call_or_const, '', type, false], binding, __FILE__) + + assert_equal %w[a_method z_method AConst ZConst], result.completion_candidates + end + + def test_const_candidates_are_sorted_before_reserved_words + scope = Struct.new(:constants).new(%w[ZConst AConst]) + result = ReplTypeCompletor::Result.new([:const, '', nil, scope], binding, __FILE__) + + assert_equal %w[AConst ZConst] + ReplTypeCompletor::Result::RESERVED_WORDS, result.completion_candidates + end + + def test_ivar_candidates_are_sorted_within_groups + scope = Struct.new(:instance_variables, :class_variables).new(%w[@z @a], %w[@@z @@a]) + result = ReplTypeCompletor::Result.new([:ivar, '@', scope], binding, __FILE__) + + assert_equal %w[a z @a @z], result.completion_candidates + end + + def test_lvar_or_method_candidates_are_sorted_within_groups + type = Struct.new(:all_methods).new(%i[z_method a_method]) + scope = Struct.new(:self_type, :local_variables).new(type, %w[z_local a_local]) + result = ReplTypeCompletor::Result.new([:lvar_or_method, '', scope], binding, __FILE__) + + expected = %w[a_local z_local a_method z_method] + ReplTypeCompletor::Result::RESERVED_WORDS + assert_equal expected, result.completion_candidates + end + def test_analyze_error with_failing_method(ReplTypeCompletor.singleton_class, :analyze_code, 'error_in_analyze_code') do assert_nil ReplTypeCompletor.analyze('1.', binding: binding)