Documentation
¶
Overview ¶
Package lineedit is Tomobit's inline line editor (ADR-0022 Decision 3).
It owns one line of the terminal the user is already looking at: raw mode while a line is being typed, cooked mode the rest of the time. It is deliberately not a full-screen TUI — Tomo's lines and the provider's output must stay in the scrollback like any other log, the same reason the avatar is drawn with half-blocks instead of a canvas (ADR-0008).
Everything but the terminal's physics is hand-rolled: golang.org/x/term is used for raw mode and the window size only, and uniseg for display width (Japanese is full-width — cursor math is wrong without it).
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrInterrupt = errors.New("lineedit: interrupted")
ErrInterrupt is returned when the line was abandoned with Ctrl-C. The distinction from io.EOF (Ctrl-D) matters to the caller: one is "not this line", the other is "no more lines".
Functions ¶
This section is empty.
Types ¶
type Editor ¶
type Editor struct {
// Completer, when set, drives Tab completion. Given the whole buffer and
// the cursor's rune index it returns the candidate replacements for the
// token at the cursor and the rune index where that token starts
// (start<0 or start>pos means "nothing to complete here"). The editor
// stays ignorant of what is being completed — the caller owns the
// vocabulary, the same boundary ADR-0022 Decision 3 drew for lineedit.
Completer func(text string, pos int) (candidates []string, start int)
// WrapIndent is the column a wrapped or multi-line input's continuation
// rows indent to, so they align under a gutter instead of the terminal's
// left edge. Zero (the default) keeps the flush-left wrap; the caller owns
// the gutter width, the same boundary Completer draws.
WrapIndent int
// TextStyle, when set, is an SGR sequence (a faint background) painted
// behind the typed text — not the prompt, not the gutter. Empty keeps the
// line unstyled. The caller owns the colour and whether the terminal wants
// one at all (NO_COLOR), the same boundary dim already draws elsewhere.
TextStyle string
// contains filtered or unexported fields
}
Editor reads lines from a terminal. Zero value is not usable — use New.
func (*Editor) AddHistory ¶
AddHistory records a submitted line. Blank lines and an immediate repeat are dropped — pressing Up should reach the last thing worth retyping. The same line is appended to the history file if one is set, so the next process starts where this one left off.
func (*Editor) Interactive ¶
Interactive reports whether both ends are a terminal. A pipe gets the cooked path: same reader, one line per read, no ANSI.
func (*Editor) ReadLine ¶
ReadLine prints prompt and returns the submitted line. It returns io.EOF on Ctrl-D at an empty line (or a closed stdin) and ErrInterrupt on Ctrl-C.
func (*Editor) Reader ¶
Reader exposes the editor's buffered view of its input, so that prompts asked between lines (the adoption question, Tomo's) read from the same place.
What must not happen is a prompt reading os.Stdin directly: this reader has already pulled bytes off that file — typeahead typed while a provider was working — and a reader over the file cannot see them. It would block for input that has already arrived, and the bytes would be stranded here. (Passing this value back through bufio.NewReader is harmless — measured: the stdlib hands the same reader back rather than stacking a second one.)
func (*Editor) SetHistoryFile ¶
SetHistoryFile loads path into the ring and remembers it for later appends, with warn as the sink for append failures (ADR-0024 Decision 1). The load failure is returned, not fatal: history is UI state, not the ledger, and has no right to keep a chat from opening — the caller warns once and carries on.
A file longer than maxHistory is compacted on load, so appending across many sessions cannot grow it without bound. A missing file is not an error: the first process to run simply has no past to recall.
func (*Editor) SetReader ¶
SetReader replaces the buffered input the cooked path reads from, so a caller can interpose on the bytes beneath the buffer. The NDJSON view's flush-on-read hook (ADR-0032 Decision 1) lives there — under os.Stdin, where bufio only pulls when a read blocks — because above the buffer the moment a read reaches the bottom is invisible. The *os.File given to New still backs raw-mode detection and width; the only caller (the view) runs with a non-TTY stdout, so Interactive() is false and ReadLine stays on the cooked path this reader feeds — raw mode never engages. Reader() returns this one afterwards, so a prompt asked between lines shares it.
type KeyType ¶
type KeyType int
KeyType is one editing intent, already decoded from the terminal's bytes.
const ( KeyUnknown KeyType = iota KeyRune KeyEnter KeyNewline // insert a literal newline (Shift/Alt+Enter, or `\` before Enter) KeyBackspace KeyDelete KeyLeft KeyRight KeyUp KeyDown KeyWordLeft KeyWordRight KeyHome KeyEnd KeyKillToEnd KeyClearInput KeyKillWord KeyYank // Ctrl-Y: put the kill slot back at the cursor KeyClearScreen KeyTab // completion KeySearch // Ctrl-R: reverse incremental history search KeySuspend // Ctrl-Z: hand the terminal back and stop (unix only) KeyInterrupt // Ctrl-C KeyEOT // Ctrl-D: end of input on an empty buffer, delete otherwise KeyPaste // Text holds the whole bracketed-paste block )