cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

Merge branch 'main' into worktree-agent-aa57c25f63a077089

Dami committed 7260ce3
commit
7260ce3e364185f3e0ea6dcd6e829f41d6754891
parent
22c4df36d9d944f924284e8948f18dde3a0e0eee
parent
e95283ba98c1144d525666304f7291f249e11bd9
author
Dami <dami.etoile@gmail.com>
146 files changed, +8859 -3475
MREADME.md11+-
MROADMAP.md15+-
Mcmd/012/main.go4+-
Mcmd/012/nu_test.go10+-
Mdemos/follow-log.tape15+-
Mdemos/notebook.tape62++-
Mdemos/nu-recipe.tape29+-
Mdocs/contributing/architecture.md145++++---
Mdocs/contributing/testing.md39++
Mdocs/contributing/ux.md4+-
Mdocs/files/following.md11+-
Mdocs/files/format.md80++-
Mdocs/media/follow-log.gifbin
Mdocs/media/notebook.gifbin
Mdocs/media/nu-recipe.gifbin
Mdocs/nushell/README.md32+-
Mdocs/nushell/cookbook.md59+-
Mdocs/nushell/notebooks.md391+++++++++++------
Mdocs/nushell/pipelines.md17+
Mdocs/nushell/types.md2+-
Mdocs/reference/config.md32+-
Mdocs/reference/keys.md37+-
Mdocs/terminal/ssh.md14+-
Me2e/notebook_test.go98+++-
Me2e/screennotebook_test.go90+++-
Me2e/testdata/nu/nu28+-
Me2e/testdata/screens/help-light.html2+-
Me2e/testdata/screens/help-narrow.html2+-
Me2e/testdata/screens/help-wide.html80++--
Me2e/testdata/screens/help.html2+-
Me2e/testdata/screens/macro-menu-light.html10+-
Me2e/testdata/screens/macro-menu.html10+-
Ae2e/testdata/screens/notebook-cells-high-contrast.html30+
Ae2e/testdata/screens/notebook-cells-light.html30+
Ae2e/testdata/screens/notebook-cells.html30+
De2e/testdata/screens/notebook-completion-light.html30-
De2e/testdata/screens/notebook-completion.html30-
Ae2e/testdata/screens/notebook-error-high-contrast.html30+
Ae2e/testdata/screens/notebook-error-light.html30+
Ae2e/testdata/screens/notebook-error.html30+
De2e/testdata/screens/notebook-failed-high-contrast.html30-
De2e/testdata/screens/notebook-failed-light.html30-
De2e/testdata/screens/notebook-failed.html30-
Ae2e/testdata/screens/notebook-output-high-contrast.html30+
Ae2e/testdata/screens/notebook-output-light.html30+
Ae2e/testdata/screens/notebook-output.html30+
De2e/testdata/screens/notebook-regions-high-contrast.html30-
De2e/testdata/screens/notebook-regions-light.html30-
De2e/testdata/screens/notebook-regions.html30-
Ae2e/testdata/screens/notebook-running-high-contrast.html30+
Ae2e/testdata/screens/notebook-running-light.html30+
Ae2e/testdata/screens/notebook-running.html30+
Re2e/testdata/screens/notebook-completion-high-contrast.html → e2e/testdata/screens/notebook-sent-high-contrast.html24+-
Ae2e/testdata/screens/notebook-sent-light.html30+
Ae2e/testdata/screens/notebook-sent.html30+
Ae2e/testdata/screens/notebook-stale-high-contrast.html30+
Ae2e/testdata/screens/notebook-stale-light.html30+
Ae2e/testdata/screens/notebook-stale.html30+
Ae2e/testdata/screens/notebook-wrap-high-contrast.html24+
Ae2e/testdata/screens/notebook-wrap-light.html24+
Ae2e/testdata/screens/notebook-wrap-narrow-high-contrast.html24+
Ae2e/testdata/screens/notebook-wrap-narrow-light.html24+
Ae2e/testdata/screens/notebook-wrap-narrow.html24+
Ae2e/testdata/screens/notebook-wrap.html24+
Me2e/testdata/screens/palette-narrow.html4+-
Me2e/testdata/screens/palette-search-light.html2+-
Me2e/testdata/screens/palette-search.html2+-
Me2e/testdata/screens/palette-wide.html12+-
Me2e/testdata/screens/palette.html2+-
Minternal/config/registry.go20+-
Minternal/fileio/nuon_test.go58++-
Ainternal/fileio/nuonrows.go35++
Ainternal/fileio/sheetscorpus_test.go241+++++++++++
Ainternal/fileio/sheetscorpus_xlsx_test.go96++++
Ainternal/fileio/testdata/sheets_corpus.tsv320++++++++++++++
Ainternal/notebook/graph.go224++++++++++
Ainternal/notebook/notebook.go158+++++++
Ainternal/notebook/notebook_test.go127++++++
Ainternal/notebook/refs.go127++++++
Minternal/nushell/nushell.go139++----
Minternal/nushell/nushell_test.go85+---
Minternal/sheet/file.go44+-
Minternal/sheet/fileread.go6+-
Minternal/sheet/filestream_test.go14+-
Minternal/sheet/linked.go38+-
Minternal/sheet/linkedregions_test.go55+--
Minternal/sheet/live.go10+-
Ainternal/sheet/notebook.go229++++++++++
Ainternal/sheet/notebookfile.go201+++++++++
Minternal/sheet/region.go343++++------------
Minternal/sheet/region_test.go331+++++++++------
Dinternal/sheet/regiondata.go66---
Minternal/sheet/regionfile.go125++---
Dinternal/sheet/regiongraph.go161-------
Minternal/sheet/regionshape.go155+-----
Dinternal/sheet/regionsort.go68---
Minternal/sheet/regionwrite.go143+-----
Minternal/sheet/sheet.go11+-
Dinternal/sheet/shellhistory.go35--
Minternal/sheet/workbook.go4+-
Minternal/ui/commands.go11+-
Minternal/ui/docs_test.go19+
Minternal/ui/file.go14+-
Minternal/ui/filter.go9+-
Minternal/ui/goto_test.go37+-
Minternal/ui/gridlines.go19-
Minternal/ui/help.go1+
Minternal/ui/hosts.go16-
Ainternal/ui/lineedit/area.go240+++++++++++
Ainternal/ui/lineedit/area_test.go93++++
Minternal/ui/linked.go2+-
Minternal/ui/menubar.go4+-
Minternal/ui/model.go42+-
Minternal/ui/monochrome_test.go60++-
Ainternal/ui/nbjob.go166+++++++
Ainternal/ui/nbrun.go407++++++++++++++++++
Ainternal/ui/nbscreen.go290+++++++++++++
Ainternal/ui/nbsend.go201+++++++++
Ainternal/ui/nbview/bench_test.go68+++
Ainternal/ui/nbview/edit.go355++++++++++++++++
Ainternal/ui/nbview/full.go298+++++++++++++
Ainternal/ui/nbview/keys.go373+++++++++++++++++
Ainternal/ui/nbview/markdown.go167++++++++
Ainternal/ui/nbview/output.go409++++++++++++++++++
Ainternal/ui/nbview/panel.go113+++++
Ainternal/ui/nbview/syntax.go241+++++++++++
Ainternal/ui/nbview/view.go405++++++++++++++++++
Ainternal/ui/nbview/view_test.go238+++++++++++
Minternal/ui/notebook.go668+++++++++++++++---------------
Minternal/ui/notebook_test.go476+++++++++++++--------
Dinternal/ui/nukey_test.go26-
Dinternal/ui/nuprompt/nuprompt.go331---------------
Dinternal/ui/nuprompt/nuprompt_test.go100----
Dinternal/ui/nurun.go325---------------
Minternal/ui/overlay.go5+-
Minternal/ui/panel.go67++-
Minternal/ui/pivot.go2+-
Minternal/ui/selection.go2+-
Minternal/ui/sort.go6+-
Minternal/ui/tabs.go4+
Minternal/ui/tabstrip/tabstrip.go11+-
Ainternal/ui/theme/code.go42++
Minternal/ui/theme/scheme.go2+-
Minternal/ui/theme/theme.go22+-
Minternal/ui/view.go5+-
Mwebsite/src/pages/index.tsx2+-
M README.md +6 -5
@@ -48,10 +48,11 @@ rest: building from a clone, importing files, `012 serve` and settings.
4848 nushell module `012 nu --install-module` installs, or as4949 `ls | to nuon | ^012 --pipe | from nuon` without it5050 ([pipelines](docs/nushell/pipelines.md)).51-- **Nushell notebooks**: `012 nu` (or Data > Shell in any workbook) runs nushell52- pipelines whose tables become live, named regions of the sheet;53- `$r1` reads one in the next command, and refreshing it runs what reads54- it ([notebooks](docs/nushell/notebooks.md)).51+- **Nushell notebooks**: `012 nu` (or Data > Shell in any workbook)52+ opens a notebook tab of code and note cells, run with Jupyter's keys,53+ each output under its cell; later cells read it as `$name`, and sent to54+ a sheet it's a live table formulas read as `nu.name`55+ ([notebooks](docs/nushell/notebooks.md)).5556 - **Macros**, recorded or written as Starlark scripts saved with the sheet5657 ([macros](docs/sheets/macros.md)).5758 - **Made for terminals**: the mouse, hyperlinks, the system clipboard over
@@ -67,7 +68,7 @@ rest: building from a clone, importing files, `012 serve` and settings.
6768 | Menus and the command palette (Ctrl+K) | Charts that float over the grid and follow their data |6869 | ![A task list with a color scale, a dropdown and checkboxes](docs/media/rules.gif) | ![A pivot table of sales by region and quarter](docs/media/pivot.gif) |6970 | Conditional formatting and data validation | Pivot tables, live |70-| ![UNIQUE, SORT and FILTER spilling their results](docs/media/arrays.gif) | ![Two nushell pipelines as live regions, the second reading the first](docs/media/notebook.gif) |71+| ![UNIQUE, SORT and FILTER spilling their results](docs/media/arrays.gif) | ![A notebook: a cell reads a CSV, a second reads its output, which is sent to a sheet and summed there](docs/media/notebook.gif) |7172 | Arrays that spill | Nushell notebooks |7273 | ![JEV functions classifying reviews](docs/media/jev.gif) | ![Recording a macro and replaying it](docs/media/macros.gif) |7374 | JEV functions in formulas | Macros |
M ROADMAP.md +6 -9
@@ -27,16 +27,13 @@ rises in measured steps; see [Bounds of support](docs/contributing/limits.md#wha
2727 2828 ### 2. A TUI Jupyter for nushell2929 30-A notebook is its own kind of tab in a workbook: cells, not a grid. Code31-cells hold a nushell pipeline (several lines if needed) with its output32-under it (a scrollable table view, text, a record or an error); note cells33-hold Markdown. You run cells in the order you choose, with the usual34-controls, and send any output to a sheet, where it is a live region that35-formulas, charts and pivots use. It replaces the grid-based notebook sheet.30+Notebook tabs are a TUI Jupyter for nushell31+([Notebooks](docs/nushell/notebooks.md)): code and note cells, outputs in32+place, sent to sheets as live regions. What's ahead makes writing a cell33+feel like nushell's own prompt.3634 3735 | Item | Result | Size |3836 |---|---|---|39-| Notebook tabs: code and note cells, outputs rendered in place (tables that scroll and open full-screen as a grid, text, records, errors), Jupyter keys inside the notebook (Shift+Enter run and next, Ctrl+Enter run, a/b add above/below, dd delete, m/y note/code, Esc/Enter command/edit mode) plus menus and palette; run cell, run all, run above, run below, stop, clear outputs; run counts and timings; cells named for their output (`$files`), stale marks when a cell they read has changed, an opt-in reactive mode that re-runs dependents; send an output to a sheet as a live region (`nu.files`); outputs saved in the workbook up to a size cap; existing notebook sheets converted, one code cell per region; `012 nu` opens a notebook | A TUI Jupyter with nushell and 012 | L |4037 | Code cells highlighted, completed and checked as you type by nushell itself: `nu --ide-ast` token shapes mapped to theme roles, `nu --ide-complete` plus 012's cell, region and sheet names on Tab, `nu --ide-check` errors underlined; debounced in the background, plain text when nu is missing or slow; hover docs through `nu --lsp` later | Writing pipelines in 012 feels like nushell's own prompt | S to M |4138 4239 ### 3. Toward multiplayer
@@ -48,7 +45,7 @@ log macros record. Each step is useful on its own.
4845 4946 | Step | Result | Size |5047 |---|---|---|51-| A nushell region follows a streaming pipeline through the linked regions' live sources (`live.Source`), rows arriving as the pipeline writes them | Pipelines as live sheets | S |48+| A notebook cell's output follows a streaming pipeline through the linked regions' live sources (`live.Source`), rows arriving on its sheet as the pipeline writes them | Pipelines as live sheets | S |5249 | Shared viewing over SSH: several `012 serve` sessions open the same workbook; one edits, the others follow live with their own cursor, scroll and theme; presence shows who is where | Watch-along and review, one writer | M |5350 | Shared editing over SSH: every session edits, the server orders operations (no CRDT needed while one server holds the workbook), per-user undo, presence and edit ownership shown in the grid, saves by the server | Multiplayer 012 over SSH | L |5451
@@ -140,7 +137,7 @@ stream above rather than a design of their own.
140137 141138 - 012 as a stage in a pipeline: `012 -` reads a table from standard input, `012 --pipe` sends the sheet or selection on, with nushell's types kept through NUON: [Pipelines](docs/nushell/pipelines.md)142139 - The `sheet` command: a nushell module shipped in the binary (`012 nu --install-module`), so nu calls 012 without `^012` or NUON on either side: [Pipelines](docs/nushell/pipelines.md#the-sheet-command)143-- `012 nu` and notebook sheets: nushell pipelines at a prompt on the formula bar become live, named regions that read each other and refresh in dependency order, saved as commands and never run on open: [Notebooks](docs/nushell/notebooks.md)140+- Notebook tabs, a TUI Jupyter for nushell: code and note cells with Jupyter's keys, outputs drawn in place and opened full-screen, `$name` between cells, stale marks and a reactive mode, outputs sent to sheets as live regions (`nu.name`), saved up to a cap, and earlier notebook sheets converted on open: [Notebooks](docs/nushell/notebooks.md)144141 145142 **Files**146143
M cmd/012/main.go +2 -2
M cmd/012/nu_test.go +5 -5
M demos/follow-log.tape +8 -7
M demos/notebook.tape +47 -15
@@ -1,7 +1,8 @@
1-# Notebooks: 012 nu opens a notebook at the nu❯ prompt. Each pipeline's2-# table becomes a named region; a second reads the first as $sales, and3-# giving sales a new command runs it again and then the region reading4-# it. Needs nu on the PATH.1+# Notebooks: 012 nu opens a notebook, its first cell ready to type in.2+# A code cell reads a CSV as sales, a second reads it as $sales, and its3+# output goes to a sheet (G) where a formula sums it; with the notebook4+# reactive, editing sales runs both again and the sheet follows. The terminal (ttyd) sends Shift+Enter as Enter, so5+# cells run with Esc then r. Needs nu on the PATH.56 Output out/notebook.gif67 Source lib/setup.tape78
@@ -11,22 +12,53 @@ Sleep 2s
1112 Show1213 1314 Sleep 800ms14-Type "sales = open data/sales.csv | where Region in [North South] | select Region Quarter Units"15+Type@25ms "sales = open data/sales.csv | where Region in [North South] | select Region Quarter Units"1516 Sleep 500ms16-Enter17+Escape18+Type "r"1719 Sleep 1.5s18-Type "$sales | where Units > 250 | sort-by Units --reverse"19-Sleep 500ms20+Type "b"2021 Enter22+Type@25ms "top = $sales | where Units > 250 | sort-by Units --reverse"23+Sleep 500ms24+Escape25+Type "r"2126 Sleep 2s22-Screenshot out/stills/notebook-regions.png23-Type "sales = open data/sales.csv | where Quarter == Q4 | select Region Quarter Units"27+Screenshot out/stills/notebook-cells.png28+Type "G"29+Sleep 800ms30+Enter31+Sleep 1.2s32+Ctrl+K33+Type@60ms "next sheet"2434 Sleep 500ms2535 Enter26-Sleep 2.5s36+Sleep 600ms37+Right Right Right Right38+Type "=SUM(nu.top)" Enter39+Sleep 1.5s40+Screenshot out/stills/notebook-sent.png41+Ctrl+K42+Type@60ms "previous sheet"43+Sleep 500ms44+Enter45+Sleep 600ms46+Ctrl+K47+Type@80ms "reactive"48+Sleep 600ms49+Enter50+Sleep 400ms51+Up Up52+Sleep 300ms53+Enter54+Type@30ms " | where Quarter != Q1"2755 Escape56+Type "r"57+Sleep 2.5s58+Screenshot out/stills/notebook-reactive.png59+Ctrl+K60+Type@60ms "next sheet"2861 Sleep 500ms29-Down Down Down30-Sleep 1.5s31-Screenshot out/stills/notebook-refreshed.png32-Sleep 1.5s62+Enter63+Sleep 2s64+Screenshot out/stills/notebook-sum.png
M demos/nu-recipe.tape +19 -10
M docs/contributing/architecture.md +80 -65
@@ -26,7 +26,8 @@ internal/chart chart layout, text rendering and kitty image encoding
2626 internal/jev the API key's resolution, answer cache and TypeSafe client2727 internal/keyring the OS credential store the API key lives in2828 internal/macro macros: the Starlark scripting API, recorded actions as scripts, step limits29-internal/nushell a notebook's commands: nu run as a process, tables in as NUON files, a table back29+internal/notebook a notebook's document: code and note cells, names, what each reads, run orders, stale outputs, save caps30+internal/nushell a notebook cell's run: nu as a process, tables in as NUON files, NUON back3031 module 012.nu, the nushell module with sheet, embedded for 012 nu --module and --install-module3132 internal/telemetry opt-in JSON log and OTLP export of spans, events and frame stats3233 internal/serve the SSH server: auth, host key, a Model per session (charm.land/wish/v2)
@@ -37,10 +38,10 @@ internal/ui the Bubble Tea model: modes, menus, overlays, rendering
3738 rowtext laying out a row of cell text across the columns on screen3839 formula reading the formula being typed (F4, the word and call at the caret)3940 overlay the contract of what takes over input: Overlay, boxes, mouse events, lists40- lineedit the one-line editor every text field shares41+ lineedit the one-line editor every text field shares, and the multi-line one of notebook cells4142 picker the searchable list behind the palette and every picker4243 cmdline the : command line and its completions43- nuprompt the nushell prompt on the formula bar, its history and completions44+ nbview a notebook tab: its cells, outputs, command and edit modes, the code editor and its language providers4445 findbar find and replace, a bar on the context line4546 themepicker File > Settings > Theme, previewing as it moves4647 rules the conditional formatting and data validation panel
@@ -230,35 +231,35 @@ style.
230231 `#REF!` with the reason. `Set` refuses spilled cells; files keep only231232 the anchor.232233 - **Regions.** A region (`region.go`) is a block of cells whose values233- come from outside the engine, of two kinds: a command region, a234- notebook's nushell command (opaque to the engine) with a label line235- above its table, and a linked file (`linked.go`), a file the UI236- follows, its table at its anchor. Both are one list per sheet, the237- sheet's `regionState`, replaced whole on every change so undo steps238- keep the definitions; what isn't undone (a label's status, the cells239- written, how a linked file's reading goes) is kept beside it240- (`regionMeta`). Their cells have one write path, `writeTable`241- (`regionwrite.go`): a header and rows, written as spilled cells are,242- derived and outside the undo history, only those that differ, with243- the region's cells it no longer needs cleared; a table that would244- overwrite other contents isn't shown, its label or first cell saying245- where, and clearing that cell shows it. Rows arrive as live246- operations, the change stream below. What undo keeps of the rows is a247- policy per kind: a command region's table (`RegionData`, handed over248- by `ShowRegion`) is in the undo state, since a run was the user's249- action and its output can't be had again, and is written again from250- there whenever its region moves; a linked file's rows are only in its251- cells, since undo can't bring back what a file held, so a linked file252- undo brings back or moves is emptied and read again. Placing a cell in253- a region (a format, undo) keeps the region's value. On a notebook254- sheet a command region's table that grows or shrinks inserts or255- deletes rows under it inside the same step, so the rows below move as256- they would by hand (`regionshape.go`). Formulas name a region's table257- as `nu.name`, resolved when evaluated as named ranges are; commands258- read regions as `$name`, which makes a dependency graph259- (`regiongraph.go`) giving refresh and run orders, cycles refused, and260- a linked file in it never runs. The file keeps the definitions261- (`regionfile.go`).234+ come from outside the engine, of two kinds: a linked file235+ (`linked.go`), a file the UI follows, and a notebook cell's output sent236+ to a sheet (`Region.Output`), each its table at its anchor. Both are237+ one list per sheet, the sheet's `regionState`, replaced whole on every238+ change so undo steps keep the definitions; what isn't undone (the239+ cells written, how the rows arrive) is kept beside it (`regionMeta`).240+ Their cells have one write path, `writeTable` (`regionwrite.go`): a241+ header and rows, written as spilled cells are, derived and outside the242+ undo history, only those that differ, with the region's cells it no243+ longer needs cleared; a table that would overwrite other contents244+ isn't shown, its first cell saying where, and clearing that cell shows245+ it. Rows arrive only as live operations, the change stream below, and246+ are never part of the undo state, since undo can't bring back what a247+ file held or a run printed: a region undo brings back or moves is248+ emptied and marked stale, and the UI sends its rows again, the file249+ read again or the cell's output as it is. Placing a cell in a region250+ (a format, undo) keeps the region's value. Formulas name a region's251+ table as `nu.name`, resolved when evaluated as named ranges are. The252+ file keeps the definitions (`regionfile.go`), and converts the command253+ regions of earlier files into notebook cells (`notebookfile.go`).254+- **Notebooks.** A notebook tab is a sheet whose `regionState` holds255+ cells (`notebook.go`), so adding, editing, moving and deleting cells256+ are undo steps as any change is, and the tab is named, moved, hidden257+ and deleted as a sheet is. The document itself, cells, names, what a258+ cell reads, run orders and stale outputs, is `internal/notebook`,259+ which knows nothing of sheets. Outputs aren't undone: the workbook260+ keeps each cell's last `notebook.Output` by the cell's ID, its NUON as261+ nu printed it, and the file keeps them up to the caps262+ (`notebookfile.go`).262263 - **Rules.** A sheet's conditional formats and data validation263264 (`rules.go`, `condfmt.go`, `validation.go`) are lists of rules on264265 ranges, replaced whole on every change so undo steps keep them as they
@@ -279,19 +280,18 @@ style.
279280 280281 ### The change stream281282 282-Rows reach a region through the regions' one write path, `writeTable`.283-A linked file's arrive as `sheet.LiveOp`s, each applied at once by284-`Workbook.ApplyLive`; a command region's table is written the same way285-from its `RegionData`, which a run puts in the undo state:283+Rows reach a region as `sheet.LiveOp`s, each applied at once by284+`Workbook.ApplyLive` through the regions' one write path, `writeTable`:285+what follows a linked file sends them, and the UI sends a notebook286+cell's output to the region it was sent to whenever the cell runs.286287 287288 ```mermaid288289 flowchart TD289290 file[a linked file] -->|polled| source[live.Source]290291 source -->|live.Update| op[sheet.LiveOp]292+ cell[a notebook cell's run] -->|its output, fileio.NUONRows| op291293 op --> apply[Workbook.ApplyLive]292- run[a notebook command's run] -->|ShowRegion, an undo step| data[RegionData]293294 apply --> write[writeTable: the cells that differ, outside the undo history]294- data --> write295295 write --> recalc[recalculation: what reads them, pivots and spills included]296296 recalc --> frame[the next frame draws the cells on screen]297297 ```
@@ -312,8 +312,7 @@ the cells recalculates when the step ends. An op carries whole
312312 values and names nothing but its region, so applying the same ops in the313313 same order to the same workbook makes the same cells: it is what a314314 session following another's over `012 serve` would be sent, beside the315-steps `Batch` records (a command region's run is one of those, since316-its table is in the undo state). `OnLive` times each op for telemetry.315+steps `Batch` records. `OnLive` times each op for telemetry.317316 318317 ### Functions319318
@@ -482,7 +481,7 @@ draw. The components:
482481 | mouse | `mouseState` | drags, hover, double clicks, the fill handle (`mouse.go`, `fill.go`) |483482 | import | `transfer.Transfer`, `pipeState` | the import in progress, its progress display and cancelling (package `transfer`); choosing and placing imports (`transfer.go`, `importplace.go`); standard input read as a sheet, and what a pipeline gets on quitting (`pipe.go`) |484483 | linked files | `followState` | a source (`live.Source`) for each linked region, polled one poll at a time on a command every `live.Interval` and reconciled with the workbook after every update, so undo, opening a file and unlinking need nothing of their own; trust in files outside a workbook's folder (`follow.go`); Data > Linked file and Import's Follow the file (`linked.go`) |485-| notebooks | `nuprompt.Prompt`, `shellState` | the prompt (package `nuprompt`); the commands running one at a time in the background, their queue and what they said (`nurun.go`); the notebook's commands, keys, lines and trust (`notebook.go`) |484+| notebooks | `nbview.View`, `nbState` | a view of each notebook tab, its selection, mode, scroll and editor (package `nbview`); the tab on the screen (`nbscreen.go`); the notebook's commands and keys (`notebook.go`); cells running one at a time in the background, their queue, stale outputs, trust (`nbrun.go`); what a run reads (`nbjob.go`); outputs sent to sheets (`nbsend.go`) |486485 | macros | `recorder`, `macroState` | a recording in progress (`macrorec.go`); a macro running, trust in the file's macros (`macrorun.go`); what scripts act on (`macrohost.go`, `macrohostnav.go`); Data > Macros and the manager (`macro.go`, `macromanage.go`) |487486 | others | `clipboard`, `trace`, `chartState`, `jevRunner`, `terminal`, `session` | what Ctrl+V pastes, a trace being shown, chart commands' target, JEV questions in flight, what the terminal supports and the chart images sent to it, what outlasts the file open (the `:` history, whether keys can be held: `keyboard.go`) |488487
@@ -509,7 +508,7 @@ methods off `ui.Model`'s exported API:
509508 | `sortbar` | `sortbar.Host` | theme, size, the sheet, close, sort (recorded as the bar's command) (5) |510509 | `filterpick` | `filterpick.Host` | theme, size, the edit line, close, the locale (5); what applying and cancelling do are callbacks, as the sheet's filter and a pivot's differ |511510 | `cmdline` | `cmdline.Host` | theme, size, the edit line, close, the commands to complete, run a line, fail, the session's history (8) |512-| `nuprompt` | `nuprompt.Host` | theme, size, the edit line, close, run a line, the words to complete, the workbook's history, what's running, stop it, what the last run said (10) |511+| `nbview` | `nbview.Host` | theme, locale, the cells, a cell's output, how its run stands, run a command, keep a cell's new source (7); the language's highlighter, completer and checker are `nbview.Providers`, small interfaces of their own |513512 | `suggest` | `suggest.Host` | theme, size, the edit line, whether an entry is being typed, the entry's sheet, the formula as parsed, where the formula bar's text starts (7) |514513 | `themepicker` | `themepicker.Host` | a picker's host, and the current theme, the themes directory, preview, keep, whether keys can be held (10) |515514 | `rules` | `rules.Host` | theme, size, the edit line, close, the sheet and selection, save a conditional format or a validation rule, follow a rule removed or moved (each recorded as the commands that do it), the terminal's palette colors (10) |
@@ -555,7 +554,7 @@ hints) are what every overlay is drawn with.
555554 556555 **Packages.** `theme`, `rowtext`, `formula`, `overlay` and `lineedit`557556 depend on nothing in `ui`, so they can be tested and measured alone. The558-components in `picker`, `cmdline`, `nuprompt`, `themepicker`, `findbar`, `rules`,557+components in `picker`, `cmdline`, `nbview`, `themepicker`, `findbar`, `rules`,559558 `sortbar`, `filterpick`, `choicebar`, `shortcuts`, `suggest`, `tabstrip`560559 and `transfer` build on them and reach the model only through their561560 hosts, with unit tests of their own against fake hosts. A component moves out of package
@@ -600,7 +599,7 @@ file followed as it grows: the importers' readers run on a goroutine of
600599 its own over a pipe that waits for the next piece, so a record cut off601600 at the end of one waits for the rest, and rows come out typed as an602601 import types them (`tail.go`). A `live.Source` is anything that yields a603-table's rows over time, which a nushell region following a pipeline602+table's rows over time, which a notebook cell following a pipeline604603 would be too.605604 606605 XLSX is read by 012's own SpreadsheetML reader on `archive/zip` and
@@ -677,34 +676,50 @@ the user's editor (`AllowEditor`), since that starts a program.
677676 678677 ## Notebooks679678 680-`internal/nushell` runs a notebook's command: a `Job` holds the command681-and the NUON of the tables it reads, and the script `nu -c` runs binds682-each table from a file named by an environment variable683-(`let r1 = (open --raw $env.NU012_TABLE_0 | from nuon)`), feeds the684-selection as `$in` on standard input, and ends in `to nuon`, so no data685-is ever spliced into the command. What nu prints is read as it's686-written by the NUON importer, capped at max-cells:679+`internal/nushell` runs a code cell: a `Job` holds the pipeline and the680+NUON of what it reads, and the script `nu -c` runs binds each from a681+file named by an environment variable682+(`let files = (open --raw $env.NU012_TABLE_0 | from nuon)`) and ends in683+`to nuon`, so no data is ever spliced into the pipeline; a range of a684+sheet (`$sheet.A1:C9`) is renamed to a variable of its own first685+(`notebook.Bind`). What nu prints is kept as it is, NUON, the cell's686+output: a later cell reads it byte for byte as `$name`, `nbview` parses687+it once to draw it, and `fileio.NUONRows` reads it as a region's rows to688+send to a sheet:687689 688690 ```mermaid689691 sequenceDiagram690692 participant UI as internal/ui691693 participant R as nushell.Runner692694 participant Nu as nu -c693- UI->>R: a Job: the command, the NUON of the tables it reads694- R->>R: each table to a file, named in NU012_TABLE_0, 1, ...695- R->>Nu: the script, with the selection on standard input696- Nu->>Nu: let r1 = (open --raw $env.NU012_TABLE_0 | from nuon)697- Nu-->>R: the command's table, to nuon698- R-->>UI: rows read as they're written, up to max-cells699- UI->>UI: ShowRegion: the table, one undo step695+ UI->>R: a Job: the pipeline, the NUON of what it reads696+ R->>R: each to a file, named in NU012_TABLE_0, 1, ...697+ R->>Nu: the script698+ Nu->>Nu: let files = (open --raw $env.NU012_TABLE_0 | from nuon)699+ Nu-->>R: the pipeline's value, to nuon700+ R-->>UI: the NUON, up to 512 MB701+ UI->>UI: the cell's output, not an undo step; sent on to its region700702 ```701- `Runner` is the702-seam: tests hand the UI a fake, the e2e screens a stand-in `nu`703-script. The UI runs one command at a time as a `tea.Cmd` with a704-context Esc cancels and a timeout, and shows each table as an undo705-step when it arrives; the queue a refresh makes comes from the706-engine's graph. Trust is the macros': a file's commands run only once707-they were made or trusted on this computer, or the user agrees.703+704+`Runner` is the seam: tests hand the UI a fake, the e2e705+screens a stand-in `nu` script. The UI runs one cell at a time as a706+`tea.Cmd` with a context Stop cancels and a timeout; the queue a run707+makes comes from `notebook.Order` (each cell after those it reads) and708+`notebook.Inputs` (what it reads that hasn't run), and a reactive709+notebook adds `notebook.Dependents`. An output records the source it710+ran and the `Seq` of each output it read, so `notebook.Stale` tells711+which outputs may be out of date without running anything. Trust is712+the macros': a file's cells run only once they were made or trusted on713+this computer, or the user agrees.714+715+`nbview` draws each cell as a block of lines, working out only how many716+lines each takes and drawing those on screen, so an expanded output of717+ten thousand rows costs the rows showing; a table's column widths are718+fitted once, when its output is parsed. The code editor719+(`lineedit.Area`) wraps at the cell's width, before pipes where it can;720+highlighting, completion and diagnostics come from `nbview.Providers`,721+asked in the background once typing pauses, with a tokenizer and the722+notebook's names as the built-in answers.708723 709724 ## Charts710725
M docs/contributing/testing.md +39 -0
@@ -69,6 +69,45 @@ formatted values with 012's. Known differences between Sheets and Excel,
6969 and places where excelize departs from Excel, are listed as skips with a7070 reason.7171 72+## The Sheets corpus73+74+012 follows Google Sheets' formula results where people rely on them75+([UX](ux.md)), and `internal/fileio/testdata/sheets_corpus.tsv` holds76+about 300 formulas that pin it down: dates and times, text, rounding and77+floating point, errors and how they propagate, coercion of text,78+booleans and blanks, lookups and criteria, array sizes, LET and LAMBDA,79+and `TEXT` formats. Each line is a formula, 012's result, Sheets'80+result and a note, separated by tabs. Results read `"text"` quoted,81+`12.5`, `TRUE`, `#N/A`, or `(blank)`. Formulas read a small fixture on a82+sheet named `Data`, and each computes one value: an array is measured83+with `ROWS`, `COLUMNS`, `INDEX` or `TEXTJOIN`, so its size is checked84+and nothing spills.85+86+`TestSheetsCorpus` holds 012 to the results recorded for it, and fails87+on a row where Sheets' result differs unless its note starts88+`on purpose:` and says why; that reason also goes in the docs page for89+the feature. Sheets' result reads `unconfirmed` until it is checked in90+Sheets:91+92+```sh93+# Add formulas as lines of their own, then record 012's results.94+go test ./internal/fileio -run SheetsCorpus -update-corpus95+# Write the corpus as a workbook with 012's XLSX exporter.96+go test ./internal/fileio -run SheetsCorpus -corpus-xlsx=$HOME/012-sheets-corpus.xlsx97+# In Sheets (locale United States): File > Import the workbook, then98+# File > Download > CSV of the Corpus sheet. Record Sheets' results:99+go test ./internal/fileio -run SheetsCorpus -update-from="$HOME/Downloads/012-sheets-corpus - Corpus.csv"100+```101+102+In the workbook, column C holds each formula, which Sheets recalculates103+on import; D and E read its result as text and its kind with formulas104+both 012 and Sheets compute, F holds 012's result and G says whether105+they agree (for two errors, compare C and F by eye). The exporter saves106+formulas Excel has nothing like (`SPLIT`, `REGEXMATCH`, `SORTN`,107+`FLATTEN`, `ARRAYFORMULA` inside a formula) as values, so those rows108+leave C empty and say so in the note: type the formula from B into C109+before downloading, or `-update-from` leaves them unconfirmed.110+72111 ## The XLSX differential test73112 74113 `TestXLSXDifferential` (`internal/fileio/xlsx_diff_test.go`) imports a
M docs/contributing/ux.md +2 -2
@@ -99,12 +99,12 @@ there.
9999 | Entries failing validation | A dotted underline, and `Invalid:` with the rule on the context line |100100 | Values an array spilled | Italic, and `Spilled from B2` on the context line |101101 | Linked files' rows, following, paused or failing | Italic; `●`, `‖` or `!` beside the tab's name and on the context line, with the state in words (`Following`, `Paused`, the error) |102-| A notebook's regions | The label line's text (`r1 ls`, and `Running…` or `failed`) underlined as wide as the table, and `Region r1 of ls` on the context line |102+| A notebook's cells and outputs | `❯` before the tab's name; the selected cell's head in reverse video; the head's words for its state (`[*]` and `running`, `waiting`, `failed`, `stale`); `×` before an error; an output table's header bold and underlined; an output sent to a sheet in italic, with `Output of files` on the context line |103103 | Pivot table results | Their headings (`SUM of Units`, `Grand Total`), and a note in words when an edit is refused |104104 | Protected ranges | A question in words before an edit (`A1:B2 is protected.`) |105105 | Notes | A `▝` in the cell's top-right corner |106106 | Macro recording | `REC` beside the mode indicator |107-| Mode | The mode indicator's word (`READY`, `ENTER`, `POINT`, `MENU`) |107+| Mode | The mode indicator's word (`READY`, `ENTER`, `POINT`, `MENU`, and on a notebook `NOTEBOOK`, `EDIT` or `OUTPUT`) |108108 | Search matches, traced cells | Reverse video, with a count or the list on the context line |109109 | The copied range | A dashed underline |110110 | Dropdowns, checkboxes, active filters | `▾`, or a chip in reverse video between `▐` and `▌`; `[ ]` and `[✓]`; `▼` instead of `▾` |
M docs/files/following.md +6 -5
M docs/files/format.md +53 -27
@@ -88,9 +88,9 @@ save), so they raise no version:
8888 | `conditionalFormats`, `validations` | a sheet | [Rules](#conditional-formats-and-data-validation), one per line |8989 | `arithmetic` | the workbook | `decimal` for [decimal arithmetic](../formulas/decimal.md) |9090 | `locale` | the workbook | The [locale](../sheets/locale.md) it's typed and shown in (`"de-DE"`), when File > Settings > Locale chose one; without it, the file follows the reader's `locale` setting. Cells are stored the same way in every locale: `input` is always as typed in en-US (`1,234.5`, `9/26/2026`, `=ROUND(A1,2)`) |91-| `macros`, `macroOrigin` | the workbook | Macros as Starlark scripts, and the computer they were made or trusted on (macros', shell regions' and [linked files'](following.md#files-from-elsewhere) alike): see [Macro scripting API](../reference/macro-api.md#in-the-file). Opening a file never runs them |92-| `notebook`, `regions` | a sheet | A [notebook sheet](../nushell/notebooks.md), and its shell regions and [linked files](following.md): see [Regions](#regions). Older builds open the sheet without them |93-| `shellHistory` | the workbook | The lines typed at its [notebook prompt](../nushell/notebooks.md#the-prompt), oldest first, for Up and Down |91+| `macros`, `macroOrigin` | the workbook | Macros as Starlark scripts, and the computer they were made or trusted on (macros', notebook cells' and [linked files'](following.md#files-from-elsewhere) alike): see [Macro scripting API](../reference/macro-api.md#in-the-file). Opening a file never runs them |92+| `regions` | a sheet | Its [linked files](following.md) and notebook cells' [outputs sent there](../nushell/notebooks.md#send-to-a-sheet): see [Regions](#regions). Older builds open the sheet without them |93+| `tab`, `reactive`, `notebookCells` | a sheet | A [notebook](../nushell/notebooks.md) tab and its cells: see [Notebooks](#notebooks). Older builds open it as an empty sheet |9494 9595 ## Column and row formats9696
@@ -163,39 +163,65 @@ with its data.
163163 164164 ## Regions165165 166-A sheet's regions, a [notebook's](../nushell/notebooks.md)167-shell regions and [linked files](following.md), are a `regions` list168-after its cells, charts and rules, one per line, in the order they were169-made; a notebook sheet also has `"notebook": true`. The file keeps what170-each region reads, never its table: reopening shows shell regions as not171-run, and nothing runs until you run it; linked files are read again.166+A sheet's regions, [linked files](following.md) and notebook cells'167+[outputs sent there](../nushell/notebooks.md#send-to-a-sheet), are a168+`regions` list after its cells, charts and rules, one per line, in the169+order they were made. The file keeps where each region is and what feeds170+it, never its table: opening the file reads linked files again and sends171+each output from its cell's saved output.172172 173173 ```json174- "notebook": true,175174 "regions": [176- {"name":"r1","command":"ls","at":"A1","rows":12,"cols":4},177- {"name":"big","command":"$r1 | where size > 1kb","at":"A16","rows":3,"cols":4,"reads":["r1"],"sort":[{"column":3,"desc":true}]},175+ {"name":"big","at":"A1","output":true},178176 {"name":"app","at":"F1","path":"logs/app.csv","window":500}179177 ]180178 ```181179 182-- `name` is what commands read it as (`$r1`) and formulas name it by183- (`nu.r1`); `command` is a shell region's nushell pipeline.184-- `at` is its label line's cell; `rows` and `cols` the size of its table185- when saved, header row included, which it keeps until it runs.186-- `reads` are the regions its command reads, `input` the range it read187- as `$in` (`Sheet1!A1:C9`), and `sort` the columns its table is sorted188- by, counting from 1 at its first column, with `desc` for Z to A.189-- A linked file has `path` instead of a command: the file, relative to190- the workbook's folder when it's saved there or below, and otherwise as191- it was linked; `at` is its table's first cell. `format` names the192- file's format (`CSV`, `TSV`, `JSON`, `NUON`, `XLSX`, `SQLite`,193- `Parquet`) when the extension doesn't tell it, `table` or `query` say194- what to read from a SQLite database, and `window` keeps the last rows195- under the header.180+- `name` is what formulas name it by (`nu.big`, `nu.app`) and notebook181+ cells read a linked file as (`$app`); an output's is its cell's.182+- `at` is its table's first cell, the header row's.183+- `output` marks a notebook cell's output.184+- A linked file has `path`: the file, relative to the workbook's folder185+ when it's saved there or below, and otherwise as it was linked.186+ `format` names the file's format (`CSV`, `TSV`, `JSON`, `NUON`,187+ `XLSX`, `SQLite`, `Parquet`) when the extension doesn't tell it,188+ `table` or `query` say what to read from a SQLite database, and189+ `window` keeps the last rows under the header.196190 197191 The table's cells are saved only for their formatting and notes, as a198-spill's are.192+spill's are. A region with a `command`, which the notebook sheets of193+earlier versions wrote (with `input`, the range it read as `$in`, and194+`rows`, `cols`, `reads` and `sort`, which opening it doesn't need),195+opens as a code cell of the workbook's notebook, its table sent where it196+was ([Notebook sheets](../nushell/notebooks.md#notebook-sheets)).197+198+## Notebooks199+200+A notebook tab is a sheet with `"tab": "notebook"`, `"reactive": true`201+when it's [reactive](../nushell/notebooks.md#stale-outputs-and-reactive-notebooks),202+and its cells in `notebookCells`, one per line:203+204+```json205+ "tab": "notebook",206+ "notebookCells": [207+ {"kind":"note","source":"# Files"},208+ {"source":"files = ls","output":"[[name, size]; [go.mod, 812b]]"},209+ {"source":"$files | nope","error":"Command `nope` not found","detail":"help: ..."},210+ {"source":"ls **/*","unsaved":true}211+ ]212+```213+214+- `kind` is `note` for Markdown, left out for code; `source` is what the215+ cell holds, a code cell's name and all, as `files` above.216+- `output` is what the cell's last run printed, as NUON text, kept when217+ it fits the `nu-save-cell-kb` and `nu-save-notebook-kb` caps218+ ([Configuration](../reference/config.md#nushell-notebooks)); `unsaved`219+ marks one left out for its size.220+- `error` and `detail` are a failed run's message and help line, `note`221+ what an output left out.222+223+Run counts, timings and stale marks aren't saved: an output read from224+the file is what its cell's source and inputs gave.199225 200226 ## Conditional formats and data validation201227
M docs/media/follow-log.gif +0 -0

Binary file.

M docs/media/notebook.gif +0 -0

Binary file.

M docs/media/nu-recipe.gif +0 -0

Binary file.

M docs/nushell/README.md +21 -11
@@ -9,8 +9,8 @@ sidebar_position: 1
99 tables: `ls`, `ps`, `open data.csv` and `http get` all hand the next1010 command rows and columns, with file sizes, durations and dates as values1111 of their own type. 012 is a spreadsheet for those tables. A table can go12-through 012 in the middle of a pipeline, and nushell can run inside 012,13-each pipeline's table a live part of the sheet.12+through 012 in the middle of a pipeline, and nushell can run inside 01213+in a notebook, its cells' outputs live parts of the sheets.1414 1515 ![In nushell, ls's table goes through 012, three of its rows are selected and sent on, and nu keeps filtering them by size](../media/pipeline.gif)1616
@@ -19,13 +19,23 @@ each pipeline's table a live part of the sheet.
1919 | Way | Looks like | For |2020 |---|---|---|2121 | [A stage in a pipeline](pipelines.md) | `ls \| sheet \| where size > 1kb` | Looking at a table on the terminal, editing it, and sending it on to the next command |22-| [A notebook](notebooks.md) | `012 nu`, then `ls \| select name size` at the `nu❯` prompt | Pipelines you keep: each one's table is a named region of the sheet that formulas, charts and other pipelines read, run again when you ask |22+| [A notebook](notebooks.md) | `sheet nu`, then `ls \| select name size` in a code cell named `files` | Pipelines you keep, as in Jupyter: cells run when you ask, each output under its cell, read by later cells as `$files` and, sent to a sheet, by formulas and charts |2323 | [A followed file](../files/following.md) | Data > Linked file, then `$app \| where status >= 500` | A log or export that keeps growing: its rows come in as they're written, and pipelines read the rows it has now |2424 25+```mermaid26+flowchart TD27+ pipe["a nushell pipeline: ls | sheet"] --> stage["012 as a stage: edit the table"]28+ stage -->|what you send back| next["the next command"]29+ nb["a notebook: sheet nu"] -->|code cells run nu| out["outputs under their cells"]30+ out -->|G| region["a live region on a sheet: nu.name"]31+ file["a file that grows"] -->|Data > Linked file| linked["a linked region: nu.app"]32+ linked -->|$app| nb33+```34+2535 The first needs nothing but nushell. The other two run `nu` from inside2636 012, so it has to be installed ([Installing27-nushell](https://www.nushell.sh/book/installation.html)); without it the28-prompt says so, and the rest of 012 works as before.37+nushell](https://www.nushell.sh/book/installation.html)); without it a38+cell's output says so, and the rest of 012 works as before.2939 3040 ## Install the `sheet` command3141
@@ -67,27 +77,27 @@ shells run it as `012`.
6777 # A table in, edited, and out again, with its types6878 ls | select name size | sheet | where size > 1kb6979 70-# A notebook: each pipeline's table lands in the grid80+# A notebook of nushell cells, their outputs under them7181 sheet nu work.0127282 ```7383 74-At the notebook's `nu❯` prompt:84+In the notebook's cells, each run with Shift+Enter:7585 7686 ```nu7787 sales = open sales.csv | update Revenue { str replace -ar '[$,]' '' | into float }7888 $sales | group-by Region --to-table | update items { get Revenue | math sum }7989 ```8090 81-The second region reads the first as `$sales`; typing a new command for82-`sales` runs it and then the region that reads it. In a formula on any83-sheet, `=SUM(nu.sales)` reads the same table.91+The second cell reads the first's output as `$sales`; running `sales`92+again marks the second `stale` until it runs too. `G` sends an output to93+a sheet, where `=SUM(nu.sales)` reads it and follows each run.8494 8595 ## Pages8696 8797 | Page | For |8898 |---|---|8999 | [Pipelines](pipelines.md) | `sheet`, `012 -` and `012 --pipe`: tables in on standard input and out on standard output, sending back, the exit status |90-| [Notebooks](notebooks.md) | `012 nu` and the `nu❯` prompt: regions, `$r1` and `$in`, `nu.r1` in formulas, refreshing, saving and trust |100+| [Notebooks](notebooks.md) | `sheet nu` and `012 nu`: code and note cells, their keys, running, `$name`, `$selection` and `$sheet.A1:C9`, outputs sent to sheets as `nu.name`, saving and trust |91101 | [Types](types.md) | How each nushell type becomes a cell and goes back, and what doesn't survive the trip |92102 | [Cookbook](cookbook.md) | Worked examples: disk usage, a log followed live, a CSV cleaned and summarized, an API, processes, git history |93103
M docs/nushell/cookbook.md +33 -26
@@ -5,31 +5,37 @@ sidebar_position: 5
55 66 # Cookbook77 8-Short worked examples. Pipelines that end in `^012` run in a nushell9-session; lines that start with a region's name and `=` are typed at a10-[notebook's](notebooks.md) `nu❯` prompt (`012 nu`, or **Data > Shell** in any workbook). Nushell's own8+Short worked examples. Pipelines that end in `sheet` or `sheet view`9+run in a nushell session, with 012's module installed ([Install the10+`sheet` command](README.md#install-the-sheet-command)); lines that start11+with a name and `=` are code cells of a [notebook](notebooks.md)12+(`sheet nu`, or **Data > Shell > Open notebook** in any workbook), one13+cell a line, each run with Shift+Enter. Nushell's own1114 [cookbook](https://www.nushell.sh/cookbook/) has more pipelines to start1215 from.1316 1417 ## A CSV cleaned, then summarized1518 1619 Money written as text (`"$2,252.50"`) becomes numbers in nushell, and a17-second region totals it by region, with a chart that follows it.20+second cell totals it by region, sent to a sheet with a chart that21+follows it.1822 19-![Nushell turns a CSV's money text into numbers, a second region totals it by region, and a column chart of the totals follows the table](../media/nu-recipe.gif)23+![Nushell turns a CSV's money text into numbers, a second cell totals it by region, and its output, sent to a sheet, feeds a column chart](../media/nu-recipe.gif)2024 2125 ```nu2226 sales = open sales.csv | update Revenue { str replace -ar '[$,]' '' | into float }2327 totals = $sales | group-by Region --to-table | update items { get Revenue | math sum } | rename Region Revenue2428 ```2529 26-To chart it, select the totals table, its header row and rows, and27-**Insert > Chart**. Change `sales`'s command (F2 on its label) to read28-another file, and `totals` runs again after it and the chart redraws.30+To chart it, `G` on `totals` sends its output to a new sheet; there,31+**Insert > Chart** charts the table around the pointer. Edit `sales` to32+read another file and run it: `totals` shows `stale` until it runs too33+(at once in a [reactive notebook](notebooks.md#stale-outputs-and-reactive-notebooks)),34+and then the sheet and the chart follow.2935 30-To pivot rather than total, select the `sales` table and31-**Data > Pivot table** ([Pivot tables](../sheets/pivots.md)): Region in32-rows, Quarter in columns, Revenue in values.36+To pivot rather than total, send `sales` to a sheet and **Data > Pivot37+table** there ([Pivot tables](../sheets/pivots.md)): Region in rows,38+Quarter in columns, Revenue in values.3339 3440 Nushell:3541 [`update`](https://www.nushell.sh/commands/docs/update.html),
@@ -42,20 +48,20 @@ Nushell:
4248 4349 A JSON lines log (`app.jsonl`) linked with **Data > Linked file > Link a4450 table** takes in rows as they're written ([Following45-files](../files/following.md)). A region reads it as `$app`:51+files](../files/following.md)). A notebook's cells read it as `$app`:4652 47-![A JSON lines log grows in a linked region; a shell region keeps its errors, and refreshing it later picks up the errors written since](../media/follow-log.gif)53+![A JSON lines log grows in a linked region; a notebook cell keeps its errors, and running it again later picks up the errors written since](../media/follow-log.gif)4854 4955 ```nu5056 errors = $app | where status >= 500 | select time path status ms5157 by_path = $errors | group-by path --to-table | update items { length } | rename path errors5258 ```5359 54-The linked region grows by itself; Enter on `errors`'s label reads the55-rows the log has now, and `by_path` follows. A log in CSV or TSV works56-the same; a plain text log needs a line of nushell to become a table,57-such as [`parse`](https://www.nushell.sh/commands/docs/parse.html) in a58-region's command:60+The linked region grows by itself; running `errors` again (Ctrl+Enter)61+reads the rows the log has now, and a reactive notebook runs `by_path`62+after it. A log in CSV or TSV works the same; a plain text log needs a63+line of nushell to become a table, such as64+[`parse`](https://www.nushell.sh/commands/docs/parse.html) in a cell:5965 6066 ```nu6167 lines = open --raw app.log | lines | parse "{time} {level} {message}"
@@ -67,23 +73,24 @@ lines = open --raw app.log | lines | parse "{time} {level} {message}"
6773 usage = du * | select path physical | update path { path basename } | sort-by physical --reverse6874 ```6975 70-Sizes arrive in the Size format ([Types](types.md)), so a chart's axis71-and `=SUM(nu.usage)` read them as bytes. Select the table and72-**Insert > Chart** for a column chart; Enter on the label runs `du`73-again.76+Sizes arrive in the Size format ([Types](types.md)), so once `G` sends77+the output to a sheet, a chart's axis and `=SUM(nu.usage)` read them as78+bytes: **Insert > Chart** there for a column chart. Ctrl+Enter on the79+cell runs `du` again, and the sheet follows.7480 7581 Nushell: [`du`](https://www.nushell.sh/commands/docs/du.html).7682 7783 ## An API response as a table7884 7985 ```nu80-http get https://api.github.com/repos/nushell/nushell/issues | select number title user.login comments created_at | update created_at { into datetime } | to nuon | ^012 -86+http get https://api.github.com/repos/nushell/nushell/issues | select number title user.login comments created_at | update created_at { into datetime } | sheet view8187 ```8288 8389 Sort by comments, filter by author, or make a pivot table of issues by8490 author, in the sheet. In a notebook, the same pipeline without85-`| to nuon | ^012 -` is a region that fetches the list again when86-refreshed; `nu-timeout` stops a request that hangs.91+`| sheet view` is a cell that fetches the list again each time it runs;92+Enter on its output opens it full-screen to sort and filter, and93+`nu-timeout` stops a request that hangs.8794 8895 Nushell: [`http get`](https://www.nushell.sh/commands/docs/http_get.html),8996 [`into datetime`](https://www.nushell.sh/commands/docs/into_datetime.html),
@@ -93,7 +100,7 @@ page.
93100 ## Processes, sorted, filtered and sent back94101 95102 ```nu96-ps | where cpu > 1 | sort-by cpu --reverse | select pid name cpu mem | to nuon | ^012 --pipe | from nuon | get pid | each {|pid| kill $pid }103+ps | where cpu > 1 | sort-by cpu --reverse | select pid name cpu mem | sheet | get pid | each {|pid| kill $pid }97104 ```98105 99106 In 012, sort the list and filter it down to the processes to stop
M docs/nushell/notebooks.md +256 -135
@@ -5,177 +5,298 @@ sidebar_position: 3
55 66 # Notebooks77 8-A notebook is a sheet where you type nushell pipelines and each one's9-table lands in the grid, named, typed and kept live. Written like a10-REPL, kept like a spreadsheet.8+A notebook is a tab of the workbook, beside its sheets, that holds cells9+rather than a grid: code cells of nushell, each with its output under it,10+and note cells of Markdown. You run the cells you choose, in the order11+you choose, as in Jupyter; any output can go to a sheet, where formulas,12+charts and pivots read it and it follows the cell's next run.1113 1214 ```nu13-sales = open sales.csv | where Region in [North South] | select Region Quarter Units14-$sales | where Units > 250 | sort-by Units --reverse15+files = ls | where type == file16+$files | where size > 1kb | sort-by size --reverse1517 ```1618 17-![Two pipelines at the nu❯ prompt become regions, the second reading the first as $sales; giving sales a new command runs both again](../media/notebook.gif)19+![A notebook: a cell reads a CSV as sales, a second reads it as $sales, its output is sent to a sheet and summed there, and with the notebook reactive, editing sales runs both again and the sum follows](../media/notebook.gif)1820 19-`012 nu` opens one, at its prompt:21+Open one from a shell with `012 nu`, or from nushell with `sheet nu`22+(`sheet` comes from 012's nushell module: [Install the `sheet`23+command](README.md#install-the-sheet-command)):2024 2125 ```sh22-012 nu # a new notebook23-012 nu work.012 # open one, or make it26+012 nu # a new notebook, its first cell ready to type in27+012 nu work.012 # the workbook's notebook, made if it has none2428 ```2529 26-In any workbook, **Data > Shell** (or the palette, or `!` on a notebook27-sheet) opens the same prompt, writing into the workbook's notebook28-sheet, or a new one named Shell 1 at the first command. On other sheets29-`!` starts an entry, as typing does. Commands run `nu` as a separate30-process, so [nushell](https://www.nushell.sh/book/installation.html)31-must be installed; without it, the prompt says so and the rest of 01230+In any workbook, **Data > Shell > Open notebook** (or the palette)31+shows the workbook's notebook, or adds one after the sheet shown. A32+notebook's tab is marked `❯`. Cells run `nu` as a separate process, so33+[nushell](https://www.nushell.sh/book/installation.html) must be34+installed; without it, a cell's output says so and the rest of 0123235 works as before.3336 34-## The prompt37+## Cells3538 36-The formula bar becomes `nu❯`, and the mode indicator says `NU`:39+A cell is its head, its source and, for a code cell, its output:3740 38-| Key | Does |39-|---|---|40-| Enter | Run the line; the prompt stays open for the next |41-| Tab, Shift+Tab | Complete the word at the caret: a region (`$r1`) or one of nu's commands, from the box under the context line; again for the next |42-| Up, Down | The workbook's earlier lines, those starting with what's typed; the last 100 are saved with the file |43-| Esc | Stop the command running; otherwise, back to the grid |44-45-A name and `=` before the pipeline name the region; without one it's46-`r1`, `r2` and so on:47-48-```nu49-sizes = ls | select name size41+```42+ [2] big ✓ <1s43+│ big = $files | where size > 1kb44+│ | sort-by size --reverse45+ name size46+ README.md 9.8 kB47+ 012 4.2 kB5048 ```5149 52-A name is letters, digits and `_`, not starting with a digit, as a53-nushell [variable's](https://www.nushell.sh/book/variables.html) is;54-`in`, `env`, `nu` and `it` are nushell's own. Typing a name that exists55-gives that region a new command and runs it again, as does F2 on its56-label.57-58-## Regions59-60-Each command's table is a region: a label line with its name and61-command, dimmed and underlined as wide as the table, then the table,62-its first row the column names. Regions stack down the sheet in the63-order they ran, a row between them; when a table grows or shrinks, the64-rows below it move, as if you'd inserted or deleted rows.65-66-A region is still cells of the grid. Its values are67-[typed](types.md) (file sizes in the Size format, durations, dates),68-formulas read them, and you can format them, filter them69-(**Data > Create a filter**), sort them (the sort is kept, so the table70-stays sorted when it runs again), chart them and make pivot tables of71-them: from any of its cells, or its label, with nothing selected, those72-commands take the region's table, from its header row down. Go to73-(Ctrl+G) takes a region's name as formulas write it (`nu.r1`). Like an74-[array's spill](../formulas/arrays.md#spilled-cells), they can't be75-typed over: change the command, or freeze the region. Copies paste76-values; downloads and XLSX hold the values; undo takes back a run (the77-table it replaced comes back) or the region itself.78-79-The context line says what's under the pointer: on a label, the keys80-that refresh and edit it, and why it failed if it did; on its table,81-which region the cell is part of. A label's end says what it's doing:82-`Running…`, `waiting` for a region it reads, `failed`, `stopped`,83-`not run`, or `no rows`.84-85-**Data > Shell regions** acts on the region under the pointer:86-87-| Command | Key | Does |88-|---|---|---|89-| Refresh region | Enter on its label | Runs its command again, then every region that reads it |90-| Run all regions | F9 | Runs every region, each after the regions it reads |91-| Edit region's command | F2 on its label | Opens its command at the prompt, to change it and run it again |92-| Freeze region | | Turns its table into plain values you can edit, and stops running its command |93-| Delete region | | Removes the region, its command and its table |94-| Stop shell command | Esc | Stops the command running, and those waiting after it |50+The head says how many runs came before this one (`[2]`, `[*]` while it51+runs, `[ ]` before it has), the cell's name, and at the right how its run52+stands, `saved` for an output read from the file:53+54+```mermaid55+stateDiagram-v256+ notrun: not run57+ waiting: waiting58+ running: running59+ done: ✓ and how long it took60+ failed: failed, and nu's message under it61+ stale: stale62+ [*] --> notrun63+ notrun --> waiting: Shift+Enter, Ctrl+Enter, F964+ waiting --> running: the cell before it ends65+ running --> done66+ running --> failed: an error, or ii67+ done --> stale: a cell it reads runs again, or its source changes68+ stale --> waiting: run again69+ failed --> waiting: run again70+ done --> waiting: run again71+```9572 96-## Tables between regions73+A source is shown whole: a line too long for the screen wraps before a74+pipe where it can, its next rows indented.9775 98-`$r1` in a command is region r1's table, and `$in` is the range99-selected when the prompt was opened, on a sheet that isn't the notebook,100-read again each time the region runs:76+Outputs are drawn by what they are, with values formatted as cells77+([Types](types.md)):10178 102-```nu103-big = $r1 | where size > 1kb | sort-by size --reverse104-$in | group-by region --to-table79+| Output | Shows |80+|---|---|81+| A table (a list of records) | Its columns fitted to their values, numbers right-aligned, its first 10 rows and how many more there are |82+| A record | Its fields, one a line: `key value` |83+| A list | Its items, numbered from 0, as nushell numbers them |84+| Text | Its lines, wrapped |85+| A value | As a cell shows it: `4.2 kB`, `9/27/2026 11:27:31` |86+| An error | `×` and nushell's message, then its help line |87+88+`o` shows a long output whole, and again its first rows. **Enter** on an89+output opens it full-screen: arrows (or `h` `j` `k` `l`) move, `s` sorts90+by the pointer's column and `S` in descending order (again for the91+output's own order), `/` keeps the rows holding what's typed, and Esc92+goes back. The output itself doesn't change.93+94+Note cells are Markdown: headings, **bold**, *italic*, `code`, links (the95+terminal opens them), lists and quotes.96+97+## Keys98+99+Like Jupyter, a notebook has two modes. In command mode (the mode100+indicator says `NOTEBOOK`) keys act on cells; in edit mode (`EDIT`) they101+type into the selected cell.102+103+```mermaid104+stateDiagram-v2105+ [*] --> Command106+ Command --> Edit: Enter, !107+ Edit --> Command: Esc108+ Edit --> Command: Shift+Enter or Ctrl+Enter runs109+ Command --> Output: Enter on an output110+ Output --> Command: Esc105111 ```106112 107-Both reach nu as NUON in a file it reads, never as text spliced into108-the command: `$r1` is a nushell variable holding a table, and `$in` is109-the pipeline's input, as nushell's own110-[`$in`](https://www.nushell.sh/book/pipelines.html#pipeline-input-and-the-special-in-variable)111-is.113+| Key | In command mode |114+|---|---|115+| Up, Down, `j`, `k` | Move between cells, stopping at each output |116+| Home, End, PgUp, PgDn | The first cell, the last, a screen up or down |117+| Enter | Edit the cell; on an output, open it full-screen |118+| Shift+Enter | Run the cell and select the next, adding one at the end |119+| Ctrl+Enter, `r` | Run the cell, staying on it |120+| Alt+Enter | Run the cell and add a code cell under it |121+| F9 | Run every cell |122+| `a`, `b` | Add a code cell above, below |123+| `!` | Add a code cell below and edit it |124+| `dd` | Delete the cell |125+| `z` | Undo, bringing back a deleted cell (as Ctrl+Z) |126+| `m`, `y` | Make the cell a note (Markdown), or code |127+| `c`, `x`, `v` | Copy, cut, paste a cell below |128+| `n` | Name the cell |129+| `o` | Show the output whole, or its first rows |130+| `G` | Send the output to a sheet |131+| `ii` | Stop: kill the cell running, and forget those waiting |132+| `00` | Restart: stop, clear every output, count runs from 1 |133+134+| Key | In edit mode |135+|---|---|136+| Esc | Keep what's typed and go back to command mode |137+| Shift+Enter, Ctrl+Enter, Alt+Enter | Run, as in command mode |138+| Enter | A new line, as indented as the one before |139+| Up, Down, Home, End | Move by the lines on screen, wrapped lines too |140+| Ctrl+A, Ctrl+E | The start, the end of the line |141+| Tab | Complete the word at the caret: a cell's `$name`, `$selection`, a linked file, or one of nu's commands |142+143+A terminal without the kitty keyboard protocol sends Shift+Enter and144+Ctrl+Enter as Enter ([Keys the terminal has to tell145+apart](../reference/keys.md#keys-the-terminal-has-to-tell-apart)): there146+Esc then `r` runs the cell, and Alt+Enter runs it and adds one under it.147+148+Every action is a command, in **Data > Shell**, the palette and the149+shortcuts (Ctrl+/), and File, Edit and the sheet tabs work as anywhere;150+commands for a sheet's cells are off on a notebook's tab. Changes to151+cells are undo steps, as any edit is.152+153+## Running154+155+Cells run in the background, one at a time, so the screen stays live:156+the one running shows `[*]` and those after it `waiting`. **Data >157+Shell** runs one cell, every cell (F9), the cells above the selected158+one or it and those below; each runs after the cells it reads. A cell159+that fails stops the rest.112160 113-A region reading another depends on it. Refreshing a region runs it114-again and then every region that reads it, directly or through others,115-each after what it reads; a region it reads that hasn't run yet runs116-first. A command reading itself, directly or through others, is117-refused.161+Each cell runs as `nu --no-config-file -c`, so your aliases and custom162+commands aren't there unless `nu-config` is on163+([nushell's configuration](https://www.nushell.sh/book/configuration.html)).164+A cell stops after `nu-timeout` (30 seconds unless set); `ii` stops it165+sooner.118166 119-### Linked files167+## Names and $name120168 121-A [linked file](../files/following.md) is a region too, named after the122-file ([The region](../files/following.md#the-region)), so commands read123-`app.csv` as `$app`. A region reading it doesn't run as rows124-arrive: refreshing it runs its command on the rows the file has now.125-The linked file itself has no command to run.169+A cell that starts `name =` gives its output that name; `n` names a170+cell for you. Later cells read the output as `$name`, a nushell171+variable holding the value, and formulas on any sheet as `nu.name` once172+it's [sent to a sheet](#send-to-a-sheet):126173 127174 ```nu128-errors = $app | where status >= 500 | select time path status ms175+files = ls176+big = $files | where size > 1kb177+$big | get name | str join ", "129178 ```130179 131-[A log file followed live](cookbook.md#a-log-file-followed-live) shows132-it working.133-134-### In formulas180+A name is letters, digits and `_`, not starting with a digit or `__`;181+`in`, `env`, `nu`, `it`, `selection` and `sheet` are taken. Two cells182+can't share a name: the second says so and doesn't run until it's183+renamed.184+185+What a cell reads decides the order cells run in: each after the cells186+it reads, and a cell that hasn't run first when one reading it runs. A187+cell reading itself, directly or through others, doesn't run.188+189+```mermaid190+flowchart TD191+ files["files = ls"] -->|$files| big["big = $files | where size > 1kb"]192+ app["a linked file, app.csv"] -->|$app| errors["errors = $app | where status >= 500"]193+ big -->|G sends it to a sheet| region["the region big, on a sheet"]194+ region --> sum["=SUM(nu.big)"]195+```135196 136-Other sheets read a region by name with `nu.` in front, as a named range137-of its table, header included: `=SUM(nu.big)`, `=VLOOKUP("go.mod",138-nu.r1, 3, FALSE)`, `=COUNTIF(nu.app, 503)`. Formulas follow the table as139-it grows, shrinks or moves, and show `#REF!` while it has none.197+A cell reads the sheets two ways, each a table:140198 141-## Running199+| In a cell | Is |200+|---|---|201+| `$selection` | The range selected on the sheet shown last before the notebook, with its first row as the header |202+| `$sheet.A1:C9`, `$sheet.Sales!A1:C9`, `$sheet.'Q1 data'!B2:B40` | That range, of the sheet shown last or the sheet named |203+| `$app` | A [linked file](../files/following.md) named `app`, its rows as they are when the cell runs |204+205+They reach nu as NUON in a file it reads, never as text spliced into206+the pipeline, so types survive ([Types](types.md)): sizes stay sizes,207+dates stay dates.208+209+### Stale outputs and reactive notebooks210+211+Running a cell again makes the outputs of the cells that read it,212+directly or through others, `stale`: they were made from what it printed213+before. So is a cell's own output once its source is changed.214+215+```mermaid216+flowchart TD217+ run["files runs again"] --> stale["big, and cells reading big: stale"]218+ stale -->|reactive off| you["run them when you want"]219+ stale -->|reactive on| again["they run again, each after what it reads"]220+ you --> fresh["fresh outputs"]221+ again --> fresh222+```142223 143-Commands run in the background, one at a time: the screen stays live,144-and Esc stops the one running and those waiting after it. Each runs as145-`nu --no-config-file -c`, so your aliases and custom commands aren't146-there unless `nu-config` is on147-([nushell's configuration](https://www.nushell.sh/book/configuration.html)).148-A command stops after `nu-timeout` (30 seconds unless set), and keeps at149-most `max-cells` cells, whole rows, saying how many it left out.224+**Data > Shell > Reactive notebook** (off unless turned on, saved225+with the notebook) runs them again whenever a cell they read runs.226+227+## Send to a sheet228+229+`G` sends the selected cell's output to a sheet: a new sheet named after230+the cell, or a cell of a sheet you pick. There it's a region named after231+the cell, italic as a linked file's rows are, that formulas read as232+`nu.name`, header row included (`=SUM(nu.big)`, `=VLOOKUP("go.mod",233+nu.files, 3, FALSE)`), and charts and pivot tables use. A cell without a234+name is given one first.235+236+```mermaid237+sequenceDiagram238+ participant C as Cell big239+ participant R as Region big on a sheet240+ participant F as =SUM(nu.big)241+ C->>R: G sends the output's rows242+ R->>F: recalculates243+ C->>C: runs again244+ C->>R: the new rows replace the old245+ R->>F: recalculates246+```150247 151-What a command prints is read as a table: a record is one row, a list152-of values that aren't records one column named `value`, and a single153-value that column with one row. When a command fails, its label says154-so and the context line gives nushell's message.248+Like an [array's spill](../formulas/arrays.md#spilled-cells), its cells249+can't be typed over: **Data > Shell > Freeze output** turns them into250+plain values, and **Remove output** takes them off the sheet. Undo takes251+back sending it, and the rows come back from the cell's output whenever252+undo brings the region back.155253 156254 ## Saving and trust157255 158-The file keeps each region's command, name, place and what it reads,159-not its table ([format](../files/format.md#regions)), and the lines160-typed at the prompt. Opening a notebook never runs anything: its161-regions say `not run`, and F9 runs them all, each after what it reads.162-163-Like [macros](../sheets/macros.md#macros-from-other-computers), a164-file's commands run as you, with your files, so a notebook saved on165-another computer asks once before they run:166-167-```168-Run this file's shell commands? Enter Run Esc Cancel256+The file keeps the cells and their outputs as NUON257+([format](../files/format.md#notebooks)), so opening it shows every258+output without running anything; the heads say `saved`. An output larger259+than `nu-save-cell-kb` (1 MB), or past `nu-save-notebook-kb` (8 MB) for260+all of them, is left out: the cell shows `not saved; run to see`, and261+saving says which.262+263+Opening a file never runs its cells. Like264+[macros](../sheets/macros.md#macros-from-other-computers), a file's cells265+run as you, with your files, so a notebook saved on another computer266+asks once before its cells run:267+268+```mermaid269+sequenceDiagram270+ participant You271+ participant N as The notebook272+ participant Nu as nu273+ You->>N: open a file saved on another computer274+ N-->>You: its cells and saved outputs, nothing run275+ You->>N: Shift+Enter, or F9276+ N-->>You: Run this file's notebook cells?277+ You->>N: Enter278+ N->>Nu: the cells, one at a time279+ Nu-->>N: their outputs169280 ```170281 171-What you type at the prompt runs without asking. The options, in172-[Configuration](../reference/config.md#nushell-notebooks):282+Cells you write in a notebook made here run without asking. The options,283+in [Configuration](../reference/config.md#nushell-notebooks):173284 174285 | Option | Default | Does |175286 |---|---|---|176-| `shell` | `ask` | `ask` as above; `on` never asks; `off` runs nothing |177-| `nu-timeout` | `30s` | Stops a command that runs longer; `0` lets it run until Esc |178-| `nu-config` | `false` | Runs commands with your `config.nu` and `env.nu` |179-180-[012 serve](../terminal/ssh.md#notebooks) shows notebooks but runs no181-commands unless the server's `serve-shell` is on.287+| `shell` | `ask` | `ask` as above; `on` never asks; `off` runs no cells |288+| `nu-timeout` | `30s` | Stops a cell that runs longer; `0` lets it run until `ii` |289+| `nu-config` | `false` | Runs cells with your `config.nu` and `env.nu` |290+| `nu-save-cell-kb`, `nu-save-notebook-kb` | `1024`, `8192` | How much of the outputs a file keeps |291+292+[012 serve](../terminal/ssh.md#notebooks) shows notebooks and their293+saved outputs, but runs no cells unless the server's `serve-shell` is on.294+295+### Notebook sheets296+297+A file with the notebook sheets of earlier versions of 012, whose298+pipelines were typed at a prompt on the formula bar, opens with each of299+those pipelines as a code cell of the workbook's notebook, named as the300+region was. Their tables stay where they were on the sheet, as the301+cells' outputs sent there, so formulas reading `nu.r1` keep working once302+the cells run; a note on opening says which sheets were converted.
M docs/nushell/pipelines.md +17 -0
M docs/nushell/types.md +1 -1
M docs/reference/config.md +28 -4
@@ -77,6 +77,8 @@ start.
7777 | [`shell`](#shell) | `ask` | `O12_SHELL` |7878 | [`nu-timeout`](#nu-timeout) | `30s` | `O12_NU_TIMEOUT` |7979 | [`nu-config`](#nu-config) | `false` | `O12_NU_CONFIG` |80+| [`nu-save-cell-kb`](#nu-save-cell-kb) | `1024` | `O12_NU_SAVE_CELL_KB` |81+| [`nu-save-notebook-kb`](#nu-save-notebook-kb) | `8192` | `O12_NU_SAVE_NOTEBOOK_KB` |8082 | [`jev-api-key-command`](#jev-api-key-command) | | |8183 | [`jev-credential-store`](#jev-credential-store) | `true` | `O12_JEV_CREDENTIAL_STORE` |8284 | [`jev-base-url`](#jev-base-url) | | `TYPESAFE_BASE_URL` |
@@ -168,7 +170,7 @@ The most cells an import keeps, and a paste or fill writes at once. Numbers and
168170 169171 #### `shell`170172 171-Whether notebook sheets run nushell commands (docs/nushell/notebooks.md). `ask` runs what you type and asks once before running the commands of a file made on another computer; `on` never asks; `off` runs none. Opening a file never runs its commands.173+Whether notebooks run their code cells (docs/nushell/notebooks.md). `ask` runs what you write and asks once before running the cells of a file made on another computer; `on` never asks; `off` runs none. Opening a file never runs its cells.172174 173175 | | |174176 |---|---|
@@ -179,7 +181,7 @@ Whether notebook sheets run nushell commands (docs/nushell/notebooks.md). `ask`
179181 180182 #### `nu-timeout`181183 182-Stop a notebook's command that runs longer than this; 0 lets it run until Esc stops it.184+Stop a notebook cell that runs longer than this; 0 lets it run until Stop (i i) stops it.183185 184186 | | |185187 |---|---|
@@ -190,7 +192,7 @@ Stop a notebook's command that runs longer than this; 0 lets it run until Esc st
190192 191193 #### `nu-config`192194 193-Run notebook commands with your nushell config files (config.nu, env.nu) rather than `nu --no-config-file`, for your own commands and aliases.195+Run notebook cells with your nushell config files (config.nu, env.nu) rather than `nu --no-config-file`, for your own commands and aliases.194196 195197 | | |196198 |---|---|
@@ -199,6 +201,28 @@ Run notebook commands with your nushell config files (config.nu, env.nu) rather
199201 | Environment | `O12_NU_CONFIG` |200202 | Applies | File > Settings > Reload config |201203 204+#### `nu-save-cell-kb`205+206+The largest output of one cell a saved file keeps, in kilobytes of NUON; a larger one is left out and shows `not saved; run to see` when the file is opened.207+208+| | |209+|---|---|210+| Type | number |211+| Default | `1024` |212+| Environment | `O12_NU_SAVE_CELL_KB` |213+| Applies | File > Settings > Reload config |214+215+#### `nu-save-notebook-kb`216+217+How much of all its cells' outputs a saved file keeps, in kilobytes of NUON: the outputs that fit, in the notebook's order.218+219+| | |220+|---|---|221+| Type | number |222+| Default | `8192` |223+| Environment | `O12_NU_SAVE_NOTEBOOK_KB` |224+| Applies | File > Settings > Reload config |225+202226 ### JEV functions203227 204228 #### `jev-api-key-command`
@@ -335,7 +359,7 @@ How many 012 serve sessions may run at once; more are turned away.
335359 336360 #### `serve-shell`337361 338-Let 012 serve sessions run notebooks' nushell commands, as the user 012 serve runs as, following the shell option. Off, served notebooks show their regions but run nothing.362+Let 012 serve sessions run notebooks' code cells, as the user 012 serve runs as, following the shell option. Off, served notebooks show their cells and saved outputs but run nothing.339363 340364 | | |341365 |---|---|
M docs/reference/keys.md +31 -6
@@ -128,13 +128,37 @@ range, F2 renames or repoints it and Ctrl+D deletes it.
128128 129129 ## Notebooks130130 131-| Key | Action |131+On a [notebook's](../nushell/notebooks.md#keys) tab, as in Jupyter:132+command mode (`NOTEBOOK`) acts on cells, edit mode (`EDIT`) types into133+one. **Data > Shell > Open notebook** opens the workbook's notebook;134+on a sheet, `!` starts an entry as any character does.135+136+| Key | In command mode |137+|---|---|138+| Up, Down, `j`, `k`, Home, End, PgUp, PgDn | Move between cells and their outputs |139+| Enter | Edit the cell; on an output, open it full-screen |140+| Shift+Enter, Ctrl+Enter, `r`, Alt+Enter | Run the cell and select the next; run it in place (`r` too); run it and add a cell under it |141+| F9 | Run every cell |142+| `a`, `b`, `!` | Add a code cell above, below; add one below and edit it |143+| `dd`, `z` | Delete the cell; undo |144+| `m`, `y` | Make the cell a note, or code |145+| `c`, `x`, `v` | Copy, cut, paste a cell |146+| `n`, `o`, `G` | Name the cell; show all of its output or its first rows; send the output to a sheet |147+| `ii`, `00` | Stop what's running; restart, clearing every output |148+149+| Key | In edit mode |150+|---|---|151+| Esc | Back to command mode, keeping what was typed |152+| Shift+Enter, Ctrl+Enter, Alt+Enter | Run, as in command mode |153+| Enter, Tab | A new line; complete the word at the caret |154+| Up, Down, Home, End, Ctrl+A, Ctrl+E | Move by the lines on screen; the start, the end of the line |155+156+| Key | On an output full-screen |132157 |---|---|133-| ! | On a notebook sheet, open the nushell prompt on the formula bar (the mode indicator says NU); on other sheets `!` starts an entry, and **Data > Shell** opens the prompt ([Notebooks](../nushell/notebooks.md)) |134-| Enter, Tab, Up, Down, Esc | At the prompt: run the line; complete a region or command; the workbook's earlier lines; stop a command running, or else go back to the grid |135-| Enter, F2 | On a region's label line: run it again (and what reads it); edit its command at the prompt |136-| F9 | Run all regions, each after those it reads |137-| Esc | Stop a region's command while it runs |158+| Arrows, `h`, `j`, `k`, `l`, PgUp, PgDn, `g`, `G` | Move |159+| `s`, `S` | Sort by the pointer's column, A to Z or Z to A; again for the output's order |160+| `/` | Keep the rows holding what's typed; Esc clears it |161+| Esc, `q` | Back to the notebook |138162 139163 ## Keys the terminal has to tell apart140164
@@ -148,6 +172,7 @@ tells them apart and says when a key is let go, and there 012 also does:
148172 |---|---|---|149173 | Shift+Enter | Accept an entry and move up, as in Sheets | As Enter: accept and move down |150174 | Ctrl+I | Italic | As Tab: move right |175+| Shift+Enter, Ctrl+Enter | In a notebook, run the cell (and select the next) | As Enter: edit the cell, or a new line in it; Esc then `r` runs it, and Alt+Enter runs it and adds a cell under it |151176 | Space, held | On a selected chart, show it across the grid until Space is let go; in File > Settings > Theme with nothing typed, hide the list to see the whole sheet in the highlighted theme | Space does what it does there: deselects the chart and starts an entry, or types a space |152177 153178 ## Vim keys
M docs/terminal/ssh.md +7 -7
@@ -88,7 +88,7 @@ the authorized keys. Everything else is closed.
8888 others.8989 - **What a session can do.** Run 012, nothing else; no command is9090 executed, unless the server's config turns on notebooks' nushell91- commands (see [Notebooks](#notebooks)). A shell request starts 012 on a new sheet. An exec request91+ cells (see [Notebooks](#notebooks)). A shell request starts 012 on a new sheet. An exec request9292 is accepted only with a terminal (`ssh -t`) and only when it is one9393 word, which is taken as a file name and never run: it resolves inside9494 the served directory like a name typed in File > Open, and anything
@@ -152,12 +152,12 @@ network or clock access in any case.
152152 ## Notebooks153153 154154 A session opens [notebooks](../nushell/notebooks.md) and shows their155-regions, but runs none of their commands: a command is a program on the156-server, with the server's user and every file it can reach, well157-beyond the served directory. `serve-shell = on` in the server's config158-lets sessions run them, as the user 012 serve runs as, following the159-`shell` option as the local app does; a file's commands still ask once160-per session before they run. Only turn it on when everyone holding an155+cells and saved outputs, but runs none of their cells: a cell is a156+program on the server, with the server's user and every file it can157+reach, well beyond the served directory. `serve-shell = on` in the158+server's config lets sessions run them, as the user 012 serve runs as,159+following the `shell` option as the local app does; a file's cells still160+ask once per session before they run. Only turn it on when everyone holding an161161 authorized key may run programs on the server.162162 163163 ## Unsaved work
M e2e/notebook_test.go +72 -26
@@ -8,8 +8,10 @@ import (
88 "testing"99 )1010 11-// Notebooks with the real nu: a region, one reading it, saving, and the12-// file reopened with its regions not run until F9.11+// Notebooks: cells written and run with the real nu, one reading12+// another's output, saved with their outputs and opened again without13+// running; an output sent to a sheet and kept up to date; the selection14+// read as $selection; and a long pipeline wrapped, never cut.1315 1416 func needNu(t *testing.T) {1517 t.Helper()
@@ -21,41 +23,85 @@ func needNu(t *testing.T) {
2123 func TestNotebookWithNu(t *testing.T) {2224 needNu(t)2325 dir := t.TempDir()24- s := startWith(t, options{dir: dir, startsOn: "nu❯"}, "nu", "book.012")25- s.keys("[[name size]; [a 2kb] [b 10b] [c 5mb]]", "<enter>")26- s.waitFor("r1: 3 rows")26+ s := startWith(t, options{dir: dir, startsOn: "EDIT"}, "nu", "book.012")27+ s.keys("sizes = [[name size]; [a 2kb] [b 10b] [c 5mb]]", "<shift+enter>")28+ s.waitFor("[1] sizes")2729 s.waitFor("5.0 MB")28- s.keys("big = $r1 | where size > 1kb | sort-by size --reverse", "<enter>")29- s.waitFor("big: 2 rows")30- s.keys("nope-not-a-command", "<enter>")31- s.waitFor("r2 failed:")32- s.keys("<esc>", "<ctrl+s>")30+ s.keys("<enter>", "$sizes | where size > 1kb | sort-by size --reverse", "<shift+enter>")31+ s.waitFor("[2]")32+ s.keys("<enter>", "nope-not-a-command", "<ctrl+enter>")33+ s.waitFor("failed")34+ s.keys("<ctrl+s>")3335 s.eventually("saved notebook", func() bool {3436 data, err := os.ReadFile(filepath.Join(dir, "book.012"))35- return err == nil && strings.Contains(string(data), `"reads":["r1"]`)37+ return err == nil && strings.Contains(string(data), `"notebookCells"`) && strings.Contains(string(data), "5000000b")3638 })3739 s.keys("<ctrl+q>")3840 s.waitExit()3941 40- s = startWith(t, options{dir: dir, startsOn: "nu❯"}, "nu", "book.012")41- s.waitFor("r1 [[name size]; [a 2kb] [b 10b] [c 5mb]] not run")42- s.keys("<esc>", "<f9>")43- s.waitFor("big $r1 | where size > 1kb | sort-by size --reverse\n")44- s.waitFor("5.0 MB")42+ s = startWith(t, options{dir: dir, startsOn: "NOTEBOOK"}, "nu", "book.012")43+ s.waitFor("5.0 MB") // the outputs, saved, show without running44+ s.waitFor("saved")45+ s.keys("<f9>")46+ s.waitFor("[1] sizes")4547 }4648 47-// Data > Shell in an ordinary workbook makes a Shell 1 sheet, and $in is48-// the selection. (! types itself there; it opens the prompt only on a49-// notebook sheet.)50-func TestShellFromWorkbook(t *testing.T) {49+func TestNotebookSendsToSheetWithNu(t *testing.T) {50+ needNu(t)51+ s := startWith(t, options{startsOn: "EDIT"}, "nu")52+ s.keys("n = [[k]; [1] [2] [3]]", "<ctrl+enter>")53+ s.waitFor("[1] n")54+ s.keys("G", "<enter>")55+ s.waitFor("Sent n to")56+ s.keys("<ctrl+pgdown>", "<ctrl+home>", "<right>", "<right>", "=SUM(nu.n)", "<enter>")57+ s.waitFor(" 6")58+ s.keys("<ctrl+pgup>", "<enter>", " | append {k: 10}", "<ctrl+enter>")59+ s.waitFor("[2] n")60+ s.keys("<ctrl+pgdown>")61+ s.waitFor(" 16")62+}63+64+// Data > Shell from a workbook makes a Notebook tab; $selection is65+// the range selected on the sheet shown before it.66+func TestNotebookReadsSelection(t *testing.T) {5167 needNu(t)5268 s := start(t, "")5369 s.keys("n", "<enter>", "3", "<enter>", "4", "<enter>", "<up>", "<up>", "<up>", "<shift+down>", "<shift+down>", "<shift+down>")5470 s.waitFor(" A1:A4 ")55- s.keys("<ctrl+k>", "Shell")56- s.waitFor("Run a nushell pipeline")57- s.keys("<enter>", "$in | math sum", "<enter>")58- s.waitFor("r1: 1 row")59- s.waitFor("Shell 1")60- s.waitFor(" 7")71+ s.keys("<ctrl+k>", "Open notebook", "<enter>")72+ s.waitFor("NOTEBOOK")73+ s.keys("!", "$selection | get n | math sum", "<ctrl+enter>")74+ s.waitFor("[1]")75+ s.waitFor(" 7")76+ if !strings.Contains(s.screen(), "Notebook") {77+ t.Errorf("no Notebook tab:\n%s", s.screen())78+ }79+}80+81+// A 200-character pipeline at 80 columns is on the screen whole, wrapped82+// before its pipes, while it's edited and once it isn't.83+func TestNotebookLongPipeline(t *testing.T) {84+ s := startWith(t, options{cols: 80, rows: 24, startsOn: "EDIT", env: fakeNu}, "nu")85+ var b strings.Builder86+ b.WriteString("ls")87+ for i := 0; b.Len() < 200; i++ {88+ b.WriteString(" | where size > " + strings.Repeat("9", i%4+1) + "kb")89+ }90+ src := b.String()91+ s.keys(src)92+ whole := func() bool {93+ var got []string94+ for _, l := range strings.Split(s.screen(), "\n") {95+ if rest, ok := strings.CutPrefix(l, "│ "); ok {96+ got = append(got, strings.Fields(rest)...)97+ }98+ }99+ return strings.Join(got, " ") == strings.Join(strings.Fields(src), " ")100+ }101+ s.eventually("the whole pipeline, edited", whole)102+ s.keys("<esc>")103+ s.waitFor("NOTEBOOK")104+ if !whole() {105+ t.Errorf("the pipeline isn't whole after editing:\n%s", s.screen())106+ }61107 }
M e2e/screennotebook_test.go +61 -29
@@ -5,10 +5,12 @@ import (
55 "path/filepath"66 )77 8-// Golden screens for notebooks: two regions, the second reading the9-// first; the prompt completing a command; a command that failed. Each is10-// recorded on a light terminal and in the high-contrast theme too. nu is11-// testdata/nu/nu, which answers what the screens type with fixed tables.8+// Golden screens for notebooks: cells with a note and table outputs, a9+// cell edited with a long pipeline wrapped (at 80 and 60 columns), a10+// failure, a cell running and one waiting, stale outputs, an output11+// full-screen, and an output sent to a sheet. Each is recorded on a12+// light terminal and in the high-contrast theme too. nu is testdata/nu/nu,13+// which answers what the screens run with fixed outputs.1214 1315 // fakeNu puts the stand-in nu first on the PATH.1416 var fakeNu = func() []string {
@@ -19,39 +21,69 @@ var fakeNu = func() []string {
1921 return []string{"PATH=" + dir + string(os.PathListSeparator) + os.Getenv("PATH")}2022 }()2123 22-// twoRegions runs ls and a region reading it, then goes back to the grid.23-func twoRegions(s *session) {24- s.keys("ls", "<enter>")25- s.waitFor("r1: 5 rows")26- s.keys("big = $r1 | where size > 1kb", "<enter>")27- s.waitFor("big: 3 rows")28- s.keys("<esc>")29- s.waitFor("READY")24+// longPipeline is a pipeline too long for one line at 80 columns.25+const longPipeline = "$files | where size > 1kb | where type == file | sort-by size --reverse | select name size | first 10 | rename file bytes"26+27+// filesNotebook turns 012 nu's first cell into a note, then writes and28+// runs a cell listing files and one reading it, leaving a new cell29+// selected.30+func filesNotebook(s *session) {31+ s.keys("<esc>", "m", "<enter>", "# Files", "<enter>", "What's big in the repo, from `ls`.", "<esc>")32+ s.keys("b", "<enter>", "files = ls", "<shift+enter>")33+ s.waitFor("[1] files")34+ s.keys("<enter>", "$files | where size > 1kb", "<shift+enter>")35+ s.waitFor("[2]")36+ s.waitFor("NOTEBOOK")3037 }3138 3239 var notebookScreens = []screen{33- {name: "notebook-regions", setup: func(s *session) {34- twoRegions(s)35- s.keys("<down>", "<down>", "<right>", "<right>")36- s.waitFor("Region big")37- }},38- {name: "notebook-completion", setup: func(s *session) {39- s.keys("ls", "<enter>")40- s.waitFor("r1: 5 rows")41- s.keys("$r1 | so")42- s.waitFor("sort-by")43- }},44- {name: "notebook-failed", setup: func(s *session) {45- s.keys("ls", "<enter>")46- s.waitFor("r1: 5 rows")47- s.keys("lss -a", "<enter>")48- s.waitFor("r2 failed: Command `lss` not found")40+ {name: "notebook-cells", setup: func(s *session) {41+ filesNotebook(s)42+ s.keys("<up>", "<up>")43+ s.waitFor("reads $files")44+ }},45+ {name: "notebook-wrap", opts: options{cols: 80, rows: 24}, setup: func(s *session) {46+ filesNotebook(s)47+ s.keys("<enter>", longPipeline)48+ s.waitFor("rename file bytes")49+ }},50+ {name: "notebook-wrap-narrow", opts: options{cols: 60, rows: 24}, setup: func(s *session) {51+ filesNotebook(s)52+ s.keys("<enter>", longPipeline, "<ctrl+enter>")53+ s.waitFor("[3]")54+ }},55+ {name: "notebook-error", setup: func(s *session) {56+ filesNotebook(s)57+ s.keys("<enter>", "lss -a", "<ctrl+enter>")58+ s.waitFor("× Command `lss` not found")59+ }},60+ {name: "notebook-running", setup: func(s *session) {61+ filesNotebook(s)62+ s.keys("<enter>", "sleep 10min", "<esc>", "b", "<enter>", "{name: 1}", "<esc>", "<f9>")63+ s.waitFor("waiting")64+ }},65+ {name: "notebook-stale", setup: func(s *session) {66+ filesNotebook(s)67+ s.keys("<up>", "<up>", "<up>", "<up>", "<enter>", " | first 4", "<esc>")68+ s.waitFor("stale")69+ }},70+ {name: "notebook-output", setup: func(s *session) {71+ filesNotebook(s)72+ s.keys("<up>", "<up>", "<up>", "<enter>")73+ s.waitFor("full-screen")74+ }},75+ {name: "notebook-sent", setup: func(s *session) {76+ filesNotebook(s)77+ s.keys("<up>", "<up>", "<up>", "<up>", "G", "<enter>")78+ s.waitFor("Sent files to")79+ s.keys("<ctrl+pgdown>", "<down>")80+ s.waitFor("Output of files")4981 }},5082 }5183 5284 func init() {5385 for _, sc := range notebookScreens {54- sc.args, sc.opts.startsOn, sc.opts.env = []string{"nu"}, "nu❯", fakeNu86+ sc.args, sc.opts.startsOn, sc.opts.env = []string{"nu"}, "EDIT", fakeNu5587 light, hc := sc, sc5688 light.name += "-light"5789 light.opts.light = true
M e2e/testdata/nu/nu +17 -11
@@ -1,21 +1,27 @@
11 #!/bin/sh2-# A stand-in for nu in the golden screens: it answers the commands the3-# screens type with fixed tables, so the screens don't depend on nu being4-# installed or on what ls finds. The script 012 runs is the last argument.2+# A stand-in for nu in the golden screens: it answers the pipelines the3+# screens run with fixed outputs, so the screens don't depend on nu being4+# installed or on what ls finds. The script 012 runs is the last5+# argument; the pipeline is between its "do {" and "} | to nuon".56 for arg; do script=$arg; done6-case $script in7+cmd=$(printf '%s\n' "$script" | sed -n '/^do {$/,/^} | to nuon$/p' | sed '1d;$d')8+case $cmd in79 *'help commands'*)8- echo '["ls", "where", "sort-by", "first", "get", "str join", "window"]' ;;9-*'$r1 | where size > 1kb'*)10- echo '[[name, type, size]; ["012", file, 4.2kb], [README.md, file, 9.8kb], [demos, dir, 3.1kb]]' ;;11-*'do {12-ls13-}'*)10+ echo '["ls", "where", "sort-by", "first", "get", "str join", "window", "sleep"]' ;;11+'ls')1412 echo '[[name, type, size]; ["012", file, 4.2kb], [CLAUDE.md, file, 1kb], [README.md, file, 9.8kb], [demos, dir, 3.1kb], [go.mod, file, 812b]]' ;;13+'$files | where size > 1kb'*)14+ echo '[[name, type, size]; ["012", file, 4.2kb], [README.md, file, 9.8kb], [demos, dir, 3.1kb]]' ;;15+'$files | get name | str join ", "')16+ echo '"012, CLAUDE.md, README.md, demos, go.mod"' ;;17+'{'*)18+ echo '{name: "012", files: 5, largest: README.md, total: 18.9kb}' ;;19+'sleep'*)20+ sleep 600 ;;1521 *)16- cmd=$(printf '%s\n' "$script" | sed -n '/^do {$/{n;p;q;}')1722 echo "Error: nu::parser::extra_positional" >&21823 echo "" >&21924 echo " × Command \`${cmd%% *}\` not found" >&225+ echo " help: commands are listed by \`help commands\`" >&22026 exit 1 ;;2127 esac
M e2e/testdata/screens/help-light.html +1 -1
@@ -25,6 +25,6 @@
2525 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Deselect <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>2626 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select all <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+A</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>2727 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select columns <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+Space</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>28-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└─────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">1-25</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">100</span> <span style="color:var(--vt-palette-8)">─┘</span>28+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└─────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">1-25</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">120</span> <span style="color:var(--vt-palette-8)">─┘</span>2929 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>3030 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Up/Down</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">scroll</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">close</span></pre>\ No newline at end of file
M e2e/testdata/screens/help-narrow.html +1 -1
@@ -11,6 +11,6 @@
1111 <span style="color:var(--vt-palette-8)">│</span> Last used cell <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+End</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>1212 <span style="color:var(--vt-palette-8)">│</span> Go to <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+G</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F5</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>1313 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>14-<span style="color:var(--vt-palette-8)">└───────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">1-11</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">100</span> <span style="color:var(--vt-palette-8)">─┘</span>14+<span style="color:var(--vt-palette-8)">└───────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">1-11</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">120</span> <span style="color:var(--vt-palette-8)">─┘</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Up/Down</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">scroll</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">close</span></pre>\ No newline at end of file
M e2e/testdata/screens/help-wide.html +40 -40
@@ -1,45 +1,45 @@
11 <pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">HELP</span><span style="background:var(--vt-palette-6)"> </span>22 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Keyboard</span> <span style="font-weight:bold">shortcuts</span> <span style="color:var(--vt-palette-8)">───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐</span>33 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>4-<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">Moving</span> <span style="font-weight:bold">around</span> <span style="font-weight:bold">Edit</span> <span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">R</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">S</span><span style="background:var(--vt-palette-8)"> </span>5-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">│</span> Move <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Arrows</span><span style="background:var(--vt-palette-8)"> </span> Undo <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Z</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>6-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Jump to the edge of the data <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+arrows</span><span style="background:var(--vt-palette-8)"> </span> Redo <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+Z</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Y</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>7-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Right, left <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Tab</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+Tab</span><span style="background:var(--vt-palette-8)"> </span> Cut <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+X</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>8-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Screen up, down <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">PgUp</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">PgDn</span><span style="background:var(--vt-palette-8)"> </span> Copy <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+C</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>9-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Screen left, right <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+PgUp</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+PgDn</span><span style="background:var(--vt-palette-8)"> </span> Paste <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+V</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>10-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Column A, cell A1 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Home</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Home</span><span style="background:var(--vt-palette-8)"> </span> Paste values only <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+V</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>11-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Last used cell <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+End</span><span style="background:var(--vt-palette-8)"> </span> Fill down <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+D</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>12-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Go to <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+G</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F5</span><span style="background:var(--vt-palette-8)"> </span> Fill right <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+R</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>13-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Find <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+F</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>14-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">Sheets</span> Find and replace <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+H</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>15-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Show it; double-click renames <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Click</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">a</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">tab</span><span style="background:var(--vt-palette-8)"> </span> Clear <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Del</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Backspace</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>16-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Next sheet <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+PgDn</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+Right</span><span style="background:var(--vt-palette-8)"> </span> Delete rows or columns <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Alt+-</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>17-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Previous sheet <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+PgUp</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+Left</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>18-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> New sheet <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+F11</span><span style="background:var(--vt-palette-8)"> </span> <span style="font-weight:bold">Insert</span> <span style="color:var(--vt-palette-8)">│</span>19-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Go to sheet <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+Shift+K</span><span style="background:var(--vt-palette-8)"> </span> Insert rows or columns <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Alt+=</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>20-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Note <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+F2</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>21-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">Selecting</span> <span style="color:var(--vt-palette-8)">│</span>22-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Extend the selection <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+arrows</span><span style="background:var(--vt-palette-8)"> </span> <span style="font-weight:bold">Format</span> <span style="color:var(--vt-palette-8)">│</span>23-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Extend to the edge of the data <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+arrows</span><span style="background:var(--vt-palette-8)"> </span> Number <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+1</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>24-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select by dragging or Shift+click <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Mouse</span><span style="background:var(--vt-palette-8)"> </span> Percent <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+5</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>25-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Deselect <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> Scientific <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>26-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select all <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+A</span><span style="background:var(--vt-palette-8)"> </span> Currency <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+4</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>27-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select columns <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Space</span><span style="background:var(--vt-palette-8)"> </span> Date <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+3</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>28-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select rows <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+Space</span><span style="background:var(--vt-palette-8)"> </span> Time <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+2</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>29-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Bold <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+B</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>30-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">26</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">Entering</span> <span style="font-weight:bold">data</span> Italic <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+I</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>31-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">27</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Replace the cell; = starts a formula <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Type</span><span style="background:var(--vt-palette-8)"> </span> Underline <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+U</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>32-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">28</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Accept and move down, right <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Tab</span><span style="background:var(--vt-palette-8)"> </span> Strikethrough <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+Shift+5</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>33-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">29</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Pick cells for a formula <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Arrows</span><span style="background:var(--vt-palette-8)"> </span> Left <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+L</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>34-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">30</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Cancel the entry <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> Center <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+E</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>35-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">31</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Edit cell <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F2</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> Right <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+R</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>36-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">32</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Outer borders <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+Shift+7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>37-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">33</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">Macros</span> Top border <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+Shift+1</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>38-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">34</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Run the macro with that shortcut <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Alt+Shift+0-9</span><span style="background:var(--vt-palette-8)"> </span> Bottom border <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+Shift+3</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>39-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">35</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Stop a macro while it runs <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> Left border <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+Shift+4</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>40-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">36</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Right border <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+Shift+2</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>41-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">37</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">Menus</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">search</span> Clear borders <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+Shift+6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>42-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">38</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Open a menu by its underlined letter <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+letter</span><span style="background:var(--vt-palette-8)"> </span> Clear formatting <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+\</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>43-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">39</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">1-40</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">51</span> <span style="color:var(--vt-palette-8)">─┘</span>4+<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">Moving</span> <span style="font-weight:bold">around</span> <span style="font-weight:bold">Menus</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">search</span> <span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">R</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">S</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">│</span> Move <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Arrows</span><span style="background:var(--vt-palette-8)"> </span> Open a menu by its underlined letter <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+letter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Jump to the edge of the data <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+arrows</span><span style="background:var(--vt-palette-8)"> </span> Search the menus <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+P</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Right, left <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Tab</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+Tab</span><span style="background:var(--vt-palette-8)"> </span> Menu <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Screen up, down <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">PgUp</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">PgDn</span><span style="background:var(--vt-palette-8)"> </span> Cell menu <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Screen left, right <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+PgUp</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+PgDn</span><span style="background:var(--vt-palette-8)"> </span> Keyboard shortcuts <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F1</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Column A, cell A1 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Home</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Home</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Last used cell <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+End</span><span style="background:var(--vt-palette-8)"> </span> <span style="font-weight:bold">File</span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Go to <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+G</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F5</span><span style="background:var(--vt-palette-8)"> </span> Open <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+O</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Save <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+S</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">Sheets</span> Quit <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Q</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Show it; double-click renames <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Click</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">a</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">tab</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Next sheet <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+PgDn</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+Right</span><span style="background:var(--vt-palette-8)"> </span> <span style="font-weight:bold">Edit</span> <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Previous sheet <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+PgUp</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+Left</span><span style="background:var(--vt-palette-8)"> </span> Undo <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Z</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> New sheet <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+F11</span><span style="background:var(--vt-palette-8)"> </span> Redo <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+Z</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Y</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Go to sheet <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+Shift+K</span><span style="background:var(--vt-palette-8)"> </span> Cut <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+X</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Copy <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+C</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">Selecting</span> Paste <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+V</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Extend the selection <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+arrows</span><span style="background:var(--vt-palette-8)"> </span> Paste values only <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+V</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Extend to the edge of the data <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+arrows</span><span style="background:var(--vt-palette-8)"> </span> Fill down <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+D</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select by dragging or Shift+click <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Mouse</span><span style="background:var(--vt-palette-8)"> </span> Fill right <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+R</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Deselect <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> Find <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+F</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select all <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+A</span><span style="background:var(--vt-palette-8)"> </span> Find and replace <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+H</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select columns <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Space</span><span style="background:var(--vt-palette-8)"> </span> Clear <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Del</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Backspace</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select rows <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+Space</span><span style="background:var(--vt-palette-8)"> </span> Delete rows or columns <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Alt+-</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>30+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">26</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">Entering</span> <span style="font-weight:bold">data</span> <span style="font-weight:bold">Insert</span> <span style="color:var(--vt-palette-8)">│</span>31+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">27</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Replace the cell; = starts a formula <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Type</span><span style="background:var(--vt-palette-8)"> </span> Insert rows or columns <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Alt+=</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>32+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">28</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Accept and move down, right <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Tab</span><span style="background:var(--vt-palette-8)"> </span> Note <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+F2</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>33+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">29</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Pick cells for a formula <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Arrows</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>34+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">30</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Cancel the entry <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="font-weight:bold">Format</span> <span style="color:var(--vt-palette-8)">│</span>35+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">31</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Edit cell <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F2</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> Number <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+1</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>36+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">32</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Percent <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+5</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>37+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">33</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">Macros</span> Scientific <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>38+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">34</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Run the macro with that shortcut <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Alt+Shift+0-9</span><span style="background:var(--vt-palette-8)"> </span> Currency <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+4</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>39+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">35</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Stop a macro while it runs <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> Date <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+3</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>40+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">36</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Time <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+2</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>41+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">37</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">Notebooks</span> Bold <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+B</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>42+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">38</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Move between cells and outputs <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Up</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Down</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">j</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">k</span><span style="background:var(--vt-palette-8)"> </span> Italic <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+I</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>43+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">39</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">1-40</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">61</span> <span style="color:var(--vt-palette-8)">─┘</span>4444 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">40</span><span style="background:var(--vt-palette-8)"> </span>4545 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Up/Down</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">scroll</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">close</span></pre>\ No newline at end of file
M e2e/testdata/screens/help.html +1 -1
@@ -25,6 +25,6 @@
2525 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Deselect <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>2626 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select all <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+A</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>2727 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select columns <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Space</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>28-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└─────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">1-25</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">100</span> <span style="color:var(--vt-palette-8)">─┘</span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└─────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">1-25</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">120</span> <span style="color:var(--vt-palette-8)">─┘</span>2929 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>3030 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Up/Down</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">scroll</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">close</span></pre>\ No newline at end of file
M e2e/testdata/screens/macro-menu-light.html +5 -5
@@ -22,9 +22,9 @@
2222 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">├─────────────────────│</span> Record macro with relative references <span style="color:var(--vt-palette-8)">│</span>2323 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Macros</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Stop</span> <span style="color:var(--vt-palette-8)">and</span> <span style="color:var(--vt-palette-8)">save</span> <span style="color:var(--vt-palette-8)">recording</span> <span style="color:var(--vt-palette-8)">│</span>2424 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Shell <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Discard</span> <span style="color:var(--vt-palette-8)">recording</span> <span style="color:var(--vt-palette-8)">│</span>25-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Shell regions <span style="color:var(--vt-palette-8)">├──────────────────────────────────────────┤</span>26-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Linked file <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Run</span> <span style="color:var(--vt-palette-8)">macro</span> <span style="color:var(--vt-palette-8)">│</span>27-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">├─────────────────────│</span> Manage macros <span style="color:var(--vt-palette-8)">│</span>28-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Ask</span> <span style="color:var(--vt-palette-8)">JEV</span> <span style="color:var(--vt-palette-8)">again</span> <span style="color:var(--vt-palette-8)">│</span> Write a macro <span style="color:var(--vt-palette-8)">│</span>29-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└─────────────────────└──────────────────────────────────────────┘</span>25+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Linked file <span style="color:var(--vt-palette-8)">├──────────────────────────────────────────┤</span>26+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">├─────────────────────│</span> <span style="color:var(--vt-palette-8)">Run</span> <span style="color:var(--vt-palette-8)">macro</span> <span style="color:var(--vt-palette-8)">│</span>27+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Ask</span> <span style="color:var(--vt-palette-8)">JEV</span> <span style="color:var(--vt-palette-8)">again</span> <span style="color:var(--vt-palette-8)">│</span> Manage macros <span style="color:var(--vt-palette-8)">│</span>28+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└─────────────────────│</span> Write a macro <span style="color:var(--vt-palette-8)">│</span>29+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────────┘</span>3030 Record what you do as a macro that selects the same ce… <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Arrows</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">move</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">choose</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">close</span></pre>\ No newline at end of file
M e2e/testdata/screens/macro-menu.html +5 -5
@@ -22,9 +22,9 @@
2222 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">├─────────────────────│</span> Record macro with relative references <span style="color:var(--vt-palette-8)">│</span>2323 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Macros</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Stop</span> <span style="color:var(--vt-palette-8)">and</span> <span style="color:var(--vt-palette-8)">save</span> <span style="color:var(--vt-palette-8)">recording</span> <span style="color:var(--vt-palette-8)">│</span>2424 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Shell <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Discard</span> <span style="color:var(--vt-palette-8)">recording</span> <span style="color:var(--vt-palette-8)">│</span>25-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Shell regions <span style="color:var(--vt-palette-8)">├──────────────────────────────────────────┤</span>26-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Linked file <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Run</span> <span style="color:var(--vt-palette-8)">macro</span> <span style="color:var(--vt-palette-8)">│</span>27-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">├─────────────────────│</span> Manage macros <span style="color:var(--vt-palette-8)">│</span>28-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Ask</span> <span style="color:var(--vt-palette-8)">JEV</span> <span style="color:var(--vt-palette-8)">again</span> <span style="color:var(--vt-palette-8)">│</span> Write a macro <span style="color:var(--vt-palette-8)">│</span>29-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└─────────────────────└──────────────────────────────────────────┘</span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Linked file <span style="color:var(--vt-palette-8)">├──────────────────────────────────────────┤</span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">├─────────────────────│</span> <span style="color:var(--vt-palette-8)">Run</span> <span style="color:var(--vt-palette-8)">macro</span> <span style="color:var(--vt-palette-8)">│</span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Ask</span> <span style="color:var(--vt-palette-8)">JEV</span> <span style="color:var(--vt-palette-8)">again</span> <span style="color:var(--vt-palette-8)">│</span> Manage macros <span style="color:var(--vt-palette-8)">│</span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└─────────────────────│</span> Write a macro <span style="color:var(--vt-palette-8)">│</span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────────┘</span>3030 Record what you do as a macro that selects the same ce… <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Arrows</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">move</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">choose</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">close</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-cells-high-contrast.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">F</span><span style="color:#ffffff;background:#1f1f1f">ile</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">E</span><span style="color:#ffffff;background:#1f1f1f">dit</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">V</span><span style="color:#ffffff;background:#1f1f1f">iew</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">I</span><span style="color:#ffffff;background:#1f1f1f">nsert</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">F</span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">o</span><span style="color:#ffffff;background:#1f1f1f">rmat</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">D</span><span style="color:#ffffff;background:#1f1f1f">ata</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">H</span><span style="color:#ffffff;background:#1f1f1f">elp</span><span style="background:#1f1f1f"> </span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">NOTEBOOK</span><span style="background:#40e0ff"> </span>2+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">cell</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">3</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">reads</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">$files</span><span style="background:#000000"> </span>3+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Enter</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">edit</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Shift+Enter</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">run</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">a</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">add</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">above</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">b</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">below</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">dd</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">delete</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">G</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">send</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">to</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">a</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">sheet</span><span style="background:#000000"> </span>4+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Notebook</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">4</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">cells</span><span style="background:#3a3a3a"> </span>5+<span style="background:#000000"> </span><span style="color:#959595;background:#000000">Markdown</span><span style="background:#000000"> </span>6+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">Files</span><span style="background:#000000"> </span>7+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">What&#39;s</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">big</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">in</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">the</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">repo,</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">from</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">ls</span><span style="color:#ffffff;background:#000000">.</span><span style="background:#000000"> </span>8+<span style="background:#000000"> </span>9+<span style="background:#000000"> </span><span style="color:#959595;background:#000000">[1]</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">files</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">✓</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">&lt;1s</span><span style="background:#000000"> </span>10+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ff80ff;background:#000000">files</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">=</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">ls</span><span style="background:#000000"> </span>11+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">name</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">type</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">size</span><span style="background:#000000"> </span>12+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">012</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">4.2</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>13+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">CLAUDE.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">1.0</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>14+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">README.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">9.8</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>15+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">demos</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">dir</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">3.1</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>16+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">go.mod</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">812</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">B</span><span style="background:#000000"> </span>17+<span style="background:#000000"> </span>18+<span style="background:#0033cc"> </span><span style="color:#ffffff;background:#0033cc">[2]</span><span style="background:#0033cc"> </span><span style="color:#ffffff;background:#0033cc">✓</span><span style="background:#0033cc"> </span><span style="color:#ffffff;background:#0033cc">&lt;1s</span><span style="background:#0033cc"> </span><span style="background:#000000"> </span>19+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ff80ff;background:#000000">$files</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">|</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">where</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">size</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">&gt;</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">1kb</span><span style="background:#000000"> </span>20+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">name</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">type</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">size</span><span style="background:#000000"> </span>21+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">012</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">4.2</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>22+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">README.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">9.8</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>23+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">demos</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">dir</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">3.1</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>24+<span style="background:#000000"> </span>25+<span style="background:#000000"> </span><span style="color:#959595;background:#000000">[</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">]</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">not</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">run</span><span style="background:#000000"> </span>26+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span>27+<span style="background:#000000"> </span>28+<span style="background:#000000"> </span>29+<span style="background:#000000"> </span>30+<span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">❯</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">Notebook</span><span style="background:#40e0ff"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">+</span><span style="background:#1f1f1f"> </span><span style="color:#757575;background:#1f1f1f">│</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">untitled</span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">modified</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+K</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">search</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+/</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">shortcuts</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">F10</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-cells-light.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">NOTEBOOK</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">cell</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">reads</span> <span style="color:var(--vt-palette-0)">$files</span>3+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">edit</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Shift+Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">run</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">a</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">add</span> <span style="color:var(--vt-palette-0)">above</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">b</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">below</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">dd</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">delete</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">send</span> <span style="color:var(--vt-palette-0)">to</span> <span style="color:var(--vt-palette-0)">a</span> <span style="color:var(--vt-palette-0)">sheet</span>4+ Notebook 4 cells5+ <span style="color:var(--vt-palette-0)">Markdown</span>6+<span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold;text-decoration:underline">Files</span>7+<span style="color:var(--vt-palette-8)">│</span> What&#39;s big in the repo, from <span style="color:var(--vt-palette-4)">ls</span>.8+9+ <span style="color:var(--vt-palette-0)">[1]</span> <span style="color:var(--vt-palette-0)">files</span> <span style="color:var(--vt-palette-0)">✓</span> <span style="color:var(--vt-palette-0)">&lt;1s</span>10+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">files</span> <span style="font-weight:bold">=</span> <span style="color:var(--vt-palette-4)">ls</span>11+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>12+ 012 file 4.2 kB13+ CLAUDE.md file 1.0 kB14+ README.md file 9.8 kB15+ demos dir 3.1 kB16+ go.mod file 812 B17+18+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">[2]</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">✓</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">&lt;1s</span><span style="background:var(--vt-palette-4)"> </span>19+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">where</span> size <span style="font-weight:bold">&gt;</span> <span style="color:var(--vt-palette-1)">1kb</span>20+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>21+ 012 file 4.2 kB22+ README.md file 9.8 kB23+ demos dir 3.1 kB24+25+ <span style="color:var(--vt-palette-0)">[</span> <span style="color:var(--vt-palette-0)">]</span> <span style="color:var(--vt-palette-0)">not</span> <span style="color:var(--vt-palette-0)">run</span>26+<span style="color:var(--vt-palette-8)">│</span>27+28+29+30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-cells.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">NOTEBOOK</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">cell</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">reads</span> <span style="color:var(--vt-palette-8)">$files</span>3+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">edit</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">run</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">a</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">add</span> <span style="color:var(--vt-palette-8)">above</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">b</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">below</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">dd</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">delete</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">send</span> <span style="color:var(--vt-palette-8)">to</span> <span style="color:var(--vt-palette-8)">a</span> <span style="color:var(--vt-palette-8)">sheet</span>4+ Notebook 4 cells5+ <span style="color:var(--vt-palette-8)">Markdown</span>6+<span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold;text-decoration:underline">Files</span>7+<span style="color:var(--vt-palette-8)">│</span> What&#39;s big in the repo, from <span style="color:var(--vt-palette-6)">ls</span>.8+9+ <span style="color:var(--vt-palette-8)">[1]</span> <span style="color:var(--vt-palette-8)">files</span> <span style="color:var(--vt-palette-8)">✓</span> <span style="color:var(--vt-palette-8)">&lt;1s</span>10+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">files</span> <span style="font-weight:bold">=</span> <span style="color:var(--vt-palette-6)">ls</span>11+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>12+ 012 file 4.2 kB13+ CLAUDE.md file 1.0 kB14+ README.md file 9.8 kB15+ demos dir 3.1 kB16+ go.mod file 812 B17+18+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">[2]</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">✓</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">&lt;1s</span><span style="background:var(--vt-palette-4)"> </span>19+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">where</span> size <span style="font-weight:bold">&gt;</span> <span style="color:var(--vt-palette-3)">1kb</span>20+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>21+ 012 file 4.2 kB22+ README.md file 9.8 kB23+ demos dir 3.1 kB24+25+ <span style="color:var(--vt-palette-8)">[</span> <span style="color:var(--vt-palette-8)">]</span> <span style="color:var(--vt-palette-8)">not</span> <span style="color:var(--vt-palette-8)">run</span>26+<span style="color:var(--vt-palette-8)">│</span>27+28+29+30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
D e2e/testdata/screens/notebook-completion-light.html +0 -30
@@ -1,30 +0,0 @@
1-<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">NU</span><span style="background:var(--vt-palette-6)"> </span>2-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A1</span><span style="background:var(--vt-palette-7)"> </span> <span style="font-weight:bold">nu❯</span> $r1 | so3-<span style="color:var(--vt-palette-0)">r1:</span> <span style="color:var(--vt-palette-0)">5</span> <span style="color:var(--vt-palette-0)">rows</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">back</span> <span style="color:var(--vt-palette-0)">to</span> <span style="color:var(--vt-palette-0)">the</span> <span style="color:var(--vt-palette-0)">grid</span>4-<span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Completions</span> <span style="color:var(--vt-palette-8)">────────────────┐</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>5-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">r1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">l</span><span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">sort-by</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">command</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>6-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> name <span style="color:var(--vt-palette-8)">└─────────────────────</span> <span style="color:var(--vt-palette-0)">1</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">1</span> <span style="color:var(--vt-palette-8)">─┘</span>7-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> 012 file 4.2 kB8-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> CLAUDE.md file 1.0 kB9-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> README.md file 9.8 kB10-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> demos dir 3.1 kB11-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> go.mod file 812 B12-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span>13-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span>14-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span>15-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span>16-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span>17-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span>18-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span>19-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span>20-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span>21-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">17</span><span style="background:var(--vt-palette-7)"> </span>22-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span>23-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span>24-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span>25-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span>26-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span>27-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span>28-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span>29-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>30-command <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Tab</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">complete</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+N</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">next</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">run</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">grid</span></pre>\ No newline at end of file
D e2e/testdata/screens/notebook-completion.html +0 -30
@@ -1,30 +0,0 @@
1-<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">NU</span><span style="background:var(--vt-palette-6)"> </span>2-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> <span style="font-weight:bold">nu❯</span> $r1 | so3-<span style="color:var(--vt-palette-8)">r1:</span> <span style="color:var(--vt-palette-8)">5</span> <span style="color:var(--vt-palette-8)">rows</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">back</span> <span style="color:var(--vt-palette-8)">to</span> <span style="color:var(--vt-palette-8)">the</span> <span style="color:var(--vt-palette-8)">grid</span>4-<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Completions</span> <span style="color:var(--vt-palette-8)">────────────────┐</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">r1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">l</span><span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">sort-by</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">command</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>6-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> name <span style="color:var(--vt-palette-8)">└─────────────────────</span> <span style="color:var(--vt-palette-8)">1</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">1</span> <span style="color:var(--vt-palette-8)">─┘</span>7-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> 012 file 4.2 kB8-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> CLAUDE.md file 1.0 kB9-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> README.md file 9.8 kB10-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> demos dir 3.1 kB11-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> go.mod file 812 B12-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span>13-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span>14-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span>15-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span>16-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span>17-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span>18-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span>19-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span>20-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span>21-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30-command <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Tab</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">complete</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+N</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">next</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">run</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">grid</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-error-high-contrast.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">F</span><span style="color:#ffffff;background:#1f1f1f">ile</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">E</span><span style="color:#ffffff;background:#1f1f1f">dit</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">V</span><span style="color:#ffffff;background:#1f1f1f">iew</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">I</span><span style="color:#ffffff;background:#1f1f1f">nsert</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">F</span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">o</span><span style="color:#ffffff;background:#1f1f1f">rmat</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">D</span><span style="color:#ffffff;background:#1f1f1f">ata</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">H</span><span style="color:#ffffff;background:#1f1f1f">elp</span><span style="background:#1f1f1f"> </span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">NOTEBOOK</span><span style="background:#40e0ff"> </span>2+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">cell</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">4</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">a</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">nushell</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">pipeline</span><span style="background:#000000"> </span>3+<span style="color:#ffe040;background:#000000">Failed:</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">Command</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">`lss`</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">not</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">found</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Enter</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">edit</span>4+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Notebook</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">4</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">cells</span><span style="background:#3a3a3a"> </span>5+<span style="background:#000000"> </span><span style="color:#959595;background:#000000">Markdown</span><span style="background:#000000"> </span>6+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">Files</span><span style="background:#000000"> </span>7+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">What&#39;s</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">big</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">in</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">the</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">repo,</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">from</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">ls</span><span style="color:#ffffff;background:#000000">.</span><span style="background:#000000"> </span>8+<span style="background:#000000"> </span>9+<span style="background:#000000"> </span><span style="color:#959595;background:#000000">[1]</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">files</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">✓</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">&lt;1s</span><span style="background:#000000"> </span>10+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ff80ff;background:#000000">files</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">=</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">ls</span><span style="background:#000000"> </span>11+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">name</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">type</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">size</span><span style="background:#000000"> </span>12+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">012</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">4.2</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>13+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">CLAUDE.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">1.0</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>14+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">README.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">9.8</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>15+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">demos</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">dir</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">3.1</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>16+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">go.mod</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">812</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">B</span><span style="background:#000000"> </span>17+<span style="background:#000000"> </span>18+<span style="background:#000000"> </span><span style="color:#959595;background:#000000">[2]</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">✓</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">&lt;1s</span><span style="background:#000000"> </span>19+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ff80ff;background:#000000">$files</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">|</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">where</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">size</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">&gt;</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">1kb</span><span style="background:#000000"> </span>20+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">name</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">type</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">size</span><span style="background:#000000"> </span>21+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">012</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">4.2</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>22+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">README.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">9.8</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>23+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">demos</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">dir</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">3.1</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>24+<span style="background:#000000"> </span>25+<span style="background:#0033cc"> </span><span style="color:#ffffff;background:#0033cc">[3]</span><span style="background:#0033cc"> </span><span style="color:#ffffff;background:#0033cc">failed</span><span style="background:#0033cc"> </span><span style="color:#ffffff;background:#0033cc">&lt;1s</span><span style="background:#0033cc"> </span><span style="background:#000000"> </span>26+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">lss</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">-a</span><span style="background:#000000"> </span>27+<span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">×</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">Command</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">`lss`</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">not</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">found</span><span style="background:#000000"> </span>28+<span style="background:#000000"> </span><span style="color:#959595;background:#000000">help:</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">commands</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">are</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">listed</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">by</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">`help</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">commands`</span><span style="background:#000000"> </span>29+<span style="background:#000000"> </span>30+<span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">❯</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">Notebook</span><span style="background:#40e0ff"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">+</span><span style="background:#1f1f1f"> </span><span style="color:#757575;background:#1f1f1f">│</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">untitled</span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">modified</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+K</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">search</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+/</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">shortcuts</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">F10</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-error-light.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">NOTEBOOK</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">cell</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">a</span> <span style="color:var(--vt-palette-0)">nushell</span> <span style="color:var(--vt-palette-0)">pipeline</span>3+<span style="color:var(--vt-palette-3)">Failed:</span> <span style="color:var(--vt-palette-3)">Command</span> <span style="color:var(--vt-palette-3)">`lss`</span> <span style="color:var(--vt-palette-3)">not</span> <span style="color:var(--vt-palette-3)">found</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">edit</span>4+ Notebook 4 cells5+ <span style="color:var(--vt-palette-0)">Markdown</span>6+<span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold;text-decoration:underline">Files</span>7+<span style="color:var(--vt-palette-8)">│</span> What&#39;s big in the repo, from <span style="color:var(--vt-palette-4)">ls</span>.8+9+ <span style="color:var(--vt-palette-0)">[1]</span> <span style="color:var(--vt-palette-0)">files</span> <span style="color:var(--vt-palette-0)">✓</span> <span style="color:var(--vt-palette-0)">&lt;1s</span>10+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">files</span> <span style="font-weight:bold">=</span> <span style="color:var(--vt-palette-4)">ls</span>11+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>12+ 012 file 4.2 kB13+ CLAUDE.md file 1.0 kB14+ README.md file 9.8 kB15+ demos dir 3.1 kB16+ go.mod file 812 B17+18+ <span style="color:var(--vt-palette-0)">[2]</span> <span style="color:var(--vt-palette-0)">✓</span> <span style="color:var(--vt-palette-0)">&lt;1s</span>19+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">where</span> size <span style="font-weight:bold">&gt;</span> <span style="color:var(--vt-palette-1)">1kb</span>20+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>21+ 012 file 4.2 kB22+ README.md file 9.8 kB23+ demos dir 3.1 kB24+25+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">[3]</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">failed</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">&lt;1s</span><span style="background:var(--vt-palette-4)"> </span>26+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">lss</span> -a27+ <span style="color:var(--vt-palette-3)">×</span> <span style="color:var(--vt-palette-3)">Command</span> <span style="color:var(--vt-palette-3)">`lss`</span> <span style="color:var(--vt-palette-3)">not</span> <span style="color:var(--vt-palette-3)">found</span>28+ <span style="color:var(--vt-palette-0)">help:</span> <span style="color:var(--vt-palette-0)">commands</span> <span style="color:var(--vt-palette-0)">are</span> <span style="color:var(--vt-palette-0)">listed</span> <span style="color:var(--vt-palette-0)">by</span> <span style="color:var(--vt-palette-0)">`help</span> <span style="color:var(--vt-palette-0)">commands`</span>29+30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-error.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">NOTEBOOK</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">cell</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">a</span> <span style="color:var(--vt-palette-8)">nushell</span> <span style="color:var(--vt-palette-8)">pipeline</span>3+<span style="color:var(--vt-palette-3)">Failed:</span> <span style="color:var(--vt-palette-3)">Command</span> <span style="color:var(--vt-palette-3)">`lss`</span> <span style="color:var(--vt-palette-3)">not</span> <span style="color:var(--vt-palette-3)">found</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">edit</span>4+ Notebook 4 cells5+ <span style="color:var(--vt-palette-8)">Markdown</span>6+<span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold;text-decoration:underline">Files</span>7+<span style="color:var(--vt-palette-8)">│</span> What&#39;s big in the repo, from <span style="color:var(--vt-palette-6)">ls</span>.8+9+ <span style="color:var(--vt-palette-8)">[1]</span> <span style="color:var(--vt-palette-8)">files</span> <span style="color:var(--vt-palette-8)">✓</span> <span style="color:var(--vt-palette-8)">&lt;1s</span>10+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">files</span> <span style="font-weight:bold">=</span> <span style="color:var(--vt-palette-6)">ls</span>11+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>12+ 012 file 4.2 kB13+ CLAUDE.md file 1.0 kB14+ README.md file 9.8 kB15+ demos dir 3.1 kB16+ go.mod file 812 B17+18+ <span style="color:var(--vt-palette-8)">[2]</span> <span style="color:var(--vt-palette-8)">✓</span> <span style="color:var(--vt-palette-8)">&lt;1s</span>19+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">where</span> size <span style="font-weight:bold">&gt;</span> <span style="color:var(--vt-palette-3)">1kb</span>20+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>21+ 012 file 4.2 kB22+ README.md file 9.8 kB23+ demos dir 3.1 kB24+25+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">[3]</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">failed</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">&lt;1s</span><span style="background:var(--vt-palette-4)"> </span>26+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">lss</span> -a27+ <span style="color:var(--vt-palette-3)">×</span> <span style="color:var(--vt-palette-3)">Command</span> <span style="color:var(--vt-palette-3)">`lss`</span> <span style="color:var(--vt-palette-3)">not</span> <span style="color:var(--vt-palette-3)">found</span>28+ <span style="color:var(--vt-palette-8)">help:</span> <span style="color:var(--vt-palette-8)">commands</span> <span style="color:var(--vt-palette-8)">are</span> <span style="color:var(--vt-palette-8)">listed</span> <span style="color:var(--vt-palette-8)">by</span> <span style="color:var(--vt-palette-8)">`help</span> <span style="color:var(--vt-palette-8)">commands`</span>29+30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
D e2e/testdata/screens/notebook-failed-high-contrast.html +0 -30
@@ -1,30 +0,0 @@
1-<pre class="screen"><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">F</span><span style="color:#ffffff;background:#1f1f1f">ile</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">E</span><span style="color:#ffffff;background:#1f1f1f">dit</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">V</span><span style="color:#ffffff;background:#1f1f1f">iew</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">I</span><span style="color:#ffffff;background:#1f1f1f">nsert</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">F</span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">o</span><span style="color:#ffffff;background:#1f1f1f">rmat</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">D</span><span style="color:#ffffff;background:#1f1f1f">ata</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">H</span><span style="color:#ffffff;background:#1f1f1f">elp</span><span style="background:#1f1f1f"> </span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">NU</span><span style="background:#40e0ff"> </span>2-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">A9</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">nu❯</span><span style="background:#000000"> </span>3-<span style="color:#ffe040;background:#000000">r2</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">failed:</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">Command</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">`lss`</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">not</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">found</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Esc</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">back</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">to</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">the</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">grid</span>4-<span style="background:#3a3a3a"> </span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">A</span><span style="background:#40e0ff"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">B</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">C</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">D</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">E</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">F</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">G</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">H</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">I</span><span style="background:#3a3a3a"> </span>5-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">1</span><span style="background:#3a3a3a"> </span><span style="background:#000000;text-decoration:underline"> </span><span style="color:#959595;background:#000000;text-decoration:underline">r1</span><span style="background:#000000;text-decoration:underline"> </span><span style="color:#959595;background:#000000;text-decoration:underline">ls</span><span style="background:#000000;text-decoration:underline"> </span><span style="background:#000000"> </span>6-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">2</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">name</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">type</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">size</span><span style="background:#000000"> </span>7-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">3</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">012</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">4.2</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>8-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">4</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">CLAUDE.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">1.0</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>9-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">5</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">README.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">9.8</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>10-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">6</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">demos</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">dir</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">3.1</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>11-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">7</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">go.mod</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">812</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">B</span><span style="background:#000000"> </span>12-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">8</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>13-<span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">9</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff">r2</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff">lss</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff">-</span><span style="color:#959595;background:#000000;text-decoration:underline">a</span><span style="background:#000000;text-decoration:underline"> </span><span style="color:#959595;background:#000000;text-decoration:underline">failed</span><span style="background:#000000"> </span>14-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">10</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>15-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">11</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>16-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">12</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>17-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">13</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>18-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">14</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>19-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">15</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>20-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">16</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>21-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">17</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>22-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">18</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>23-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">19</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>24-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">20</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>25-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">21</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>26-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">22</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>27-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">23</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>28-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">24</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>29-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">25</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>30-<span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">Shell</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">1</span><span style="background:#40e0ff"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">+</span><span style="background:#1f1f1f"> </span><span style="color:#757575;background:#1f1f1f">│</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">untitled</span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">modified</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+K</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">search</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+/</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">shortcuts</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">F10</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">menu</span></pre>\ No newline at end of file
D e2e/testdata/screens/notebook-failed-light.html +0 -30
@@ -1,30 +0,0 @@
1-<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">NU</span><span style="background:var(--vt-palette-6)"> </span>2-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A9</span><span style="background:var(--vt-palette-7)"> </span> <span style="font-weight:bold">nu❯</span>3-<span style="color:var(--vt-palette-3)">r2</span> <span style="color:var(--vt-palette-3)">failed:</span> <span style="color:var(--vt-palette-3)">Command</span> <span style="color:var(--vt-palette-3)">`lss`</span> <span style="color:var(--vt-palette-3)">not</span> <span style="color:var(--vt-palette-3)">found</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">back</span> <span style="color:var(--vt-palette-0)">to</span> <span style="color:var(--vt-palette-0)">the</span> <span style="color:var(--vt-palette-0)">grid</span>4-<span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>5-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">1</span><span style="background:var(--vt-palette-7)"> </span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-0);text-decoration:underline">r1</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-0);text-decoration:underline">ls</span><span style="text-decoration:underline"> </span>6-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> name type size7-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> 012 file 4.2 kB8-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> CLAUDE.md file 1.0 kB9-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> README.md file 9.8 kB10-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> demos dir 3.1 kB11-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> go.mod file 812 B12-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span>13-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">9</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">r2</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">lss</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">-</span><span style="color:var(--vt-palette-0);text-decoration:underline">a</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-0);text-decoration:underline">failed</span>14-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span>15-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span>16-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span>17-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span>18-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span>19-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span>20-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span>21-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">17</span><span style="background:var(--vt-palette-7)"> </span>22-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span>23-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span>24-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span>25-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span>26-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span>27-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span>28-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span>29-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>30-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Shell</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">menu</span></pre>\ No newline at end of file
D e2e/testdata/screens/notebook-failed.html +0 -30
@@ -1,30 +0,0 @@
1-<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">NU</span><span style="background:var(--vt-palette-6)"> </span>2-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A9</span><span style="background:var(--vt-palette-8)"> </span> <span style="font-weight:bold">nu❯</span>3-<span style="color:var(--vt-palette-3)">r2</span> <span style="color:var(--vt-palette-3)">failed:</span> <span style="color:var(--vt-palette-3)">Command</span> <span style="color:var(--vt-palette-3)">`lss`</span> <span style="color:var(--vt-palette-3)">not</span> <span style="color:var(--vt-palette-3)">found</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">back</span> <span style="color:var(--vt-palette-8)">to</span> <span style="color:var(--vt-palette-8)">the</span> <span style="color:var(--vt-palette-8)">grid</span>4-<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-8);text-decoration:underline">r1</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-8);text-decoration:underline">ls</span><span style="text-decoration:underline"> </span>6-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> name type size7-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> 012 file 4.2 kB8-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> CLAUDE.md file 1.0 kB9-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> README.md file 9.8 kB10-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> demos dir 3.1 kB11-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> go.mod file 812 B12-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span>13-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">9</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">r2</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">lss</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">-</span><span style="color:var(--vt-palette-8);text-decoration:underline">a</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-8);text-decoration:underline">failed</span>14-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span>15-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span>16-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span>17-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span>18-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span>19-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span>20-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span>21-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Shell</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-output-high-contrast.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">F</span><span style="color:#ffffff;background:#1f1f1f">ile</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">E</span><span style="color:#ffffff;background:#1f1f1f">dit</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">V</span><span style="color:#ffffff;background:#1f1f1f">iew</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">I</span><span style="color:#ffffff;background:#1f1f1f">nsert</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">F</span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">o</span><span style="color:#ffffff;background:#1f1f1f">rmat</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">D</span><span style="color:#ffffff;background:#1f1f1f">ata</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">H</span><span style="color:#ffffff;background:#1f1f1f">elp</span><span style="background:#1f1f1f"> </span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">OUTPUT</span><span style="background:#40e0ff"> </span>2+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">files</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">a</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">table:</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">5</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">rows,</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">3</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">columns</span><span style="background:#000000"> </span>3+<span style="color:#959595;background:#000000">files</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">[1]</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">5</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">rows</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">s</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">sort</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">S</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">descending</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">/</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">filter</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Esc</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">back</span>4+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">files</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">[1],</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">full-screen</span><span style="background:#3a3a3a"> </span>5+<span style="background:#3a3a3a"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">name</span><span style="background:#40e0ff"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">type</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">size</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>6+<span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">1</span><span style="background:#000000"> </span><span style="color:#000000;background:#40e0ff">012</span><span style="background:#40e0ff"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">4.2</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>7+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">2</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">CLAUDE.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">1.0</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>8+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">3</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">README.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">9.8</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>9+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">4</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">demos</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">dir</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">3.1</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>10+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">5</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">go.mod</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">812</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">B</span><span style="background:#000000"> </span>11+<span style="background:#000000"> </span>12+<span style="background:#000000"> </span>13+<span style="background:#000000"> </span>14+<span style="background:#000000"> </span>15+<span style="background:#000000"> </span>16+<span style="background:#000000"> </span>17+<span style="background:#000000"> </span>18+<span style="background:#000000"> </span>19+<span style="background:#000000"> </span>20+<span style="background:#000000"> </span>21+<span style="background:#000000"> </span>22+<span style="background:#000000"> </span>23+<span style="background:#000000"> </span>24+<span style="background:#000000"> </span>25+<span style="background:#000000"> </span>26+<span style="background:#000000"> </span>27+<span style="background:#000000"> </span>28+<span style="background:#000000"> </span>29+<span style="background:#000000"> </span>30+<span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">❯</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">Notebook</span><span style="background:#40e0ff"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">+</span><span style="background:#1f1f1f"> </span><span style="color:#757575;background:#1f1f1f">│</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">untitled</span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">modified</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+K</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">search</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+/</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">shortcuts</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">F10</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-output-light.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">OUTPUT</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">files</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">a</span> <span style="color:var(--vt-palette-0)">table:</span> <span style="color:var(--vt-palette-0)">5</span> <span style="color:var(--vt-palette-0)">rows,</span> <span style="color:var(--vt-palette-0)">3</span> <span style="color:var(--vt-palette-0)">columns</span>3+<span style="color:var(--vt-palette-0)">files</span> <span style="color:var(--vt-palette-0)">[1]</span> <span style="color:var(--vt-palette-0)">5</span> <span style="color:var(--vt-palette-0)">rows</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">s</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">sort</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">S</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">descending</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">filter</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">back</span>4+ files [1], full-screen5+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">name</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">type</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">size</span><span style="background:var(--vt-palette-7)"> </span>6+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span> <span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">012</span><span style="background:var(--vt-palette-6)"> </span> file 4.2 kB7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span> CLAUDE.md file 1.0 kB8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span> README.md file 9.8 kB9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span> demos dir 3.1 kB10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span> go.mod file 812 B11+12+13+14+15+16+17+18+19+20+21+22+23+24+25+26+27+28+29+30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-output.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">OUTPUT</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">files</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">a</span> <span style="color:var(--vt-palette-8)">table:</span> <span style="color:var(--vt-palette-8)">5</span> <span style="color:var(--vt-palette-8)">rows,</span> <span style="color:var(--vt-palette-8)">3</span> <span style="color:var(--vt-palette-8)">columns</span>3+<span style="color:var(--vt-palette-8)">files</span> <span style="color:var(--vt-palette-8)">[1]</span> <span style="color:var(--vt-palette-8)">5</span> <span style="color:var(--vt-palette-8)">rows</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">s</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">sort</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">S</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">descending</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">filter</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">back</span>4+ files [1], full-screen5+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">name</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">type</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">size</span><span style="background:var(--vt-palette-8)"> </span>6+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span> <span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">012</span><span style="background:var(--vt-palette-6)"> </span> file 4.2 kB7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span> CLAUDE.md file 1.0 kB8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span> README.md file 9.8 kB9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span> demos dir 3.1 kB10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span> go.mod file 812 B11+12+13+14+15+16+17+18+19+20+21+22+23+24+25+26+27+28+29+30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
D e2e/testdata/screens/notebook-regions-high-contrast.html +0 -30
@@ -1,30 +0,0 @@
1-<pre class="screen"><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">F</span><span style="color:#ffffff;background:#1f1f1f">ile</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">E</span><span style="color:#ffffff;background:#1f1f1f">dit</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">V</span><span style="color:#ffffff;background:#1f1f1f">iew</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">I</span><span style="color:#ffffff;background:#1f1f1f">nsert</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">F</span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">o</span><span style="color:#ffffff;background:#1f1f1f">rmat</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">D</span><span style="color:#ffffff;background:#1f1f1f">ata</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">H</span><span style="color:#ffffff;background:#1f1f1f">elp</span><span style="background:#1f1f1f"> </span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">READY</span><span style="background:#40e0ff"> </span>2-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">C11</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">nu❯</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">$r1</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">|</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">where</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">size</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">&gt;</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">1kb</span><span style="background:#000000"> </span>3-<span style="color:#959595;background:#000000">Region</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">big</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">of</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">$r1</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">|</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">where</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">size</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">&gt;</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">1kb</span><span style="background:#000000"> </span>4-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">A</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">B</span><span style="background:#3a3a3a"> </span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">C</span><span style="background:#40e0ff"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">D</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">E</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">F</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">G</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">H</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">I</span><span style="background:#3a3a3a"> </span>5-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">1</span><span style="background:#3a3a3a"> </span><span style="background:#000000;text-decoration:underline"> </span><span style="color:#959595;background:#000000;text-decoration:underline">r1</span><span style="background:#000000;text-decoration:underline"> </span><span style="color:#959595;background:#000000;text-decoration:underline">ls</span><span style="background:#000000;text-decoration:underline"> </span><span style="background:#000000"> </span>6-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">2</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">name</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">type</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">size</span><span style="background:#000000"> </span>7-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">3</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">012</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">4.2</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>8-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">4</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">CLAUDE.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">1.0</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>9-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">5</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">README.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">9.8</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>10-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">6</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">demos</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">dir</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">3.1</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>11-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">7</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">go.mod</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">812</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">B</span><span style="background:#000000"> </span>12-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">8</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>13-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">9</span><span style="background:#3a3a3a"> </span><span style="background:#000000;text-decoration:underline"> </span><span style="color:#959595;background:#000000;text-decoration:underline">big</span><span style="background:#000000;text-decoration:underline"> </span><span style="color:#959595;background:#000000;text-decoration:underline">$r1</span><span style="background:#000000;text-decoration:underline"> </span><span style="color:#959595;background:#000000;text-decoration:underline">|</span><span style="background:#000000;text-decoration:underline"> </span><span style="color:#959595;background:#000000;text-decoration:underline">where</span><span style="background:#000000;text-decoration:underline"> </span><span style="color:#959595;background:#000000;text-decoration:underline">size</span><span style="background:#000000;text-decoration:underline"> </span><span style="color:#959595;background:#000000;text-decoration:underline">&gt;</span><span style="background:#000000;text-decoration:underline"> </span><span style="color:#959595;background:#000000;text-decoration:underline">1kb</span><span style="background:#000000;text-decoration:underline"> </span><span style="background:#000000"> </span>14-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">10</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">name</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">type</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">size</span><span style="background:#000000"> </span>15-<span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">11</span><span style="background:#40e0ff"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">012</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff">4.2</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff">kB</span><span style="background:#40e0ff"> </span><span style="background:#000000"> </span>16-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">12</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">README.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">9.8</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>17-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">13</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">demos</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">dir</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">3.1</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>18-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">14</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>19-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">15</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>20-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">16</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>21-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">17</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>22-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">18</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>23-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">19</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>24-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">20</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>25-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">21</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>26-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">22</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>27-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">23</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>28-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">24</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>29-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">25</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>30-<span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">Shell</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">1</span><span style="background:#40e0ff"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">+</span><span style="background:#1f1f1f"> </span><span style="color:#757575;background:#1f1f1f">│</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">untitled</span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">modified</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+K</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">search</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+/</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">shortcuts</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">F10</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">menu</span></pre>\ No newline at end of file
D e2e/testdata/screens/notebook-regions-light.html +0 -30
@@ -1,30 +0,0 @@
1-<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">nu❯</span> <span style="color:var(--vt-palette-0)">$r1</span> <span style="color:var(--vt-palette-0)">|</span> <span style="color:var(--vt-palette-0)">where</span> <span style="color:var(--vt-palette-0)">size</span> <span style="color:var(--vt-palette-0)">&gt;</span> <span style="color:var(--vt-palette-0)">1kb</span>3-<span style="color:var(--vt-palette-0)">Region</span> <span style="color:var(--vt-palette-0)">big</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">$r1</span> <span style="color:var(--vt-palette-0)">|</span> <span style="color:var(--vt-palette-0)">where</span> <span style="color:var(--vt-palette-0)">size</span> <span style="color:var(--vt-palette-0)">&gt;</span> <span style="color:var(--vt-palette-0)">1kb</span>4-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">C</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>5-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">1</span><span style="background:var(--vt-palette-7)"> </span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-0);text-decoration:underline">r1</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-0);text-decoration:underline">ls</span><span style="text-decoration:underline"> </span>6-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> name type size7-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> 012 file 4.2 kB8-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> CLAUDE.md file 1.0 kB9-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> README.md file 9.8 kB10-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> demos dir 3.1 kB11-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> go.mod file 812 B12-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span>13-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-0);text-decoration:underline">big</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-0);text-decoration:underline">$r1</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-0);text-decoration:underline">|</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-0);text-decoration:underline">where</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-0);text-decoration:underline">size</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-0);text-decoration:underline">&gt;</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-0);text-decoration:underline">1kb</span><span style="text-decoration:underline"> </span>14-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> name type size15-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">11</span><span style="background:var(--vt-palette-6)"> </span> 012 file <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">4.2</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">kB</span><span style="background:var(--vt-palette-6)"> </span>16-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span> README.md file 9.8 kB17-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> demos dir 3.1 kB18-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span>19-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span>20-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span>21-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">17</span><span style="background:var(--vt-palette-7)"> </span>22-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span>23-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span>24-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span>25-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span>26-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span>27-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span>28-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span>29-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>30-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Shell</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">menu</span></pre>\ No newline at end of file
D e2e/testdata/screens/notebook-regions.html +0 -30
@@ -1,30 +0,0 @@
1-<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">nu❯</span> <span style="color:var(--vt-palette-8)">$r1</span> <span style="color:var(--vt-palette-8)">|</span> <span style="color:var(--vt-palette-8)">where</span> <span style="color:var(--vt-palette-8)">size</span> <span style="color:var(--vt-palette-8)">&gt;</span> <span style="color:var(--vt-palette-8)">1kb</span>3-<span style="color:var(--vt-palette-8)">Region</span> <span style="color:var(--vt-palette-8)">big</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">$r1</span> <span style="color:var(--vt-palette-8)">|</span> <span style="color:var(--vt-palette-8)">where</span> <span style="color:var(--vt-palette-8)">size</span> <span style="color:var(--vt-palette-8)">&gt;</span> <span style="color:var(--vt-palette-8)">1kb</span>4-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">C</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-8);text-decoration:underline">r1</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-8);text-decoration:underline">ls</span><span style="text-decoration:underline"> </span>6-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> name type size7-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> 012 file 4.2 kB8-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> CLAUDE.md file 1.0 kB9-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> README.md file 9.8 kB10-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> demos dir 3.1 kB11-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> go.mod file 812 B12-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span>13-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-8);text-decoration:underline">big</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-8);text-decoration:underline">$r1</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-8);text-decoration:underline">|</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-8);text-decoration:underline">where</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-8);text-decoration:underline">size</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-8);text-decoration:underline">&gt;</span><span style="text-decoration:underline"> </span><span style="color:var(--vt-palette-8);text-decoration:underline">1kb</span><span style="text-decoration:underline"> </span>14-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> name type size15-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">11</span><span style="background:var(--vt-palette-6)"> </span> 012 file <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">4.2</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">kB</span><span style="background:var(--vt-palette-6)"> </span>16-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> README.md file 9.8 kB17-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> demos dir 3.1 kB18-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span>19-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span>20-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span>21-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Shell</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-running-high-contrast.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">F</span><span style="color:#ffffff;background:#1f1f1f">ile</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">E</span><span style="color:#ffffff;background:#1f1f1f">dit</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">V</span><span style="color:#ffffff;background:#1f1f1f">iew</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">I</span><span style="color:#ffffff;background:#1f1f1f">nsert</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">F</span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">o</span><span style="color:#ffffff;background:#1f1f1f">rmat</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">D</span><span style="color:#ffffff;background:#1f1f1f">ata</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">H</span><span style="color:#ffffff;background:#1f1f1f">elp</span><span style="background:#1f1f1f"> </span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">NOTEBOOK</span><span style="background:#40e0ff"> </span>2+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">cell</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">5</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">a</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">nushell</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">pipeline</span><span style="background:#000000"> </span>3+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Enter</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">edit</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Shift+Enter</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">run</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">a</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">add</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">above</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">b</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">below</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">dd</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">delete</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">G</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">send</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">to</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">a</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">sheet</span><span style="background:#000000"> </span>4+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Notebook</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">5</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">cells</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">running</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">1,</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">1</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">waiting</span><span style="background:#3a3a3a"> </span>5+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">Files</span><span style="background:#000000"> </span>6+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">What&#39;s</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">big</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">in</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">the</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">repo,</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">from</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">ls</span><span style="color:#ffffff;background:#000000">.</span><span style="background:#000000"> </span>7+<span style="background:#000000"> </span>8+<span style="background:#000000"> </span><span style="color:#959595;background:#000000">[3]</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">files</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">✓</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">&lt;1s</span><span style="background:#000000"> </span>9+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ff80ff;background:#000000">files</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">=</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">ls</span><span style="background:#000000"> </span>10+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">name</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">type</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">size</span><span style="background:#000000"> </span>11+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">012</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">4.2</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>12+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">CLAUDE.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">1.0</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>13+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">README.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">9.8</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>14+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">demos</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">dir</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">3.1</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>15+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">go.mod</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">812</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">B</span><span style="background:#000000"> </span>16+<span style="background:#000000"> </span>17+<span style="background:#000000"> </span><span style="color:#959595;background:#000000">[4]</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">✓</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">&lt;1s</span><span style="background:#000000"> </span>18+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ff80ff;background:#000000">$files</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">|</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">where</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">size</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">&gt;</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">1kb</span><span style="background:#000000"> </span>19+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">name</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">type</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">size</span><span style="background:#000000"> </span>20+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">012</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">4.2</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>21+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">README.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">9.8</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>22+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">demos</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">dir</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">3.1</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>23+<span style="background:#000000"> </span>24+<span style="background:#000000"> </span><span style="color:#959595;background:#000000">[*]</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">running</span><span style="background:#000000"> </span>25+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">sleep</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">10min</span><span style="background:#000000"> </span>26+<span style="background:#000000"> </span>27+<span style="background:#0033cc"> </span><span style="color:#ffffff;background:#0033cc">[</span><span style="background:#0033cc"> </span><span style="color:#ffffff;background:#0033cc">]</span><span style="background:#0033cc"> </span><span style="color:#ffffff;background:#0033cc">waiting</span><span style="background:#0033cc"> </span><span style="background:#000000"> </span>28+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">{name:</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">1</span><span style="color:#ffffff;background:#000000">}</span><span style="background:#000000"> </span>29+<span style="background:#000000"> </span>30+<span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">❯</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">Notebook</span><span style="background:#40e0ff"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">+</span><span style="background:#1f1f1f"> </span><span style="color:#757575;background:#1f1f1f">│</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">untitled</span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">modified</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+K</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">search</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+/</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">shortcuts</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">F10</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-running-light.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">NOTEBOOK</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">cell</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">a</span> <span style="color:var(--vt-palette-0)">nushell</span> <span style="color:var(--vt-palette-0)">pipeline</span>3+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">edit</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Shift+Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">run</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">a</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">add</span> <span style="color:var(--vt-palette-0)">above</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">b</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">below</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">dd</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">delete</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">send</span> <span style="color:var(--vt-palette-0)">to</span> <span style="color:var(--vt-palette-0)">a</span> <span style="color:var(--vt-palette-0)">sheet</span>4+ Notebook 5 cells running 1, 1 waiting5+<span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold;text-decoration:underline">Files</span>6+<span style="color:var(--vt-palette-8)">│</span> What&#39;s big in the repo, from <span style="color:var(--vt-palette-4)">ls</span>.7+8+ <span style="color:var(--vt-palette-0)">[3]</span> <span style="color:var(--vt-palette-0)">files</span> <span style="color:var(--vt-palette-0)">✓</span> <span style="color:var(--vt-palette-0)">&lt;1s</span>9+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">files</span> <span style="font-weight:bold">=</span> <span style="color:var(--vt-palette-4)">ls</span>10+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>11+ 012 file 4.2 kB12+ CLAUDE.md file 1.0 kB13+ README.md file 9.8 kB14+ demos dir 3.1 kB15+ go.mod file 812 B16+17+ <span style="color:var(--vt-palette-0)">[4]</span> <span style="color:var(--vt-palette-0)">✓</span> <span style="color:var(--vt-palette-0)">&lt;1s</span>18+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">where</span> size <span style="font-weight:bold">&gt;</span> <span style="color:var(--vt-palette-1)">1kb</span>19+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>20+ 012 file 4.2 kB21+ README.md file 9.8 kB22+ demos dir 3.1 kB23+24+ <span style="color:var(--vt-palette-0)">[*]</span> <span style="color:var(--vt-palette-0)">running</span>25+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">sleep</span> <span style="color:var(--vt-palette-1)">10min</span>26+27+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">[</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">]</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">waiting</span><span style="background:var(--vt-palette-4)"> </span>28+<span style="color:var(--vt-palette-8)">│</span> {name: <span style="color:var(--vt-palette-1)">1</span>}29+30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-running.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">NOTEBOOK</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">cell</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">a</span> <span style="color:var(--vt-palette-8)">nushell</span> <span style="color:var(--vt-palette-8)">pipeline</span>3+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">edit</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">run</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">a</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">add</span> <span style="color:var(--vt-palette-8)">above</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">b</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">below</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">dd</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">delete</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">send</span> <span style="color:var(--vt-palette-8)">to</span> <span style="color:var(--vt-palette-8)">a</span> <span style="color:var(--vt-palette-8)">sheet</span>4+ Notebook 5 cells running 1, 1 waiting5+<span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold;text-decoration:underline">Files</span>6+<span style="color:var(--vt-palette-8)">│</span> What&#39;s big in the repo, from <span style="color:var(--vt-palette-6)">ls</span>.7+8+ <span style="color:var(--vt-palette-8)">[3]</span> <span style="color:var(--vt-palette-8)">files</span> <span style="color:var(--vt-palette-8)">✓</span> <span style="color:var(--vt-palette-8)">&lt;1s</span>9+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">files</span> <span style="font-weight:bold">=</span> <span style="color:var(--vt-palette-6)">ls</span>10+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>11+ 012 file 4.2 kB12+ CLAUDE.md file 1.0 kB13+ README.md file 9.8 kB14+ demos dir 3.1 kB15+ go.mod file 812 B16+17+ <span style="color:var(--vt-palette-8)">[4]</span> <span style="color:var(--vt-palette-8)">✓</span> <span style="color:var(--vt-palette-8)">&lt;1s</span>18+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">where</span> size <span style="font-weight:bold">&gt;</span> <span style="color:var(--vt-palette-3)">1kb</span>19+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>20+ 012 file 4.2 kB21+ README.md file 9.8 kB22+ demos dir 3.1 kB23+24+ <span style="color:var(--vt-palette-8)">[*]</span> <span style="color:var(--vt-palette-8)">running</span>25+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">sleep</span> <span style="color:var(--vt-palette-3)">10min</span>26+27+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">[</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">]</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">waiting</span><span style="background:var(--vt-palette-4)"> </span>28+<span style="color:var(--vt-palette-8)">│</span> {name: <span style="color:var(--vt-palette-3)">1</span>}29+30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
R e2e/testdata/screens/notebook-completion-high-contrast.html → e2e/testdata/screens/notebook-sent-high-contrast.html +12 -12
@@ -1,14 +1,14 @@
1-<pre class="screen"><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">F</span><span style="color:#ffffff;background:#1f1f1f">ile</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">E</span><span style="color:#ffffff;background:#1f1f1f">dit</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">V</span><span style="color:#ffffff;background:#1f1f1f">iew</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">I</span><span style="color:#ffffff;background:#1f1f1f">nsert</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">F</span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">o</span><span style="color:#ffffff;background:#1f1f1f">rmat</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">D</span><span style="color:#ffffff;background:#1f1f1f">ata</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">H</span><span style="color:#ffffff;background:#1f1f1f">elp</span><span style="background:#1f1f1f"> </span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">NU</span><span style="background:#40e0ff"> </span>2-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">A1</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">nu❯</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">$r1</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">|</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">so</span><span style="background:#000000"> </span>3-<span style="color:#959595;background:#000000">r1:</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">5</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">rows</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Esc</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">back</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">to</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">the</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">grid</span>4-<span style="background:#3a3a3a"> </span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">A</span><span style="background:#40e0ff"> </span><span style="color:#757575;background:#000000">┌─</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">Completions</span><span style="background:#000000"> </span><span style="color:#757575;background:#000000">────────────────┐</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">E</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">F</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">G</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">H</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">I</span><span style="background:#3a3a3a"> </span>5-<span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">1</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff">r1</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff">l</span><span style="color:#757575;background:#000000">│</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff">sort-by</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff">command</span><span style="background:#40e0ff"> </span><span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span>6-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">2</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">name</span><span style="background:#000000"> </span><span style="color:#757575;background:#000000">└─────────────────────</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">1</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">of</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">1</span><span style="background:#000000"> </span><span style="color:#757575;background:#000000">─┘</span><span style="background:#000000"> </span>7-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">3</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">012</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">4.2</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>8-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">4</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">CLAUDE.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">1.0</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>9-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">5</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">README.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">9.8</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>10-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">6</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">demos</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">dir</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">3.1</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>11-<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">7</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">go.mod</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">812</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">B</span><span style="background:#000000"> </span>1+<pre class="screen"><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">F</span><span style="color:#ffffff;background:#1f1f1f">ile</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">E</span><span style="color:#ffffff;background:#1f1f1f">dit</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">V</span><span style="color:#ffffff;background:#1f1f1f">iew</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">I</span><span style="color:#ffffff;background:#1f1f1f">nsert</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">F</span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">o</span><span style="color:#ffffff;background:#1f1f1f">rmat</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">D</span><span style="color:#ffffff;background:#1f1f1f">ata</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">H</span><span style="color:#ffffff;background:#1f1f1f">elp</span><span style="background:#1f1f1f"> </span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">READY</span><span style="background:#40e0ff"> </span>2+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">A2</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">012</span><span style="background:#000000"> </span>3+<span style="color:#959595;background:#000000">Output</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">of</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">files</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">from</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">Notebook,</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">nu.files</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">in</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">formulas</span><span style="background:#000000"> </span>4+<span style="background:#3a3a3a"> </span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">A</span><span style="background:#40e0ff"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">B</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">C</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">D</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">E</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">F</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">G</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">H</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">I</span><span style="background:#3a3a3a"> </span>5+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">1</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">name</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">type</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">size</span><span style="background:#000000"> </span>6+<span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">2</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff">012</span><span style="background:#40e0ff"> </span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">file</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">4.2</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">kB</span><span style="background:#000000"> </span>7+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">3</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">CLAUDE.md</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">file</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">1.0</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">kB</span><span style="background:#000000"> </span>8+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">4</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">README.md</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">file</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">9.8</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">kB</span><span style="background:#000000"> </span>9+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">5</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">demos</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">dir</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">3.1</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">kB</span><span style="background:#000000"> </span>10+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">6</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">go.mod</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">file</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">812</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">B</span><span style="background:#000000"> </span>11+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">7</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>1212 <span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">8</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>1313 <span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">9</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>1414 <span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">10</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>
@@ -27,4 +27,4 @@
2727 <span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">23</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>2828 <span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">24</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>2929 <span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">25</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span>30-<span style="color:#ffffff;background:#1f1f1f">command</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Tab</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">complete</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+N</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">next</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Enter</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">run</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Esc</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">grid</span></pre>\ No newline at end of file30+<span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">❯</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">Notebook</span><span style="background:#1f1f1f"> </span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">files</span><span style="background:#40e0ff"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">+</span><span style="background:#1f1f1f"> </span><span style="color:#757575;background:#1f1f1f">│</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">untitled</span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">modified</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+K</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">search</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+/</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">shortcuts</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">F10</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-sent-light.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A2</span><span style="background:var(--vt-palette-7)"> </span> 0123+<span style="color:var(--vt-palette-0)">Output</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">files</span> <span style="color:var(--vt-palette-0)">from</span> <span style="color:var(--vt-palette-0)">Notebook,</span> <span style="color:var(--vt-palette-0)">nu.files</span> <span style="color:var(--vt-palette-0)">in</span> <span style="color:var(--vt-palette-0)">formulas</span>4+<span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">1</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-4);font-style:italic">name</span> <span style="color:var(--vt-palette-4);font-style:italic">type</span> <span style="color:var(--vt-palette-4);font-style:italic">size</span>6+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">2</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">012</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-4);font-style:italic">file</span> <span style="color:var(--vt-palette-4);font-style:italic">4.2</span> <span style="color:var(--vt-palette-4);font-style:italic">kB</span>7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-4);font-style:italic">CLAUDE.md</span> <span style="color:var(--vt-palette-4);font-style:italic">file</span> <span style="color:var(--vt-palette-4);font-style:italic">1.0</span> <span style="color:var(--vt-palette-4);font-style:italic">kB</span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-4);font-style:italic">README.md</span> <span style="color:var(--vt-palette-4);font-style:italic">file</span> <span style="color:var(--vt-palette-4);font-style:italic">9.8</span> <span style="color:var(--vt-palette-4);font-style:italic">kB</span>9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-4);font-style:italic">demos</span> <span style="color:var(--vt-palette-4);font-style:italic">dir</span> <span style="color:var(--vt-palette-4);font-style:italic">3.1</span> <span style="color:var(--vt-palette-4);font-style:italic">kB</span>10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-4);font-style:italic">go.mod</span> <span style="color:var(--vt-palette-4);font-style:italic">file</span> <span style="color:var(--vt-palette-4);font-style:italic">812</span> <span style="color:var(--vt-palette-4);font-style:italic">B</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span>12+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span>13+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span>14+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span>15+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span>16+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span>17+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span>18+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span>19+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span>20+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span>21+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">17</span><span style="background:var(--vt-palette-7)"> </span>22+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span>23+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span>24+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span>25+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span>26+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span>27+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span>28+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span>29+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>30+ ❯ Notebook <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">files</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-sent.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A2</span><span style="background:var(--vt-palette-8)"> </span> 0123+<span style="color:var(--vt-palette-8)">Output</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">files</span> <span style="color:var(--vt-palette-8)">from</span> <span style="color:var(--vt-palette-8)">Notebook,</span> <span style="color:var(--vt-palette-8)">nu.files</span> <span style="color:var(--vt-palette-8)">in</span> <span style="color:var(--vt-palette-8)">formulas</span>4+<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-style:italic">name</span> <span style="color:var(--vt-palette-6);font-style:italic">type</span> <span style="color:var(--vt-palette-6);font-style:italic">size</span>6+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">2</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">012</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-6);font-style:italic">file</span> <span style="color:var(--vt-palette-6);font-style:italic">4.2</span> <span style="color:var(--vt-palette-6);font-style:italic">kB</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-style:italic">CLAUDE.md</span> <span style="color:var(--vt-palette-6);font-style:italic">file</span> <span style="color:var(--vt-palette-6);font-style:italic">1.0</span> <span style="color:var(--vt-palette-6);font-style:italic">kB</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-style:italic">README.md</span> <span style="color:var(--vt-palette-6);font-style:italic">file</span> <span style="color:var(--vt-palette-6);font-style:italic">9.8</span> <span style="color:var(--vt-palette-6);font-style:italic">kB</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-style:italic">demos</span> <span style="color:var(--vt-palette-6);font-style:italic">dir</span> <span style="color:var(--vt-palette-6);font-style:italic">3.1</span> <span style="color:var(--vt-palette-6);font-style:italic">kB</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-style:italic">go.mod</span> <span style="color:var(--vt-palette-6);font-style:italic">file</span> <span style="color:var(--vt-palette-6);font-style:italic">812</span> <span style="color:var(--vt-palette-6);font-style:italic">B</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30+ ❯ Notebook <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">files</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-stale-high-contrast.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">F</span><span style="color:#ffffff;background:#1f1f1f">ile</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">E</span><span style="color:#ffffff;background:#1f1f1f">dit</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">V</span><span style="color:#ffffff;background:#1f1f1f">iew</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">I</span><span style="color:#ffffff;background:#1f1f1f">nsert</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">F</span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">o</span><span style="color:#ffffff;background:#1f1f1f">rmat</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">D</span><span style="color:#ffffff;background:#1f1f1f">ata</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">H</span><span style="color:#ffffff;background:#1f1f1f">elp</span><span style="background:#1f1f1f"> </span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">NOTEBOOK</span><span style="background:#40e0ff"> </span>2+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">files</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">a</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">nushell</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">pipeline;</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">read</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">as</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">$files,</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">nu.files</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">in</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">formulas</span><span style="background:#000000"> </span>3+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Enter</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">edit</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Shift+Enter</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">run</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">a</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">add</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">above</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">b</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">below</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">dd</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">delete</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">G</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">send</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">to</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">a</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">sheet</span><span style="background:#000000"> </span>4+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Notebook</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">4</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">cells</span><span style="background:#3a3a3a"> </span>5+<span style="background:#000000"> </span><span style="color:#959595;background:#000000">Markdown</span><span style="background:#000000"> </span>6+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">Files</span><span style="background:#000000"> </span>7+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">What&#39;s</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">big</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">in</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">the</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">repo,</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">from</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">ls</span><span style="color:#ffffff;background:#000000">.</span><span style="background:#000000"> </span>8+<span style="background:#000000"> </span>9+<span style="background:#0033cc"> </span><span style="color:#ffffff;background:#0033cc">[1]</span><span style="background:#0033cc"> </span><span style="color:#ffffff;background:#0033cc">files</span><span style="background:#0033cc"> </span><span style="color:#ffffff;background:#0033cc">stale</span><span style="background:#0033cc"> </span><span style="color:#ffffff;background:#0033cc">&lt;1s</span><span style="background:#0033cc"> </span><span style="background:#000000"> </span>10+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ff80ff;background:#000000">files</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">=</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">ls</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">|</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">first</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">4</span><span style="background:#000000"> </span>11+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">name</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">type</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">size</span><span style="background:#000000"> </span>12+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">012</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">4.2</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>13+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">CLAUDE.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">1.0</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>14+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">README.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">9.8</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>15+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">demos</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">dir</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">3.1</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>16+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">go.mod</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">812</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">B</span><span style="background:#000000"> </span>17+<span style="background:#000000"> </span>18+<span style="background:#000000"> </span><span style="color:#959595;background:#000000">[2]</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000;font-style:italic">stale</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">&lt;1s</span><span style="background:#000000"> </span>19+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ff80ff;background:#000000">$files</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">|</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">where</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">size</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">&gt;</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">1kb</span><span style="background:#000000"> </span>20+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">name</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">type</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">size</span><span style="background:#000000"> </span>21+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">012</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">4.2</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>22+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">README.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">9.8</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>23+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">demos</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">dir</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">3.1</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>24+<span style="background:#000000"> </span>25+<span style="background:#000000"> </span><span style="color:#959595;background:#000000">[</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">]</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">not</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">run</span><span style="background:#000000"> </span>26+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span>27+<span style="background:#000000"> </span>28+<span style="background:#000000"> </span>29+<span style="background:#000000"> </span>30+<span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">❯</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">Notebook</span><span style="background:#40e0ff"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">+</span><span style="background:#1f1f1f"> </span><span style="color:#757575;background:#1f1f1f">│</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">untitled</span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">modified</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+K</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">search</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+/</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">shortcuts</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">F10</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-stale-light.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">NOTEBOOK</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">files</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">a</span> <span style="color:var(--vt-palette-0)">nushell</span> <span style="color:var(--vt-palette-0)">pipeline;</span> <span style="color:var(--vt-palette-0)">read</span> <span style="color:var(--vt-palette-0)">as</span> <span style="color:var(--vt-palette-0)">$files,</span> <span style="color:var(--vt-palette-0)">nu.files</span> <span style="color:var(--vt-palette-0)">in</span> <span style="color:var(--vt-palette-0)">formulas</span>3+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">edit</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Shift+Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">run</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">a</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">add</span> <span style="color:var(--vt-palette-0)">above</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">b</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">below</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">dd</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">delete</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">send</span> <span style="color:var(--vt-palette-0)">to</span> <span style="color:var(--vt-palette-0)">a</span> <span style="color:var(--vt-palette-0)">sheet</span>4+ Notebook 4 cells5+ <span style="color:var(--vt-palette-0)">Markdown</span>6+<span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold;text-decoration:underline">Files</span>7+<span style="color:var(--vt-palette-8)">│</span> What&#39;s big in the repo, from <span style="color:var(--vt-palette-4)">ls</span>.8+9+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">[1]</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">files</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">stale</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">&lt;1s</span><span style="background:var(--vt-palette-4)"> </span>10+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">files</span> <span style="font-weight:bold">=</span> <span style="color:var(--vt-palette-4)">ls</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">first</span> <span style="color:var(--vt-palette-1)">4</span>11+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>12+ 012 file 4.2 kB13+ CLAUDE.md file 1.0 kB14+ README.md file 9.8 kB15+ demos dir 3.1 kB16+ go.mod file 812 B17+18+ <span style="color:var(--vt-palette-0)">[2]</span> <span style="color:var(--vt-palette-5);font-style:italic">stale</span> <span style="color:var(--vt-palette-0)">&lt;1s</span>19+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">where</span> size <span style="font-weight:bold">&gt;</span> <span style="color:var(--vt-palette-1)">1kb</span>20+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>21+ 012 file 4.2 kB22+ README.md file 9.8 kB23+ demos dir 3.1 kB24+25+ <span style="color:var(--vt-palette-0)">[</span> <span style="color:var(--vt-palette-0)">]</span> <span style="color:var(--vt-palette-0)">not</span> <span style="color:var(--vt-palette-0)">run</span>26+<span style="color:var(--vt-palette-8)">│</span>27+28+29+30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-stale.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">NOTEBOOK</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">files</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">a</span> <span style="color:var(--vt-palette-8)">nushell</span> <span style="color:var(--vt-palette-8)">pipeline;</span> <span style="color:var(--vt-palette-8)">read</span> <span style="color:var(--vt-palette-8)">as</span> <span style="color:var(--vt-palette-8)">$files,</span> <span style="color:var(--vt-palette-8)">nu.files</span> <span style="color:var(--vt-palette-8)">in</span> <span style="color:var(--vt-palette-8)">formulas</span>3+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">edit</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">run</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">a</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">add</span> <span style="color:var(--vt-palette-8)">above</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">b</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">below</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">dd</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">delete</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">send</span> <span style="color:var(--vt-palette-8)">to</span> <span style="color:var(--vt-palette-8)">a</span> <span style="color:var(--vt-palette-8)">sheet</span>4+ Notebook 4 cells5+ <span style="color:var(--vt-palette-8)">Markdown</span>6+<span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold;text-decoration:underline">Files</span>7+<span style="color:var(--vt-palette-8)">│</span> What&#39;s big in the repo, from <span style="color:var(--vt-palette-6)">ls</span>.8+9+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">[1]</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">files</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">stale</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">&lt;1s</span><span style="background:var(--vt-palette-4)"> </span>10+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">files</span> <span style="font-weight:bold">=</span> <span style="color:var(--vt-palette-6)">ls</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">first</span> <span style="color:var(--vt-palette-3)">4</span>11+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>12+ 012 file 4.2 kB13+ CLAUDE.md file 1.0 kB14+ README.md file 9.8 kB15+ demos dir 3.1 kB16+ go.mod file 812 B17+18+ <span style="color:var(--vt-palette-8)">[2]</span> <span style="color:var(--vt-palette-3);font-style:italic">stale</span> <span style="color:var(--vt-palette-8)">&lt;1s</span>19+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">where</span> size <span style="font-weight:bold">&gt;</span> <span style="color:var(--vt-palette-3)">1kb</span>20+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>21+ 012 file 4.2 kB22+ README.md file 9.8 kB23+ demos dir 3.1 kB24+25+ <span style="color:var(--vt-palette-8)">[</span> <span style="color:var(--vt-palette-8)">]</span> <span style="color:var(--vt-palette-8)">not</span> <span style="color:var(--vt-palette-8)">run</span>26+<span style="color:var(--vt-palette-8)">│</span>27+28+29+30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-wrap-high-contrast.html +24 -0
@@ -0,0 +1,24 @@
1+<pre class="screen"><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">F</span><span style="color:#ffffff;background:#1f1f1f">ile</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">E</span><span style="color:#ffffff;background:#1f1f1f">dit</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">V</span><span style="color:#ffffff;background:#1f1f1f">iew</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">I</span><span style="color:#ffffff;background:#1f1f1f">nsert</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">F</span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">o</span><span style="color:#ffffff;background:#1f1f1f">rmat</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">D</span><span style="color:#ffffff;background:#1f1f1f">ata</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">H</span><span style="color:#ffffff;background:#1f1f1f">elp</span><span style="background:#1f1f1f"> </span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">EDIT</span><span style="background:#40e0ff"> </span>2+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">cell</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">4</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">a</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">nushell</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">pipeline</span><span style="background:#000000"> </span>3+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Shift+Enter</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">run</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+Enter</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">run</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">here</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Tab</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">complete</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Esc</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">done</span>4+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Notebook</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">4</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">cells</span><span style="background:#3a3a3a"> </span>5+<span style="background:#000000"> </span><span style="color:#959595;background:#000000">[1]</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">files</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">✓</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">&lt;1s</span><span style="background:#000000"> </span>6+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ff80ff;background:#000000">files</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">=</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">ls</span><span style="background:#000000"> </span>7+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">name</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">type</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">size</span><span style="background:#000000"> </span>8+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">012</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">4.2</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>9+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">CLAUDE.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">1.0</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>10+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">README.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">9.8</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>11+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">demos</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">dir</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">3.1</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>12+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">go.mod</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">812</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">B</span><span style="background:#000000"> </span>13+<span style="background:#000000"> </span>14+<span style="background:#000000"> </span><span style="color:#959595;background:#000000">[2]</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">✓</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">&lt;1s</span><span style="background:#000000"> </span>15+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ff80ff;background:#000000">$files</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">|</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">where</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">size</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">&gt;</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">1kb</span><span style="background:#000000"> </span>16+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">name</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">type</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">size</span><span style="background:#000000"> </span>17+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">012</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">4.2</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>18+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">README.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">9.8</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>19+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">demos</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">dir</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">3.1</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>20+<span style="background:#000000"> </span>21+<span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff">[</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff">]</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff">not</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff">run</span><span style="background:#40e0ff"> </span><span style="background:#000000"> </span>22+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ff80ff;background:#000000">$files</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">|</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">where</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">size</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">&gt;</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">1kb</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">|</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">where</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">type</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">==</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">|</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">sort-by</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">size</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">--reverse</span><span style="background:#000000"> </span>23+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">|</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">select</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">name</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">size</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">|</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">first</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">10</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">|</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">rename</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">bytes</span><span style="background:#000000"> </span>24+<span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">❯</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">Notebook</span><span style="background:#40e0ff"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">+</span><span style="background:#1f1f1f"> </span><span style="color:#757575;background:#1f1f1f">│</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">untitled</span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">modified</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+K</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">search</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+/</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">shortcuts</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-wrap-light.html +24 -0
@@ -0,0 +1,24 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">EDIT</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">cell</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">a</span> <span style="color:var(--vt-palette-0)">nushell</span> <span style="color:var(--vt-palette-0)">pipeline</span>3+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Shift+Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">run</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">run</span> <span style="color:var(--vt-palette-0)">here</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Tab</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">complete</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">done</span>4+ Notebook 4 cells5+ <span style="color:var(--vt-palette-0)">[1]</span> <span style="color:var(--vt-palette-0)">files</span> <span style="color:var(--vt-palette-0)">✓</span> <span style="color:var(--vt-palette-0)">&lt;1s</span>6+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">files</span> <span style="font-weight:bold">=</span> <span style="color:var(--vt-palette-4)">ls</span>7+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>8+ 012 file 4.2 kB9+ CLAUDE.md file 1.0 kB10+ README.md file 9.8 kB11+ demos dir 3.1 kB12+ go.mod file 812 B13+14+ <span style="color:var(--vt-palette-0)">[2]</span> <span style="color:var(--vt-palette-0)">✓</span> <span style="color:var(--vt-palette-0)">&lt;1s</span>15+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">where</span> size <span style="font-weight:bold">&gt;</span> <span style="color:var(--vt-palette-1)">1kb</span>16+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>17+ 012 file 4.2 kB18+ README.md file 9.8 kB19+ demos dir 3.1 kB20+21+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">[</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">]</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">not</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">run</span><span style="background:var(--vt-palette-6)"> </span>22+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">where</span> size <span style="font-weight:bold">&gt;</span> <span style="color:var(--vt-palette-1)">1kb</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">where</span> type <span style="font-weight:bold">==</span> file <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">sort-by</span> size --reverse23+<span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">select</span> name size <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">first</span> <span style="color:var(--vt-palette-1)">10</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">rename</span> file bytes24+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-wrap-narrow-high-contrast.html +24 -0
@@ -0,0 +1,24 @@
1+<pre class="screen"><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">F</span><span style="color:#ffffff;background:#1f1f1f">ile</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">E</span><span style="color:#ffffff;background:#1f1f1f">dit</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">V</span><span style="color:#ffffff;background:#1f1f1f">iew</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">I</span><span style="color:#ffffff;background:#1f1f1f">nsert</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">F</span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">o</span><span style="color:#ffffff;background:#1f1f1f">rmat</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">D</span><span style="color:#ffffff;background:#1f1f1f">ata</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f;text-decoration:underline">H</span><span style="color:#ffffff;background:#1f1f1f">elp</span><span style="background:#1f1f1f"> </span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">NOTEBOOK</span><span style="background:#40e0ff"> </span>2+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">cell</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">4</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">reads</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">$files</span><span style="background:#000000"> </span>3+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Enter</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">edit</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Shift+Enter</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">run</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">a</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">add</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">above</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">b</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">below</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span>4+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Notebook</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">4</span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">cells</span><span style="background:#3a3a3a"> </span>5+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">name</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">type</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">size</span><span style="background:#000000"> </span>6+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">012</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">4.2</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>7+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">CLAUDE.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">1.0</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>8+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">README.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">9.8</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>9+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">demos</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">dir</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">3.1</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>10+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">go.mod</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">812</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">B</span><span style="background:#000000"> </span>11+<span style="background:#000000"> </span>12+<span style="background:#000000"> </span><span style="color:#959595;background:#000000">[2]</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">✓</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">&lt;1s</span><span style="background:#000000"> </span>13+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ff80ff;background:#000000">$files</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">|</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">where</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">size</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">&gt;</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">1kb</span><span style="background:#000000"> </span>14+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">name</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">type</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">size</span><span style="background:#000000"> </span>15+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">012</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">4.2</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>16+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">README.md</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">9.8</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>17+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">demos</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">dir</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">3.1</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">kB</span><span style="background:#000000"> </span>18+<span style="background:#000000"> </span>19+<span style="background:#0033cc"> </span><span style="color:#ffffff;background:#0033cc">[3]</span><span style="background:#0033cc"> </span><span style="color:#ffffff;background:#0033cc">✓</span><span style="background:#0033cc"> </span><span style="color:#ffffff;background:#0033cc">&lt;1s</span><span style="background:#0033cc"> </span><span style="background:#000000"> </span>20+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ff80ff;background:#000000">$files</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">|</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">where</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">size</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">&gt;</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">1kb</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">|</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">where</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">type</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">==</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span>21+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">|</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">sort-by</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">size</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">--reverse</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">|</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">select</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">name</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">size</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">|</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">first</span><span style="background:#000000"> </span><span style="color:#ffe040;background:#000000">10</span><span style="background:#000000"> </span>22+<span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">|</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000">rename</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">file</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">bytes</span><span style="background:#000000"> </span>23+<span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">name</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">type</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">size</span><span style="background:#000000"> </span>24+<span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">❯</span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">Notebook</span><span style="background:#40e0ff"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">+</span><span style="background:#1f1f1f"> </span><span style="color:#757575;background:#1f1f1f">│</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">untitled</span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">modified</span><span style="background:#1f1f1f"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Ctrl+K</span><span style="background:#3a3a3a"> </span><span style="background:#1f1f1f"> </span><span style="color:#959595;background:#1f1f1f">search</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-wrap-narrow-light.html +24 -0
@@ -0,0 +1,24 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">NOTEBOOK</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">cell</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">reads</span> <span style="color:var(--vt-palette-0)">$files</span>3+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">edit</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Shift+Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">run</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">a</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">add</span> <span style="color:var(--vt-palette-0)">above</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">b</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">below</span> <span style="background:var(--vt-palette-7)"> </span>4+ Notebook 4 cells5+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>6+ 012 file 4.2 kB7+ CLAUDE.md file 1.0 kB8+ README.md file 9.8 kB9+ demos dir 3.1 kB10+ go.mod file 812 B11+12+ <span style="color:var(--vt-palette-0)">[2]</span> <span style="color:var(--vt-palette-0)">✓</span> <span style="color:var(--vt-palette-0)">&lt;1s</span>13+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">where</span> size <span style="font-weight:bold">&gt;</span> <span style="color:var(--vt-palette-1)">1kb</span>14+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>15+ 012 file 4.2 kB16+ README.md file 9.8 kB17+ demos dir 3.1 kB18+19+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">[3]</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">✓</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-4)">&lt;1s</span><span style="background:var(--vt-palette-4)"> </span>20+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">where</span> size <span style="font-weight:bold">&gt;</span> <span style="color:var(--vt-palette-1)">1kb</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">where</span> type <span style="font-weight:bold">==</span> file21+<span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">sort-by</span> size --reverse <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">select</span> name size <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">first</span> <span style="color:var(--vt-palette-1)">10</span>22+<span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-4)">rename</span> file bytes23+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>24+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-wrap-narrow.html +24 -0
@@ -0,0 +1,24 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">NOTEBOOK</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">cell</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">reads</span> <span style="color:var(--vt-palette-8)">$files</span>3+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">edit</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">run</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">a</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">add</span> <span style="color:var(--vt-palette-8)">above</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">b</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">below</span> <span style="background:var(--vt-palette-8)"> </span>4+ Notebook 4 cells5+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>6+ 012 file 4.2 kB7+ CLAUDE.md file 1.0 kB8+ README.md file 9.8 kB9+ demos dir 3.1 kB10+ go.mod file 812 B11+12+ <span style="color:var(--vt-palette-8)">[2]</span> <span style="color:var(--vt-palette-8)">✓</span> <span style="color:var(--vt-palette-8)">&lt;1s</span>13+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">where</span> size <span style="font-weight:bold">&gt;</span> <span style="color:var(--vt-palette-3)">1kb</span>14+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>15+ 012 file 4.2 kB16+ README.md file 9.8 kB17+ demos dir 3.1 kB18+19+<span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">[3]</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">✓</span><span style="background:var(--vt-palette-4)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-4)">&lt;1s</span><span style="background:var(--vt-palette-4)"> </span>20+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">where</span> size <span style="font-weight:bold">&gt;</span> <span style="color:var(--vt-palette-3)">1kb</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">where</span> type <span style="font-weight:bold">==</span> file21+<span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">sort-by</span> size --reverse <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">select</span> name size <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">first</span> <span style="color:var(--vt-palette-3)">10</span>22+<span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">rename</span> file bytes23+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>24+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span></pre>\ No newline at end of file
A e2e/testdata/screens/notebook-wrap.html +24 -0
@@ -0,0 +1,24 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">EDIT</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">cell</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">a</span> <span style="color:var(--vt-palette-8)">nushell</span> <span style="color:var(--vt-palette-8)">pipeline</span>3+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">run</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">run</span> <span style="color:var(--vt-palette-8)">here</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Tab</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">complete</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">done</span>4+ Notebook 4 cells5+ <span style="color:var(--vt-palette-8)">[1]</span> <span style="color:var(--vt-palette-8)">files</span> <span style="color:var(--vt-palette-8)">✓</span> <span style="color:var(--vt-palette-8)">&lt;1s</span>6+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">files</span> <span style="font-weight:bold">=</span> <span style="color:var(--vt-palette-6)">ls</span>7+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>8+ 012 file 4.2 kB9+ CLAUDE.md file 1.0 kB10+ README.md file 9.8 kB11+ demos dir 3.1 kB12+ go.mod file 812 B13+14+ <span style="color:var(--vt-palette-8)">[2]</span> <span style="color:var(--vt-palette-8)">✓</span> <span style="color:var(--vt-palette-8)">&lt;1s</span>15+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">where</span> size <span style="font-weight:bold">&gt;</span> <span style="color:var(--vt-palette-3)">1kb</span>16+ <span style="font-weight:bold;text-decoration:underline">name</span> <span style="font-weight:bold;text-decoration:underline">type</span> <span style="font-weight:bold;text-decoration:underline">size</span>17+ 012 file 4.2 kB18+ README.md file 9.8 kB19+ demos dir 3.1 kB20+21+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">[</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">]</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">not</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">run</span><span style="background:var(--vt-palette-6)"> </span>22+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">$files</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">where</span> size <span style="font-weight:bold">&gt;</span> <span style="color:var(--vt-palette-3)">1kb</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">where</span> type <span style="font-weight:bold">==</span> file <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">sort-by</span> size --reverse23+<span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">select</span> name size <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">first</span> <span style="color:var(--vt-palette-3)">10</span> <span style="font-weight:bold">|</span> <span style="color:var(--vt-palette-6)">rename</span> file bytes24+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">❯</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Notebook</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span></pre>\ No newline at end of file
M e2e/testdata/screens/palette-narrow.html +2 -2
@@ -10,7 +10,7 @@
1010 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-3);font-weight:bold">S</span>ort range Z to <span style="color:var(--vt-palette-3);font-weight:bold">A</span> <span style="color:var(--vt-palette-8)">│</span>1111 <span style="color:var(--vt-palette-8)">│</span> Sort <span style="color:var(--vt-palette-3);font-weight:bold">s</span>heet <span style="color:var(--vt-palette-3);font-weight:bold">A</span> to Z <span style="color:var(--vt-palette-8)">│</span>1212 <span style="color:var(--vt-palette-8)">│</span> Sort <span style="color:var(--vt-palette-3);font-weight:bold">s</span>heet Z to <span style="color:var(--vt-palette-3);font-weight:bold">A</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Show</span> <span style="color:var(--vt-palette-8)">all</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">the</span> <span style="color:var(--vt-palette-8)">output</span> <span style="color:var(--vt-palette-8)">o</span> <span style="color:var(--vt-palette-8)">│</span>1314 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Ask</span> <span style="color:var(--vt-palette-8)">JEV</span> <span style="color:var(--vt-palette-8)">again</span> <span style="color:var(--vt-palette-8)">│</span>14-<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-3);font-weight:bold">S</span>ort r<span style="color:var(--vt-palette-3);font-weight:bold">a</span>nge <span style="color:var(--vt-palette-8)">│</span>15-<span style="color:var(--vt-palette-8)">└──────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">49</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">187</span> <span style="color:var(--vt-palette-8)">─┘</span>15+<span style="color:var(--vt-palette-8)">└──────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">53</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">208</span> <span style="color:var(--vt-palette-8)">─┘</span>1616 Save the sheet u… <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Up/Down</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">move</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">run</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">close</span></pre>\ No newline at end of file
M e2e/testdata/screens/palette-search-light.html +1 -1
@@ -14,7 +14,7 @@
1414 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Reset <span style="color:var(--vt-palette-4);font-weight:bold">col</span>umn width <span style="color:var(--vt-palette-0)">Format</span> <span style="color:var(--vt-palette-8)">│</span>1515 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Insert <span style="color:var(--vt-palette-4);font-weight:bold">col</span>umns right <span style="color:var(--vt-palette-0)">Insert</span> <span style="color:var(--vt-palette-8)">│</span>1616 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Delete rows or <span style="color:var(--vt-palette-4);font-weight:bold">col</span>umns <span style="color:var(--vt-palette-0)">Edit</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+Alt+-</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>17-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└────────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">32</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">187</span> <span style="color:var(--vt-palette-8)">─┘</span>17+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└────────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">39</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">208</span> <span style="color:var(--vt-palette-8)">─┘</span>1818 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span>1919 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span>2020 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span>
M e2e/testdata/screens/palette-search.html +1 -1
@@ -14,7 +14,7 @@
1414 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Reset <span style="color:var(--vt-palette-3);font-weight:bold">col</span>umn width <span style="color:var(--vt-palette-8)">Format</span> <span style="color:var(--vt-palette-8)">│</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Insert <span style="color:var(--vt-palette-3);font-weight:bold">col</span>umns right <span style="color:var(--vt-palette-8)">Insert</span> <span style="color:var(--vt-palette-8)">│</span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Delete rows or <span style="color:var(--vt-palette-3);font-weight:bold">col</span>umns <span style="color:var(--vt-palette-8)">Edit</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Alt+-</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>17-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└────────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">32</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">187</span> <span style="color:var(--vt-palette-8)">─┘</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└────────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">39</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">208</span> <span style="color:var(--vt-palette-8)">─┘</span>1818 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span>1919 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span>2020 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span>
M e2e/testdata/screens/palette-wide.html +6 -6
@@ -6,15 +6,15 @@
66 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-3);font-weight:bold">Sel</span>ect rows <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+Space</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>77 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Rent 1450 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Select</span> <span style="color:var(--vt-palette-8)">chart</span> <span style="color:var(--vt-palette-8)">│</span>88 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Groceries 612.4 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-3);font-weight:bold">Sel</span>ect columns <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Space</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>9-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Transit 96 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Move</span> <span style="color:var(--vt-palette-8)">sheet</span> <span style="color:var(--vt-palette-8)">left</span> <span style="color:var(--vt-palette-8)">Edit</span> <span style="color:var(--vt-palette-8)">›</span> <span style="color:var(--vt-palette-8)">Sheet</span> <span style="color:var(--vt-palette-8)">│</span>10-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> Savings r <span style="color:var(--vt-palette-1);text-decoration:underline wavy var(--vt-palette-1)">#DIV/0!</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-3);font-weight:bold">S</span>h<span style="color:var(--vt-palette-3);font-weight:bold">el</span>l <span style="color:var(--vt-palette-8)">Data</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Transit 96 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Run</span> <span style="color:var(--vt-palette-8)">cell</span> <span style="color:var(--vt-palette-8)">and</span> <span style="color:var(--vt-palette-8)">select</span> <span style="color:var(--vt-palette-8)">next</span> <span style="color:var(--vt-palette-8)">Data</span> <span style="color:var(--vt-palette-8)">›</span> <span style="color:var(--vt-palette-8)">Shell</span> <span style="color:var(--vt-palette-8)">Shift+Enter</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> Savings r <span style="color:var(--vt-palette-1);text-decoration:underline wavy var(--vt-palette-1)">#DIV/0!</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Move</span> <span style="color:var(--vt-palette-8)">sheet</span> <span style="color:var(--vt-palette-8)">left</span> <span style="color:var(--vt-palette-8)">Edit</span> <span style="color:var(--vt-palette-8)">›</span> <span style="color:var(--vt-palette-8)">Sheet</span> <span style="color:var(--vt-palette-8)">│</span>1111 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">7</span><span style="background:var(--vt-palette-6)"> </span> Total <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">2158.4</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">│</span> De<span style="color:var(--vt-palette-3);font-weight:bold">sel</span>ect <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>1212 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> In<span style="color:var(--vt-palette-3);font-weight:bold">se</span>rt columns <span style="color:var(--vt-palette-3);font-weight:bold">l</span>eft <span style="color:var(--vt-palette-8)">Insert</span> <span style="color:var(--vt-palette-8)">│</span>13-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Stop</span> <span style="color:var(--vt-palette-8)">shell</span> <span style="color:var(--vt-palette-8)">command</span> <span style="color:var(--vt-palette-8)">Data</span> <span style="color:var(--vt-palette-8)">›</span> <span style="color:var(--vt-palette-8)">Shell</span> <span style="color:var(--vt-palette-8)">regio…</span> <span style="color:var(--vt-palette-8)">│</span>14-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-3);font-weight:bold">S</span>av<span style="color:var(--vt-palette-3);font-weight:bold">e</span> <span style="color:var(--vt-palette-8)">Fi</span><span style="color:var(--vt-palette-3);font-weight:bold">l</span><span style="color:var(--vt-palette-8)">e</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+S</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>15-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-3);font-weight:bold">S</span>av<span style="color:var(--vt-palette-3);font-weight:bold">e</span> as <span style="color:var(--vt-palette-8)">Fi</span><span style="color:var(--vt-palette-3);font-weight:bold">l</span><span style="color:var(--vt-palette-8)">e</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-3);font-weight:bold">S</span>av<span style="color:var(--vt-palette-3);font-weight:bold">e</span> <span style="color:var(--vt-palette-8)">Fi</span><span style="color:var(--vt-palette-3);font-weight:bold">l</span><span style="color:var(--vt-palette-8)">e</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+S</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-3);font-weight:bold">S</span>av<span style="color:var(--vt-palette-3);font-weight:bold">e</span> as <span style="color:var(--vt-palette-8)">Fi</span><span style="color:var(--vt-palette-3);font-weight:bold">l</span><span style="color:var(--vt-palette-8)">e</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Paste</span> <span style="color:var(--vt-palette-8)">cell</span> <span style="color:var(--vt-palette-8)">Data</span> <span style="color:var(--vt-palette-8)">›</span> <span style="color:var(--vt-palette-8)">Shell</span> <span style="color:var(--vt-palette-8)">v</span> <span style="color:var(--vt-palette-8)">│</span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> In<span style="color:var(--vt-palette-3);font-weight:bold">se</span>rt rows be<span style="color:var(--vt-palette-3);font-weight:bold">l</span>ow <span style="color:var(--vt-palette-8)">Insert</span> <span style="color:var(--vt-palette-8)">│</span>17-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└────────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">32</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">187</span> <span style="color:var(--vt-palette-8)">─┘</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└────────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">50</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">208</span> <span style="color:var(--vt-palette-8)">─┘</span>1818 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span>1919 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span>2020 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span>
M e2e/testdata/screens/palette.html +1 -1
@@ -14,7 +14,7 @@
1414 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Download as JSON <span style="color:var(--vt-palette-8)">File</span> <span style="color:var(--vt-palette-8)">›</span> <span style="color:var(--vt-palette-8)">Download</span> <span style="color:var(--vt-palette-8)">│</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Download as NUON <span style="color:var(--vt-palette-8)">File</span> <span style="color:var(--vt-palette-8)">›</span> <span style="color:var(--vt-palette-8)">Download</span> <span style="color:var(--vt-palette-8)">│</span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Decimal arithmetic <span style="color:var(--vt-palette-8)">File</span> <span style="color:var(--vt-palette-8)">›</span> <span style="color:var(--vt-palette-8)">Settings</span> <span style="color:var(--vt-palette-8)">│</span>17-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└───────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">187</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">187</span> <span style="color:var(--vt-palette-8)">─┘</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└───────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">208</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">208</span> <span style="color:var(--vt-palette-8)">─┘</span>1818 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span>1919 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span>2020 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span>
M internal/config/registry.go +13 -7
@@ -119,14 +119,20 @@ var Options = []Option{
119119 "The grid itself is 1,048,576 rows by 16,384 columns (A to XFD) whatever this is."},120120 121121 {Name: "shell", Kind: Enum, Group: GroupShell, Default: "ask", Values: []string{"off", "ask", "on"}, Env: []string{"O12_SHELL"}, Live: true,122- Desc: "Whether notebook sheets run nushell commands (docs/nushell/notebooks.md). `ask` runs what you type " +123- "and asks once before running the commands of a file made on another computer; `on` never asks; " +124- "`off` runs none. Opening a file never runs its commands."},122+ Desc: "Whether notebooks run their code cells (docs/nushell/notebooks.md). `ask` runs what you write " +123+ "and asks once before running the cells of a file made on another computer; `on` never asks; " +124+ "`off` runs none. Opening a file never runs its cells."},125125 {Name: "nu-timeout", Kind: Duration, Group: GroupShell, Default: "30s", Env: []string{"O12_NU_TIMEOUT"}, Live: true,126- Desc: "Stop a notebook's command that runs longer than this; 0 lets it run until Esc stops it."},126+ Desc: "Stop a notebook cell that runs longer than this; 0 lets it run until Stop (i i) stops it."},127127 {Name: "nu-config", Kind: Bool, Group: GroupShell, Default: "false", Env: []string{"O12_NU_CONFIG"}, Live: true,128- Desc: "Run notebook commands with your nushell config files (config.nu, env.nu) rather than " +128+ Desc: "Run notebook cells with your nushell config files (config.nu, env.nu) rather than " +129129 "`nu --no-config-file`, for your own commands and aliases."},130+ {Name: "nu-save-cell-kb", Kind: Int, Group: GroupShell, Default: "1024", Env: []string{"O12_NU_SAVE_CELL_KB"}, Live: true,131+ Desc: "The largest output of one cell a saved file keeps, in kilobytes of NUON; a larger one is left out " +132+ "and shows `not saved; run to see` when the file is opened."},133+ {Name: "nu-save-notebook-kb", Kind: Int, Group: GroupShell, Default: "8192", Env: []string{"O12_NU_SAVE_NOTEBOOK_KB"}, Live: true,134+ Desc: "How much of all its cells' outputs a saved file keeps, in kilobytes of NUON: the outputs that fit, " +135+ "in the notebook's order."},130136 131137 {Name: "jev-api-key-command", Kind: Command, Group: GroupJEV,132138 Desc: "A command that prints the TypeSafe API key, used when TYPESAFE_API_KEY isn't set and the " +
@@ -165,8 +171,8 @@ var Options = []Option{
165171 {Name: "serve-max-sessions", Kind: Int, Group: GroupServe, Default: "8",166172 Desc: "How many 012 serve sessions may run at once; more are turned away."},167173 {Name: "serve-shell", Kind: Bool, Group: GroupServe, Default: "false",168- Desc: "Let 012 serve sessions run notebooks' nushell commands, as the user 012 serve runs as, " +169- "following the shell option. Off, served notebooks show their regions but run nothing."},174+ Desc: "Let 012 serve sessions run notebooks' code cells, as the user 012 serve runs as, " +175+ "following the shell option. Off, served notebooks show their cells and saved outputs but run nothing."},170176 171177 {Name: "config-file", Kind: Path, Group: GroupFiles, Repeat: true,172178 Desc: "Read another config file after this one, relative to this file's directory. " +
M internal/fileio/nuon_test.go +50 -8
@@ -173,18 +173,20 @@ func TestEncodeFiltered(t *testing.T) {
173173 }174174 }175175 176-// A notebook's regions download as their values, labels included.176+// A notebook cell's output sent to a sheet downloads as its values.177177 func TestExportRegion(t *testing.T) {178178 s := sheet.New()179- s.MakeNotebook()180- if err := s.AddRegion(sheet.Region{Name: "r1", Command: "ls"}); err != nil {179+ if err := s.AddRegion(sheet.Region{Name: "r1", Output: true}); err != nil {181180 t.Fatal(err)182181 }183- d := &sheet.RegionData{Rows: 2, Cols: 2, Values: []sheet.Value{{Kind: sheet.Text, Str: "name"}, {Kind: sheet.Text, Str: "n"}, {Kind: sheet.Text, Str: "a"}, {Kind: sheet.Number, Num: 1}}}184- if err := s.ShowRegion("r1", d); err != nil {182+ rows, _, err := NUONRows(context.Background(), []byte("[[name, n]; [a, 1]]"), 0)183+ if err != nil {184+ t.Fatal(err)185+ }186+ if err := s.Book().ApplyLive(sheet.LiveOp{Region: "r1", Reset: true, Header: rows.Header, Rows: rows.Rows}); err != nil {185187 t.Fatal(err)186188 }187- if got := encode(t, s, CSV); got != "r1 ls,\nname,n\na,1\n" {189+ if got := encode(t, s, CSV); got != "name,n\na,1\n" {188190 t.Errorf("CSV %q", got)189191 }190192 path := filepath.Join(t.TempDir(), "nb.xlsx")
@@ -195,8 +197,48 @@ func TestExportRegion(t *testing.T) {
195197 if err != nil {196198 t.Fatal(err)197199 }198- if c := res.Sheet.Cell(addr(t, "B3")); c == nil || c.IsFormula() || c.Value.Num != 1 {199- t.Errorf("B3 read back as %+v", c)200+ if c := res.Sheet.Cell(addr(t, "B2")); c == nil || c.IsFormula() || c.Value.Num != 1 {201+ t.Errorf("B2 read back as %+v", c)202+ }203+}204+205+// NUONRows reads any value nu prints as a region's rows.206+func TestNUONRows(t *testing.T) {207+ for _, c := range []struct {208+ out string209+ want string210+ }{211+ {"[[name, size]; [a, 1kb], [b, 2b]]", "name size|a 1.0 kB|b 2 B"},212+ {"{a: 1, b: true}", "a b|1 TRUE"},213+ {"3", "value|3"},214+ {`"hello"`, "value|hello"},215+ {"null", "value"},216+ {"[1, 2]", "value|1|2"},217+ {"", ""},218+ } {219+ rows, _, err := NUONRows(context.Background(), []byte(c.out), 0)220+ if err != nil {221+ t.Errorf("%q: %v", c.out, err)222+ continue223+ }224+ var lines []string225+ for _, r := range append([]sheet.LiveRow{rows.Header}, rows.Rows...) {226+ var texts []string227+ for _, lc := range r {228+ texts = append(texts, sheet.FormatText(lc.V, lc.F))229+ }230+ if len(texts) > 0 {231+ lines = append(lines, strings.TrimSpace(strings.Join(texts, " ")))232+ }233+ }234+ if got := strings.Join(lines, "|"); got != c.want {235+ t.Errorf("%q read as %q, want %q", c.out, got, c.want)236+ }237+ }238+ // Past max-cells, whole rows are kept and the note says so.239+ rows, note, err := NUONRows(context.Background(), []byte("[[a, b]; [1, 2], [3, 4], [5, 6]]"), 4)240+ if err != nil || len(rows.Rows) != 1 || note == "" {241+ t.Errorf("capped: %+v %q %v", rows, note, err)200242 }201243 }202244
A internal/fileio/nuonrows.go +35 -0
A internal/fileio/sheetscorpus_test.go +241 -0
@@ -0,0 +1,241 @@
1+package fileio2+3+import (4+ "flag"5+ "fmt"6+ "os"7+ "strconv"8+ "strings"9+ "testing"10+11+ "github.com/FelineStateMachine/012/internal/sheet"12+)13+14+// The Sheets corpus (testdata/sheets_corpus.tsv) holds formulas whose15+// results 012 matches with Google Sheets on purpose, each with 012's16+// result and Sheets' as checked in Sheets. TestSheetsCorpus holds 012 to17+// its recorded results and asks every difference from Sheets for a18+// reason. The corpus reaches Sheets as an XLSX workbook written by 012's19+// own exporter (sheetscorpus_xlsx_test.go), and Sheets' answers come back20+// as a CSV download of its corpus sheet.21+var (22+ updateCorpus = flag.Bool("update-corpus", false, "record 012's current results in testdata/sheets_corpus.tsv")23+ corpusXLSX = flag.String("corpus-xlsx", "", "write the Sheets corpus as an XLSX workbook to this path, to import into Sheets")24+ corpusFrom = flag.String("update-from", "", "record Sheets' results from a CSV download of the imported corpus sheet")25+)26+27+const (28+ corpusPath = "testdata/sheets_corpus.tsv"29+ unconfirmed = "unconfirmed" // Sheets' result, until checked in Sheets30+ onPurpose = "on purpose:" // starts the note of a deliberate difference31+)32+33+// corpusFixture is the Data sheet the corpus formulas read, typed as a34+// user would: the oracle's fixture, with a text number, an error, a35+// FALSE and a #N/A in column J.36+//37+// A B C D E F G H I J38+// 1 10 north 1 Apple 1.5 9/26/2026 1 -1000 30 '339+// 2 20 south 2 Banana 2.5 2026-01-31 2 300 20 =1/040+// 3 30 north 3 Cherry 3.5 14:30 2 400 10 FALSE41+// 4 apple east 4 date x 2/29/2024 3 500 =NA()42+// 5 north 5 Apple 9 12/31/2025 543+// 6 ="" 6 TRUE44+// 7 -5 Ab*d 745+var corpusFixture = map[string]string{46+ "A1": "10", "A2": "20", "A3": "30", "A4": "apple", "A7": "-5",47+ "B1": "north", "B2": "south", "B3": "north", "B4": "east", "B5": "north", "B6": `=""`, "B7": "Ab*d",48+ "C1": "1", "C2": "2", "C3": "3", "C4": "4", "C5": "5", "C6": "6", "C7": "7",49+ "D1": "Apple", "D2": "Banana", "D3": "Cherry", "D4": "date", "D5": "Apple",50+ "E1": "1.5", "E2": "2.5", "E3": "3.5", "E4": "x", "E5": "9", "E6": "TRUE",51+ "F1": "9/26/2026", "F2": "2026-01-31", "F3": "14:30", "F4": "2/29/2024", "F5": "12/31/2025",52+ "G1": "1", "G2": "2", "G3": "2", "G4": "3",53+ "H1": "-1000", "H2": "300", "H3": "400", "H4": "500",54+ "I1": "30", "I2": "20", "I3": "10",55+ "J1": "'3", "J2": "=1/0", "J3": "FALSE", "J4": "=NA()",56+}57+58+// The corpus sheet's columns: each formula is in C, and D and E read its59+// result as text and its kind with formulas both 012 and Sheets compute,60+// so both answer in the same terms.61+const (62+ colSection = iota63+ colFormula64+ colResult65+ colText66+ colKind67+ colOurs68+ colCheck69+ colNote70+)71+72+var corpusHeaders = []string{"section", "formula", "result", "as text", "kind", "012 result", "check", "note"}73+74+// corpusLine is a line of the corpus file: a formula row, or a comment,75+// section heading or blank line kept as it is.76+type corpusLine struct {77+ text string78+ section string79+ formula string80+ ours string // 012's recorded result81+ sheets string // Sheets' result, or unconfirmed82+ note string83+}84+85+func readCorpus(t *testing.T) []*corpusLine {86+ t.Helper()87+ data, err := os.ReadFile(corpusPath)88+ if err != nil {89+ t.Fatal(err)90+ }91+ var out []*corpusLine92+ section := ""93+ for _, text := range strings.Split(strings.TrimSuffix(string(data), "\n"), "\n") {94+ if !strings.HasPrefix(text, "=") {95+ if h, ok := strings.CutPrefix(text, "## "); ok {96+ section = h97+ }98+ out = append(out, &corpusLine{text: text})99+ continue100+ }101+ f := append(strings.Split(text, "\t"), "", "", "")102+ out = append(out, &corpusLine{section: section, formula: f[0], ours: f[1], sheets: f[2], note: f[3]})103+ }104+ return out105+}106+107+func writeCorpus(t *testing.T, lines []*corpusLine) {108+ t.Helper()109+ var b strings.Builder110+ for _, l := range lines {111+ if l.formula == "" {112+ b.WriteString(l.text + "\n")113+ continue114+ }115+ if l.sheets == "" {116+ l.sheets = unconfirmed117+ }118+ b.WriteString(strings.TrimRight(strings.Join([]string{l.formula, l.ours, l.sheets, l.note}, "\t"), "\t") + "\n")119+ }120+ if err := os.WriteFile(corpusPath, []byte(b.String()), 0o644); err != nil {121+ t.Fatal(err)122+ }123+}124+125+// corpusRows are the formula rows of lines, in order.126+func corpusRows(lines []*corpusLine) []*corpusLine {127+ var rows []*corpusLine128+ for _, l := range lines {129+ if l.formula != "" {130+ rows = append(rows, l)131+ }132+ }133+ return rows134+}135+136+// corpusBook builds the workbook the corpus is computed in: the Corpus137+// sheet, a row per formula under a header row, and the Data sheet.138+func corpusBook(t *testing.T, rows []*corpusLine) *sheet.Sheet {139+ t.Helper()140+ s := sheet.New()141+ book := s.Book()142+ if err := book.RenameSheet(s, "Corpus"); err != nil {143+ t.Fatal(err)144+ }145+ data, err := book.AddSheet("Data", 1)146+ if err != nil {147+ t.Fatal(err)148+ }149+ for a, in := range corpusFixture {150+ at := addr(t, a)151+ setAt(t, data, at.Col, at.Row, in)152+ }153+ for c, h := range corpusHeaders {154+ setAt(t, s, c, 0, "'"+h)155+ }156+ for i, r := range rows {157+ n := strconv.Itoa(i + 2)158+ setAt(t, s, colSection, i+1, r.section)159+ setAt(t, s, colFormula, i+1, "'"+r.formula)160+ setAt(t, s, colResult, i+1, r.formula)161+ setAt(t, s, colText, i+1, `=C`+n+`&""`)162+ setAt(t, s, colKind, i+1, kindFormula("C"+n))163+ }164+ for c, w := range []int{22, 48, 14, 24, 8, 24, 8, 30} {165+ s.SetColWidth(c, w)166+ }167+ return s168+}169+170+// kindFormula names the kind of the value in ref, in words both 012 and171+// Sheets compute alike.172+func kindFormula(ref string) string {173+ return fmt.Sprintf(`=IF(ISERROR(%[1]s), "error", IF(ISNUMBER(%[1]s), "number", IF(ISTEXT(%[1]s), "text", `+174+ `IF(ISLOGICAL(%[1]s), "logical", IF(ISBLANK(%[1]s), "blank", "other")))))`, ref)175+}176+177+// setAt types in into the cell at col and row of s.178+func setAt(t *testing.T, s *sheet.Sheet, col, row int, in string) {179+ t.Helper()180+ a := sheet.Addr{Col: col, Row: row}181+ if err := s.Set(a, in); err != nil {182+ t.Errorf("%s %q: %v", a, in, err)183+ }184+}185+186+// corpusResult reads what 012 computed for the formula on row i of the187+// corpus sheet, in the corpus file's terms.188+func corpusResult(s *sheet.Sheet, i int) string {189+ text := s.Value(sheet.Addr{Col: colText, Row: i + 1})190+ kind := s.Value(sheet.Addr{Col: colKind, Row: i + 1})191+ return encodeResult(text.String(), kind.String())192+}193+194+// encodeResult writes a result, given as text and its kind, as the195+// corpus file does: text quoted, (blank) for an empty cell, and196+// numbers, booleans and errors as they read.197+func encodeResult(text, kind string) string {198+ switch kind {199+ case "number", "logical", "error":200+ return text201+ case "text":202+ return strconv.Quote(text)203+ case "blank":204+ return "(blank)"205+ }206+ return "(" + kind + ") " + text207+}208+209+func TestSheetsCorpus(t *testing.T) {210+ lines := readCorpus(t)211+ rows := corpusRows(lines)212+ s := corpusBook(t, rows)213+ if *corpusFrom != "" {214+ foldSheets(t, rows, *corpusFrom)215+ }216+ for i, r := range rows {217+ got := corpusResult(s, i)218+ if *updateCorpus {219+ r.ours = got220+ }221+ checkCorpusRow(t, r, got)222+ }223+ if *updateCorpus || *corpusFrom != "" {224+ writeCorpus(t, lines)225+ }226+ if *corpusXLSX != "" {227+ exportCorpus(t, s, rows, *corpusXLSX)228+ }229+}230+231+// checkCorpusRow holds 012 to its recorded result, and a difference232+// from Sheets to a note saying why.233+func checkCorpusRow(t *testing.T, r *corpusLine, got string) {234+ t.Helper()235+ if got != r.ours {236+ t.Errorf("%s = %s, recorded %s (go test ./internal/fileio -run SheetsCorpus -update-corpus records it)", r.formula, got, r.ours)237+ }238+ if r.sheets != "" && r.sheets != unconfirmed && r.sheets != r.ours && !strings.HasPrefix(r.note, onPurpose) {239+ t.Errorf("%s = %s in 012 and %s in Sheets: make 012 agree, or say why in a note starting %q", r.formula, r.ours, r.sheets, onPurpose)240+ }241+}
A internal/fileio/sheetscorpus_xlsx_test.go +96 -0
@@ -0,0 +1,96 @@
1+package fileio2+3+import (4+ "context"5+ "encoding/csv"6+ "os"7+ "slices"8+ "strconv"9+ "testing"10+11+ "github.com/FelineStateMachine/012/internal/sheet"12+)13+14+// sheetsOnlyNote marks a corpus row whose formula 012's exporter saves as15+// its value, because Excel has nothing the same (formula.go): in Sheets16+// the formula has to be typed into C.17+const sheetsOnlyNote = "saved as a value: type the formula in B into C"18+19+// exportCorpus writes the corpus workbook s as an XLSX file at name with20+// 012's exporter, for importing into Sheets. Sheets recalculates C, D21+// and E on import; F keeps 012's results, and G compares the two.22+func exportCorpus(t *testing.T, s *sheet.Sheet, rows []*corpusLine, name string) {23+ t.Helper()24+ for i, r := range rows {25+ n := strconv.Itoa(i + 2)26+ setAt(t, s, colOurs, i+1, "'"+corpusResult(s, i))27+ setAt(t, s, colCheck, i+1, checkFormula(n))28+ note := r.note29+ if !excelKeeps(r.formula) {30+ setAt(t, s, colResult, i+1, "")31+ note = sheetsOnlyNote32+ }33+ setAt(t, s, colNote, i+1, note)34+ }35+ res, err := Export(context.Background(), name, XLSX, SnapBook(s), ExportOptions{})36+ if err != nil {37+ t.Fatal(err)38+ }39+ for _, n := range res.Notes {40+ t.Log(n)41+ }42+ t.Logf("wrote %d formulas to %s", len(rows), name)43+}44+45+// checkFormula compares, on row n, the result in D and E with 012's in46+// F, writing it as encodeResult does. Errors only say both are errors;47+// the error in C and the one in F are compared by eye.48+func checkFormula(n string) string {49+ d, e, f := "D"+n, "E"+n, "F"+n50+ enc := `IF(` + e + `="text", """" & ` + d + ` & """", IF(` + e + `="blank", "(blank)", ` + d + ` & ""))`51+ return `=IFERROR(IF(EXACT(` + enc + `, ` + f + `), "same", "DIFFERS"), IF(LEFT(` + f + `, 1)="#", "both errors", "DIFFERS"))`52+}53+54+// excelKeeps reports whether 012's exporter keeps formula as a formula.55+func excelKeeps(formula string) bool {56+ _, ok := toExcelFormula(formula, nil)57+ return ok58+}59+60+// foldSheets records Sheets' results from name, a CSV download of the61+// corpus sheet after importing the XLSX workbook into Sheets. Rows left62+// out of the download, and formulas saved as values that weren't typed63+// in, stay as they are.64+func foldSheets(t *testing.T, rows []*corpusLine, name string) {65+ t.Helper()66+ f, err := os.Open(name)67+ if err != nil {68+ t.Fatal(err)69+ }70+ defer f.Close()71+ recs, err := csv.NewReader(f).ReadAll()72+ if err != nil {73+ t.Fatal(err)74+ }75+ if len(recs) == 0 || !slices.Equal(recs[0][:colOurs], corpusHeaders[:colOurs]) {76+ t.Fatalf("%s: expected a CSV download of the corpus sheet, with headers %q", name, corpusHeaders[:colOurs])77+ }78+ sheets := map[string]string{}79+ for _, rec := range recs[1:] {80+ if len(rec) > colKind {81+ sheets[rec[colFormula]] = encodeResult(rec[colText], rec[colKind])82+ }83+ }84+ skipped := 085+ for _, r := range rows {86+ got, ok := sheets[r.formula]87+ if !ok || (got == "(blank)" && !excelKeeps(r.formula)) {88+ skipped++89+ continue90+ }91+ r.sheets = got92+ }93+ if skipped > 0 {94+ t.Logf("%d formulas have no result from Sheets in %s", skipped, name)95+ }96+}
A internal/fileio/testdata/sheets_corpus.tsv +320 -0
@@ -0,0 +1,320 @@
1+# Formulas where 012 matches Google Sheets on purpose (docs/contributing/testing.md).2+# Tab-separated: formula, 012's result, Sheets' result, note.3+# Results read "text" (quoted), 12.5, TRUE, #N/A, or (blank) for an empty cell.4+# Sheets' result is "unconfirmed" until checked in Sheets. A row where it5+# differs from 012's needs a note starting "on purpose:" that says why.6+# Every formula computes one value; array results are measured with ROWS,7+# COLUMNS, INDEX or TEXTJOIN. Data! is the fixture sheet (sheetscorpus_test.go).8+## Dates and times9+=DATE(2026, 9, 26) 46291 unconfirmed10+=DATE(2026, 14, 1) 46419 unconfirmed11+=DATE(2026, 1, 0) 46022 unconfirmed12+=DATE(26, 1, 1) 9498 unconfirmed13+=DATE(1899, 12, 30) 693961 unconfirmed14+=DATE(1900, 1, 1) 2 unconfirmed15+=DATE(1900, 2, 29) 61 unconfirmed16+=DATE(-1, 1, 1) #NUM! unconfirmed17+=DATE("2026", "9", "26") 46291 unconfirmed18+=TIME(14, 30, 0) 0.604166666666667 unconfirmed19+=TIME(25, 0, 0) 0.0416666666666667 unconfirmed20+=TIME(0, -1, 0) #NUM! unconfirmed21+=YEAR(0) 1899 unconfirmed22+=DAY(Data!A5) 30 unconfirmed23+=YEAR(Data!A4) #VALUE! unconfirmed24+=WEEKDAY(Data!F1) 7 unconfirmed25+=WEEKDAY(Data!F1, 2) 6 unconfirmed26+=WEEKDAY(Data!F1, 4) #NUM! unconfirmed27+=HOUR(0.99999999) 0 unconfirmed28+=HOUR(1.75) 18 unconfirmed29+=MINUTE("14:30:59") 30 unconfirmed30+=SECOND(59.6/86400) 0 unconfirmed31+=EDATE(DATE(2024, 2, 29), 12) 45716 unconfirmed32+=EDATE(Data!F1, 1.9) 46321 unconfirmed33+=EOMONTH(DATE(2026, 1, 31), 1) 46081 unconfirmed34+=DATEDIF(Data!F4, Data!F1, "MD") 28 unconfirmed35+=DATEDIF(Data!F4, Data!F1, "YD") 210 unconfirmed36+=DATEDIF(DATE(2024, 1, 31), DATE(2024, 3, 1), "MD") -1 unconfirmed37+=DATEDIF(Data!F2, Data!F5, "D") #NUM! unconfirmed38+=DAYS(Data!F1, Data!F2) 238 unconfirmed39+=NETWORKDAYS(Data!F5, Data!F2, Data!F5) 22 unconfirmed40+=NETWORKDAYS(Data!F1, Data!F1) 0 unconfirmed41+=DATEVALUE("9/26/2026") 46291 unconfirmed42+=DATEVALUE("Sep 26, 2026") 46291 unconfirmed43+=DATEVALUE("9/26/2026 18:00") 46291 unconfirmed44+=DATEVALUE("2/30/2026") #VALUE! unconfirmed45+=DATEVALUE(46291) #VALUE! unconfirmed46+=TIMEVALUE("2:30 PM") 0.604166666666667 unconfirmed47+=TIMEVALUE("9/26/2026 6:00") 0.25 unconfirmed48+=TIMEVALUE("noon") #VALUE! unconfirmed49+="2026-09-27"-Data!F1 1 unconfirmed50+=Data!F1+Data!F3 46291.6041666667 unconfirmed51+=Data!F1="2026-09-26" FALSE unconfirmed52+## Text53+=CONCATENATE("a", 1, TRUE) "a1TRUE" unconfirmed54+=CONCATENATE(Data!A1:A3) "102030" unconfirmed55+=TEXTJOIN(", ", TRUE, Data!A3:A5) "30, apple" unconfirmed56+=TEXTJOIN(",", FALSE, Data!B5:B7) "north,,Ab*d" unconfirmed57+=LEFT("hi", -1) #VALUE! unconfirmed58+=LEFT(12345, 2) "12" unconfirmed59+=LEFT(TRUE, 1) "T" unconfirmed60+=MID("abc", 5, 2) "" unconfirmed61+=MID("abc", 0, 2) #VALUE! unconfirmed62+=LEN(Data!F1) 5 unconfirmed63+=LEN(1/4) 4 unconfirmed64+=LEN("héllo") 5 unconfirmed65+=LEN("😀") 1 unconfirmed66+=LEFT("日本語", 2) "日本" unconfirmed67+=UPPER("straße") "STRAßE" unconfirmed68+=UPPER(1.5) "1.5" unconfirmed69+=PROPER("hello wORLD it's") "Hello World It'S" unconfirmed70+=PROPER("o'neil mcdonald 3rd") "O'Neil Mcdonald 3Rd" unconfirmed71+=TRIM(" a b ") "a b" unconfirmed72+=SUBSTITUTE("aaa", "aa", "b") "ba" unconfirmed73+=SUBSTITUTE("abc", "", "x") "abc" unconfirmed74+=REPLACE("abc", 10, 1, "X") "abcX" unconfirmed75+=FIND("B", "abc") #VALUE! unconfirmed76+=FIND("", "abc") 1 unconfirmed77+=SEARCH("b?d", "abcbxd") 4 unconfirmed78+=SEARCH("~*", "a*b") 2 unconfirmed79+=REPT("ab", 2.9) "abab" unconfirmed80+=REPT("ab", -1) #VALUE! unconfirmed81+=EXACT(1, "1") TRUE unconfirmed82+=EXACT(TRUE, "TRUE") TRUE unconfirmed83+="a"&Data!A5 "a" unconfirmed84+=1&2 "12" unconfirmed85+=VALUE("1,234.5") 1234.5 unconfirmed86+=VALUE(" 42 ") 42 unconfirmed87+=VALUE("") 0 unconfirmed88+=VALUE("abc") #VALUE! unconfirmed89+=VALUE("9/26/2026") 46291 unconfirmed90+=VALUE(TRUE) #VALUE! unconfirmed91+## Regular expressions and SPLIT92+=REGEXMATCH("ABC", "(?i)abc") TRUE unconfirmed93+=REGEXMATCH(123, "1") #VALUE! unconfirmed94+=REGEXEXTRACT("abc", "\d+") #N/A unconfirmed95+=TEXTJOIN("|", TRUE, REGEXEXTRACT("a1b2", "(\d)b(\d)")) "1" unconfirmed96+=REGEXREPLACE("john smith", "(\w+) (\w+)", "$2, $1") "smith, john" unconfirmed97+=COLUMNS(SPLIT("a,b,,c", ",", TRUE, FALSE)) 4 unconfirmed98+=SUM(SPLIT("1,2,3", ",")) 6 unconfirmed99+=COLUMNS(SPLIT("a-b_c", "-_")) 3 unconfirmed100+## TEXT and number formats101+=TEXT(1234.567, "$#,##0.00") "$1,234.57" unconfirmed102+=TEXT(1234567.891, "#,##0,") "1,235" unconfirmed103+=TEXT(1234.5, "0.00E+00") "1.23E+03" unconfirmed104+=TEXT(0.0000000001, "0.00E+00") "1.00E-10" unconfirmed105+=TEXT(-1234.5, "#,##0.00;(#,##0.00)") "(1,234.50)" unconfirmed106+=TEXT(0, "0;-0;\z\e\r\o") "zero" unconfirmed107+=TEXT(3, "0.###") "3." unconfirmed108+=TEXT(2.5, "0") "3" unconfirmed109+=TEXT(-0.5, "0") "-1" unconfirmed110+=TEXT(1.005, "0.00") "1.01" unconfirmed111+=TEXT(0.75, "# ?/?") " /1" unconfirmed112+=TEXT("abc", "0") "abc" unconfirmed113+=TEXT("12", "0.00") "12.00" unconfirmed114+=TEXT(TRUE, "0") "1" unconfirmed115+=TEXT(Data!F1, "dddd") "Saturday" unconfirmed116+=TEXT(Data!F1, "mmm d, yyyy") "Sep 26, 2026" unconfirmed117+=TEXT(Data!F1, "dddd, mmmm d, yyyy") "Saturday, September 26, 2026" unconfirmed118+=TEXT(Data!F3, "h:mm AM/PM") "2:30 PM" unconfirmed119+=TEXT(0.75, "h AM/PM") "6 PM" unconfirmed120+=TEXT(Data!F1+Data!F3, "yyyy-mm-dd hh:mm") "2026-09-26 14:30" unconfirmed121+=TEXT(1.5, "[h]:mm") "36:00" unconfirmed122+=TEXT(90/1440, "[mm]:ss") "90:00" unconfirmed123+## Rounding and floating point124+=ROUND(1.005, 2) 1.01 unconfirmed125+=ROUND(2.675, 2) 2.68 unconfirmed126+=ROUND(0.285, 2) 0.29 unconfirmed127+=ROUND(-2.5) -3 unconfirmed128+=ROUND(1.5, 0.9) 2 unconfirmed129+=ROUND(1.23456789012345, 14) 1.23456789012345 unconfirmed130+=ROUNDUP(0.1+0.2, 1) 0.3 unconfirmed131+=ROUNDDOWN(1-0.9, 1) 0.1 unconfirmed132+=TRUNC(1234, -2) 1200 unconfirmed133+=INT(-1.5) -2 unconfirmed134+=INT(1-0.9+0.9) 1 unconfirmed135+=CEILING(0.3, 0.1) 0.3 unconfirmed136+=CEILING(-2.5, 1) -2 unconfirmed137+=CEILING(-2.5, -1) -3 unconfirmed138+=CEILING(2.5, -1) #NUM! unconfirmed139+=CEILING(2.5, 0) 0 unconfirmed140+=FLOOR(-2.5, 1) -3 unconfirmed141+=FLOOR(-2.5, -1) -2 unconfirmed142+=MOD(-7, 3) 2 unconfirmed143+=MOD(10, 0) #DIV/0! unconfirmed144+=MOD(0.3, 0.1) 0.1 unconfirmed145+=QUOTIENT(-7, 2) -3 unconfirmed146+=0.1+0.2 0.3 unconfirmed147+=0.1+0.2=0.3 FALSE unconfirmed148+=0.1+0.2-0.3 5.55111512312578E-17 unconfirmed149+=1-0.9 0.1 unconfirmed150+=(1-0.9)=0.1 FALSE unconfirmed151+=SUM(0.1, 0.2, 0.3)=0.6 FALSE unconfirmed152+=1/3 0.333333333333333 unconfirmed153+=100*1.1 110 unconfirmed154+=1E+21 1E+21 unconfirmed155+=123456789012345678 123456789012346000 unconfirmed156+=9007199254740993 9007199254740990 unconfirmed157+=0.000001 0.000001 unconfirmed158+=-0 0 unconfirmed159+=-2^2 4 unconfirmed160+=0^0 1 unconfirmed161+=POWER(0, 0) 1 unconfirmed162+=POWER(-8, 1/3) #NUM! unconfirmed163+=(-8)^(1/3) #NUM! unconfirmed164+=1E+308*10 #NUM! unconfirmed165+=LOG(10, 1) #DIV/0! unconfirmed166+## Errors and their propagation167+=0/0 #DIV/0! unconfirmed168+=Data!J2+1 #DIV/0! unconfirmed169+=SUM(Data!J2, 1) #DIV/0! unconfirmed170+=SUM(1, NA()) #N/A unconfirmed171+=NA()+1/0 #N/A unconfirmed172+=1/0+NA() #DIV/0! unconfirmed173+=IFERROR(1/0, "x") "x" unconfirmed174+=IFERROR(NA()) "" unconfirmed175+=IFERROR(Data!J2, Data!J4) #N/A unconfirmed176+=IFNA(1/0, 0) #DIV/0! unconfirmed177+=ISERR(NA()) FALSE unconfirmed178+=IF(1/0, 1, 2) #DIV/0! unconfirmed179+=IF("abc", 1, 2) #VALUE! unconfirmed180+=AND(TRUE, 1/0) #DIV/0! unconfirmed181+=OR(TRUE, NA()) #N/A unconfirmed182+=Data!A1+nosuchname #NAME? unconfirmed183+=CHOOSE(0, "a") #VALUE! unconfirmed184+=INDEX(Data!A1:A3, 5) #REF! unconfirmed185+=AVERAGEIF(Data!A1:A7, ">100") #DIV/0! unconfirmed186+=SWITCH(Data!B4, "north", 1) #N/A unconfirmed187+=PMT(0, 0, 1000) #NUM! unconfirmed188+=COUNT(Data!J1:J4) #DIV/0! unconfirmed189+=COUNTA(Data!J1:J4) #DIV/0! unconfirmed190+## Coercion191+=" 3 "*1 3 unconfirmed192+="1,000"*1 1000 unconfirmed193+="$5"*2 10 unconfirmed194+="50%"*2 1 unconfirmed195+="9/26/2026"*1 46291 unconfirmed196+="TRUE"*1 #VALUE! unconfirmed197+=-TRUE -1 unconfirmed198+=Data!A5&"x" "x" unconfirmed199+=Data!A5="" TRUE unconfirmed200+=Data!B6=0 FALSE unconfirmed201+=ISBLANK(Data!B6) FALSE unconfirmed202+=Data!J1+1 4 unconfirmed203+=SUM(Data!J1) 0 unconfirmed204+=SUM("3", 1) 4 unconfirmed205+=SUM(Data!E6, 1) 1 unconfirmed206+=AVERAGE(Data!E6, 3) 3 unconfirmed207+=COUNT("1", 2) 2 unconfirmed208+=COUNT(TRUE) 1 unconfirmed209+=COUNTBLANK(Data!B1:B7) 1 unconfirmed210+="10"<9 FALSE unconfirmed211+=1<"a" TRUE unconfirmed212+=TRUE>1 TRUE unconfirmed213+=TRUE="TRUE" FALSE unconfirmed214+=Data!A5<"a" TRUE unconfirmed215+=""=0 FALSE unconfirmed216+=IF("TRUE", 1, 2) #VALUE! unconfirmed217+=IF(Data!A5, "y", "n") "n" unconfirmed218+=AND("abc") #VALUE! unconfirmed219+=AND(Data!A4) #VALUE! unconfirmed220+=NOT("") #VALUE! unconfirmed221+=ROUND("2.5") 3 unconfirmed222+=LEFT(Data!F1, 3) "462" unconfirmed223+## Lookups and criteria224+=VLOOKUP("B*", Data!D1:E3, 2, FALSE) 2.5 unconfirmed225+=VLOOKUP("Blueberry", Data!D1:E3, 2) 2.5 unconfirmed226+=VLOOKUP("Aardvark", Data!D1:E3, 2, TRUE) #N/A unconfirmed227+=VLOOKUP(2.5, Data!C1:D5, 2) "Banana" unconfirmed228+=VLOOKUP("1", Data!C1:D5, 2, FALSE) #N/A unconfirmed229+=VLOOKUP("Apple", Data!D1:E3, 3, FALSE) #REF! unconfirmed230+=VLOOKUP("Apple", Data!D1:E3, 0, FALSE) #VALUE! unconfirmed231+=HLOOKUP("Apple", Data!D1:E2, 2, FALSE) "Banana" unconfirmed232+=MATCH("cherry", Data!D1:D3, 0) 3 unconfirmed233+=MATCH("B*", Data!D1:D3, 0) 2 unconfirmed234+=MATCH(3.2, Data!C1:C7) 3 unconfirmed235+=MATCH(0, Data!C1:C7) #N/A unconfirmed236+=MATCH(35, Data!I1:I3, -1) #N/A unconfirmed237+=MATCH(3, {1, 3, 5}, 0) 2 unconfirmed238+=INDEX(Data!C1:E1, 2) "Apple" unconfirmed239+=INDEX(Data!D1:E3, 4, 1) #REF! unconfirmed240+=ROWS(INDEX(Data!A1:C3, 0, 1)) 1 unconfirmed241+=SUM(INDEX(Data!C1:E3, 0, 1)) 1 unconfirmed242+=XLOOKUP("kiwi", Data!D1:D3, Data!E1:E3) #N/A unconfirmed243+=XLOOKUP("Apple", Data!D1:D5, Data!E1:E5, , 0, -1) 9 unconfirmed244+=XLOOKUP(3.2, Data!C1:C7, Data!D1:D7, , -1) "Cherry" unconfirmed245+=XLOOKUP(3.2, Data!C1:C7, Data!D1:D7, , 1) "date" unconfirmed246+=XLOOKUP("Ch*", Data!D1:D3, Data!E1:E3, , 2) 3.5 unconfirmed247+=XLOOKUP(2, Data!G1:G4, Data!H1:H4, , 0, -1) 400 unconfirmed248+=CHOOSE(2.9, "a", "b", "c") "b" unconfirmed249+=SUMIF(Data!A1:A7, "<>10") 45 unconfirmed250+=SUMIF(Data!C1:C7, ">=" & Data!C5) 18 unconfirmed251+=SUMIFS(Data!C1:C5, Data!B1:B5, "north", Data!A1:A5, ">10") 3 unconfirmed252+=COUNTIF(Data!B1:B7, "?o*") 4 unconfirmed253+=COUNTIF(Data!B1:B7, "Ab~*d") 1 unconfirmed254+=COUNTIF(Data!B1:B7, "<>north") 4 unconfirmed255+=COUNTIF(Data!A1:A7, "") 2 unconfirmed256+=COUNTIF(Data!A1:A7, "10") 1 unconfirmed257+=COUNTIF(Data!J1:J4, 3) 0 unconfirmed258+=COUNTIF(Data!E1:E7, TRUE) 1 unconfirmed259+=COUNTIF(Data!F1:F5, ">" & DATE(2026, 1, 1)) 2 unconfirmed260+## Arrays and spills261+=SUM(SEQUENCE(3, 3, 0, 2)) 72 unconfirmed262+=INDEX(SEQUENCE(3, 2, 10, 5), 3, 2) 35 unconfirmed263+=ROWS(SEQUENCE(0)) 1 unconfirmed264+=ROWS(FILTER(Data!A1:A7, Data!C1:C7>3)) 4 unconfirmed265+=TEXTJOIN(",", TRUE, FILTER(Data!D1:D5, Data!B1:B5="north")) "Apple,Cherry,Apple" unconfirmed266+=FILTER(Data!A1:A3, Data!A1:A3>100) #N/A unconfirmed267+=TEXTJOIN(",", TRUE, SORT(Data!C1:C5, 1, FALSE)) "5,4,3,2,1" unconfirmed268+=INDEX(SORT(Data!D1:E5, 2, FALSE), 1, 1) "date" unconfirmed269+=TEXTJOIN(",", TRUE, SORT(Data!D1:D5)) "Apple,Apple,Banana,Cherry,date" unconfirmed270+=TEXTJOIN(",", TRUE, UNIQUE(Data!B1:B5)) "north,south,east" unconfirmed271+=TEXTJOIN(",", TRUE, UNIQUE(Data!G1:G4, FALSE, TRUE)) "1,3" unconfirmed272+=TEXTJOIN(",", TRUE, FLATTEN(Data!C1:D2)) "1,Apple,2,Banana" unconfirmed273+=ROWS(FLATTEN(Data!C1:D2, Data!G1:G2)) 6 unconfirmed274+=TEXTJOIN(",", TRUE, SORTN(Data!C1:C7, 3, 0, 1, FALSE)) "7,6,5" unconfirmed275+=ROWS(SORTN(Data!G1:G4, 2, 1, 1, TRUE)) 3 unconfirmed276+=TEXTJOIN(",", TRUE, CHOOSECOLS(Data!C1:E1, -1, 1)) "1.5,1" unconfirmed277+=ROWS(CHOOSEROWS(Data!A1:A7, 1, 3)) 2 unconfirmed278+=SUM(ARRAYFORMULA(Data!C1:C3*Data!G1:G3)) 11 unconfirmed279+=ARRAYFORMULA(SUM(Data!C1:C3^2)) 14 unconfirmed280+=SUM(Data!C1:C3*2) 12 unconfirmed281+=SUMPRODUCT(Data!C1:C3>1) 0 unconfirmed282+=SUMPRODUCT(Data!A1:A4, Data!C1:C4) 140 unconfirmed283+=SUM({1, 2; 3, 4}) 10 unconfirmed284+=INDEX({1, 2; 3, 4}, 2, 1) 3 unconfirmed285+## LET and LAMBDA286+=LET(x, 2, y, x+1, x*y) 6 unconfirmed287+=LET(x, SEQUENCE(3), SUM(x)) 6 unconfirmed288+=LET(x, 1/0, 5) #DIV/0! unconfirmed289+=LAMBDA(x, x+1)() #N/A unconfirmed290+=LET(f, LAMBDA(x, x+1), f(f(1))) 3 unconfirmed291+=SUM(MAP({1, 2, 3}, LAMBDA(x, x*10))) 60 unconfirmed292+=SUM(MAP(Data!A1:A3, Data!C1:C3, LAMBDA(a, b, a+b))) 66 unconfirmed293+=REDUCE(0, Data!C1:C5, LAMBDA(a, b, a+b)) 15 unconfirmed294+=TEXTJOIN(",", TRUE, SCAN(0, {1, 2, 3}, LAMBDA(a, b, a+b))) "1,3,6" unconfirmed295+=SUM(BYROW(Data!C1:E3, LAMBDA(r, COUNT(r)))) 6 unconfirmed296+=TEXTJOIN(",", TRUE, BYCOL(Data!C1:E3, LAMBDA(c, COUNTA(c)))) "3,3,3" unconfirmed297+=SUM(MAKEARRAY(3, 3, LAMBDA(r, c, r*c))) 36 unconfirmed298+## Logic299+=IFS(Data!A1>20, "big", Data!A1>5, "medium") "medium" unconfirmed300+=SWITCH(Data!B2, "north", 1, "south", 2) 2 unconfirmed301+=SWITCH(Data!B4, "north", 1, "south", 2, 0) 0 unconfirmed302+=XOR(Data!A1:A3) TRUE unconfirmed303+## Math, statistics and finance304+=PRODUCT(2, "3") 6 unconfirmed305+=MEDIAN(1, 2, 3, 4) 2.5 unconfirmed306+=MODE(Data!G1:G4) 2 unconfirmed307+=MODE(Data!C1:C5) #N/A unconfirmed308+=STDEV(Data!A1:A7) 14.9303940559741 unconfirmed309+=VAR(Data!A1:A7) 222.916666666667 unconfirmed310+=RANK(2, Data!G1:G4) 2 unconfirmed311+=RANK(99, Data!A1:A7) #N/A unconfirmed312+=ROUND(PMT(0.05/12, 360, 200000), 6) -1073.643246 unconfirmed313+=PMT(0, 10, 1000) -100 unconfirmed314+=ROUND(PV(0.05/12, 360, -1000), 6) 186281.617046 unconfirmed315+=ROUND(NPER(0.01, -1000, 10000), 9) 10.588644459 unconfirmed316+=NPER(0.01, 100, 10000) -69.6607168935748 unconfirmed317+=ROUND(RATE(48, -200, 8000), 9) 0.007701472 unconfirmed318+=ROUND(NPV(0.1, Data!H1:H4), 6) -19.124377 unconfirmed319+=ROUND(IRR(Data!H1:H4), 9) 0.088963395 unconfirmed320+=IRR(Data!C1:C5) #NUM! unconfirmed
A internal/notebook/graph.go +224 -0
@@ -0,0 +1,224 @@
1+package notebook2+3+import (4+ "fmt"5+ "slices"6+ "strings"7+)8+9+// The cells' dependency graph: a cell reading $sales depends on the cell10+// named sales. A name belongs to the first code cell giving it; a later11+// one giving the same name can't run until it's renamed. Cells run in12+// the order asked, each after the cells it reads among those running,13+// and a cell reading itself, directly or through others, doesn't run.14+15+// Names maps each name to the index of the first code cell giving it.16+func Names(cells []Cell) map[string]int {17+ out := map[string]int{}18+ for i, c := range cells {19+ if n := c.Name(); n != "" {20+ if _, dup := out[n]; !dup {21+ out[n] = i22+ }23+ }24+ }25+ return out26+}27+28+// Taken says why cell i can't use its name: an earlier cell gives it29+// (names ignore case there, as formulas' nu.name do), or "".30+func Taken(cells []Cell, i int) string {31+ name := cells[i].Name()32+ if name == "" {33+ return ""34+ }35+ for j, c := range cells[:i] {36+ if strings.EqualFold(c.Name(), name) {37+ return fmt.Sprintf("cell %d is already named %s: rename one", j+1, c.Name())38+ }39+ }40+ return ""41+}42+43+// Reads returns the indices of the cells cell i reads, in the order it44+// names them.45+func Reads(cells []Cell, names map[string]int, i int) []int {46+ var out []int47+ for _, n := range Refs(cells[i].Pipeline()) {48+ if j, ok := names[n]; ok && j != i && !slices.Contains(out, j) {49+ out = append(out, j)50+ }51+ }52+ return out53+}54+55+// Order returns the code cells of want, each after the cells it reads56+// among them, in want's order otherwise, and the cells left out for57+// reading themselves, directly or through others.58+func Order(cells []Cell, want []int) (order, cycle []int) {59+ names := Names(cells)60+ in := map[int]bool{}61+ for _, i := range want {62+ if cells[i].Kind == Code {63+ in[i] = true64+ }65+ }66+ done := map[int]bool{}67+ for len(order) < len(in) {68+ progress := false69+ for _, i := range want {70+ if !in[i] || done[i] {71+ continue72+ }73+ ready := !slices.ContainsFunc(Reads(cells, names, i), func(j int) bool { return in[j] && !done[j] })74+ if ready {75+ order, done[i], progress = append(order, i), true, true76+ }77+ }78+ if !progress {79+ break80+ }81+ }82+ for _, i := range want {83+ if in[i] && !done[i] && !slices.Contains(cycle, i) {84+ cycle = append(cycle, i)85+ }86+ }87+ return order, cycle88+}89+90+// Dependents returns the cells that read cell i, directly or through91+// others, in the notebook's order.92+func Dependents(cells []Cell, i int) []int {93+ names := Names(cells)94+ want := map[int]bool{i: true}95+ for changed := true; changed; {96+ changed = false97+ for j := range cells {98+ if !want[j] && cells[j].Kind == Code && slices.ContainsFunc(Reads(cells, names, j), func(k int) bool { return want[k] }) {99+ want[j], changed = true, true100+ }101+ }102+ }103+ var out []int104+ for j := range cells {105+ if want[j] && j != i {106+ out = append(out, j)107+ }108+ }109+ return out110+}111+112+// Inputs returns the cells cell i reads, directly or through others,113+// that have no output, in the notebook's order: what has to run before114+// it can.115+func Inputs(cells []Cell, i int, output func(id int) *Output) []int {116+ names := Names(cells)117+ need := map[int]bool{}118+ var visit func(j int)119+ visit = func(j int) {120+ for _, k := range Reads(cells, names, j) {121+ if need[k] || k == i {122+ continue123+ }124+ if o := output(cells[k].ID); o == nil || o.NUON == nil {125+ need[k] = true126+ visit(k)127+ }128+ }129+ }130+ visit(i)131+ var out []int132+ for j := range cells {133+ if need[j] {134+ out = append(out, j)135+ }136+ }137+ return out138+}139+140+// Stale returns the cells, by ID, whose output may not be what running141+// them now would give: their source changed since, or an output they142+// read changed or is stale itself.143+func Stale(cells []Cell, output func(id int) *Output) map[int]bool {144+ names := Names(cells)145+ all := make([]int, len(cells))146+ for i := range all {147+ all[i] = i148+ }149+ order, _ := Order(cells, all)150+ stale := map[int]bool{}151+ for _, i := range order {152+ c := cells[i]153+ o := output(c.ID)154+ if o == nil {155+ continue156+ }157+ s := o.Source != c.Source158+ for _, name := range Refs(c.Pipeline()) {159+ j, ok := names[name]160+ seq := 0161+ if ok {162+ if d := output(cells[j].ID); d != nil {163+ seq = d.Seq164+ }165+ s = s || stale[cells[j].ID]166+ }167+ if read, had := o.Reads[name]; had || ok {168+ s = s || read != seq169+ }170+ }171+ if s {172+ stale[c.ID] = true173+ }174+ }175+ return stale176+}177+178+// Settle makes outputs read from a file what their cells' runs would179+// have left: made from the source as it is, having read the outputs as180+// they are, so none is stale until something changes.181+func Settle(cells []Cell, outputs map[int]*Output) {182+ names := Names(cells)183+ for _, c := range cells {184+ o := outputs[c.ID]185+ if o == nil {186+ continue187+ }188+ o.Source, o.Reads = c.Source, map[string]int{}189+ for _, name := range Refs(c.Pipeline()) {190+ if j, ok := names[name]; ok {191+ if d := outputs[cells[j].ID]; d != nil {192+ o.Reads[name] = d.Seq193+ } else {194+ o.Reads[name] = 0195+ }196+ }197+ }198+ }199+}200+201+// Caps are how much of the outputs a file keeps, in bytes of NUON: at202+// most Cell of one cell's, Total of all of them.203+type Caps struct{ Cell, Total int }204+205+// DefaultCaps keep 1 MB of a cell's output and 8 MB of a notebook's.206+var DefaultCaps = Caps{Cell: 1 << 20, Total: 8 << 20}207+208+// Kept returns the cells, by ID, whose outputs a file keeps within caps:209+// in the notebook's order, each that fits in what's left.210+func Kept(cells []Cell, output func(id int) *Output, caps Caps) map[int]bool {211+ out := map[int]bool{}212+ left := caps.Total213+ for _, c := range cells {214+ o := output(c.ID)215+ if o == nil || o.NUON == nil {216+ continue217+ }218+ if n := len(o.NUON); n <= caps.Cell && n <= left {219+ out[c.ID] = true220+ left -= n221+ }222+ }223+ return out224+}
A internal/notebook/notebook.go +158 -0
@@ -0,0 +1,158 @@
1+// Package notebook is a notebook's document: its cells (code cells2+// holding a nushell pipeline, note cells holding Markdown), the names3+// cells give their outputs, what each cell reads, the order cells run4+// in, and what a cell's last run left (its output). It knows nothing of5+// sheets, terminals or nu itself: the engine (internal/sheet) keeps a6+// notebook tab's cells in its undo history and its file, and the UI runs7+// them and draws them.8+package notebook9+10+import (11+ "errors"12+ "fmt"13+ "slices"14+ "strings"15+ "time"16+)17+18+// Kind is what a cell holds.19+type Kind uint820+21+const (22+ // Code cells hold a nushell pipeline, several lines if need be.23+ Code Kind = iota24+ // Note cells hold Markdown.25+ Note26+)27+28+// String is how the file names the kind.29+func (k Kind) String() string {30+ if k == Note {31+ return "note"32+ }33+ return "code"34+}35+36+// ParseKind reads a kind as the file names it.37+func ParseKind(s string) (Kind, bool) {38+ switch s {39+ case "code", "":40+ return Code, true41+ case "note":42+ return Note, true43+ }44+ return Code, false45+}46+47+// Cell is one cell of a notebook. Cells are values: a change makes a new48+// one, so the undo history can keep the list it replaced.49+type Cell struct {50+ // ID identifies the cell for as long as the workbook is open: its51+ // output is kept by it. It isn't saved.52+ ID int53+ Kind Kind54+ Source string55+}56+57+// Name is the name the cell gives its output, from a source of the form58+// `name = pipeline`, or "".59+func (c Cell) Name() string {60+ if c.Kind != Code {61+ return ""62+ }63+ name, _ := SplitName(c.Source)64+ return name65+}66+67+// Pipeline is what runs: the source without its name.68+func (c Cell) Pipeline() string {69+ _, p := SplitName(c.Source)70+ return p71+}72+73+// SplitName splits `name = pipeline` into the name and the pipeline; a74+// source that doesn't start so has no name. `==` isn't a name's `=`.75+func SplitName(src string) (name, pipeline string) {76+ head, rest, ok := strings.Cut(src, "=")77+ head = strings.TrimSpace(head)78+ if !ok || strings.HasPrefix(rest, "=") || ValidName(head) != nil {79+ return "", src80+ }81+ return head, strings.TrimLeft(rest, " \t")82+}83+84+// WithName is src given the name name, replacing any name it has; ""85+// takes its name away.86+func WithName(src, name string) string {87+ _, p := SplitName(src)88+ if name == "" {89+ return p90+ }91+ return name + " = " + p92+}93+94+// Reserved are nushell's own variables and the notebook's: $selection95+// is the selection on a sheet, $sheet.A1:C9 a range of one.96+var Reserved = []string{"in", "env", "nu", "it", "selection", "sheet"}97+98+// ValidName checks that name can name a cell's output: a nushell99+// variable's name of letters, digits and _, not starting with a digit,100+// not one of Reserved, and not starting with __, which the notebook's101+// own variables do.102+func ValidName(name string) error {103+ switch {104+ case name == "":105+ return errors.New("Enter a name")106+ case len(name) > 64:107+ return errors.New("A name can be at most 64 characters")108+ case isDigit(name[0]):109+ return errors.New("A name can't start with a digit")110+ case strings.HasPrefix(name, "__"):111+ return errors.New("Names starting with __ are the notebook's own")112+ case slices.Contains(Reserved, strings.ToLower(name)):113+ return fmt.Errorf("$%s is nushell's own", name)114+ }115+ for i := range len(name) {116+ if c := name[i]; !isLetter(c) && !isDigit(c) && c != '_' {117+ return errors.New("A name can only have letters, digits and _")118+ }119+ }120+ return nil121+}122+123+func isLetter(c byte) bool { return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' }124+func isDigit(c byte) bool { return c >= '0' && c <= '9' }125+func isWord(c byte) bool { return isLetter(c) || isDigit(c) || c == '_' }126+127+// Output is what a code cell's last run left: what it printed, as NUON,128+// or why it failed. Outputs are values too, replaced whole.129+type Output struct {130+ // NUON is what the pipeline printed, as NUON; nil when it failed or131+ // its output wasn't saved.132+ NUON []byte133+ // Err is nu's message when the run failed, and Detail the rest of134+ // what it said (its help line).135+ Err, Detail string136+ // Note says what was left out.137+ Note string138+ // Count is the run's number in this session, [3]; 0 for an output139+ // read from the file.140+ Count int141+ // Seq identifies the output among the workbook's, for telling142+ // whether what a cell read has changed since.143+ Seq int144+ // Took is how long the run took.145+ Took time.Duration146+ // Source is the cell's source when it ran.147+ Source string148+ // Reads are the Seqs of the outputs the run read, by name.149+ Reads map[string]int150+ // Selection is the range the run read as $selection, "Sheet1!A1:C9".151+ Selection string152+ // Unsaved is set on an output the file didn't keep, being larger153+ // than the caps allowed: there's nothing to show until it runs.154+ Unsaved bool155+}156+157+// Failed reports whether the run failed.158+func (o *Output) Failed() bool { return o != nil && o.Err != "" }
A internal/notebook/notebook_test.go +127 -0
@@ -0,0 +1,127 @@
1+package notebook2+3+import (4+ "slices"5+ "testing"6+)7+8+func TestSplitName(t *testing.T) {9+ for _, tc := range []struct{ src, name, pipe string }{10+ {"sales = open sales.csv", "sales", "open sales.csv"},11+ {"big=\n$sales | first", "big", "\n$sales | first"},12+ {"ls | where name == x", "", "ls | where name == x"},13+ {"a == b", "", "a == b"},14+ {"in = ls", "", "in = ls"},15+ {"9x = ls", "", "9x = ls"},16+ {"let x = 1", "", "let x = 1"},17+ {"__s = 1", "", "__s = 1"},18+ } {19+ name, p := SplitName(tc.src)20+ if name != tc.name || p != tc.pipe {21+ t.Errorf("SplitName(%q) = %q, %q", tc.src, name, p)22+ }23+ }24+ if got := WithName("ls", "files"); got != "files = ls" {25+ t.Errorf("WithName %q", got)26+ }27+ if got := WithName("files = ls", ""); got != "ls" {28+ t.Errorf("WithName without %q", got)29+ }30+ if (Cell{Kind: Note, Source: "x = y"}).Name() != "" {31+ t.Error("a note cell has a name")32+ }33+}34+35+func TestRefs(t *testing.T) {36+ got := Refs("$r1 | where x == $in.a and $nu.home | $r_2.x | $selection | $sheet.A1")37+ if !slices.Equal(got, []string{"r1", "r_2"}) {38+ t.Errorf("refs %v", got)39+ }40+ if !ReadsSelection("$selection | math sum") || ReadsSelection("$selections") {41+ t.Error("ReadsSelection")42+ }43+}44+45+func TestBind(t *testing.T) {46+ got, refs := Bind("$sheet.A1:C9 | append $sheet.'Q1 data'!B2:B5 | append $sheet.A1:C9.name | $x$sheet.A1 | $sheet.")47+ want := "$__sheet1 | append $__sheet2 | append $__sheet1.name | $x$sheet.A1 | $sheet."48+ if got != want {49+ t.Errorf("Bind = %q", got)50+ }51+ if len(refs) != 2 || refs[0].Ref != "A1:C9" || refs[1].Ref != "'Q1 data'!B2:B5" || refs[1].Var != "__sheet2" {52+ t.Errorf("refs %+v", refs)53+ }54+}55+56+func cells(srcs ...string) []Cell {57+ out := make([]Cell, len(srcs))58+ for i, s := range srcs {59+ out[i] = Cell{ID: i + 1, Source: s}60+ }61+ return out62+}63+64+func TestOrder(t *testing.T) {65+ cs := cells("big = $files | first", "files = ls", "# note", "$big | length", "a = $b", "b = $a")66+ cs[2].Kind = Note67+ order, cycle := Order(cs, []int{0, 1, 2, 3, 4, 5})68+ if !slices.Equal(order, []int{1, 0, 3}) || !slices.Equal(cycle, []int{4, 5}) {69+ t.Errorf("order %v cycle %v", order, cycle)70+ }71+ if got := Dependents(cs, 1); !slices.Equal(got, []int{0, 3}) {72+ t.Errorf("dependents %v", got)73+ }74+ outs := map[int]*Output{2: {NUON: []byte("x")}}75+ if got := Inputs(cs, 3, func(id int) *Output { return outs[id] }); !slices.Equal(got, []int{0}) {76+ t.Errorf("inputs %v", got)77+ }78+ dup := cells("x = ls", "X = ps")79+ if Taken(dup, 1) == "" || Taken(dup, 0) != "" {80+ t.Error("Taken")81+ }82+}83+84+func TestStale(t *testing.T) {85+ cs := cells("files = ls", "big = $files | first", "$big", "ps")86+ outs := map[int]*Output{}87+ out := func(id int) *Output { return outs[id] }88+ run := func(i int, seq int) {89+ o := &Output{NUON: []byte("x"), Seq: seq, Count: seq, Source: cs[i].Source, Reads: map[string]int{}}90+ for _, n := range Refs(cs[i].Pipeline()) {91+ if j, ok := Names(cs)[n]; ok && outs[cs[j].ID] != nil {92+ o.Reads[n] = outs[cs[j].ID].Seq93+ }94+ }95+ outs[cs[i].ID] = o96+ }97+ run(0, 1)98+ run(1, 2)99+ run(2, 3)100+ run(3, 4)101+ if st := Stale(cs, out); len(st) != 0 {102+ t.Fatalf("stale after running all: %v", st)103+ }104+ run(0, 5) // files ran again: big and what reads it are stale105+ if st := Stale(cs, out); !st[2] || !st[3] || st[1] || st[4] {106+ t.Errorf("stale %v", st)107+ }108+ run(1, 6)109+ run(2, 7)110+ cs[3].Source = "ps | first" // edited since it ran111+ if st := Stale(cs, out); len(st) != 1 || !st[4] {112+ t.Errorf("stale after editing %v", st)113+ }114+}115+116+func TestSettleAndKept(t *testing.T) {117+ cs := cells("files = ls", "$files | first", "ps")118+ outs := map[int]*Output{1: {NUON: []byte("12345"), Seq: 1}, 2: {NUON: []byte("123"), Seq: 2}, 3: {NUON: []byte("1234567"), Seq: 3}}119+ Settle(cs, outs)120+ if st := Stale(cs, func(id int) *Output { return outs[id] }); len(st) != 0 {121+ t.Errorf("stale after settling: %v", st)122+ }123+ kept := Kept(cs, func(id int) *Output { return outs[id] }, Caps{Cell: 6, Total: 9})124+ if !kept[1] || !kept[2] || kept[3] {125+ t.Errorf("kept %v", kept)126+ }127+}
A internal/notebook/refs.go +127 -0
@@ -0,0 +1,127 @@
1+package notebook2+3+import (4+ "slices"5+ "strconv"6+ "strings"7+)8+9+// What a pipeline reads. A code cell reads another's output as $name,10+// the selection on a sheet as $selection, and a range of a sheet as11+// $sheet.A1:C9 (or $sheet.Sales!A1:C9, $sheet.'Q1 data'!A1:B5). Each12+// reaches nu as a variable holding a table, read from a NUON file, never13+// as text spliced into the pipeline: a $sheet range is renamed to a14+// variable of its own ($__sheet1) before the pipeline runs.15+16+// Selection is the variable that holds the selection.17+const Selection = "selection"18+19+// sheetVar starts a range of a sheet.20+const sheetVar = "$sheet."21+22+// Refs returns the names a pipeline reads as $name, in the order it23+// first names them, leaving out nushell's variables and the notebook's24+// own ($selection, $sheet).25+func Refs(pipeline string) []string {26+ var out []string27+ for i := 0; i < len(pipeline); i++ {28+ if pipeline[i] != '$' {29+ continue30+ }31+ j := i + 132+ for j < len(pipeline) && isWord(pipeline[j]) {33+ j++34+ }35+ name := pipeline[i+1 : j]36+ if name != "" && ValidName(name) == nil && !slices.Contains(out, name) {37+ out = append(out, name)38+ }39+ i = j - 140+ }41+ return out42+}43+44+// ReadsSelection reports whether a pipeline reads $selection.45+func ReadsSelection(pipeline string) bool {46+ for i := 0; ; {47+ k := strings.Index(pipeline[i:], "$"+Selection)48+ if k < 0 {49+ return false50+ }51+ end := i + k + 1 + len(Selection)52+ if end == len(pipeline) || !isWord(pipeline[end]) {53+ return true54+ }55+ i = end56+ }57+}58+59+// SheetRef is a range of a sheet a pipeline reads, $sheet.A1:C9.60+type SheetRef struct {61+ // Ref is the range as written after $sheet.: "A1:C9", "Sales!A1:C9".62+ Ref string63+ // Var is the variable it's read as once renamed: __sheet1.64+ Var string65+}66+67+// Bind renames each range of a sheet the pipeline reads ($sheet.A1:C9)68+// to a variable of its own, returning the pipeline to run and the ranges69+// in the order they appear, the same range read twice once.70+func Bind(pipeline string) (string, []SheetRef) {71+ var b strings.Builder72+ var refs []SheetRef73+ rest := pipeline74+ for {75+ k := strings.Index(rest, sheetVar)76+ if k < 0 || k > 0 && isWord(rest[k-1]) {77+ if k < 0 {78+ b.WriteString(rest)79+ return b.String(), refs80+ }81+ b.WriteString(rest[:k+len(sheetVar)])82+ rest = rest[k+len(sheetVar):]83+ continue84+ }85+ b.WriteString(rest[:k])86+ rest = rest[k+len(sheetVar):]87+ n := refLen(rest)88+ if n == 0 {89+ b.WriteString(sheetVar)90+ continue91+ }92+ ref := rest[:n]93+ rest = rest[n:]94+ i := slices.IndexFunc(refs, func(r SheetRef) bool { return r.Ref == ref })95+ if i < 0 {96+ refs = append(refs, SheetRef{Ref: ref, Var: "__sheet" + strconv.Itoa(len(refs)+1)})97+ i = len(refs) - 198+ }99+ b.WriteString("$" + refs[i].Var)100+ }101+}102+103+// refLen is how long the range at the start of s is: a sheet's name,104+// quoted in ” if it needs to be, then ! and cells, or cells alone. A105+// dot ends it, so a cell path may follow: $sheet.A1:C9.name.106+func refLen(s string) int {107+ i := 0108+ if strings.HasPrefix(s, "'") {109+ for i = 1; i < len(s); i++ {110+ if s[i] == '\'' {111+ if i+1 < len(s) && s[i+1] == '\'' {112+ i++ // '' is a quote in the name113+ continue114+ }115+ break116+ }117+ }118+ if i >= len(s) || i+1 >= len(s) || s[i+1] != '!' {119+ return 0120+ }121+ i += 2122+ }123+ for i < len(s) && (isWord(s[i]) || strings.IndexByte("!:$", s[i]) >= 0) {124+ i++125+ }126+ return i127+}
M internal/nushell/nushell.go +55 -84
@@ -1,14 +1,13 @@
1-// Package nushell runs a notebook's commands: each command is a nushell2-// pipeline run by `nu` as a separate process, reading other regions'3-// tables as variables ($r1) and the selection as $in, and writing a4-// table back as NUON, which becomes the region's cells. Tables reach nu5-// as NUON in files it opens, never as text spliced into the command.6-// The package knows neither the terminal nor the workbook's regions:7-// the UI hands it a Job and gets back a table.1+// Package nushell runs a notebook's code cells: each is a nushell2+// pipeline run by `nu` as a separate process, reading other cells'3+// outputs and ranges of sheets as variables ($sales, $selection), and4+// printing what it made as NUON, which the notebook keeps as the cell's5+// output. Tables reach nu as NUON in files it opens, never as text6+// spliced into the pipeline. The package knows neither the terminal nor7+// the notebook: the UI hands it a Job and gets back NUON.88 package nushell99 1010 import (11- "bufio"1211 "bytes"1312 "context"1413 "errors"
@@ -23,18 +22,15 @@ import (
2322 2423 "github.com/charmbracelet/x/ansi"2524 26- "github.com/FelineStateMachine/012/internal/fileio"27- "github.com/FelineStateMachine/012/internal/sheet"25+ "github.com/FelineStateMachine/012/internal/nuon"2826 )2927 30-// Job is a command to run.28+// Job is a pipeline to run.3129 type Job struct {3230 Command string33- // Tables are the regions the command reads, by name, as NUON: each34- // becomes the variable of its name.31+ // Tables are what the pipeline reads, by variable name, as NUON:32+ // each becomes the variable of its name.3533 Tables map[string][]byte36- // Input is the table the command reads as $in, as NUON, or nil.37- Input []byte3834 // Config runs nu with the user's config files rather than without.3935 Config bool4036 }
@@ -45,10 +41,10 @@ type Runner interface {
4541 Run(ctx context.Context, job Job, script string, stdout io.Writer) error4642 }4743 48-// ErrMissing is what running a command says when nu isn't installed.44+// ErrMissing is what running a cell says when nu isn't installed.4945 var ErrMissing = errors.New("nu isn't installed or isn't on your PATH: see docs/nushell/notebooks.md")5046 51-// Error is a command that failed: nu's message, and all it wrote to47+// Error is a pipeline that failed: nu's message, and all it wrote to5248 // standard error.5349 type Error struct {5450 Msg string
@@ -57,8 +53,18 @@ type Error struct {
5753 5854 func (e *Error) Error() string { return e.Msg }5955 56+// Help is the help line of nu's message, "help: ...", or "".57+func (e *Error) Help() string {58+ for line := range strings.SplitSeq(e.Stderr, "\n") {59+ if line = strings.TrimSpace(line); strings.HasPrefix(line, "help:") {60+ return line61+ }62+ }63+ return ""64+}65+6066 // Script is what nu runs for job: the tables bound to their names, then61-// the command, its output as NUON. The files are named by environment67+// the pipeline, its output as NUON. The files are named by environment6268 // variables (NU012_TABLE_0 and so on) the runner sets, so no data and no6369 // path is written into the script.6470 func Script(job Job, names []string) string {
@@ -66,80 +72,47 @@ func Script(job Job, names []string) string {
6672 for i, name := range names {6773 fmt.Fprintf(&b, "let %s = (open --raw $env.NU012_TABLE_%d | from nuon)\n", name, i)6874 }69- if job.Input != nil {70- b.WriteString("$in | from nuon | ")71- }7275 b.WriteString("do {\n" + job.Command + "\n} | to nuon\n")7376 return b.String()7477 }7578 76-// Exec runs job and reads what it prints as a table, keeping at most77-// maxCells cells (0 for the max-cells setting), within timeout.78-func Exec(ctx context.Context, r Runner, job Job, timeout time.Duration, maxCells int) (*sheet.RegionData, error) {79+// ErrTooLarge is a run that printed more than it may keep.80+var ErrTooLarge = errors.New("the output is too large to keep")81+82+// Exec runs job and returns what it printed, NUON, at most maxBytes of83+// it, within timeout.84+func Exec(ctx context.Context, r Runner, job Job, timeout time.Duration, maxBytes int) ([]byte, error) {7985 if timeout > 0 {8086 var cancel context.CancelFunc8187 ctx, cancel = context.WithTimeoutCause(ctx, timeout, fmt.Errorf("stopped after %s", timeout))8288 defer cancel()8389 }84- names := sortedNames(job.Tables)85- pr, pw := io.Pipe()86- done := make(chan error, 1)87- go func() {88- err := r.Run(ctx, job, Script(job, names), pw)89- pw.CloseWithError(err)90- done <- err91- }()92- data, readErr := readTable(ctx, pr, maxCells)93- pr.CloseWithError(errors.New("done reading")) // the command stops if it's still writing94- runErr := <-done90+ ctx, cancel := context.WithCancelCause(ctx)91+ defer cancel(nil)92+ out := &capped{max: maxBytes, stop: func() { cancel(ErrTooLarge) }}93+ err := r.Run(ctx, job, Script(job, sortedNames(job.Tables)), out)9594 switch {9695 case ctx.Err() != nil:9796 return nil, context.Cause(ctx)98- case runErr != nil:99- return nil, runErr100- case readErr != nil:101- return nil, fmt.Errorf("nu's output isn't NUON: %w", readErr)97+ case err != nil:98+ return nil, err10299 }103- return data, nil100+ return bytes.TrimSpace(out.b.Bytes()), nil104101 }105102 106-// readTable reads NUON as a region's table. A value that isn't a table,107-// record or list (a number, a string, null) is read as a list of one,108-// a table of one column named value.109-func readTable(ctx context.Context, r io.Reader, maxCells int) (*sheet.RegionData, error) {110- br := bufio.NewReader(r)111- first, err := firstByte(br)112- if err != nil {113- return nil, err114- }115- in := io.Reader(br)116- if first != '[' && first != '{' {117- in = io.MultiReader(strings.NewReader("["), br, strings.NewReader("]"))118- }119- res, err := fileio.ImportReader(ctx, "nu", in, fileio.Options{MaxCells: maxCells})120- if err != nil {121- return nil, err122- }123- d := sheet.RegionDataOf(res.Sheet)124- d.Note = strings.Join(res.Notes, "; ")125- return d, nil103+// capped keeps at most max bytes, stopping the run past them.104+type capped struct {105+ b bytes.Buffer106+ max int107+ stop func()126108 }127109 128-// firstByte is the first byte past spaces, without taking it; 0 when129-// there's nothing else.130-func firstByte(br *bufio.Reader) (byte, error) {131- for {132- c, err := br.ReadByte()133- switch {134- case errors.Is(err, io.EOF):135- return 0, nil136- case err != nil:137- return 0, err138- case c == ' ' || c == '\t' || c == '\n' || c == '\r':139- continue140- }141- return c, br.UnreadByte()110+func (c *capped) Write(p []byte) (int, error) {111+ if c.max > 0 && c.b.Len()+len(p) > c.max {112+ c.stop()113+ return 0, ErrTooLarge142114 }115+ return c.b.Write(p)143116 }144117 145118 func sortedNames(tables map[string][]byte) []string {
@@ -153,14 +126,18 @@ func sortedNames(tables map[string][]byte) []string {
153126 154127 // Commands lists nu's command names, for completing them.155128 func Commands(ctx context.Context, r Runner, timeout time.Duration) ([]string, error) {156- d, err := Exec(ctx, r, Job{Command: "help commands | get name"}, timeout, -1)129+ data, err := Exec(ctx, r, Job{Command: "help commands | get name"}, timeout, 0)130+ if err != nil {131+ return nil, err132+ }133+ v, err := nuon.Parse(data)157134 if err != nil {158135 return nil, err159136 }160137 var out []string161- for row := 1; row < d.Rows; row++ {162- if v, _ := d.At(row, 0); v.Kind == sheet.Text {163- out = append(out, v.Str)138+ for _, n := range v.List {139+ if n.Kind == nuon.String {140+ out = append(out, n.Str)164141 }165142 }166143 return out, nil
@@ -198,15 +175,9 @@ func (n Nu) Run(ctx context.Context, job Job, script string, stdout io.Writer) e
198175 if !job.Config {199176 args = append([]string{"--no-config-file"}, args...)200177 }201- if job.Input != nil {202- args = append([]string{"--stdin"}, args...)203- }204178 cmd := exec.CommandContext(ctx, path, args...)205179 cmd.Env, cmd.Stdout = env, stdout206180 cmd.WaitDelay = 2 * time.Second207- if job.Input != nil {208- cmd.Stdin = bytes.NewReader(job.Input)209- }210181 var stderr bytes.Buffer211182 cmd.Stderr = &limited{w: &stderr, n: 64 << 10}212183 if err := cmd.Run(); err != nil {
M internal/nushell/nushell_test.go +21 -64
@@ -8,8 +8,6 @@ import (
88 "strings"99 "testing"1010 "time"11-12- "github.com/FelineStateMachine/012/internal/sheet"1311 )1412 1513 // fake prints out, or fails with err, and keeps the script it was given.
@@ -29,71 +27,29 @@ func (f *fake) Run(ctx context.Context, job Job, script string, stdout io.Writer
2927 return err3028 }3129 32-func cells(d *sheet.RegionData) [][]string {33- var out [][]string34- for r := range d.Rows {35- var row []string36- for c := range d.Cols {37- v, f := d.At(r, c)38- row = append(row, sheet.FormatText(v, f))39- }40- out = append(out, row)41- }42- return out43-}44-4530 func TestScript(t *testing.T) {46- got := Script(Job{Command: "$r1 | where size > 1kb", Tables: map[string][]byte{"r1": nil}, Input: []byte("[]")}, []string{"big", "r1"})31+ got := Script(Job{Command: "$r1 | where size > 1kb", Tables: map[string][]byte{"r1": nil}}, []string{"big", "r1"})4732 want := "let big = (open --raw $env.NU012_TABLE_0 | from nuon)\n" +4833 "let r1 = (open --raw $env.NU012_TABLE_1 | from nuon)\n" +49- "$in | from nuon | do {\n$r1 | where size > 1kb\n} | to nuon\n"34+ "do {\n$r1 | where size > 1kb\n} | to nuon\n"5035 if got != want {5136 t.Errorf("script:\n%s\nwant:\n%s", got, want)5237 }5338 }5439 55-func TestExecReadsTables(t *testing.T) {56- for _, c := range []struct {57- out string58- want string59- }{60- {"[[name, size]; [a, 1kb], [b, 2b]]", "name size|a 1.0 kB|b 2 B"},61- {"{a: 1, b: true}", "a b|1 TRUE"},62- {"3", "value|3"},63- {`"hello"`, "value|hello"},64- {"null", "value"},65- {"[1, 2]", "value|1|2"},66- {"", ""},67- } {68- d, err := Exec(context.Background(), &fake{out: c.out}, Job{Command: "x"}, time.Second, 0)69- if err != nil {70- t.Errorf("%q: %v", c.out, err)71- continue72- }73- var rows []string74- for _, r := range cells(d) {75- rows = append(rows, strings.TrimSpace(strings.Join(r, " ")))76- }77- if got := strings.Join(rows, "|"); got != c.want {78- t.Errorf("%q read as %q, want %q", c.out, got, c.want)79- }40+func TestExec(t *testing.T) {41+ out, err := Exec(context.Background(), &fake{out: " [[name, size]; [a, 1kb]]\n"}, Job{Command: "x"}, time.Second, 0)42+ if err != nil || string(out) != "[[name, size]; [a, 1kb]]" {43+ t.Errorf("%q %v", out, err)8044 }81-}82-83-func TestExecFails(t *testing.T) {84- _, err := Exec(context.Background(), &fake{err: &Error{Msg: "Cannot find column 'x'"}}, Job{Command: "x"}, time.Second, 0)45+ _, err = Exec(context.Background(), &fake{err: &Error{Msg: "Cannot find column 'x'", Stderr: " × Cannot find column 'x'\n help: try get\n"}}, Job{Command: "x"}, time.Second, 0)8546 var e *Error86- if !errors.As(err, &e) || e.Msg != "Cannot find column 'x'" {47+ if !errors.As(err, &e) || e.Msg != "Cannot find column 'x'" || e.Help() != "help: try get" {8748 t.Errorf("error %v", err)8849 }89- _, err = Exec(context.Background(), &fake{out: "[[a]; [1"}, Job{Command: "x"}, time.Second, 0)90- if err == nil || !strings.Contains(err.Error(), "isn't NUON") {91- t.Errorf("bad output: %v", err)92- }93- // Past max-cells, whole rows are kept and the note says so.94- d, err := Exec(context.Background(), &fake{out: "[[a, b]; [1, 2], [3, 4], [5, 6]]"}, Job{Command: "x"}, time.Second, 4)95- if err != nil || d.Rows != 2 || d.Note == "" {96- t.Errorf("capped: %+v %v", d, err)50+ _, err = Exec(context.Background(), &fake{out: strings.Repeat("x", 100)}, Job{Command: "x"}, time.Second, 10)51+ if !errors.Is(err, ErrTooLarge) {52+ t.Errorf("too large: %v", err)9753 }9854 }9955
@@ -107,24 +63,25 @@ func TestMessage(t *testing.T) {
10763 }10864 }10965 110-// The real nu, when it's installed: tables go in as variables and $in,111-// and come back typed.66+// The real nu, when it's installed: tables go in as variables and come67+// back as NUON.11268 func TestNu(t *testing.T) {11369 if _, err := exec.LookPath("nu"); err != nil {11470 t.Skip("nu isn't installed")11571 }11672 job := Job{117- Command: "$in | append ($r1 | where size > 1kb)",118- Tables: map[string][]byte{"r1": []byte("[[name, size]; [a, 2kb], [b, 10b]]")},119- Input: []byte("[[name, size]; [c, 5mb]]"),73+ Command: "$r1 | where size > 1kb | append $__sheet1",74+ Tables: map[string][]byte{"r1": []byte("[[name, size]; [a, 2kb], [b, 10b]]"), "__sheet1": []byte("[[name, size]; [c, 5mb]]")},12075 }121- d, err := Exec(context.Background(), Nu{}, job, 30*time.Second, 0)76+ out, err := Exec(context.Background(), Nu{}, job, 30*time.Second, 0)12277 if err != nil {12378 t.Fatal(err)12479 }125- got := cells(d)126- if len(got) != 3 || got[1][0] != "c" || got[1][1] != "5.0 MB" || got[2][0] != "a" || got[2][1] != "2.0 kB" {127- t.Errorf("table %v", got)80+ if got := string(out); got != "[[name, size]; [a, 2000b], [c, 5000000b]]" && got != "[[name, size]; [a, 2kB], [c, 5MB]]" {81+ t.Logf("output %s", got) // nu's own spelling of sizes varies by version82+ if !strings.Contains(got, "a") || !strings.Contains(got, "c") || strings.Contains(got, "b,") {83+ t.Errorf("output %s", got)84+ }12885 }12986 _, err = Exec(context.Background(), Nu{}, Job{Command: "ls | get nosuchcolumn"}, 30*time.Second, 0)13087 var e *Error
M internal/sheet/file.go +24 -20
@@ -56,12 +56,10 @@ type fileFormat struct {
5656 Locale string `json:"locale,omitempty"`5757 // Macros and where they were made need no version bump either:5858 // earlier builds ignore them, and the sheets read the same.59- MacroOrigin string `json:"macroOrigin,omitempty"`60- // ShellHistory needs no version either: see shellhistory.go.61- ShellHistory []string `json:"shellHistory,omitempty"`62- Macros []fileMacro `json:"macros,omitempty"`63- fileSheet // versions 1 to 3: the only sheet64- Sheets []fileSheet `json:"sheets,omitempty"` // version 459+ MacroOrigin string `json:"macroOrigin,omitempty"`60+ Macros []fileMacro `json:"macros,omitempty"`61+ fileSheet // versions 1 to 3: the only sheet62+ Sheets []fileSheet `json:"sheets,omitempty"` // version 46563 }6664 6765 // fileSheet is one sheet of a file.
@@ -80,9 +78,12 @@ type fileSheet struct {
8078 // Rules need no version: see rulefile.go.8179 CondFormats []fileCondFormat `json:"conditionalFormats,omitempty"`8280 Validations []fileValidation `json:"validations,omitempty"`83- // A notebook and its regions need no version: see regionfile.go.84- Notebook bool `json:"notebook,omitempty"`85- Regions []fileRegion `json:"regions,omitempty"`81+ // Regions need no version: see regionfile.go.82+ Regions []fileRegion `json:"regions,omitempty"`83+ // Nor do notebook tabs: see notebookfile.go.84+ Tab string `json:"tab,omitempty"`85+ Reactive bool `json:"reactive,omitempty"`86+ NotebookCells []fileNotebookCell `json:"notebookCells,omitempty"`8687 }8788 8889 // Write saves the workbook the sheet belongs to; see Workbook.Write.
@@ -141,9 +142,10 @@ func (w *Workbook) Write(out io.Writer) error {
141142 }142143 143144 // single reports whether the workbook fits the single-sheet format of144-// versions 2 and 3: one sheet, and no formula naming a sheet.145+// versions 2 and 3: one sheet, not a notebook, and no formula naming a146+// sheet.145147 func (w *Workbook) single() bool {146- return len(w.sheets) == 1 && len(w.crossUsers) == 0 && !w.hasPivots()148+ return len(w.sheets) == 1 && len(w.crossUsers) == 0 && !w.hasPivots() && !w.hasNotebook()147149 }148150 149151 func jsonString(s string) string {
@@ -164,16 +166,12 @@ func (w *Workbook) headLines() string {
164166 if w.locale != nil {165167 lines = append(lines, `"locale": `+jsonString(w.locale.Tag))166168 }167- if w.macroOrigin != "" && (len(w.macros) > 0 || len(w.allRegions()) > 0) {169+ if w.macroOrigin != "" && (len(w.macros) > 0 || len(w.allRegions()) > 0 || w.hasNotebook()) {168170 lines = append(lines, `"macroOrigin": `+jsonString(w.macroOrigin))169171 }170172 if len(w.macros) > 0 {171173 lines = append(lines, w.macrosLines())172174 }173- if len(w.shellHistory) > 0 {174- raw, _ := json.Marshal(w.shellHistory)175- lines = append(lines, `"shellHistory": `+string(raw))176- }177175 return strings.Join(lines, ",\n ")178176 }179177
@@ -275,7 +273,10 @@ func (s *Sheet) writeObjects(b *bufio.Writer, indent string) error {
275273 fmt.Fprintf(b, ",\n%s\"pivot\": %s", indent, raw)276274 }277275 s.writeRules(b, indent)278- return s.writeRegionDefs(b, indent)276+ if err := s.writeRegionDefs(b, indent); err != nil {277+ return err278+ }279+ return s.writeNotebook(b, indent)279280 }280281 281282 // Read loads a file written by Write, of this or an earlier version, and
@@ -326,8 +327,8 @@ func (w *Workbook) readNames(names map[string]string) error {
326327 }327328 328329 // read fills a sheet, its cells read (fileread.go), from the rest of329-// its part of a file.330-func (s *Sheet) read(f fileSheet) error {330+// its part of a file, adding to old the command regions to convert.331+func (s *Sheet) read(f fileSheet, old *[]oldRegion) error {331332 s.tabHidden = f.Hidden332333 for name, width := range f.Widths {333334 c, ok := ParseCol(name)
@@ -363,7 +364,10 @@ func (s *Sheet) read(f fileSheet) error {
363364 if err := s.readRules(f.CondFormats, f.Validations); err != nil {364365 return err365366 }366- if err := s.readRegions(f.Notebook, f.Regions); err != nil {367+ if err := s.readRegions(f.Regions, old); err != nil {368+ return err369+ }370+ if err := s.readNotebook(f); err != nil {367371 return err368372 }369373 return s.readView(f.fileView)
M internal/sheet/fileread.go +4 -2
M internal/sheet/filestream_test.go +12 -2
@@ -43,6 +43,7 @@ func readWhole(data []byte) (w *Workbook, ambiguous bool, err error) {
4343 if err := w.readNames(f.Names); err != nil {4444 return nil, false, err4545 }46+ var old []oldRegion4647 for i, body := range bodies {4748 s := w.sheets[i]4849 amb, err := placeWhole(s, body.Cells, f.Version)
@@ -50,7 +51,7 @@ func readWhole(data []byte) (w *Workbook, ambiguous bool, err error) {
5051 return nil, false, err5152 }5253 ambiguous = ambiguous || amb53- if err := s.read(body); err != nil {54+ if err := s.read(body, &old); err != nil {5455 return nil, false, err5556 }5657 }
@@ -64,7 +65,8 @@ func readWhole(data []byte) (w *Workbook, ambiguous bool, err error) {
6465 return nil, false, err6566 }6667 w.macroOrigin = f.MacroOrigin67- w.readShellHistory(f.ShellHistory)68+ w.convertOld(old)69+ w.nb.changed = 06870 w.RecalcAll()6971 return w, ambiguous, nil7072 }
@@ -243,6 +245,14 @@ func streamSeeds(t testing.TB) [][]byte {
243245 []byte(`{"version": 2, "regions": [{"name": "r1", "command": "ls", "at": "A1"}, {"name": "R1", "command": "x", "at": "A5"}]}`),244246 []byte(`{"version": 2, "regions": [{"name": "in", "command": "ls", "at": "A1"}, {"name": "r2", "at": "ZZZZ1", "rows": -1}]}`),245247 []byte(`{"version": 2, "regions": [{"name": "a", "command": "$b", "at": "A1", "reads": ["b"]}, {"name": "b", "command": "$a", "at": "A3", "reads": ["a"], "sort": [{"column": 0}]}]}`),248+ []byte(`{"version": 4, "sheets": [{"name": "S", "cells": {}, "regions": [{"name": "files", "at": "B2", "output": true}]},249+ {"name": "Notebook", "tab": "notebook", "reactive": true, "cells": {}, "notebookCells": [250+ {"kind": "note", "source": "# Files"}, {"source": "files = ls", "output": "[[name, size]; [a, 2kb]]"},251+ {"source": "$files | nope", "error": "Command not found", "detail": "help: ls", "note": "x"}, {"source": "ls **/*", "unsaved": true}]}]}`),252+ []byte(`{"version": 4, "sheets": [{"name": "N", "tab": "notebook", "cells": {}, "notebookCells": [{"kind": "table", "source": "x"}]}]}`),253+ []byte(`{"version": 4, "sheets": [{"name": "N", "cells": {}, "notebookCells": [{"source": "x"}]}]}`),254+ []byte(`{"version": 2, "cells": {}, "regions": [{"name": "o", "at": "A1", "output": true, "command": "ls"}, {"name": "p", "at": "A1", "output": true, "path": "p"}]}`),255+ []byte(`{"version": 4, "sheets": [{"name": "Notebook", "tab": "notebook", "cells": {}}, {"name": "Shell 1", "notebook": true, "cells": {}, "regions": [{"name": "r1", "command": "ls", "at": "A1"}]}]}`),246256 []byte(`null`), []byte(`[]`), []byte(`{"version": 2, "cells": {"A1": "1",}}`), []byte(`{"version": 2, "cells": {"A1"`),247257 }248258 }
M internal/sheet/linked.go +6 -32
@@ -9,10 +9,9 @@ import (
99 )1010 1111 // Linked files: regions (region.go) whose rows come from a file the UI12-// follows as it grows or is rewritten. A linked file has no label line:13-// its anchor is its table's first cell, the file's first row, which14-// stays; the rows under it are the file's, all of them up to max-cells,15-// or the last Window of them. Its rows arrive only as live operations12+// follows as it grows or is rewritten. Its anchor is its table's first13+// cell, the file's first row, which stays; the rows under it are the14+// file's, all of them up to max-cells, or the last Window of them. Its rows arrive only as live operations1615 // (live.go) and are never part of the undo state (see region.go for1716 // why), so a linked file is read again whenever its cells may no longer1817 // be the file's: undo put it back, it moved, or a cell blocking it was
@@ -88,7 +87,7 @@ func (s *Sheet) linkedInfo(r Region) LinkedRegion {
8887 8988 // LinkedAt returns the linked file covering the cell at a.9089 func (s *Sheet) LinkedAt(a Addr) (LinkedRegion, bool) {91- if r, _, ok := s.RegionAt(a); ok && r.Linked() {90+ if r, ok := s.RegionAt(a); ok && r.Linked() {9291 return s.linkedInfo(r), true9392 }9493 return LinkedRegion{}, false
@@ -138,20 +137,12 @@ var errLinkOver = errors.New("A linked file needs an empty cell to start in")
138137 139138 // AddLinked makes a linked file at a reading src, named after the file,140139 // as one undo step, and returns its name. It starts empty and stale: the141-// UI reads the file and sends its rows. On a notebook sheet it goes142-// below everything, as a command region does.140+// UI reads the file and sends its rows.143141 func (s *Sheet) AddLinked(a Addr, src LinkSource) (string, error) {144- if !a.Valid() || s.cells.filledAt(a) || s.InPivot(Rect{From: a, To: a}) {145- return "", errLinkOver146- }147- if _, _, ok := s.RegionAt(a); ok {148- return "", errLinkOver149- }150142 name := s.wb.linkName(src.Path)151143 if err := s.AddRegion(Region{Name: name, At: a, File: src}); err != nil {152144 return "", err153145 }154- s.meta(nameKey(name)).stale = true155146 return name, nil156147 }157148
@@ -175,29 +166,12 @@ func (w *Workbook) linkName(path string) string {
175166 if ValidRegionName(name) != nil {176167 name = "file_" + name177168 }178- for n := 2; w.checkRegionName(name) != nil; n++ {169+ for n := 2; w.checkRegionName(name, false) != nil; n++ {179170 name = strings.TrimSuffix(name, "_"+strconv.Itoa(n-1)) + "_" + strconv.Itoa(n)180171 }181172 return name182173 }183174 184-// putRegionsBack puts back a sheet's regions from an undo step. A185-// linked file the step brings back, or moves, is emptied and read again186-// once the change ends: its rows aren't part of the undo state, so the187-// cells undo put there may not be the file's.188-func (s *Sheet) putRegionsBack(st regionState) {189- for _, r := range st.list {190- if !r.Linked() {191- continue192- }193- i := s.regionIndex(nameKey(r.Name))194- if i < 0 || s.regions.list[i].At != r.At || s.regions.list[i].File != r.File {195- s.meta(nameKey(r.Name)).reread = true196- }197- }198- s.regions, s.regionsStale = st, true199-}200-201175 // SetLinkSource changes what a linked file reads (its window, say), as202176 // one undo step; it is read again.203177 func (w *Workbook) SetLinkSource(name string, src LinkSource) error {
M internal/sheet/linkedregions_test.go +18 -37
@@ -1,30 +1,19 @@
11 package sheet22 3-import (4- "slices"5- "testing"6-)3+import "testing"74 8-// Linked files and command regions are one set: a formula names a9-// linked file as nu.name, a command reads it as $name, and refreshing it10-// runs what reads it, while the linked file itself never runs.5+// Linked files and sent outputs are one set: a formula names either as6+// nu.name, and their names are one namespace.117 func TestLinkedAmongRegions(t *testing.T) {12- s := notebook(t)8+ s := New()139 w := s.Book()1410 id, err := s.AddLinked(Addr{}, LinkSource{Path: "logs/app-2026.csv"})1511 if err != nil || id != "app_2026" {1612 t.Fatalf("name %q, %v", id, err)1713 }1814 apply(t, s, LiveOp{Region: id, Reset: true, Header: liveRow("n"), Rows: []LiveRow{liveRow("2"), liveRow("3")}})19- addShown(t, s, "r1", "$app_2026 | first", table([]string{"n"}, []string{"2"}))20- if _, r, _ := w.Region("r1"); !slices.Equal(r.Deps, []string{"app_2026"}) {21- t.Fatalf("r1 reads %v", r.Deps)22- }23- // Stacked on a notebook, the command region goes below the file.24- if _, r, _ := w.Region("r1"); r.At.Row <= 2 {25- t.Errorf("r1 at %v, over the linked file", r.At)26- }27- other, _ := w.AddSheet("Sheet2", 1)15+ sent(t, s, "r1", "D1")16+ other, _ := w.AddSheet("Sheet2", 2)2817 other.Set(Addr{}, "=SUM(nu.app_2026)")2918 if v := other.Value(Addr{}).Num; v != 5 {3019 t.Fatalf("SUM(nu.app_2026) = %v", other.Value(Addr{}))
@@ -33,29 +22,21 @@ func TestLinkedAmongRegions(t *testing.T) {
3322 if v := other.Value(Addr{}).Num; v != 9 {3423 t.Fatalf("after a row arrived, SUM = %v", v)3524 }36- if got := w.RunOrder(); !slices.Equal(got, []string{"r1"}) {37- t.Errorf("run all: %v", got)38- }39- if got := w.RefreshOrder(id); !slices.Equal(got, []string{"r1"}) {40- t.Errorf("refreshing the linked file runs %v", got)41- }42- if err := s.EditRegion(id, "ls", nil, ""); err == nil {43- t.Error("a linked file took a command")44- }45- if err := s.ShowRegion(id, table([]string{"x"})); err == nil {46- t.Error("a linked file was shown a command's table")47- }4825 if got := w.linkName("logs/app-2026.csv"); got != "app_2026_2" {4926 t.Errorf("a second link to the file is named %q", got)5027 }5128 if got := w.linkName("9.csv"); got != "file_9" {5229 t.Errorf("a file named 9 is %q", got)5330 }31+ if got := w.linkName("r1.csv"); got != "r1_2" {32+ t.Errorf("a file named like a cell is %q", got)33+ }34+ if len(w.LinkedRegions()) != 1 {35+ t.Errorf("linked regions %+v", w.LinkedRegions())36+ }5437 }5538 56-// A linked file's rows are applied as ops outside the undo history,57-// while a command region's run is an undo step: the policies region.go58-// gives the two kinds.39+// Rows arriving are never undo steps, for either kind.5940 func TestRegionKindsUndo(t *testing.T) {6041 s := New()6142 id, _ := s.AddLinked(Addr{}, LinkSource{Path: "log.csv"})
@@ -63,11 +44,11 @@ func TestRegionKindsUndo(t *testing.T) {
6344 if s.Book().UndoLabel() != "add log" {6445 t.Errorf("rows arriving made a step: %q", s.Book().UndoLabel())6546 }66- s.AddRegion(Region{Name: "r1", Command: "ls", At: Addr{Col: 3}})67- s.ShowRegion("r1", table([]string{"n"}, []string{"7"}))68- if s.Book().UndoLabel() != "run r1" {69- t.Errorf("a run isn't a step: %q", s.Book().UndoLabel())47+ sent(t, s, "r1", "D1")48+ apply(t, s, LiveOp{Region: "r1", Reset: true, Header: liveRow("n"), Rows: []LiveRow{liveRow("7")}})49+ if s.Book().UndoLabel() != "add r1" {50+ t.Errorf("an output arriving made a step: %q", s.Book().UndoLabel())7051 }7152 s.Book().Undo()72- wantShown(t, s, map[string]string{"D1": "r1 ls not run", "D3": "", "A2": "1"})53+ wantShown(t, s, map[string]string{"D1": "", "D2": "", "A2": "1"})7354 }
M internal/sheet/live.go +4 -6
@@ -16,8 +16,8 @@ import (
1616 // depends on nothing but the region: the same op applied to the same1717 // workbook elsewhere makes the same cells, which is what a session1818 // following another's would be sent. Whatever reads a linked file sends19-// them; a command region's table is written through the same path20-// (writeTable), from its RegionData.19+// them, and the UI sends a notebook cell's output to the region it was20+// sent to whenever the cell runs.2121 2222 // LiveCell is a value as a source gave it, with its format (a date's,2323 // a file size's), Automatic for none.
@@ -65,9 +65,7 @@ var OnLive func(trace any, i LiveInfo)
6565 6666 // ApplyLive applies op to its region: its rows are written, a window6767 // drops the oldest, and what reads the cells that changed recalculates.68-// A command region's table so written lasts until the region shows its69-// table again (ShowRegion). It returns ErrNoRegion for a region the70-// workbook doesn't hold.68+// It returns ErrNoRegion for a region the workbook doesn't hold.7169 func (w *Workbook) ApplyLive(op LiveOp) error {7270 s, r, ok := w.Region(op.Region)7371 if !ok {
@@ -172,7 +170,7 @@ func (s *Sheet) liveRows(r Region, me *regionMeta, op LiveOp) (rows []LiveRow, h
172170 // readRow reads row i of the region's table (0 for the header) back173171 // from its cells.174172 func (s *Sheet) readRow(r Region, me *regionMeta, i int) LiveRow {175- o := tableOrigin(r)173+ o := r.At176174 row := make(LiveRow, me.cols)177175 for c := range me.cols {178176 v, lk, kind := s.cells.derivedOf(Addr{Col: o.Col + c, Row: o.Row + i})
A internal/sheet/notebook.go +229 -0
@@ -0,0 +1,229 @@
1+package sheet2+3+import (4+ "slices"5+ "strconv"6+ "strings"7+8+ "github.com/FelineStateMachine/012/internal/notebook"9+)10+11+// Notebook tabs: a sheet of the workbook that holds a notebook's cells12+// rather than a grid (package notebook). The tab sits among the sheets,13+// is named, moved, hidden and deleted as a sheet is, and its cells are14+// part of the undo state (regionState, region.go), so adding, editing,15+// moving and deleting cells undo as any change does. What running a cell16+// left, its output, isn't: a run can't be taken back, as a linked file's17+// rows can't. Outputs are kept by the cell's ID in the workbook, and18+// saved with it up to the caps (notebookfile.go).19+20+// notebookState is the workbook's side of its notebooks: the outputs,21+// and the counters that make IDs and Seqs.22+type notebookState struct {23+ outputs map[int]*notebook.Output24+ lastID int25+ lastSeq int26+ // changed counts changes to outputs, which the modified flag27+ // follows as it follows the undo history.28+ changed int29+ caps notebook.Caps30+ // notes say what opening the file did that the user should know:31+ // the notebook sheets it converted.32+ notes []string33+}34+35+// IsNotebook reports whether the sheet is a notebook tab.36+func (s *Sheet) IsNotebook() bool { return s.regions.notebook }37+38+// MakeNotebook turns an empty sheet into a notebook tab, without an undo39+// step, as 012 nu does with a new file's sheet.40+func (s *Sheet) MakeNotebook() { s.regions.notebook = true }41+42+// AddNotebook adds a notebook tab named name ("" for Notebook, then43+// Notebook 2 and so on) after the sheet after, as one undo step.44+func (w *Workbook) AddNotebook(name string, after *Sheet) (*Sheet, error) {45+ if name == "" {46+ name = w.freeName("Notebook")47+ }48+ if err := w.checkName(nil, name); err != nil {49+ return nil, err50+ }51+ s := w.newSheet(name)52+ s.regions.notebook = true53+ at := len(w.sheets)54+ if i := w.Index(after); i >= 0 {55+ at = i + 156+ }57+ w.change(s, "insert notebook "+name, Rect{}, func() {58+ w.recordSheets()59+ w.insert(s, at)60+ })61+ return s, nil62+}63+64+// Notebook is the workbook's first notebook tab, or nil.65+func (w *Workbook) Notebook() *Sheet {66+ for _, s := range w.sheets {67+ if s.IsNotebook() {68+ return s69+ }70+ }71+ return nil72+}73+74+// NotebookCells returns a notebook tab's cells.75+func (s *Sheet) NotebookCells() []notebook.Cell { return slices.Clone(s.regions.cells) }76+77+// SetNotebookCells replaces a notebook tab's cells as one undo step78+// described by label ("add cell"). Cells without an ID are given one.79+func (s *Sheet) SetNotebookCells(label string, cells []notebook.Cell) {80+ cells = slices.Clone(cells)81+ for i := range cells {82+ if cells[i].ID == 0 {83+ cells[i].ID = s.wb.NewCellID()84+ }85+ }86+ if slices.Equal(cells, s.regions.cells) {87+ return88+ }89+ s.change(label, Rect{}, func() {90+ st := s.regionsCopy()91+ st.cells = cells92+ s.putRegions(st)93+ })94+}95+96+// NewCellID is an ID no cell of the workbook has had.97+func (w *Workbook) NewCellID() int {98+ w.nb.lastID++99+ return w.nb.lastID100+}101+102+// Reactive reports whether the notebook runs the cells reading a cell103+// again whenever it runs.104+func (s *Sheet) Reactive() bool { return s.regions.reactive }105+106+// SetReactive turns the notebook's reactive setting on or off, as one107+// undo step.108+func (s *Sheet) SetReactive(on bool) {109+ if s.regions.reactive == on {110+ return111+ }112+ s.change("reactive notebook", Rect{}, func() {113+ st := s.regionsCopy()114+ st.reactive = on115+ s.putRegions(st)116+ })117+}118+119+// Output is what the cell with the ID id left when it last ran, or nil.120+func (w *Workbook) Output(id int) *notebook.Output { return w.nb.outputs[id] }121+122+// SetOutput keeps o as what the cell with the ID id left, nil for123+// nothing, giving it a Seq of its own. It isn't an undo step.124+func (w *Workbook) SetOutput(id int, o *notebook.Output) {125+ w.nb.changed++126+ if o == nil {127+ delete(w.nb.outputs, id)128+ return129+ }130+ cp := *o131+ w.nb.lastSeq++132+ cp.Seq = w.nb.lastSeq133+ if w.nb.outputs == nil {134+ w.nb.outputs = map[int]*notebook.Output{}135+ }136+ w.nb.outputs[id] = &cp137+}138+139+// OutputsChanged counts changes to outputs, for the modified flag.140+func (w *Workbook) OutputsChanged() int { return w.nb.changed }141+142+// SetOutputCaps sets how much of the outputs a file keeps.143+func (w *Workbook) SetOutputCaps(c notebook.Caps) { w.nb.caps = c }144+145+// OutputCaps are how much of the outputs a file keeps.146+func (w *Workbook) OutputCaps() notebook.Caps {147+ if w.nb.caps == (notebook.Caps{}) {148+ return notebook.DefaultCaps149+ }150+ return w.nb.caps151+}152+153+// LoadNotes say what opening the file changed that the user should154+// know, such as notebook sheets converted.155+func (w *Workbook) LoadNotes() []string { return slices.Clone(w.nb.notes) }156+157+// cellNamed reports whether a notebook's cell is named name, ignoring158+// case.159+func (w *Workbook) cellNamed(name string) bool {160+ for _, s := range w.sheets {161+ for _, c := range s.regions.cells {162+ if strings.EqualFold(c.Name(), name) {163+ return true164+ }165+ }166+ }167+ return false168+}169+170+// hasNotebook reports whether the workbook has a notebook tab.171+func (w *Workbook) hasNotebook() bool { return w.Notebook() != nil }172+173+// nextNotebookCellName is a name for a cell no cell and no region has:174+// cell1, cell2 and so on.175+func (w *Workbook) nextNotebookCellName() string {176+ for n := 1; ; n++ {177+ name := "cell" + strconv.Itoa(n)178+ if w.checkRegionName(name, false) == nil {179+ return name180+ }181+ }182+}183+184+// FreeCellName is a name like base that no cell and no region of the185+// workbook has: base, then base_2 and so on; cell1 and on for "".186+func (w *Workbook) FreeCellName(base string) string {187+ if base == "" || ValidRegionName(base) != nil {188+ return w.nextNotebookCellName()189+ }190+ name := base191+ for n := 2; w.checkRegionName(name, false) != nil; n++ {192+ name = base + "_" + strconv.Itoa(n)193+ }194+ return name195+}196+197+// StaleOutputs names the outputs sent to sheets that are to be sent198+// again: new, put back by undo, moved, or no longer blocked.199+func (w *Workbook) StaleOutputs() []string {200+ var out []string201+ for _, s := range w.sheets {202+ for _, r := range s.regions.list {203+ if me := s.regionMeta[nameKey(r.Name)]; r.Output && (me == nil || me.stale || me.reread) {204+ out = append(out, r.Name)205+ }206+ }207+ }208+ return out209+}210+211+// RenameRegion gives a sent output its cell's new name, as one undo212+// step; it is sent again.213+func (w *Workbook) RenameRegion(old, name string) error {214+ s, r, ok := w.Region(old)215+ if !ok {216+ return ErrNoRegion217+ }218+ if err := w.checkRegionName(name, r.Output); err != nil && nameKey(old) != nameKey(name) {219+ return err220+ }221+ k := nameKey(r.Name)222+ s.change("rename "+r.Name, s.covered(r), func() {223+ st := s.regionsCopy()224+ st.list[s.regionIndex(k)].Name = name225+ s.putRegions(st)226+ })227+ s.meta(nameKey(name)).stale = true228+ return nil229+}
A internal/sheet/notebookfile.go +201 -0
@@ -0,0 +1,201 @@
1+package sheet2+3+import (4+ "bufio"5+ "encoding/json"6+ "fmt"7+ "strings"8+9+ "github.com/FelineStateMachine/012/internal/notebook"10+)11+12+// A notebook tab in the file: a sheet marked "tab": "notebook", its13+// cells one per line, each with its output as NUON text when the caps14+// (OutputCaps) keep it, or "unsaved" when they don't. None of it needs a15+// version: earlier builds ignore the fields and open the tab as an16+// empty sheet.17+//18+// "tab": "notebook",19+// "reactive": true,20+// "notebookCells": [21+// {"kind": "note", "source": "# Sales"},22+// {"source": "sales = open sales.csv", "output": "[[Region, Units]; [North, 120]]"},23+// {"source": "$sales | where Units > 500", "error": "Column not found", "detail": "help: ..."},24+// {"source": "ls **/*", "unsaved": true}25+// ]26+type fileNotebookCell struct {27+ Kind string `json:"kind,omitempty"`28+ Source string `json:"source"`29+ Output string `json:"output,omitempty"`30+ Error string `json:"error,omitempty"`31+ Detail string `json:"detail,omitempty"`32+ Note string `json:"note,omitempty"`33+ Unsaved bool `json:"unsaved,omitempty"`34+}35+36+// notebookTab is what "tab" says of a notebook.37+const notebookTab = "notebook"38+39+// maxSavedError caps what a file keeps of a failed run's message.40+const maxSavedError = 4 << 1041+42+// writeNotebook writes a notebook tab's fields.43+func (s *Sheet) writeNotebook(b *bufio.Writer, indent string) error {44+ if !s.regions.notebook {45+ return nil46+ }47+ b.WriteString(",\n" + indent + `"tab": "notebook"`)48+ if s.regions.reactive {49+ b.WriteString(",\n" + indent + `"reactive": true`)50+ }51+ if len(s.regions.cells) == 0 {52+ return nil53+ }54+ w := s.wb55+ kept := notebook.Kept(s.regions.cells, w.Output, w.OutputCaps())56+ b.WriteString(",\n" + indent + `"notebookCells": [`)57+ for i, c := range s.regions.cells {58+ fc := fileNotebookCell{Source: c.Source}59+ if c.Kind == notebook.Note {60+ fc.Kind = c.Kind.String()61+ }62+ if o := w.Output(c.ID); o != nil {63+ fc.Error, fc.Detail, fc.Note = clip(o.Err), clip(o.Detail), clip(o.Note)64+ switch {65+ case kept[c.ID]:66+ fc.Output = string(o.NUON)67+ case o.NUON != nil || o.Unsaved:68+ fc.Unsaved = true69+ }70+ }71+ raw, err := json.Marshal(fc)72+ if err != nil {73+ return err74+ }75+ if i > 0 {76+ b.WriteByte(',')77+ }78+ fmt.Fprintf(b, "\n%s %s", indent, raw)79+ }80+ b.WriteString("\n" + indent + "]")81+ return nil82+}83+84+func clip(s string) string {85+ if len(s) > maxSavedError {86+ return strings.ToValidUTF8(s[:maxSavedError], "") + "…"87+ }88+ return s89+}90+91+// UnsavedOutputs names the cells whose outputs a file written now would92+// leave out, over the caps.93+func (w *Workbook) UnsavedOutputs() []string {94+ var out []string95+ for _, s := range w.sheets {96+ kept := notebook.Kept(s.regions.cells, w.Output, w.OutputCaps())97+ for i, c := range s.regions.cells {98+ if o := w.Output(c.ID); o != nil && o.NUON != nil && !kept[c.ID] {99+ name := c.Name()100+ if name == "" {101+ name = "cell " + fmt.Sprint(i+1)102+ }103+ out = append(out, name)104+ }105+ }106+ }107+ return out108+}109+110+// readNotebook reads a notebook tab's cells and their outputs, as its111+// cells would have left them.112+func (s *Sheet) readNotebook(f fileSheet) error {113+ if f.Tab != notebookTab {114+ if len(f.NotebookCells) > 0 {115+ return fmt.Errorf("notebook cells on a sheet that isn't a notebook")116+ }117+ return nil118+ }119+ w := s.wb120+ s.regions.notebook, s.regions.reactive = true, f.Reactive121+ outputs := map[int]*notebook.Output{}122+ for i, fc := range f.NotebookCells {123+ kind, ok := notebook.ParseKind(fc.Kind)124+ if !ok {125+ return fmt.Errorf("cell %d: invalid kind %q", i+1, fc.Kind)126+ }127+ c := notebook.Cell{ID: w.NewCellID(), Kind: kind, Source: fc.Source}128+ s.regions.cells = append(s.regions.cells, c)129+ if fc.Output == "" && fc.Error == "" && !fc.Unsaved {130+ continue131+ }132+ o := &notebook.Output{Err: fc.Error, Detail: fc.Detail, Note: fc.Note, Unsaved: fc.Unsaved}133+ if fc.Output != "" && !fc.Unsaved {134+ o.NUON = []byte(fc.Output)135+ }136+ w.SetOutput(c.ID, o)137+ outputs[c.ID] = w.Output(c.ID)138+ }139+ notebook.Settle(s.regions.cells, outputs)140+ return nil141+}142+143+// oldRegion is a command region of a notebook sheet as earlier builds144+// wrote it, which opening the file converts: its command becomes a145+// code cell of the workbook's notebook tab, and its table stays on the146+// sheet as the cell's output sent there.147+type oldRegion struct {148+ s *Sheet149+ fr fileRegion150+}151+152+// convertOld turns the command regions of a file into code cells of a153+// notebook tab, made after the last sheet that held them, and their154+// tables into sent outputs where the tables were, saying so in the load155+// notes.156+func (w *Workbook) convertOld(old []oldRegion) {157+ if len(old) == 0 {158+ return159+ }160+ nb := w.Notebook()161+ if nb == nil {162+ nb = w.newSheet(w.freeName("Notebook"))163+ nb.regions.notebook = true164+ w.insert(nb, w.Index(old[len(old)-1].s)+1)165+ }166+ var from []string167+ for _, o := range old {168+ fr := o.fr169+ src := fr.Command170+ if fr.Input != "" {171+ src = "$sheet." + quoteRef(fr.Input) + " | do {\n" + src + "\n}"172+ }173+ nb.regions.cells = append(nb.regions.cells, notebook.Cell{ID: w.NewCellID(), Source: fr.Name + " = " + src})174+ at, _ := ParseAddr(fr.At)175+ at.Row = min(at.Row+1, MaxRows-1) // the table was under the label line176+ r := Region{Name: fr.Name, At: at, Output: true}177+ o.s.regions.list = append(o.s.regions.list, r)178+ o.s.meta(nameKey(r.Name)).stale = true179+ o.s.regionsStale = true180+ if name := o.s.name; len(from) == 0 || from[len(from)-1] != name {181+ from = append(from, name)182+ }183+ }184+ w.nb.notes = append(w.nb.notes, fmt.Sprintf("%s's shell commands are now code cells of %s; their tables stay where they were, sent from the cells, and show once the cells run",185+ strings.Join(from, ", "), nb.name))186+}187+188+// quoteRef writes Sheet!A1:B2 as $sheet. reads it: the sheet's name in189+// quotes when it isn't a plain word.190+func quoteRef(ref string) string {191+ sheet, rest := SplitSheet(ref)192+ if sheet == "" {193+ return rest194+ }195+ for i := range len(sheet) {196+ if c := sheet[i]; !isLetter(c) && !isDigit(c) && c != '_' {197+ return "'" + strings.ReplaceAll(sheet, "'", "''") + "'!" + rest198+ }199+ }200+ return sheet + "!" + rest201+}
M internal/sheet/region.go +83 -260
@@ -3,201 +3,107 @@ package sheet
33 import (44 "errors"55 "fmt"6- "maps"76 "slices"8- "strconv"9- "strings"7+8+ "github.com/FelineStateMachine/012/internal/notebook"109 )1110 1211 // Regions are blocks of cells whose values come from outside the13-// engine, of two kinds: a command region (a nushell command's output on14-// a notebook sheet) and a linked file (a file followed as it grows or is15-// rewritten, linked.go). A region is an anchor (At: a command region's16-// label line, a linked file's first cell) and a definition saying where17-// its table comes from (Command, opaque to the engine, or File). Like a18-// spill, its cells are derived: written by the engine outside the undo19-// history, refused by Set, values to formulas, copies and exports,20-// formatting kept, never saved; the file keeps the definitions only.12+// engine, of two kinds: a linked file (a file followed as it grows or is13+// rewritten, linked.go) and a notebook cell's output sent to a sheet14+// (Output). A region is an anchor (At, its table's first cell) and a15+// definition saying where its table comes from. Like a spill, its cells16+// are derived: written by the engine outside the undo history, refused17+// by Set, values to formulas, copies and exports, formatting kept, never18+// saved; the file keeps the definitions only.2119 //2220 // Both kinds are one indexed set, the sheet's regionState, and their2321 // cells have one write path: rows arrive as live operations (LiveOp,24-// live.go), written by writeTable (regionwrite.go). What differs is what25-// undo keeps, a policy per kind:26-//27-// - A command region's table (RegionData, handed over by ShowRegion)28-// is part of the undo state, so undoing a run shows the table it29-// replaced: the run was the user's action, and its output can't be30-// had again (the command may read what has changed since). The31-// engine writes the table again from it whenever the region moves.32-// - A linked file's rows are not: the file is where they come from,33-// and undo can't bring back what the file held. Its rows live only34-// in its cells; when undo puts the region back, or it moves, it is35-// emptied and marked stale, and the UI reads the file again.36-//37-// The definitions of both are undo steps.22+// live.go), written by writeTable (regionwrite.go). Their rows are never23+// part of the undo state: the source is where they come from, and undo24+// can't bring back what it held. When undo puts a region back, or it25+// moves, it is emptied and marked stale, and the UI sends its rows26+// again: the file read again, or the cell's output as it stands.3827 //39-// On a notebook sheet regions stack in run order, a gap row between40-// them: a new one goes below everything on the sheet, and a table that41-// grows or shrinks inserts or deletes rows under it, so what's below42-// moves as rows inserted by hand would move it.28+// The definitions are undo steps. So are a notebook tab's cells, which29+// the same state holds (notebook.go).4330 //44-// In formulas a region is a name: nu. and its own (=SUM(nu.r1)), which45-// stands for its table, header row included.31+// In formulas a region is a name: nu. and its own (=SUM(nu.sales)),32+// which stands for its table, header row included.4633 4734 // Region is a region's definition.4835 type Region struct {49- // Name identifies the region in the workbook, ignoring case: r1, or50- // a name the user gave. Commands read other regions as $name.36+ // Name identifies the region in the workbook, ignoring case: a37+ // linked file's is made from the file's name, a sent output's is its38+ // cell's. Notebook cells read linked files as $name.5139 Name string52- // Command is what fills the region: for a notebook, a nushell53- // pipeline.54- Command string55- // At is the label line's first cell; the table starts below it.40+ // At is the table's first cell.5641 At Addr57- // Rows and Cols are the table's size as last shown, header row58- // included, so a region not run yet keeps its place.59- Rows, Cols int60- // Deps are the regions the command reads, in the order it names61- // them.62- Deps []string63- // Input is the range the command reads as $in, qualified with its64- // sheet ("Sheet1!A1:C9"), or "".65- Input string66- // Sort orders the table's rows below its header, by columns counted67- // from the region's first.68- Sort []SortKey69- // File is the file a linked file follows; zero for a command region.42+ // File is the file a linked file follows; zero for a sent output.7043 File LinkSource44+ // Output is set on a notebook cell's output sent to the sheet: the45+ // output of the cell of the region's name.46+ Output bool7147 }7248 73-// Linked reports whether the region is a linked file rather than a74-// command region.49+// Linked reports whether the region is a linked file.7550 func (r Region) Linked() bool { return r.File.Path != "" }7651 77-// labelled reports whether the region has a label line above its table:78-// a command region does.79-func (r Region) labelled() bool { return !r.Linked() }80-8152 // regionState is what a sheet keeps of its regions in the undo history:82-// the definitions in run order and the data each shows, by name key.83-// Both are replaced whole, never changed in place.53+// the definitions in the order they were made and, on a notebook tab,54+// its cells. It is replaced whole, never changed in place.8455 type regionState struct {8556 notebook bool8657 list []Region87- data map[string]*RegionData58+ cells []notebook.Cell59+ reactive bool8860 }8961 90-// regionMeta is what isn't undone: what the region's label says it's91-// doing, the cells written last and what the rows written made of it.62+// regionMeta is what isn't undone: the cells written last and what the63+// rows written made of the region.9264 type regionMeta struct {93- status string9465 written Rect9566 has bool // written holds something9667 why string // why the table isn't shown, or ""9768 need Rect // the cells the table needs while it isn't shown9869 // rows and cols are the table's size as written, header included.9970 rows, cols int100- liveMeta // a linked file's: see linked.go71+ liveMeta // how its rows arrive: see linked.go10172 }10273 10374 // Errors of regions.10475 var (105- // ErrRegionEdit is what typing into a region's cell says.106- ErrRegionEdit = errors.New("That cell is part of a shell region: change its command, or Freeze it to edit its values")76+ // ErrOutputEdit is what typing into a sent output's cell says.77+ ErrOutputEdit = errors.New("That cell shows a notebook cell's output: change the cell, or freeze the region to edit its values")10778 // ErrNoRegion is returned for a region the workbook doesn't hold:10879 // deleted, unlinked, undone, or on a sheet deleted.109- ErrNoRegion = errors.New("There's no region by that name")110- errFileRegion = errors.New("That region is a linked file, which has no command")80+ ErrNoRegion = errors.New("There's no region by that name")11181 )11282 11383 // regionPrefix starts a region's name in formulas.11484 const regionPrefix = "nu."11585 116-// FormulaName is how formulas name the region: nu.r1.86+// FormulaName is how formulas name the region: nu.sales.11787 func (r Region) FormulaName() string { return regionPrefix + r.Name }11888 119-// reservedRegionNames are nushell's own variables.120-var reservedRegionNames = []string{"in", "env", "nu", "it"}121-12289 // ValidRegionName checks that name can name a region: a nushell123-// variable name of letters, digits and _, not starting with a digit.124-func ValidRegionName(name string) error {125- switch {126- case name == "":127- return errors.New("Enter a name")128- case len(name) > 64:129- return errors.New("A region's name can be at most 64 characters")130- case isDigit(name[0]):131- return errors.New("A region's name can't start with a digit")132- case slices.Contains(reservedRegionNames, strings.ToLower(name)):133- return fmt.Errorf("$%s is nushell's own", name)134- }135- for i := range len(name) {136- if c := name[i]; !isLetter(c) && !isDigit(c) && c != '_' {137- return errors.New("A region's name can only have letters, digits and _")138- }139- }140- return nil141-}142-143-// Notebook reports whether the sheet is a notebook: its regions stack144-// in run order.145-func (s *Sheet) Notebook() bool { return s.regions.notebook }146-147-// MakeNotebook turns an empty sheet into a notebook, as a loader does,148-// without an undo step.149-func (s *Sheet) MakeNotebook() { s.regions.notebook = true }150-151-// AddNotebook adds a notebook sheet named name ("" for the next Shell152-// N) after the active one, as one undo step.153-func (w *Workbook) AddNotebook(name string, after *Sheet) (*Sheet, error) {154- if name == "" {155- name = w.NextNotebookName()156- }157- if err := w.checkName(nil, name); err != nil {158- return nil, err159- }160- s := w.newSheet(name)161- s.regions.notebook = true162- at := len(w.sheets)163- if i := w.Index(after); i >= 0 {164- at = i + 1165- }166- w.change(s, "insert sheet "+name, Rect{}, func() {167- w.recordSheets()168- w.insert(s, at)169- })170- return s, nil171-}172-173-// NextNotebookName is Shell 1, then 2 and so on.174-func (w *Workbook) NextNotebookName() string {175- for n := 1; ; n++ {176- if name := "Shell " + strconv.Itoa(n); w.Lookup(name) == nil {177- return name178- }179- }180-}90+// variable's name of letters, digits and _, not starting with a digit,91+// as a notebook cell's name is.92+func ValidRegionName(name string) error { return notebook.ValidName(name) }18193 18294 // HasRegions reports whether the sheet has regions, so what draws it can18395 // skip looking for them.18496 func (s *Sheet) HasRegions() bool { return len(s.regions.list) > 0 }18597 186-// Regions returns the sheet's regions in run order.187-func (s *Sheet) Regions() []Region {188- out := slices.Clone(s.regions.list)189- for i := range out {190- out[i].Deps, out[i].Sort = slices.Clone(out[i].Deps), slices.Clone(out[i].Sort)191- }192- return out193-}98+// Regions returns the sheet's regions in the order they were made.99+func (s *Sheet) Regions() []Region { return slices.Clone(s.regions.list) }194100 195101 // Region finds a region by name, ignoring case, on any sheet.196102 func (w *Workbook) Region(name string) (*Sheet, Region, bool) {197103 k := nameKey(name)198104 for _, s := range w.sheets {199105 if i := s.regionIndex(k); i >= 0 {200- return s, s.Regions()[i], true106+ return s, s.regions.list[i], true201107 }202108 }203109 return nil, Region{}, false
@@ -207,19 +113,20 @@ func (s *Sheet) regionIndex(k string) int {
207113 return slices.IndexFunc(s.regions.list, func(r Region) bool { return nameKey(r.Name) == k })208114 }209115 210-// NextRegionName is the first of r1, r2 and so on that no region or211-// named range has.212-func (w *Workbook) NextRegionName() string {213- for n := 1; ; n++ {214- name := "r" + strconv.Itoa(n)215- if w.checkRegionName(name) == nil {216- return name217- }116+// allRegions returns every region of the workbook's sheets, sheet by117+// sheet.118+func (w *Workbook) allRegions() []Region {119+ var out []Region120+ for _, s := range w.sheets {121+ out = append(out, s.regions.list...)218122 }123+ return out219124 }220125 221-// checkRegionName reports why name can't name a new region.222-func (w *Workbook) checkRegionName(name string) error {126+// checkRegionName reports why name can't name a new region. A sent127+// output (output set) takes its cell's name, which a notebook's cell128+// has; any other region's name is one no cell has.129+func (w *Workbook) checkRegionName(name string, output bool) error {223130 if err := ValidRegionName(name); err != nil {224131 return err225132 }
@@ -229,84 +136,30 @@ func (w *Workbook) checkRegionName(name string) error {
229136 if n, ok := w.LookupName(regionPrefix + name); ok {230137 return fmt.Errorf("%s already names %s", n.Name, n.Ref())231138 }232- return nil233-}234-235-// RegionShown reports whether the region has data to show: it has run236-// since the file was opened.237-func (s *Sheet) RegionShown(name string) bool {238- return s.regions.data[nameKey(name)] != nil239-}240-241-// RegionStatus is what the region's label says it's doing: "Running…",242-// "not run", why its table can't be shown, or "".243-func (s *Sheet) RegionStatus(name string) string {244- k := nameKey(name)245- if me := s.regionMeta[k]; me != nil {246- switch {247- case me.status != "":248- return me.status249- case me.why != "":250- return me.why251- }139+ if !output && w.cellNamed(name) {140+ return fmt.Errorf("A notebook cell is named %s", name)252141 }253- switch d := s.regions.data[k]; {254- case d == nil:255- return "not run"256- case d.Rows == 0:257- return "no rows"258- }259- return ""260-}261-262-// RegionNote is what the data shown left out, or "".263-func (s *Sheet) RegionNote(name string) string {264- if d := s.regions.data[nameKey(name)]; d != nil {265- return d.Note266- }267- return ""142+ return nil268143 }269144 270-// AddRegion adds a region to the sheet as one undo step. On a notebook271-// its label goes below everything on the sheet, a row left free, and r.At272-// is ignored.145+// AddRegion adds a region to the sheet as one undo step. It starts146+// empty and stale: the UI sends its rows.273147 func (s *Sheet) AddRegion(r Region) error {274- if err := s.wb.checkRegionName(r.Name); err != nil {148+ if err := s.wb.checkRegionName(r.Name, r.Output); err != nil {275149 return err276150 }277- if err := s.wb.checkDeps(r.Name, r.Deps); err != nil {278- return err151+ if !r.At.Valid() || s.cells.filledAt(r.At) || s.InPivot(Rect{From: r.At, To: r.At}) {152+ return errLinkOver279153 }280- if s.regions.notebook {281- r.At = Addr{Row: s.nextRegionRow()}154+ if _, ok := s.RegionAt(r.At); ok {155+ return errLinkOver282156 }283- r.Deps, r.Sort = slices.Clone(r.Deps), slices.Clone(r.Sort)284157 s.change("add "+r.Name, Rect{From: r.At, To: r.At}, func() {285158 st := s.regionsCopy()286159 st.list = append(st.list, r)287160 s.putRegions(st)288161 })289- return nil290-}291-292-// EditRegion changes a region's command, what it reads and its input,293-// as one undo step. The table stays until the command runs again.294-func (s *Sheet) EditRegion(name, command string, deps []string, input string) error {295- i := s.regionIndex(nameKey(name))296- if i < 0 {297- return ErrNoRegion298- }299- if s.regions.list[i].Linked() {300- return errFileRegion301- }302- if err := s.wb.checkDeps(s.regions.list[i].Name, deps); err != nil {303- return err304- }305- s.change("edit "+s.regions.list[i].Name, Rect{From: s.regions.list[i].At, To: s.regions.list[i].At}, func() {306- st := s.regionsCopy()307- st.list[i].Command, st.list[i].Deps, st.list[i].Input = command, slices.Clone(deps), input308- s.putRegions(st)309- })162+ s.meta(nameKey(r.Name)).stale = true310163 return nil311164 }312165
@@ -318,10 +171,9 @@ func (s *Sheet) DeleteRegion(name string) error {
318171 return ErrNoRegion319172 }320173 r := s.regions.list[i]321- s.change("delete "+r.Name, s.regionArea(r), func() {174+ s.change("delete "+r.Name, s.covered(r), func() {322175 st := s.regionsCopy()323176 st.list = slices.Delete(st.list, i, i+1)324- delete(st.data, k)325177 s.putRegions(st)326178 })327179 return nil
@@ -350,7 +202,6 @@ func (s *Sheet) FreezeRegion(name string) error {
350202 // The region goes first, so its cells take the values placed.351203 st := s.regionsCopy()352204 st.list = slices.Delete(st.list, i, i+1)353- delete(st.data, k)354205 s.putRegions(st)355206 for j, a := range at {356207 s.place(a, cells[j])
@@ -369,49 +220,17 @@ func frozen(c *Cell) *Cell {
369220 return p.plain().withNote(c.Note)370221 }371222 372-// ShowRegion shows d in the region, as one undo step: the table grows or373-// shrinks, and on a notebook the rows under it move with it.374-func (s *Sheet) ShowRegion(name string, d *RegionData) error {375- k := nameKey(name)376- i := s.regionIndex(k)377- if i < 0 {378- return ErrNoRegion379- }380- if d == nil {381- d = &RegionData{}382- }383- r := s.regions.list[i]384- if r.Linked() {385- return errFileRegion386- }387- s.change("run "+r.Name, s.regionArea(r), func() {388- if s.regions.notebook {389- s.fitRegion(k, d.Rows)390- }391- st := s.regionsCopy()392- i := s.regionIndex(k) // fitting may have moved it393- if i < 0 {394- return395- }396- st.list[i].Rows, st.list[i].Cols = d.Rows, d.Cols397- st.data[k] = d398- s.putRegions(st)399- })400- return nil401-}402-403223 // regionsCopy is a copy of the sheet's regions to change and put back.404224 func (s *Sheet) regionsCopy() regionState {405- return regionState{notebook: s.regions.notebook, list: slices.Clone(s.regions.list), data: maps.Clone(s.regions.data)}225+ st := s.regions226+ st.list = slices.Clone(st.list)227+ return st406228 }407229 408230 // putRegions replaces the sheet's regions, recording them for undo, and409231 // has their cells written again.410232 func (s *Sheet) putRegions(st regionState) {411233 s.recordRegions()412- if st.data == nil {413- st.data = map[string]*RegionData{}414- }415234 s.regions = st416235 s.regionsStale = true417236 }
@@ -429,15 +248,19 @@ func (s *Sheet) recordRegions() {
429248 // sameRegions reports whether two states of a sheet's regions are the430249 // same.431250 func sameRegions(a, b regionState) bool {432- if a.notebook != b.notebook || len(a.list) != len(b.list) || !maps.Equal(a.data, b.data) {433- return false434- }435- for i := range a.list {436- x, y := a.list[i], b.list[i]437- if x.Name != y.Name || x.Command != y.Command || x.At != y.At || x.Rows != y.Rows || x.Cols != y.Cols ||438- x.Input != y.Input || x.File != y.File || !slices.Equal(x.Deps, y.Deps) || !slices.Equal(x.Sort, y.Sort) {439- return false251+ return a.notebook == b.notebook && a.reactive == b.reactive && slices.Equal(a.list, b.list) && slices.Equal(a.cells, b.cells)252+}253+254+// putRegionsBack puts back a sheet's regions from an undo step. A255+// region the step brings back, or moves, is emptied and fed again once256+// the change ends: its rows aren't part of the undo state, so the cells257+// undo put there may not be its source's.258+func (s *Sheet) putRegionsBack(st regionState) {259+ for _, r := range st.list {260+ i := s.regionIndex(nameKey(r.Name))261+ if i < 0 || s.regions.list[i] != r {262+ s.meta(nameKey(r.Name)).reread = true440263 }441264 }442- return true265+ s.regions, s.regionsStale = st, true443266 }
M internal/sheet/region_test.go +190 -141
@@ -1,189 +1,238 @@
11 package sheet22 33 import (4- "slices"4+ "bytes"5+ "strings"56 "testing"7+8+ "github.com/FelineStateMachine/012/internal/notebook"69 )710 8-// table makes region data of text rows: the first is the header, and9-// cells that read as numbers are numbers.10-func table(rows ...[]string) *RegionData {11- d := &RegionData{Rows: len(rows)}12- for _, r := range rows {13- d.Cols = max(d.Cols, len(r))14- }15- d.Values = make([]Value, d.Rows*d.Cols)16- for i, r := range rows {17- for j, text := range r {18- v, _, ok := ParseValue(text)19- switch {20- case text == "":21- continue22- case ok && i > 0:23- d.Values[i*d.Cols+j] = Value{Kind: Number, Num: v}24- default:25- d.Values[i*d.Cols+j] = Value{Kind: Text, Str: text}26- }11+// sent adds a notebook cell named name to a new notebook tab and sends12+// its output to s at a, returning the notebook.13+func sent(t *testing.T, s *Sheet, name, a string) *Sheet {14+ t.Helper()15+ w := s.Book()16+ nb := w.Notebook()17+ if nb == nil {18+ var err error19+ if nb, err = w.AddNotebook("", s); err != nil {20+ t.Fatal(err)2721 }2822 }29- return d23+ nb.SetNotebookCells("add cell", append(nb.NotebookCells(), notebook.Cell{Source: name + " = ls"}))24+ if err := s.AddRegion(Region{Name: name, At: at(a), Output: true}); err != nil {25+ t.Fatal(err)26+ }27+ return nb3028 }3129 32-func notebook(t *testing.T) *Sheet {33- t.Helper()30+func TestSentOutputFollowsRuns(t *testing.T) {3431 s := New()35- s.MakeNotebook()36- return s32+ w := s.Book()33+ sent(t, s, "files", "B2")34+ other, _ := w.AddSheet("Sheet2", 1)35+ other.Set(at("A1"), "=SUM(nu.files)")36+ wantShown(t, other, map[string]string{"A1": "#REF!"}) // not run yet37+ apply(t, s, LiveOp{Region: "files", Reset: true, Header: liveRow("n", "size"), Rows: []LiveRow{liveRow("a", "1"), liveRow("b", "2")}})38+ wantShown(t, s, map[string]string{"B2": "n", "C3": "1", "B4": "b", "B1": ""})39+ wantShown(t, other, map[string]string{"A1": "3"})40+ // A run that gives fewer rows clears the rest.41+ apply(t, s, LiveOp{Region: "files", Reset: true, Header: liveRow("n", "size"), Rows: []LiveRow{liveRow("c", "10")}})42+ wantShown(t, s, map[string]string{"B3": "c", "B4": ""})43+ wantShown(t, other, map[string]string{"A1": "10"})44+ if err := s.Set(at("B3"), "x"); err != ErrOutputEdit {45+ t.Errorf("typing over an output: %v", err)46+ }47+ if w.UndoLabel() != "edit A1" {48+ t.Errorf("rows arriving made an undo step: %q", w.UndoLabel())49+ }3750 }3851 39-func addShown(t *testing.T, s *Sheet, name, command string, d *RegionData) {40- t.Helper()41- if err := s.AddRegion(Region{Name: name, Command: command, Deps: s.Book().RegionDeps(command)}); err != nil {52+func TestSentOutputNames(t *testing.T) {53+ s := New()54+ w := s.Book()55+ sent(t, s, "files", "A1")56+ if err := s.AddRegion(Region{Name: "files", At: at("D1"), Output: true}); err == nil {57+ t.Error("two regions share a name")58+ }59+ if _, err := s.AddLinked(at("F1"), LinkSource{Path: "files.csv"}); err != nil {4260 t.Fatal(err)4361 }44- if err := s.ShowRegion(name, d); err != nil {62+ if _, r, _ := w.Region("files_2"); !r.Linked() {63+ t.Error("a linked file took a cell's name")64+ }65+ if got := w.FreeCellName("files"); got != "files_3" {66+ t.Errorf("a free name like files: %q", got)67+ }68+ if err := w.DefineName("nu.big", s, rect("A9")); err != nil {4569 t.Fatal(err)4670 }71+ if err := s.AddRegion(Region{Name: "big", At: at("H1"), Output: true}); err == nil {72+ t.Error("a region took a named range's name")73+ }4774 }4875 49-func TestRegionsStack(t *testing.T) {50- s := notebook(t)51- addShown(t, s, "r1", "ls", table([]string{"name", "size"}, []string{"a", "1"}, []string{"b", "2"}))52- wantShown(t, s, map[string]string{"A1": "r1 ls", "A2": "name", "B2": "size", "A3": "a", "B4": "2", "A5": ""})53- addShown(t, s, "r2", "$r1 | where size > 1", table([]string{"name", "size"}, []string{"b", "2"}))54- // A gap row, then r2's label.55- wantShown(t, s, map[string]string{"A5": "", "A6": "r2 $r1 | where size > 1", "A7": "name", "A8": "b"})56- if r, ok := s.RegionTable("r2"); !ok || r != rect("A7:B8") {57- t.Errorf("r2's table %v %v", r, ok)58- }59- // r1 growing pushes r2 down; shrinking pulls it back.60- if err := s.ShowRegion("r1", table([]string{"name", "size"}, []string{"a", "1"}, []string{"b", "2"}, []string{"c", "3"})); err != nil {76+// Undo puts a region back emptied and stale, for its source to send its77+// rows again; moving it with inserted rows does the same.78+func TestSentOutputUndoAndShift(t *testing.T) {79+ s := New()80+ w := s.Book()81+ sent(t, s, "t", "A2")82+ apply(t, s, LiveOp{Region: "t", Reset: true, Header: liveRow("n"), Rows: []LiveRow{liveRow("1")}})83+ if err := s.DeleteRegion("t"); err != nil {6184 t.Fatal(err)6285 }63- wantShown(t, s, map[string]string{"A5": "c", "A6": "", "A7": "r2 $r1 | where size > 1", "A9": "b"})64- s.ShowRegion("r1", table([]string{"name"}, []string{"a"}))65- wantShown(t, s, map[string]string{"A3": "a", "B2": "", "A4": "", "A5": "r2 $r1 | where size > 1", "A7": "b"})66- // Undo shows the table it replaced, with r2 where it was.67- s.Book().Undo()68- wantShown(t, s, map[string]string{"A5": "c", "A7": "r2 $r1 | where size > 1"})69- s.Book().Undo()70- s.Book().Undo() // r2's run71- wantShown(t, s, map[string]string{"A6": "r2 $r1 | where size > 1 not run", "A7": ""})72- s.Book().Undo() // r2 itself73- wantShown(t, s, map[string]string{"A6": ""})74-}75-76-func TestRegionGuards(t *testing.T) {77- s := notebook(t)78- addShown(t, s, "r1", "ls", table([]string{"n"}, []string{"1"}))79- for _, a := range []string{"A1", "A2", "A3"} {80- if err := s.Set(at(a), "x"); err != ErrRegionEdit {81- t.Errorf("Set %s: %v, want ErrRegionEdit", a, err)82- }86+ wantShown(t, s, map[string]string{"A2": "", "A3": ""})87+ w.Undo()88+ if !s.regionMeta[nameKey("t")].stale {89+ t.Error("the region undo put back isn't stale")8390 }84- if a, r, ok := s.InRegion(rect("A3:C9")); !ok || a != at("A3") || r.Name != "r1" {85- t.Errorf("InRegion = %v %v %v", a, r.Name, ok)91+ apply(t, s, LiveOp{Region: "t", Reset: true, Header: liveRow("n"), Rows: []LiveRow{liveRow("1")}})92+ s.InsertRows(0, 2)93+ if _, r, _ := w.Region("t"); r.At != at("A4") {94+ t.Errorf("after inserting rows the region is at %v", r.At)8695 }87- // Formatting stays, and the value with it.88- s.Batch(Change{Label: "bold"}, func() error {89- c := s.Cell(at("A3")).clone()90- c.Style.Bold = true91- s.place(at("A3"), c)92- return nil93- })94- if c := s.Cell(at("A3")); c == nil || !c.Style.Bold || c.Value.Num != 1 {95- t.Errorf("A3 after bold: %+v", c)96+ if !s.regionMeta[nameKey("t")].stale {97+ t.Error("the region moved isn't stale")9698 }97- // Saved: the definition, not the table.98- t2 := roundTrip(t, s)99- if !t2.Notebook() || len(t2.Regions()) != 1 || t2.RegionShown("r1") {100- t.Fatalf("read back: notebook %v, regions %+v", t2.Notebook(), t2.Regions())99+ if err := s.FreezeRegion("t"); err != nil {100+ t.Fatal(err)101101 }102- wantShown(t, t2, map[string]string{"A1": "r1 ls not run", "A3": ""})103- if !t2.Cell(at("A3")).Style.Bold {104- t.Error("the table's formatting wasn't kept")102+}103+104+func TestNotebookCellsUndo(t *testing.T) {105+ w := NewBook()106+ nb, err := w.AddNotebook("", w.Sheet(0))107+ if err != nil || nb.Name() != "Notebook" || !nb.IsNotebook() {108+ t.Fatalf("%v %v", nb, err)109+ }110+ nb.SetNotebookCells("add cell", []notebook.Cell{{Source: "ls"}})111+ cells := nb.NotebookCells()112+ if len(cells) != 1 || cells[0].ID == 0 {113+ t.Fatalf("cells %+v", cells)114+ }115+ nb.SetNotebookCells("edit cell 1", []notebook.Cell{{ID: cells[0].ID, Source: "files = ls"}})116+ w.Undo()117+ if got := nb.NotebookCells(); got[0].Source != "ls" {118+ t.Errorf("after undo: %+v", got)119+ }120+ nb.SetReactive(true)121+ w.Undo()122+ if nb.Reactive() {123+ t.Error("undo left the notebook reactive")124+ }125+ if w.Notebook() != nb {126+ t.Error("the workbook's notebook")105127 }106128 }107129 108-func TestRegionFormulaName(t *testing.T) {109- s := notebook(t)110- w := s.Book()111- other, _ := w.AddSheet("Sheet2", 1)112- if err := other.Set(at("A1"), "=SUM(nu.r1)"); err != nil {130+func writeBook(t *testing.T, w *Workbook) string {131+ t.Helper()132+ var b bytes.Buffer133+ if err := w.Write(&b); err != nil {113134 t.Fatal(err)114135 }115- wantShown(t, other, map[string]string{"A1": "#NAME?"})116- addShown(t, s, "r1", "ls", table([]string{"n"}, []string{"1"}, []string{"2"}))117- wantShown(t, other, map[string]string{"A1": "3"})118- s.ShowRegion("r1", table([]string{"n"}, []string{"5"}))119- wantShown(t, other, map[string]string{"A1": "5"})120- if err := w.DefineName("nu.r2", s, rect("A1")); err != nil {136+ return b.String()137+}138+139+func TestNotebookRoundTrip(t *testing.T) {140+ w := NewBook()141+ nb, _ := w.AddNotebook("", w.Sheet(0))142+ nb.SetNotebookCells("add cells", []notebook.Cell{143+ {Kind: notebook.Note, Source: "# Files"},144+ {Source: "files = ls"},145+ {Source: "$files | where size > 1kb"},146+ {Source: "nope"},147+ })148+ nb.SetReactive(true)149+ cells := nb.NotebookCells()150+ w.SetOutput(cells[1].ID, &notebook.Output{NUON: []byte(`[[name, size]; [a, 2kb]]`), Count: 1, Source: cells[1].Source})151+ w.SetOutput(cells[2].ID, &notebook.Output{NUON: []byte(strings.Repeat("x", 100)), Count: 2})152+ w.SetOutput(cells[3].ID, &notebook.Output{Err: "Command `nope` not found", Detail: "help: try ls"})153+ w.SetOutputCaps(notebook.Caps{Cell: 50, Total: 1000})154+ if got := w.UnsavedOutputs(); len(got) != 1 || got[0] != "cell 3" {155+ t.Errorf("unsaved %v", got)156+ }157+ text := writeBook(t, w)158+ for _, want := range []string{`"tab": "notebook"`, `"reactive": true`, `{"kind":"note","source":"# Files"}`,159+ `"output":"[[name, size]; [a, 2kb]]"`, `"unsaved":true`, `"error":"Command`} {160+ if !strings.Contains(text, want) {161+ t.Errorf("the file lacks %s:\n%s", want, text)162+ }163+ }164+ back, err := ReadBook(strings.NewReader(text))165+ if err != nil {121166 t.Fatal(err)122167 }123- if err := s.AddRegion(Region{Name: "r2", Command: "x"}); err == nil {124- t.Error("a region took a named range's name")168+ nb2 := back.Notebook()169+ if nb2 == nil || !nb2.Reactive() || nb2.Name() != "Notebook" {170+ t.Fatalf("read back %v", nb2)125171 }126-}127-128-func TestRegionGraph(t *testing.T) {129- s := notebook(t)130- w := s.Book()131- addShown(t, s, "r1", "ls", table([]string{"n"}))132- addShown(t, s, "r2", "$r1 | first", table([]string{"n"}))133- addShown(t, s, "big", "$r2 | append $r1", table([]string{"n"}))134- addShown(t, s, "r3", "$env.PATH | $in", table([]string{"n"}))135- if got := w.RefreshOrder("r1"); !slices.Equal(got, []string{"r1", "r2", "big"}) {136- t.Errorf("refresh r1: %v", got)172+ got := nb2.NotebookCells()173+ if len(got) != 4 || got[0].Kind != notebook.Note || got[1].Source != "files = ls" {174+ t.Fatalf("cells %+v", got)175+ }176+ if o := back.Output(got[1].ID); o == nil || string(o.NUON) != `[[name, size]; [a, 2kb]]` || o.Count != 0 {177+ t.Errorf("output of files: %+v", o)137178 }138- if got := w.RefreshOrder("r2"); !slices.Equal(got, []string{"r2", "big"}) {139- t.Errorf("refresh r2: %v", got)179+ if o := back.Output(got[2].ID); o == nil || !o.Unsaved || o.NUON != nil {180+ t.Errorf("the output over the cap: %+v", o)140181 }141- if got := w.RunOrder(); !slices.Equal(got, []string{"r1", "r2", "big", "r3"}) {142- t.Errorf("run all: %v", got)182+ if o := back.Output(got[3].ID); o == nil || o.Err == "" || o.Detail != "help: try ls" {183+ t.Errorf("the error: %+v", o)143184 }144- if err := s.EditRegion("r1", "$big", w.RegionDeps("$big"), ""); err == nil {145- t.Error("a cycle was accepted")185+ if st := notebook.Stale(got, back.Output); len(st) != 0 {186+ t.Errorf("stale on opening: %v", st)146187 }147- if err := s.EditRegion("r1", "$r1", w.RegionDeps("$r1"), ""); err == nil {148- t.Error("a region reading itself was accepted")188+ if back.OutputsChanged() != 0 {189+ t.Error("reading outputs counts as a change")149190 }150- if got := RegionRefs("$r1 | where x == $in.a and $nu.home | $r_2.x"); !slices.Equal(got, []string{"r1", "r_2"}) {151- t.Errorf("refs %v", got)191+ // Written again, it's the same file.192+ if again := writeBook(t, back); again != text {193+ t.Errorf("written again:\n%s\nwas\n%s", again, text)152194 }153195 }154196 155-func TestRegionFreezeAndSort(t *testing.T) {156- s := notebook(t)157- addShown(t, s, "r1", "ls", table([]string{"n", "s"}, []string{"b", "2"}, []string{"a", "10"}, []string{"c", ""}))158- if err := s.SortRegion("r1", []SortKey{{Col: 1, Desc: true}}); err != nil {197+// A notebook sheet of an earlier build opens with its commands as code198+// cells of a notebook tab, their tables sent where they were.199+func TestOldNotebookConverted(t *testing.T) {200+ old := `{"version": 4, "sheets": [201+ {"name": "Shell 1", "notebook": true, "cells": {}, "regions": [202+ {"name": "r1", "command": "ls", "at": "A1", "rows": 3, "cols": 2},203+ {"name": "big", "command": "$r1 | where size > 1kb", "at": "A6", "reads": ["r1"]},204+ {"name": "sel", "command": "$in | math sum", "at": "A9", "input": "Data 1!A1:A4"}205+ ]},206+ {"name": "Data 1", "cells": {"B1": "=SUM(nu.big)"}}207+]}`208+ w, err := ReadBook(strings.NewReader(old))209+ if err != nil {159210 t.Fatal(err)160211 }161- wantShown(t, s, map[string]string{"A3": "a", "A4": "b", "A5": "c"})162- // Running again keeps the order.163- s.ShowRegion("r1", table([]string{"n", "s"}, []string{"x", "1"}, []string{"y", "3"}))164- wantShown(t, s, map[string]string{"A3": "y", "A4": "x"})165- if err := s.FreezeRegion("r1"); err != nil {166- t.Fatal(err)212+ nb := w.Notebook()213+ if nb == nil || w.Index(nb) != 1 || nb.Name() != "Notebook" {214+ t.Fatalf("notebook %v at %d", nb, w.Index(nb))167215 }168- wantShown(t, s, map[string]string{"A1": "", "A2": "n", "A3": "y", "B3": "3"})169- if err := s.Set(at("A3"), "z"); err != nil {170- t.Errorf("a frozen cell can't be edited: %v", err)216+ cells := nb.NotebookCells()217+ want := []string{"r1 = ls", "big = $r1 | where size > 1kb", "sel = $sheet.'Data 1'!A1:A4 | do {\n$in | math sum\n}"}218+ for i, c := range cells {219+ if c.Source != want[i] {220+ t.Errorf("cell %d: %q, want %q", i+1, c.Source, want[i])221+ }171222 }172- s.Book().Undo()173- s.Book().Undo()174- wantShown(t, s, map[string]string{"A1": "r1 ls", "A3": "y"})175-}176-177-func TestRegionBlocked(t *testing.T) {178- s := New()179- if err := s.Set(at("B3"), "mine"); err != nil {180- t.Fatal(err)223+ shell := w.Lookup("Shell 1")224+ if shell.IsNotebook() {225+ t.Error("the old notebook sheet is a notebook tab")181226 }182- if err := s.AddRegion(Region{Name: "feed", Command: "tail", At: at("A1")}); err != nil {183- t.Fatal(err)227+ if _, r, ok := w.Region("big"); !ok || !r.Output || r.At != at("A7") {228+ t.Errorf("big's table: %+v %v", r, ok)229+ }230+ if notes := w.LoadNotes(); len(notes) != 1 || !strings.Contains(notes[0], "Shell 1") {231+ t.Errorf("notes %v", notes)232+ }233+ apply(t, shell, LiveOp{Region: "big", Reset: true, Header: liveRow("n"), Rows: []LiveRow{liveRow("4")}})234+ wantShown(t, w.Lookup("Data 1"), map[string]string{"B1": "4"})235+ if text := writeBook(t, w); strings.Contains(text, `"command"`) || !strings.Contains(text, `"output":true`) {236+ t.Errorf("saved again:\n%s", text)184237 }185- s.ShowRegion("feed", table([]string{"a", "b"}, []string{"1", "2"}, []string{"3", "4"}))186- wantShown(t, s, map[string]string{"A1": "feed tail can't show: it would overwrite data in B3", "A2": ""})187- s.Set(at("B3"), "")188- wantShown(t, s, map[string]string{"A1": "feed tail", "A2": "a", "B3": "2", "B4": "4"})189238 }
D internal/sheet/regiondata.go +0 -66

Not shown: this page already shows 6000 lines of diff.

M internal/sheet/regionfile.go +53 -72

Not shown: this page already shows 6000 lines of diff.

D internal/sheet/regiongraph.go +0 -161

Not shown: this page already shows 6000 lines of diff.

M internal/sheet/regionshape.go +33 -122

Not shown: this page already shows 6000 lines of diff.

D internal/sheet/regionsort.go +0 -68

Not shown: this page already shows 6000 lines of diff.

M internal/sheet/regionwrite.go +22 -121

Not shown: this page already shows 6000 lines of diff.

M internal/sheet/sheet.go +6 -5

Not shown: this page already shows 6000 lines of diff.

D internal/sheet/shellhistory.go +0 -35

Not shown: this page already shows 6000 lines of diff.

M internal/sheet/workbook.go +2 -2

Not shown: this page already shows 6000 lines of diff.

M internal/ui/commands.go +9 -2

Not shown: this page already shows 6000 lines of diff.

M internal/ui/docs_test.go +19 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/file.go +10 -4

Not shown: this page already shows 6000 lines of diff.

M internal/ui/filter.go +4 -5

Not shown: this page already shows 6000 lines of diff.

M internal/ui/goto_test.go +19 -18

Not shown: this page already shows 6000 lines of diff.

M internal/ui/gridlines.go +0 -19

Not shown: this page already shows 6000 lines of diff.

M internal/ui/help.go +1 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/hosts.go +0 -16

Not shown: this page already shows 6000 lines of diff.

A internal/ui/lineedit/area.go +240 -0

Not shown: this page already shows 6000 lines of diff.

A internal/ui/lineedit/area_test.go +93 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/linked.go +1 -1

Not shown: this page already shows 6000 lines of diff.

M internal/ui/menubar.go +1 -3

Not shown: this page already shows 6000 lines of diff.

M internal/ui/model.go +29 -13

Not shown: this page already shows 6000 lines of diff.

M internal/ui/monochrome_test.go +45 -15

Not shown: this page already shows 6000 lines of diff.

A internal/ui/nbjob.go +166 -0

Not shown: this page already shows 6000 lines of diff.

A internal/ui/nbrun.go +407 -0

Not shown: this page already shows 6000 lines of diff.

A internal/ui/nbscreen.go +290 -0

Not shown: this page already shows 6000 lines of diff.

A internal/ui/nbsend.go +201 -0

Not shown: this page already shows 6000 lines of diff.

A internal/ui/nbview/bench_test.go +68 -0

Not shown: this page already shows 6000 lines of diff.

A internal/ui/nbview/edit.go +355 -0

Not shown: this page already shows 6000 lines of diff.

A internal/ui/nbview/full.go +298 -0

Not shown: this page already shows 6000 lines of diff.

A internal/ui/nbview/keys.go +373 -0

Not shown: this page already shows 6000 lines of diff.

A internal/ui/nbview/markdown.go +167 -0

Not shown: this page already shows 6000 lines of diff.

A internal/ui/nbview/output.go +409 -0

Not shown: this page already shows 6000 lines of diff.

A internal/ui/nbview/panel.go +113 -0

Not shown: this page already shows 6000 lines of diff.

A internal/ui/nbview/syntax.go +241 -0

Not shown: this page already shows 6000 lines of diff.

A internal/ui/nbview/view.go +405 -0

Not shown: this page already shows 6000 lines of diff.

A internal/ui/nbview/view_test.go +238 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/notebook.go +338 -330

Not shown: this page already shows 6000 lines of diff.

M internal/ui/notebook_test.go +296 -180

Not shown: this page already shows 6000 lines of diff.

D internal/ui/nukey_test.go +0 -26

Not shown: this page already shows 6000 lines of diff.

D internal/ui/nuprompt/nuprompt.go +0 -331

Not shown: this page already shows 6000 lines of diff.

D internal/ui/nuprompt/nuprompt_test.go +0 -100

Not shown: this page already shows 6000 lines of diff.

D internal/ui/nurun.go +0 -325

Not shown: this page already shows 6000 lines of diff.

M internal/ui/overlay.go +4 -1

Not shown: this page already shows 6000 lines of diff.

M internal/ui/panel.go +38 -29

Not shown: this page already shows 6000 lines of diff.

M internal/ui/pivot.go +1 -1

Not shown: this page already shows 6000 lines of diff.

M internal/ui/selection.go +1 -1

Not shown: this page already shows 6000 lines of diff.

M internal/ui/sort.go +5 -1

Not shown: this page already shows 6000 lines of diff.

M internal/ui/tabs.go +4 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/tabstrip/tabstrip.go +9 -2

Not shown: this page already shows 6000 lines of diff.

A internal/ui/theme/code.go +42 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/theme/scheme.go +1 -1

Not shown: this page already shows 6000 lines of diff.

M internal/ui/theme/theme.go +15 -7

Not shown: this page already shows 6000 lines of diff.

M internal/ui/view.go +1 -4

Not shown: this page already shows 6000 lines of diff.

M website/src/pages/index.tsx +1 -1

Not shown: this page already shows 6000 lines of diff.