ranked match: prefer input order over alphabetical order for user-specified 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 #1709, #4813
This commit is contained in:
Johannes Altmanninger 2023-11-18 09:12:05 +01:00
parent d6215dc25d
commit 658c3385a9
8 changed files with 45 additions and 10 deletions

View File

@ -25,7 +25,7 @@ define-command -params ..1 \
cut -f 1 "$tags" | grep -v '^!' | uniq > "$namecache"
fi
cat "$namecache"
done} \
done | sort } \
-docstring %{
ctags-search [<symbol>]: jump to a symbol's definition
If no symbol is passed then the current selection is used as symbol name

View File

@ -189,7 +189,7 @@ complete-command doc shell-script-candidates %{
/^\[\[[^\]]+\]\]/ { sub(/^\[\[/, ""); sub(/\]\].*/, ""); print }
' < $page | tr '[A-Z ]' '[a-z-]'
fi;;
esac
esac | sort
}
alias global help doc

View File

@ -62,27 +62,49 @@ define-command -params 1.. \
Available commands:
add
apply (alias for "patch git apply")
rm
reset
blame
commit
checkout
commit
diff
edit
grep
hide-blame
hide-diff
init
log
next-hunk
prev-hunk
reset
rm
show
show-branch
show-diff
status
update-diff
grep
} -shell-script-candidates %{
if [ $kak_token_to_complete -eq 0 ]; then
printf "add\napply\nrm\nreset\nblame\ncommit\ncheckout\ndiff\nhide-blame\nhide-diff\nlog\nnext-hunk\nprev-hunk\nshow\nshow-branch\nshow-diff\ninit\nstatus\nupdate-diff\ngrep\nedit\n"
printf %s\\n \
apply \
blame \
checkout \
commit \
diff \
edit \
grep \
hide-blame \
hide-diff \
init \
log \
next-hunk \
prev-hunk \
reset \
rm \
show \
show-branch \
show-diff \
status \
update-diff \
;
else
case "$1" in
commit) printf -- "--amend\n--no-edit\n--all\n--reset-author\n--fixup\n--squash\n"; git ls-files -m ;;

View File

@ -63,7 +63,10 @@ define-command -hidden -params ..3 man-impl %{ evaluate-commands %sh{
define-command -params ..1 \
-shell-script-candidates %{
find /usr/share/man/ $(printf %s "${MANPATH}" | sed 's/:/ /') -name '*.[1-8]*' | sed 's,^.*/\(.*\)\.\([1-8][a-zA-Z]*\).*$,\1(\2),'
find /usr/share/man/ $(printf %s "${MANPATH}" |
sed 's/:/ /') -name '*.[1-8]*' |
sed 's,^.*/\(.*\)\.\([1-8][a-zA-Z]*\).*$,\1(\2),' |
sort
} \
-docstring %{
man [<page>]: manpage viewer wrapper

View File

@ -337,10 +337,13 @@ private:
{
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;

View File

@ -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() ?

View File

@ -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; };

View File

@ -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);
@ -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;
};
}