TurboVI
A vi clone for the terminal: modes, counts, operators, registers, marks, macros, split windows, :s, and find-in-project with a quickfix list. About 4,800 lines of Python.
uv sync
uv run turbovi somefile.py
uv run turbovi one.py two.py # two buffers, :bn between them
uv run turbovi # a scratch buffer
:q to leave, :help for what is here.
What a vim user gets
The grammar, ["x] [count] operator [count] motion, over hjkl w b e W B E 0 ^ $ gg G { } f F t T %. Operators d c y > < with doubling (dd, yy, cc), text objects (iw aw i( a" i{), and x X D C s S r ~ J p P o O.
Modes: normal, insert, visual, visual-line and command.
Paging with <C-f> <C-b> <C-d> <C-u>, by the window rather than by a fixed number, and <C-e> / <C-y> to move the view without moving the cursor.
Search with / ? n N and match highlighting. Undo and redo with u and <C-r>. . repeats the last change, recorded as keystrokes the way vi does it, so a change that ends in insert mode replays whole.
:s with ranges (%, ., $, 1,5, .+1), the g, i and n flags, any non-alphanumeric delimiter, and & in the replacement. One u undoes the whole substitution.
Marks: ma puts one down, `a comes back to the character and 'a to the first non-blank of that line. Every jump leaves a mark, so `` returns from a G or a search. `:marks` lists them. They are not shifted when the text moves, which is the one place this is smaller than vi.
Macros: qa records into register a, q stops, @a plays, 3@a plays three times, @@ repeats the last. They share storage with the yank registers the way vi's do, so "ap pastes a macro as text and @a runs a yanked line. A macro that calls itself stops with a message rather than a stack that runs out.
Several files at once with :e :ls :bn :bp :b, split windows with :sp and :vsp, and <C-w> then w s v c o or h j k l to move between them. <C-p> and :find are a fuzzy picker over the project: type a few letters of the path and it opens in the current window.
Find in project on <C-g> or :grep: a box that searches on every keystroke, grouped by file, with the match underlined where it actually is. ^A case, ^W whole word and ^R regex are shown rather than hidden — a search box whose rules you cannot see is one you stop trusting the first time it misses something you know is there.
Enter fills the quickfix list and jumps, as vim's :grep does. :copen shows the list with a line of context either side, :cn and :cp walk it, Enter in the pane goes to the entry under the cursor, and :cdo s/from/to/g rewrites every line in it and writes the files. The pane is an ordinary buffer whose text is built from the list, which is what saves the editor from growing a second kind of window.
The system clipboard on "+y and "+p, over OSC 52 and a local helper, so it crosses ssh.
Syntax colouring from the file's name. :set syntax=rust colours a file with no suffix, :set nosyntax stops. A search hit and a selection paint over the colouring, because finding something inside a comment has to be visible.
.editorconfig, honoured. Read from the buffer's own directory upwards, stopping at the first root = true, with the nearest section winning: indent_style, indent_size, tab_width, end_of_line, insert_final_newline and trim_trailing_whitespace. Hand-rolled rather than taking a second dependency. charset is deliberately ignored — the encoding is detected from the bytes, and a config file overriding that would silently re-encode every file it was wrong about.
A file opens and saves as itself. The encoding, the line endings and whether the last line had a newline are read once and written back unchanged. A latin-1 file stays latin-1, a CRLF file stays CRLF, and a file with no trailing newline does not gain one.
Settings and recovery
Settings live in $XDG_CONFIG_HOME/turbovi/config, a file of ex commands the way a vimrc is:
set nu
set rnu
sp
A modified buffer is copied to $XDG_DATA_HOME/turbovi/recover/ every few seconds, keyed by a hash of its absolute path, and :recover puts it back after a crash without writing anything to disk. The recovery copy is dropped when the buffer is saved.
What it is not
Not a vim: no plugins, no scripting language, no :g, no folds, no tabs, no persistent undo across sessions yet. Searches are Python re rather than vim's own dialect, which is a stated difference rather than a gap.
Built on turbodesk
turbodesk is the immediate-mode terminal UI library underneath. This editor is the application on it that draws its own buffer: views, events and hooks, with no editor() widget under the text. That is what proved the core carries a real application on its own. Two dialogs are the library's, the <C-p> file picker and the frame around the find box. It lived in that repository as examples/vi/ until 2026-08-26; a smaller showcase version stays there.
Release files for turbovi 0.3.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| turbovi-0.3.3.tar.gz | 62.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| turbovi-0.3.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 133.2 kB
Release files / turbovi-0.3.3.tar.gz
| Download URL | turbovi-0.3.3.tar.gz |
|---|---|
| Size | 62.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
30c6d6c544e08ada4bef94f70397ce3b4a959d493e465a119980cb01f68d7db3
|
|
BLAKE2b-256 checksum How to use checksums |
744a8fb746d5d25e7154192268c777bb571c77557674782d3b22fb680466bbe0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / turbovi-0.3.3-py3-none-any.whl
| Download URL | turbovi-0.3.3-py3-none-any.whl |
|---|---|
| Size | 70.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ec756ce746e09ccc2c8a236575bf4f41cee78bb252766a78fff59a29bdc95ba4
|
|
BLAKE2b-256 checksum How to use checksums |
6d3e7a2a6af3fead4ffc86633036f2f77856c507edca765dfc139ee6b7bcee99
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|