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 — and it edits more than text: your game's data as a form, tile sheets, curves and colours as pictures over the code they live in.

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 →
10

Data as a form, not JSON

A .bischema gives your data types; a .bidata opens as a form — sliders, checks, enums, refs — edited with the keys you already use. :bi rename rewrites every data file of the schema.

:bi setSpacegd
learn how →
11

Pictures are buffers too

Open a png and it draws. :tool tileset moves tiles, :tool image crops, blurs and converts, :zoom scales, and a normal map generator puts its result beside the height map.

:tool tileset:zoom
learn how →
12

Schema in, code out

bi gen struct writes a schema and its instances as C, C++, Go, Rust, C3 or Lua — types, defaults, ids and a find(id) — steered by a .bimapping.

bi gen struct
learn how →

See it work

real sessions, step by step
jump

Aim at a word, not a line number

Press s and the screen dims. Type a couple of letters of where you are looking; every match grows a label. One more key and you are there — no Enter, no n n n.

the full lesson →
lstring.c Jump with s 1 / 4
Jump with s: step 0 Jump with s: step 1 Jump with s: step 2 Jump with s: step 3
multiple cursors

Rename four places in one edit

Ctrl-N adds a cursor at the next occurrence, Ctrl-X skips one you do not want. Then edit normally: it happens everywhere, and one u takes it all back.

the full lesson →
lstring.c Rename with Ctrl-N 1 / 6
Rename with Ctrl-N: step 0 Rename with Ctrl-N: step 1 Rename with Ctrl-N: step 2 Rename with Ctrl-N: step 3 Rename with Ctrl-N: step 4 Rename with Ctrl-N: step 5
registers

Paste from anything you deleted

Every yank and delete lands in a ring. "p opens it with a preview, so the line you deleted five edits ago is a search away, not five undos.

the full lesson →
lstring.c The ring and its picker 1 / 6
The ring and its picker: step 0 The ring and its picker: step 1 The ring and its picker: step 2 The ring and its picker: step 3 The ring and its picker: step 4 The ring and its picker: step 5
the property view

Your data, as a form

A .bidata file opens as one row per field — sliders, checks, enums, refs — laid out the way its schema says. It is still the file: :w, u and the modified marker are the buffer’s.

the full lesson →
level1.bidata A data file as a form 1 / 4
A data file as a form: step 0 A data file as a form: step 1 A data file as a form: step 2 A data file as a form: step 3
tools

Tune a curve where it lives

:set editor curve draws the point list under your cursor as a curve, with a form at the edge. Moving a point rewrites the numbers in the file — the picture is a view of the code, not a copy.

the full lesson →
tuning.c The curve editor 1 / 4
The curve editor: step 0 The curve editor: step 1 The curve editor: step 2 The curve editor: step 3
language server

Completion and signatures, no plugin

rust-analyzer starts when the file opens. Type . and the menu is there; type ( and the signature floats above, the parameter you are on underlined. Diagnostics, gd, gr and K come with it.

the full lesson →
src/main.rs Completion and signatures 1 / 6
Completion and signatures: step 0 Completion and signatures: step 1 Completion and signatures: step 2 Completion and signatures: step 3 Completion and signatures: step 4 Completion and signatures: step 5

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 -- <path>Open a file even when it is spelled like a command — `bi ./gen` does the same.
bi --help -h helpThe list of commands; `bi help <command>` or a bare command word lists that one's subcommands.
bi --version -VThe version.
bi config initWrite ~/.config/bi/config.toml with every default commented out. Never overwrites.
bi config editOpen the config directory as a tree, writing the file first when it is not there.
bi gen sample [schema|data|mapping]Write the sample .bischema, .bidata and .bimapping beside you.
bi gen struct --lang <lang> -o <dir> -i <file>A schema and its data as C, C++, Go, Rust, C3 or Lua — types, defaults, ids and find(id).
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.