Skip to content

Commit

Permalink
ranked match: prefer input order over alphabetical order for user-spe…
Browse files Browse the repository at this point in the history
…cified completions

When using either of

	set-option g completers option=my_option
	prompt -shell-script-candidates ...

While the search text is empty, the completions will be sorted
alphabetically.
This is bad because it means the most important entries are not listed
first, making them harder to select or even spot.

Let's apply input order before resorting to sorting alphabetically.

In theory there is a more elegant solution: sort candidates (except
if they're user input) before passing them to RankedMatch, and then
always use stable sort. However that doesn't work because we use a
heap which doesn't support stable sort.

Closes mawww#1709, mawww#4813
  • Loading branch information
krobelus committed Dec 2, 2023
1 parent d6215dc commit d6b5774
Show file tree
Hide file tree
Showing 4 changed files with 12 additions and 2 deletions.
5 changes: 4 additions & 1 deletion src/commands.cc
Original file line number Diff line number Diff line change
Expand Up @@ -337,10 +337,13 @@ struct ShellCandidatesCompleter
{
UsedLetters query_letters = used_letters(query);
Vector<RankedMatch> matches;
for (auto&& candidate : m_candidates)
for (auto&& [i, candidate] : m_candidates | enumerate())
{
if (RankedMatch m{candidate.first, candidate.second, query, query_letters})
{
m.set_input_sequence_number(i);
matches.push_back(m);
}
}

constexpr size_t max_count = 100;
Expand Down
3 changes: 2 additions & 1 deletion src/insert_completer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -298,10 +298,11 @@ InsertCompletion complete_option(const SelectionList& sels,
StringView query = buffer.substr(coord, cursor_pos);
Vector<RankedMatchAndInfo> matches;

for (auto& candidate : opt.list)
for (auto&& [i, candidate] : opt.list | enumerate())
{
if (RankedMatchAndInfo match{std::get<0>(candidate), query})
{
match.set_input_sequence_number(i);
match.on_select = std::get<1>(candidate);
auto& menu = std::get<2>(candidate);
match.menu_entry = not menu.empty() ?
Expand Down
3 changes: 3 additions & 0 deletions src/ranked_match.cc
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,9 @@ bool RankedMatch::operator<(const RankedMatch& other) const
if (m_max_index != other.m_max_index)
return m_max_index < other.m_max_index;

if (m_input_sequence_number != other.m_input_sequence_number)
return m_input_sequence_number < other.m_input_sequence_number;

// Reorder codepoints to improve matching behaviour
auto order = [](Codepoint cp) { return cp == '/' ? 0 : cp; };

Expand Down
3 changes: 3 additions & 0 deletions src/ranked_match.hh
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ struct RankedMatch

explicit operator bool() const { return m_matches; }

void set_input_sequence_number(size_t i) { m_input_sequence_number = i; }

private:
template<typename TestFunc>
RankedMatch(StringView candidate, StringView query, TestFunc test);
Expand All @@ -54,6 +56,7 @@ private:
Flags m_flags = Flags::None;
int m_word_boundary_match_count = 0;
int m_max_index = 0;
size_t m_input_sequence_number = 0;
};

}
Expand Down

0 comments on commit d6b5774

Please sign in to comment.