Erabi.Match (Erabi v0.1.0)

Copy Markdown View Source

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 :priority is added to its score.

Summary

Types

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.

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

result()

@type result() :: %{score: integer(), positions: [non_neg_integer()]}

The score of an option, and the positions in its label to highlight.

Functions

fold(text)

@spec fold(String.t()) :: String.t()

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"

rank(options, query)

@spec rank([map()], String.t()) :: [{map(), result()}]

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.

score(option, query)

@spec score(map(), String.t()) :: result() | nil

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