editor

package
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 7, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package editor is the composer's line editor: the multi-line buffer the user types a prompt into, with grapheme-correct cursor movement, a kill ring, undo, and atomic paste chips.

The buffer is a sequence of atoms. An atom is either one grapheme cluster (so an emoji ZWJ sequence or a combining accent moves and deletes as one unit, never splitting into broken runes) or a paste chip: a large paste is held out of the buffer and represented by a single atom that the cursor steps over and Backspace removes whole, so a 400-line paste occupies one cell of editing surface instead of drowning the composer. Content expands chips back to their full text at submit time.

The editor is pure state: no terminal, no reader, no timers. It consumes the input package's decoded events through Handle and reports what the caller should do (redraw, submit, recall history), and it renders itself to width-bounded rows for the screen painter. Determinism holds by construction: undo grouping keys off edit boundaries, not wall-clock idle time.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ParseChord

func ParseChord(s string) (string, error)

ParseChord normalizes one chord spelling ("Ctrl+Shift+Left", "alt+d", "enter") into the canonical form Keymap keys use: lowercase, modifiers in ctrl-alt-shift-super order, then a key name or a single character. It refuses spellings it cannot represent, so a typo in a keymap file is an error, not a binding that never fires.

Types

type Action

type Action int

Action is what the caller should do after the editor consumed an event.

const (
	// ActionNone reports the event was not the editor's to handle.
	ActionNone Action = iota
	// ActionRedraw reports the buffer or cursor changed; repaint the composer.
	ActionRedraw
	// ActionSubmit reports the user pressed Enter; send Content and Clear.
	ActionSubmit
	// ActionEsc reports Escape with the buffer untouched by this key; the
	// caller owns what Esc means (backtrack, dismiss an overlay, interrupt).
	ActionEsc
	// ActionTab hands completion to the caller, since it needs the file and
	// command universe the editor has no business knowing.
	ActionTab
	// ActionHistoryPrev reports the cursor tried to move above the first
	// line; recall the previous prompt.
	ActionHistoryPrev
	// ActionHistoryNext reports the cursor tried to move below the last
	// line; recall the next prompt.
	ActionHistoryNext
)

type Attachment

type Attachment struct {
	MediaType string
	Data      []byte
}

Attachment is one image bound to the composer: the encoded bytes and their IANA media type. The clipboard port yields PNG; the value is what a submit carries alongside the prompt text so the image reaches the model. It is deliberately a plain UI-layer type, not the model port's llm.Image, so the reusable composer stays free of a model-port dependency; the host converts it at the one point where the prompt is turned into a turn.

type Command

type Command string

Command names one editor operation a key chord can bind to. Names are dotted: "compose.*" commands resolve to caller-owned actions (submit, escape, completion), "editor.*" commands act on the buffer directly.

const (
	CmdUnbind Command = "none"

	CmdSubmit   Command = "compose.submit"
	CmdNewline  Command = "compose.newline"
	CmdEscape   Command = "compose.escape"
	CmdComplete Command = "compose.complete"

	CmdBackspace       Command = "editor.backspace"
	CmdDelete          Command = "editor.delete"
	CmdDeleteOrEOF     Command = "editor.delete-or-eof"
	CmdLeft            Command = "editor.left"
	CmdRight           Command = "editor.right"
	CmdWordLeft        Command = "editor.word-left"
	CmdWordRight       Command = "editor.word-right"
	CmdCursorUp        Command = "editor.cursor-up"
	CmdCursorDown      Command = "editor.cursor-down"
	CmdLineStart       Command = "editor.line-start"
	CmdLineEnd         Command = "editor.line-end"
	CmdKillToEnd       Command = "editor.kill-to-end"
	CmdKillToStart     Command = "editor.kill-to-start"
	CmdKillWordBack    Command = "editor.kill-word-back"
	CmdKillWordForward Command = "editor.kill-word-forward"
	CmdYank            Command = "editor.yank"
	CmdYankPop         Command = "editor.yank-pop"
	CmdUndo            Command = "editor.undo"
	CmdRedo            Command = "editor.redo"
)

The bindable command vocabulary. CmdUnbind is the explicit "none": binding a chord to it removes the default binding for that chord.

type Editor

type Editor struct {
	// contains filtered or unexported fields
}

Editor is the multi-line composer buffer. The zero value is ready to use.

func (*Editor) Attachments

func (e *Editor) Attachments() []Attachment

Attachments returns the images bound to the composer in buffer order, so the order the model sees matches the order the chips appear and a chip the user backspaced away is absent. It returns nil when there are none.

func (*Editor) Backspace

func (e *Editor) Backspace()

Backspace deletes the atom before the cursor: one grapheme cluster, or a whole chip.

func (*Editor) Clear

func (e *Editor) Clear()

Clear resets the buffer, history, and chips for the next prompt.

func (*Editor) CompleteToken

func (e *Editor) CompleteToken(trigger rune, text string)

CompleteToken replaces the active trigger token with the trigger, the chosen text, and a trailing space, and puts the cursor after the space. Without an active token it does nothing, so a stale accept (the buffer changed under a queued key) cannot splice the wrong range. One undo step restores the typed query.

func (*Editor) Content

func (e *Editor) Content() string

