-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
ctrlvim is a Cargo workspace of twelve library crates plus the ctrlvim binary crate that produces cvi. Each crate handles one concern, so the dependency graph reads roughly bottom up: text storage, then editing logic on top of it, then language integration, then the terminal frontend on top of all of it.
| Crate | Purpose |
|---|---|
ctrlvim-types |
Shared value types used across the engine |
ctrlvim-text |
Rope backed buffers, marks, the undo tree, registers |
ctrlvim-options |
Vim style options ('number', 'wrap', ...) and :set
|
ctrlvim-regex |
A Vim regex engine written from scratch: magic levels, backreferences, lookaround, \zs / \ze
|
ctrlvim-editor |
Motions, operators, text objects, window splits |
ctrlvim-vimscript |
The Vimscript interpreter backing vim.fn and user functions |
ctrlvim-treesitter |
Tree sitter integration and the highlights.scm to styled span highlighter |
ctrlvim-markdown |
Markdown parsing for live rendering in the TUI |
ctrlvim-api-macro / ctrlvim-api
|
The #[ctrlvim_api] macro and the dispatch table it generates, exposing engine functionality to Lua and RPC uniformly |
ctrlvim-lua |
Lua embedding (mlua) and the vim.* API compatibility layer plugins run against |
ctrlvim-async |
The Tokio event loop and msgpack-RPC server |
ctrlvim-term |
Terminal emulation backing the pseudoterminal panel |
ctrlvim-lsp |
The LSP client: JSON-RPC transport, request and response parsing |
ctrlvim-core |
Ties the engine crates together behind one Ctrlvim facade |
ctrlvim-tui |
The Ratatui terminal UI: dashboard, file editor, overlays, plugin manager |
ctrlvim-core::Ctrlvim is the single entry point the frontend, and any headless caller, drives. It owns a Session (editor plus modal state, the same object interactive key input and the Lua host operate on), and a separate Editor that Lua plugin state, such as floating windows and their buffers, lives in. Keeping those separate is deliberate: a plugin that opens and focuses its own floating window must not be able to silently clobber the buffer you are actually editing.
Ctrlvim::run_lua and Ctrlvim::run_lua_as run a chunk of Lua and then sync two things back out to the frontend afterward: any mapping the chunk registered through vim.keymap.set, and any command it registered through vim.api.ctrlvim_create_user_command. This is the mechanism that lets a plugin register the rest of its mappings and commands lazily, on first use, rather than needing to declare everything up front.
| Module | Role |
|---|---|
app |
App state and the Action enum that both the keymap and mouse hit testing dispatch through; owns the real ctrlvim_core::Ctrlvim engine |
input |
Keyboard handling: editor focus routes straight to the engine, the shell keymap handles everything else |
model |
Domain types and static UI data |
icons |
Nerd Font detection and the per filetype icon table |
theme |
The color palette |
data |
Real project data gathering: recent files, git status, session list, lines of code, all off the actual project on disk, gathered on a worker thread so a large project never blocks the first frame |
config |
config.toml parsing, defaults, and the format preserving write path the Settings tab uses |
lsp_config |
Reads lsp.lua's declared servers for the Settings tab's Language Servers list |
ui/* |
Rendering: the shell chrome, dashboard, plugin manager, file editor, and overlays |
A file buffer in the TUI is a live editor window rather than a static text view: keystrokes are translated into ctrlvim_core::Key values and fed to the engine's Session::feed, and what renders is the engine's real buffer text, its real cursor position, and its real mode, not a copy the frontend maintains independently.
Gathering project data, recent files, git status, and a line count, involves a full recursive directory walk, a stat per file, several git subprocess calls, and reading every source file to count lines. That is disk bound and, on a large checkout, slow enough to make the editor look hung on launch if done before the first frame draws. Project::load instead starts that work on a background thread immediately and returns an empty snapshot right away; the event loop's idle tick polls for the result and fills the dashboard's panels in a moment after the first frame, rather than holding up startup for it.