Generic filterable picker data structure with fuzzy/orderless matching.
A picker holds a list of items and lets the user filter them by typing a query string, navigate with up/down, and select an item. The picker is a pure data structure with no side effects — the editor owns the rendering and action dispatch.
Fuzzy matching
The query is split on whitespace into segments. Each segment must match independently somewhere in the candidate label or description (orderless). Candidates are scored by match quality and sorted best-first:
- Exact prefix match scores highest
- Contiguous substring match scores well
- Fuzzy character-by-character match scores lower
- Shorter candidates score higher (tighter match)
Usage
alias MingaEditor.UI.Picker.Item
picker = Picker.new([
%Item{id: "pid1", label: "README.md", description: "/project/README.md"},
%Item{id: "pid2", label: "config.exs", description: "/project/config/config.exs [+]"}
], title: "Switch buffer")
picker = Picker.type_char(picker, "r")
# filtered to items matching "r"
picker = Picker.move_down(picker)
%Item{id: id} = Picker.selected_item(picker)
Summary
Types
A picker item struct.
0-based character indices of matched characters in a string.
Picker state. The marked map uses item ids as keys (values are true).
Functions
Removes the last character from the query and refilters.
Returns the number of filtered items.
Sets the query to an exact value and refilters.
Returns whether an item is marked.
Returns all marked items. If none are marked, returns the selected item in a list.
Returns the indices of characters in text that match the current query,
for use in highlighting matched characters during rendering.
Moves the selection down by one (wraps around).
Moves the selection up by one (wraps around).
Creates a new picker with the given items.
Moves the selection down by one page (max_visible items), clamped to the last item.
Moves the selection up by one page (max_visible items), clamped to the first item.
Returns the selected item's id, or nil.
Returns the currently selected item, or nil if no items match.
Toggles the mark on the currently selected item (for multi-select).
Returns the total number of items (unfiltered).
Appends a character to the query and refilters.
Returns the slice of filtered items visible in the picker window, along with the index of the selected item within that slice.
Types
@type item() :: MingaEditor.UI.Picker.Item.t()
A picker item struct.
@type match_positions() :: [non_neg_integer()]
0-based character indices of matched characters in a string.
@type option() :: {:title, String.t()} | {:max_visible, pos_integer()}
@type t() :: %MingaEditor.UI.Picker{ filtered: [MingaEditor.UI.Picker.Item.t()], items: [MingaEditor.UI.Picker.Item.t()], marked: %{optional(term()) => true}, max_visible: pos_integer(), query: String.t(), selected: non_neg_integer(), title: String.t() }
Picker state. The marked map uses item ids as keys (values are true).
Functions
Removes the last character from the query and refilters.
@spec count(t()) :: non_neg_integer()
Returns the number of filtered items.
Sets the query to an exact value and refilters.
@spec marked?(t(), MingaEditor.UI.Picker.Item.t()) :: boolean()
Returns whether an item is marked.
@spec marked_items(t()) :: [MingaEditor.UI.Picker.Item.t()]
Returns all marked items. If none are marked, returns the selected item in a list.
@spec match_positions(String.t(), String.t()) :: match_positions()
Returns the indices of characters in text that match the current query,
for use in highlighting matched characters during rendering.
Returns an empty list if the query is empty or doesn't match.
Examples
iex> MingaEditor.UI.Picker.match_positions("buffer-switch", "b sw")
[0, 7, 8]
iex> MingaEditor.UI.Picker.match_positions("README.md", "")
[]
Moves the selection down by one (wraps around).
Moves the selection up by one (wraps around).
Creates a new picker with the given items.
Moves the selection down by one page (max_visible items), clamped to the last item.
Moves the selection up by one page (max_visible items), clamped to the first item.
Returns the selected item's id, or nil.
Returns the currently selected item, or nil if no items match.
Toggles the mark on the currently selected item (for multi-select).
@spec total(t()) :: non_neg_integer()
Returns the total number of items (unfiltered).
Appends a character to the query and refilters.
@spec visible_items(t()) :: {[item()], non_neg_integer()}
Returns the slice of filtered items visible in the picker window, along with the index of the selected item within that slice.
Returns {visible_items, selected_offset}.