TermBuf::Terminal
Inherits TermBuf::Drawing < Reference < Object
Stability: stable — changes only in a major release.
The terminal, as an application talks to it.
One fibre owns the buffer. Every drawing method builds a command and sends it there, so ordering is total and nothing needs locking. A command that has something to report — a paint, a shutdown — carries a reply channel and the caller waits on it; the rest are fire and forget.
Input is Input::Stream's: one fibre reads the device, in an execution
context of its own when the terminal is a real one because a blocking read
would otherwise stall every fibre sharing that thread, and a second turns
what it read into the events on #events.
Whatever happens, the terminal is given back: the owning fibre restores it
on the way out, signal handlers restore it before dying, and at_exit
catches anything that got past both.
Constants
Deep enough that a full redraw does not stall the fibre sending it, shallow enough to be backpressure rather than an unbounded queue.
The name the cursor shape is registered under, so that asking for a different one replaces the mode rather than stacking a second one.
DECSCUSR's own reset, which gives the shape back to whatever the terminal was configured for.
The window title saved on the terminal's own stack. Pushed the first
time a title is set and popped by Tty#leave, so a program that changed
the title leaves the one it found behind — and a takeover after a
suspend saves it again.
CSI 22 ; 0 t pushes the icon name and the window title, CSI 23 ; 0 t
pops them. A terminal with Capability::Titles and no stack ignores
both and the title simply stays changed, which is where a program that
never pushed ends up anyway.
Constructors
The device is the one thing a terminal cannot be built without, so it stays positional; everything else is named. There are ten of them and most default, which is exactly the shape that reads wrong positionally.
Detects what the terminal can do, takes it over, and starts running.
The block form is the one to reach for: it gives the terminal back even when the body raises, which no amount of care in the body can guarantee on its own.
Class methods
Instance methods
Asks for an Events::Timer in span from now, and returns the nonce
that will name it.
The tick travels the input channel, so it is ordered against everything the terminal said before it was armed rather than racing it. It is a floor and not a promise: the event arrives no sooner than span, and no sooner than the application drains what is queued ahead of it.
Withdraws the timer nonce names. Nothing is delivered for it, even when it is called after the timer has already gone off.
What the terminal was found to be able to do. The encoder emits nothing that is not in here.
Whether to write the cell after a glyph that may have painted outside its
own columns. See Painter#clear_overhang?, which this is.
On unless the application says otherwise: every terminal measured draws over a neighbouring cell without repainting it, so ink left there stays until something writes that cell again.
Whether to write the cell after a glyph that may have painted outside its
own columns. See Painter#clear_overhang?, which this is.
On unless the application says otherwise: every terminal measured draws over a neighbouring cell without repainting it, so ink left there stays until something writes that cell again.
The system clipboard, written through the terminal with OSC 52.
A copy goes out in order with the frames around it, the way a colour
change does. See Clipboard for what a terminal will take and for why
nothing comes back to say whether it took it.
Restores the terminal and stops. Safe to call more than once, and safe to call from an exception handler.
The terminal's own colours: the defaults, the cursor, and the palette.
Changes go out in order with the frames around them. See ColorStack for
why every one of them needs Capability::KittyColorStack.
A cursor over rect, keeping scrollback rows of what scrolls off it.
The cursor streamed output goes to, covering the whole screen.
cursor.io is an IO, so puts, print, printf, and anything else
that writes to one can be pointed at the screen.
Made on first use rather than in the constructor, which would hand a half-built terminal to something that keeps hold of it.
Whether the cursor shape asked for blinks. On by default, which is what DECSCUSR's odd-numbered parameters mean and what most terminals are configured for.
CursorShape::Default has a blink of its own — it is the terminal's
setting, blink included — so this says nothing about it until some other
shape is asked for.
What the terminal is drawing its own cursor as, as far as it was asked.
CursorShape::Default until something else is asked for, and again once
the terminal has been given its own setting back.
Asks the terminal to draw its own cursor as shape.
terminal.cursor_shape = :bar
terminal.cursor_blink = false
terminal.cursor_shape = TermBuf::CursorShape::Default
DECSCUSR, CSI Ps SP q, and it goes through the mode registry rather
than a bare write: #close sends CSI 0 SP q and the person gets the
cursor their terminal was configured for back, and a takeover after a
suspend asks for the shape again. Asking for a second shape replaces the
first rather than stacking on it.
Whether the shape blinks is #cursor_blink=, and a setter is where it
has to live: Crystal allows an assignment method one argument, so the
pair cannot be one call. Each of them re-asks with the other's current
value, so the order they are set in does not matter.
Does nothing without Capability::CursorShape. This is the terminal's
own cursor, not one of the buffer's — see #hardware_cursor= for which
of those it follows.
Turns mode off and forgets it, so a later takeover does not bring it back. A mode that was never enabled is nothing to turn off.
Turns mode on, and keeps it on across a suspend and resume.
See Tty::Mode for what a mode is and Tty#enable for what enabling one
twice does. Before #start the mode is only recorded, and goes out with
the rest of the takeover; afterwards it goes through the owning fibre,
since writing to the device from anywhere else would land in the middle
of a frame.
See Decoder::ESCAPE_TIMEOUT. Worth raising over a slow link, where a
sequence can take longer than that to arrive in full.
See Decoder::ESCAPE_TIMEOUT. Worth raising over a slow link, where a
sequence can take longer than that to arrive in full.
Says that a reply beginning with prefix and ending with terminator is
expected, so it arrives as an Events::Response rather than as input.
An application that wants the reply as something more than its bytes can
register with Input::Patterns directly and return an event of its own.
Stops running handler on a resize, and says whether it was registered.
Stops expecting pattern, so sequences matching it are input again.
Which cursor the terminal's own cursor follows, or nil while it is
hidden.
Hidden is the default, which is what a full screen application wants: a cursor blinking wherever the last run of text ended is a distraction. An application with somewhere for someone to type points this at the cursor they are typing into, and every paint puts the terminal's cursor back there afterwards.
Which cursor the terminal's own cursor follows, or nil while it is
hidden.
Hidden is the default, which is what a full screen application wants: a cursor blinking wherever the last run of text ended is a distraction. An application with somewhere for someone to type points this at the cursor they are typing into, and every paint puts the terminal's cursor back there afterwards.
Yields what sits at the buffer cell (x, y), or nil when that is off
the screen. See Buffer#hit.
The read goes through #sync, so it sees the buffer between frames
rather than part way through one, and the block runs on the fibre that
owns it — keep it short, since the frame after it is waiting.
terminal.hit column, row do |hit|
next unless hit
terminal.write 0, 0, "clicked #{hit.text}"
end
Coordinates are 0-based buffer cells. An SGR mouse report numbers its columns and rows from one; subtracting that is the mouse decoder's job, not this method's.
The images on screen.
They are drawn over the cells after each frame rather than into them, so
text written where one sits gets both. See ImageStore.
The input side: the fibres that read the device, the decoder they feed, and the sequence patterns the application registered.
Sends command to the fibre that owns the buffer, which is what every drawing method here does. A closed terminal drops it rather than raising: there is nothing left to draw on.
How many bytes the last paint sent. The point of the buffer is that a frame costs a diff rather than a screenful, and this is how an application checks that it is getting one.
Interns a hyperlink and returns the id a Style carries it by.
link = terminal.link "https://example.com"
screen.write 0, 0, "example", Style::DEFAULT.linked(link)
Safe to call from any fibre. Nothing is emitted for it on a terminal
without Capability::Osc8Links; the text is drawn and the link is not.
Registers handler, run when the screen changes size: after the grids
have been resized and before Events::Resize is sent.
This is where an application puts its layout. A Region the application
made covers a pane it chose, and the driver has no idea what that pane
was meant to be a fraction or an edge of, so it does not move it — see
Region#bounds=. Rather than repeating the arithmetic at every
Events::Resize, work it out once here:
terminal.on_resize do |size|
status.region.bounds = Rect.new 0, size.rows - 1, size.columns, 1
log.region.bounds = Rect.new 0, 0, size.columns, size.rows - 1
end
Handlers run in the order they were registered, on the fibre that owns
the buffer. That fibre is the one servicing commands, so a handler must
not call back into #batch, #paint, or #sync; moving regions and
recomputing rectangles is what it is for. Anything it raises arrives as
an Events::Failure and the remaining handlers still run.
Returns the handler, which #forget_resize takes back.
Registers handler, run when the screen changes size: after the grids
have been resized and before Events::Resize is sent.
This is where an application puts its layout. A Region the application
made covers a pane it chose, and the driver has no idea what that pane
was meant to be a fraction or an edge of, so it does not move it — see
Region#bounds=. Rather than repeating the arithmetic at every
Events::Resize, work it out once here:
terminal.on_resize do |size|
status.region.bounds = Rect.new 0, size.rows - 1, size.columns, 1
log.region.bounds = Rect.new 0, 0, size.columns, size.rows - 1
end
Handlers run in the order they were registered, on the fibre that owns
the buffer. That fibre is the one servicing commands, so a handler must
not call back into #batch, #paint, or #sync; moving regions and
recomputing rectangles is what it is for. Anything it raises arrives as
an Events::Failure and the remaining handlers still run.
Returns the handler, which #forget_resize takes back.
Rewrites every cell, whatever the buffer thinks the terminal is showing. For after a resize, a suspend, or anything else that leaves the screen in a state the buffer cannot know about.
See Decoder::PASTE_STALL. Worth raising for an application expecting
very large pastes over a very slow link.
See Decoder::PASTE_STALL. Worth raising for an application expecting
very large pastes over a very slow link.
What the buffer measures clusters with, so a View cuts writes where the
cells will fall.
How often a window resize is acted on, at most.
Dragging a window corner produces a SIGWINCH for every intermediate
geometry the window passes through, and acting on each one means a full
repaint per pixel of travel. The first resize of a burst goes through at
once, since a window that has been still is one whose new size is worth
showing immediately; everything after it inside the interval collapses
into a single further resize taken on the geometry the window finished
at.
Zero turns the limiting off, and every resize is acted on as it arrives.
How often a window resize is acted on, at most.
Dragging a window corner produces a SIGWINCH for every intermediate
geometry the window passes through, and acting on each one means a full
repaint per pixel of travel. The first resize of a burst goes through at
once, since a window that has been still is one whose new size is worth
showing immediately; everything after it inside the interval collapses
into a single further resize taken on the geometry the window finished
at.
Zero turns the limiting off, and every resize is acted on as it arrives.
Gives the terminal back without going through the owning fibre. What the
signal handlers and at_exit call, since by then there may be no fibre
left to ask.
What the operating system says, and what this terminal does about it.
SIGTERM, SIGINT and SIGHUP give the terminal back and re-raise
themselves; SIGWINCH becomes an Events::Resize; SIGTSTP and
SIGCONT hand the screen over and take it back. An application that
wants a first interrupt to ask rather than kill says so here:
terminal.signals.mode Signal::INT, TermBuf::Input::Signals::Mode::WarnThenExit
and then draws something on the Events::Signal that arrives, calling
Input::Signals#reset_count if the person decides to stay.
How big the terminal was when it was last looked at. Updated by the owning fibre on a resize, so this is a snapshot rather than a promise.
The chain every event walks before the application sees it.
Two stages are built during #start:
:resizeconsumes theEvents::SignalforSIGWINCHand answers it withEvents::Resize, since the buffer has to be resized and marked for redraw before anyone is told, and a window change should be one event rather than two.:signalspasses everything through. It is a placeholder, there so that an application with a policy about signals has somewhere named to put it: replace that one entry and leave the rest of the chain alone.
See Input::Stage for what a stage may do with an event and
Input::Stages for changing the chain while events are flowing: #push
adds one, #replace reorders or removes.
terminal.stages.replace terminal.stages.to_a.reverse
Starts painting automatically at up to fps frames a second, coalescing whatever was drawn in between. A paint with nothing to do costs nothing, so this is safe to leave running.
Off by default: an application that draws in response to input knows better than a timer when a frame is worth sending.
Runs action against the buffer on the owning fibre, and waits.
The escape hatch for anything the drawing API does not cover, and the only safe way to read the buffer: doing it from another fibre would race with whoever is drawing.
The window title this terminal was last asked for, or nil when it has
not been asked for one — or was asked to give the original back.
Sets the window title, or gives back the one the terminal started with.
terminal.title = "termbuf — #{path}"
terminal.title = nil # whatever it said before
The title goes out in order with the frames around it, the way a colour
or a clipboard write does. The terminal is asked to save the title it
had the first time one is set and #close asks for it back, so a
program that changed the title does not leave its own behind in a tab
somebody goes on using.
Does nothing at all without Capability::Titles: a terminal that does
not take the sequence prints it.
Whether to give the screen back and say so on stderr the first time a cluster this terminal will misplace is drawn.
An application that handles Events::Warning itself will want this off:
the event arrives either way, and being pulled out of the alternate
screen mid-frame is worse than a message it chose where to put. On by
default, since a terminal quietly misrendering is the thing being
guarded against.
Whether to give the screen back and say so on stderr the first time a cluster this terminal will misplace is drawn.
An application that handles Events::Warning itself will want this off:
the event arrives either way, and being pulled out of the alternate
screen mid-frame is worse than a message it chose where to put. On by
default, since a terminal quietly misrendering is the thing being
guarded against.
What the width probe asked and what came back, for diagnostics. Empty when it did not run.
How this terminal measures a grapheme cluster, which is what the buffer
writes with. Measured at startup unless TERMBUF_WIDTHS=off or the
terminal declined to answer.
Tells the terminal its window changed size.
This is what the SIGWINCH handler calls. An application that does not
get the signal — one driving a pty whose size it sets itself, say — calls
it in the signal's place, and passes the size rather than leaving it to
be read off the terminal.
Rate limited by #resize_interval. The leading edge is issued straight
away; a call inside the interval instead leaves a single fibre to sleep
out the remainder and issue one resize at the end of it, reading the size
then so that the repaint lands on the geometry the window settled at
rather than one it passed through. Further calls inside the interval do
no more than replace what that fibre will use.