Bart's IDE · a modal editor in Rust

Vim muscle memory.
Batteries included.

Tree-sitter, a language server client, git signs, multiple cursors and a file tree are built in, not plugins you assemble. One binary, defaults that are the product, and an update that never breaks your setup.

Install Learn bi → Coming from Vim
cc++goc3rust + 15 more grammars
samples/lua A session in bi
The file tree, two files side by side, a TODO tag and a git sign on the line just added.

What is in the box

eleven lessons of it →
01

Tree-sitter, all the way down

Twenty grammars compiled in. Highlighting is only the start: S selects by structure, ]] walks the parse tree, :symbols lists what is declared, and a closing brace tells you what it closes.

S]]:symbols
learn how →
02

A language server, no plugin

Diagnostics on the text, completion as you type, hover, go to definition, references in a pane, code actions and formatting. rust-analyzer, gopls, clangd, pyright and friends are already configured.

gdgrK]d
learn how →
03

Multiple cursors

Vim has none, so the keys are bi’s own. Every command runs at every cursor — motions, operators, . — and the whole edit is one undo step.

Ctrl-NCtrl-XCtrl-Alt-↓
learn how →
04

Jump to what you can see

s, a few characters, a letter: you are there. The labels sit between the characters instead of over them, so nothing you aim at is hidden.

sCtrl-W f
learn how →
05

Registers as a ring

Every yank, delete and change lands in a 4096-deep ring. The decision moves to paste time, where "p opens a picker with a preview.

"p"n"+y
learn how →
06

Find and replace, with a preview

:find searches the project into a pane you can prune. :replace shows every rewrite before any happens, and applies into buffers, never straight to disk.

:find:replace:g
learn how →
07

Files, buffers, the other file

A file tree that is a pane, a fuzzy file picker, a buffer switcher, and ga to hop between lstring.c and lstring.h — or a test and its implementation.

-gfgbga
learn how →
08

Text tools and the shell

:sort, :align, :case, base64, JSON and uuids as : commands on a selection or range. :! runs a job into a buffer while you keep editing; a range makes it a filter.

:sort:case:!
learn how →
09

Config that is a patch

One TOML file laid over compiled-in defaults, so new defaults keep arriving. :reload without restarting, sixteen themes, and .bi.toml per project.

:set:reloadbi config init
learn how →

Built in, not plugged in

nothing to install, nothing to break

Everything below is a plugin you would choose, install, configure and keep compatible in Neovim. In bi it ships in the binary and is on by default.

Tree-sitterhighlighting, structure select, contextinstead of nvim-treesitter
LSP + completiondiagnostics, hover, actions, auto-importsinstead of lspconfig + nvim-cmp
Surroundys, ds, cs, S in visualinstead of vim-surround
Commentsgc, gcc — language-awareinstead of Comment.nvim
Label jumpss, S, Ctrl-W finstead of leap / flash / hop
Pickersfiles, buffers, registers, historyinstead of telescope / fzf
File treecopy, move, rename, create, deleteinstead of nvim-tree / oil
Git signsgutter marks, +3 ~1 -2 numstatinstead of gitsigns.nvim
Multiple cursorsCtrl-N, Ctrl-X, Ctrl-Alt-↓instead of vim-visual-multi
Shell jobs:! into a buffer, filters, :r !instead of vim-dispatch / asyncrun
Alternate filega, gA, [alternate] rulesinstead of projectionist
Project replace:find, :replace with previewinstead of spectre / grug-far
Indent guidesand :whitespace to audit blanksinstead of indent-blankline
TODO tagsTODO: FIX: HACK: in colourinstead of todo-comments.nvim
Colour swatches#fb4934, rgb(), shader rgb()instead of nvim-colorizer
.editorconfigread, no switch neededinstead of editorconfig-vim

Where bi parts ways with vim

every difference →

Good defaults stayed — counts, operators, text objects, ., :s, :g. A few keys were spent better.

Key
In vim
In bi
s S
cl and cc, spelled shorter
s jumps to a match on screen; S selects a tree-sitter scope
registers
pick one of 36 slots at yank time
every capture goes to a ring; choose at paste time with "p
ga gA
print the character’s code
open the alternate file — header, source, test — or in a split
gf gb
file under the cursor / nothing
fuzzy file picker / buffer switcher
:sp :vs
new window above / left
below / right, and focus moves there
Tab
same byte as Ctrl-I
the same — so it is jump-forward, never buffer-next

Key reference

