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" cut -f 1 "$tags" | grep -v '^!' | uniq > "$namecache"
fi fi
cat "$namecache" cat "$namecache"
done} \ done | sort } \
-docstring %{ -docstring %{
ctags-search [<symbol>]: jump to a symbol's definition ctags-search [<symbol>]: jump to a symbol's definition
If no symbol is passed then the current selection is used as symbol name 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 } /^\[\[[^\]]+\]\]/ { sub(/^\[\[/, ""); sub(/\]\].*/, ""); print }
' < $page | tr '[A-Z ]' '[a-z-]' ' < $page | tr '[A-Z ]' '[a-z-]'
fi;; fi;;
esac esac | sort
} }
alias global help doc alias global help doc

View File

@ -62,27 +62,49 @@ define-command -params 1.. \
Available commands: Available commands:
add add
apply (alias for "patch git apply") apply (alias for "patch git apply")
rm
reset
blame blame
commit
checkout checkout
commit
diff diff
edit
grep
hide-blame hide-blame
hide-diff hide-diff
init init
log log
next-hunk next-hunk
prev-hunk prev-hunk
reset
rm
show show
show-branch show-branch
show-diff show-diff
status status
update-diff update-diff
grep
} -shell-script-candidates %{ } -shell-script-candidates %{
if [ $kak_token_to_complete -eq 0 ]; then 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 else
case "$1" in case "$1" in
commit) printf -- "--amend\n--no-edit\n--all\n--reset-author\n--fixup\n--squash\n"; git ls-files -m ;; 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 \ define-command -params ..1 \
-shell-script-candidates %{ -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 %{ -docstring %{
man [<page>]: manpage viewer wrapper man [<page>]: manpage viewer wrapper

View File

@ -337,11 +337,14 @@ private:
{ {
UsedLetters query_letters = used_letters(query); UsedLetters query_letters = used_letters(query);
Vector<RankedMatch> matches; 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}) if (RankedMatch m{candidate.first, candidate.second, query, query_letters})
{
m.set_input_sequence_number(i);
matches.push_back(m); matches.push_back(m);
} }
}
constexpr size_t max_count = 100; constexpr size_t max_count = 100;
CandidateList res; CandidateList res;

View File

@ -298,10 +298,11 @@ InsertCompletion complete_option(const SelectionList& sels,
StringView query = buffer.substr(coord, cursor_pos); StringView query = buffer.substr(coord, cursor_pos);
Vector<RankedMatchAndInfo> matches; Vector<RankedMatchAndInfo> matches;
for (auto& candidate : opt.list) for (auto&& [i, candidate] : opt.list | enumerate())
{ {
if (RankedMatchAndInfo match{std::get<0>(candidate), query}) if (RankedMatchAndInfo match{std::get<0>(candidate), query})
{ {
match.set_input_sequence_number(i);
match.on_select = std::get<1>(candidate); match.on_select = std::get<1>(candidate);
auto& menu = std::get<2>(candidate); auto& menu = std::get<2>(candidate);
match.menu_entry = not menu.empty() ? 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) if (m_max_index != other.m_max_index)
return 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 // Reorder codepoints to improve matching behaviour
auto order = [](Codepoint cp) { return cp == '/' ? 0 : cp; }; auto order = [](Codepoint cp) { return cp == '/' ? 0 : cp; };

View File

@ -31,6 +31,8 @@ struct RankedMatch
explicit operator bool() const { return m_matches; } explicit operator bool() const { return m_matches; }
void set_input_sequence_number(size_t i) { m_input_sequence_number = i; }
private: private:
template<typename TestFunc> template<typename TestFunc>
RankedMatch(StringView candidate, StringView query, TestFunc test); RankedMatch(StringView candidate, StringView query, TestFunc test);
@ -54,6 +56,7 @@ private:
Flags m_flags = Flags::None; Flags m_flags = Flags::None;
int m_word_boundary_match_count = 0; int m_word_boundary_match_count = 0;
int m_max_index = 0; int m_max_index = 0;
size_t m_input_sequence_number = 0;
}; };
} }