Documentation
¶
Overview ¶
Package termui extracts the terminal into a self-contained component (TerminalUI) with its own goroutine, step 3 of the vtui integration roadmap (see AGENTS.md, Phase 5).
The terminal thread owns: input events from EventChan, resize requests, writes to the child process and waiting for the child to exit. The UI thread never blocks on pty writes or input translation: it enqueues vtinput events and, once per frame, asks for a layout (LayoutIfDirty) and reads the published Snapshot.
Layout stays frame-paced by design: frameFunc drives LayoutIfDirty when the parser marked the content dirty (the read-side flood pacing — 4 MiB cap per read plus the dirty flag — depends on it). A self-paced layout on the terminal thread would break that pacing, so the terminal thread only lays out on resize.
Index ¶
- func BGRToRGB(c uint32) uint32
- func CellToCharInfo(c *termcell.TextCell, wide uint8) vtui.CharInfo
- func DefaultHistoryPath() string
- type CmdLineAction
- type CmdLineFrame
- func (f *CmdLineFrame) Close()
- func (f *CmdLineFrame) GetType() vtui.FrameType
- func (f *CmdLineFrame) ProcessKey(e *vtinput.InputEvent) bool
- func (f *CmdLineFrame) ProcessMouse(e *vtinput.InputEvent) bool
- func (f *CmdLineFrame) ResizeConsole(w, h int)
- func (f *CmdLineFrame) SetPosition(x1, y1, x2, y2 int)
- func (f *CmdLineFrame) Show(scr *vtui.ScreenBuf)
- type CmdLineMode
- type CmdLineSwitcher
- type CommandLine
- func (cl *CommandLine) Clear()
- func (cl *CommandLine) InsertString(text string)
- func (cl *CommandLine) IsEmpty() bool
- func (cl *CommandLine) ProcessKey(e *vtinput.InputEvent) bool
- func (cl *CommandLine) ProcessMouse(e *vtinput.InputEvent) bool
- func (cl *CommandLine) RefreshPrompt()
- func (cl *CommandLine) SetFocus(f bool)
- func (cl *CommandLine) SetPosition(x1, y1, x2, y2 int)
- func (cl *CommandLine) Show(scr *vtui.ScreenBuf)
- type FileHistoryProvider
- type Session
- func (s *Session) Close()
- func (s *Session) Done() <-chan struct{}
- func (s *Session) Exited() <-chan struct{}
- func (s *Session) IsBusy() bool
- func (s *Session) LayoutIfDirty() bool
- func (s *Session) Resize(cols, rows int)
- func (s *Session) Run(inputHandler func(*vtinput.InputEvent))
- func (s *Session) Snapshot() Snapshot
- func (s *Session) Write(b []byte)
- type Snapshot
- type TerminalFrame
- func (f *TerminalFrame) Close()
- func (f *TerminalFrame) GetType() vtui.FrameType
- func (f *TerminalFrame) ProcessKey(e *vtinput.InputEvent) bool
- func (f *TerminalFrame) ProcessMouse(e *vtinput.InputEvent) bool
- func (f *TerminalFrame) ResizeConsole(w, h int)
- func (f *TerminalFrame) SetPosition(x1, y1, x2, y2 int)
- func (f *TerminalFrame) Show(scr *vtui.ScreenBuf)
- type TerminalUI
- func (t *TerminalUI) Enqueue(e *vtinput.InputEvent)
- func (t *TerminalUI) ProcessKey(e *vtinput.InputEvent) bool
- func (t *TerminalUI) ProcessMouse(e *vtinput.InputEvent) bool
- func (t *TerminalUI) ResizeConsole()
- func (t *TerminalUI) SetPosition(x1, y1, x2, y2 int)
- func (t *TerminalUI) Show(scr *vtui.ScreenBuf)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BGRToRGB ¶
BGRToRGB swaps the terminal's 0x00BBGGRR cell colors into vtui's 0xRRGGBB (the inverse of vtuibridge.RGBToBGR).
func CellToCharInfo ¶
CellToCharInfo converts one terminal text cell (plus its Wide flags byte, 0 when the cell is not part of a wide glyph) into a vtui CharInfo.
func DefaultHistoryPath ¶
func DefaultHistoryPath() string
DefaultHistoryPath returns os.UserConfigDir()/thrupty/history.json.
Types ¶
type CmdLineAction ¶
type CmdLineAction int
CmdLineAction tells the caller how to change the frame stack.
const ( CmdLineNoAction CmdLineAction = iota CmdLineHide // child went busy: detach the frame CmdLineShow // child idle again: re-attach the same frame )
type CmdLineFrame ¶
type CmdLineFrame struct {
vtui.BaseFrame
CL *CommandLine
}
CmdLineFrame is the minimal vtui frame hosting the pipes-mode CommandLine: a single line docked to the bottom of the screen. It is deliberately NOT modal and never "done" — busy switching only detaches it from the frame stack, the CommandLine instance (text, cursor, history) is never recreated.
func NewCmdLineFrame ¶
func NewCmdLineFrame(cl *CommandLine, cols, rows int) *CmdLineFrame
NewCmdLineFrame docks a CommandLine to the bottom line of a cols×rows console.
func (*CmdLineFrame) Close ¶
func (f *CmdLineFrame) Close()
Close is a no-op: the command line lives as long as its session, so the frame must never become "done" (the FrameManager would garbage-collect it from the stack).
func (*CmdLineFrame) GetType ¶
func (f *CmdLineFrame) GetType() vtui.FrameType
GetType returns TypeUser: the frame is neither a dialog nor a menu.
func (*CmdLineFrame) ProcessKey ¶
func (f *CmdLineFrame) ProcessKey(e *vtinput.InputEvent) bool
ProcessKey forwards to the command line (which also handles focus events).
func (*CmdLineFrame) ProcessMouse ¶
func (f *CmdLineFrame) ProcessMouse(e *vtinput.InputEvent) bool
ProcessMouse forwards to the command line.
func (*CmdLineFrame) ResizeConsole ¶
func (f *CmdLineFrame) ResizeConsole(w, h int)
ResizeConsole re-docks the frame to the bottom line of the new size.
func (*CmdLineFrame) SetPosition ¶
func (f *CmdLineFrame) SetPosition(x1, y1, x2, y2 int)
SetPosition moves the frame and the command line with it.
func (*CmdLineFrame) Show ¶
func (f *CmdLineFrame) Show(scr *vtui.ScreenBuf)
Show renders the command line (prompt + Edit control).
type CmdLineMode ¶
type CmdLineMode int
CmdLineMode is the input state of a pipes-mode session.
const ( // CmdLineEdit: the command-line frame is on the overlay stack and owns // all input. CmdLineEdit CmdLineMode = iota // CmdLinePassthrough: the frame is detached, input flows to the // terminal (and from there to the busy child) as in PTY mode. CmdLinePassthrough )
type CmdLineSwitcher ¶
type CmdLineSwitcher struct {
// contains filtered or unexported fields
}
CmdLineSwitcher is the pure busy-switching state machine, kept free of FrameManager calls so tests can drive it directly. The caller applies the returned actions to the real stack (all on the UI thread).
func NewCmdLineSwitcher ¶
func NewCmdLineSwitcher() *CmdLineSwitcher
NewCmdLineSwitcher starts in edit mode: the pipes shell is not busy at startup and the frame is pushed right away.
func (*CmdLineSwitcher) Mode ¶
func (s *CmdLineSwitcher) Mode() CmdLineMode
Mode reports the current input state.
func (*CmdLineSwitcher) Update ¶
func (s *CmdLineSwitcher) Update(busy bool) CmdLineAction
Update folds one busy-poll sample into the state machine.
type CommandLine ¶
type CommandLine struct {
vtui.ScreenObject
Edit *vtui.Edit
RichPrompt []vtui.CharInfo
// OnEnter is called with the line text after Enter; the CommandLine
// itself never writes to the session. Runs on the FrameManager
// goroutine.
OnEnter func(text string)
// contains filtered or unexported fields
}
CommandLine is the pipes-mode shell input line: a vtui.Edit with a rich prompt, modeled on f4's command_line.go. One instance lives for the whole session — busy switching only detaches its frame from the stack, so text, cursor and history survive.
func NewCommandLine ¶
func NewCommandLine() *CommandLine
NewCommandLine creates the editor with shell-style history settings and loads the persisted history (when a vtui.GlobalHistoryProvider is set).
func (*CommandLine) InsertString ¶
func (cl *CommandLine) InsertString(text string)
InsertString adds text to the command line.
func (*CommandLine) IsEmpty ¶
func (cl *CommandLine) IsEmpty() bool
IsEmpty returns true if there is no text in the command line.
func (*CommandLine) ProcessKey ¶
func (cl *CommandLine) ProcessKey(e *vtinput.InputEvent) bool
ProcessKey handles Enter (history + OnEnter), delegates everything else to the Edit control, exits history-browsing mode on edits and opens the autocomplete menu on editing keys (f4 command_line.go:88-121, without the AppConfig gate).
func (*CommandLine) ProcessMouse ¶
func (cl *CommandLine) ProcessMouse(e *vtinput.InputEvent) bool
ProcessMouse delegates to the Edit control.
func (*CommandLine) RefreshPrompt ¶
func (cl *CommandLine) RefreshPrompt()
RefreshPrompt rebuilds the rich prompt for the current width, re-reading the working directory every time (tracking the child's cwd is out of v1 scope).
func (*CommandLine) SetFocus ¶
func (cl *CommandLine) SetFocus(f bool)
SetFocus propagates the focus state to the Edit control.
func (*CommandLine) SetPosition ¶
func (cl *CommandLine) SetPosition(x1, y1, x2, y2 int)
SetPosition moves the line; the Edit control starts right after the prompt.
func (*CommandLine) Show ¶
func (cl *CommandLine) Show(scr *vtui.ScreenBuf)
Show draws the prompt and the Edit control.
type FileHistoryProvider ¶
type FileHistoryProvider struct {
// contains filtered or unexported fields
}
FileHistoryProvider implements vtui.HistoryProvider with JSON file persistence (modeled on f4's history_provider.go): one map of history-ID to string list, rewritten on every save.
func NewFileHistoryProvider ¶
func NewFileHistoryProvider(path string) *FileHistoryProvider
NewFileHistoryProvider loads (or starts) the history store at path.
func (*FileHistoryProvider) LoadHistory ¶
func (hp *FileHistoryProvider) LoadHistory(id string) []string
LoadHistory returns a copy of the stored list for id (nil when unknown).
func (*FileHistoryProvider) SaveHistory ¶
func (hp *FileHistoryProvider) SaveHistory(id string, history []string)
SaveHistory stores the list for id and persists the whole store.
type Session ¶
type Session struct {
// Term is the terminal state (text cells are the single source of
// truth). Exported: the reader goroutine and the UI thread already
// synchronize on Term.Lock.
Term *terminal.Terminal
// EventChan is the input queue consumed by Run on the terminal
// thread. Senders must use TerminalUI.Enqueue (non-blocking, drop
// policy, close-safe); never send while holding Term.Lock.
EventChan chan *vtinput.InputEvent
// contains filtered or unexported fields
}
Session owns one terminal and its child process I/O (a PtyBackend in shell mode, anonymous pipes in --exec mode, nothing in --demo mode).
func NewExecSession ¶
func NewExecSession(term *terminal.Terminal, stdout io.Reader, stdin io.Writer, child *exec.Cmd) *Session
NewExecSession creates a session around an already-started exec.Cmd on anonymous pipes (the --exec mode: no PTY, no SetSize) and spawns the reader goroutine.
func NewSession ¶
func NewSession(term *terminal.Terminal, backend pty.PtyBackend) *Session
NewSession creates a session around an already-started PTY backend and spawns the reader goroutine pumping child output into the terminal. A nil backend is the demo mode: no child, no reader, Write is a no-op.
func (*Session) Close ¶
func (s *Session) Close()
Close stops the terminal thread and the child process. Idempotent.
func (*Session) Done ¶
func (s *Session) Done() <-chan struct{}
Done is closed when Run returns (after Close).
func (*Session) Exited ¶
func (s *Session) Exited() <-chan struct{}
Exited is closed when the child process ends (the shell exited or crashed). Hosts use it to shut down with the session — thrupty is a single-session application for now.
func (*Session) IsBusy ¶
IsBusy reports whether the shell is running a foreground command. PTY mode delegates to the backend; exec (pipes) mode checks whether the child has children of its own. Demo mode is never busy.
func (*Session) LayoutIfDirty ¶
LayoutIfDirty runs a layout pass when the parser produced new output and publishes the result. Called from the UI thread (frameFunc) — layout is deliberately frame-paced. Reports whether a layout happened.
func (*Session) Resize ¶
Resize requests a new console size. Non-blocking: a pending stale request is evicted, only the newest size matters.
func (*Session) Run ¶
func (s *Session) Run(inputHandler func(*vtinput.InputEvent))
Run is the terminal thread loop: it consumes input events (handed to inputHandler, which translates and writes them back via Write) and resize requests until Close. Blocking; run it on its own goroutine.
func (*Session) Write ¶
Write delivers bytes to the child process. It is the ONLY way anything writes to the child (input translation, terminal answers, OSC 52, far2l) and is serialized: the PTY backend is also written from the reader goroutine (OnWriteAnswer), which used to race with UI-thread writes.
type Snapshot ¶
type Snapshot struct {
Version uint64
Cells []termcell.TextCell
Wide []uint8
DimX, DimY int
CursorX int32
CursorY int32
CursorHidden bool
CursorShape int // DECSCUSR (0/1/2 block, 3/4 underline, 5/6 bar)
}
Snapshot is a versioned, immutable view of the terminal text grid, published after every layout pass. Slices are shared with previous snapshots but never mutated after publication: each publish allocates fresh ones. Readers take it by value under a short RLock.
type TerminalFrame ¶
type TerminalFrame struct {
vtui.BaseFrame
Term *TerminalUI
}
TerminalFrame wraps a TerminalUI so the FrameManager can host it. The frame is never "done" and never modal: it lives as long as its session.
func NewTerminalFrame ¶
func NewTerminalFrame(ui *TerminalUI, cols, rows int) *TerminalFrame
NewTerminalFrame covers the whole cols×rows console with the terminal.
func (*TerminalFrame) Close ¶
func (f *TerminalFrame) Close()
Close is a no-op: the frame lives as long as its session and must never become "done" (the FrameManager would garbage-collect it from the stack).
func (*TerminalFrame) GetType ¶
func (f *TerminalFrame) GetType() vtui.FrameType
GetType returns TypeUser: the frame is neither a dialog nor a menu.
func (*TerminalFrame) ProcessKey ¶
func (f *TerminalFrame) ProcessKey(e *vtinput.InputEvent) bool
ProcessKey intercepts the host-level shortcuts (F2 demo window, Ctrl+Shift+V paste) and enqueues everything else to the terminal thread.
func (*TerminalFrame) ProcessMouse ¶
func (f *TerminalFrame) ProcessMouse(e *vtinput.InputEvent) bool
ProcessMouse forwards to the terminal element (which shifts the coordinates to terminal-relative cells before enqueueing).
func (*TerminalFrame) ResizeConsole ¶
func (f *TerminalFrame) ResizeConsole(w, h int)
ResizeConsole re-covers the whole console after a host resize.
func (*TerminalFrame) SetPosition ¶
func (f *TerminalFrame) SetPosition(x1, y1, x2, y2 int)
SetPosition moves the frame and the terminal element with it (a size change also notifies the session via TerminalUI.SetPosition).
func (*TerminalFrame) Show ¶
func (f *TerminalFrame) Show(scr *vtui.ScreenBuf)
Show lays out pending output (event-paced: cheap when not dirty) and paints the latest snapshot into the ScreenBuf, clipped to both the frame's rect and the snapshot dimensions — they can disagree for a frame or two right after a resize. The whole rect is always painted (snapshot first, terminal default background for the rest): the bottom frame is opaque, so nothing below has to show through.
type TerminalUI ¶
type TerminalUI struct {
vtui.ScreenObject
Session *Session
// OnDrop, if set, is called when EventChan is full and a non-mouse-move
// event is dropped. Mouse moves drop silently (the next move
// coalesces the position anyway).
OnDrop func(e *vtinput.InputEvent)
}
TerminalUI is the terminal as a vtui UIElement: a movable rectangle (X1..Y2, absolute vtui cell coordinates) hosting one Session. Input is only enqueued into the session's EventChan — translation and the (potentially blocking) child write happen on the terminal thread, never on the caller's.
func NewTerminalUI ¶
func NewTerminalUI(sess *Session) *TerminalUI
NewTerminalUI wraps a session in a UI element covering the whole terminal grid at the origin.
func (*TerminalUI) Enqueue ¶
func (t *TerminalUI) Enqueue(e *vtinput.InputEvent)
Enqueue delivers an event to the terminal thread. Non-blocking: a full queue drops the event. Never call while holding the terminal lock.
func (*TerminalUI) ProcessKey ¶
func (t *TerminalUI) ProcessKey(e *vtinput.InputEvent) bool
ProcessKey implements vtui.UIElement: the event is queued for the terminal thread and always consumed.
func (*TerminalUI) ProcessMouse ¶
func (t *TerminalUI) ProcessMouse(e *vtinput.InputEvent) bool
ProcessMouse implements vtui.UIElement. vtui coordinates are absolute; the terminal is a rectangle, so the event is shifted to terminal-relative cells before enqueueing.
func (*TerminalUI) ResizeConsole ¶
func (t *TerminalUI) ResizeConsole()
ResizeConsole implements vtui.UIElement: the session is asked (by message, not in place) to adopt the element's current size.
func (*TerminalUI) SetPosition ¶
func (t *TerminalUI) SetPosition(x1, y1, x2, y2 int)
SetPosition moves/resizes the element. A size change (not a bare move) also notifies the session.
func (*TerminalUI) Show ¶
func (t *TerminalUI) Show(scr *vtui.ScreenBuf)
Show is a placeholder: rendering the session's TextCell grid into the vtui ScreenBuf (TextCell -> CharInfo) is roadmap step 5. For now the terminal paints itself through the GPU renderer, so this only maintains the visibility flag.