Skip to content

Repository files navigation

levim

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.

Download

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.

Build and install from source

cargo run --release -- README.md

Install it locally with Rust:

cargo +1.97.0 install --path .
levim README.md

Keys

Normal mode

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

Insert mode

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.

Mouse

  • 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-b browser, 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.

Commands

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.

Floating terminals

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!.

Themes

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.md

Themes 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.

Workspace sidebar

The sidebar is hidden by default so editing uses the full terminal width. Ctrl-b and Ctrl-g open separate workflows:

  • Ctrl-b toggles all workspace files. Moving the selection with j/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/PageDown or 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-p opens fuzzy file search from anywhere.
  • Ctrl-g independently 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.
  • Enter focuses the already-visible diff; it does not load or replace it. Press Tab to return to the Git list. e, o, Ctrl-o, or right-click opens the working-tree file for editing.
  • Esc, Tab, or q returns 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.

File safety

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.

Validation results

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-v to show, focus, or hide the panel.
  • Use j/k or the arrow keys to select a result.
  • Press Enter or click a diagnostic to open its file and exact line.
  • Press r in the panel to rerun the default task.
  • Press c in the panel to cancel the active run.
  • Press q in 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.

Language tooling

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.

English spelling

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.

Diff review

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.

Current scope

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.

Plugins

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.

About

Rust made vim

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages