Skip to content

Sort completion candidates#78

Merged
tompng merged 3 commits into
ruby:mainfrom
kryzhovnik:sort-completion-candidates
Jul 17, 2026
Merged

Sort completion candidates#78
tompng merged 3 commits into
ruby:mainfrom
kryzhovnik:sort-completion-candidates

Conversation

@kryzhovnik

Copy link
Copy Markdown
Contributor

Method completion candidates are returned in method table order, which is not even definition order and looks random in the completion dialog. With an ActiveRecord model, u.email_address is listed below all its generated variants:

u.email_address_before_last_save
u.email_address_change_to_be_saved
u.email_address=
u.email_address_was
...
u.email_address

Constants, ivars and symbols are already sorted here, and the same problem was fixed for the readline menu in ruby/reline#96 and for part of RegexpCompletor in ruby/irb#379 (ruby/irb#349). This applies the same policy to all candidate kinds: sort once at the end of Result#completion_candidates and drop the per-branch sorts.

One exception: candidates starting with _ go last, otherwise completing u. would begin with __id__, __send__, _run_commit_callbacks and similar internals. The check uses the full method name, not the part after the typed prefix, so u.email still lists email_address before emails.

Side effect of sorting everything alphabetically: @ completion no longer groups ivars before class variables, and Foo:: now mixes constants and methods alphabetically. Can move the sort into the individual branches instead if that grouping is worth keeping.

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.
Comment thread lib/repl_type_completor/result.rb Outdated
end
end.sort_by do
# Alphabetical order, but internal methods (leading underscore) last
[_1.start_with?('_') ? 1 : 0, _1]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think some candidates are worth sorted in groups, so sorting at the end is not always the best.
Sorting by leading-underscore-last looks good. However this is only needed in method call with dot(in [:call, name, type, self_call]).

Examples of grouped sort:

Constants first, reserved words last(BEGIN or END, rarely used)

scope.constants.sort | RESERVED_WORDS

Instance variables first, class variables last when a single @ is typed

# Current
name == '@' ? ivars + scope.class_variables : ivars
# Sorted
name == '@' ? ivars.sort + scope.class_variables.sort : ivars

Local variables first, methods next, reserved words last

# Current
scope.self_type.all_methods.map(&:to_s) | scope.local_variables | RESERVED_WORDS
# Sorted
scope.local_variables.sort | scope.self_type.all_methods.map(&:to_s).sort | RESERVED_WORDS

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in 93ca6a1 based on your feedback.
Sorting now preserves candidate groups, and underscore-last is limited to dotted method calls

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you.
Instead of sort with condition if @analyze_result.first == :call, can you move the sort to in [:call, name, type, self_call] part?

in [:call, name, type, self_call]
  methods = (self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS
  methods.sort_by { ... } # sort here

If the sort is moved, candidates.filter_map part will be simple, and doesn't need change in this PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. One note: the sort now runs before the rescue EncodingError part, where start_with?('_') can raise, so I used 1[0] == '' instead

@tompng tompng left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Thank you 👍

@tompng
tompng merged commit 4bfb5c6 into ruby:main Jul 17, 2026
20 of 21 checks passed
@kryzhovnik

Copy link
Copy Markdown
Contributor Author

Thank you for your patience

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants