How a picker matches and ranks options as a person types, in Elixir.
The browser filters the options it has with the same rules, and the
same numbers (test/fixtures/match.json holds the two to the same
results), so this module says what a picker shows: Erabi.Test uses
it, and a search of your own can rank with it on the server.
The rules are Jumon's, whose palette ranks its commands so:
- case, accents and the width of characters do not count: "cafe" finds "Café", "home" finds "Home";
- each word of the query must be found in the option, its letters in order (a subsequence): "nwiss" finds "New issue";
- a letter scores more at the start of the label or of a word, and after the letter before it; letters far apart score less;
- a word is found in the label first, then in the keywords, the hint and the group, each scoring less, and there only as letters next to each other;
- a word of four letters or more that is not found may have one typo (a letter missing, added, changed, or two letters swapped) against the start of a word of the label or of a keyword: "isue" finds "Issues";
- the label equal to the query, then a label that starts with it, rank first; a shorter label ranks before a longer one;
- the option's
:priorityis added to its score.
Summary
Functions
Folds a text for matching: decomposed (NFKD), without combining marks, in lower case, character by character.
Ranks options for a query: those that match, best first, each with its
score and positions. Options with the same score keep their order. With
an empty query, every option, by :priority.
The score of an option for a query, or nil when it does not match.
The positions are the characters of the label, by index, that the query
matched. An option is an Erabi.Option, or a map with :label (and
:keywords, :hint, :group, :priority).
Types
@type result() :: %{score: integer(), positions: [non_neg_integer()]}
The score of an option, and the positions in its label to highlight.
Functions
Folds a text for matching: decomposed (NFKD), without combining marks, in lower case, character by character.
Examples
iex> Erabi.Match.fold("Café AB")
"cafe ab"
Ranks options for a query: those that match, best first, each with its
score and positions. Options with the same score keep their order. With
an empty query, every option, by :priority.
The score of an option for a query, or nil when it does not match.
The positions are the characters of the label, by index, that the query
matched. An option is an Erabi.Option, or a map with :label (and
:keywords, :hint, :group, :priority).
Examples
iex> Erabi.Match.score(%{label: "New issue"}, "nis").positions
[0, 4, 5]
iex> Erabi.Match.score(%{label: "New issue"}, "xyz")
nil