A hackable, console-flavoured code editor written in modern C++20.
Someone (Peter Norton?) once said that to really become a programmer you have to write an editor. This started as that playground — and grew into something I actually use every day.
GoatEdit pairs a real text editor with a first-class embedded shell. Hit a key and the
bottom of the window becomes a live terminal — run make, git, vi, anything — and the same
prompt doubles as a command line for the editor itself. Think Amiga AsmOne or a game console's
drop-down terminal, but driving a syntax-highlighting, multi-backend editor.
- The terminal is a feature, not an afterthought. A genuine PTY-backed shell lives inside the
editor (
bash/zsh/whatever you fancy). It handles colours, full-screen apps likeviandless, command history, tab-completion, and window-size reporting — the real deal, not a fake prompt. - One prompt, two brains. Type a normal command and it goes to the shell. Prefix it (configurable,
default
.) and it runs an editor command instead — save, load, search, or any plugin you've written. No mode-juggling. - Pluggable rendering backends. SDL3 (the primary, "real application" frontend with TrueType rendering via stb_truetype) and SDL2 for CI/fallback. The core knows nothing about pixels, so a fresh backend is a self-contained job — a modern-terminal backend (no NCurses baggage) is on the roadmap.
- Scriptable in JavaScript. An embedded Duktape engine exposes the editor through a clean JS API
(
Editor,Document,View,Theme,Console…). The startup banner goat? That's a plugin. - Its own syntax engine. A compact stack-based tokenizer drives highlighting (C++, JSON, Make, and a default fallback), tokenizing in the background so big files don't stall the UI.
- Runs on Linux and macOS.
Build your project without leaving the editor — the output streams into the same window, with colours intact.
A fast overlay for search, navigation, and plugin commands without reaching for the full terminal. Results are written straight to the terminal pane — here, a search across the buffer.
The build is driven by CMake 3.22+. CMake auto-clones the local dependencies under ext/ on
first configure; system dependencies you install yourself.
sudo apt-get update
sudo apt-get install -y libyaml-cpp-dev libsdl2-dev
./setup_deps.sh # clones ext/ deps (json, gnklog, dukglue, fmt)
# Configure (SDL3 is on by default)
cmake -B ./cmake-build-debug -DCMAKE_BUILD_TYPE=Debug
# ...or build the SDL2 backend instead (handy on CI / older boxes)
cmake -B ./cmake-build-debug -DCMAKE_BUILD_TYPE=Debug -DGEDIT_BUILD_SDL3=OFF -DGEDIT_BUILD_SDL2=ON
# Build the editor
cmake --build ./cmake-build-debug --config Debug --target goatedit -jSame flow, swap apt-get for brew.
| Library | Kind | Notes |
|---|---|---|
| yaml-cpp | system | config / themes |
| SDL | system | SDL2 or SDL3 backend |
| nlohmann/json | ext/ (auto) |
|
| fmtlib | ext/ (auto) |
u8/u16/u32 string formatting |
| dukglue | ext/ (auto) |
C++↔JS binding |
| gnklog | ext/ (auto) |
logging |
| stb | vendored | TrueType + rect_pack |
| duktape 2.7.0 | vendored | JS engine (pre-configured in src/ext/) |
Duktape ships pre-configured in the repo (
src/ext/duktape-2.7.0) so you don't have to fight its build — a scar from an old GitHub Actions battle.
Everything lives in the gedit namespace. A few landmarks:
main.cpp— picks a backend, thenEditor::Instance().Initialize()/OpenScreen().src/Core/Editor.*— the application singleton (owns the workspace, model, plugin engine, theme, keymap, runloop).src/Core/TextBuffer / Line / EditorModel / Workspace— the data model (a buffer is a vector ofLines; aLineis astd::u32stringplus token attributes).src/Core/Views/— a view tree (EditorView,GutterView,TerminalView,WorkspaceView, split/stack containers, modal overlays).src/Core/Controllers/— input logic decoupled from views (EditController,TerminalController,QuickCommandController).src/Core/SDL3/— backends behind theScreenBase/DrawContextinterfaces.src/Core/Language/— the tokenizer and per-language configs.src/Core/JSEngine/— Duktape host + the JS API wrappers; plugin scripts live insrc/Plugins/.src/Core/Config/— YAML config and theming.
There's a much deeper architecture/coding-standards write-up in CLAUDE.md — worth a
read before a first PR.
Contributions are very welcome — this is a personal project that has outgrown one person's spare evenings, and there's a lot of low-friction surface area to jump in on:
- 🧩 Write a plugin. The JS API is the easiest on-ramp — add a cmdlet, a theme tweak, a goat.
- 🎨 Add a language. Implement a
LanguageBase+ a tokenizer config (seeCPPLanguage/JSONLanguageas templates). - 🖥️ A modern-terminal backend. SDL3 is primary; the big open prize is a clean console backend built for today's terminals (no NCurses legacy baggage) — a stepping stone toward running GoatEdit over SSH on a remote box.
- 🐚 Terminal/VT corner cases. The VT parser (
VTermParser) handles a healthy subset of xterm — origin mode, bracketed paste, and friends are still open. - 🧪 Tests. The suite runs under trun; modules live in
utests/. Run them fromcmake-build-debug/:cd cmake-build-debug && trun --sequential ./libutests.so
If you're picking something up, open an issue or draft PR early so we can compare notes. Style and
conventions are documented in CLAUDE.md; the short version is C++20, RAII everywhere,
4-space K&R, and code ordered caller-before-callee so files read top-to-bottom.
BSD 3-Clause © 2023 Fredrik Kling. See LICENSE.