twin

package module
v0.9.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 5, 2026 License: MIT Imports: 21 Imported by: 0

README

Linux CI Windows CI

Twin is a library for drawing to the terminal screen.

Write once, run anywhere your terminal does: Twin is tested on Linux, macOS and Windows.

Demo

ftop is built using twin:

ftop screenshot

Installation

go get github.com/walles/twin

Usage

  • Open a screen using twin.NewScreen() or one of its friends
  • Use screen.SetCell() to draw characters in an off-screen buffer
  • Use screen.Show() to send the buffer contents to the terminal
  • Listen to events on the screen.Events() channel, for keyboard, mouse and window resize
  • Close the screen when done using screen.Close()

Twin opens an alternate screen buffer that it draws into.

Making a new release

  1. git tag --annotate vX.Y.Z, note the leading v in the version number. Write something descriptive in the annotation message.
  2. git push --tags

TODO

Done

  • Add an ftop screenshot demonstrating what twin can do
  • Make Color implement stdlib's color.Color
  • Enable revive's "exported" rule and validate comments for our public surface
  • Get CI running for PRs and pushes to main, and add a CI status badge at the top of the README.
  • Review the API based on how it is being used

Documentation

Overview

Package twin provides Terminal Window Interaction

Index

Constants

This section is empty.

Variables

View Source
var ColorDefault = newColor(ColorCountDefault, 0)

ColorDefault is the terminal's own default foreground / background color, used when no explicit color has been set.

Functions

func Printable

func Printable(char rune) bool

Printable reports whether char should be rendered as-is rather than escaped, covering some cases that unicode.IsPrint() gets wrong for terminal output.

Types

type AttrMask

type AttrMask uint

AttrMask is a bitmask of text attributes (bold, blink, ...), combined with bitwise OR.

const (
	AttrBold AttrMask = 1 << iota
	AttrBlink
	AttrReverse
	AttrUnderline
	AttrDim
	AttrItalic
	AttrStrikeThrough
	AttrHidden
	AttrNone AttrMask = 0 // Normal text
)

AttrMask bit values.

type Color

type Color uint32

Color represents a terminal color. Create one using NewColor16(), NewColor256(), or NewColor24Bit(), or use ColorDefault.

func NewColor16

func NewColor16(colorNumber0to15 int) Color

NewColor16 creates a 4-bit ANSI color (16 colors) from a palette index 0-15.

Ref: https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit

func NewColor24Bit

func NewColor24Bit(red uint8, green uint8, blue uint8) Color

NewColor24Bit creates a 24-bit RGB color from its red, green and blue channels.

func NewColor256

func NewColor256(colorNumber uint8) Color

NewColor256 creates an 8-bit ANSI color (256 colors) from a palette index.

Ref: https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit

func NewColorHex

func NewColorHex(rgb uint32) Color

NewColorHex creates a 24-bit RGB color from a packed 0xRRGGBB value.

func (Color) Distance

func (color Color) Distance(other Color) float64

Distance approximates the perceptual difference between two colors, using the formula from https://www.compuphase.com/cmetric.htm, scaled to 0.0-1.0 where 1.0 is the distance between black and white.

Panics if either color is ColorDefault.

func (Color) Mix

func (color Color) Mix(other Color, weight float64) Color

Mix blends color and other, weighted 0.0 (all color) to 1.0 (all other).

Panics if either color is ColorDefault, or if weight is outside 0.0-1.0.

func (Color) RGBA

func (color Color) RGBA() (r, g, b, a uint32)

RGBA implements color.Color. All twin colors are fully opaque, so alpha is always 0xffff.

ColorDefault has no defined RGB value. Calling RGBA() on it panics; this is reserved / unspecified behavior and may change without a major release.

func (Color) String

func (color Color) String() string

type ColorCount

type ColorCount uint8

ColorCount represents the terminal's color capability, one of the ColorCount* constants.

const (
	// ColorCountDefault is no explicit color: the terminal's own default
	// foreground / background.
	ColorCountDefault ColorCount = iota

	// ColorCount8 is 3-bit ANSI color (8 colors):
	// https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit
	//
	// Note that this type is only used for output, on input we store 3 bit
	// colors as 4 bit colors since they map to the same values.
	ColorCount8

	// ColorCount16 is 4-bit ANSI color (16 colors):
	// https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit
	ColorCount16

	// ColorCount256 is 8-bit ANSI color (256 colors):
	// https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
	ColorCount256

	// ColorCount24bit is an RGB color:
	// https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit
	ColorCount24bit
)

