Open-Source Wikis

/

Bubble Tea

/

Systems

/

Input handling

charmbracelet/bubbletea

Input handling

Active contributors: aymanbagabas, meowgorithm

Purpose

The input subsystem turns raw bytes from stdin or the controlling TTY into typed tea.Msgs. It owns the cancel reader, the escape sequence parser, and the translation layer between ultraviolet's low-level events and Bubble Tea's exported message types.

Directory layout

tty.go         ← cancelReader, readLoop, OpenTTY, checkResize
tty_unix.go    ← raw mode + GetWinsize on Unix
tty_windows.go ← console mode flags on Windows
input.go       ← translateInputEvent
key.go         ← Key, KeyPressMsg, KeyReleaseMsg, key constants
keyboard.go    ← KeyboardEnhancementsMsg + flag helpers
mod.go         ← KeyMod constants
mouse.go       ← Mouse, MouseClickMsg, MouseReleaseMsg, MouseWheelMsg, MouseMotionMsg
paste.go       ← PasteMsg, PasteStartMsg, PasteEndMsg
focus.go       ← FocusMsg, BlurMsg
clipboard.go   ← ClipboardMsg + OSC 52 helpers
xterm.go       ← TerminalVersionMsg + RequestTerminalVersion
termcap.go     ← CapabilityMsg + RequestCapability

Key abstractions

Type / func File Purpose
Program.initInputReader tty.go Wraps stdin in a cancelreader, creates a uv.TerminalReader, starts readLoop.
Program.readLoop tty.go Calls inputScanner.StreamEvents and forwards events into p.msgs.
OpenTTY tty.go Public helper that returns the controlling tty's read/write file pair.
Program.translateInputEvent input.go Switches on uv.Event type and returns the matching tea message.
Key (struct) key.go Key code + text + base + shifted code + modifiers + repeat flag.
KeyMsg (interface) key.go Implemented by KeyPressMsg and KeyReleaseMsg.
Mouse (struct), MouseMsg (interface) mouse.go Coordinates, button, modifier mask, plus typed sub-messages.
KeyboardEnhancementsMsg keyboard.go Reply that says which Kitty/modify-other-keys features are active.
ClipboardMsg clipboard.go Result of an OSC 52 read.

How it works

graph LR
    Stdin["stdin / TTY file"] -->|bytes| CR["uv.NewCancelReader"]
    CR --> TR["uv.NewTerminalReader"]
    TR -->|StreamEvents| L["Program.readLoop"]
    L -->|uv.Event| Translate["translateInputEvent"]
    Translate -->|tea.Msg| Loop["Program.eventLoop"]
    subgraph "Synthetic events"
      Sig["handleSignals → InterruptMsg / QuitMsg"]
      Resize["handleResize → WindowSizeMsg"]
    end
    Sig --> Loop
    Resize --> Loop

Raw mode

tty_unix.go calls term.MakeRaw on the input fd. On Windows, tty_windows.go configures the console using golang.org/x/sys/windows. The previous state is saved in previousTtyInputState / previousOutputState so restoreInput can put the terminal back when the program exits.

Cancel reader

Reading from stdin without a way to cancel is a classic Go problem (a blocked read can hold a goroutine forever). The runtime uses github.com/muesli/cancelreader to wrap the file. On shutdown, cancelReader.Cancel() interrupts the in-flight read; waitForReadLoop then waits up to 500 ms for the goroutine to drain.

uv.TerminalReader

The actual escape-sequence parser lives in github.com/charmbracelet/ultraviolet. It produces typed events for keys, mouse, focus, paste, clipboard, mode reports, capability replies, and more. The reader respects the TERM env variable to pick a key map.

Translation

translateInputEvent is a pure switch:

case uv.KeyPressEvent:
    return KeyPressMsg(e)
case uv.MouseClickEvent:
    return MouseClickMsg(e)
case uv.PasteEvent:
    return PasteMsg(e)

Adding a new input type means adding both an ultraviolet event and a corresponding case here.