Motion 13 bindings
h j k lleft, down, up, right — j k keep a goal column
w b e genext / previous word start, next / previous word end
W B E gEthe same over WORDs — foo.bar is one
f F t Tonto / just before a character, within the line
; ,repeat the last find, or reverse it
0 ^ g_ $column zero, first non-blank, last non-blank, end of line
%the bracket matching the one at or after the cursor
{ }previous / next blank line
gg Gfirst line, last line — or line {n}
Ctrl-D Ctrl-Uhalf a window down / up, cursor along
Ctrl-E Ctrl-Ythe window one line down / up
Ctrl-O Ctrl-Iback / forward through the jump list, across files
'' ``toggle to the position before the latest jump
Jump and selectbi 5 bindings
s{chars}{label}every match on screen gets a letter; press it to go there
S{label}a letter at both ends of every scope around the cursor; press one to select it
]] [[next / previous tree-sitter boundary — blocks and arguments
:tsshow every boundary until :ts again
Ctrl-W fa letter in the middle of every window
Operators and editing 12 bindings
d c ydelete, change, yank over a motion — dd cc yy for lines
> < =indent, outdent, reindent by brackets
gc gcccomment / uncomment, language-aware
gq gqqreflow to textwidth, comment leaders kept
D C Yd$, c$, yy
x Xdelete the character under / before the cursor
r{c} ~ Joverwrite, flip case, join lines
i a I A o Oenter insert mode before, after, at line start, end, below, above
Rreplace mode — Backspace restores what was overwritten
Shift-↓ Shift-↑move the line or selected block one row
.repeat the last change — at every cursor
u Ctrl-Rundo, redo — one command is one step
Text objects 4 bindings
iw aw iW aWword, WORD
i" i' i`inside quotes; a" takes them too
i( i[ i{ i<inside brackets; b B alias ( {
ip apparagraph
Surroundbi 5 bindings
ys{motion}{c}wrap what the motion covers — ysiw"
yss{c}wrap the whole line
ds{c}delete the nearest pair
cs{old}{new}change one pair into another
S{c}in visual mode: wrap the selection
Multiple cursorsbi 4 bindings
Ctrl-Na cursor at the next occurrence of the word — or of the selection, in visual
Ctrl-Xskip: move the newest cursor on to the next occurrence
Ctrl-Alt-↓ Ctrl-Alt-↑a cursor on the line below / above
Escback to one cursor
Visual and block 5 bindings
v V Ctrl-Vcharwise, linewise, blockwise
o Oswap the ends; in a block, swap the columns
I A $block: insert at the left / right edge of every row, ragged right edge
p Preplace the selection; p rings what it displaced, P does not
:prefills 'v — the selection itself, whatever its shape
Registersbi 5 bindings
p Ppaste the newest capture after / before
"p "Ppick from everything captured, then paste
"nnamed space: "nyy then :yname x; "np picks among names
"_black hole — delete without capturing
"+y "+psystem clipboard over OSC 52, so it works over SSH
Search and replace 7 bindings
/ ? n Nsearch, smartcase, with a [3/17] counter in the footer
* #whole word under the cursor
:hls :nohhighlight every match / stop
:s/a/b/gsubstitute — & repeats it with its flags, g& over the file
:g/pat/cmd :vrun d, s, m, case, normal… on matching / other lines
:find :find~search the project into a results pane; ~ for regex
:replace /a/b/preview every rewrite; a applies one, A all
Language server 8 bindings
gd gidefinition, implementations
grreferences in a pane — :replace //new/ over it renames
Kwhat the server knows, floated
]d [dnext / previous diagnostic
<leader><leader>code actions
:peek :decldefinition in a split, declaration
:format :diags :lspformat the file, every diagnostic, server status
Ctrl-N Ctrl-P Tabinsert mode: walk and accept completion
Files, buffers, windows 10 bindings
-into the file tree
Ctrl-W ethe tree beside this file, or away again
gf gbfile picker, buffer switcher
ga gAthe alternate file, or it in a vertical split
Ctrl-^the alternate buffer
Ctrl-W s vsplit below / right
Ctrl-W h j k lfocus a neighbour
Ctrl-W + - < > =resize, equalise — :resize 1:2 for ratios
:zenhide gutter, numbers and status rows
:w :q :wa :qa :bdwrite, close window, all, quit, delete buffer
File treebi 8 bindings
l h Enteropen, close, toggle
s Ctrl-Vopen in a vertical split
- +re-root at the parent / here
a r ddcreate, rename, delete
c x pmark to copy, to move, put here
yyank the path
gh Rdotfiles, re-read
gf /find any path, or the rows on screen

Command line

bi [path]
biAn empty buffer.
bi <file>Open a file. Encoding, BOM and line endings are detected and kept.
bi .A directory opens the file tree, rooted there for the whole session.
bi config initWrite ~/.config/bi/config.toml with every default commented out. Never overwrites.
bi config editOpen the config directory as a tree.
BI_CONFIG=<dir>Use another config directory instead of $XDG_CONFIG_HOME/bi or ~/.config/bi.

Install

linux · macos
release tarball
# from https://github.com/qbart/bi/releases/latest
$ tar xzf bi-*.tar.gz
$ sudo mv bi-*/bi /usr/local/bin/bi
from source
# needs Rust and a C compiler (grammars are C)
$ git clone https://github.com/qbart/bi
$ cd bi && cargo build --release
macos
# let Gatekeeper run the downloaded binary
$ xattr -dr com.apple.quarantine /usr/local/bin/bi
config, when you want one
$ bi config init   # defaults, commented out
$ bi config edit   # opens the directory
next Run bi . in a project, then take the first lesson. Language servers start by themselves when their binary is on your PATH.