etui/terminal

Types

Where the hardware cursor should be after a frame is drawn.

pub type Cursor {
  CursorUntouched
  CursorHidden
  CursorShown(geometry.Position)
}

Constructors

  • CursorUntouched

    Leave the cursor wherever it was. The default, and what a full-screen app wants: it hides the cursor once at start-up and never thinks about it again.

  • CursorHidden

    Hide the cursor for this frame.

  • CursorShown(geometry.Position)

    Show the cursor at a 0-based cell position. Text inputs want this so the insertion point is where the terminal actually blinks.

One frame under construction: the area it covers, the cells drawn into it so far, and where the cursor should end up.

pub type Frame {
  Frame(
    area: geometry.Rect,
    buffer: buffer.Buffer,
    cursor: Cursor,
  )
}

Constructors

An open terminal: the backend, its state, and what was last drawn.

pub opaque type Terminal(backend_state)

How much of the terminal an app takes over.

ViewportWhat it usesWhat happens to the scrollback
Fullscreenthe whole terminal, on the alternate screenuntouched, and the shell reappears on exit
Inline(n)the bottom n rows of the normal screenkept, and the app’s last frame stays above the prompt
Fixed(rect)one rect of the normal screeneverything outside the rect is left alone

Inline is how a build tool or an installer draws a progress area without taking the screen away from you: the rows above it keep scrolling, and what it drew is still on screen after it exits.

pub type Viewport {
  Fullscreen
  Inline(height: Int)
  Fixed(area: geometry.Rect)
}

Constructors

Values

pub fn area(term: Terminal(backend_state)) -> geometry.Rect

The area a frame will be given, which is the viewport rather than always the whole screen.

pub fn close_viewport(
  vp: Viewport,
  area: geometry.Rect,
) -> List(backend.RenderOp)

Ops that hand the terminal back, once the app is done.

Public for the same reason as frame_ops: what a viewport does on the way out is worth being able to check without a terminal to do it to, and for an inline app it is the visible difference from a full-screen one.

pub fn draw(
  term: Terminal(backend_state),
  build: fn(Frame) -> Frame,
) -> Result(Terminal(backend_state), backend.Error)

Draw one frame. build is handed an empty frame the size of the screen and returns it filled in.

pub fn draw_widget(
  frame: Frame,
  area: geometry.Rect,
  w: fn(buffer.Buffer, geometry.Rect) -> buffer.Buffer,
) -> Frame

Draw a widget into part of the frame.

pub fn draw_with(
  term: Terminal(backend_state),
  build: fn(Frame) -> #(Frame, a),
) -> Result(#(Terminal(backend_state), a), backend.Error)

Draw one frame and carry a value back out of it.

A frame closure works out things the rest of your program wants: the rects the layout produced, so a click can be matched against them, or the state a list settled on once it knew how tall it was. Without a way out those die inside the closure and have to be computed a second time.

let assert Ok(#(term, panes)) =
  terminal.draw_with(term, fn(frame) {
    let panes = geometry.split_h(frame.area, [Fill, Fill])
    #(draw_panes(frame, panes), panes)
  })
// `panes` is now available for hit-testing the next mouse event
pub fn frame_ops(
  prev: buffer.Buffer,
  curr: buffer.Buffer,
  first_frame: Bool,
  cur: Cursor,
  clear_first: Bool,
) -> List(backend.RenderOp)

The render ops that take the terminal from prev to curr.

A first frame, at start-up or after a resize, repaints everything: what the terminal is showing is unknown, so there is nothing to diff against. Every frame after that emits only the cells that changed.

A frame that emits anything is wrapped in synchronized output, so the terminal shows the old frame until the whole new one has arrived.

Public because it is worth being able to check what a frame will emit without a terminal to emit it into, which is how the diffing and cursor rules are tested. draw is what an app calls.

pub fn hide_cursor(frame: Frame) -> Frame

Hide the cursor when this frame is drawn.

pub fn new(
  b: backend.Backend(backend_state),
) -> Result(Terminal(backend_state), backend.Error)

Open a terminal that takes over the whole screen.

pub fn new_with_viewport(
  b: backend.Backend(backend_state),
  vp: Viewport,
) -> Result(Terminal(backend_state), backend.Error)

Open a terminal that uses only part of the screen.

// A five-row progress area under whatever the shell has already printed
let assert Ok(term) = terminal.new_with_viewport(default.new(), terminal.Inline(5))
pub fn open_viewport(vp: Viewport) -> List(backend.RenderOp)

Ops that make room for the viewport before the first frame.

An inline viewport prints its own height in newlines, which scrolls whatever was on screen up and leaves the bottom rows blank for the app. Without it the first frame would draw over the last lines of output.

Public alongside close_viewport; see there.

pub fn poll(
  term: Terminal(backend_state),
  timeout_ms: Int,
) -> Result(
  #(backend.InputEvent, Terminal(backend_state)),
  backend.Error,
)

Wait up to timeout_ms for an event.

A resize is reported like any other event, and also resets the terminal’s idea of what is on screen, so the next draw repaints at the new size.

pub fn restore(term: Terminal(backend_state)) -> Nil

Leave the alternate screen, restore the cursor and hand the terminal back.

pub fn set_cursor(frame: Frame, pos: geometry.Position) -> Frame

Put the cursor at pos when this frame is drawn.

pub fn viewport(term: Terminal(backend_state)) -> Viewport

The viewport this terminal was opened with.

pub fn with_buffer(frame: Frame, buf: buffer.Buffer) -> Frame

Replace the frame’s buffer, for code that renders by returning a buffer rather than by applying widgets.

✨ Search Document