HI version is available. Content is displayed in original English for accuracy.
While speccing out this spreadsheet tool, I realized that I never had to think about the keybindings. It all just came naturally from Vim. Normal/insert/visual modes, hjkl navigation, dd/yy/p, :w, :q. The usual muscle memory works.
It supports CSV/TSV import and export, and a native .cell format that preserves formulas. The formula engine handles SUM, AVERAGE, COUNT, MIN, MAX, and IF with range references.
The codebase is a Cargo workspace: a pure cell-sheet-core library (no TUI dependency) and a cell-sheet-tui crate on top of ratatui. Early days, but it's usable.
To try it out: cargo install cell-sheet-tui
Feedback of any kind is greatly appreciated!

Discussion (24 Comments)Read Original on HackerNews
See also: https://github.com/maaslalani/sheets
The reason why I find your software promising is because dealing with csv in plain text is very disorienting with the inconsistent horizontal cell space. A csv-aware text editor would help a lot there with two issues in particular:
- not having to set commas manually, especially not having to count them between empty cells
- being able to navigate using h/j/k/l between cells instead of having to get creative with vim-motions on plain text
OP, if you're reading this:
I would greatly appreciate if this tool could focus on the writing experience and less with anything formula engine related, since xan is already a very performant tool for that, even at large data sets. Replacing or even competing with xan seems very difficult in my eyes, considering both tools are targeting a similar audience (rust, minimalist, and performance), which makes me think cell could focus more on the writing experience, like I said.
There are some bugs I countered right away and I'll get to open issues in due time. Thanks for making this and I hope I can replace neovim with cell for anything csv in the future!
Cell is very much tailored towards what you're looking for. My vision was "Excel but it's (Neo)Vim". Editing files should feel just as smooth as looking at the data. I believe Xan and Cell could actually pipe into each other quite nicely for rendering more complex data.
I'd really appreciate your time to report the bugs you encountered. Looking forward to them.
I will definitely try out your tool and check if it works better to me.
Two random thoughts:
- In excel I often use the dragging feature, i.e. use a formula like $A2 + B$1 and then drag it in both directions. Does your tool have something similar?
- Another nice feature are visualizations. In my tool I added a bar() function that renders a value between 0 and 1 as a bar in the cell. Not sure how flexible that is, but I did have some fun with it.
Drag-fill. Not yet, but the parts are mostly there. The formula layer already carries abs_col/abs_row through tokenization → AST → eval, so $A2 + B$1 parses correctly today; what's missing is the editing op that copies a formula across a range and shifts only the relative components. Opened #17 for it. The tricky part isn't the rewrite, it's the keybinding — Vim doesn't have an obvious idiom for "drag," so I'm leaning toward a visual-selection + fill-from-anchor key (Y is a candidate) or a :fill command. Open to suggestions if you have a feel for what works in a modal editor. It also needs to land on top of the bulk-undo work in #8/#9 so a fill is one undo step instead of N.
bar() / inline visualizations. Love it, opened #18. The interesting design call is whether BAR returns a CellValue::Visual { … } that the renderer dispatches on (correct under column resizes, but ripples into CSV export, copy/paste, and how SUM treats a visual cell), or whether it just returns a string of block-drawing chars at eval time (trivial to add, but width gets baked in at compute time which is wrong). The first is the right answer; the second is a tempting MVP. SPARKLINE(range) is the natural follow-up once the abstraction settles.
#17 — https://github.com/garritfra/cell/issues/17 #18 — https://github.com/garritfra/cell/issues/18
Really, thanks for making and sharing this; so far, I feel calm and happy when I'm using it.
I'd have to look into the tools you mentioned to really understand all the details. Thanks!
I made a comment on that issue which hopefully will inform this --- I will note that while on the surface it's a great idea, the UI will be _very_ challenging and not being able to address it may make it not worth pursuing.
Alternately, maybe the correct approach would be some sort of hybrid where columns/rows can be named and then referred to using said names....
I really miss Lotus Improv, and can't afford Quantrix, and don't have the programming chops to resurrect Flexisheet, so I keep pestering folks about this, but in the meanwhile, I use pyspread....
QQ: does it support programmatic cell access/modifications?
Eg `cell file.cell --write A2 "42"` or `cell file.cell --read "=SUM(A1:A10)"`? Couldn't surmise that from the glimpse I gave the README, but it would be pretty cool for scripting stuff.
I'll try to add this ASAP. :)
The Vim modal model feels almsot native for spreadsheets, and splitting the formula engine into a TUI-free core was prolly a great architectural choice.
How are you thinking about recalculation, dependency tracking, and undo/redo as the sheet grows, especially once multi-cell edits and larger CSVs start stressing the core?
Honestly, the current implementations are pretty naive — they pass the tests and feel snappy on the small sheets I work with, but they'd buckle pretty quickly under real load. Most of what you're asking about is already on the tracker; I opened a batch of issues citing your comment as the prompt.
Recalculation. Right now it's a full recalc on every edit: recalculate collects all formula cells, computes in-degrees across the whole formula set, topo-sorts, and evaluates top to bottom. The dirty flag gets propagated by mark_dirty but isn't actually used to prune work. It's also re-parsing every formula from its raw string on every pass. Two issues cover this: #8 introduces a batch boundary so paste/fill/CSV import trigger one recalc instead of N, and #7 adds criterion benches so we can actually tell whether the parser, the BFS, or the topo sort is the hotspot before optimizing. AST caching on Cell is the obvious next step once #7 confirms parsing dominates.
https://github.com/garritfra/cell/issues/8 https://github.com/garritfra/cell/issues/7
Dependency tracking. The bigger smell is in extract_deps — a range like SUM(A1:A1000) literally enumerates 1000 cell positions into the dep graph, with a HashSet per cell on each side. Fine at hundreds of cells, a disaster at hundreds of thousands. Range expansion is one of the bench cases in #7; the proper fix (interval-keyed deps so ranges stay first-class instead of fanning out) doesn't have its own issue yet — I should open one, since #7 only measures the problem.
Undo/redo. This is the worst offender right now. UndoEntry only had a single-cell variant until very recently; #12 added MultiCellEdit, but #13 tracks two destructive paths I missed — visual-mode d and p/P paste — that still don't push undo entries at all. #9 is the broader coalescing story (one dd = one undo, CSV import = one undo, etc.), tied to the batch mechanism from #8 so a single transaction produces a single undo entry. sort_by_column is also non-undoable today and belongs in that bucket.
https://github.com/garritfra/cell/issues/13 https://github.com/garritfra/cell/issues/9
Larger CSVs. Storage is HashMap<CellPos, Cell> — fine sparse but with overhead per cell; for very wide imports a column-oriented or arena layout would pay off. I haven't profiled it though, so this is speculative; the dependency-graph blowup will hurt before raw storage does. #7 includes a 100k-row CSV load case to put numbers on it.
And #10 is the meta-issue to lift all of this out of source comments and into actual architecture docs, which I probably should have done before posting.
https://github.com/garritfra/cell/issues/10
So: nothing here scales today, but the architecture splits cleanly enough that none of it needs a rewrite — AST caching, dirty-set recalc, range-aware deps, and grouped undo are the four threads, and most have issues attached.
On the AST caching point, worth caching by structural hash of the parsed expression, not the source string. Copy paste with relative references produces different strings but identical AST shape, which hits a lot in financial-model-style workbooks where parallel columns share structure
Also worth a look: the "сalculation chain" docs in Microsot's OOXML SpreadsheetML spec describe how they serialize the dep order in xlsx files. Different problem (persistence vs runtime) but the data model is informative for what level of granularity ends up being practical
I have been looking for something like this for a long time. Maybe this has some potential to become somewhat popular.