From f02035ab42bc5c4110f03684662d579590cfd611 Mon Sep 17 00:00:00 2001 From: Andrey Samsonov Date: Wed, 15 Jul 2026 18:46:17 +0200 Subject: [PATCH] Sort remaining completion candidates in RegexpCompletor ruby/irb#379 sorted local variables and string method candidates, but receiver.method completion (the most common case), symbols, global variables and absolute constants were still returned in method table order, which looks random in the completion dialog. Sort them too. Candidates starting with an underscore go last, so that completing `u.` does not begin with __id__, __send__ and other internals. --- lib/irb/completion.rb | 16 ++++++++++------ test/irb/test_completion.rb | 28 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 3dc2fa22a..3fbbf988a 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -302,7 +302,7 @@ def retrieve_completion_data(input, bind:, doc_namespace:) rescue EncodingError # ignore end - candidates.grep(/^#{Regexp.quote(sym)}/) + candidates.grep(/^#{Regexp.quote(sym)}/).sort end when /^::([A-Z][^:\.\(\)]*)$/ # Absolute Constant or class methods @@ -313,7 +313,7 @@ def retrieve_completion_data(input, bind:, doc_namespace:) if doc_namespace candidates.find { |i| i == receiver } else - candidates.grep(/^#{Regexp.quote(receiver)}/).collect{|e| "::" + e} + candidates.grep(/^#{Regexp.quote(receiver)}/).sort.collect{|e| "::" + e} end when /^([A-Z].*)::([^:.]*)$/ @@ -331,7 +331,7 @@ def retrieve_completion_data(input, bind:, doc_namespace:) candidates = [] end - select_message(receiver, message, candidates.sort, "::") + select_message(receiver, message, candidates, "::") end when /^(:[^:.]+)(\.|::)([^.]*)$/ @@ -400,7 +400,7 @@ def retrieve_completion_data(input, bind:, doc_namespace:) if doc_namespace all_gvars.find{ |i| i == gvar } else - all_gvars.grep(Regexp.new(Regexp.quote(gvar))) + all_gvars.grep(Regexp.new(Regexp.quote(gvar))).sort end when /^([^.:"].*)(\.|::)([^.]*)$/ @@ -452,7 +452,7 @@ def retrieve_completion_data(input, bind:, doc_namespace:) if doc_namespace "String.#{candidates.find{ |i| i == message }}" else - select_message(receiver, message, candidates.sort) + select_message(receiver, message, candidates) end when /^\s*$/ # empty input @@ -491,7 +491,11 @@ def retrieve_completion_data(input, bind:, doc_namespace:) Operators = %w[% & * ** + - / < << <= <=> == === =~ > >= >> [] []= ^ ! != !~] def select_message(receiver, message, candidates, sep = ".") - candidates.grep(/^#{Regexp.quote(message)}/).collect do |e| + sorted_candidates = candidates.grep(/^#{Regexp.quote(message)}/).sort_by do |e| + # Alphabetical order, but internal methods (leading underscore) last + [e.start_with?('_') ? 1 : 0, e] + end + sorted_candidates.collect do |e| case e when /^[a-zA-Z_]/ receiver + sep + e diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb index 8959604cb..a08767f81 100644 --- a/test/irb/test_completion.rb +++ b/test/irb/test_completion.rb @@ -95,6 +95,17 @@ def test_complete_class assert_include(completion_candidates("String.ne", binding), "String.new") assert_equal("String.new", doc_namespace("String.new", binding)) end + + def test_complete_methods_are_sorted + u = '' + u.clear + + candidates = completion_candidates("u.", binding) + assert_include(candidates, "u.__id__") + underscore, regular = candidates.partition { |c| c.start_with?("u._") } + # Alphabetical order, internal methods (leading underscore) last + assert_equal(regular.sort + underscore.sort, candidates) + end end class RequireComepletionTest < CompletionTest @@ -215,6 +226,11 @@ def test_complete_sort_variables candidates = completion_candidates("xz", binding) assert_equal(%w[xzy xzy2 xzy_1], candidates) end + + def test_complete_sort_global_variables + candidates = completion_candidates("$std", binding) + assert_equal(%w[$stderr $stdin $stdout], candidates) + end end class ConstantCompletionTest < CompletionTest @@ -255,6 +271,18 @@ def test_complete_symbol assert_empty(completion_candidates(":", binding)) end + def test_complete_symbols_are_sorted + candidates = completion_candidates(":a", binding) + refute_empty(candidates) + assert_equal(candidates.sort, candidates) + end + + def test_complete_absolute_constants_are_sorted + candidates = completion_candidates("::S", binding) + refute_empty(candidates) + assert_equal(candidates.sort, candidates) + end + def test_complete_invalid_three_colons assert_empty(completion_candidates(":::A", binding)) assert_empty(completion_candidates(":::", binding))