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 | bashand follow the prompts.
The 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 (or the core editor itself). -
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 --recursiveNote: --recursive is needed so it can clone the vendored tree-sitter runtime.`
Targets:
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 |
yazi |
:yazi file-manager picker |
copilot-language-server |
copilot plugin (:copilot login, ghost-text suggestions) |
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. - whichkey hints: pause partway through a chord (e.g. after
<space>) and a sorted, 1–4-column table of completions appears in the message bar. Toggle with<space>th. - Multiple cursors:
:mc_add_below/:mc_add_aboveadd extra cursors that mirror every subsequent keystroke through the full dispatch pipeline. - 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 |
whichkey |
While a multi-key chord is in progress, list the candidate completions in a 1–4 column table |
multicursor |
Extra cursors that mirror every keypress (:mc_add_below, :mc_add_above) |
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 |
claude |
Toggle a tmux pane running Claude Code (rides the tmux pane registry) |
copilot |
GitHub Copilot ghost-text suggestions via copilot-language-server, with a [copilot] alternatives pane |
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 |
shell |
:shell / ! prompt; capture tokens splice stdout into the buffer or yank register |
reload |
:reload rebuilds and execs the new binary |
session |
Save / restore the open-buffer list per cwd |
autosave |
Idle/timer autosave to per-cwd cache dir, with recovery prompt on reopen |
yazi |
Launch the yazi file manager as a chooser; selected paths open as buffers |
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.