# 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.* ![demo](docs/demo.gif) ## 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 ``` ## 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