type Event

type Event any

Event is the type of value received on a Screen's event channel.

EventRune, EventKeyCode and EventMouse can be constructed directly by embedding applications, to programmatically feed input into the screen.

Ref: https://github.com/walles/moor/pull/456

type EventExit

type EventExit struct {
}

EventExit is sent, and the application should exit, when we're unable to continue showing the screen.

Ref: https://github.com/walles/moor/issues/126

type EventKeyCode

type EventKeyCode struct {
	KeyCode KeyCode
}

EventKeyCode is sent when the user presses a non-printable key.

type EventMouse

type EventMouse struct {
	Buttons MouseButtonMask
}

EventMouse is sent on mouse wheel activity.

type EventResize

type EventResize struct {
}

EventResize is sent when the terminal window is resized. Query Screen.Size() after receiving this to get the new size.

type EventRune

type EventRune struct {
	Rune rune
}

EventRune is sent when the user types a printable rune.

type FakeScreen

type FakeScreen struct {
	// contains filtered or unexported fields
}

FakeScreen is an in-memory Screen implementation, needing no real terminal. Typically used for testing code that renders to a Screen, but also suitable for headless rendering in general.

Create one with NewFakeScreen(width, height) and hand it to the code under test. Let that code call SetCell(), Clear(), Show() etc. as it normally would, then call GetRow() or GetCell() afterwards to inspect what ended up on screen.

Events() always returns nil.

func NewFakeScreen

func NewFakeScreen(width int, height int) *FakeScreen

NewFakeScreen creates a FakeScreen of the given size. Initial contents is undefined.

func (*FakeScreen) Clear

func (screen *FakeScreen) Clear()

Clear erases all screen cells, replacing them with spaces in the default style.

func (*FakeScreen) Close

func (screen *FakeScreen) Close()

Close does nothing, since a FakeScreen owns no real terminal to restore.

func (*FakeScreen) Events

func (screen *FakeScreen) Events() chan Event

Events always returns nil, so anything reading from it will block forever.

func (*FakeScreen) GetCell

func (screen *FakeScreen) GetCell(column int, row int) StyledRune

GetCell returns the StyledRune at the given screen position.

Note that this does not read cells from a physical screen, since there is none, but rather from what was previously set using SetCell().

For out-of-bounds requests, a space with default style is returned.

func (*FakeScreen) GetRow

func (screen *FakeScreen) GetRow(row int) []StyledRune

GetRow returns the row's cells, skipping any cell hidden behind a preceding wide rune.

func (*FakeScreen) PauseAndCall

func (screen *FakeScreen) PauseAndCall(run func() error) error

PauseAndCall runs the given function and returns its result. A FakeScreen has no terminal state to pause and resume, so there is nothing else to do.

func (*FakeScreen) SetCell

func (screen *FakeScreen) SetCell(column int, row int, styledRune StyledRune) int

SetCell returns the width of the rune just added, in number of columns.

Note that if you set a wide rune (like '午') in one column, then whatever you put in the next column will be hidden by the wide rune. A wide rune in the last screen column will be replaced by a space, to prevent it from overflowing onto the next line.

func (*FakeScreen) SetProgress

func (screen *FakeScreen) SetProgress(state ProgressState, percent int)

SetProgress does nothing, since a FakeScreen has no terminal to show a progress bar in.

func (*FakeScreen) Show

func (screen *FakeScreen) Show()

Show does nothing, since a FakeScreen has no real terminal to render into.

func (*FakeScreen) ShowNLines

func (screen *FakeScreen) ShowNLines(int)

ShowNLines does nothing, since a FakeScreen has no real terminal to render into.

func (*FakeScreen) Size

func (screen *FakeScreen) Size() (width int, height int)

Size returns the width and height given to NewFakeScreen().

func (*FakeScreen) TerminalBackground

func (screen *FakeScreen) TerminalBackground() *Color

TerminalBackground always returns nil, since a FakeScreen has no real terminal to query.

type KeyCode

type KeyCode uint16

KeyCode identifies a non-printable key the user pressed

const (
	KeyEscape KeyCode = iota
	KeyEnter

	KeyBackspace
	KeyDelete

	KeyUp
	KeyDown
	KeyRight
	KeyLeft

	KeyAltUp
	KeyAltDown
	KeyAltRight
	KeyAltLeft

	KeyHome
	KeyEnd
	KeyPgUp
	KeyPgDown
)

KeyCode values for non-printable keys, sent via EventKeyCode

