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 + RequestCapabilityKey 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 --> LoopRaw 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 likeKeyEnter).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.handleResizefires it onSIGWINCH(Unix) or viacheckResizepolling (Windows). Initial size is also sent whenRunstarts.EnvMsg: a snapshot ofProgram.environsent once at startup, useful for SSH-hosted programs that want the client'sTERM.
Integration points
- The input subsystem produces messages; only the runtime consumes them.
Program.initInputReader(cancel bool)is also called fromRestoreTerminalto re-attach input afterExecorSuspend.OpenTTYis 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
translateInputEventand add the correspondingMsgtype 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 byMouseMode,ReportFocus, andKeyboardEnhancements.
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.