Gritty is a high-performance terminal emulator built in Go, designed for speed and visual excellence. It leverages the Ebitengine game engine for GPU-accelerated rendering and features a custom-built terminal emulator and PTY manager.
- Blazing Fast Rendering: Custom raster-based renderer with dirty-region tracking to minimize CPU usage.
- GPU-Accelerated Glyph Cache: Efficient rendering of text using a dynamic glyph cache.
- Nerd Font Support: Full support for Nerd Fonts and modern typography.
- Adaptive Theming: Automatically detects system dark mode and adjusts themes accordingly.
- Neovim-Friendly VT Support: Scroll regions (DECSTBM), alternate-screen buffer restore, italic/underline rendering, and Ctrl/F-key input mapping for TUIs.
- Smooth Scrolling: Optimized scroll performance with alternate-screen handling (e.g., for
vim,less). - macOS Integration: Easily packageable as a native
.appbundle.
Gritty uses a layered terminal-emulator architecture. Input starts in the GUI layer, flows through the PTY bridge into the shell, and the shell output returns through the ANSI parser into the in-memory terminal model. The renderer then paints that model to the screen using GPU-backed buffers.
graph TD
User([User Input]) --> Renderer[Renderer - pkg/renderer]
Renderer --> |Input Events| PTY[PTY Manager - pkg/pty]
PTY --> |Shell Stream| Parser[ANSI Parser - pkg/emulator]
Parser --> |State Updates| Term[Terminal State - pkg/emulator]
Term --> |Grid Snapshot| Renderer
Renderer --> |Draw| Screen(OS Window)
cmd/termis the composition root. It creates the renderer, terminal model, parser, and PTY manager, then wires keyboard, mouse wheel, resize, and shell I/O together.pkg/emulatoris the terminal state machine. It owns the screen grid, cursor, scrollback buffer, dirty-row tracking, alternate-screen mode, and ANSI/VT parsing.pkg/rendereris the presentation layer. It translates terminal cells into pixels, handles theme and color resolution, caches glyphs, and manages the Ebiten game loop.pkg/ptyis the operating-system boundary. It starts the shell under a pseudo-terminal, resizes it when the viewport changes, and exposes read/write methods for streaming bytes.
cmd/term/main.go: Desktop app entry point and dependency wiring.pkg/emulator/parser.go: ANSI/escape-sequence parser that mutates terminal state.pkg/emulator/terminal.go: Core terminal data model and viewport/scrollback behavior.pkg/renderer/gui.go: Ebiten-based GUI renderer, input polling, drawing, and color handling.pkg/renderer/theme.go: Theme definitions and system appearance detection.pkg/renderer/renderer.go: Secondary renderer abstraction for the tcell/TUI implementation.pkg/pty/manager.go: PTY lifecycle and shell process management.verify_terminal.py: Manual verification script for colors, glyphs, and rendering behavior.assets/: Static assets such as the application icon.term,term_test: Local build artifacts or helper binaries produced during development.
- The user types or scrolls in the window managed by
pkg/renderer. cmd/termmaps that input either to PTY bytes or to scrollback navigation on the in-memory terminal model.pkg/ptyforwards bytes to the child shell process and reads shell output back.pkg/emulator/parser.godecodes control sequences and updatespkg/emulator/terminal.go.pkg/renderer/gui.goreads the terminal snapshot, redraws only changed regions, and presents the framebuffer.
- The emulator is isolated from the renderer, so terminal semantics can evolve without coupling them to drawing code.
- The PTY manager is isolated from the parser, so shell/process concerns stay separate from ANSI/state concerns.
- The renderer works from terminal snapshots and dirty flags, which keeps repaint cost predictable even when output is heavy.
- The entry point stays thin and orchestration-focused, which makes it easier to swap renderers or add startup configuration later.
- Go: Version 1.25 or later.
- Dependencies:
ebitengine/v2creack/ptygolang.org/x/image
make runTo build a standalone binary:
make buildTo create a native Gritty.app requires Ebitengine
make build-macGritty includes a Python-based verification script to test terminal capabilities like colors, Nerd Font icons, and emoji rendering.
python3 verify_terminal.py
The verification script also exercises TUI-critical sequences used heavily by Neovim, including scroll regions, alternate screen, and text styling.
- Fonts: If icons don't appear correctly, ensure you have a Nerd Font installed and that it's being picked up by the system.
- Performance: High CPU usage may occur if the terminal is flooded with output. Dirty-region tracking helps, but extremely high-throughput streams are still being optimized.
Contributions should preserve the existing separation between orchestration, terminal semantics, rendering, and PTY integration. Before making changes, identify which layer owns the behavior you are touching and keep the fix inside that boundary unless there is a clear architectural reason not to.
- Put application wiring, startup defaults, and cross-package coordination in
cmd/term. - Put escape-sequence handling, cursor movement, scrollback, resize semantics, and screen-buffer logic in
pkg/emulator. - Put drawing, theme, font, viewport, and GPU/image concerns in
pkg/renderer. - Put process spawning, shell lifecycle, and PTY read/write behavior in
pkg/pty. - Put verification utilities and developer experiments outside
pkg/unless they are reusable library code.
cmd/termOwns top-level dependency construction and event routing. This package should stay small. Ifmain.gostarts accumulating terminal behavior or rendering logic, that logic probably belongs inpkg/emulatororpkg/renderer.pkg/emulatorTreat this as the source of truth for terminal behavior. Cursor rules, erase semantics, alternate-screen behavior, scrollback management, dirty tracking, and ANSI parsing should be implemented here, not inferred later in the renderer.pkg/rendererThis package should convert terminal state into visual output, not invent terminal semantics. It may do presentation-only adjustments such as theme selection, glyph caching, contrast correction, cursor drawing, and viewport layout.pkg/ptyKeep this package narrowly focused on the shell process and pseudo-terminal. Avoid leaking renderer or parser concepts into it.assetsStore static resources only. Avoid mixing generated binaries or build outputs into this directory.
- Follow standard Go formatting. Run
gofmton every touched Go file before submitting a change. - Keep packages cohesive. Prefer adding methods to an existing type in the correct package over introducing cross-cutting helpers in the wrong layer.
- Prefer small, explicit functions over large multipurpose ones, especially in the parser and renderer hot paths.
- Preserve ASCII by default in source files unless a file already relies on non-ASCII content.
- Keep comments short and high-signal. Explain non-obvious behavior, invariants, or performance-sensitive choices; do not narrate trivial assignments.
- Use the existing naming style: exported types and methods in PascalCase, internal helpers in camelCase, short receiver names when idiomatic.
- Avoid hidden side effects. If a method mutates terminal state, it should be obvious from the method name and package.
- Be careful with locks in
pkg/emulator. Hold the terminal mutex only for the minimum work required, and avoid introducing renderer or PTY calls while the lock is held. - Preserve dirty-region behavior in the renderer. Full redraws are sometimes necessary, but they should be deliberate.
- When adding input behavior, handle alternate-screen applications carefully so programs like
vim,less, or TUIs still receive the keys they expect.
- This project is optimized around incremental rendering. Prefer dirty-row or dirty-cell updates over whole-frame redraws where possible.
- Avoid unnecessary allocations inside per-frame or per-cell rendering code.
- Keep scrollback and resize behavior efficient. These paths are hit frequently during real shell usage.
- If a change affects drawing or parsing throughput, explain the tradeoff in the pull request or commit message.
- Run
go test ./...for build and package verification. - Use
python3 verify_terminal.pywhen changing colors, fonts, glyph rendering, or ANSI behavior that is easier to validate visually. - Manually test common shell flows after UI or input changes: typing, resizing, scrollback,
vim,less, and colored output such asls --color. - If a change is platform-specific, call that out clearly in the README update, issue, or pull request description.
- Keep pull requests focused. A renderer change and a parser refactor should usually be separate unless one depends directly on the other.
- Describe the user-visible behavior change first, then summarize the implementation.
- Include screenshots or short clips for visual changes when possible.
- Note any known limitations, untested paths, or follow-up work explicitly.
- Do not put terminal parsing logic in the renderer.
- Do not send shell bytes from low-level emulator code.
- Do not bypass the terminal model when adding scrollback or cursor behavior.
- Do not add unrelated formatting or broad refactors to a behavior-focused patch.
Feel Free to use but Attribution required.
