# Picker

> A searchable list on a floating surface, with fuzzy matching. A native Pyzed component for Python desktop GUIs, with a live preview, code examples and its API.

Source: https://albakore.github.io/pyzed/library/picker/

:::tip[In plain words]
A floating search box with a list of results that narrows as you type.
:::

A search box over a list of results on a floating surface: the basis of the command palette and the file finder. `ui.fuzzy_filter(query, items, key=)` ranks items (best match first) and returns `[(item, positions), ...]`; pass those pairs to `picker` to highlight the matches. `ui.fuzzy_match` scores one string and `ui.highlighted` draws one with its matches.

`ui.picker` · Category: [Overlays](https://albakore.github.io/pyzed/library/#overlays) · [Full reference →](https://albakore.github.io/pyzed/reference/ui/#picker)

**Code:**

```python
files = ["src/app.rs", "src/render.rs", "python/pyzed/app.py", "python/pyzed/theme.py", "README.md"]
items = ui.fuzzy_filter("app", [ui.PaletteItem(f, on_open, icon="file-code") for f in files],
                        key=lambda item: item.label)
ui.picker("app", on_query, items, selected=0, placeholder="Search files…", width=480)
```

## When to use

- For your own finders: files, symbols, branches, projects, emoji.

## Usage

```python
import pyzed as pz
from pyzed import ui

class Finder(pz.App):
    def __init__(self):
        self.query, self.selected = "", 0
        self.files = ["src/app.rs", "src/render.rs", "README.md"]

    def view(self):
        matches = ui.fuzzy_filter(self.query, self.files)
        items = [(ui.PaletteItem(f, lambda f=f: self.open(f), icon="file"), positions)
                 for f, positions in matches[:50]]
        return ui.picker(self.query, lambda q: setattr(self, "query", q), items,
                         selected=self.selected, placeholder="Search files…")
```

## Variants

:::note[About these captures]
Each capture is recorded by pyzed itself, off-screen at 2x, in One Dark or One Light to match this site's theme, and cropped to the component so it shows at its real size. The pointer hovers, presses or types into it; a component that does not react to the pointer cycles through a few themes. With reduced motion you see the first frame. Open **Code** under a capture to see the snippet that draws it; free names such as `on_save` stand for your own callbacks.
:::

### Results

```python
files = ["src/app.rs", "src/render.rs", "app.py"]
items = ui.fuzzy_filter("app", [ui.PaletteItem(f, on_open, icon="file-code") for f in files],
                        key=lambda item: item.label)
ui.picker("app", on_query, items, width=380)
```

```python
ui.picker("zzz", on_query, [], empty_text="No files match", width=380)
```

```python
ui.picker("", on_query, [(ui.PaletteItem("main", on_open, icon="git-branch", detail="2 hours ago"), []),
                        (ui.PaletteItem("docs", on_open, icon="git-branch", detail="yesterday"), [])], width=380)
```

### Highlight

```python
t = pz.theme.current()
score, positions = ui.fuzzy_match("tgsw", "editor: toggle soft wrap")
ui.highlighted("editor: toggle soft wrap", positions, color=t.text, match_color=t.accent, size=15)
```

## Examples

### Highlighting matches

`fuzzy_match` returns `(score, positions)` or `None`; `highlighted` paints those positions.

**Code:**

```python
t = pz.theme.current()
score, positions = ui.fuzzy_match("tgsw", "editor: toggle soft wrap")
ui.highlighted("editor: toggle soft wrap", positions, color=t.text, match_color=t.accent, size=16)
```

## Events

Picker has **2 events of its own**, listed below. Like every widget it also has the **18 common events** (`click`, `double_click`, `right_click`, `mouse_enter`, `drag`, `key_down`, `resize`…), attached with `.on(...)`: see [Events](https://albakore.github.io/pyzed/guides/events/) and [Events of every component](https://albakore.github.io/pyzed/library/events/).

| Event | Handler gets | When |
| --- | --- | --- |
| `on_query` | `(text)` | Callable that receives the new text. |
| `on_submit` | `()` | Message or callable sent when Enter is pressed. |

**Handler gets** is what Pyzed passes to your callable: `()` means nothing. Any handler can also be a message (a value that is not callable), which goes to `update()`.

### Handling the events

The same events written three ways. **Imperative**: methods of the app change `self`. **Declarative**: the handlers produce messages and one `update()` decides. **Functional**: a pure function draws an immutable state and asks for changes with `set_state(...)`. See [Imperative, declarative and functional](https://albakore.github.io/pyzed/guides/code-styles/).

**Imperative:**

```python
class Finder(pz.App):
    def __init__(self):
        self.query, self.opened = "", None
        self.files = ["src/app.rs", "src/render.rs", "README.md"]

    def matches(self):
        return ui.fuzzy_filter(self.query, self.files)

    def open_first(self):                # on_submit: Enter
        found = self.matches()
        self.opened = found[0][0] if found else None

    def view(self):
        items = [(ui.PaletteItem(f, lambda f=f: setattr(self, "opened", f), icon="file"), positions)
                 for f, positions in self.matches()]
        return ui.picker(self.query, lambda q: setattr(self, "query", q), items,   # on_query
                         on_submit=self.open_first, placeholder="Search files…")
```

**Declarative:**

```python
from dataclasses import dataclass

@dataclass
class Query:
    text: str

@dataclass
class Open:
    path: str

class Finder(pz.App):
    query, opened = "", None
    files = ["src/app.rs", "src/render.rs", "README.md"]

    def update(self, message):
        match message:
            case Query(text):
                self.query = text
            case Open(path):
                self.opened = path

    def view(self):
        items = [(ui.PaletteItem(f, Open(f), icon="file"), p) for f, p in ui.fuzzy_filter(self.query, self.files)]
        return ui.picker(self.query, Query, items, placeholder="Search files…")
```

**Functional:**

```python
from dataclasses import dataclass

FILES = ["src/app.rs", "src/render.rs", "README.md"]

@dataclass(frozen=True)
class State:
    query: str = ""
    opened: str = ""

def view(state, set_state):
    items = [(ui.PaletteItem(f, lambda f=f: set_state(opened=f), icon="file"), p)
             for f, p in ui.fuzzy_filter(state.query, FILES)]
    return ui.picker(state.query, lambda q: set_state(query=q), items, placeholder="Search files…")
```

## Properties and API

### `ui.picker`

```python
picker(
    query,
    on_query,
    items,
    *,
    selected=0,
    placeholder='Search…',
    input_id='picker',
    on_submit=None,
    width=560,
    max_height=340,
    empty_text='No matches',
    theme=None,
)
```

A searchable list on a floating surface. The basis of the palette and the file finder.

| Property | Default | Description |
| --- | --- | --- |
| `query` | *required* | Search text. |
| `items` | *required* | `(PaletteItem, positions)` pairs, for example from `fuzzy_filter`. |
| `selected` | `0` | Highlighted index. |
| `placeholder` | `"Search…"` | Hint text shown when empty. |
| `input_id` | `"picker"` | Field id. |
| `width` | `560` | Width: pixels, `"fill"`, `"shrink"` or `"fill:N"` (proportion). |
| `max_height` | `340` | Maximum height in pixels. |
| `empty_text` | `"No matches"` | Text shown when there are no results. |
| `theme` | `None` | Theme to use instead of the current one (`pyzed.theme.current()`). |

Events: `on_query`, `on_submit` ([events](#events)).

[Full reference →](https://albakore.github.io/pyzed/reference/ui/#picker)

### `ui.fuzzy_filter`

```python
fuzzy_filter(query, items, key=<class 'str'>)
```

Filters and sorts `items` by fuzzy score. Returns `[(item, positions), ...]`.

| Property | Default | Description |
| --- | --- | --- |
| `query` | *required* | Search text. |
| `items` | *required* | Items. |
| `key` | `<class 'str'>` | Function that returns the text of each item. |

[Full reference →](https://albakore.github.io/pyzed/reference/ui/#fuzzy_filter)

### `ui.fuzzy_match`

```python
fuzzy_match(query, candidate)
```

Returns `(score, positions)` if every letter of `query` appears in order in `candidate`; otherwise `None`. Rewards word starts and consecutive letters.

| Property | Default | Description |
| --- | --- | --- |
| `query` | *required* | Search text. |
| `candidate` | *required* | Candidate text. |

[Full reference →](https://albakore.github.io/pyzed/reference/ui/#fuzzy_match)

### `ui.highlighted`

```python
highlighted(text, positions, *, color, match_color, size=13.5, weight=None)
```

Text with the characters at `positions` highlighted (fuzzy search matches).

| Property | Default | Description |
| --- | --- | --- |
| `text` | *required* | Text. |
| `positions` | *required* | Indices to highlight. |
| `color` | *required* | Text or icon color. |
| `match_color` | *required* | Color of the matches. |
| `size` | `13.5` | Size. |
| `weight` | `None` | Text weight. |

[Full reference →](https://albakore.github.io/pyzed/reference/ui/#highlighted)

## Related

- [Command palette](https://albakore.github.io/pyzed/library/command-palette/): A command palette with fuzzy search and shortcuts.
- [Search input](https://albakore.github.io/pyzed/library/search-input/): An input with a magnifying glass, for filtering.
- [Layer](https://albakore.github.io/pyzed/library/layer/): Draws an overlay over your view; clicking outside dismisses it.