Content returns the buffer's text with every paste chip expanded to its full pasted text. Image chips contribute nothing: an image travels with the turn as a separate attachment (see Attachments), not as text. This is the prompt text submit sends.

func (*Editor) Delete

func (e *Editor) Delete()

Delete deletes the atom under the cursor.

func (*Editor) Down

func (e *Editor) Down() bool

Down mirrors Up toward the next line; false on the last line means the caller should move forward in prompt history.

func (*Editor) Empty

func (e *Editor) Empty() bool

Empty reports whether the buffer holds nothing.

func (*Editor) Handle

func (e *Editor) Handle(ev input.Event) Action

Handle consumes one decoded input event, resolving keys through the editor's keymap (the default map unless SetKeymap changed it).

func (*Editor) Insert

func (e *Editor) Insert(s string)

Insert splices text at the cursor. Line endings are normalized to \n, tabs become spaces (a tab's cell width depends on its column, which would make row widths unstable under the painter's overflow guard), and other control characters are dropped.

func (*Editor) InsertImage

func (e *Editor) InsertImage(att Attachment)

InsertImage binds an image to the composer and inserts an atomic chip for it at the cursor: like a paste chip, the cursor steps over it whole and Backspace removes it whole, and it holds no bytes in the buffer. The image itself is carried out of line and surfaced by Attachments at submit.

func (*Editor) InsertPaste

func (e *Editor) InsertPaste(text string)

InsertPaste inserts one paste event: inline when small and single-line, as an atomic chip otherwise.

func (*Editor) KillToEnd

func (e *Editor) KillToEnd()

KillToEnd kills from the cursor to the end of the line, or the newline itself when the cursor already sits at the end (emacs Ctrl+K).

func (*Editor) KillToStart

func (e *Editor) KillToStart()

KillToStart kills from the start of the line to the cursor.

func (*Editor) KillWordBack

func (e *Editor) KillWordBack()

KillWordBack kills the word before the cursor.

func (*Editor) KillWordForward

func (e *Editor) KillWordForward()

KillWordForward kills the word after the cursor.

func (*Editor) Left

func (e *Editor) Left()

Left moves the cursor one atom back; atom granularity is what makes chips and grapheme clusters atomic to the user.

func (*Editor) LineEnd

func (e *Editor) LineEnd()

LineEnd moves the cursor to the end of the current logical line.

func (*Editor) LineStart

func (e *Editor) LineStart()

LineStart moves the cursor to the start of the current logical line.

func (*Editor) Redo

func (e *Editor) Redo() bool

Redo reverses an Undo, reporting whether there was one to reverse.

func (*Editor) Render

func (e *Editor) Render(width int) (rows []string, curRow, curCol int)

Render lays the buffer out as width-bounded rows for the screen painter and reports the cursor's cell position within them. Logical lines soft-wrap at the width; no row ever exceeds it, matching the painter's overflow guard. Rows are plain text: styling (the chip highlight, the prompt gutter) is the caller's composition step, and keeping ANSI out of the editor keeps width arithmetic exact.

func (*Editor) Right

func (e *Editor) Right()

Right moves the cursor one atom forward.

func (*Editor) SetKeymap

func (e *Editor) SetKeymap(km Keymap)

SetKeymap replaces the editor's bindings. Nil restores the default map. The map is used as given and must not be mutated afterwards.

func (*Editor) Token

func (e *Editor) Token(trigger rune) (string, bool)

Token returns the text after the trigger in the token ending at the cursor, and whether such a token is active. The query may be empty: a trigger just typed is an active token asking for the unfiltered universe.

func (*Editor) Undo

func (e *Editor) Undo() bool

Undo restores the previous snapshot, reporting whether there was one.

func (*Editor) Up

func (e *Editor) Up() bool

Up moves the cursor to the previous logical line, keeping the column when it fits. It reports false when the cursor is already on the first line, which is the caller's cue to recall prompt history instead.

func (*Editor) WordLeft

func (e *Editor) WordLeft()

WordLeft moves the cursor to the start of the previous word.

func (*Editor) WordRight

func (e *Editor) WordRight()

WordRight moves the cursor past the end of the next word.

func (*Editor) Yank

func (e *Editor) Yank()

Yank inserts the most recent kill at the cursor.

func (*Editor) YankPop

func (e *Editor) YankPop()

YankPop replaces the text a yank just inserted with the next older ring entry, cycling through the ring. It applies only immediately after a yank.

type Keymap

type Keymap map[string]Command

Keymap maps a normalized key chord (see ParseChord) to a command. Chords not in the map fall through: printable text inserts, everything else is unclaimed.

func Default

func Default() Keymap

Default is the built-in keymap: the emacs and readline set every shell user already knows, plus the arrow, Home/End, and Delete keys in both their legacy and kitty encodings (the input decoder has already normalized those onto one vocabulary).

func LoadKeymap

func LoadKeymap(r io.Reader) (Keymap, error)

LoadKeymap reads a JSON keymap and returns it layered over the default map, so a user keymap starts from the complete built-in set and only overrides what it names. Binding a chord to "none" removes its default. Unknown commands and malformed chords are refused rather than ignored: a misspelled name in a keymap file would otherwise silently bind nothing, which is the kind of failure a user cannot debug.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL