Documentation
¶
Overview ¶
Package screen wraps a terminal emulator: the pty writes into it, and it renders the resulting screen for display.
This replaces the earlier "filter the escape sequences gocui cannot render" approach, which could not work: a themed shell prompt redraws itself in place (cursor up, erase, rewrite), and an append-only view stacks every redraw instead of overwriting it. Measured on a real zsh startup: 5 ESC[A, 4 ESC[J and 16 CR before the user types anything, producing four stacked prompts. See docs/adr/0001-rendu-ansi-et-clavier.md.
Index ¶
- type MouseMode
- type Screen
- func (s *Screen) ActivityPending() bool
- func (s *Screen) ApplicationCursorKeys() bool
- func (s *Screen) BellPending() bool
- func (s *Screen) ClearActivity()
- func (s *Screen) ClearBell()
- func (s *Screen) Close() error
- func (s *Screen) CursorPosition() (x, y int)
- func (s *Screen) CursorVisible() bool
- func (s *Screen) Find(pattern string) []int
- func (s *Screen) IsAltScreen() bool
- func (s *Screen) LastCommandExit() (code int, hasCode bool, seq int64, ok bool)
- func (s *Screen) LastCommandOutputRange() (from, to int, ok bool)
- func (s *Screen) MouseTracking() (MouseMode, bool)
- func (s *Screen) PlainTail(n int) string
- func (s *Screen) PromptMarks() []int
- func (s *Screen) Read(p []byte) (int, error)
- func (s *Screen) Render() string
- func (s *Screen) RenderAt(offset int, highlight string) string
- func (s *Screen) RenderAtSelection(offset, fromAbs, toAbs int) string
- func (s *Screen) Resize(cols, rows int)
- func (s *Screen) ScrollbackLen() int
- func (s *Screen) Size() (cols, rows int)
- func (s *Screen) TextRange(fromAbs, toAbs int) string
- func (s *Screen) Title() string
- func (s *Screen) Write(p []byte) (int, error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type MouseMode ¶
type MouseMode int
MouseMode is how much of the mouse an application asked to be told about. The values are the DECSET numbers themselves, so a reader can match them against any terminal reference without a translation table.
const ( // MouseOff means the application never asked; the mouse stays lazyshell's. MouseOff MouseMode = 0 // MouseX10 (DECSET 9) reports button presses only, no releases. MouseX10 MouseMode = 9 // MouseNormal (DECSET 1000) reports presses and releases. MouseNormal MouseMode = 1000 // MouseButtonEvent (DECSET 1002) adds motion while a button is held. MouseButtonEvent MouseMode = 1002 // MouseAnyEvent (DECSET 1003) adds motion with no button held. MouseAnyEvent MouseMode = 1003 )
The mouse tracking modes lazyshell honours. Modes 1005/1015/1016 (alternate encodings) are deliberately absent: SGR is the one every modern application asks for alongside them, so answering in SGR whenever 1006 is set covers them, and inventing partial support for the others would be worse than having none.
type Screen ¶
type Screen struct {
// contains filtered or unexported fields
}
Screen is a terminal emulator safe for concurrent use: the session's drain goroutine writes into it while the render loop reads from it.
func New ¶
New returns a Screen of the given size, in cells, with the emulator's default scrollback size (vt.DefaultScrollbackSize).
func NewWithScrollback ¶
NewWithScrollback is New with an explicit scrollback size, in lines — pkg/config's ScrollbackSize, threaded through by pkg/session.Manager.
func (*Screen) ActivityPending ¶
ActivityPending reports whether output has been received since the last ClearActivity. Like BellPending, a latch rather than an event: a session that produced output while it was not on screen must still be able to say so when the user comes back to the list.
func (*Screen) ApplicationCursorKeys ¶
ApplicationCursorKeys reports whether DECCKM is set. When it is, the arrow and Home/End keys must be sent as SS3 sequences (ESC O A) rather than CSI ones (ESC [ A) — less and several full-screen applications only accept the former. Consumed by pkg/keys.TranslateWithMode.
func (*Screen) BellPending ¶
BellPending reports whether a BEL has been received since the last ClearBell. It is a latch, not an event: a session that rang while it was not on screen must still be able to say so when the user comes back to it.
func (*Screen) ClearActivity ¶
func (s *Screen) ClearActivity()
ClearActivity acknowledges pending activity.
func (*Screen) Close ¶
Close shuts the emulator down, which unblocks any pending Read with io.EOF. It is the only way to release a goroutine parked in Read once the session is done: closing the pty's file descriptor does not affect this in-memory pipe.
func (*Screen) CursorPosition ¶
CursorPosition is where the emulated cursor sits, in cells, relative to the top-left of the visible screen. Meaningful for display only while the live screen is shown: RenderAt with a non-zero offset shows history the cursor is not in.
func (*Screen) CursorVisible ¶
CursorVisible reports whether the application wants the cursor drawn — full screen applications hide it while they redraw (DECTCEM, ESC[?25l). vt tracks this internally but exposes no getter, hence the callback in NewWithScrollback.
func (*Screen) Find ¶
Find returns the absolute line indices whose text contains pattern (a case-insensitive substring match): scrollback lines first, oldest to newest, then the still-live rows. Absolute index i is the line RenderAt puts at the top of its window when called with offset = ScrollbackLen() - i — that is the contract callers use to scroll to a result. Returns nil for an empty pattern or no match.
func (*Screen) IsAltScreen ¶
IsAltScreen reports whether a full-screen application (vim, htop, less) is currently in control.
func (*Screen) LastCommandExit ¶
LastCommandExit returns the most recently finished command's exit code, whether the shell actually reported one, and seq — a value that changes once per finished command, letting a caller polling on a tick detect "a new command just finished" rather than re-reading a stale code. ok is false until the first command has finished.
func (*Screen) LastCommandOutputRange ¶
LastCommandOutputRange returns the [from, to] range (Find's contract) of the most recently finished command's output. ok is false when no command has completed yet, or when the start of its output has since scrolled out of the scrollback.
func (*Screen) MouseTracking ¶
MouseTracking reports whether the application running in this session asked to receive mouse events itself, and in which encoding to send them: the tracking mode it last set, and whether it also asked for the SGR form (DECSET 1006) rather than the cramped historical one. MouseOff means it never asked — a shell or an AI agent CLI never does — and lazyshell keeps the mouse for its own panels. Consumed by pkg/gui's forwardMouseToApp.
func (*Screen) PlainTail ¶
PlainTail returns the plain text (no SGR) of the n lines ending at the cursor's current row, oldest first, joined by "\n" — pkg/agent's manifest evaluator reads this instead of Render/RenderAt because a regex has no business seeing color codes. Bounded by the cursor rather than by the bottom of the fixed-size screen buffer: on a screen that has not filled yet (the common case — a shell prompt a few lines in), the rows below the cursor are simply unwritten, and a literal "last n rows of the screen" would return blank lines instead of what was actually just printed. n is clamped to what is available; n <= 0 returns "".
func (*Screen) PromptMarks ¶
PromptMarks returns the absolute index (Find's contract) of every prompt start still reachable in the scrollback, oldest first. A mark whose line has since been evicted is silently dropped.
func (*Screen) Read ¶
Read returns what the emulator answers back to the application: terminal capability queries, cursor position reports... These must be written to the pty, otherwise a shell that asks a question waits for an answer that never comes — and the stray bytes end up displayed instead.
Deliberately not guarded by mu: the underlying read blocks on an internal pipe until there is something to answer, which is most of the time (replies only happen for DA/CPR/OSC-colour/focus/mouse queries). Holding the lock for that unbounded wait would block every Write and Render call for as long as the session runs, and Write itself sometimes writes a reply synchronously into the same pipe — a caller looping on Read while holding the lock would deadlock its own Write. This mirrors vt.SafeEmulator.Read, the only method its own concurrency wrapper also leaves unlocked, for the same reason.
func (*Screen) Render ¶
Render returns the visible screen, with SGR sequences for colours and attributes. Its size is bounded by the terminal geometry, so the cost of a redraw no longer grows with the amount of output — which is what used to freeze the UI on a chatty session.
func (*Screen) RenderAt ¶
RenderAt returns the screen as it looked offset lines back from the live bottom, in the same SGR-encoded shape as Render. offset <= 0 is the live view (identical to Render); offset is clamped to ScrollbackLen(), so scrolling past the oldest history just stops there instead of erroring.
highlight, if non-empty, marks every case-insensitive occurrence of that substring in reverse video — the scrollback-search feature's rendering half (Find is the other half, in search.go). Passing "" skips this entirely and, combined with offset <= 0, keeps the original fast path: the emulator's own Render, with no line-by-line reconstruction.
The emulator has no built-in scroll viewport — Render always shows the live screen — so any other case rebuilds the requested window itself via linesAt: scrolled-off rows come from Scrollback().Line, still-live rows are reconstructed cell by cell via CellAt (bounded by the panel's height, so this stays cheap), and the combined slice is rendered with uv.Lines.Render, the same styling path vt.Emulator.Render uses internally.
func (*Screen) RenderAtSelection ¶
RenderAtSelection is RenderAt's copy-mode sibling: the same offset-lines- back window, but with every full line whose absolute index (Find's contract) falls in [fromAbs, toAbs] marked in reverse video, instead of highlighting a substring match. fromAbs/toAbs may be given in either order.
Copy-mode selects whole lines, never a column range, so this needs none of highlightLine's per-cell text matching — selectLine below just reverses every cell of a line that is in range.
func (*Screen) Resize ¶
Resize changes the emulated geometry. The caller is responsible for calling pty.Setsize as well, so the shell learns about it too.
func (*Screen) ScrollbackLen ¶
ScrollbackLen is the number of lines that have scrolled off the top.
func (*Screen) Size ¶
Size reports the emulated geometry last set by Resize (or New's initial size). Render trims trailing blank lines, so it cannot answer "how many rows does the emulator think it has" — this can, which is what callers actually sizing a pty or a view against it need.
func (*Screen) TextRange ¶
TextRange returns the plain text (no SGR) of every line whose absolute index (Find's contract) falls in [fromAbs, toAbs], oldest first, joined by "\n" — copy-mode's yank and the scrollback export both build on this. fromAbs/toAbs may be given in either order; both are clamped to the addressable range, so a selection made against a screen that has since grown or shrunk (a resize, more output arriving) never panics.