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.28+. Source dependencies (json, gnklog, dukglue, fmt) are fetched by CMake itself via FetchContent on first configure — no manual step; system dependencies you install yourself.
sudo apt-get update
sudo apt-get install -y libyaml-cpp-dev libsdl2-dev
# 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.
The following must be installed on the system
- libsdl2-dev or libsdl3-dev
- libyaml-dev
- testrunner (from https://github.com/gnilk/testrunner) - for unit testing
sudo apt install libsdl2-dev libsdl3-dev libyaml-devThe following are installed during build
- nlohmann/json
- fmtlib
- dukglue
- gnklog
The following are bundled directly with the source
- stb (various parts)
- duktape 2.7.0
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.
1) Build (release recommended for distribution):
cmake --preset release
cmake --build --preset release --target goatedit2a) .deb — the CPack package target (uses the DEB generator on Linux); the package lands
in the build dir:
cmake --build ./cmake-build-release --target package
# -> cmake-build-release/goatedit-<ver>-linux-<arch>.deb2b) AppImage — built separately; writes goatedit-<arch>.AppImage to the repo root by default:
./scripts/build-appimage.shNotes on the AppImage script:
./scripts/build-appimage.sh [BUILD_DIR]— first arg (or$BUILD_DIR) overrides the build dir (defaultcmake-build-release);$OUTPUT_DIRoverrides where the.AppImagelands;$VERSION, if set, is folded into the filename (goatedit-<VERSION>-<arch>.AppImage).- Needs
curl(fetcheslinuxdeployonce, cached in<BUILD_DIR>/appimage-tools/) and FUSE — the script setsAPPIMAGE_EXTRACT_AND_RUN=1as a fallback if FUSE isn't available. - It runs
cmake --install <BUILD_DIR> --prefix <AppDir>/usr(reusing the project's install rules), bundling the binary, SDL + yaml-cpp, the assets, the desktop file, and the icon, then sets the customAppRun.
BSD 3-Clause © 2023 Fredrik Kling. See LICENSE.