Key disambiguation

In v2 a Key carries:

  • Code — the rune (or special key constant like KeyEnter).
  • Text — printable text or "" for special keys.
  • Mod — modifier bitmask (ModShift|ModCtrl|ModAlt|…).
  • ShiftedCode, BaseCode — Kitty/Windows-Console only; useful for international keyboards.
  • IsRepeat — Kitty/Windows-Console only.

KeyPressMsg.String() returns the friendly form ("ctrl+shift+a" rather than "shift+ctrl+a" — the order in Keystroke() is fixed).

Kitty keyboard protocol

Set View.KeyboardEnhancements to request advanced features. The renderer issues the appropriate CSI > … u sequence; the terminal replies with CSI ? Ps u which becomes a KeyboardEnhancementsMsg. The flag bitmask is queryable via helpers like SupportsEventTypes, SupportsAlternateKeys, SupportsAllKeysAsEscapeCodes, and SupportsAssociatedText (keyboard.go).

The renderer also unconditionally enables xterm modifyOtherKeys mode 2, which gives Kitty-like fidelity on terminals that don't support the Kitty protocol but do support modifyOtherKeys.

Mouse

Mouse messages come in four flavors: click, release, wheel, motion. The MouseMsg interface provides Mouse() Mouse for code that wants to handle them uniformly. The button constants (MouseLeft, MouseRight, MouseMiddle, MouseWheelUp, …) are aliases re-exported from uv.

Bubble Tea opt-ins to mouse mode via View.MouseMode (MouseModeNone | MouseModeCellMotion | MouseModeAllMotion) — there is no startup option.

Bracketed paste

When View.DisableBracketedPasteMode is false (the default), the renderer enables DEC mode ?2004. The terminal then sends PasteStartMsg, one or more PasteMsgs, and PasteEndMsg. Without bracketed paste, pasted text would arrive interleaved with key events and applications could not tell typing from pasting.

Clipboard (OSC 52)

SetClipboard(s) and ReadClipboard return Cmds that emit internal sentinel messages. The runtime handles them in eventLoop by calling Program.execute(ansi.SetSystemClipboard(s)) or ansi.RequestSystemClipboard. The reply (OSC 52 ; c ; <base64>) is translated by ultraviolet into a uv.ClipboardEvent, which becomes ClipboardMsg.

SetPrimaryClipboard / ReadPrimaryClipboard target X11/Wayland's primary selection (the middle-click clipboard) and are no-ops elsewhere.

Capabilities and version

RequestCapability("RGB") and RequestCapability("Tc") ask the terminal to confirm true color support via XTGETTCAP. The reply arrives as CapabilityMsg; the runtime upgrades the color profile if it sees RGB or Tc.

RequestTerminalVersion issues XTVERSION; the reply becomes a TerminalVersionMsg.

Synthetic events

Two events are not produced by the input pipeline at all:

  • WindowSizeMsg: Program.handleResize fires it on SIGWINCH (Unix) or via checkResize polling (Windows). Initial size is also sent when Run starts.
  • EnvMsg: a snapshot of Program.environ sent once at startup, useful for SSH-hosted programs that want the client's TERM.

Integration points

  • The input subsystem produces messages; only the runtime consumes them.
  • Program.initInputReader(cancel bool) is also called from RestoreTerminal to re-attach input after Exec or Suspend.
  • OpenTTY is part of the public API so user code that needs raw terminal access can share the controlling tty.

Entry points for modification

  • New input event type → extend translateInputEvent and add the corresponding Msg type in the appropriate file. Keep the rule "lowercase = internal, exported = public".
  • A new ANSI mode toggle that needs an opt-in field on View → mirror the pattern used by MouseMode, ReportFocus, and KeyboardEnhancements.

Key source files

File Purpose
tty.go Input lifecycle
input.go Event translation
key.go Keyboard model
mouse.go Mouse model
paste.go / focus.go / clipboard.go / xterm.go / termcap.go Specialized event types

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Input handling – Bubble Tea wiki | Factory