levim is a small modal text editor that runs directly in the terminal. It is
an intentionally focused first step toward a Neovim-like editor core, written
in Rust.
Prebuilt, unsigned archives are attached to each GitHub release:
| Platform | Archive |
|---|---|
| Linux x86_64 (glibc 2.39+) | levim-linux-x86_64.tar.gz |
| Linux ARM64 (glibc 2.39+) | levim-linux-aarch64.tar.gz |
| macOS Apple Silicon | levim-macos-aarch64.tar.gz |
| macOS Intel | levim-macos-x86_64.tar.gz |
Extract the archive, move levim somewhere on your PATH, and run
levim FILE. The release also contains SHA256SUMS for verification.
cargo run --release -- README.mdInstall it locally with Rust:
cargo +1.97.0 install --path .
levim README.md| Key | Action |
|---|---|
h j k l or arrows |
Move |
0 / $ |
Start / end of line |
gg / G |
First / last line |
i / a |
Insert before / after cursor |
I / A |
Insert at start / end of line |
o / O |
Open a line below / above |
x |
Delete character |
dd |
Delete line |
u / Ctrl-r |
Undo / redo |
: |
Enter command mode |
Ctrl-b |
Toggle the file browser with live preview |
Ctrl-p |
Open fuzzy file search |
Ctrl-g |
Toggle the changed-files and diff browser |
Ctrl-v |
Show, focus, or hide validation results |
Ctrl-t |
Show or hide the floating terminal |
Type normally. Enter, Backspace, Delete, arrow keys, Home, End, and
Tab are supported. Press Ctrl-Space to request language-server completion;
use the arrow keys and Enter/Tab to accept, or Esc to close it. Press
Esc to return to normal mode.
- Left-click editor text to position the cursor.
- Use the wheel over the editor or diff to move or scroll.
- Left-click a sidebar file to select it. In the
Ctrl-bbrowser, selection immediately previews the file; double-click opens it. - Right-click a sidebar file to open it for editing.
- Use the wheel over the sidebar to move its selection.
- Click a validation diagnostic to open its file and line.
- Use the wheel over validation results to move its selection.
- Click a completion suggestion to apply it.
| Command | Action |
|---|---|
:w |
Save |
:w path |
Save as |
:w! |
Force-save over an external change |
:w! path |
Force Save As over an existing file |
:e! |
Discard local changes and reload from disk |
:q |
Quit (refuses if modified) |
:q! |
Quit without saving |
:qt |
Finish every terminal session |
:wq or :x |
Save and quit; pauses for :q acknowledgement after a durability warning |
:wq! or :x! |
Force-save and quit; pauses for :q/:q! acknowledgement after a durability warning |
:theme |
Show the active theme |
:themes |
List available themes |
:theme NAME |
Change theme |
:plugins |
List registered Rust plugins |
:format |
Format the current buffer with its language formatter |
:check |
Run the default validation task |
:check TASK |
Run a named validation task |
:check all |
Run every available validation task |
:checkstop |
Cancel the active validation run |
:checks |
List available validation tasks |
:checkpanel |
Show, focus, or hide validation results |
:terminal |
Show or hide the floating terminal |
:terminal new [NAME] |
Create and focus a terminal session |
:terminal next / :terminal prev |
Switch terminal sessions |
:terminal rename NAME |
Rename the active terminal |
:terminal close |
Close an exited terminal |
:terminal close! |
Terminate and close a running terminal |
:terminals |
List terminal sessions |
Ctrl-s saves in any mode. Ctrl-c returns to normal mode, or quits from
normal mode when there are no unsaved changes.
Ctrl-t opens a centered terminal overlay rooted at the workspace directory.
Each tab owns an independent PTY, so a frontend server, backend server, and
interactive AI CLI can run at the same time. Sessions continue reading output
while the overlay is hidden or another tab is active.
Use Alt-1 through Alt-9 while the overlay is visible to select a tab, or
click a tab with the mouse. The [+] control creates another terminal. All
other keyboard input—including Ctrl-c, Esc, arrows, and function keys—is
sent to the active session. Paste honors applications that request bracketed
paste, and the mouse wheel scrolls terminal history.
Terminal sessions persist for the lifetime of the levim process. Ordinary
quit refuses while a session is still running; :q! quits without saving and
normal application shutdown cleans up its sessions. Use :qt to explicitly
finish every session without leaving the editor. Closing a live tab likewise
requires :terminal close!.
Tokyo Night is the default and colors the full UI, including the editor, sidebar, selections, status line, messages, and both diff layouts.
:theme tokyonight
:theme terminal
Set the startup theme without changing source code:
LEVIM_THEME=terminal levim README.mdThemes are Rust plugins implementing ThemeProvider. The built-in Tokyo Night
palette uses the Night variant's dark background and blue, cyan, green, red,
orange, and purple accents.
The sidebar is hidden by default so editing uses the full terminal width.
Ctrl-b and Ctrl-g open separate workflows:
Ctrl-btoggles all workspace files. Moving the selection withj/k, arrows, or a click immediately shows a read-only, syntax-highlighted preview on the right. The file is not opened in the editor.PageUp/PageDownor the mouse wheel over the preview scrolls it.Enter,e,Ctrl-o, right-click, or double-click opens the selected file for editing./filters the visible files by name.Ctrl-popens fuzzy file search from anywhere.Ctrl-gindependently toggles the Git viewer. Changes are grouped as conflicts, staged changes, and unstaged changes. Moving the selection immediately shows that exact layer's read-only diff on the right.Enterfocuses the already-visible diff; it does not load or replace it. PressTabto return to the Git list.e,o,Ctrl-o, or right-click opens the working-tree file for editing.Esc,Tab, orqreturns focus to the editor without hiding the sidebar.
Preview loading and syntax parsing happen outside the input thread. Requests are latest-only, binary and non-UTF-8 files are rejected, and preview text is limited to the first 1 MiB. A preview never changes the active buffer.
File markers show repository and editor state:
| Marker | Meaning |
|---|---|
M |
Modified in Git |
A |
Added to Git |
D |
Deleted from Git |
R |
Renamed in Git |
C |
Copied in Git |
T |
File type changed |
? |
Untracked |
U |
Merge conflict |
+ |
Unsaved changes inside levim |
The current Git branch appears in the editor status line when available. Git status refreshes automatically, so files changed by an external AI tool appear without restarting the editor. To protect unsaved work, save the current file before opening another file from the sidebar.
Saves use a same-directory temporary file, flush it, atomically replace the
destination, and preserve existing permissions. levim fingerprints the file
on disk and refuses :w if another process—including an AI agent—changed it
after opening or during the save. A clean buffer reloads external changes
automatically; a dirty buffer shows a conflict and waits for :e! or :w!.
If replacement commits but syncing its directory fails, levim treats the
written bytes as saved and reports a durability warning. Combined save-and-quit
commands remain open until a subsequent :q or :q! acknowledges the warning.
Ordinary :w path never overwrites an existing file.
The built-in Cargo validation plugin provides check, test, fmt, and
clippy tasks when the workspace contains Cargo.toml. Tasks run outside the
input thread, and the panel updates as each task completes.
- Press
Ctrl-vto show, focus, or hide the panel. - Use
j/kor the arrow keys to select a result. - Press
Enteror click a diagnostic to open its file and exact line. - Press
rin the panel to rerun the default task. - Press
cin the panel to cancel the active run. - Press
qin the panel to hide it.
Validation refuses to start while the current buffer has unsaved changes so the
result always describes files on disk. Task discovery and diagnostic parsing
belong to the replaceable ValidationProvider Rust plugin; the editor
supervises execution and cancellation.
Tree-sitter syntax highlighting is bundled for TypeScript, TSX, HTML, CSS, Python, Go, Rust, Java, shell scripts, Markdown, YAML, and JSON. In addition to language constructs, the renderer distinguishes Markdown headings, emphasis, strong text, links, inline code, and fenced-language injections, plus structured data keys, booleans, numbers, and escapes. Language detection uses file names, extensions, and shell shebangs. Parsing is debounced and performed away from the input thread; stale results are discarded using document identity and revision checks.
Markdown fence labels are case-insensitive and accept common aliases for
JavaScript/TypeScript, JSX/TSX, Markdown, HTML, CSS, Python, Go, Rust, Java,
shell scripts, YAML, JSON, and JSONC. Labels such as python3, golang,
shellscript, {.Rust}, and rust,ignore are normalized automatically.
Unsupported fence languages remain readable as Markdown code.
:format sends the current in-memory text to the configured formatter and
applies a successful result as one undoable edit. It never runs a shell,
preserves the buffer's LF/CRLF disk policy, enforces output and time limits,
and discards results if the document changed while the command ran.
| Language | Formatter executable | Language-server executable |
|---|---|---|
| TypeScript / TSX | prettier |
typescript-language-server |
| HTML | prettier |
vscode-html-language-server |
| CSS | prettier |
vscode-css-language-server |
| Python | ruff |
basedpyright-langserver or pyright-langserver |
| Go | gofmt |
gopls |
| Rust | rustfmt |
rust-analyzer |
| Java | google-java-format |
jdtls |
| Shell | shfmt |
bash-language-server |
| Markdown | prettier |
marksman |
| YAML | prettier |
yaml-language-server |
| JSON / JSONC | prettier |
vscode-json-language-server |
Install the executable for the language you use and make it available on
PATH. Missing tools are reported in the message line and do not prevent
editing. A matching language server starts automatically for named files.
Live diagnostics appear in the validation panel (Ctrl-v). Completion uses
Ctrl-Space in insert mode and supports keyboard and mouse selection.
The LSP client negotiates UTF-8/UTF-16 positions, uses monotonically increasing document versions even across undo, bounds JSON-RPC messages, drains server stderr, rejects unsupported snippet/additional-edit completions, and shuts servers down when the active file is replaced or the editor exits. Versioned diagnostics are guarded against stale document changes. For safety, versionless push diagnostics are accepted only before the first edit; servers should publish diagnostic versions for continuous live updates.
The bundled local en-US spellchecker runs automatically and marks possible
misspellings with a red underline. Ctrl-v lists spelling warnings alongside
language-server diagnostics.
Markdown prose is checked while code, fenced content, and link destinations are ignored. In programming languages, structured data, and configuration files, only comments and string values are checked so identifiers and keys do not become noise. Spellchecking is debounced, runs outside the input thread, and uses a bundled dictionary: it launches no executable and requires no network access.
Ctrl-g opens a complete, read-only Git viewer. Selection previews the exact
diff immediately; Enter focuses that same diff for navigation:
- staged changes compare
HEAD → index; - unstaged changes compare
index → worktree; - untracked files compare
empty → worktree; - conflicts compare
ours (index stage 2) → worktree.
Press c to toggle a combined HEAD → worktree view for the selected path.
| Key | Action |
|---|---|
j / k or arrows |
Scroll the synchronized diff |
]c / [c |
Next / previous hunk |
]f / [f |
Next / previous changed file |
t |
Toggle side-by-side / inline layout |
c |
Toggle selected-layer / combined diff |
r |
Reload the selected diff |
e / o |
Open the working-tree file for editing |
Ctrl-g |
Show or hide the Git viewer |
Ctrl-b |
Leave changes and switch to files with live preview |
Tab |
Focus the visible sidebar |
q, Esc, or Ctrl-c |
Close diff review |
Side-by-side mode aligns the original and modified lines. Deleted lines use a
red background, inserted lines use green, and stronger highlights identify the
exact changed characters. Inline mode presents deleted and inserted lines in a
single pane, which is easier to read in a narrow terminal. The active diff is
marked with > in the sidebar and refreshes when an external tool edits it.
Initial and refreshed diff loading and comparison run outside the input thread.
Inputs are bounded to 8 MiB per side, 200,000 lines, and 256 KiB per line so a
pathological file cannot consume unbounded diff resources.
This version uses a grapheme-aware line buffer, groups each insert session into one bounded undo change, refreshes workspace/diff/syntax state outside the input thread, supervises formatters and language servers, and performs isolated full-screen redraws for host UI changes plus scoped floating-terminal redraws for PTY output. The next architectural steps are a rope or piece-table buffer, incremental Tree-sitter edits/rendering, selections and operators, AI-session baselines, splits, configuration, and runtime plugin isolation.
levim now builds as a library and binary. Tokyo Night, the terminal fallback
theme, diff algorithm, file-search matcher, workspace discovery, Git change
tracking, Cargo validation, language detection, syntax highlighting,
formatter selection, language-server selection, file preview, and English
spellchecking register through the public Rust plugin API. Those
implementations are replaceable today. See
docs/PLUGIN_ARCHITECTURE.md and the compiling
examples/custom_theme.rs, examples/custom_search.rs,
examples/custom_validation.rs, examples/custom_language.rs, and
examples/custom_preview.rs, examples/custom_spelling.rs, and
examples/custom_frontend.rs examples. A frontend plugin can replace the
complete input/command/sidebar/diff/rendering experience while retaining the
safe editor kernel.
API version 4 currently supports statically linked Rust plugins with structured compatibility and service-validation errors. Registration is transactional: duplicate, incompatible, or panicking plugins cannot leave a partial registry. Runtime loading of independently compiled Rust plugin executables is planned through a versioned MessagePack-RPC protocol.