type Logger

type Logger interface {
	// Debug logs low-level diagnostic messages. This level is recommended for
	// messages that the user has to explicitly ask to see.
	Debug(message string)

	// Info logs high-level status messages. This level and up is recommended
	// for adding to panic reports.
	Info(message string)

	// Error logs a problem the user should be told about whether they asked for
	// it or not.
	Error(message string)
}

Logger receives twin's log messages. Implement it and pass it in Options.Logger to NewScreen to consume those messages.

type MouseButtonMask

type MouseButtonMask uint16

MouseButtonMask is a bitmask of the MouseWheel* constants, used in EventMouse.Buttons.

const (
	// MouseWheelUp is set in EventMouse.Buttons when the wheel scrolls up.
	MouseWheelUp MouseButtonMask = 1 << iota

	// MouseWheelDown is set in EventMouse.Buttons when the wheel scrolls down.
	MouseWheelDown

	// MouseWheelLeft is set in EventMouse.Buttons when the wheel scrolls left.
	MouseWheelLeft

	// MouseWheelRight is set in EventMouse.Buttons when the wheel scrolls right.
	MouseWheelRight
)

type MouseMode

type MouseMode int

MouseMode controls how mouse events are captured. See MouseModeAuto, MouseModeSelect and MouseModeScroll for the available behaviors.

const (
	// MouseModeAuto auto-detects whether to capture mouse events, based on
	// the terminal.
	MouseModeAuto MouseMode = iota

	// MouseModeSelect doesn't capture mouse events. This makes selecting with
	// the mouse work. On some terminals mouse scrolling will work using arrow
	// keys emulation, and on some not.
	MouseModeSelect

	// MouseModeScroll captures mouse events. This makes mouse scrolling work.
	// Special gymnastics will be required for marking with the mouse to copy
	// text.
	MouseModeScroll
)

type Options

type Options struct {
	// MouseMode controls how mouse events are captured. Leave as
	// MouseModeAuto to auto-detect based on the terminal.
	MouseMode MouseMode

	// TerminalColorCount overrides how many colors twin assumes the terminal
	// supports. Leave as ColorCountDefault to auto-detect from the
	// environment.
	TerminalColorCount ColorCount

	// Logger receives twin's own log messages. Leave nil to disable logging.
	Logger Logger
}

Options configures a new Screen, created with NewScreen.

The zero value auto-detects mouse mode and terminal color count from the environment, and disables twin's own logging.

type Progress

type Progress struct {
	State   ProgressState
	Percent int
}

Progress is a terminal progress bar's state and completion percentage.

Ref: https://rockorager.dev/misc/osc-9-4-progress-bars/

type ProgressState

type ProgressState int

ProgressState is a terminal progress bar's state, one of the ProgressState* constants.

const (
	// ProgressStateRemove hides the progress bar.
	ProgressStateRemove ProgressState = 0

	// ProgressStateSet shows the progress bar at Progress.Percent.
	ProgressStateSet ProgressState = 1

	// ProgressStateError shows the progress bar, in an error color, at
	// Progress.Percent.
	ProgressStateError ProgressState = 2

	// ProgressStateIndeterminate shows a busy progress bar with no known
	// percentage.
	ProgressStateIndeterminate ProgressState = 3

	// ProgressStatePause shows the progress bar, in a paused color, at
	// Progress.Percent.
	ProgressStatePause ProgressState = 4
)

ProgressState values. The numbers match the ones from https://rockorager.dev/misc/osc-9-4-progress-bars/.

type Screen

