-
Notifications
You must be signed in to change notification settings - Fork 0
Embedding
go-term is a Go library. You embed it into a go-gui application. This page covers the two embedding layers:
-
term— the core widget. It gives you one terminal in one window. -
term/workspace— the pane manager. It gives you tabs and split panes.
The examples/minimal directory holds a complete single-terminal program.
The examples/falcon directory holds a complete workspace program.
- Go 1.26 or newer (the
godirective ingo.mod) - a
go-guiwindow - macOS, Linux, or Windows
Add the module to your project:
go get github.com/go-gui-org/go-term@latest
Build against v0.9.0, the API-freeze release, or newer v0.10.x. What the
docs document is what v1.0.0 will keep. The v0.10 breaking change
(Cfg.CursorBlink from *bool to bool, plus CursorLocked) is the only
one since the freeze.
This program creates a window, starts a shell, and renders it:
package main
import (
"log"
"github.com/go-gui-org/go-gui/gui"
"github.com/go-gui-org/go-gui/gui/backend"
"github.com/go-gui-org/go-term/term"
)
func main() {
gui.SetTheme(gui.ThemeDark.WithBorders(true))
var tm *term.Term
w := gui.NewWindow(gui.WindowCfg{
Title: "go-term minimal",
Width: 800,
Height: 500,
OnInit: func(w *gui.Window) {
var err error
tm, err = term.New(w, term.Cfg{
TextStyle: gui.TextStyle{Family: "monospace", Size: 14},
})
if err != nil {
log.Fatalf("term.New: %v", err)
}
w.UpdateView(tm.View)
},
})
defer func() {
if tm != nil {
_ = tm.Close()
}
}()
backend.Run(w)
}The steps:
- Create a
gui.NewWindowwith anOnInitcallback. - Call
term.NewinsideOnInit, with the window and aterm.Cfg. - Pass
tm.Viewtow.UpdateView. - Call
tm.Closewhen the window closes.
New starts the shell and its reader goroutine before it returns.
term.Cfg carries the widget's options. Every field is optional. A zero value
selects the built-in default.
| Field | Meaning |
|---|---|
TextStyle |
Font family and size. Zero means gui.CurrentTheme().M5
|
Themes |
Named themes for runtime switching. The first entry is the initial theme |
Command, Args
|
The binary to run instead of $SHELL
|
Env |
Extra environment variables for the child, applied last |
Dir |
Working directory for the child. Empty inherits the process CWD |
Identity |
Value for TERM_PROGRAM. Defaults to go-term
|
ScrollbackRows |
Scrollback cap. Zero means 5000, negative disables it |
BellMode |
How BEL is signalled: auto, audible, visual, both, none
|
BellFlashDuration |
Visual-bell overlay duration. Zero uses the built-in 100 ms. Negative disables it |
ScrollbarWidth |
Scrollbar thumb width in px. Negative hides the scrollbar |
MinimumContrast |
WCAG ratio floor, 1.0–21.0, applied at render time |
MiddleClickPaste |
Middle-click pastes. Off by default |
NotifyAfter |
Notify when a long command finishes while you look away |
CursorStyle |
Initial cursor shape: block, underline, bar (exported term.CursorStyle) |
CursorBlink |
Whether the cursor blinks. Seeds the cursor, not an override |
CursorLocked |
When true, ignore the child's DECSCUSR shape/blink changes |
AllowOSC52Write |
Permit child programs to write the clipboard. Off by default |
DisableGraphics |
Skip sixel, Kitty, and iTerm2 image decoding |
NoWindowHandler |
Do not install a window event handler. Set for pane managers |
DownloadDir |
Where OSC 1337 file transfers land. Empty disables transfers |
OnDownload |
Receives each OSC 1337 download. Nil with DownloadDir set uses the built-in writer |
RecordPath |
Start a session recording at this path |
RecordInput |
Record keystrokes into recordings. Off by default |
KeyBindings |
Override the default chords for term-level actions |
New spawns the child process and the reader goroutine. It also starts the
blink, scroll, momentum, write, and resize loops.
Call Close to kill the child, close the PTY, and stop the loops. Always
call it when the window closes, or you leak file descriptors and
goroutines. Close is idempotent.
Use Cfg.OnExit to detect child death. Use Term.Alive to poll it.
The exported methods are safe to call from any goroutine: View, Close,
Cwd, Theme, SetTheme, Rows, Cols, Write, PID, Alive,
SetFocused, HandleWindowEvent, SetMinimumContrast, SetKeyBindings,
SetNotifyAfter, SetCursorStyle, SetCursorBlink, SetCursorLocked,
StartRecording, StopRecording, SendInput.
Cfg carries callbacks for the events a host cares about:
| Callback | Fires | Thread |
|---|---|---|
OnTitle |
The child sets the window title (OSC 0/1/2) | main |
OnNotify |
A child asks for a desktop notification (OSC 9/777) | background |
OnActivity |
Bell or OSC 133 command end (ActivityKind: Bell, CommandDone, CommandFailed) |
main |
OnExit |
The child process exits | reader |
OnClickFocus |
The user clicks the canvas | main |
OnInput |
User input is about to go to the child | main |
With OnTitle nil, the widget sets the window title itself. That is right
for a single-terminal window. A pane manager sets OnTitle to keep titles
per pane.
OnInput receives every user byte sequence. It runs alongside the local
write and cannot suppress it. A pane manager mirrors input to sibling panes
with it. Mouse reporting and focus reports are excluded on purpose.
OnActivity fires once per explicit event — a BEL or an OSC 133 D mark —
not per PTY read. Plain screen output is not reported: a timer-driven app
would otherwise mark every background tab permanently. Command kinds need
shell integration (OSC 133).
Themes are named color schemes. NamedTheme pairs a display name with a
Theme.
term.BundledThemes returns the bundled corpus, sorted by name. There are
602 themes. term.DefaultTheme is the fallback used when Cfg.Themes is
empty.
Register Default first and the corpus after it. The first entry seeds the
grid and decides COLORFGBG at spawn. That value cannot be corrected once
the child runs, so the order matters.
themes := []term.NamedTheme{
{Name: "Default", Theme: term.DefaultTheme},
}
themes = append(themes, term.BundledThemes()...)Theme.IsDark reports the light or dark character of a theme. The child
asks the same question through DSR ?996.
A light theme does not fix an application's own colors. A truecolor SGR is
not themeable. Cfg.MinimumContrast is the render-time floor that forces
text above a contrast ratio against its cell background. The grid keeps the
color the child sent, so copy, search, and recordings are unaffected.
The child environment is the parent's, with the host terminal identity
scrubbed and TERM, COLORTERM, and COLORFGBG set for the pane. Entries
in Cfg.Env apply last, so they win over all of it, including
TERM_PROGRAM.
TERM_PROGRAM is how TUI file managers choose their image protocol. yazi
and superfile use the Kitty Graphics Protocol under a name they recognize
and fall back to sixel otherwise. Set Cfg.Identity (or an Env entry) to
name a known emulator and get its image quality. Falcon advertises Falcon.
A window with several panes is a pane manager. Set Cfg.NoWindowHandler to
true so the widget does not install its own window event handler.
Then the manager owns the window-level event dispatch. It routes events to the focused pane:
- keyboard input to the focused pane's
Viewcontainer -
Term.HandleWindowEventfor the rest
Use Term.SetFocused to move focus between panes. OnClickFocus tells you
when the user clicks a pane. OnActivity tells you when a background pane
did something.
Term.Rows, Term.Cols, Term.Write, Term.PID, Term.Alive, and
Term.Cwd support introspection without touching internal state.
Term.SendInput replays captured input onto a pane, per its bracketed-paste
state.
term/workspace is the ready-made pane manager. It builds a multi-tab,
multi-pane window on top of term, and it reads the user config file.
ws, err := workspace.New(w, workspace.Cfg{
TextStyle: defaultTextStyle(),
Themes: themeList(),
Identity: "Falcon",
ConfigPath: "",
SavePath: defaultSavePath, // where Cmd+S writes the layout
})
if err != nil {
log.Fatal(err)
}
w.UpdateView(ws.View)workspace.Cfg adds the window-level fields:
| Field | Meaning |
|---|---|
Identity |
TERM_PROGRAM for every pane. Defaults to go-term
|
ConfigPath |
Path to the user config file. Empty uses the default location |
SavePath |
Where Cmd+S writes the layout. Empty uses the default workspace path |
RecordDir |
Where Cmd+Shift+R recordings land |
RecordInput |
Record keystrokes into recordings |
DownloadDir |
Where OSC 1337 transfers land. Empty disables them |
ExitWhenLastShellExits |
Close the window when the last shell exits |
OnLastShellExit |
Runs instead of the close when the last shell exits |
OnColorScheme |
Reports light/dark changes, so the host can theme its chrome |
The workspace wires the multi-pane callbacks for you: focus, activity, bell, title, exit, and broadcast input.
Workspace.Save(path) writes the tab and pane layout to a JSON file,
atomically. workspace.Restore(w, cfg, path) rebuilds the workspace from
that file. The workspace.save command (default Cmd+S) triggers the
save at runtime. It writes to Cfg.SavePath when set. Otherwise it uses
the default workspace path.
Restore falls back to a fresh workspace on a missing, unparseable, or
version-mismatched file. A bad workspace file never blocks startup.
workspace.DefaultWorkspacePath and workspace.DefaultConfigPath return
the standard locations, resolved against $XDG_CONFIG_HOME,
~/.config, or os.UserConfigDir() (<base>/go-term/config and
<base>/go-term/workspace.json).
Falcon overrides both since v0.10: it derives
<base>/falcon/config and <base>/falcon/workspace.json from the default
and passes them as Cfg.ConfigPath / SavePath, so both files live under
the application name. It restores on startup and saves on quit, with two
paths so a read-only template layout is possible. See Falcon.
The workspace layer reads the user config file: an INI-style file that overrides fonts, theme, scrollback, bell, scrollbar, cursor, keybindings, and the child environment. It can be reloaded without restarting. The full reference is on the Configuration page.
term.NewReplay returns a Term that plays back a .gtr recording
instead of spawning a shell. The widget runs the entire normal stack —
parser, grid, rendering, scrollback, search, graphics — against recorded
bytes.
ReplayCfg controls playback:
| Field | Meaning |
|---|---|
Path |
The .gtr file to play. Required |
Speed |
Playback multiplier. 2 plays twice as fast |
IdleLimit |
Caps any single gap between frames. Zero means no cap |
Loop |
Restart at the end of the stream |
Controls |
Interpret space, +/-, ., and 0 as playback commands |
Cfg.Command, Args, Env, and Dir are ignored for replay: no child
exists. The widget holds on the final frame until Close.
Recordings store the PTY bytes verbatim. A rendering bug reproduces in
replay, which makes recordings the preferred way to file a bug report.
Record them with StartRecording, or with falcon's Cmd+Shift+R. The
gotermrec CLI inspects and converts them. See Usage.
GOTERM_LATENCY measures the delay between a keystroke and its frame.
Set it to 1 (or to a sample-batch size). Every 25 keystrokes, go-term
logs the percentiles for these spans:
-
key→echo: the round trip through the child -
echo→wake: the reader goroutine to the main thread -
wake→paint: the frame itself - the total of the three spans
- the time that
onDrawran - the number of PTY reads that landed while the keystroke was outstanding
The variable is off by default. It does nothing when off. The numbers are a lower bound: GPU submit, compositor, and vsync are invisible from inside the process.
GOTERM_LATENCY=1 go run . # in examples/falconThe public surface was frozen at v0.9.0, the release where the export audit and Godoc pass landed, and held through v0.10.0. What the docs document is what v1.0.0 keeps. Build against v0.9.0 or newer.
v0.10.0 shipped one breaking change: Cfg.CursorBlink from *bool to bool
(now seeds rather than overrides; the override half moved to the new
Cfg.CursorLocked which covers shape and blink together) and the internal
cursorShape became the exported term.CursorStyle. nil becomes false;
*true becomes CursorBlink: true, CursorLocked: true.
The kept surface is deliberately small — the widget, themes, actions, recording and replay, the live setters, and the activity/input taps. Everything that pkg.go.dev documents is stable. Names not documented there are internal:
-
Termis an opaque handle. All fields are unexported. Embedders interact through methods only. -
term/workspacekeepsWorkspace,New,Restore,Close,View,Cfg,Save,DefaultWorkspacePath,DefaultConfigPath,LiveTermCount, andActivePane. The per-tab and keyboard-command methods (AddTab,SplitPane,NextPane, and the rest) are unexported. A pane manager is one window with one workspace. Its commands run through the keybindings. - Import only the
termpackage. The source-file organisation is not a contract.
Pin v0.9.0 or newer in go.mod. Use Cfg zero values for everything you do
not set. Read the changelog before a minor upgrade.