# Editor

> A full code editor: highlighting, folding, minimap, git markers. A Pyzed component for Python desktop GUIs, with examples and its API.

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

:::tip[In plain words]
A box for writing code, with colors, line numbers and everything a programmer expects.
:::

A code editor with syntax highlighting in the theme colors (100+ languages), line numbers, git markers, auto-closing pairs, folding, a minimap and font zoom. It is **uncontrolled**: the text, cursor and undo history live in Rust, tied to `id`, and `text` is only the initial value. `on_change` receives the full text and `on_cursor` the `(line, column)`. `pz.code_editor` is the base widget without the theme.

`ui.editor` · Category: [Code and tools](https://albakore.github.io/pyzed/library/#code-and-tools) · [Full reference →](https://albakore.github.io/pyzed/reference/ui/#editor)

**Code:**

```python
code = '''import pyzed as pz
from pyzed import ui

class Counter(pz.App):
    def __init__(self):
        self.count = 0

    def view(self):
        return ui.button(f"Clicks: {self.count}", self.increment)
'''
pz.container(ui.editor("counter.py", code, path="counter.py", markers={7: "modified", 10: "created"}),
             width=620, height=290)
```

## When to use

- For code, configuration files, logs you can edit, or any long text.
- Use the file path as `id`: each file keeps its cursor and history when you switch tabs.
- To replace the text from code, return `pz.Command.set_editor_text(id, text)`.

## Usage

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

from pathlib import Path

class Editor(pz.App):
    def __init__(self):
        self.path = "main.py"
        self.text = Path(self.path).read_text()

    def save(self):
        Path(self.path).write_text(self.text)

    def view(self):
        return ui.editor(self.path, self.text, path=self.path,
                         on_change=lambda text: setattr(self, "text", text))
```

## 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.
:::

### Language

```python
pz.container(ui.editor("v-py", "class Counter(pz.App):\n    count = 0\n\n    def view(self):\n        return ui.button('Add', self.add)", path="counter.py"), width=470, height=110)
```

```python
pz.container(ui.editor("v-rs", "fn main() {\n    println!(\"hi\");\n}\n", language="rust"),
             width=420, height=90)
```

```python
pz.container(ui.editor("v-json", '{\n  "name": "notes",\n  "version": 2\n}\n', language="json"),
             width=420, height=110)
```

### Options

```python
pz.container(ui.editor("v-g", "class Counter(pz.App):\n    count = 0\n\n    def view(self):\n        return ui.button('Add', self.add)", path="a.py", gutter=False), width=470, height=110)
```

```python
pz.container(ui.editor("v-m", "class Counter(pz.App):\n    count = 0\n\n    def view(self):\n        return ui.button('Add', self.add)", path="a.py", markers={2: "created", 5: "modified"}),
             width=470, height=110)
```

```python
pz.container(ui.editor("v-mm", "class Counter(pz.App):\n    count = 0\n\n    def view(self):\n        return ui.button('Add', self.add)", path="a.py", minimap=True), width=470, height=124)
```

```python
pz.container(ui.editor("v-ws", "class Counter(pz.App):\n    count = 0\n\n    def view(self):\n        return ui.button('Add', self.add)", path="a.py", show_whitespace=True), width=470, height=110)
```

```python
pz.container(ui.editor("v-ph", "", placeholder="Write something…"), width=420, height=60)
```

```python
pz.container(ui.editor("v-fs", "print(\"big\")\n", path="a.py", font_size=16), width=420, height=60)
```

## Examples

### Folding, code lens and minimap

Blocks fold by indentation (Ctrl+Shift+[ and ]), `annotations` adds a code lens above a line and `minimap=True` draws the overview on the right.

**Code:**

```python
code = '''import pyzed as pz
from pyzed import ui

class Counter(pz.App):
    def __init__(self):
        self.count = 0

    def increment(self):
        self.count += 1

    def view(self):
        return ui.button(f"Clicks: {self.count}", self.increment)
'''
pz.container(ui.editor("lens.py", code, path="counter.py", minimap=True, annotations={5: "1 reference"}),
             width=680, height=290)
```

### Cursor and whitespace

`cursor_shape` is `"bar"`, `"block"`, `"underline"` or `"hollow"`; `show_whitespace=True` draws spaces and tabs; `wrap=True` wraps long lines.

**Code:**

```python
pz.container(ui.editor("ws.py", "def greet(name):\n    return f'Hello, {name}'\n",
                       path="ws.py", cursor_shape="block", show_whitespace=True, font_size=15),
             width=620, height=120)
```

## Events

Editor has **6 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 | Of | Handler gets | When |
| --- | --- | --- | --- |
| `on_change` | `ui.editor`, `pz.code_editor` | `(text)` | Callable that receives the new value. |
| `on_cursor` | `ui.editor`, `pz.code_editor` | `(line, column)` | Callable that receives `(line, column)`. |
| `on_scroll` | `ui.editor`, `pz.code_editor` | `(info)` | Called on scroll. |
| `on_zoom` | `ui.editor`, `pz.code_editor` | `(step)` | Callable that receives `1`, `-1` or `0` for Ctrl+= / Ctrl+- / Ctrl+0 and Ctrl+wheel. |
| `on_action` | `ui.editor`, `pz.code_editor` | `(name)` | Callable that receives the name of each editor action that runs (`"undo"`, `"select_next"`…), e.g. to count them. |
| `on_hover` | `ui.editor`, `pz.code_editor` | `()` | Callable that receives `{line, column, start, end, x, y, height}` (the word under the mouse, from 1, and its place in the editor) after the mouse rests on it, and `None` when it leaves (hover cards). |

**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 Editor(pz.App):
    def __init__(self):
        self.text = "print('hello')\n"
        self.cursor = (1, 1)
        self.font_size = 14

    def zoom(self, step):                # on_zoom: 1, -1 or 0 (Ctrl+=, Ctrl+-, Ctrl+0)
        self.font_size = 14 if step == 0 else self.font_size + step

    def view(self):
        line, column = self.cursor
        return pz.column([
            ui.editor("main.py", self.text, path="main.py", font_size=self.font_size, height=200,
                      on_change=lambda text: setattr(self, "text", text),            # every edit
                      on_cursor=lambda ln, col: setattr(self, "cursor", (ln, col)),  # from 1
                      on_zoom=self.zoom,
                      on_action=lambda name: print("ran", name)),                    # "undo"...
            ui.status_bar([], [ui.status_item(f"Ln {line}, Col {column}")]),
        ])
```

**Declarative:**

```python
from dataclasses import dataclass

@dataclass
class Edited:
    text: str

@dataclass
class Moved:
    line: int
    column: int

class Editor(pz.App):
    text, cursor = "print('hello')\n", (1, 1)

    def update(self, message):
        match message:
            case Edited(text):
                self.text = text
            case Moved(line, column):
                self.cursor = (line, column)

    def view(self):
        return ui.editor("main.py", self.text, path="main.py", height=200,
                         on_change=Edited, on_cursor=Moved)   # the classes build the messages
```

**Functional:**

```python
from dataclasses import dataclass

@dataclass(frozen=True)
class State:
    text: str = "print('hello')\n"
    line: int = 1
    column: int = 1

def view(state, set_state):
    return pz.column([
        ui.editor("main.py", state.text, path="main.py", height=200,
                  on_change=lambda text: set_state(text=text),
                  on_cursor=lambda line, column: set_state(line=line, column=column)),
        ui.label(f"Ln {state.line}, Col {state.column}"),
    ])
```

## Properties and API

### `ui.editor`

```python
editor(
    id,
    text='',
    *,
    language=None,
    path=None,
    on_change=None,
    on_cursor=None,
    markers=None,
    font_size=None,
    line_height=None,
    gutter=True,
    wrap=False,
    active_line=True,
    height='fill',
    placeholder=None,
    annotations=None,
    show_whitespace=False,
    tab_size=None,
    hard_tabs=False,
    backspace_unindent=True,
    repeat_delay=None,
    repeat_rate=None,
    capture_keys=(),
    on_scroll=None,
    auto_close=True,
    folding=True,
    minimap=False,
    cursor_shape='bar',
    cursor_blink=True,
    cursor_width=2.0,
    cursor_color=None,
    on_zoom=None,
    on_action=None,
    highlights=None,
    match_brackets=True,
    selection_highlight=True,
    font=None,
    keymap=None,
    scrollbar=True,
    readonly=False,
    on_hover=None,
    hover_delay=500,
    sticky_scroll=0,
    theme=None,
)
```

A code editor: highlighting with the theme colors, line numbers and git markers.

| Property | Default | Description |
| --- | --- | --- |
| `id` | *required* | Identifies the buffer (use the file path). |
| `text` | `""` | Initial text. |
| `language` | `None` | Language. If missing, it is inferred from `path`. |
| `path` | `None` | File path. |
| `markers` | `None` | `{line: "created" \| "modified" \| "deleted"}`. |
| `font_size` | `None` | Font size (defaults to the theme's). |
| `line_height` | `None` | Line height. |
| `gutter` | `True` | Shows line numbers. |
| `wrap` | `False` | Line wrapping. |
| `active_line` | `True` | Background color of the cursor line. |
| `height` | `"fill"` | Height: pixels, `"fill"`, `"shrink"` or `"fill:N"` (proportion). |
| `placeholder` | `None` | Hint text shown when empty. |
| `annotations` | `None` | `{line: "text"}` or `{line: ("text", color)}`: virtual text at the end of the line (code lens, diagnostics). |
| `show_whitespace` | `False` | Draws spaces as `·` and tabs as `→`. |
| `tab_size` | `None` | Tab inserts spaces up to the next multiple; Enter keeps the indentation (one more level after `:`); Backspace removes a level. |
| `hard_tabs` | `False` | With `tab_size`: Tab, Enter and indent (Ctrl+]) use tab characters instead of spaces. |
| `backspace_unindent` | `True` | With `tab_size`: Backspace in leading spaces removes a whole level (`False`: one space). |
| `repeat_delay` | `None` | Milliseconds Backspace and Delete wait, held down, before repeating (`None`: the system's). |
| `repeat_rate` | `None` | Deletions per second at most while Backspace or Delete is held down (`None`: the system's pace). |
| `capture_keys` | `()` | Keys the editor leaves to the app's `on_key` (`"up"`, `"enter"`…), e.g. while a completion list is open. |
| `auto_close` | `True` | Closes brackets and quotes as you type (and wraps the selection). |
| `folding` | `True` | Fold chevrons in the gutter; Ctrl+Shift+[ folds and Ctrl+Shift+] unfolds. |
| `minimap` | `False` | `True` (or a width in pixels) shows a minimap on the right. |
| `cursor_shape` | `"bar"` | `"bar"`, `"block"`, `"underline"` or `"hollow"`. |
| `cursor_blink` | `True` | Whether the cursor blinks. |
| `cursor_width` | `2.0` | Width of the bar cursor. |
| `cursor_color` | `None` | Cursor color (the theme's player color by default). |
| `highlights` | `None` | Ranges painted behind the text in the theme's search color: `[(line, column, end_line, end_column)]`, from 1, with exclusive ends. |
| `match_brackets` | `True` | Outlines the bracket that matches the one next to the cursor (Ctrl+M jumps to it). |
| `selection_highlight` | `True` | Highlights the other occurrences of the selected text (Ctrl+D adds the next one as a cursor). |
| `font` | `None` | Typeface: `"mono"` by default; `"sans"` makes a multi-line text box, like a chat's message box. |
| `keymap` | `None` | Zed-style `{binding: action}` dict (`{"ctrl-shift-d": "duplicate_line_down"}`); `None` as the action removes a default key. Build it from per-action overrides with `pyzed.ui.editor.keymap_for`. |
| `scrollbar` | `True` | `False` hides the scrollbars. |
| `readonly` | `False` | `True` lets the text be selected and copied but not changed (a log, a traceback). |
| `hover_delay` | `500` | Milliseconds the mouse rests on a word before `on_hover` is called. |
| `sticky_scroll` | `0` | How many lines of the enclosing blocks (`class`, `def`, `if`…) stay pinned at the top while scrolling (`0`: none). |
| `theme` | `None` | Theme to use instead of the current one (`pyzed.theme.current()`). |

Events: `on_change`, `on_cursor`, `on_scroll`, `on_zoom`, `on_action`, `on_hover` ([events](#events)).

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

### `ui.language_token`

```python
language_token(path)
```

Returns the highlighter language token for a path (`"main.rs"` → `"rs"`).

| Property | Default | Description |
| --- | --- | --- |
| `path` | *required* | File path. |

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

### `pz.code_editor`

```python
code_editor(
    id,
    text='',
    *,
    language='txt',
    size=14.0,
    line_height=None,
    font=None,
    width=None,
    height=None,
    padding=None,
    syntax=None,
    bg=None,
    color=None,
    selection_color=None,
    placeholder=None,
    placeholder_color=None,
    gutter=True,
    gutter_color=None,
    gutter_active_color=None,
    gutter_width=None,
    markers=None,
    wrap=False,
    on_change=None,
    on_cursor=None,
    active_line=None,
    scrollbar_color=None,
    annotations=None,
    whitespace=None,
    tab_size=None,
    capture_keys=None,
    on_scroll=None,
    auto_close=False,
    folding=False,
    minimap=None,
    minimap_thumb=None,
    cursor_shape='bar',
    cursor_width=2.0,
    cursor_blink=True,
    cursor_color=None,
    on_zoom=None,
    on_action=None,
    highlights=None,
    highlight_color=None,
    match_brackets=True,
    selection_highlight=True,
    keymap=None,
    scrollbar=True,
    readonly=False,
    hard_tabs=False,
    backspace_unindent=True,
    repeat_delay=None,
    repeat_rate=None,
    on_hover=None,
    hover_delay=500.0,
    sticky_scroll=0,
)
```

A code editor with syntax highlighting, line numbers and git markers, based on Iced's
`text_editor`. It is **uncontrolled**: `text` is only the initial value for that `id`; the
text, cursor and history live in Rust. Use `Command.set_editor_text` to replace it.
For the themed editor use [`ui.editor`](https://albakore.github.io/pyzed/components/code/).

| Property | Default | Description |
| --- | --- | --- |
| `id` | *required* | Identifies the buffer. Reuse it to keep state across renders. |
| `text` | `""` | Initial text. |
| `language` | `"txt"` | Language extension or name (`"py"`, `"rs"`, `"json"`…). |
| `size` | `14.0` | Font size. |
| `line_height` | `None` | Line height in pixels. |
| `font` | `None` | Family (monospace by default). |
| `width` | `None` | Width: pixels, `"fill"`, `"shrink"` or `"fill:N"` (proportion). |
| `height` | `None` | Height: pixels, `"fill"`, `"shrink"` or `"fill:N"` (proportion). |
| `padding` | `None` | Inner padding: `8`, `(vertical, horizontal)` or `(top, right, bottom, left)`. |
| `syntax` | `None` | Map of TextMate scopes to colors (provided by `Theme.syntax_scopes()`). |
| `bg` | `None` | Background color (`"#rrggbb[aa]"`, `(r, g, b[, a])` or `"transparent"`). |
| `color` | `None` | Text or icon color. |
| `selection_color` | `None` | Selection color. |
| `placeholder` | `None` | Hint text shown when empty. |
| `placeholder_color` | `None` | Placeholder color. |
| `gutter` | `True` | Shows line numbers. |
| `gutter_color` | `None` | Line number color. |
| `gutter_active_color` | `None` | Color of the cursor line's number. |
| `gutter_width` | `None` | Gutter width (automatic by default). |
| `markers` | `None` | `{line: color}` for git markers in the gutter (lines from 1). |
| `wrap` | `False` | Line wrapping. |
| `active_line` | `None` | Background color of the cursor line. |
| `scrollbar_color` | `None` | Scrollbar color. |
| `annotations` | `None` | `{line: "text"}` or `{line: ("text", color)}`: virtual text at the end of the line (code lens, diagnostics). |
| `whitespace` | `None` | Draws spaces as `·` and tabs as `→` in this color. |
| `tab_size` | `None` | Tab inserts spaces up to the next multiple; Enter keeps the indentation; Backspace removes a level. |
| `capture_keys` | `None` | Keys the editor leaves to the app's `on_key` (`"up"`, `"enter"`…), e.g. while a completion list is open. |
| `auto_close` | `False` | Typing a bracket or quote also inserts its closing pair. |
| `folding` | `False` | Fold chevrons in the gutter and Ctrl+Shift+[ / ]. |
| `minimap` | `None` | Width of the minimap in pixels (`None`: no minimap). |
| `minimap_thumb` | `None` | Color of the minimap's visible-area highlight. |
| `cursor_shape` | `"bar"` | `"bar"`, `"block"`, `"underline"` or `"hollow"`. |
| `cursor_width` | `2.0` | Width of the bar cursor. |
| `cursor_blink` | `True` | Whether the cursor blinks. |
| `cursor_color` | `None` | Cursor color. |
| `highlights` | `None` | Ranges painted behind the text (search results): `[(line, column, end_line, end_column)]`, from 1, with exclusive ends. |
| `highlight_color` | `None` | Color of `highlights`. |
| `match_brackets` | `True` | Outlines the bracket that matches the one next to the cursor. |
| `selection_highlight` | `True` | Highlights the other occurrences of the selected text. |
| `keymap` | `None` | Zed-style `{binding: action}` dict; `None` as the action removes a default key (`pyzed.ui.editor.keymap_for` builds one from per-action overrides). |
| `scrollbar` | `True` | `False` hides the scrollbars. |
| `readonly` | `False` | `True` lets the text be selected and copied but not changed (a log, a traceback). |
| `hard_tabs` | `False` | With `tab_size`: Tab, Enter and indent (Ctrl+]) use tab characters instead of spaces. |
| `backspace_unindent` | `True` | With `tab_size`: Backspace in leading spaces removes a whole level (`False`: one space). |
| `repeat_delay` | `None` | Milliseconds Backspace and Delete wait, held down, before repeating (`None`: the system's). |
| `repeat_rate` | `None` | Deletions per second at most while Backspace or Delete is held down (`None`: the system's pace). |
| `hover_delay` | `500.0` | Milliseconds the mouse rests on a word before `on_hover` is called. |
| `sticky_scroll` | `0` | How many lines of the enclosing blocks (`class`, `def`, `if`…) stay pinned at the top while scrolling (`0`: none). |

Events: `on_change`, `on_cursor`, `on_scroll`, `on_zoom`, `on_action`, `on_hover` ([events](#events)).

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

## Related

- [Terminal](https://albakore.github.io/pyzed/library/terminal/): A read-only terminal view with ANSI colors.
- [Console](https://albakore.github.io/pyzed/library/console/): A dev-tools style log with levels, filter, copy and clear.
- [Workspace](https://albakore.github.io/pyzed/library/workspace/): The layout of an editor-style window: title bar, docks, center, status bar.