type Screen interface {
	// Close restores the terminal to normal state, must be called after you are
	// done with the screen returned by NewScreen().
	Close()

	// Erases all screen cells, replacing them with spaces in the default
	// style.
	Clear()

	// Returns the width of the rune just added, in number of columns.
	//
	// Note that if you set a wide rune (like '午') in one column, then whatever
	// you put in the next column will be hidden by the wide rune. A wide rune
	// in the last screen column will be replaced by a space, to prevent it from
	// overflowing onto the next line.
	SetCell(column int, row int, styledRune StyledRune) int

	// Returns the StyledRune at the given screen position.
	//
	// Note that this does not read cells from the physical screen, but rather
	// from what was previously set using SetCell().
	//
	// For out-of-bounds requests, a space with default style is returned.
	GetCell(column int, row int) StyledRune

	// Ask the terminal to show a progress bar
	//
	// Ref: https://rockorager.dev/misc/osc-9-4-progress-bars/
	SetProgress(state ProgressState, percent int)

	// Render our contents into the terminal window.
	//
	// The first call takes over the terminal: alternate screen, cursor hidden,
	// mouse tracked. Until then the user's own screen is left alone.
	//
	// Renders nothing while somebody else owns the terminal, meaning after
	// Close() or during PauseAndCall(); Ctrl-Z handling makes that possible
	// without your main loop asking for it.
	Show()

	// Can be called after Close()ing the screen to fake retaining its output.
	// Plain Show() is what you'd call during normal operation.
	//
	// Unlike Show(), this one never takes over the terminal; it prints where
	// the cursor already is.
	ShowNLines(lineCountToShow int)

	// Returns screen width and height.
	//
	// NOTE: Never cache this response! On window resizes you'll get an
	// EventResize on the Screen.Events channel, and this method will start
	// returning the new size instead.
	Size() (width int, height int)

	// The first call may delay up to 50ms while waiting for the terminal to
	// respond to a background color query. After that, it's instant.
	//
	// Can be nil if not (yet?) detected.
	TerminalBackground() *Color

	// This channel is what your main loop should be checking.
	Events() chan Event

	// Pause the screen, run the given function, then resume the screen. Blocks
	// until the function has completed and the screen has been resumed again.
	//
	// Error returns mean that either pausing failed or the run function failed.
	// If resuming fails, this method will panic.
	PauseAndCall(run func() error) error
}

Screen is the main interface for interacting with the terminal, created with NewScreen.

func NewScreen

func NewScreen(options Options) (Screen, error)

NewScreen creates a new Screen according to options. Passing the zero value Options{} auto-detects mouse mode and terminal color count, and disables twin's own logging.

The returned Screen requires Close() to be called after you are done with it, most likely somewhere in your shutdown code.

type Style

type Style struct {
	// contains filtered or unexported fields
}

Style is a foreground color, background color, underline color, and a set of text attributes (bold, italic, ...), applied together to a piece of text.

var StyleDefault Style

StyleDefault is the zero value Style: default foreground and background colors, no attributes, no hyperlink.

func (Style) Background

func (style Style) Background() Color

Background returns style's background color.

func (Style) Equal

func (style Style) Equal(other Style) bool

Equal reports whether style and other render identically: same colors, same attributes, and same hyperlink.

func (Style) Foreground

func (style Style) Foreground() Color

Foreground returns style's foreground color.

func (Style) HasAttr

func (style Style) HasAttr(attr AttrMask) bool

HasAttr reports whether attr is set on style.

func (Style) HyperlinkURL

func (style Style) HyperlinkURL() *string

HyperlinkURL returns the hyperlink URL if set, or nil otherwise.

func (Style) RenderUpdateFrom

func (style Style) RenderUpdateFrom(previous Style, terminalColorCount ColorCount) string

RenderUpdateFrom returns the ANSI escape sequence needed to switch terminal state from previous to style.

func (Style) String

func (style Style) String() string

func (Style) WithAttr

func (style Style) WithAttr(attr AttrMask) Style

WithAttr returns a copy of style with attr added. AttrBold and AttrDim are mutually exclusive, so adding one clears the other.

func (Style) WithBackground

func (style Style) WithBackground(color Color) Style

WithBackground returns a copy of style with its background color set to color.

func (Style) WithForeground

func (style Style) WithForeground(color Color) Style

WithForeground returns a copy of style with its foreground color set to color.

func (style Style) WithHyperlink(hyperlinkURL *string) Style

WithHyperlink returns a copy of style with its hyperlink URL set. Call with nil to remove the link.

func (Style) WithUnderlineColor

func (style Style) WithUnderlineColor(color Color) Style

WithUnderlineColor returns a copy of style with its underline color set to color.

func (Style) WithoutAttr

func (style Style) WithoutAttr(attr AttrMask) Style

WithoutAttr returns a copy of style with attr removed.

type StyledRune

type StyledRune struct {
	Rune  rune
	Style Style
}

StyledRune is a rune with a style to be written to a one or more cells on the screen. Note that a StyledRune may use more than one cell on the screen ('午' for example).

func (StyledRune) Equal

func (styledRune StyledRune) Equal(other StyledRune) bool

Equal reports whether styledRune and other have the same rune and style.

func (StyledRune) String

func (styledRune StyledRune) String() string

func (StyledRune) Width

func (styledRune StyledRune) Width() int

Width returns how many screen cells this rune will cover. Most runes cover one, but some like '午' will cover two.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL