A small sized(<1MB), hackable terminal editor written in C.
Modal by default, but ships Emacs and VSCode keymaps you can swap to at runtime. Tree-sitter highlights, ripgrep / fzf integrations, window splits, tmux runner pane, OSC 52 clipboard, LSP. Every user-facing feature is a plugin you can rip out, fork, or replace.
One line install:
curl -fsSL https://github.com/42dotmk/hed/releases/latest/download/install.sh | bashThe installer asks two things:
- Source or binary? Binary is faster (one download, ready to
run). Source clones the repo into
~/.local/share/hed, builds it, and symlinkshedandtsiinto~/.local/bin— pick this if you want to hack on plugins. - Optional tools? Offers to download portable static
fzfandripgrepbinaries into the same~/.local/bin, then lets you pick tree-sitter grammars to install for syntax highlighting.
No sudo. No package manager. Everything ends up under
~/.local/.
After install, make sure ~/.local/bin is on your PATH:
export PATH="$HOME/.local/bin:$PATH"Then run hed and you're in.
If you want to skip the installer:
git clone --recursive https://github.com/42dotmk/hed.git
cd hed
make
./build/hedIf you forgot --recursive:
git submodule update --init --recursiveTargets:
make # build build/hed and build/tsi
make install # symlink build/hed and build/tsi into ~/.local/bin
make run # build then run
make clean # remove build/
make test # Unity unit testsmake install symlinks rather than copies — rebuilds (make,
:reload) update the installed binary automatically.
- gcc or clang, C11
- POSIX terminal, libdl
The vendored tree-sitter runtime is included as a submodule under
vendor/tree-sitter and statically linked. No libtree-sitter
system package required.
Each integration degrades cleanly if its tool is missing:
| Tool | Used by |
|---|---|
ripgrep |
:rg, :ssearch, :rgword |
fzf |
:fzf, :recent, :c, history pickers |
tmux |
runner pane (:tmux_toggle, :tmux_send_line) |
lazygit |
:git |
bat |
fzf previews |
ctags |
:tag |
clang-format / rustfmt / prettier / black / gofmt / shfmt |
:fmt |
git, cc |
tsi (tree-sitter grammar installer) |
- Vim-like modal editing by default, with full operator + text-object
composition (
diw,ci(,ya",>i{, …) and the usual undo / redo, registers, macros, and search stack. - Three swappable keymaps: Vim (default), Emacs (modeless,
C-a/C-e/C-xcluster), VSCode (modeless,Ctrl+S/Z/F/D, shift-arrow selection). Toggle at runtime with:keymap. - Tree-sitter highlighting for any language you install via
tsi. Grammars load on demand withdlopen. - Fuzzy pickers:
:fzffiles,:recentrecent,:ccommands, history fzf, jump-list fzf — all powered byfzf. - ripgrep + quickfix:
:rg,:rgword,:ssearchpopulate a quickfix buffer with live preview as the cursor moves. - Splits & windows: vertical / horizontal splits, window focus navigation, modal floating windows.
- tmux runner pane: send the paragraph under your cursor to a shell pane next door.
- System clipboard over SSH via OSC 52 — no
xclip,pbcopy, orwl-copyshelling out. :reloadrebuilds the editor and execs the new binary in place, restoring all open buffers.- No flicker on terminals that support DEC mode 2026 (kitty, alacritty, wezterm, foot, ghostty).
The core editor knows about buffers, windows, terminal I/O, the
keybind dispatcher, the command registry, and the hook system.
Every user-facing feature is a plugin in plugins/<name>/.
Each has its own README; here's the catalogue:
| Plugin | What it does |
|---|---|
core |
Default : command set (:q, :w, :e, :bn, :fzf, :rg, …) |
vim_keybinds |
Default modal Vim keymap |
emacs_keybinds |
Modeless Emacs keymap (C-a/C-e, M-x, C-x cluster) |
vscode_keybinds |
Modeless VSCode keymap (Ctrl+S, shift-arrow selection) |
keymap |
:keymap and :keymap-toggle for runtime swap |
treesitter |
Syntax highlighting; grammars via dlopen |
clipboard |
OSC 52 yank to system clipboard (works over SSH) |
dired |
oil.nvim-style directory browser |
tmux |
Runner pane integration |
fmt |
:fmt runs an external formatter on the buffer |
auto_pair |
Auto-insert matching brackets and quotes |
smart_indent |
Carry indent onto new lines |
quickfix_preview |
Live preview of the quickfix entry under the cursor |
viewmd |
Markdown live preview in the browser |
scratch |
:scratch ephemeral unnamed buffer |
sed |
:sed <expr> pipes the buffer through external sed |
reload |
:reload rebuilds and execs the new binary |
session |
Save / restore the open-buffer list per cwd |
lsp |
LSP client (work in progress) |
example |
Starter template — copy and rename for your own |
All user-facing customization lives in src/config.c:
void config_init(void) {
plugin_load(&plugin_core, 1);
plugin_load(&plugin_vim_keybinds, 1);
plugin_load(&plugin_emacs_keybinds, 0); // registered, swappable
plugin_load(&plugin_vscode_keybinds, 0);
plugin_load(&plugin_treesitter, 1);
plugin_load(&plugin_clipboard, 1);
/* ... */
/* Personal overrides — last-write-wins, beats plugin defaults. */
cmapn(" ff", "fzf");
cmapn(" rr", "reload");
/* ... */
}Edit, run make, and :reload from inside the editor to pick up
the changes — no need to quit and relaunch.
cp -r plugins/example plugins/myplugin
# rename example → myplugin in the source files
# add plugin_load(&plugin_myplugin, 1) to src/config.c
makeSee plugins/example/README.md for the
full recipe and the plugin contract.
Keep your plugin set anywhere on disk and point the build at it:
make PLUGINS_DIR=$HOME/my-hed-pluginssrc/ # core editor (buffers, windows, terminal,
# commands, keybinds, hooks, undo, fold, …)
plugins/ # all user-facing functionality (see catalogue above)
vendor/tree-sitter/ # vendored runtime, statically linked
ts/ # tsi (grammar installer) source
queries/ # tree-sitter highlight queries by language
test/ # Unity unit tests
- Logs:
~/.cache/hed/<encoded-cwd>/log. Tail it while hed runs; clear with:logclear. :pluginslists everything currently loaded.:keybindslists every binding registered for the active mode.- If
fzf,ripgrep,tmux, orlazygitare missing, related commands fail with a status-line message and the rest of the editor keeps working. - If
:reloadfails to rebuild, the error goes to the status line — open~/.cache/hed/<encoded-cwd>/logfor the full output.