A keyboard-driven markdown editor in C, SDL2, and Lua. Aims at the Obsidian / Lite XL ergonomics — vault sidebar, wiki links, full preview rendering, plugin host — without the Electron tax.
- Live preview of CommonMark via md4c — headings, lists, task lists, code fences, block quotes, tables, inline styles, links, soft and hard breaks.
- Edit mode with a real text buffer (undo/redo, multi-byte caret, selection, smart Enter for lists, auto-pairs, find/replace). Soft word wrap on by default; toggle off (Alt+Z) for a horizontal scrollbar with arrow buttons.
- Vault sidebar with collapsible folders, drag-and-drop reorder, right-click context menu, recents tracking, and image files that open straight into the preview pane.
- Wiki links
[[note name]]resolve case-insensitively across the vault; backlinks panel shows every note that points at the current one. - Outline panel auto-generated from headings, pinned or modal.
- Find / Replace with regex, case-insensitive, whole-word, live match count, caret-aware editing, click-to-position. Searches the raw source in edit mode and the rendered text in preview so the highlights line up in either view.
- Vault-wide search across every note with a results overlay.
- Quick switcher (Ctrl+P) — recents-first, fuzzy match.
- Command palette (Ctrl+Shift+P) — every action plus every Lua-registered plugin action with a category chip and bound shortcut.
- Plugin host — drop
*.luaindata/plugins/, register actions withdescry.register_action("name", fn), surface UI withdescry.notifyanddescry.dialog. Plugins overlay lists every loaded file and the actions each registered, with a hot-reload button. - In-app modals for Save / Save As / Rename / New File — no native Win32 dialogs, full keyboard nav, sidebar-style file browser.
- Custom title bar with File / Edit / View / Help menus, aero
snap, drag-to-move, min/maximize/close, working under
SDL_WINDOW_BORDERLESSvia a WS_THICKFRAME + WM_NCCALCSIZE trick. - Live resize indicator — a centered
W x Hbadge plus a window outline render while the user drags an edge. The resize loop pumps through an SDL event watch so the indicator animates during the drag, not after. - Plugin actions, Ctrl+click external links, image preview, About dialog, settings persistence, theme picker, keybinding editor, daily-notes, HTML export, ASCII-art table aligner ...
| Layer | Library |
|---|---|
| Language | C11 |
| Window / input | SDL2 |
| Glyph cache | FreeType + HarfBuzz |
| Markdown parser | md4c (vendored) |
| Scripting | Lua 5.4 (vendored) |
| SVG icons / pills | nanosvg (vendored) |
| Image decode | libpng, libjpeg |
| Anti-aliasing | custom analytic signed-distance-field pill rasterizer |
| Build | CMake + Ninja |
No GTK, no Qt, no web view, no JS runtime. The compiled binary is a
single descry.exe plus its DLL dependencies (SDL2, FreeType,
HarfBuzz, libpng, libjpeg).
pacman -S mingw-w64-x86_64-{gcc,cmake,ninja,SDL2,freetype,harfbuzz,libpng,libjpeg-turbo}
cmake -G Ninja -B build
ninja -C build
./build/descry.exeapt install build-essential cmake ninja-build libsdl2-dev libfreetype-dev libharfbuzz-dev libpng-dev libjpeg-dev
cmake -G Ninja -B build
ninja -C build
./build/descryOn Windows the build pulls every transitive MinGW DLL next to the exe
via file(GET_RUNTIME_DEPENDENCIES) so the build dir is portable —
zip it and run anywhere without an MSYS2 install. The data/ folder
is not copied; the exe loads data/ from its own directory at
runtime, so put your vault wherever you like and point Descry at it.
src/ - C sources (single-binary)
main.c - app loop, UI, every overlay
buffer.c/h - the gap-free text buffer + cursor/selection/undo
markdown.c/h - md4c wrapper, line/style/wiki/link extraction
font.c/h - FreeType+HarfBuzz glyph cache, fallback chain
icons.c/h - nanosvg icon raster + SDF pill rasterizer
lua_host.c/h - Lua state, plugin loader, action registry
vault.c/h - recursive directory scan + native dialogs
image.c/h - PNG/JPG decode -> SDL_Texture cache
regex.c/h - in-house regex engine (find/replace)
data/ - default vault: sample notes, init.lua, plugins/
vendor/ - lua 5.4, md4c, nanosvg
A plugin is any .lua file under data/plugins/. The host exposes a
tiny global table:
-- data/plugins/hello.lua
descry.register_action("say_hello", function()
descry.dialog("Hello", "Hello from the plugin system!")
end)
descry.notify("[hello plugin] loaded")After a reload (Ctrl+Alt+P > Reload, or restart), the action shows up
in the command palette with a Plugin category chip and can be
invoked by name or bound to a key.
Full reference — every available API call, lifecycle, debugging, and an honest list of what's not exposed yet — lives in docs/plugins.md.
A non-exhaustive list. Every binding is editable from Settings > Keybindings and persists to settings.lua next to the exe.
| Action | Shortcut |
|---|---|
| Toggle edit / preview | Ctrl+E |
| Save | Ctrl+S |
| Save As | Ctrl+Shift+S |
| New file | Ctrl+N |
| Rename | F2 |
| Quick switcher | Ctrl+P |
| Command palette | Ctrl+Shift+P |
| Plugins overlay | Ctrl+Alt+P |
| Find | Ctrl+F |
| Find / Replace | Ctrl+H |
| Vault search | Ctrl+Shift+F |
| Outline | Ctrl+Shift+O |
| Backlinks | Ctrl+Shift+B |
| Tags | Ctrl+Shift+G |
| Daily note | Ctrl+D |
| Toggle sidebar | Ctrl+B |
| Toggle word wrap | Alt+Z |
| Help & Keybindings | F1 |
| Settings | Ctrl+, or F10 |
When word wrap is off, long edit-mode lines extend past the viewport; Shift+wheel pans, or use the horizontal scrollbar that appears at the bottom of the editor.




