40-row CSV with obviously-fake sales data (fictional companies like Acme Corp, Wonka Industries, Cyberdyne Systems). demo.improv generated via headless import with Profit formula and a default view showing Region+Product on rows, Date_Month+Measure on columns. Added the import command to the README quick-start section. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
201 lines
6.1 KiB
Markdown
201 lines
6.1 KiB
Markdown
# improvise
|
|
|
|
*Terminal pivot-table modeling in the spirit of Lotus Improv — multidimensional cells, formulas over dimensions instead of cell addresses, and vim-style keybindings for reassigning axes on the fly.*
|
|
|
|

|
|
|
|
## Why this exists
|
|
|
|
Lotus Improv (NeXT, 1991) separated data from its presentation: cells were
|
|
addressed by named dimensions, not grid coordinates, and rearranging a view
|
|
didn't break formulas. That idea never made it into mainstream tools. Excel
|
|
pivot tables borrowed the visual rearrangement but kept cell-address formulas.
|
|
Terminal data tools like sc-im and VisiData do different things well — sc-im is
|
|
a traditional spreadsheet, VisiData is a data explorer — but neither offers the
|
|
dimension-keyed data model that made Improv interesting. improvise is a small
|
|
attempt to bring that model to a modern terminal, with a formula language that
|
|
references category and item names, views that can be rearranged with a single
|
|
keystroke, and a plain-text file format you can diff in git.
|
|
|
|
See [docs/design-notes.md](docs/design-notes.md) for the original product
|
|
vision and non-goals.
|
|
|
|
## Quick start
|
|
|
|
```sh
|
|
nix build .
|
|
./result/bin/improvise examples/demo.improv
|
|
```
|
|
|
|
Or import your own CSV:
|
|
|
|
```sh
|
|
./result/bin/improvise import path/to/data.csv
|
|
```
|
|
|
|
The included `examples/demo.improv` was generated from `examples/demo.csv`:
|
|
|
|
```sh
|
|
improvise import examples/demo.csv \
|
|
--no-wizard \
|
|
--category Region --category Product --category Customer \
|
|
--measure Revenue --measure Cost \
|
|
--time Date --extract Date:Month \
|
|
--axis Region:row --axis Product:row \
|
|
--axis Date_Month:column --axis Measure:column \
|
|
--axis Customer:page --axis Date:none \
|
|
--formula "Profit = Revenue - Cost" \
|
|
--name "Acme Sales Demo" \
|
|
-o examples/demo.improv
|
|
```
|
|
|
|
## Key bindings to try first
|
|
|
|
| Key | Action |
|
|
|-----|--------|
|
|
| `T` | Enter tile mode — reassign category axes |
|
|
| `[` / `]` | Cycle through page-axis items |
|
|
| `>` | Drill into an aggregated cell |
|
|
| `<` | Return from drill-down |
|
|
| `F` | Open the formula panel |
|
|
| `t` | Transpose rows and columns |
|
|
| `?` / `F1` | Full key reference |
|
|
| `:w` | Save |
|
|
| `:q` | Quit |
|
|
|
|
## Installation
|
|
|
|
### With Nix (preferred)
|
|
|
|
```sh
|
|
nix build .
|
|
# or install into your profile:
|
|
nix profile install .
|
|
```
|
|
|
|
### From crates.io
|
|
|
|
```sh
|
|
cargo install improvise
|
|
```
|
|
|
|
### Prebuilt binaries
|
|
|
|
See the [GitHub releases page](https://github.com/fiddlerwoaroof/improvise/releases)
|
|
for prebuilt binaries (Linux x86_64, macOS Intel and Apple Silicon).
|
|
|
|
## The data model
|
|
|
|
Every cell lives at the intersection of named categories and items —
|
|
`(Region=East, Measure=Revenue)` — not at a grid address like `B3`. A model
|
|
can have up to 12 categories, each with an ordered list of items that can be
|
|
organized into collapsible groups (e.g. months grouped into quarters).
|
|
|
|
Formulas reference dimension names, not cell addresses:
|
|
|
|
```
|
|
Profit = Revenue - Cost
|
|
Tax = Revenue * 0.08
|
|
Margin = IF(Revenue > 0, Profit / Revenue, 0)
|
|
BigDeal = Sum(Revenue WHERE Region = "West")
|
|
```
|
|
|
|
The formula language supports `+` `-` `*` `/` `^`, comparisons, `IF`, and
|
|
aggregation functions (`Sum`, `Avg`, `Min`, `Max`, `Count`) with optional
|
|
`WHERE` clauses. Formulas apply uniformly across all intersections — no
|
|
copying, no dragging, no `$A$1` anchoring.
|
|
|
|
## Views and axes
|
|
|
|
A view assigns each category to one of four axes: **row**, **column**,
|
|
**page** (slicer), or **hidden**. The grid layout is a pure function of
|
|
`(Model, View)` → `GridLayout` — transposing is just swapping row and column
|
|
assignments, and it happens in one keystroke (`t`). Page-axis categories act
|
|
as filters: `[` and `]` cycle through items.
|
|
|
|
Press `T` to enter tile mode, where each category appears as a tile in the
|
|
tile bar. Move between tiles with `h`/`l`, then press `Space` to cycle axes
|
|
or `r`/`c`/`p` to set one directly.
|
|
|
|
Records mode (`R`) flips to a long-format view by assigning the virtual
|
|
`_Index` and `_Dim` categories to row and column axes. Drill-down (`>`) on an
|
|
aggregated cell captures a snapshot; edits accumulate in a staging area and
|
|
commit atomically on exit.
|
|
|
|
## File format
|
|
|
|
Models persist to a plain-text `.improv` format that reads like markdown and
|
|
diffs cleanly in git:
|
|
|
|
```
|
|
# Sales 2025
|
|
|
|
## Category: Region
|
|
- North
|
|
- South
|
|
- East [Coastal]
|
|
- West [Coastal]
|
|
> Coastal
|
|
|
|
## Category: Measure
|
|
- Revenue
|
|
- Cost
|
|
- Profit
|
|
|
|
## Formulas
|
|
- Profit = Revenue - Cost [Measure]
|
|
|
|
## Data
|
|
Region=East, Measure=Revenue = 1200
|
|
Region=East, Measure=Cost = 800
|
|
|
|
## View: Default (active)
|
|
Region: row
|
|
Measure: column
|
|
format: ,.2f
|
|
```
|
|
|
|
Gzip-compressed `.improv.gz` is also supported. Legacy JSON is auto-detected
|
|
for backward compatibility.
|
|
|
|
## Import and scripting
|
|
|
|
The import wizard analyzes CSV columns and proposes each as a category
|
|
(string-valued), measure (numeric), time dimension (date-like, with optional
|
|
year/month/quarter extraction), or skip. Multiple CSVs merge automatically
|
|
with a synthetic "File" category.
|
|
|
|
All model mutations go through a typed command registry, so the same
|
|
operations that back the TUI work headless:
|
|
|
|
```sh
|
|
# single commands
|
|
improvise cmd 'add-cat Region' 'add-item Region East' -f model.improv
|
|
|
|
# script file (one command per line, # comments)
|
|
improvise script setup.txt -f model.improv
|
|
```
|
|
|
|
## What's interesting about the architecture
|
|
|
|
All user actions flow through a two-phase command/effect pipeline. Commands
|
|
are pure functions: they receive an immutable `CmdContext` (model, layout,
|
|
cursor position, mode) and return a list of effects. Effects are the only
|
|
things that mutate app state, and each one is a small, debuggable struct.
|
|
This means commands are testable without a terminal, effects can be logged or
|
|
replayed, and the 40+ commands and 50+ effect types are all polymorphic —
|
|
dispatched through trait objects and a registry, not a central match block.
|
|
The keybinding system gives each of the 14 modes its own keymap, with
|
|
Emacs-style prefix keys for multi-stroke sequences.
|
|
|
|
## Expectations
|
|
|
|
improvise is a personal project I built for my own use. I'm sharing it because
|
|
other people might find it useful, but I can't promise active maintenance or
|
|
feature development. Issues and PRs are welcome but may not get a fast
|
|
response. If you want to build on it, fork away.
|
|
|
|
## License
|
|
|
|
Apache-2.0
|