// A spreadsheet editor that speaks Vim
Edit spreadsheets at the speed of thought. Modal keybindings, live formula evaluation, multi-sheet workbooks, and a full undo stack — all in your terminal.
// capabilities
Built from the ground up for keyboard-driven workflows.
Normal, Insert, Visual, Command, Search, and Recording modes — just like Vim. Every keystroke has purpose.
50+ built-in functions across math, text, logic, lookup, statistical, finance, and date. Volatile functions recalculate every frame — =NOW() auto-displays as YYYY-MM-DD HH:MM with no formatting needed. Tab-completion as you type.
Tab bar navigation, :tabnew, :tabclose, and quick-switch with gt / gT.
Read and write CSV, TSV, XLSX, and ODS (OpenDocument). Native .asat format with bincode + zstd compression.
1000-command undo stack with merge support. Row/column operations, cell edits, pastes — all undoable.
Record key sequences into named registers with q{a-z}. Replay with @{reg} or N@@.
18 built-in themes selectable with :theme. Set theme_name = "nord" in config for a clean one-liner — no hex values needed. Full custom color override support.
Python plugins via PyO3 — enabled by default. Hook into cell changes, mode transitions, and file events. Register custom formula functions via ~/.config/asat/init.py. Manage with :plugins.
:help opens a full-screen overlay with Keybindings and Formulas tabs. Type to filter entries instantly. Tab switches tabs, j/k scroll, q closes.
Formula dependency cycles are detected before evaluation. Cells in a cycle display #CIRC! — no hanging, no crash. Break the cycle to restore normal evaluation.
gd in Normal mode jumps to the first cell referenced in the current formula — like IDE go-to-definition for your spreadsheet.
Press : from any Visual mode to run an ex-command against the entire selection. Style commands like :bold, :fg #hex, :sort all apply to the range.
Sort rows by any column letter (:sort A, :sort B! = descending). Regex find & replace (:s/pat/repl/g). Both are fully undoable.
Hide rows matching a condition with :filter col op val. Freeze top rows or left columns as sticky pane headers.
Define named ranges with :name SALES A1:C10 and use them directly in formulas — =SUM(SALES).
Attach comments to any cell with :note. A corner marker (▸) indicates noted cells at a glance.
Live rules via :cf A1:C10 > 100 bg=#ff0000 — re-evaluated every frame. Supports > < = != contains blank error. :cf clear removes all rules.
Yank operations copy as TSV to your OS clipboard. Ctrl+V in Insert mode pastes from the system clipboard.
. in Normal mode replays your last insert or destructive edit — just like Vim. Works for cell edits, deletes, and style changes.
Select a range in Visual mode and press Ctrl+F to extend a pattern: arithmetic sequences, weekday names (Mon→Tue→…), month names (Jan→Feb→…), or cyclic repeat.
Live formula preview shows → result in the formula bar as you type. Referenced cells highlight in the grid. Formula cells render in muted blue to distinguish them from data.
ci" ci( ci[ ci{ — change content inside delimiter pairs, just like Vim. Works on any cell containing quoted or bracketed text.
// getting started
Pick your platform — pre-built binaries, package managers, or build from source.
Resolves dependencies automatically. Uninstall with sudo apt remove asat.
The install script copies the binary to ~/.local/bin/asat and warns if it isn't on your $PATH.
Pre-built binaries for Linux x86_64, Linux aarch64, macOS arm64, macOS x86_64, and Windows x86_64 are available on the v0.1.19 GitHub Release.
~/.local/bin is on your $PATH.// formula guide
Everything you need to write powerful formulas — syntax, references, functions, and the interactive cell picker.
Start any cell with = to enter formula mode. Formulas are evaluated live — the result appears in the cell, and the raw formula shows in the formula bar.
=42+8→ 50="Hello "&"World"→ Hello World=A1*B1→ product of A1 and B1=SUM(A1:A10)→ sum of 10 cellsReference individual cells, ranges, or cells on other sheets. Use $ to make a reference absolute (it won't shift when copying).
A1relative — shifts when copied$A$1absolute — never shifts$A1col fixed, row relativeA1:C10range from A1 to C10Sheet2.B4cell B4 on Sheet2While typing a formula, press Ctrl+R to enter F-REF mode. Navigate the grid with hjkl. Your formula stays visible in the formula bar the whole time. Press Enter to insert a single-cell reference, or use v to anchor a range then navigate to the end and press Enter.
=SUM(type the start of a formulaStandard arithmetic, comparison, and text operators work across all formula contexts.
+ - * /add, subtract, multiply, divide^power =2^10 → 1024= <> < <= > >=return TRUE or FALSE&concatenate ="Hi"&" "&A1SUM(range)total of a rangeAVERAGE(range)mean valueMIN(range)smallest valueMAX(range)largest valueROUND(n, d)round to d decimal placesABS(n)absolute valueMOD(n, d)remainder after divisionPOWER(n, e)n raised to the power eSQRT(n)square rootINT(n)truncate to integerFLOOR(n, s)round down to multiple of sCEILING(n, s)round up to multiple of sLEN(text)character countLEFT(text, n)first n charactersRIGHT(text, n)last n charactersMID(text, s, n)n chars starting at sTRIM(text)remove leading/trailing spacesUPPER(text)convert to uppercaseLOWER(text)convert to lowercasePROPER(text)Title CaseCONCATENATE(…)join multiple valuesSUBSTITUTE(t,o,n)replace old text with newFIND(find, in)position of substring (case sensitive)TEXT(n, fmt)number to formatted stringIF(cond, t, f)branch on conditionAND(a, b, …)true if all are trueOR(a, b, …)true if any is trueNOT(cond)invert a booleanIFERROR(val, alt)fallback if val is an errorISBLANK(ref)true if cell is emptyISNUMBER(val)true if val is numericISTEXT(val)true if val is textISERROR(val)true if val is an error=SUM($A$1:A1)expanding range=IF(B1>0, B1, 0)clamp negatives to zero=ROUND(A1/B1*100, 1)% to 1 decimal=IFERROR(A1/B1, 0)0 instead of #DIV/0!=Sheet2.B4*1.2reference + calculation// keybind reference
The complete reference for all modes, keybindings, ex-commands, and formula functions.
| Keys | Action |
|---|
| Keys | Action |
|---|---|
| NORMAL MODE — enter / modify cells | |
| i / Enter / F2 | Edit current cell (cursor at end of content) |
| a | Append: edit current cell with cursor at end |
| s | Substitute: clear cell and enter edit mode |
| r | Replace mode: overwrite characters |
| cc | Change: clear cell and enter edit mode |
| ~ | Toggle case of text cell (upper ↔ lower) |
| x / Del / D | Delete cell content (clear to empty) |
| J | Join: concatenate cell below into current cell (space-separated), then clear the cell below |
| Ctrla | Increment number by 1 — or cycle date forward (day → month name → weekday) |
| Ctrlx | Decrement number by 1 — or cycle date backward |
| gd | Go-to definition — jump to the first cell referenced in the current formula |
| gw | Toggle line wrap on current cell (text reflows into merged rows below for vertical merges) |
| U | Unmerge the merged region under the cursor |
| INSERT MODE — while editing a cell | |
| Esc | Confirm edit and return to Normal mode |
| Enter | Confirm edit and move down one row |
| Tab | Confirm edit and move right one column |
| ← → | Move edit cursor left / right within cell text |
| Home | Move edit cursor to start of cell text |
| End | Move edit cursor to end of cell text |
| Backspace | Delete character before edit cursor |
| Delete | Delete character after edit cursor |
| Ctrla | Move edit cursor to start of cell text |
| Ctrle | Move edit cursor to end of cell text |
| Ctrlw | Delete word backward (to previous whitespace) |
| Ctrlu | Delete everything from edit cursor to start of text |
| Ctrlk | Delete everything from edit cursor to end of text |
| Ctrlr | Enter F-REF mode to pick a cell reference (formulas only) |
| Keys | Action |
|---|---|
| o | Insert row below cursor and enter insert mode |
| O | Insert row above cursor and enter insert mode |
| dd | Delete current row (undoable) |
| dC | Delete current column (undoable) |
| >> | Increase current column width (+2 chars) |
| << | Decrease current column width (-2 chars, min 3) |
| = | Auto-fit current column to its widest content |
| + | Increase current row height (+1 terminal line) |
| - | Decrease current row height (-1 terminal line, min 1) |
| _ | Reset row height to auto (1 line) |
| :ic / :insertcol | Insert column to the left of cursor (undoable) |
| :icr / :insertcolright | Insert column to the right of cursor (undoable) |
| :dc / :deletecol | Delete current column (undoable) |
| :ir [N] / :insertrow [N] | Insert row at cursor or line N (undoable) |
| :dr [N] / :deleterow [N] | Delete row at cursor or line N (undoable) |
| :cw <N> / :colwidth <N> | Set current column width to exactly N chars |
| :rh <N> / :rowheight <N> | Set current row height to exactly N lines |
| :sort [asc|desc] | Sort all rows by cursor column (undoable) |
| :s/pat/repl/[g][i] | Find & replace in text cells (undoable) |
All yank operations copy to the system clipboard as tab-separated text, in addition to ASAT's internal register. Paste into any external app with your usual Ctrl+V.
| Keys | Action |
|---|---|
| yy / yr | Yank entire current row → register + system clipboard |
| yc | Yank current cell value only → register + system clipboard |
| y (in visual) | Yank selected cells → register + system clipboard (as TSV) |
| p | Paste after cursor (next row for line yanks) |
| P | Paste before cursor (current row for line yanks) |
| {N}p | Paste N times |
| yS | Copy current cell's style to style clipboard |
| pS | Paste style clipboard onto current cell / visual selection |
| Keys | Action |
|---|---|
| v | Enter character/cell visual mode (rectangular selection) |
| V | Enter line (full-row) visual mode |
| Ctrlv | Enter column block (vertical) visual mode |
| Esc / v (again) | Exit visual mode without action |
| V (in char visual) | Switch to line visual mode |
| MOVEMENT (while selecting) | |
| hjkl / arrows | Extend selection |
| w / b | Extend to next / previous non-empty cell (horizontal) |
| W / B | Extend to next / previous non-empty cell (vertical) |
| 0 / $ | Extend selection to first / last column |
| g / G | Extend selection to first / last row |
| ACTIONS ON SELECTION | |
| M | Merge selection into one spanning cell (undoable) |
| d / x / Del | Delete all cells in selection (undoable) |
| c / s | Clear selection and enter insert mode at top-left cell |
| y | Yank selection → register + system clipboard (tab-separated) |
| S | Insert = SUM(range) formula into the cell immediately below the selection |
| > | Widen all columns in the selection (+2 chars each) |
| < | Narrow all columns in the selection (−2 chars each, min 3) |
| : | Enter command mode with the selection range pre-loaded — style commands apply to all selected cells |
| Keys | Action |
|---|---|
| u | Undo last change (up to 1000 deep) |
| Ctrlr | Redo undone change |
| {N}u | Undo N changes at once |
| m{a–z} | Set named mark at current position (e.g. ma) |
| '{a–z} | Jump to named mark (e.g. 'a) |
| '' | Jump back to position before last mark jump |
| Keys | Action |
|---|---|
| / | Open forward search prompt (supports regex) |
| ? | Open backward search prompt (supports regex) |
| Enter (in search) | Execute search, jump to first match |
| Esc (in search) | Cancel search |
| n | Jump to next search match |
| N | Jump to previous search match |
| * | Search for the content of the current cell |
Tip: search patterns are case-insensitive regex. Use ^Total to match cells starting with "Total", or \d+\.\d{2} to match decimals. Invalid regex falls back to literal substring match.
| |
| Keys | Action |
|---|---|
| Tab / gt / Ctrlt | Switch to next sheet |
| ⇧Tab / gT / CtrlT | Switch to previous sheet |
| :tabnew [name] | Create a new sheet (optional name) |
| :tabedit [name] | Alias for :tabnew |
| :tabclose | Close current sheet (must have at least one remaining) |
| Keys | Action |
|---|---|
| q{a–z} | Start recording macro to named register (e.g. qa). Mode shows REC. |
| q (while recording) | Stop recording, save key sequence to register |
| @{a–z} | Play macro from named register (e.g. @a) |
| @@ | Replay the most recently played macro |
| {N}@{reg} | Play macro N times in a row (e.g. 5@a) |
| Macros record raw key events including mode transitions. Recursive macro playback is safely prevented. | |
| Command | Action |
|---|---|
| : | Enter command mode |
| Esc | Cancel command mode |
:q |
Quit — warns if there are unsaved changes |
:q! | Force quit, discarding all unsaved changes |
:w [file] | Save current workbook (optionally to a new path) |
:wq / :x | Save and quit |
:e <file> | Open a file, replacing current workbook |
:tabnew [name] | Create new sheet |
:tabclose | Close current sheet |
:ic / :insertcol | Insert column at cursor (left) |
:icr / :insertcolright | Insert column to the right of cursor |
:dc / :deletecol | Delete current column (undoable) |
:ir [N] / :insertrow [N] | Insert row at cursor or at line N |
:dr [N] / :deleterow [N] | Delete row at cursor or at line N (undoable) |
:cw <N> / :colwidth <N> | Set current column width to exactly N characters |
:rh <N> / :rowheight <N> | Set current row height to exactly N terminal lines |
:bold | Toggle bold on current cell / visual selection |
:italic | Toggle italic on current cell / visual selection |
:underline | Toggle underline on current cell / visual selection |
:strike | Toggle strikethrough on current cell / visual selection |
:fg <color> | Set foreground (text) colour — hex #rrggbb or named (red, blue, green…) |
:bg <color> | Set background colour — hex or named |
:hl <color> | Highlight: set background with auto-contrast foreground |
:hl | Clear highlight (remove fg and bg colours) |
:align <l/c/r> | Set cell alignment: left, center, or right |
:fmt <spec> | Number format: %, $, 0.00, int, date, none |
:cs | Clear all styles from current cell / visual selection |
:copystyle | Copy current cell's style to style clipboard (same as yS) |
:pastestyle | Paste style clipboard to cell / selection (same as pS) |
:theme | Open the interactive theme picker |
:theme <name> | Apply a named theme directly (Tab-completes available names) |
:set <option> | Set a configuration option at runtime |
:sort [asc|desc] | Sort all rows by the current cursor column (undoable; default asc) |
:s/pat/repl/[g][i] | Find & replace in text cells — g = all occurrences, i = case-insensitive, undoable |
:goto <addr> | Jump to a cell address, e.g. :goto B15 |
:name <NAME> <range> | Define a named range usable in formulas, e.g. :name SALES A1:C10 |
:filter <col> <op> <val> | Hide rows where column doesn't match — ops: = != > < >= <= ~ |
:filter off | Unhide all filtered rows |
:freeze rows <N> | Freeze top N rows as a sticky header |
:freeze cols <N> | Freeze left N columns as a sticky header |
:freeze off | Clear all frozen panes |
:note [text] | Attach a comment to the current cell; :note shows it; :note! clears it |
:colfmt <op> <val> <color> | Conditional format — apply background color to matching cells in the column |
:transpose | Transpose rows and columns in the visual selection |
:dedup | Remove duplicate rows by the current cursor column |
:filldown | Fill the anchor cell's value down through the selection |
:fillright | Fill the anchor cell's value right through the selection |
:fmt thousands | Thousands-separator number format (#,##0); :fmt t2 adds 2 decimal places |
:merge | Merge visual selection (or current cell) into one spanning cell |
:unmerge | Unmerge the merged region under the cursor |
:wrap / :ww | Toggle line wrap on current cell or visual selection |
:help / :h | Open full-screen searchable help (Keybindings + Formulas tabs) |
:plugins | Open plugin manager TUI (engine status, custom functions, reload) |
Tip: press Tab in command mode to cycle completions. Shift+Tab goes backwards. All structural operations are undoable with u.
Start any cell value with = to enter a formula. Formulas are re-evaluated automatically after every edit.
Reference cells with A1, ranges with A1:B10, and other sheets with Sheet2.C4.
Both absolute ($A$1) and relative references are supported.