- commit
- 33f6c06e8a9164dcb7f4358a110417b3fd73df51
- parent
- 48aed3604e0437c9ddc3a7df7eaa258cebf8a37f
- parent
- c999d9d32f9a3b62645754d3d0ccb7d4b9b4e876
- author
- Dami <dami.etoile@gmail.com>
@@ -29,7 +29,9 @@ rest: building from a clone, importing files, `012 serve` and settings.
2929 suggestions and argument hints, pointing at cells with the arrows or the3030 mouse, named ranges, references between sheets, arrays that spill3131 (FILTER, SORT, UNIQUE, LAMBDA), optional decimal32- arithmetic for money, and undo for everything32+ arithmetic for money, undo for everything, and33+ [tracing](docs/formulas/tracing.md) that marks what a formula reads34+ and what reads it and steps through it part by part3335 ([formulas](docs/formulas/README.md)); numbers, dates and formulas3436 typed and shown in a [locale](docs/sheets/locale.md) per file.3537 - **Data tools**: freeze, sort, filter, find and replace, conditional@@ -68,7 +68,6 @@ shared editing in section 3.
6868 6969 | Item | Result | Size |7070 |---|---|---|71-| Formula tracing: precedents and dependents shown in the grid, a formula evaluated step by step | Finding why a number is wrong | M |7271 | A sheet or chart exported as a static HTML page in 012's look | Sharing a sheet with someone without 012 | S |7372 | Release archives and an install script served from the owner's nzip server | Installing without Go | S |7473 @@ -105,7 +104,8 @@ stream above rather than a design of their own.
105104 106105 - Sheets-structured menus, a command palette and a key for every command: [Keys and mouse](docs/reference/keys.md)107106 - Formula suggestions, argument hints and pointing at cells and sheets: [Building formulas](docs/formulas/building.md)108-- Tracing precedents and dependents, hidden sheets explained: [Building formulas](docs/formulas/building.md)107+- Tracing precedents and dependents, hidden sheets explained: [Tracing formulas](docs/formulas/tracing.md#stepping-through-them)108+- Formula tracing: precedents and dependents marked in the grid as the pointer moves, named ranges, spills, regions and their sources included, listed to go to, and a formula evaluated step by step: [Tracing formulas](docs/formulas/tracing.md)109109 - An optional vim keymap with a `:` command line: [Keys and mouse](docs/reference/keys.md#vim-keys)110110 - Vim `.` repeat, registers, marks, `cc` and `s`, `:` line history and `:w!`: [Keys and mouse](docs/reference/keys.md#vim-keys)111111 @@ -0,0 +1,54 @@
1+# Tracing: with precedents and dependents shown, the pointer on North's2+# revenue marks the regions and revenues it reads and the price per unit3+# that reads it; then Evaluate formula steps through the price, into4+# the revenue it reads and back out.5+Output out/tracing.gif6+Source lib/setup.tape7+8+Hide9+Type "../bin/012 data/sales.csv" Enter10+Sleep 2s11+Ctrl+G12+Type "F1" Enter13+Type "North" Enter14+Type@40ms '=SUMIF(A2:A17,"North",D2:D17)' Enter15+Type "Units" Enter16+Type@40ms '=SUMIF(A2:A17,"North",C2:C17)' Enter17+Type "Per unit" Enter18+Type@40ms "=F2/F4" Enter19+Show20+21+Sleep 800ms22+Ctrl+K23+Type@60ms "show precedents"24+Sleep 800ms25+Enter26+Sleep 3s27+Ctrl+G28+Type "F2" Enter29+Sleep 3s30+Screenshot out/stills/tracing-marks.png31+Down@600ms 232+Sleep 1.5s33+Down@600ms 234+Sleep 2s35+Ctrl+K36+Type@60ms "evaluate formula"37+Sleep 800ms38+Enter39+Sleep 1.5s40+Right41+Sleep 1.5s42+Screenshot out/stills/tracing-into.png43+Enter44+Sleep 1.2s45+Left46+Sleep 1.5s47+Screenshot out/stills/tracing-evaluate.png48+Enter49+Sleep 1.2s50+Enter51+Sleep 2s52+Screenshot out/stills/tracing-value.png53+Escape54+Sleep 1s@@ -43,6 +43,7 @@ internal/ui the Bubble Tea model: modes, menus, overlays, rendering
4343 cmdline the : command line and its completions4444 nbview a notebook tab: its cells, outputs, command and edit modes, the code editor and its language providers4545 findbar find and replace, a bar on the context line46+ evalview Data > Evaluate formula: a formula stepped through part by part4647 themepicker File > Settings > Theme, previewing as it moves4748 rules the conditional formatting and data validation panel4849 sortbar the sort bar: the columns and order to sort a range by@@ -399,8 +400,12 @@ recalculations, so the buffers are made once. See
399400 What stays in the engine is what needs cells or the workbook: the400401 running aggregates' storage and extension (`rangememo.go`), the links a401402 HYPERLINK cell opens (`link.go`), the questions a cell's JEV formulas ask402-(`RemoteCalls`), explaining errors (`explain.go`) and the decimal403-setting (`SetDecimal`, which marks formulas with `functions.Decimalize`).403+(`RemoteCalls`), explaining errors (`explain.go`), tracing precedents404+and dependents through the dependency indexes (`trace.go`,405+`tracedeps.go`), stepping through a formula (`steps.go`, where the406+function library records each part's value where it stands,407+`functions.EvalParts`) and the decimal setting (`SetDecimal`, which408+marks formulas with `functions.Decimalize`).404409 405410 ### Where the engine can grow406411 @@ -476,14 +481,14 @@ draw. The components:
476481 | edit line | `lineedit.Line` | the one-line editor shared by cell entries, prompts and search fields (package `lineedit`) |477482 | cell entry | `entry`, `suggest.List` | typing into a cell, pointing at references, other sheets while pointing (`entry.go`, `assistsheets.go`); formula suggestions and signatures (package `suggest`, with `assist.go`) |478483 | prompt | `prompt` | a question on the context line, typed or pointed at (`prompt.go`) |479-| overlays | `overlay.Overlay` | whatever has taken over input: menus (`menuoverlay.go`), the palette and pickers (package `picker`, with `palette.go`, `names.go`), the command line (package `cmdline`), the theme picker (package `themepicker`), the find bar (package `findbar`), the filter picker (package `filterpick`, with `filter.go`), the sort bar (package `sortbar`, with `sort.go`), choice bars (package `choicebar`, with `dialog.go`), the chart editor and selection (`charteditor.go`, `chartsel.go`), the pivot editor (`pivoteditor.go`, `pivotactions.go`), the rules panel (package `rules`, with `rules.go`), the shortcuts (package `shortcuts`, with `help.go`) |484+| overlays | `overlay.Overlay` | whatever has taken over input: menus (`menuoverlay.go`), the palette and pickers (package `picker`, with `palette.go`, `names.go`), the command line (package `cmdline`), the theme picker (package `themepicker`), the find bar (package `findbar`), the filter picker (package `filterpick`, with `filter.go`), the sort bar (package `sortbar`, with `sort.go`), choice bars (package `choicebar`, with `dialog.go`), the chart editor and selection (`charteditor.go`, `chartsel.go`), the pivot editor (`pivoteditor.go`, `pivotactions.go`), the rules panel (package `rules`, with `rules.go`), the shortcuts (package `shortcuts`, with `help.go`), Evaluate formula (package `evalview`, with `evaluate.go`) |480485 | sheet tabs | `tabstrip.Strip` | where each sheet was left, the tab strip's scroll and layout (package `tabstrip`); what clicks on it do (`tabstrip.go`) |481486 | mouse | `mouseState` | drags, hover, double clicks, the fill handle (`mouse.go`, `fill.go`) |482487 | 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`) |483488 | 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`) |484489 | 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`) |485490 | 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`) |486-| 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`) |491+| others | `clipboard`, `trace`, `traceView`, `chartState`, `jevRunner`, `terminal`, `session` | what Ctrl+V pastes, a trace being stepped through and tracing that stays on (`traceview.go`), 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`) |487492 488493 Overlays implement `overlay.Overlay` (package `overlay`): an indicator for489494 the mode, `Key` and `Mouse` handlers, a `Layout` of boxes to draw, and@@ -505,6 +510,7 @@ methods off `ui.Model`'s exported API:
505510 | `picker` | `picker.Host` | theme, size, the edit line, close, record the answer to a command's question (5) |506511 | `choicebar` | `choicebar.Host` | theme, close, record the key chosen (3); the choices are closures over the model |507512 | `shortcuts` | `shortcuts.Host` | theme, size, close, the rows, built from the key bindings and the registry (4) |513+| `evalview` | `evalview.Host` | theme, size, close, the locale (4); the formula's steps are the engine's `sheet.Steps` |508514 | `sortbar` | `sortbar.Host` | theme, size, the sheet, close, sort (recorded as the bar's command) (5) |509515 | `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 |510516 | `cmdline` | `cmdline.Host` | theme, size, the edit line, close, the commands to complete, run a line, fail, the session's history (8) |@@ -555,7 +561,7 @@ hints) are what every overlay is drawn with.
555561 **Packages.** `theme`, `rowtext`, `formula`, `overlay` and `lineedit`556562 depend on nothing in `ui`, so they can be tested and measured alone. The557563 components in `picker`, `cmdline`, `nbview`, `themepicker`, `findbar`, `rules`,558-`sortbar`, `filterpick`, `choicebar`, `shortcuts`, `suggest`, `tabstrip`564+`sortbar`, `filterpick`, `choicebar`, `shortcuts`, `evalview`, `suggest`, `tabstrip`559565 and `transfer` build on them and reach the model only through their560566 hosts, with unit tests of their own against fake hosts. A component moves out of package561567 `ui` when its host stays small (about ten methods or fewer); one that@@ -29,6 +29,7 @@ it lags, and past a second it stalls.
2929 | Incremental recalc | A change that makes formulas read under about 500,000 cells in total (fan-out, chains and volatiles of 8192 cells: 1.3 to 1.9 ms); 1000 SUMs over a full column: 0.53 ms; 8192 running totals: 2.7 ms | 60 criteria functions (SUMIF, COUNTIFS, AVERAGEIF) over whole columns of 8192 rows: 29 ms an edit | | About 3 ns per cell read: an index into the column's block |3030 | Full recalc | Any sheet: numbers and text hold their values and cost nothing; 1000 full-column SUMs 0.5 ms; 8192 running totals 1.8 ms | 60 whole-column criteria functions over 8192 rows: 44 ms | | Same as above |3131 | Rendering | Any sheet at up to 200 x 60: 1.3 ms a frame; 400 x 120: 4.2 ms; a color scale on every cell shown adds 0.4 ms at 200 x 60, borders on every cell and wrapped text 0.4 ms | 20 charts at 400 x 120: 6 ms | | View building styled strings, then Bubble Tea parsing and diffing them |32+| Tracing (`BenchmarkTrace`) | An arrow key through to its frame at 200 x 60 with tracing on, on a million cells: a chain of a million formulas 0.66 ms (0.64 off), a million numbers 0.82 ms (0.82 off), a cell read by a million formulas 1.6 ms (0.85 off); off, it costs nothing | | A cell's dependents listed past a thousand: the list says there are more | Finding a cell's links (dependents capped at a thousand), and asking each formula on screen whether it reads the cell |3233 | Selection statistics | Any selection: extending one over all 2.1 M cells of 8192 x 256 costs 0.3 ms a key | | | Per column with data, 1024 or 64 rows at a time from an index on the blocks of filled cells, plus the rows at the selection's ends |3334 | Imports | CSV, SQLite, Parquet: 2 to 4 M cells/s (a million cells in 0.25 to 0.4 s); XLSX numbers or text: 0.8 to 1.4 M cells/s | XLSX with formulas: 0.4 to 0.6 M cells/s | Data past `max-cells` or the grid (dropped, with a note); XLSX files past the reader's limits (refused) | Building cells one at a time; XML decoding; XLSX formula translation |3435 | Undo | One step of any size: undo costs what the edit cost; clearing all ten million cells of a full sheet holds 193 MB | History capped at 100 steps and 256 MB of before-images: 100 whole-column steps hold 15 MB | A step past 1 GB (millions of formulas or long distinct texts at once): asks, and runs without undo if told to | Before-images in the store's form: a slot (20 B) per plain cell, a whole `Cell` per formula |@@ -106,7 +106,8 @@ there.
106106 | Notes | A `▝` in the cell's top-right corner |107107 | Macro recording | `REC` beside the mode indicator |108108 | Mode | The mode indicator's word (`READY`, `ENTER`, `POINT`, `MENU`, and on a notebook `NOTEBOOK`, `EDIT` or `OUTPUT`) |109-| Search matches, traced cells | Reverse video, with a count or the list on the context line |109+| Search matches, traced cells | Reverse video, with a count or the list on the context line; dependents also bold |110+| Evaluate formula | The part computed next underlined and bold, values in its place italic, and `Next` and `Value` in words |110111 | The copied range | A dashed underline |111112 | Dropdowns, checkboxes, active filters | `▾`, or a chip in reverse video between `▐` and `▌`; `[ ]` and `[✓]`; `▼` instead of `▾` |112113 | Data bars, icon sets | Eighth blocks as long as the number, and reverse video under the text they run beneath; the icon's glyph (`↑`, `◑`, `✓`, `▆`) |@@ -72,7 +72,8 @@ numbers as numbers.
7272 | Page | For |7373 |---|---|7474 | [References](references.md) | Cells, ranges, names, other sheets, and a range where one value is wanted |75-| [Building formulas](building.md) | Pointing, suggestions, argument hints, tracing |75+| [Building formulas](building.md) | Pointing, suggestions, argument hints |76+| [Tracing formulas](tracing.md) | Precedents and dependents in the grid, and a formula evaluated step by step |7677 | [Arrays and spills](arrays.md) | FILTER, SORT, UNIQUE, SEQUENCE, ARRAYFORMULA, LET and LAMBDA |7778 | [Decimal arithmetic](decimal.md) | Exact decimal math for money |7879 | [JEV functions](jev.md) | Asking a hosted model from formulas |@@ -22,11 +22,9 @@ sidebar_position: 3
2222 `]`. The context line lists the table's columns, the one being typed2323 marked: `Sales[Region, Units, Amount]`2424 ([Tables by column name](references.md#tables-by-column-name)).25-- Alt+, and Alt+. trace precedents and dependents: the cells a formula reads26- and the formulas that read a cell. Press again to step through them.27- Cells on hidden sheets are skipped; when that leaves nothing, the28- context line names the hidden sheets ("Reads only Data, a hidden sheet;29- View > Hidden sheets shows it").25+- Alt+; marks what the active cell's formula reads and the formulas that26+ read it, as the pointer moves; Alt+= steps through a formula one part27+ at a time. See [Tracing formulas](tracing.md).3028 - A formula left Automatic shows the format of what it reads: `=B5*2` of3129 a currency cell shows currency, and so does `=SUM(B2:B9)`. A blank cell3230 counts with its column's, row's or sheet's format, and changing those@@ -0,0 +1,88 @@
1+---2+title: "Tracing formulas"3+sidebar_position: 64+---5+6+# Tracing formulas7+8+When a number looks wrong, trace it: the cells its formula reads (its9+precedents), the formulas that read it (its dependents), and what each10+part of the formula computes.11+12+13+14+## Showing precedents and dependents15+16+Data > Formula tracing > Show precedents and dependents (Alt+;) marks them in the grid as17+the pointer moves: the cells the active cell's formula reads in green,18+the formulas that read the active cell in magenta and bold. Without19+color both are in reverse video, dependents bold as well. The context20+line lists them all, those off screen with an arrow toward them and21+those on other sheets with their sheet:22+23+```24+C7 reads B3, Rate, Data!B2:B9; read by D7, A40↓, Summary!B2 +3 more25+```26+27+Alt+; again stops. What counts as a link:28+29+| The active cell | Its precedents | Its dependents |30+|---|---|---|31+| A formula | the cells and ranges it reads, on any sheet; a named range, a region (`nu.sales`) or a [table's](../sheets/tables.md) cells (`Sales[Amount]`) by its name | the formulas that read it through a reference, a range, a named range, a table or a region's name, on any sheet |32+| A formula whose array spills | as any formula | the cells it spills into, and the formulas reading any of them |33+| A value an array spilled | the formula that spilled it | the formulas reading it |34+| A cell of a linked file or of a notebook output sent to the sheet ([regions](../nushell/notebooks.md)) | the file, or the notebook cell | the formulas reading it, `nu.name` included |35+36+Only direct links show; go to one and tracing shows its own, a level37+further. Excel draws arrows between cells; 012 marks the cells and lists38+the rest, which reads the same for a range on another sheet or far off39+screen.40+41+## Going to one42+43+Data > Formula tracing > Go to a precedent or dependent (Alt+') lists44+the active cell's precedents, then its dependents, with what each is45+(`precedent, named range B2:B9`, `dependent, on Summary`). Type to narrow the46+list; Enter goes to one, selecting a range, showing another sheet, or a47+notebook's cell. A cell read by more than a thousand formulas lists the48+first thousand.49+50+## Stepping through them51+52+Alt+, and Alt+. step through the precedents and the dependents, as53+Excel's Ctrl+[ and Ctrl+], which terminals send as Esc: the first press54+marks them all and jumps to the first, each press after to the next,55+and Esc goes back to the cell traced. Cells on hidden sheets are56+skipped; when that leaves nothing, the context line names the hidden57+sheets ("Reads only Data, a hidden sheet; View > Hidden sheets shows58+it").59+60+## Evaluating a formula step by step61+62+Data > Formula tracing > Evaluate formula (Alt+=), after Excel's63+Evaluate Formula, opens a box over the grid with the active cell's64+formula. The part computed next is underlined, and the line under it65+says what it computes:66+67+```68+┌─ Evaluate D1 ────────────────────────────────────────┐69+│ =IF(TRUE,SUM(A2:A3)*2,A3) │70+├──────────────────────────────────────────────────────┤71+│ Next SUM(A2:A3) = 5 │72+└───────────────────────────────────────────── 2 of 5 ─┘73+```74+75+- Enter puts the value in its place, in italic, and underlines the next76+ part, until the formula's value is all that's left; Enter then starts77+ over.78+- → steps into the underlined part when it's a reference to another79+ formula: the box shows that formula, and its title the way in80+ (`Evaluate D1 › B1`). ← or Esc steps back out, the reference's value81+ in its place. Esc at the first formula closes the box.82+83+The values are what the formula computes where each part stands: an84+array where the part is read as one (`SUM(A1:A9*2)` shows the doubled85+column, `{2;4;…}`), names bound inside `LET`, and nothing for the branch86+`IF`, `IFERROR` or `CHOOSE` didn't take, which stays as written. A blank87+cell reads as 0. The formula is written in the88+[locale's](../sheets/locale.md) syntax, spaced as 012 stores it.Binary file.
@@ -97,7 +97,10 @@ The rest of the formats are in the Format menu.
9797 | Ctrl+F, Ctrl+H | Find; find and replace | [Find and replace](../sheets/find-replace.md) |9898 | Alt+Down | Open the active cell's dropdown (type to search, Enter picks); elsewhere, with a filter on (Data > Create a filter), the column's filter (Space checks values, type to search, or pick a condition) | [validation](../sheets/rules.md#data-validation), [filter](../sheets/sort-filter.md#filter) |9999 | Space | Check or uncheck the selected checkboxes (Insert > Checkbox); elsewhere Space starts an entry | [Conditional formatting and data validation](../sheets/rules.md#data-validation) |100-| Alt+, Alt+. | Trace precedents, dependents: highlight the cells a formula reads, or the formulas that read the cell, and jump to the first; again for the next, Esc to go back (Excel's Ctrl+[ and Ctrl+], which terminals send as Esc) | [Building formulas](../formulas/building.md) |100+| Alt+, Alt+. | Trace precedents, dependents: highlight the cells a formula reads, or the formulas that read the cell, and jump to the first; again for the next, Esc to go back (Excel's Ctrl+[ and Ctrl+], which terminals send as Esc) | [Tracing formulas](../formulas/tracing.md#stepping-through-them) |101+| Alt+; | Show precedents and dependents: mark them as the pointer moves and list them on the context line; again to stop | [Tracing formulas](../formulas/tracing.md) |102+| Alt+' | Go to a precedent or dependent, picked from a list | [Tracing formulas](../formulas/tracing.md#going-to-one) |103+| Alt+= | Evaluate formula: step through the active cell's formula one part at a time; Enter evaluates, → steps into a reference, ← steps out | [Tracing formulas](../formulas/tracing.md#evaluating-a-formula-step-by-step) |101104 | Alt+Shift+F | Frequency table of the active column on a new sheet, as VisiData's Shift+F | [Pivot tables](../sheets/pivots.md#frequency-tables) |102105 | Ctrl+Alt+T | Make the selection, or the data around the active cell, a table whose first row names its columns (Format > Convert to table) | [Tables](../sheets/tables.md) |103106 | Shift+F2 | Add or edit the active cell's note; Alt+Enter or Shift+Enter starts a new line | [Notes and protection](../sheets/notes-protection.md#notes) |@@ -0,0 +1,49 @@
1+package e2e2+3+// Golden screens for formula tracing: precedents and dependents shown as4+// the pointer moves, the list to go to one, and Evaluate formula, on dark5+// and light terminals and in the high-contrast theme.6+7+// traced is the named-range budget with the total read by a quarter8+// beside it and a yearly figure far below, tracing on at the total.9+func traced(s *session) {10+ named(s)11+ s.keys("<ctrl+g>", "B30", "<enter>", "=B7*12", "<enter>")12+ s.keys("<ctrl+g>", "C7", "<enter>", "=B7/4", "<enter>")13+ s.keys("<ctrl+home>", "<down>", "<down>", "<down>", "<down>", "<down>", "<down>", "<right>", "<alt+;>")14+ s.waitFor("B7 reads Expenses; read by C7, B30↓")15+}16+17+// evaluating is Evaluate formula on B8 with two parts computed.18+func evaluating(s *session) {19+ named(s)20+ s.keys("<alt+=>", "<enter>", "<enter>")21+ s.waitFor("2 of 3")22+}23+24+func init() {25+ screens = append(screens,26+ screen{name: "trace-view", setup: traced},27+ screen{name: "trace-view-light", opts: options{light: true}, setup: traced},28+ screen{name: "trace-view-high-contrast", opts: options{config: highContrastConfig}, setup: traced},29+ screen{name: "trace-view-high-contrast-light", opts: options{light: true, config: highContrastConfig}, setup: traced},30+ screen{name: "trace-list", setup: func(s *session) {31+ traced(s)32+ s.keys("<alt+'>")33+ s.waitFor("Precedents and dependents of B7")34+ }},35+ screen{name: "evaluate", setup: evaluating},36+ screen{name: "evaluate-light", opts: options{light: true}, setup: evaluating},37+ screen{name: "evaluate-high-contrast", opts: options{config: highContrastConfig}, setup: evaluating},38+ screen{name: "evaluate-into", setup: func(s *session) {39+ traced(s)40+ s.keys("<right>", "<alt+=>", "<right>")41+ s.waitFor("Evaluate C7 › B7")42+ }},43+ screen{name: "evaluate-narrow", opts: options{cols: 60, rows: 16}, setup: func(s *session) {44+ named(s)45+ s.keys("<alt+=>", "<enter>")46+ s.waitFor("1 of 3")47+ }},48+ )49+}@@ -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">EVAL</span><span style="background:#40e0ff"> </span>2+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">B8</span><span style="background:#3a3a3a"> </span><span style="color:#757575;background:#000000">┌─</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">Evaluate</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold">B8</span><span style="background:#000000"> </span><span style="color:#757575;background:#000000">──────────────────────────────────────────────────────────────┐</span><span style="background:#000000"> </span>3+<span style="background:#000000"> </span><span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">=</span><span style="color:#40e0ff;background:#000000;font-weight:bold;font-style:italic;text-decoration:underline">719.466666666667</span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">*</span><span style="color:#40e0ff;background:#000000;font-weight:bold;font-style:italic;text-decoration:underline">4</span><span style="background:#000000"> </span><span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span>4+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">A</span><span style="color:#757575;background:#000000">├────────────────────────────────────────────────────────────────────────────┤</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:#ffffff;background:#000000">Hous</span><span style="color:#757575;background:#000000">│</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">Next</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000;font-weight:bold;text-decoration:underline">AVERAGE(Expenses)*C2</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">=</span><span style="background:#000000"> </span><span style="color:#40e0ff;background:#000000;font-style:italic">2877.86666666667</span><span style="background:#000000"> </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:#757575;background:#000000">└───────────────────────────────────────────────────────────────────</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">2</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">3</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">Rent</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">1450</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">Groceries</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">612.4</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">Transit</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">96</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">Savings</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">r</span><span style="background:#000000"> </span><span style="color:#ff8080;background:#000000;text-decoration:underline wavy #ff8080">#DIV/0!</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">Total</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">2158.4</span><span style="background:#000000"> </span>12+<span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">8</span><span style="background:#40e0ff"> </span><span style="background:#000000"> </span><span style="color:#000000;background:#40e0ff">2877.8667</span><span style="background:#40e0ff"> </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"> </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="color:#ffffff;background:#1f1f1f">The</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">underlined</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">part</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">is</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;background:#1f1f1f">computed</span><span style="background:#1f1f1f"> </span><span style="color:#ffffff;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">evaluate</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">close</span></pre>\ No newline at end of file@@ -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">EVAL</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)">C7</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Evaluate</span> <span style="font-weight:bold">C7</span> <span style="font-weight:bold">›</span> <span style="font-weight:bold">B7</span> <span style="color:var(--vt-palette-8)">─────────────────────────────────────────────────────────┐</span>3+ <span style="color:var(--vt-palette-8)">│</span> =<span style="font-weight:bold;text-decoration:underline">SUM(B3:B5)</span> <span style="color:var(--vt-palette-8)">│</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="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)">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> Hous<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Next</span> <span style="font-weight:bold;text-decoration:underline">SUM(B3:B5)</span> <span style="color:var(--vt-palette-8)">=</span> <span style="color:var(--vt-palette-6);font-style:italic">2158.4</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> <span style="color:var(--vt-palette-8)">0</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> Rent 14508+<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.49+<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 9610+<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>11+<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-2)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-2)">2158.4</span><span style="background:var(--vt-palette-2)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">539.6</span><span style="background:var(--vt-palette-6)"> </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> 2877.866713+<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+The underlined part is computed next <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)">evaluate</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)">step</span> <span style="color:var(--vt-palette-8)">out</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@@ -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">EVAL</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)">B8</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Evaluate</span> <span style="font-weight:bold">B8</span> <span style="color:var(--vt-palette-8)">──────────────────────────────────────────────────────────────┐</span>3+ <span style="color:var(--vt-palette-8)">│</span> =<span style="color:var(--vt-palette-4);font-weight:bold;font-style:italic;text-decoration:underline">719.466666666667</span><span style="font-weight:bold;text-decoration:underline">*</span><span style="color:var(--vt-palette-4);font-weight:bold;font-style:italic;text-decoration:underline">4</span> <span style="color:var(--vt-palette-8)">│</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="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)">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> Hous<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">Next</span> <span style="font-weight:bold;text-decoration:underline">AVERAGE(Expenses)*C2</span> <span style="color:var(--vt-palette-0)">=</span> <span style="color:var(--vt-palette-4);font-style:italic">2877.86666666667</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> <span style="color:var(--vt-palette-8)">└───────────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">2</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">3</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> Rent 14508+<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> Groceries 612.49+<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> Transit 9610+<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> Savings r <span style="color:var(--vt-palette-1);text-decoration:underline wavy var(--vt-palette-1)">#DIV/0!</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> Total 2158.412+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">8</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">2877.8667</span><span style="background:var(--vt-palette-6)"> </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+The underlined part is computed next <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)">evaluate</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@@ -0,0 +1,16 @@
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">EVAL</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Evaluate</span> <span style="font-weight:bold">B8</span> <span style="color:var(--vt-palette-8)">────────────────────────────────────────────┐</span>3+<span style="color:var(--vt-palette-8)">│</span> =<span style="color:var(--vt-palette-6);font-style:italic">719.466666666667</span>*<span style="font-weight:bold;text-decoration:underline">C2</span> <span style="color:var(--vt-palette-8)">│</span>4+<span style="color:var(--vt-palette-8)">├──────────────────────────────────────────────────────────┤</span>5+<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Next</span> <span style="font-weight:bold;text-decoration:underline">C2</span> <span style="color:var(--vt-palette-8)">=</span> <span style="color:var(--vt-palette-6);font-style:italic">4</span> <span style="color:var(--vt-palette-8)">│</span>6+<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)">3</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> Rent 14508+<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.49+<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 9610+<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>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> Total 2158.412+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">8</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">2877.8667</span><span style="background:var(--vt-palette-6)"> </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+The underlined part is comp… <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)">evaluate</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@@ -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">EVAL</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)">B8</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Evaluate</span> <span style="font-weight:bold">B8</span> <span style="color:var(--vt-palette-8)">──────────────────────────────────────────────────────────────┐</span>3+ <span style="color:var(--vt-palette-8)">│</span> =<span style="color:var(--vt-palette-6);font-weight:bold;font-style:italic;text-decoration:underline">719.466666666667</span><span style="font-weight:bold;text-decoration:underline">*</span><span style="color:var(--vt-palette-6);font-weight:bold;font-style:italic;text-decoration:underline">4</span> <span style="color:var(--vt-palette-8)">│</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="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)">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> Hous<span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Next</span> <span style="font-weight:bold;text-decoration:underline">AVERAGE(Expenses)*C2</span> <span style="color:var(--vt-palette-8)">=</span> <span style="color:var(--vt-palette-6);font-style:italic">2877.86666666667</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> <span style="color:var(--vt-palette-8)">2</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">3</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> Rent 14508+<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.49+<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 9610+<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>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> Total 2158.412+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">8</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">2877.8667</span><span style="background:var(--vt-palette-6)"> </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+The underlined part is computed next <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)">evaluate</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@@ -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)">121</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)">123</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@@ -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)">121</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)">123</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@@ -40,6 +40,6 @@
4040 <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>4141 <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>4242 <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)">62</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)">64</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@@ -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)">121</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)">123</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@@ -13,18 +13,18 @@
1313 <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="color:var(--vt-palette-8)">├────────────────────────────────────────────────┤</span>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> Named ranges <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> Define named range <span style="color:var(--vt-palette-8)">│</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> <span style="color:var(--vt-palette-8)">│</span> Table › <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> Protect sheets and ranges <span style="color:var(--vt-palette-8)">│</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> <span style="color:var(--vt-palette-8)">│</span> Data validation <span style="color:var(--vt-palette-8)">│</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> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Remove</span> <span style="color:var(--vt-palette-8)">data</span> <span style="color:var(--vt-palette-8)">validation</span> <span style="color:var(--vt-palette-8)">│</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> <span style="color:var(--vt-palette-8)">├─────────────────────┌──────────────────────────────────────────┐</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> <span style="color:var(--vt-palette-8)">│</span> Trace precedents <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)">Record</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">macro</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</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> <span style="color:var(--vt-palette-8)">│</span> Trace dependents <span style="color:var(--vt-palette-8)">│</span> Record macro with relative references <span style="color:var(--vt-palette-8)">│</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> <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>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> <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)">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 <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>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> <span style="color:var(--vt-palette-8)">│</span> Protect sheets and ranges <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> Data validation <span style="color:var(--vt-palette-8)">│</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> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Remove</span> <span style="color:var(--vt-palette-8)">data</span> <span style="color:var(--vt-palette-8)">validation</span> <span style="color:var(--vt-palette-8)">│</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> <span style="color:var(--vt-palette-8)">├────────────────────────────────────────────────┤</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> <span style="color:var(--vt-palette-8)">│</span> Formula tracing <span style="color:var(--vt-palette-8)">┌──────────────────────────────────────────┐</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> <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)">Record</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">macro</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</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> <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> Record macro with relative references <span style="color:var(--vt-palette-8)">│</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> <span style="color:var(--vt-palette-8)">│</span> Shell <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>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> <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)">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>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)">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> <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> 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@@ -13,18 +13,18 @@
1313 <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>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> Named ranges <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> Define named range <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> Table › <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> Protect sheets and ranges <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> Data validation <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> <span style="color:var(--vt-palette-8)">Remove</span> <span style="color:var(--vt-palette-8)">data</span> <span style="color:var(--vt-palette-8)">validation</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>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> Trace precedents <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)">Record</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">macro</span><span style="background:var(--vt-palette-6)"> </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> Trace dependents <span style="color:var(--vt-palette-8)">│</span> Record macro with relative references <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> <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>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><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)">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 <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>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> Protect sheets and ranges <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> Data validation <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> <span style="color:var(--vt-palette-8)">Remove</span> <span style="color:var(--vt-palette-8)">data</span> <span style="color:var(--vt-palette-8)">validation</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>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> Formula tracing <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="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Record</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">macro</span><span style="background:var(--vt-palette-6)"> </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><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> Record macro with relative references <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> Shell <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>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> Linked file <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>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)">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> <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> 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@@ -12,5 +12,5 @@
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>1313 <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>1414 <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>15-<span style="color:var(--vt-palette-8)">└──────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">57</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">215</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)">54</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">211</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@@ -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)">40</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">215</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)">43</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">211</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>@@ -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)">40</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">215</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)">43</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">211</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>@@ -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> <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>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> <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)">52</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">215</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)">51</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">211</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>@@ -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)">215</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">215</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)">211</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">211</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>@@ -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">MENU</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)">B7</span><span style="background:var(--vt-palette-8)"> </span> =SUM(B3:B5) <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Precedents</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">dependents</span> <span style="font-weight:bold">of</span> <span style="font-weight:bold">B7</span> <span style="color:var(--vt-palette-8)">──────┐</span>3+ <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">›</span> <span style="color:var(--vt-palette-8)">Type</span> <span style="color:var(--vt-palette-8)">a</span> <span style="color:var(--vt-palette-8)">cell,</span> <span style="color:var(--vt-palette-8)">range</span> <span style="color:var(--vt-palette-8)">or</span> <span style="color:var(--vt-palette-8)">name</span> <span style="color:var(--vt-palette-8)">│</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="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">B</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </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)">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> Household budget 2026 <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)">Expenses</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">precedent</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> <span style="color:var(--vt-palette-8)">│</span> C7 <span style="color:var(--vt-palette-8)">dependent</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> Rent <span style="background:var(--vt-palette-2)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-2)">1450</span><span style="background:var(--vt-palette-2)"> </span> <span style="color:var(--vt-palette-8)">│</span> B30 <span style="color:var(--vt-palette-8)">dependent</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> Groceries<span style="background:var(--vt-palette-2)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-2)">612.4</span><span style="background:var(--vt-palette-2)"> </span> <span style="color:var(--vt-palette-8)">└───────────────────────────────</span> <span style="color:var(--vt-palette-8)">3</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">3</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 <span style="background:var(--vt-palette-2)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-2)">96</span><span style="background:var(--vt-palette-2)"> </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>11+<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="background:var(--vt-palette-5)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-5);font-weight:bold">539.6</span><span style="background:var(--vt-palette-5)"> </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> 2877.866713+<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+Go to Expenses <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)">go</span> <span style="color:var(--vt-palette-8)">to</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@@ -0,0 +1,30 @@
1+<pre class="screen"><span style="background:#e0e0e0"> </span><span style="color:#000000;background:#e0e0e0;text-decoration:underline">F</span><span style="color:#000000;background:#e0e0e0">ile</span><span style="background:#e0e0e0"> </span><span style="color:#000000;background:#e0e0e0;text-decoration:underline">E</span><span style="color:#000000;background:#e0e0e0">dit</span><span style="background:#e0e0e0"> </span><span style="color:#000000;background:#e0e0e0;text-decoration:underline">V</span><span style="color:#000000;background:#e0e0e0">iew</span><span style="background:#e0e0e0"> </span><span style="color:#000000;background:#e0e0e0;text-decoration:underline">I</span><span style="color:#000000;background:#e0e0e0">nsert</span><span style="background:#e0e0e0"> </span><span style="color:#000000;background:#e0e0e0">F</span><span style="color:#000000;background:#e0e0e0;text-decoration:underline">o</span><span style="color:#000000;background:#e0e0e0">rmat</span><span style="background:#e0e0e0"> </span><span style="color:#000000;background:#e0e0e0;text-decoration:underline">D</span><span style="color:#000000;background:#e0e0e0">ata</span><span style="background:#e0e0e0"> </span><span style="color:#000000;background:#e0e0e0;text-decoration:underline">H</span><span style="color:#000000;background:#e0e0e0">elp</span><span style="background:#e0e0e0"> </span><span style="background:#a0e8ff"> </span><span style="color:#000000;background:#a0e8ff;font-weight:bold">READY</span><span style="background:#a0e8ff"> </span>2+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">B7</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">=SUM(B3:B5)</span><span style="background:#ffffff"> </span>3+<span style="color:#000000;background:#ffffff">B7</span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">reads</span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">Expenses;</span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">read</span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">by</span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">C7,</span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">B30↓</span><span style="background:#ffffff"> </span><span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">Alt+'</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">list</span><span style="background:#ffffff"> </span><span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">Alt+;</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">stop</span>4+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">A</span><span style="background:#d0d0d0"> </span><span style="background:#a0e8ff"> </span><span style="color:#000000;background:#a0e8ff;font-weight:bold">B</span><span style="background:#a0e8ff"> </span><span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">C</span><span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">D</span><span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">E</span><span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">F</span><span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">G</span><span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">H</span><span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">I</span><span style="background:#d0d0d0"> </span>5+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">1</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">Household</span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">budget</span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">2026</span><span style="background:#ffffff"> </span>6+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">2</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">4</span><span style="background:#ffffff"> </span>7+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">3</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">Rent</span><span style="background:#ffffff"> </span><span style="background:#b0f0b0"> </span><span style="color:#000000;background:#b0f0b0">1450</span><span style="background:#b0f0b0"> </span><span style="background:#ffffff"> </span>8+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">4</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">Groceries</span><span style="background:#b0f0b0"> </span><span style="color:#000000;background:#b0f0b0">612.4</span><span style="background:#b0f0b0"> </span><span style="background:#ffffff"> </span>9+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">5</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">Transit</span><span style="background:#ffffff"> </span><span style="background:#b0f0b0"> </span><span style="color:#000000;background:#b0f0b0">96</span><span style="background:#b0f0b0"> </span><span style="background:#ffffff"> </span>10+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">6</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">Savings</span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">r</span><span style="background:#ffffff"> </span><span style="color:#a00000;background:#ffffff;text-decoration:underline wavy #a00000">#DIV/0!</span><span style="background:#ffffff"> </span>11+<span style="background:#a0e8ff"> </span><span style="color:#000000;background:#a0e8ff;font-weight:bold">7</span><span style="background:#a0e8ff"> </span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">Total</span><span style="background:#ffffff"> </span><span style="background:#a0e8ff"> </span><span style="color:#000000;background:#a0e8ff">2158.4</span><span style="background:#a0e8ff"> </span><span style="background:#800080"> </span><span style="color:#dedede;background:#800080;font-weight:bold">539.6</span><span style="background:#800080"> </span><span style="background:#ffffff"> </span>12+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">8</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span><span style="color:#000000;background:#ffffff">2877.8667</span><span style="background:#ffffff"> </span>13+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">9</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span>14+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">10</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span>15+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">11</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span>16+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">12</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span>17+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">13</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span>18+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">14</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span>19+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">15</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span>20+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">16</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span>21+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">17</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span>22+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">18</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span>23+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">19</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span>24+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">20</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span>25+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">21</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span>26+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">22</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span>27+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">23</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span>28+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">24</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span>29+<span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">25</span><span style="background:#d0d0d0"> </span><span style="background:#ffffff"> </span>30+<span style="background:#a0e8ff"> </span><span style="color:#000000;background:#a0e8ff;font-weight:bold">Sheet1</span><span style="background:#a0e8ff"> </span><span style="background:#e0e0e0"> </span><span style="color:#000000;background:#e0e0e0">+</span><span style="background:#e0e0e0"> </span><span style="color:#6a6a6a;background:#e0e0e0">│</span><span style="background:#e0e0e0"> </span><span style="color:#000000;background:#e0e0e0">untitled</span><span style="background:#e0e0e0"> </span><span style="color:#000000;background:#e0e0e0">modified</span><span style="background:#e0e0e0"> </span><span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">Ctrl+K</span><span style="background:#d0d0d0"> </span><span style="background:#e0e0e0"> </span><span style="color:#000000;background:#e0e0e0">search</span><span style="background:#e0e0e0"> </span><span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">Ctrl+/</span><span style="background:#d0d0d0"> </span><span style="background:#e0e0e0"> </span><span style="color:#000000;background:#e0e0e0">shortcuts</span><span style="background:#e0e0e0"> </span><span style="background:#d0d0d0"> </span><span style="color:#000000;background:#d0d0d0">F10</span><span style="background:#d0d0d0"> </span><span style="background:#e0e0e0"> </span><span style="color:#000000;background:#e0e0e0">menu</span></pre>\ No newline at end of file@@ -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">READY</span><span style="background:#40e0ff"> </span>2+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">B7</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">=SUM(B3:B5)</span><span style="background:#000000"> </span>3+<span style="color:#959595;background:#000000">B7</span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">reads</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">Expenses</span><span style="color:#959595;background:#000000">;</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">by</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">C7,</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">B30↓</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Alt+'</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">list</span><span style="background:#000000"> </span><span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">Alt+;</span><span style="background:#3a3a3a"> </span><span style="background:#000000"> </span><span style="color:#959595;background:#000000">stop</span>4+<span style="background:#3a3a3a"> </span><span style="color:#ffffff;background:#3a3a3a">A</span><span style="background:#3a3a3a"> </span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">B</span><span style="background:#40e0ff"> </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:#ffffff;background:#000000">Household</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">budget</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">2026</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">4</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">Rent</span><span style="background:#000000"> </span><span style="background:#40e040"> </span><span style="color:#000000;background:#40e040">1450</span><span style="background:#40e040"> </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">Groceries</span><span style="background:#40e040"> </span><span style="color:#000000;background:#40e040">612.4</span><span style="background:#40e040"> </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">Transit</span><span style="background:#000000"> </span><span style="background:#40e040"> </span><span style="color:#000000;background:#40e040">96</span><span style="background:#40e040"> </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">Savings</span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">r</span><span style="background:#000000"> </span><span style="color:#ff8080;background:#000000;text-decoration:underline wavy #ff8080">#DIV/0!</span><span style="background:#000000"> </span>11+<span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff;font-weight:bold">7</span><span style="background:#40e0ff"> </span><span style="background:#000000"> </span><span style="color:#ffffff;background:#000000">Total</span><span style="background:#000000"> </span><span style="background:#40e0ff"> </span><span style="color:#000000;background:#40e0ff">2158.4</span><span style="background:#40e0ff"> </span><span style="background:#ff80ff"> </span><span style="color:#000000;background:#ff80ff;font-weight:bold">539.6</span><span style="background:#ff80ff"> </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><span style="color:#ffffff;background:#000000">2877.8667</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"> </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">Sheet1</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@@ -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)">B7</span><span style="background:var(--vt-palette-7)"> </span> =SUM(B3:B5)3+<span style="color:var(--vt-palette-0)">B7</span> <span style="color:var(--vt-palette-0)">reads</span> Expenses<span style="color:var(--vt-palette-0)">;</span> <span style="color:var(--vt-palette-0)">read</span> <span style="color:var(--vt-palette-0)">by</span> C7, B30↓ <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Alt+'</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">list</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Alt+;</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">stop</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="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">B</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)">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> Household budget 20266+<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> 47+<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> Rent <span style="background:var(--vt-palette-2)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-2)">1450</span><span style="background:var(--vt-palette-2)"> </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> Groceries<span style="background:var(--vt-palette-2)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-2)">612.4</span><span style="background:var(--vt-palette-2)"> </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> Transit <span style="background:var(--vt-palette-2)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-2)">96</span><span style="background:var(--vt-palette-2)"> </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> Savings r <span style="color:var(--vt-palette-1);text-decoration:underline wavy var(--vt-palette-1)">#DIV/0!</span>11+<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="background:var(--vt-palette-5)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-5);font-weight:bold">539.6</span><span style="background:var(--vt-palette-5)"> </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> 2877.866713+<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+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</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@@ -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)">B7</span><span style="background:var(--vt-palette-8)"> </span> =SUM(B3:B5)3+<span style="color:var(--vt-palette-8)">B7</span> <span style="color:var(--vt-palette-8)">reads</span> Expenses<span style="color:var(--vt-palette-8)">;</span> <span style="color:var(--vt-palette-8)">read</span> <span style="color:var(--vt-palette-8)">by</span> C7, B30↓ <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)">list</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)">stop</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="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">B</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)">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> Household budget 20266+<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> 47+<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 <span style="background:var(--vt-palette-2)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-2)">1450</span><span style="background:var(--vt-palette-2)"> </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> Groceries<span style="background:var(--vt-palette-2)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-2)">612.4</span><span style="background:var(--vt-palette-2)"> </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 <span style="background:var(--vt-palette-2)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-2)">96</span><span style="background:var(--vt-palette-2)"> </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>11+<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="background:var(--vt-palette-5)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-5);font-weight:bold">539.6</span><span style="background:var(--vt-palette-5)"> </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> 2877.866713+<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+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</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@@ -0,0 +1,47 @@
1+package e2e2+3+import (4+ "strings"5+ "testing"6+)7+8+// Tracing stays on as the pointer moves, lists what's off screen, goes9+// to one from a list, and Evaluate formula steps into a reference and10+// back out.11+func TestTraceAndEvaluate(t *testing.T) {12+ s := start(t, "")13+ s.keys("<ctrl+g>", "C1", "<enter>")14+ s.keys("2", "<enter>", "=C1*3", "<enter>", "=C2+C1", "<enter>")15+ s.keys("<ctrl+g>", "C40", "<enter>", "=C3", "<enter>")16+ s.keys("<ctrl+g>", "C2", "<enter>")17+ s.waitForBar("C2", "=C1*3")18+19+ s.keys("<alt+;>")20+ s.waitFor("C2 reads C1↑; read by C3")21+ s.keys("<down>")22+ s.waitFor("C3 reads C2, C1↑; read by C40↓")23+24+ // Go to an off-screen dependent from the list.25+ s.keys("<alt+'>")26+ s.waitFor("Precedents and dependents of C3")27+ s.keys("C40", "<enter>")28+ s.waitForName("C40")29+ s.waitFor("C40 reads C3")30+31+ // Evaluate C40: step into C3, compute it, step out.32+ s.keys("<alt+=>")33+ s.waitFor("Evaluate C40")34+ s.waitFor("Next C3 = 8")35+ s.keys("<right>")36+ s.waitFor("Evaluate C40 › C3")37+ s.keys("<enter>", "<enter>", "<enter>")38+ s.waitFor("Value 8")39+ s.keys("<left>")40+ s.waitFor("Value 8")41+ s.eventually("back at C40", func() bool { return !strings.Contains(s.screen(), "›") })42+ s.keys("<esc>")43+ s.eventually("closed", func() bool { return strings.Contains(s.line(0), "READY") })44+45+ s.keys("<alt+;>")46+ s.eventually("tracing off", func() bool { return !strings.Contains(s.line(contextLine), "reads") })47+}@@ -170,10 +170,13 @@ func eval(n Node, get lookup) Value {
170170 }171171 172172 // evalOther evaluates what formulas rarely hold: array literals, names173-// LET and LAMBDA bind, LAMBDA calls, and arguments standing in for174-// arrays being mapped over.173+// LET and LAMBDA bind, LAMBDA calls, arguments standing in for arrays174+// being mapped over, and parts of a formula being stepped through175+// (probe.go).175176 func evalOther(n Node, get lookup) Value {176177 switch n := n.(type) {178+ case probe:179+ return n.eval(get)177180 case formula.Array:178181 return get.literal(n)179182 case formula.Local:@@ -0,0 +1,170 @@
1+package functions2+3+import (4+ "slices"5+6+ "github.com/FelineStateMachine/012/internal/formula"7+)8+9+// Parts of a formula evaluated where they stand, for stepping through it10+// as Excel's Evaluate Formula does (the engine's steps.go). Rather than a11+// second evaluator, the formula is evaluated once as usual with each12+// part wrapped in a probe, which records what the part computed there:13+// in its array context or not, inside LET with its names bound, and not14+// at all in an IF's branch not taken. Only operators, calls and LAMBDA15+// calls are wrapped: functions look at their arguments' nodes only to16+// tell references, ranges, names and literals apart, which stay as they17+// are, so a probe changes nothing a function sees.18+19+// Part is what one part of a formula computed the first time it was20+// computed.21+type Part struct {22+ Value Value23+ // Array holds the first rows and columns of an array the part24+ // computed, with Rows and Cols its whole size; nil otherwise.25+ Array *Array26+ // Lambda is set when the part computed a LAMBDA.27+ Lambda bool28+ // Reached is false for a part the formula never computed.29+ Reached bool30+}31+32+// previewSize is how many rows and columns of an array a Part keeps.33+const previewSize = 834+35+// probe is a part of a formula being recorded (see Part).36+type probe struct {37+ x Node38+ i int39+ parts []Part40+}41+42+// EvalParts evaluates n in the cell at, as EvalCell does, and returns43+// what it computed, with the array it spills, and what each part at44+// paths computed. A path is child45+// indexes from the root, in formula.EachChild's order; the empty path is46+// the whole formula. A path to a reference, name or literal records47+// nothing: those are read where they stand.48+func EvalParts(n Node, paths [][]int, get *Reader, at Addr) (Part, []Part) {49+ parts := make([]Part, len(paths))50+ for i, p := range paths {51+ n = wrapAt(n, p, probe{i: i, parts: parts})52+ }53+ top := Part{Value: EvalCell(n, get, at), Reached: true}54+ if sp := get.Spilled(); sp != nil {55+ top.Array = preview(sp)56+ }57+ return top, parts58+}59+60+// probed reports whether a part of this kind is wrapped in a probe.61+func probed(n Node) bool {62+ switch n.(type) {63+ case formula.Unary, formula.Binary, formula.Call, formula.Invoke, decUnary, decBinary:64+ return true65+ }66+ return false67+}68+69+// ReplaceAt returns a copy of n with the node at path, child indexes in70+// formula.EachChild's order, replaced by what f makes of it. The nodes71+// on the way are copied; n itself is left as it is.72+func ReplaceAt(n Node, path []int, f func(Node) Node) Node {73+ if len(path) == 0 {74+ return f(n)75+ }76+ return withChild(n, path[0], func(k Node) Node { return ReplaceAt(k, path[1:], f) })77+}78+79+// wrapAt returns n with the node at path wrapped in p, when it is one80+// probed.81+func wrapAt(n Node, path []int, p probe) Node {82+ if len(path) == 0 {83+ if !probed(n) {84+ return n85+ }86+ p.x = n87+ return p88+ }89+ return withChild(n, path[0], func(k Node) Node { return wrapAt(k, path[1:], p) })90+}91+92+// withChild returns a copy of n with its child i replaced by f's result,93+// looking through probes already in place.94+func withChild(n Node, i int, f func(Node) Node) Node {95+ switch n := n.(type) {96+ case probe:97+ n.x = withChild(n.x, i, f)98+ return n99+ case formula.Unary:100+ n.X = f(n.X)101+ return n102+ case decUnary:103+ n.X = f(n.X)104+ return n105+ case formula.Binary:106+ n.L, n.R = pick(i, n.L, n.R, f)107+ return n108+ case decBinary:109+ n.L, n.R = pick(i, n.L, n.R, f)110+ return n111+ case formula.Call:112+ n.Args = slices.Clone(n.Args)113+ n.Args[i] = f(n.Args[i])114+ return n115+ case formula.Invoke:116+ if i == 0 {117+ n.Fn = f(n.Fn)118+ return n119+ }120+ n.Args = slices.Clone(n.Args)121+ n.Args[i-1] = f(n.Args[i-1])122+ return n123+ case formula.Array:124+ rows := slices.Clone(n.Rows)125+ for r, row := range rows {126+ if i < len(row) {127+ rows[r] = slices.Clone(row)128+ rows[r][i] = f(row[i])129+ break130+ }131+ i -= len(row)132+ }133+ n.Rows = rows134+ return n135+ }136+ return n137+}138+139+// pick applies f to l or r, by i.140+func pick(i int, l, r Node, f func(Node) Node) (Node, Node) {141+ if i == 0 {142+ return f(l), r143+ }144+ return l, f(r)145+}146+147+// eval computes the part and records it, the first time.148+func (p probe) eval(get lookup) Value {149+ v := eval(p.x, get)150+ if pt := &p.parts[p.i]; !pt.Reached {151+ pt.Value, pt.Reached = v, true152+ if arr := get.arrayOf(v); arr != nil {153+ pt.Array = preview(arr)154+ }155+ pt.Lambda = get.lambdaOf(v) != nil156+ }157+ return v158+}159+160+// preview copies the first rows and columns of a.161+func preview(a *Array) *Array {162+ rows, cols := min(a.Rows, previewSize), min(a.Cols, previewSize)163+ out := &Array{Rows: a.Rows, Cols: a.Cols, DRows: rows, DCols: cols, V: make([]Value, rows*cols), Fill: a.Fill}164+ for r := range rows {165+ for c := range cols {166+ out.V[r*cols+c] = a.At(r, c)167+ }168+ }169+ return out170+}@@ -0,0 +1,58 @@
1+package sheet2+3+// overlapping calls fn with each formula one of whose ranges shares a4+// cell with r, as readers does for one cell; a formula may come more5+// than once. It walks r's columns or the indexed ones, whichever are6+// fewer.7+func (x *rangeIndex) overlapping(r Rect, fn func(Addr)) {8+ if r.From == r.To {9+ x.readers(r.From, fn)10+ return11+ }12+ if r.To.Col-r.From.Col < len(x.byCol) {13+ for c := r.From.Col; c <= r.To.Col; c++ {14+ if iv := x.byCol[c]; iv != nil {15+ iv.overlap(r, fn)16+ }17+ }18+ } else {19+ for c, iv := range x.byCol {20+ if c >= r.From.Col && c <= r.To.Col {21+ iv.overlap(r, fn)22+ }23+ }24+ }25+ x.wide.overlap(r, fn)26+}27+28+// overlap calls fn with the users of every range sharing a cell with r.29+func (iv *intervals) overlap(r Rect, fn func(Addr)) {30+ if len(iv.users) == 0 {31+ return32+ }33+ if iv.sorted == nil {34+ iv.build()35+ }36+ iv.queryRect(0, len(iv.sorted), r, fn)37+}38+39+// queryRect is query for the rows of r rather than one row.40+func (iv *intervals) queryRect(lo, hi int, r Rect, fn func(Addr)) {41+ for lo < hi {42+ mid := (lo + hi) / 243+ if iv.maxEnd[mid] < r.From.Row {44+ return // nothing below reaches r's rows45+ }46+ iv.queryRect(lo, mid, r, fn)47+ g := iv.sorted[mid]48+ if g.From.Row > r.To.Row {49+ return // this and everything after start below r50+ }51+ if _, over := intersectRect(g, r); over {52+ for u := range iv.users[g] {53+ fn(u)54+ }55+ }56+ lo = mid + 157+ }58+}@@ -0,0 +1,228 @@
1+package sheet2+3+import (4+ "slices"5+6+ "github.com/FelineStateMachine/012/internal/formula"7+ "github.com/FelineStateMachine/012/internal/functions"8+)9+10+// Steps is a formula evaluated one part at a time, as Excel's Evaluate11+// Formula: the part computed next is marked in the formula, computing it12+// puts its value in its place, and a reference to another formula can be13+// stepped into. The values come from one evaluation by the function14+// library with each part recorded where it stands (functions.EvalParts),15+// so they are what the formula computes, context and all, and a branch16+// IF didn't take is never computed. References are read as the formula17+// reads them: the cell's value.18+type Steps struct {19+ sheet *Sheet20+ at Addr21+ expr Node // the formula as written, which Text prints22+ parts []part // in the order they're computed: operands first, left to right23+ next int // the part computed next; len(parts) once done24+ top functions.Part25+ index map[string]int // parts by pathKey, while references are read26+}27+28+// part is one part of the formula: an operator, a call or a reference.29+type part struct {30+ path []int31+ node Node // as written32+ functions.Part33+ into loc // a reference to a formula's cell, which can be stepped into34+}35+36+// EvaluateSteps starts evaluating the formula at a step by step, or37+// returns nil when a holds no formula.38+func (s *Sheet) EvaluateSteps(a Addr) *Steps {39+ c, _, _ := s.cells.peek(a)40+ if c == nil || !c.IsFormula() {41+ return nil42+ }43+ st := &Steps{sheet: s, at: a, expr: c.expr}44+ var paths [][]int45+ st.collect(c.expr, nil, func(p []int, n Node) {46+ st.parts = append(st.parts, part{path: p, node: n})47+ paths = append(paths, p)48+ })49+ run := s.wb.arith(s.bound(a, c))50+ var parts []functions.Part51+ st.top, parts = functions.EvalParts(run, paths, s.wb.values(s).lib, a)52+ for i := range st.parts {53+ st.parts[i].Part = parts[i]54+ }55+ st.index = make(map[string]int, len(st.parts))56+ for i, p := range st.parts {57+ st.index[pathKey(p.path)] = i58+ }59+ st.readRefs()60+ st.index = nil61+ st.parts = slices.DeleteFunc(st.parts, func(p part) bool { return !p.Reached })62+ return st63+}64+65+// collect calls fn with the path of each part of n, operands before66+// what they're operands of: operators, calls, LAMBDA calls, and67+// references to one cell, written or named.68+func (st *Steps) collect(n Node, path []int, fn func([]int, Node)) {69+ i := 070+ formula.EachChild(n, func(k Node) {71+ st.collect(k, append(slices.Clip(path), i), fn)72+ i++73+ })74+ switch n := n.(type) {75+ case formula.Unary, formula.Binary, formula.Call, formula.Invoke, formula.Ref:76+ fn(path, n)77+ case formula.Name:78+ if nm, ok := st.sheet.wb.names[nameKey(n.Name)]; ok && !nm.Gone() && nm.Range.From == nm.Range.To {79+ fn(path, n)80+ }81+ case formula.TableRef:82+ if _, ok := st.sheet.bindTable(st.at, n, 0).(formula.Ref); ok {83+ fn(path, n) // one cell, as Sales[@Amount] is84+ }85+ }86+}87+88+// readRefs reads the references among the parts, as the formula reads89+// them, where the formula reached them.90+func (st *Steps) readRefs() {91+ for i := range st.parts {92+ p := &st.parts[i]93+ t, a, ok := st.refCell(p.node)94+ if !ok || !st.reached(p.path) {95+ continue96+ }97+ p.Reached = true98+ if t == nil {99+ p.Value = ErrRef100+ continue101+ }102+ p.Value = t.Value(a)103+ if c, _, _ := t.cells.peek(a); c != nil && c.IsFormula() {104+ p.into = loc{t, a}105+ }106+ }107+}108+109+// refCell is the cell a reference, single-cell name or table reference110+// points at, and111+// its sheet (nil for a sheet no sheet has the name of).112+func (st *Steps) refCell(n Node) (*Sheet, Addr, bool) {113+ s := st.sheet114+ switch n := n.(type) {115+ case formula.Ref:116+ return s.wb.resolve(s, n.Sheet), n.Addr, true117+ case formula.Name:118+ nm := s.wb.names[nameKey(n.Name)]119+ return nm.Sheet, nm.Range.From, true120+ case formula.TableRef:121+ if r, ok := s.bindTable(st.at, n, 0).(formula.Ref); ok {122+ return s.wb.resolve(s, r.Sheet), r.Addr, true123+ }124+ }125+ return nil, Addr{}, false126+}127+128+// reached reports whether the formula computed what's at path, a129+// reference: when the part it's in was computed, and, for an argument of130+// IF, IFERROR, IFNA or CHOOSE, when that call took it.131+func (st *Steps) reached(path []int) bool {132+ for up := len(path) - 1; up >= 0; up-- {133+ i, ok := st.index[pathKey(path[:up])]134+ if !ok {135+ continue // an array literal: go on out136+ }137+ o := &st.parts[i]138+ if !o.Reached {139+ return false140+ }141+ if up == len(path)-1 {142+ return st.taken(o, path[up])143+ }144+ return true145+ }146+ return true147+}148+149+// taken reports whether the call o, computed, took its argument arg.150+func (st *Steps) taken(o *part, arg int) bool {151+ call, ok := o.node.(formula.Call)152+ if !ok || arg == 0 {153+ return true154+ }155+ first := st.valueAt(append(slices.Clip(o.path), 0), call.Args[0])156+ switch call.Fn.Signature().Name {157+ case "IF":158+ if first.Kind == Error || first.Kind == Text {159+ return false160+ }161+ return (first.Num != 0) == (arg == 1)162+ case "IFERROR":163+ return first.Kind == Error164+ case "IFNA":165+ return first == ErrNA166+ case "CHOOSE":167+ return first.Kind == Number && int(first.Num) == arg168+ }169+ return true170+}171+172+// valueAt is what the node n at path computed: a part's recorded value,173+// or a literal's own.174+func (st *Steps) valueAt(path []int, n Node) Value {175+ if i, ok := st.index[pathKey(path)]; ok && st.parts[i].Reached {176+ return st.parts[i].Value177+ }178+ if t, a, ok := st.refCell(n); ok && t != nil {179+ return t.Value(a)180+ }181+ return functions.EvalAt(n, st.sheet.wb.values(st.sheet).lib, st.at)182+}183+184+// Cell is the formula's cell.185+func (st *Steps) Cell() (*Sheet, Addr) { return st.sheet, st.at }186+187+// Done reports whether every part is computed.188+func (st *Steps) Done() bool { return st.next >= len(st.parts) }189+190+// Progress is how many parts are computed, of how many.191+func (st *Steps) Progress() (done, total int) { return st.next, len(st.parts) }192+193+// Step computes the next part.194+func (st *Steps) Step() {195+ if !st.Done() {196+ st.next++197+ }198+}199+200+// Restart goes back to the formula as written.201+func (st *Steps) Restart() { st.next = 0 }202+203+// Next is the part computed next, as written, and its value as the204+// formula would write it; ok is false once done.205+func (st *Steps) Next() (expr, value string, ok bool) {206+ if st.Done() {207+ return "", "", false208+ }209+ p := st.parts[st.next]210+ return formula.Expr(p.node), partText(p.Part), true211+}212+213+// Result is the formula's value, an array it spills written whole.214+func (st *Steps) Result() string { return partText(st.top) }215+216+// CanStepIn reports whether the next part is a reference to a formula's217+// cell, which Into steps into.218+func (st *Steps) CanStepIn() bool { return !st.Done() && st.parts[st.next].into.s != nil }219+220+// Into starts stepping through the formula the next part refers to,221+// when it is a reference to a formula's cell; nil otherwise.222+func (st *Steps) Into() *Steps {223+ if !st.CanStepIn() {224+ return nil225+ }226+ l := st.parts[st.next].into227+ return l.s.EvaluateSteps(l.a)228+}@@ -0,0 +1,118 @@
1+package sheet2+3+import (4+ "strings"5+ "testing"6+)7+8+// shown writes what Steps shows: values in [], the next part in _.9+func shown(st *Steps) string {10+ var out strings.Builder11+ in := false12+ for _, sg := range st.Text() {13+ if sg.Next != in {14+ out.WriteByte('_')15+ in = sg.Next16+ }17+ t := sg.Text18+ if sg.Value {19+ t = "[" + t + "]"20+ }21+ out.WriteString(t)22+ }23+ if in {24+ out.WriteByte('_')25+ }26+ return out.String()27+}28+29+// walk steps through the formula at a, returning what it shows before30+// each step and at the end.31+func walk(t *testing.T, s *Sheet, a string) []string {32+ t.Helper()33+ st := s.EvaluateSteps(at(a))34+ if st == nil {35+ t.Fatalf("%s: no steps", a)36+ }37+ var out []string38+ for !st.Done() {39+ out = append(out, shown(st))40+ st.Step()41+ }42+ return append(out, shown(st))43+}44+45+func TestSteps(t *testing.T) {46+ s := sheetOf(t, map[string]string{47+ "A1": "5", "A2": "2", "A3": "x",48+ "B1": "=A1+A2*3",49+ "B2": "=IF(A1>3, A1*2, A2+1)",50+ "B3": "=(A1+A2)*2",51+ "B4": "=SUM(A1:A2*2)",52+ "B5": "=LET(x, A1, x*2)",53+ "B6": "=A3&D9",54+ "B7": "=IFERROR(1/0, A2)",55+ "B8": "=SEQUENCE(2)",56+ })57+ for _, tc := range []struct {58+ cell string59+ want string60+ }{61+ {"B1", "=_A1_+A2*3 | =[5]+_A2_*3 | =[5]+_[2]*3_ | =_[5]+[6]_ | [11]"},62+ // The branch IF doesn't take is never computed.63+ {"B2", "=IF(_A1_>3,A1*2,A2+1) | =IF(_[5]>3_,A1*2,A2+1) | =IF([TRUE],_A1_*2,A2+1) | =IF([TRUE],_[5]*2_,A2+1) | =_IF([TRUE],[10],A2+1)_ | [10]"},64+ // The part computed next keeps its parentheses.65+ {"B3", "=(_A1_+A2)*2 | =([5]+_A2_)*2 | =_([5]+[2])_*2 | =_[7]*2_ | [14]"},66+ // In an array context a part computes an array.67+ {"B4", "=SUM(_A1:A2*2_) | =_SUM([{10;4}])_ | [14]"},68+ {"B5", "=LET(x,_A1_,x*2) | =LET(x,[5],_x*2_) | =_LET(x,[5],[10])_ | [10]"},69+ {"B6", `=_A3_&D9 | =["x"]&_D9_ | =_["x"]&[0]_ | ["x"]`},70+ {"B7", "=IFERROR(_1/0_,A2) | =IFERROR([#DIV/0!],_A2_) | =_IFERROR([#DIV/0!],[2])_ | [2]"},71+ {"B8", "=_SEQUENCE(2)_ | [{1;2}]"},72+ } {73+ if got := strings.Join(walk(t, s, tc.cell), " | "); got != tc.want {74+ t.Errorf("%s:\n got %s\nwant %s", tc.cell, got, tc.want)75+ }76+ }77+ if s.EvaluateSteps(at("A1")) != nil {78+ t.Error("steps for a number")79+ }80+}81+82+func TestStepsDecimal(t *testing.T) {83+ s := sheetOf(t, map[string]string{"A1": "0.1", "B1": "=A1+0.2"})84+ s.SetDecimal(true)85+ if got := strings.Join(walk(t, s, "B1"), " | "); got != "=_A1_+0.2 | =_[0.1]+0.2_ | [0.3]" {86+ t.Errorf("decimal: %s", got)87+ }88+}89+90+func TestStepsInto(t *testing.T) {91+ s := sheetOf(t, map[string]string{"A1": "4", "B1": "=A1*2", "C1": "=B1+Rate"})92+ s.DefineName("Rate", rect("A1"))93+ st := s.EvaluateSteps(at("C1"))94+ if expr, v, _ := st.Next(); expr != "B1" || v != "8" {95+ t.Errorf("next %q = %q", expr, v)96+ }97+ in := st.Into()98+ if in == nil {99+ t.Fatal("no stepping into B1")100+ }101+ if _, a := in.Cell(); a != at("B1") || shown(in) != "=_A1_*2" {102+ t.Errorf("into %v: %s", a, shown(in))103+ }104+ if in.Into() != nil {105+ t.Error("stepped into a number")106+ }107+ st.Step()108+ if expr, v, _ := st.Next(); expr != "Rate" || v != "4" {109+ t.Errorf("a name: %q = %q", expr, v)110+ }111+ if done, total := st.Progress(); done != 1 || total != 3 {112+ t.Errorf("progress %d of %d", done, total)113+ }114+ st.Restart()115+ if shown(st) != "=_B1_+Rate" {116+ t.Errorf("restart: %s", shown(st))117+ }118+}@@ -0,0 +1,149 @@
1+package sheet2+3+import (4+ "slices"5+ "strconv"6+ "strings"7+8+ "github.com/FelineStateMachine/012/internal/formula"9+ "github.com/FelineStateMachine/012/internal/functions"10+)11+12+// How Steps shows the formula: as written, with the parts computed so13+// far written as their values and the part computed next marked. The14+// formula printer writes it, from a copy of the formula in which each15+// value stands as a name made of a marker, so the parentheses come out16+// as they would; the marked part is found by printing the copy twice,17+// once with a marker in its place.18+19+// Segment is a piece of the formula Steps shows.20+type Segment struct {21+ Text string22+ // Value marks a value a part computed, written in its place.23+ Value bool24+ // Next marks the part computed next.25+ Next bool26+}27+28+// Markers in the printed formula: a value, \x01 and its index and \x02,29+// and the part computed next, \x03.30+const (31+ valueOpen = "\x01"32+ valueClose = "\x02"33+ nextMark = "\x03"34+)35+36+// Text is the formula as it stands: "=" and its text, with the parts37+// computed so far as their values and the next part marked, or once38+// every part is computed, the formula's value.39+func (st *Steps) Text() []Segment {40+ if st.Done() {41+ return []Segment{{Text: st.Result(), Value: true}}42+ }43+ shown, values := st.expr, []string(nil)44+ var replaced [][]int45+ for i := st.next - 1; i >= 0; i-- {46+ p := st.parts[i]47+ if inside(p.path, replaced) {48+ continue49+ }50+ replaced = append(replaced, p.path)51+ mark := formula.Name{Name: valueOpen + strconv.Itoa(len(values)) + valueClose}52+ values = append(values, partText(p.Part))53+ shown = functions.ReplaceAt(shown, p.path, func(Node) Node { return mark })54+ }55+ whole := formula.Expr(shown)56+ marked := formula.Expr(functions.ReplaceAt(shown, st.parts[st.next].path, func(Node) Node { return formula.Name{Name: nextMark} }))57+ at := strings.Index(marked, nextMark)58+ after := marked[at+len(nextMark):]59+ end := len(whole) - len(after)60+ out := []Segment{{Text: "="}}61+ out = appendValues(out, whole[:at], values, false)62+ out = appendValues(out, whole[at:end], values, true)63+ return appendValues(out, whole[end:], values, false)64+}65+66+// inside reports whether path is within one of paths.67+func inside(path []int, paths [][]int) bool {68+ for _, p := range paths {69+ if len(p) <= len(path) && slices.Equal(p, path[:len(p)]) {70+ return true71+ }72+ }73+ return false74+}75+76+// appendValues splits text at its value markers into segments.77+func appendValues(out []Segment, text string, values []string, next bool) []Segment {78+ for text != "" {79+ i := strings.Index(text, valueOpen)80+ if i < 0 {81+ return append(out, Segment{Text: text, Next: next})82+ }83+ if i > 0 {84+ out = append(out, Segment{Text: text[:i], Next: next})85+ }86+ j := strings.Index(text, valueClose)87+ k, _ := strconv.Atoi(text[i+len(valueOpen) : j])88+ out = append(out, Segment{Text: values[k], Value: true, Next: next})89+ text = text[j+len(valueClose):]90+ }91+ return out92+}93+94+// partText writes what a part computed as a formula would write it: a95+// number plainly, text in quotes, an array in braces (its first rows and96+// columns, then …). A blank reads as 0, as it does in arithmetic.97+func partText(p functions.Part) string {98+ switch {99+ case p.Lambda:100+ return "LAMBDA"101+ case p.Array != nil:102+ return arrayText(p.Array)103+ }104+ return valueText(p.Value)105+}106+107+func valueText(v Value) string {108+ switch v.Kind {109+ case Empty:110+ return "0"111+ case Text:112+ return `"` + strings.ReplaceAll(v.Str, `"`, `""`) + `"`113+ }114+ return v.String()115+}116+117+func arrayText(a *functions.Array) string {118+ var b strings.Builder119+ b.WriteByte('{')120+ for r := range a.DRows {121+ if r > 0 {122+ b.WriteByte(';')123+ }124+ for c := range a.DCols {125+ if c > 0 {126+ b.WriteByte(',')127+ }128+ b.WriteString(valueText(a.At(r, c)))129+ }130+ if a.Cols > a.DCols {131+ b.WriteString(",…")132+ }133+ }134+ if a.Rows > a.DRows {135+ b.WriteString(";…")136+ }137+ b.WriteByte('}')138+ return b.String()139+}140+141+// pathKey is a path as a map key.142+func pathKey(path []int) string {143+ var b strings.Builder144+ for _, i := range path {145+ b.WriteString(strconv.Itoa(i))146+ b.WriteByte('.')147+ }148+ return b.String()149+}@@ -269,3 +269,25 @@ func TestRegionIsTable(t *testing.T) {
269269 t.Error("a table took a region's name")270270 }271271 }272+273+// Tracing follows structured references: a formula's precedents name274+// the table's cells as written, and a table's cells list the formulas275+// reading them.276+func TestTableTracing(t *testing.T) {277+ s := salesSheet(t)278+ s.Set(at("E1"), "=SUM(Sales[Amount])+ROWS(Sales)")279+ s.Set(at("D3"), "=Sales[@Units]*2")280+ links := s.PrecedentLinks(at("E1"))281+ if len(links) != 2 || links[0].Kind != LinkTable || links[0].Name != "Sales[Amount]" || links[0].Range != rect("C2:C4") ||282+ links[1].Name != "Sales" || links[1].Range != rect("A2:C4") {283+ t.Errorf("precedents %+v", links)284+ }285+ deps, _ := s.DependentLinks(at("B3"), 0)286+ if len(deps) != 2 {287+ t.Errorf("dependents of B3 %+v", deps)288+ }289+ st := s.EvaluateSteps(at("D3"))290+ if expr, value, ok := st.Next(); !ok || expr != "Sales[@Units]" || value != "3" || st.Result() != "6" {291+ t.Errorf("first step %q = %q, result %q", expr, value, st.Result())292+ }293+}@@ -1,8 +1,8 @@
11 package sheet22 33 import (4- "cmp"54 "slices"5+ "strings"66 77 "github.com/FelineStateMachine/012/internal/formula"88 )@@ -10,7 +10,10 @@ import (
1010 // Tracing, as Excel's Trace Precedents and Trace Dependents: which cells1111 // a formula reads, and which formulas read a cell, on any sheet. Only1212 // direct links are followed; tracing again from a found cell goes a level13-// further.13+// further. The dependency indexes recalculation keeps answer both ways,14+// so tracing costs what it finds, not the sheet: a cell's precedents are15+// its formula's references, and its dependents come from the indexes by16+// cell, by range and by name (tracedeps.go).1417 1518 // Target is a traced cell or range and the sheet it is on.1619 type Target struct {@@ -18,61 +21,159 @@ type Target struct {
1821 Range Rect1922 }2023 24+// LinkKind is how a traced cell is linked to the one traced.25+type LinkKind uint826+27+const (28+ // LinkRef is a reference or range the formula writes out, or a29+ // formula reading the cell through one.30+ LinkRef LinkKind = iota31+ // LinkName is a named range the formula uses: Link.Name is its name.32+ LinkName33+ // LinkRegion is a region's table the formula names, nu.sales:34+ // Link.Name is how the formula names it.35+ LinkRegion36+ // LinkSpill is the array formula whose values the cell shows, among37+ // precedents, or the cells a formula spills into, among dependents.38+ LinkSpill39+ // LinkFile is the linked file a region's cells come from: Link.Name40+ // is its path, and Target the region's cells.41+ LinkFile42+ // LinkTable is a table's cells a formula reads by a structured43+ // reference or the table's name alone: Link.Name is the reference as44+ // written, Sales[Amount].45+ LinkTable46+ // LinkOutput is the notebook cell whose output a region shows:47+ // Link.Name is the cell's name, Target.Sheet the notebook tab and48+ // Target.Range.From.Row the cell's index there.49+ LinkOutput50+)51+52+// Link is a precedent or dependent: where it is, and how it's linked.53+type Link struct {54+ Target55+ Kind LinkKind56+ Name string // see LinkKind57+}58+59+// OnGrid reports whether the link is cells of a sheet's grid rather than60+// a notebook cell.61+func (l Link) OnGrid() bool { return !l.Sheet.IsNotebook() }62+2163 // Precedents returns the cells and ranges the formula at a reads, in the22-// order the formula mentions them, with named ranges resolved and23-// repeats dropped. References to sheets that don't exist are left out. It24-// is empty for anything but a formula.25-func (s *Sheet) Precedents(a Addr) []Target {26- c := s.cells.get(a)27- if c == nil || !c.IsFormula() {28- return nil29- }64+// order the formula mentions them; see PrecedentLinks.65+func (s *Sheet) Precedents(a Addr) []Target { return targetsOf(s.PrecedentLinks(a)) }66+67+// Dependents returns the formula cells that read a directly; see68+// DependentLinks.69+func (s *Sheet) Dependents(a Addr) []Target {70+ links, _ := s.DependentLinks(a, 0)71+ return targetsOf(links)72+}73+74+func targetsOf(links []Link) []Target {3075 var out []Target31- add := func(sheet string, r Rect) {32- t := Target{s.wb.resolve(s, sheet), r}33- if t.Sheet != nil && !slices.Contains(out, t) {34- out = append(out, t)76+ for _, l := range links {77+ if l.OnGrid() {78+ out = append(out, l.Target)3579 }3680 }37- formula.WalkRefs(s.bound(a, c), func(sheet string, a Addr) { add(sheet, Rect{From: a, To: a}) }, add)3881 return out3982 }4083 41-// Dependents returns the formula cells that read a directly, through a42-// reference, a range, a named range or a table: those on this sheet first in43-// row-major order, then those on other sheets in tab order.44-func (s *Sheet) Dependents(a Addr) []Target {45- w := s.wb46- seen := map[loc]bool{}47- for d := range s.dependents[a] {48- seen[loc{s, d}] = true84+// PrecedentLinks returns what the cell at a comes from. For a formula,85+// the cells and ranges it reads in the order it mentions them, named86+// ranges and regions as their cells, with repeats dropped and references87+// to sheets that don't exist left out. For a cell an array spilled into,88+// the formula that spilled it; for a region's cell, the linked file or89+// notebook cell the region shows.90+func (s *Sheet) PrecedentLinks(a Addr) []Link {91+ var out []Link92+ add := func(l Link) {93+ if l.Sheet != nil && !slices.ContainsFunc(out, func(o Link) bool { return o.Target == l.Target && o.Kind == l.Kind }) {94+ out = append(out, l)95+ }96+ }97+ if c, _, _ := s.cells.peek(a); c != nil && c.IsFormula() {98+ s.walkPrecedents(c.expr, a, add)99+ }100+ if anchor, ok := s.SpillAnchor(a); ok {101+ add(Link{Target: Target{s, Rect{From: anchor, To: anchor}}, Kind: LinkSpill})102+ }103+ if r, ok := s.RegionAt(a); ok {104+ add(s.regionSource(r))49105 }50- s.rangeUsers.readers(a, func(u Addr) { seen[loc{s, u}] = true })51- for _, nu := range w.namedInUse() {52- if nu.s == s && nu.r.Contains(a) {53- for u := range nu.users {54- seen[u] = true106+ return out107+}108+109+// walkPrecedents calls add with each reference, range, name and table110+// in n, the formula at a, in the order they're written.111+func (s *Sheet) walkPrecedents(n Node, a Addr, add func(Link)) {112+ w := s.wb113+ switch n := n.(type) {114+ case formula.Ref:115+ add(Link{Target: Target{w.resolve(s, n.Sheet), Rect{From: n.Addr, To: n.Addr}}})116+ case formula.Range:117+ add(Link{Target: Target{w.resolve(s, n.Sheet), n.Rect}})118+ case formula.Name:119+ if nm, ok := w.names[nameKey(n.Name)]; ok {120+ if !nm.Gone() {121+ add(Link{Target: Target{nm.Sheet, nm.Range}, Kind: LinkName, Name: nm.Name})55122 }123+ return56124 }57- }58- for u := range w.crossUsers {59- if w.crossReads(u, loc{s, a}) {60- seen[u] = true125+ if _, found := w.findTable(nameKey(n.Name)); found {126+ s.tableLink(a, formula.TableRef{Table: n.Name}, n.Name, add)127+ return128+ }129+ if t, r, ok := w.regionName(nameKey(n.Name)); ok && r != (Rect{}) {130+ add(Link{Target: Target{t, r}, Kind: LinkRegion, Name: s.regionSpelling(n.Name)})61131 }132+ case formula.TableRef:133+ s.tableLink(a, n, n.String(), add)134+ default:135+ formula.EachChild(n, func(k Node) { s.walkPrecedents(k, a, add) })136+ }137+}138+139+// tableLink adds the cells the structured reference t, written as name140+// in the formula at a, reads, when it reads any.141+func (s *Sheet) tableLink(a Addr, t formula.TableRef, name string, add func(Link)) {142+ var r Target143+ switch n := s.bindTable(a, t, 0).(type) {144+ case formula.Ref:145+ r = Target{s.wb.resolve(s, n.Sheet), Rect{From: n.Addr, To: n.Addr}}146+ case formula.Range:147+ r = Target{s.wb.resolve(s, n.Sheet), n.Rect}148+ default:149+ return150+ }151+ add(Link{Target: r, Kind: LinkTable, Name: name})152+}153+154+// regionSpelling is how formulas name the region a name like NU.SALES155+// finds: nu. and the region's own name.156+func (s *Sheet) regionSpelling(name string) string {157+ if _, r, ok := s.wb.Region(name[len(regionPrefix):]); ok {158+ return r.FormulaName()62159 }63- out := make([]Target, 0, len(seen))64- for l := range seen {65- out = append(out, Target{l.s, Rect{From: l.a, To: l.a}})160+ return strings.ToLower(name)161+}162+163+// regionSource is where a region's cells come from: its linked file, or164+// the notebook cell whose output it shows (on the notebook's tab, when165+// there is one; otherwise the region itself).166+func (s *Sheet) regionSource(r Region) Link {167+ area := Target{s, s.covered(r)}168+ if r.Linked() {169+ return Link{Target: area, Kind: LinkFile, Name: r.File.Path}66170 }67- rank := func(t *Sheet) int {68- if t == s {69- return -1171+ for _, nb := range s.wb.sheets {172+ for i, c := range nb.regions.cells {173+ if strings.EqualFold(c.Name(), r.Name) {174+ return Link{Target: Target{nb, Rect{From: Addr{Row: i}, To: Addr{Row: i}}}, Kind: LinkOutput, Name: c.Name()}175+ }70176 }71- return w.Index(t)72177 }73- slices.SortFunc(out, func(x, y Target) int {74- return cmp.Or(cmp.Compare(rank(x.Sheet), rank(y.Sheet)),75- cmp.Compare(x.Range.From.Row, y.Range.From.Row), cmp.Compare(x.Range.From.Col, y.Range.From.Col))76- })77- return out178+ return Link{Target: area, Kind: LinkOutput, Name: r.Name}78179 }@@ -0,0 +1,101 @@
1+package sheet2+3+import (4+ "strings"5+ "testing"6+)7+8+// links writes links as they'd be listed: the range, with its sheet when9+// not s, and the kind's name where it isn't a plain reference.10+func links(s *Sheet, ls []Link) string {11+ var out []string12+ for _, l := range ls {13+ ref := l.Range.String()14+ if l.Sheet != s {15+ ref = Qualified(l.Sheet.Name(), l.Range)16+ }17+ switch l.Kind {18+ case LinkName, LinkRegion, LinkFile, LinkOutput:19+ ref = l.Name + "=" + ref20+ case LinkSpill:21+ ref = "spill " + ref22+ }23+ out = append(out, ref)24+ }25+ return strings.Join(out, " ")26+}27+28+func TestPrecedentLinks(t *testing.T) {29+ s := sheetOf(t, map[string]string{30+ "A1": "1", "A2": "2", "B1": "=SEQUENCE(3)", "C1": "=Rate*A1+SUM(B1:B3)", "C2": "=missing!A1+A1",31+ })32+ s.DefineName("Rate", rect("A2"))33+ if got, want := links(s, s.PrecedentLinks(at("C1"))), "Rate=A2 A1 B1:B3"; got != want {34+ t.Errorf("C1 = %q, want %q", got, want)35+ }36+ if got, want := links(s, s.PrecedentLinks(at("C2"))), "A1"; got != want {37+ t.Errorf("an unknown sheet: %q, want %q", got, want)38+ }39+ // A spilled cell comes from its anchor.40+ if got, want := links(s, s.PrecedentLinks(at("B3"))), "spill B1"; got != want {41+ t.Errorf("B3 = %q, want %q", got, want)42+ }43+ if got := s.PrecedentLinks(at("A1")); got != nil {44+ t.Errorf("a number: %v", got)45+ }46+}47+48+func TestDependentLinks(t *testing.T) {49+ s := sheetOf(t, map[string]string{50+ "A1": "=SEQUENCE(3)", "C1": "=A2*2", "C2": "=SUM(A1:A9)", "C3": "=A1", "D5": "=B1",51+ })52+ other, _ := s.Book().AddSheet("Other", 1)53+ other.Set(at("A1"), "=Sheet1!A3")54+ s.DefineName("Mid", rect("A2"))55+ other.Set(at("B1"), "=Mid")56+ // The anchor's dependents read any cell it spills into.57+ got, more := s.DependentLinks(at("A1"), 0)58+ if want := "spill A1:A3 C1 C2 C3 Other!A1 Other!B1"; links(s, got) != want || more {59+ t.Errorf("A1 = %q (more %v), want %q", links(s, got), more, want)60+ }61+ if got, _ := s.DependentLinks(at("A2"), 0); links(s, got) != "C1 C2 Other!B1" {62+ t.Errorf("A2 = %q", links(s, got))63+ }64+ // A cap finds that many and says there are more.65+ got, more = s.DependentLinks(at("A1"), 2)66+ if len(got) != 3 || !more {67+ t.Errorf("capped: %q, more %v", links(s, got), more)68+ }69+ if !s.Reads(at("C2"), s, rect("A3")) || !other.Reads(at("B1"), s, rect("A1:A2")) || s.Reads(at("D5"), s, rect("A1:A3")) {70+ t.Error("Reads")71+ }72+ if s.Reads(at("A2"), s, rect("A1")) {73+ t.Error("a spilled cell reads its anchor")74+ }75+}76+77+func TestTraceRegions(t *testing.T) {78+ s := New()79+ nb := sent(t, s, "files", "B2")80+ s.Set(at("A1"), "=ROWS(nu.files)")81+ apply(t, s, LiveOp{Region: "files", Reset: true, Header: liveRow("n"), Rows: []LiveRow{liveRow("a"), liveRow("b")}})82+ if got, want := links(s, s.PrecedentLinks(at("A1"))), "nu.files=B2:B4"; got != want {83+ t.Errorf("A1 = %q, want %q", got, want)84+ }85+ // The region's cells come from the notebook cell.86+ if got, want := links(s, s.PrecedentLinks(at("B3"))), "files="+Qualified(nb.Name(), rect("A1")); got != want {87+ t.Errorf("B3 = %q, want %q", got, want)88+ }89+ if got, _ := s.DependentLinks(at("B4"), 0); links(s, got) != "A1" {90+ t.Errorf("B4's dependents = %q", links(s, got))91+ }92+ if !s.Reads(at("A1"), s, rect("B3")) {93+ t.Error("Reads through a region")94+ }95+ if _, err := s.AddLinked(at("F1"), LinkSource{Path: "log.csv"}); err != nil {96+ t.Fatal(err)97+ }98+ if got, want := links(s, s.PrecedentLinks(at("F1"))), "log.csv=F1"; got != want {99+ t.Errorf("F1 = %q, want %q", got, want)100+ }101+}@@ -0,0 +1,179 @@
1+package sheet2+3+import (4+ "cmp"5+ "slices"6+)7+8+// Dependents, for tracing (trace.go): the formulas reading a cell, found9+// through the indexes recalculation keeps, and Reads, which asks the10+// same of one formula, so what draws the sheet can mark the dependents it11+// shows without listing them all.12+13+// DependentLinks returns the formulas that read the cell at a directly:14+// through a reference, a range, a named range or a region's name. When15+// the formula at a spills, the cells it spills into come first, and the16+// formulas reading any of them count too. Formulas on this sheet come in17+// row-major order, then those on other sheets in tab order. With max > 018+// at most max formulas are found, and more reports that there were19+// others: a cell read by a million formulas costs what max does.20+func (s *Sheet) DependentLinks(a Addr, max int) (links []Link, more bool) {21+ subject := Rect{From: a, To: a}22+ if area, ok := s.SpillArea(a); ok && area != subject {23+ subject = area24+ links = append(links, Link{Target: Target{s, area}, Kind: LinkSpill})25+ }26+ f := &depFinder{max: max, seen: map[loc]struct{}{}}27+ s.readersOf(subject, f)28+ found := make([]Link, 0, len(f.seen))29+ for l := range f.seen {30+ found = append(found, Link{Target: Target{l.s, Rect{From: l.a, To: l.a}}})31+ }32+ w := s.wb33+ rank := func(t *Sheet) int {34+ if t == s {35+ return -136+ }37+ return w.Index(t)38+ }39+ slices.SortFunc(found, func(x, y Link) int {40+ return cmp.Or(cmp.Compare(rank(x.Sheet), rank(y.Sheet)),41+ cmp.Compare(x.Range.From.Row, y.Range.From.Row), cmp.Compare(x.Range.From.Col, y.Range.From.Col))42+ })43+ return append(links, found...), f.more44+}45+46+// depFinder collects dependents, up to max when it's positive.47+type depFinder struct {48+ max int49+ seen map[loc]struct{}50+ more bool51+}52+53+func (f *depFinder) add(l loc) {54+ if _, ok := f.seen[l]; ok || f.more {55+ return56+ }57+ if f.max > 0 && len(f.seen) >= f.max {58+ f.more = true59+ return60+ }61+ f.seen[l] = struct{}{}62+}63+64+// readersOf adds the formulas that read any cell of r, on s.65+func (s *Sheet) readersOf(r Rect, f *depFinder) {66+ s.refReaders(r, f)67+ s.rangeUsers.overlapping(r, func(u Addr) { f.add(loc{s, u}) })68+ w := s.wb69+ for k, users := range w.nameUsers {70+ if f.more || !w.namesOverlap(k, s, r) {71+ continue72+ }73+ for u := range users {74+ if f.add(u); f.more {75+ break76+ }77+ }78+ }79+ for u := range w.crossUsers {80+ if f.more {81+ return82+ }83+ if c := u.s.cells.get(u.a); c != nil && w.xrefsOverlap(c, s, r) {84+ f.add(u)85+ }86+ }87+}88+89+// refReaders adds the formulas on s with a single-cell reference into90+// r, walking whichever is smaller: r's cells or the cells referenced.91+func (s *Sheet) refReaders(r Rect, f *depFinder) {92+ add := func(users map[Addr]struct{}) {93+ for u := range users {94+ if f.add(loc{s, u}); f.more {95+ return96+ }97+ }98+ }99+ if n := (r.To.Row - r.From.Row + 1) * (r.To.Col - r.From.Col + 1); n <= len(s.dependents) {100+ for row := r.From.Row; row <= r.To.Row && !f.more; row++ {101+ for col := r.From.Col; col <= r.To.Col; col++ {102+ add(s.dependents[Addr{Col: col, Row: row}])103+ }104+ }105+ return106+ }107+ for a, users := range s.dependents {108+ if f.more {109+ return110+ }111+ if r.Contains(a) {112+ add(users)113+ }114+ }115+}116+117+// namesOverlap reports whether the name with key k, a named range, a118+// table (a region's too) or a region's (nu.sales), stands for cells of119+// r on s.120+func (w *Workbook) namesOverlap(k string, s *Sheet, r Rect) bool {121+ if nm, ok := w.names[k]; ok {122+ _, over := intersectRect(nm.Range, r)123+ return !nm.Gone() && nm.Sheet == s && over124+ }125+ if v, ok := w.findTable(k); ok {126+ _, over := intersectRect(v.r, r)127+ return v.ok && v.s == s && over128+ }129+ t, table, ok := w.regionName(k)130+ if !ok || t != s || table == (Rect{}) {131+ return false132+ }133+ _, over := intersectRect(table, r)134+ return over135+}136+137+// xrefsOverlap reports whether c reads cells of r on s through a138+// reference that names s.139+func (w *Workbook) xrefsOverlap(c *Cell, s *Sheet, r Rect) bool {140+ for _, x := range c.xrefs {141+ if _, over := intersectRect(x.r, r); over && w.byKey[x.key] == s {142+ return true143+ }144+ }145+ return false146+}147+148+// Reads reports whether the formula at u, on s, reads any cell of r on149+// t directly: through a reference, a range, a named range or a region's150+// name. It costs the formula's references, so asking it of every cell151+// on screen is cheap.152+func (s *Sheet) Reads(u Addr, t *Sheet, r Rect) bool {153+ c, _, _ := s.cells.peek(u)154+ if c == nil || !c.IsFormula() {155+ return false156+ }157+ w := s.wb158+ if t == s {159+ for _, a := range c.refs {160+ if r.Contains(a) {161+ return true162+ }163+ }164+ for _, g := range c.ranges {165+ if _, over := intersectRect(g, r); over {166+ return true167+ }168+ }169+ }170+ if w.xrefsOverlap(c, t, r) {171+ return true172+ }173+ for _, k := range c.names {174+ if w.namesOverlap(k, t, r) {175+ return true176+ }177+ }178+ return false179+}@@ -0,0 +1,28 @@
1+package ui2+3+import (4+ tea "charm.land/bubbletea/v2"5+6+ "github.com/FelineStateMachine/012/internal/ui/evalview"7+)8+9+// Data > Evaluate formula: the active cell's formula stepped through one10+// part at a time in a box over the grid (package evalview), the values11+// from the engine's sheet.Steps.12+13+func init() {14+ register(&command{id: "data.evaluate", macro: macroView, title: "Evaluate formula",15+ desc: "Step through the active cell's formula one part at a time, each part's value in its place",16+ enabled: func(m *Model) bool {17+ c := m.sheet.Cell(m.cur)18+ return c != nil && c.IsFormula()19+ },20+ run: func(m *Model) tea.Cmd {21+ if st := m.sheet.EvaluateSteps(m.cur); st != nil {22+ m.clearSelection()23+ m.openOverlay(evalview.New(m.host(), st))24+ }25+ return nil26+ }})27+ keymap["alt+="] = "data.evaluate"28+}@@ -0,0 +1,41 @@
1+package ui2+3+import (4+ "strings"5+ "testing"6+7+ tea "charm.land/bubbletea/v2"8+)9+10+func TestEvaluateFormula(t *testing.T) {11+ m := traceModel(t)12+ m.cur = addr("A1")13+ if commands["data.evaluate"].available(m) {14+ t.Error("Evaluate formula on a number")15+ }16+ m.cur = addr("C1")17+ press(t, m, "<alt+=>")18+ if m.mode != modeMenu || !strings.Contains(line(m, menuLine), "EVAL") {19+ t.Fatalf("mode %v:\n%s", m.mode, screen(m))20+ }21+ if scr := screen(m); !strings.Contains(scr, "Evaluate C1") || !strings.Contains(scr, "Next B1 = 6") {22+ t.Errorf("box:\n%s", scr)23+ }24+ press(t, m, "<right>")25+ if scr := screen(m); !strings.Contains(scr, "Evaluate C1 › B1") || !strings.Contains(scr, "=A1+SUM(A2:A3)") {26+ t.Errorf("stepped in:\n%s", scr)27+ }28+ press(t, m, "<left>", "<enter>")29+ if scr := screen(m); !strings.Contains(scr, "Value 12") {30+ t.Errorf("done:\n%s", scr)31+ }32+ // A click outside closes it, as Esc does.33+ send(m, tea.MouseClickMsg{X: 2, Y: m.height - 3, Button: tea.MouseLeft})34+ if m.mode != modeReady || m.overlay != nil {35+ t.Errorf("mode %v after a click outside", m.mode)36+ }37+ press(t, m, "<alt+=>", "<esc>")38+ if m.mode != modeReady {39+ t.Errorf("mode %v after Esc", m.mode)40+ }41+}@@ -0,0 +1,229 @@
1+// Package evalview is Data > Evaluate formula, after Excel's Evaluate2+// Formula: a box over the grid showing the active cell's formula with3+// the part computed next underlined and its value below. Enter puts the4+// value in its place and underlines the next part, until the formula's5+// value is left; → steps into a reference to another formula, ← and Esc6+// step back out. The engine does the evaluating (sheet.Steps); the view7+// draws it. It knows the UI only through Host.8+package evalview9+10+import (11+ "strconv"12+ "strings"13+14+ tea "charm.land/bubbletea/v2"15+ "charm.land/lipgloss/v2"16+ "github.com/charmbracelet/x/ansi"17+18+ "github.com/FelineStateMachine/012/internal/formula"19+ "github.com/FelineStateMachine/012/internal/locale"20+ "github.com/FelineStateMachine/012/internal/sheet"21+ "github.com/FelineStateMachine/012/internal/ui/overlay"22+ "github.com/FelineStateMachine/012/internal/ui/theme"23+)24+25+// Host is what the view needs of the UI.26+type Host interface {27+ Theme() *theme.Theme28+ Size() (width, height int)29+ // Close closes the open overlay.30+ Close()31+ // Locale writes formulas as the user types them.32+ Locale() *locale.Locale33+}34+35+// View is the open Evaluate formula box.36+type View struct {37+ h Host38+ stack []*sheet.Steps // the formula stepped into last on top39+}40+41+// ID identifies the view's box in mouse events.42+const ID = "evaluate"43+44+// maxInner is the widest the box's inside gets.45+const maxInner = 7646+47+// New returns the view of st, a formula about to be stepped through.48+func New(h Host, st *sheet.Steps) *View { return &View{h: h, stack: []*sheet.Steps{st}} }49+50+// Steps is the formula being stepped through: the one stepped into last.51+func (v *View) Steps() *sheet.Steps { return v.stack[len(v.stack)-1] }52+53+// Depth is how many formulas are open, the first one included.54+func (v *View) Depth() int { return len(v.stack) }55+56+func (v *View) Indicator() string { return "EVAL" }57+58+// Key: Enter computes the next part (at the end, restarts, or steps59+// out of a formula stepped into), → steps in, ← steps out, Esc steps60+// out or closes.61+func (v *View) Key(k tea.KeyPressMsg) tea.Cmd {62+ st := v.Steps()63+ switch k.String() {64+ case "enter", "space":65+ switch {66+ case !st.Done():67+ st.Step()68+ case len(v.stack) > 1:69+ v.out()70+ default:71+ st.Restart()72+ }73+ case "right":74+ if in := st.Into(); in != nil {75+ v.stack = append(v.stack, in)76+ }77+ case "left":78+ if len(v.stack) > 1 {79+ v.out()80+ }81+ case "esc":82+ if len(v.stack) > 1 {83+ v.out()84+ return nil85+ }86+ v.h.Close()87+ }88+ return nil89+}90+91+// out steps out of the formula stepped into: the reference to it takes92+// its value in the formula before.93+func (v *View) out() {94+ v.stack = v.stack[:len(v.stack)-1]95+ v.Steps().Step()96+}97+98+// Mouse closes the box on a click outside it.99+func (v *View) Mouse(e overlay.MouseEvent) tea.Cmd {100+ if e.Kind == overlay.MousePress && e.Box != ID {101+ v.h.Close()102+ }103+ return nil104+}105+106+// Status says what the keys do now.107+func (v *View) Status() (string, string) {108+ th, st := v.h.Theme(), v.Steps()109+ var keys []string110+ switch {111+ case !st.Done():112+ keys = append(keys, "Enter", "evaluate")113+ case len(v.stack) > 1:114+ keys = append(keys, "Enter", "step out")115+ default:116+ keys = append(keys, "Enter", "restart")117+ }118+ if st.CanStepIn() {119+ keys = append(keys, "→", "step in")120+ }121+ if len(v.stack) > 1 {122+ keys = append(keys, "←", "step out")123+ }124+ keys = append(keys, "Esc", "close")125+ desc := "The underlined part is computed next"126+ if st.Done() {127+ desc = "Every part is computed"128+ }129+ return desc, th.KeyHints(keys...)130+}131+132+// Layout is the box: the formula, wrapped, then the next part and its133+// value, under the menu bar and centered.134+func (v *View) Layout() []overlay.Box {135+ th := v.h.Theme()136+ width, height := v.h.Size()137+ inner := min(width-2, maxInner)138+ st := v.Steps()139+ text := v.formulaLines(inner-2, max(height-12, 1))140+ rows := make([]string, 0, len(text)+2)141+ for _, l := range text {142+ rows = append(rows, th.MenuBar.Render(" ")+l+th.MenuBar.Render(strings.Repeat(" ", max(inner-1-ansi.StringWidth(l), 0))))143+ }144+ rows = append(rows, theme.SepRow, v.nextRow(inner))145+ done, total := st.Progress()146+ footer := strconv.Itoa(done) + " of " + strconv.Itoa(total)147+ return []overlay.Box{{ID: ID, X: (width - inner - 2) / 2, Y: overlay.MenuLine + 1, Lines: th.Frame(inner, v.title(), footer, rows)}}148+}149+150+// title names the formulas open: Evaluate C1 › B1.151+func (v *View) title() string {152+ first, _ := v.stack[0].Cell()153+ names := make([]string, len(v.stack))154+ for i, st := range v.stack {155+ s, a := st.Cell()156+ names[i] = a.String()157+ if s != first {158+ names[i] = sheet.Qualified(s.Name(), sheet.Rect{From: a, To: a})159+ }160+ }161+ return "Evaluate " + strings.Join(names, " › ")162+}163+164+// nextRow is "Next B1 = 8", or once done "Value 14".165+func (v *View) nextRow(inner int) string {166+ th, st, loc := v.h.Theme(), v.Steps(), v.h.Locale()167+ line := th.Muted.Render(" Value ") + th.Evaluated.Render(formula.Localize(st.Result(), loc))168+ if expr, value, ok := st.Next(); ok {169+ line = th.Muted.Render(" Next ") + th.EvalNext.Render(formula.Localize(expr, loc)) + th.Muted.Render(" = ") + th.Evaluated.Render(formula.Localize(value, loc))170+ }171+ line = ansi.Truncate(line, inner, "…")172+ return line + th.MenuBar.Render(strings.Repeat(" ", max(inner-ansi.StringWidth(line), 0)))173+}174+175+// run is text in one style.176+type run struct {177+ text string178+ style *lipgloss.Style179+}180+181+// formulaLines draws the formula as it stands in lines of w columns, at182+// most max of them: those from the line the next part starts on.183+func (v *View) formulaLines(w, most int) []string {184+ th, loc := v.h.Theme(), v.h.Locale()185+ both := th.Evaluated.Bold(true).Underline(true)186+ var lines [][]run187+ var cur []run188+ col, nextLine := 0, -1189+ for _, sg := range v.Steps().Text() {190+ style := &th.MenuBar191+ switch {192+ case sg.Value && sg.Next:193+ style = &both194+ case sg.Value:195+ style = &th.Evaluated196+ case sg.Next:197+ style = &th.EvalNext198+ }199+ if sg.Next && nextLine < 0 {200+ nextLine = len(lines)201+ }202+ for _, r := range formula.Localize(sg.Text, loc) {203+ rw := ansi.StringWidth(string(r))204+ if col+rw > w && col > 0 {205+ lines, cur, col = append(lines, cur), nil, 0206+ }207+ if n := len(cur); n > 0 && cur[n-1].style == style {208+ cur[n-1].text += string(r)209+ } else {210+ cur = append(cur, run{string(r), style})211+ }212+ col += rw213+ }214+ }215+ lines = append(lines, cur)216+ start := 0217+ if nextLine >= 0 && len(lines) > most {218+ start = min(max(nextLine-1, 0), len(lines)-most)219+ }220+ out := make([]string, 0, min(len(lines), most))221+ for _, l := range lines[start:min(len(lines), start+most)] {222+ var b strings.Builder223+ for _, r := range l {224+ b.WriteString(r.style.Render(r.text))225+ }226+ out = append(out, b.String())227+ }228+ return out229+}@@ -0,0 +1,110 @@
1+package evalview2+3+import (4+ "strings"5+ "testing"6+7+ tea "charm.land/bubbletea/v2"8+ "github.com/charmbracelet/x/ansi"9+10+ "github.com/FelineStateMachine/012/internal/locale"11+ "github.com/FelineStateMachine/012/internal/sheet"12+ "github.com/FelineStateMachine/012/internal/ui/theme"13+)14+15+type fakeHost struct {16+ th theme.Theme17+ loc *locale.Locale18+ w int19+ closed bool20+}21+22+func (h *fakeHost) Theme() *theme.Theme { return &h.th }23+func (h *fakeHost) Size() (int, int) { return h.w, 24 }24+func (h *fakeHost) Close() { h.closed = true }25+func (h *fakeHost) Locale() *locale.Locale { return h.loc }26+func named(code rune) tea.KeyPressMsg { return tea.KeyPressMsg{Code: code} }27+func text(v *View) string { return ansi.Strip(strings.Join(v.Layout()[0].Lines, "\n")) }28+func status(v *View) string { d, k := v.Status(); return ansi.Strip(d + " | " + k) }29+func at(s string) sheet.Addr { a, _ := sheet.ParseAddr(s); return a }30+func newHost(w int, l *locale.Locale) *fakeHost { return &fakeHost{th: theme.New(true), loc: l, w: w} }31+32+func book(t *testing.T) *sheet.Sheet {33+ t.Helper()34+ s := sheet.New()35+ for a, in := range map[string]string{"A1": "1.5", "A2": "2", "B1": "=A1*2", "C1": "=SUM(B1,A2)"} {36+ if err := s.Set(at(a), in); err != nil {37+ t.Fatal(err)38+ }39+ }40+ return s41+}42+43+func TestEvaluate(t *testing.T) {44+ s := book(t)45+ h := newHost(60, locale.Canonical)46+ v := New(h, s.EvaluateSteps(at("C1")))47+ if got := text(v); !strings.Contains(got, "Evaluate C1") || !strings.Contains(got, "=SUM(B1,A2)") ||48+ !strings.Contains(got, "Next B1 = 3") || !strings.Contains(got, "0 of 3") {49+ t.Errorf("first:\n%s", got)50+ }51+ if got := status(v); !strings.Contains(got, "Enter evaluate") || !strings.Contains(got, "→ step in") {52+ t.Errorf("status %q", got)53+ }54+ // Stepping into B1 shows its formula; stepping out puts its value in.55+ v.Key(named(tea.KeyRight))56+ if got := text(v); v.Depth() != 2 || !strings.Contains(got, "Evaluate C1 › B1") || !strings.Contains(got, "=A1*2") {57+ t.Errorf("into B1:\n%s", got)58+ }59+ v.Key(named(tea.KeyEnter))60+ v.Key(named(tea.KeyEnter))61+ if got := text(v); !strings.Contains(got, "Value 3") || !strings.Contains(status(v), "Enter step out") {62+ t.Errorf("B1 done:\n%s", got)63+ }64+ v.Key(named(tea.KeyEnter))65+ if got := text(v); v.Depth() != 1 || !strings.Contains(got, "=SUM(3,A2)") || !strings.Contains(got, "Next A2 = 2") {66+ t.Errorf("back out:\n%s", got)67+ }68+ v.Key(named(tea.KeyEnter))69+ v.Key(named(tea.KeyEnter))70+ if got := text(v); !strings.Contains(got, "Value 5") || !strings.Contains(status(v), "Enter restart") {71+ t.Errorf("done:\n%s", got)72+ }73+ v.Key(named(tea.KeyEnter))74+ if got := text(v); !strings.Contains(got, "0 of 3") {75+ t.Errorf("restart:\n%s", got)76+ }77+ // Esc steps out, then closes.78+ v.Key(named(tea.KeyRight))79+ v.Key(named(tea.KeyEscape))80+ if v.Depth() != 1 || h.closed {81+ t.Error("Esc didn't step out")82+ }83+ v.Key(named(tea.KeyEscape))84+ if !h.closed {85+ t.Error("Esc didn't close")86+ }87+}88+89+// Formulas are written in the locale's syntax, and long ones wrap.90+func TestEvaluateLocaleAndWrap(t *testing.T) {91+ s := book(t)92+ s.Set(at("D1"), "=A1+A1+A1+A1+A1+A1+A1+A1+A1+A1+A1+A1+A1+A1+A1+A1+A1+A1+A1+A1+A1")93+ de, _ := locale.Lookup("de-DE")94+ v := New(newHost(40, de), s.EvaluateSteps(at("C1")))95+ v.Key(named(tea.KeyRight))96+ v.Key(named(tea.KeyEnter))97+ if got := text(v); !strings.Contains(got, "=1,5*2") {98+ t.Errorf("de-DE:\n%s", got)99+ }100+ v = New(newHost(40, locale.Canonical), s.EvaluateSteps(at("D1")))101+ lines := v.Layout()[0].Lines102+ if len(lines) < 6 {103+ t.Errorf("didn't wrap:\n%s", text(v))104+ }105+ for _, l := range lines {106+ if ansi.StringWidth(l) != ansi.StringWidth(lines[0]) {107+ t.Errorf("ragged box:\n%s", text(v))108+ }109+ }110+}@@ -118,7 +118,10 @@ var menuBar = []menuDef{
118118 {cmd: "data.pivot"}, {cmd: "data.pivot_edit"}, {cmd: "data.frequency"}, sep,119119 {cmd: "data.named_ranges"}, {cmd: "data.define_name"}, {title: "Table", items: tableItems}, {cmd: "data.protect"},120120 {cmd: "data.validation"}, {cmd: "data.validation_clear"}, sep,121- {cmd: "data.precedents"}, {cmd: "data.dependents"}, sep,121+ {title: "Formula tracing", items: []menuItem{122+ {cmd: "data.precedents"}, {cmd: "data.dependents"}, {cmd: "data.trace_list"}, sep,123+ {cmd: "view.trace"}, {cmd: "data.evaluate"},124+ }}, sep,122125 {title: "Macros", items: macroItems}, {title: "Shell", items: notebookItems}, {title: "Linked file", items: linkedItems}, sep,123126 {cmd: "jev.refresh"},124127 }},@@ -120,6 +120,7 @@ type Model struct {
120120 charts chartState // chart commands' target: charts.go121121 copied clipboard // what Ctrl+V pastes: clipboard.go122122 trace *trace // precedents or dependents being shown: trace.go123+ tview *traceView // tracing that stays on, nil when off: traceview.go123124 xfer transfer.Transfer // the import running and the file imported: package transfer124125 pipe pipeState // standard input and output, for 012 - and --pipe: pipe.go125126 jev *jevRunner // answers JEV functions; nil without an API key: jev.go@@ -256,6 +257,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
256257 m.scrollTo(f)257258 }258259 m.clampView()260+ m.syncTrace()259261 // Any edit may have queued JEV questions.260262 // Chart images follow any change, see graphics.go.261263 // Linked regions may have come, gone or changed: follow.go.@@ -326,6 +326,14 @@ func TestMonochromeFoundTraced(t *testing.T) {
326326 if c := cellAt(m, addr("A2")); !every(c, false, isReverse) {327327 t.Errorf("a traced cell isn't reversed: %+v", c)328328 }329+ // Tracing on: precedents reversed, dependents reversed and bold.330+ press(t, m, "<esc>", "<alt+;>")331+ if c := cellAt(m, addr("A2")); !every(c, false, isReverse) || every(c, true, func(c monoCell) bool { return c.bold }) {332+ t.Errorf("a precedent isn't reversed alone: %+v", c)333+ }334+ if c := cellAt(m, addr("C1")); !every(c, false, func(c monoCell) bool { return c.reverse && c.bold }) {335+ t.Errorf("a dependent isn't reversed and bold: %+v", c)336+ }329337 m = newModel()330338 m.sheet.Set(addr("B2"), "Rent")331339 press(t, m, "<ctrl+f>", "Rent")@@ -54,7 +54,7 @@ func TestNamedRangesPicker(t *testing.T) {
5454 m.sheet.DefineName("Costs", rectOf("C2:C9"))5555 m.sheet.DefineName("Rate", rectOf("E1"))5656 press(t, m, "<alt+d>")57- if !strings.Contains(screen(m), "Named ranges") || !strings.Contains(screen(m), "Trace precedents") {57+ if !strings.Contains(screen(m), "Named ranges") || !strings.Contains(screen(m), "Formula tracing") {5858 t.Fatalf("Data menu:\n%s", screen(m))5959 }6060 press(t, m, "<esc>")@@ -277,7 +277,13 @@ func (m *Model) readyContext() (left, right string) {
277277 case m.trace != nil:278278 return m.trace.line(&m.th, m.width, m.sheet)279279 }280- for _, f := range []func() string{m.readyLine, func() string { return m.jev.line(&m.th, m.sheet.RemoteCalls(m.cur)) },280+ if left = m.readyLine(); left != "" {281+ return left, ""282+ }283+ if m.tview != nil {284+ return m.traceLine()285+ }286+ for _, f := range []func() string{func() string { return m.jev.line(&m.th, m.sheet.RemoteCalls(m.cur)) },281287 m.errorLine, m.validationLine, m.noteLine, m.spillLine, m.regionLine, m.tableLine, m.recordingLine} {282288 if left = f(); left != "" {283289 return left, ""@@ -22,7 +22,7 @@ func TestRuleColorsReadOnReferencePalettes(t *testing.T) {
2222 bg := reference(dark, s.GetBackground().(ansi.BasicColor))2323 // The reference palettes' own colors fall a little short2424 // of 4.5 in places: black on the light green is 4.29, as25- // for Traced.25+ // for Precedent.2626 if r := contrast(fg, bg); r < 4.25 {2727 t.Errorf("dark %v: %s text on %s fill: %.2f", dark, text.Title(), c.Title(), r)2828 }@@ -26,7 +26,7 @@ func TestStandouts(t *testing.T) {
2626 if d.GetReverse() || d.GetForeground() != lipgloss.Black || d.GetBackground() != lipgloss.Cyan {2727 t.Errorf("underlined: reverse %v fg %v bg %v", d.GetReverse(), d.GetForeground(), d.GetBackground())2828 }29- for _, s := range []lipgloss.Style{th.Selection, th.HeaderSel, th.HeaderActive, th.Found, th.Traced, th.MenuSelected} {29+ for _, s := range []lipgloss.Style{th.Selection, th.HeaderSel, th.HeaderActive, th.Found, th.Precedent, th.Dependent, th.MenuSelected} {3030 if !s.GetReverse() {3131 t.Errorf("%v isn't reversed", s)3232 }@@ -41,12 +41,21 @@ type Theme struct {
4141 // ErrorMark is layered on an error's text, so errors show beyond4242 // color: a curly underline, in the error color where the terminal4343 // supports colored underlines.44- ErrorMark lipgloss.Style45- Link lipgloss.Style // a cell's URL or HYPERLINK label, layered on the cell's role46- Spilled lipgloss.Style // values an array formula spilled into the cells below and right of it47- Found lipgloss.Style // cells matching an open search48- Traced lipgloss.Style // precedents or dependents being traced49- Argument lipgloss.Style // the argument at the caret in a function's signature44+ ErrorMark lipgloss.Style45+ Link lipgloss.Style // a cell's URL or HYPERLINK label, layered on the cell's role46+ Spilled lipgloss.Style // values an array formula spilled into the cells below and right of it47+ Found lipgloss.Style // cells matching an open search48+ // Precedent and Dependent mark what a formula reads and the formulas49+ // reading a cell, while traced: in reverse video, dependents bold too,50+ // so the two read apart without color.51+ Precedent lipgloss.Style52+ Dependent lipgloss.Style53+ Argument lipgloss.Style // the argument at the caret in a function's signature54+ // EvalNext is the part of a formula Evaluate formula computes next,55+ // underlined; Evaluated the values it has put in place of the parts56+ // it computed, italic.57+ EvalNext lipgloss.Style58+ Evaluated lipgloss.Style5059 Progress lipgloss.Style // the done part of an import's progress bar5160 ProgressTodo lipgloss.Style // the rest of the progress bar5261 // Copied marks the range on the clipboard, like Sheets' dashed border:@@ -206,7 +215,7 @@ func New(dark bool) Theme {
206215 // without it. Call it last: the roles' colors read reversed afterwards.207216 func (t *Theme) standouts() {208217 for _, s := range []*lipgloss.Style{&t.Pointer, &t.Selection, &t.HeaderActive, &t.HeaderSel,209- &t.Found, &t.Traced, &t.MenuSelected, &t.MenuAccelSelected, &t.DropdownChip} {218+ &t.Found, &t.Precedent, &t.Dependent, &t.MenuSelected, &t.MenuAccelSelected, &t.DropdownChip} {210219 fg, bg := s.GetForeground(), s.GetBackground()211220 *s = s.Foreground(bg).Background(fg).Reverse(true)212221 }@@ -278,8 +287,11 @@ func roles(dark bool) Theme {
278287 Link: lipgloss.NewStyle().Foreground(link).Underline(true),279288 Spilled: lipgloss.NewStyle().Foreground(bar).Italic(true),280289 Found: lipgloss.NewStyle().Background(lipgloss.Yellow).Foreground(lipgloss.Black),281- Traced: lipgloss.NewStyle().Background(lipgloss.Green).Foreground(lipgloss.Black),290+ Precedent: lipgloss.NewStyle().Background(lipgloss.Green).Foreground(lipgloss.Black),291+ Dependent: lipgloss.NewStyle().Background(lipgloss.Magenta).Foreground(lipgloss.Black).Bold(true),282292 Argument: lipgloss.NewStyle().Bold(true).Underline(true),293+ EvalNext: lipgloss.NewStyle().Bold(true).Underline(true),294+ Evaluated: lipgloss.NewStyle().Foreground(bar).Italic(true),283295 Progress: lipgloss.NewStyle().Foreground(bar),284296 ProgressTodo: lipgloss.NewStyle().Foreground(lipgloss.BrightBlack),285297 Copied: lipgloss.NewStyle().Foreground(lipgloss.Magenta).@@ -1,33 +1,40 @@
11 package ui22 33 import (4+ "path/filepath"45 "slices"56 "strconv"67 "strings"78 89 tea "charm.land/bubbletea/v2"10+ "charm.land/lipgloss/v2"911 "github.com/charmbracelet/x/ansi"1012 1113 "github.com/FelineStateMachine/012/internal/sheet"1214 "github.com/FelineStateMachine/012/internal/ui/theme"1315 )1416 15-// Tracing precedents and dependents, after Excel's Ctrl+[ and Ctrl+]: the16-// cells a formula reads, or the formulas that read a cell, light up and17-// the active cell jumps to the first; pressing the key again steps18-// through the rest. Ctrl+[ is Esc to most terminals, so the keys are19-// Alt+, and Alt+. (the < and > keys: back to the inputs, on to the20-// results). The highlight is transient, as a search's is: any other key21-// or click ends it, and Esc returns to the traced cell.17+// Stepping through precedents and dependents, after Excel's Ctrl+[ and18+// Ctrl+]: the cells a formula reads, or the formulas that read a cell,19+// light up and the active cell jumps to the first; pressing the key20+// again steps through the rest. Ctrl+[ is Esc to most terminals, so the21+// keys are Alt+, and Alt+. (the < and > keys: back to the inputs, on to22+// the results). The highlight is transient, as a search's is: any other23+// key or click ends it, and Esc returns to the traced cell. Tracing that24+// stays on as the pointer moves is traceview.go.2225 2326 type trace struct {2427 dependents bool2528 home *sheet.Sheet // the traced cell's sheet2629 origin sheet.Addr27- targets []sheet.Target // on any sheet: stepping to one shows its sheet28- at int // the target the active cell is on30+ targets []sheet.Link // on any sheet: stepping to one shows its sheet31+ at int // the target the active cell is on2932 }3033 34+// maxListed caps the dependents found for a cell, so one read by a35+// million formulas lists a thousand and says there are more.36+const maxListed = 100037+3138 func init() {3239 register(3340 &command{id: "data.precedents", macro: macroView, title: "Trace precedents", desc: "Highlight the cells the formula reads; press again to jump through them", run: func(m *Model) tea.Cmd {@@ -50,13 +57,14 @@ func (m *Model) stepTrace(dependents bool) {
5057 if t == nil || t.dependents != dependents {5158 t = &trace{dependents: dependents, home: m.sheet, origin: m.cur, at: -1}5259 if dependents {53- t.targets = m.sheet.Dependents(m.cur)60+ t.targets, _ = m.sheet.DependentLinks(m.cur, maxListed)5461 } else {55- t.targets = m.sheet.Precedents(m.cur)62+ t.targets = m.sheet.PrecedentLinks(m.cur)5663 }57- // Cells on hidden sheets can't be shown, so the trace skips them.64+ // Cells on hidden sheets can't be shown, nor a notebook's cell65+ // among the grid's, so the trace skips them.5866 hidden := hiddenSheets(t.targets)59- t.targets = slices.DeleteFunc(t.targets, func(tg sheet.Target) bool { return tg.Sheet.Hidden() })67+ t.targets = slices.DeleteFunc(t.targets, func(l sheet.Link) bool { return l.Sheet.Hidden() || !l.OnGrid() })6068 if len(t.targets) == 0 {6169 m.trace = nil6270 m.note = noTrace(dependents, m.cur, hidden)@@ -70,12 +78,12 @@ func (m *Model) stepTrace(dependents bool) {
7078 m.cur = t.targets[t.at].Range.From7179 }7280 73-// hiddenSheets names the hidden sheets targets are on, in order.74-func hiddenSheets(targets []sheet.Target) []string {81+// hiddenSheets names the hidden sheets links are on, in order.82+func hiddenSheets(links []sheet.Link) []string {7583 var out []string76- for _, tg := range targets {77- if tg.Sheet.Hidden() && !slices.Contains(out, tg.Sheet.Name()) {78- out = append(out, tg.Sheet.Name())84+ for _, l := range links {85+ if l.Sheet.Hidden() && !slices.Contains(out, l.Sheet.Name()) {86+ out = append(out, l.Sheet.Name())7987 }8088 }8189 return out@@ -121,18 +129,21 @@ func (m *Model) traceKey(k tea.KeyPressMsg) bool {
121129 return false122130 }123131 124-// covers reports whether a, on the sheet shown, is in a range being125-// traced.126-func (t *trace) covers(shown *sheet.Sheet, a sheet.Addr) bool {132+// role is the role a, on the sheet shown, is drawn in while it's being133+// traced, or nil.134+func (t *trace) role(th *theme.Theme, shown *sheet.Sheet, a sheet.Addr) *lipgloss.Style {127135 if t == nil {128- return false136+ return nil129137 }130138 for _, tg := range t.targets {131139 if tg.Sheet == shown && tg.Range.Contains(a) {132- return true140+ if t.dependents {141+ return &th.Dependent142+ }143+ return &th.Precedent133144 }134145 }135- return false146+ return nil136147 }137148 138149 // line is the context line during a trace, e.g. "2 precedents of@@ -155,36 +166,63 @@ func (t *trace) line(th *theme.Theme, width int, shown *sheet.Sheet) (left, righ
155166 if room < ansi.StringWidth(left)+12 {156167 right, room = "", width157168 }158- var b strings.Builder159- b.WriteString(left)169+ labels := make([]string, len(t.targets))160170 for i, tg := range t.targets {161- part := targetLabel(shown, tg, t.home)171+ labels[i] = linkLabel(shown, tg, t.home)162172 if i == t.at {163- part = th.Key.Render(part)173+ labels[i] = th.Key.Render(labels[i])164174 }175+ }176+ return left + listFit(th, labels, len(labels), room-ansi.StringWidth(left), false), right177+}178+179+// listFit joins labels, the first of total, with commas in room180+// columns, ending in "+3 more" when they don't all fit, or "and more"181+// when there are more than total (more).182+func listFit(th *theme.Theme, labels []string, total, room int, more bool) string {183+ var b strings.Builder184+ for i, part := range labels {165185 if i > 0 {166186 part = ", " + part167187 }168- more := " +" + strconv.Itoa(len(t.targets)-i) + " more"169- if ansi.StringWidth(b.String()+part)+len(more) > room && i < len(t.targets)-1 {170- b.WriteString(th.Muted.Render(more))171- break188+ rest := " +" + strconv.Itoa(total-i) + " more"189+ if more {190+ rest = " and more"191+ }192+ if ansi.StringWidth(b.String()+part)+len(rest) > room && (i < total-1 || more) {193+ b.WriteString(th.Muted.Render(rest))194+ return b.String()172195 }173196 b.WriteString(part)174197 }175- return b.String(), right198+ switch {199+ case more:200+ b.WriteString(th.Muted.Render(" and more"))201+ case len(labels) < total:202+ b.WriteString(th.Muted.Render(" +" + strconv.Itoa(total-len(labels)) + " more"))203+ }204+ return b.String()176205 }177206 178-// targetLabel names a range the way a formula on from refers to it: by179-// its name if it has one, with its sheet if it's on another.180-func targetLabel(shown *sheet.Sheet, t sheet.Target, from *sheet.Sheet) string {207+// linkLabel names a link the way a formula on from refers to it: by the208+// name of its named range or region, with its sheet when it's on209+// another, or by what a region's cells come from.210+func linkLabel(shown *sheet.Sheet, l sheet.Link, from *sheet.Sheet) string {211+ switch l.Kind {212+ case sheet.LinkName, sheet.LinkRegion, sheet.LinkTable:213+ return l.Name214+ case sheet.LinkFile:215+ return filepath.Base(l.Name)216+ case sheet.LinkOutput:217+ return "notebook cell " + l.Name218+ }181219 for _, n := range shown.Names() {182- if !n.Gone() && n.Sheet == t.Sheet && n.Range == t.Range && t.Range.From != t.Range.To {220+ if !n.Gone() && n.Sheet == l.Sheet && n.Range == l.Range && l.Range.From != l.Range.To {183221 return n.Name184222 }185223 }186- if t.Sheet != from {187- return sheet.Qualified(t.Sheet.Name(), t.Range)224+ if l.Sheet != from {225+ return sheet.Qualified(l.Sheet.Name(), l.Range)188226 }189- return t.Range.String()227+ return l.Range.String()190228 }@@ -19,7 +19,7 @@ func TestTracePrecedents(t *testing.T) {
1919 m := traceModel(t)2020 m.cur = addr("B1")2121 press(t, m, "<alt+,>")22- if m.cur != addr("A1") || !m.trace.covers(m.sheet, addr("A3")) || m.trace.covers(m.sheet, addr("B1")) {22+ if m.cur != addr("A1") || !(m.trace.role(&m.th, m.sheet, addr("A3")) != nil) || (m.trace.role(&m.th, m.sheet, addr("B1")) != nil) {2323 t.Fatalf("cur %v", m.cur)2424 }2525 if l := line(m, contextLine); !strings.HasPrefix(l, "2 precedents of B1: A1, A2:A3") || !strings.Contains(l, "Alt+, next") {@@ -44,7 +44,7 @@ func TestTraceDependents(t *testing.T) {
4444 m := traceModel(t)4545 m.cur = addr("B1")4646 press(t, m, "<alt+.>")47- if m.cur != addr("C1") || !m.trace.covers(m.sheet, addr("C2")) {47+ if m.cur != addr("C1") || !(m.trace.role(&m.th, m.sheet, addr("C2")) != nil) {4848 t.Fatalf("cur %v", m.cur)4949 }5050 // Any other key ends the trace and does its own thing.@@ -0,0 +1,48 @@
1+//go:build stress2+3+package ui4+5+import (6+ "fmt"7+ "testing"8+9+ "github.com/FelineStateMachine/012/internal/sheet"10+ "github.com/FelineStateMachine/012/internal/stress"11+)12+13+// BenchmarkTrace is an arrow key through to its frame on sheets of a14+// million cells, with tracing off and on: a chain of a million formulas,15+// a cell read by a million formulas (the pointer moving onto and off16+// it), and a million numbers. Tracing costs the links it lists, capped,17+// and a question per cell on screen.18+func BenchmarkTrace(b *testing.B) {19+ const million = 1 << 2020+ shapes := []struct {21+ name string22+ build func() *sheet.Sheet23+ }{24+ {"chain-1M", func() *sheet.Sheet { return stress.Chain(million) }},25+ {"fanout-1M", func() *sheet.Sheet { return stress.FanOut(million - 1) }},26+ {"dense-1M", func() *sheet.Sheet { return stress.Dense(stress.Rows, million/stress.Rows) }},27+ }28+ for _, sh := range shapes {29+ s := lazy(sh.build)30+ for _, on := range []bool{false, true} {31+ b.Run(fmt.Sprintf("arrow/%s/on=%v/200x60", sh.name, on), func(b *testing.B) {32+ m := sized(s(), 200, 60)33+ t := newFakeTerm(200, 60)34+ m.tview = nil35+ if on {36+ m.runCommand("view.trace")37+ }38+ keys := []string{"down", "up"}39+ i := 040+ for b.Loop() {41+ m.Update(key(keys[i%len(keys)]))42+ t.frame(m)43+ i++44+ }45+ })46+ }47+ }48+}@@ -0,0 +1,259 @@
1+package ui2+3+import (4+ "slices"5+ "strconv"6+ "strings"7+8+ tea "charm.land/bubbletea/v2"9+ "charm.land/lipgloss/v2"10+ "github.com/charmbracelet/x/ansi"11+12+ "github.com/FelineStateMachine/012/internal/sheet"13+ "github.com/FelineStateMachine/012/internal/ui/picker"14+)15+16+// Tracing that stays on (Data > Formula tracing > Show precedents and17+// dependents, Alt+;): as the pointer moves, the cells the active18+// cell's formula reads are marked in the Precedent role and the19+// formulas reading it in the Dependent role, and the context line lists20+// both, those on other sheets or off screen included, with an arrow21+// toward the ones off screen.22+// Alt+' lists them in a picker to go to one, which then shows its own.23+//24+// The links are found once per cell and change (the engine's25+// DependentLinks and PrecedentLinks), and each cell drawn asks the26+// engine whether its formula reads the active cell (Reads), so drawing27+// costs the cells on screen however many formulas read the cell. Off,28+// it costs nothing: the model holds no traceView.29+30+// traceView is what tracing shows of the active cell.31+type traceView struct {32+ key traceViewKey33+ home *sheet.Sheet34+ subject sheet.Rect // the cell, or the cells its formula spills into35+ prec []sheet.Link36+ deps []sheet.Link37+ more bool // dependents past maxListed left out38+}39+40+// traceViewKey is what the links were found for: the cell and the41+// workbook's state and values.42+type traceViewKey struct {43+ s *sheet.Sheet44+ a sheet.Addr45+ state int46+ version uint6447+}48+49+func init() {50+ register(51+ &command{id: "view.trace", macro: macroView, title: "Show precedents and dependents",52+ desc: "Mark the cells the active cell's formula reads and the formulas that read it, as the pointer moves",53+ checked: func(m *Model) bool { return m.tview != nil },54+ run: func(m *Model) tea.Cmd {55+ if m.tview != nil {56+ m.tview = nil57+ return nil58+ }59+ m.tview = &traceView{}60+ m.syncTrace()61+ return nil62+ }},63+ &command{id: "data.trace_list", macro: macroView, title: "Go to a precedent or dependent",64+ desc: "List what the active cell reads and the formulas that read it, on any sheet, and go to one",65+ run: func(m *Model) tea.Cmd {66+ m.openTraceList()67+ return nil68+ }},69+ )70+ keymap["alt+;"] = "view.trace"71+ keymap["alt+'"] = "data.trace_list"72+}73+74+// syncTrace finds the active cell's links again when it or the workbook75+// changed.76+func (m *Model) syncTrace() {77+ v := m.tview78+ if v == nil {79+ return80+ }81+ k := traceViewKey{m.sheet, m.cur, m.sheet.StateID(), m.sheet.Version()}82+ if k == v.key {83+ return84+ }85+ *v = traceView{key: k, home: m.sheet, subject: sheet.Rect{From: m.cur, To: m.cur}}86+ v.prec = m.sheet.PrecedentLinks(m.cur)87+ v.deps, v.more = m.sheet.DependentLinks(m.cur, maxListed)88+ if area, ok := m.sheet.SpillArea(m.cur); ok {89+ v.subject = area90+ }91+}92+93+// traceRole is the role a, on the sheet shown, is drawn in for a trace,94+// or nil.95+func (m *Model) traceRole(a sheet.Addr) *lipgloss.Style {96+ if m.trace != nil {97+ return m.trace.role(&m.th, m.sheet, a)98+ }99+ v := m.tview100+ if v == nil {101+ return nil102+ }103+ for _, l := range v.prec {104+ if l.Sheet == m.sheet && l.Range.Contains(a) {105+ return &m.th.Precedent106+ }107+ }108+ if m.sheet == v.home && v.subject.Contains(a) || m.sheet.Reads(a, v.home, v.subject) {109+ return &m.th.Dependent110+ }111+ return nil112+}113+114+// traceLine is the context line while tracing is on: "C1 reads A1,115+// Data!B2:B9; read by D1 +3 more", with an arrow after cells off screen116+// toward them, and the keys.117+func (m *Model) traceLine() (left, right string) {118+ v, th := m.tview, &m.th119+ right = th.KeyHints(m.shortcut("data.trace_list"), "list", m.shortcut("view.trace"), "stop")120+ name := m.cur.String()121+ if len(v.prec) == 0 && len(v.deps) == 0 {122+ return th.Muted.Render(name + " reads no cells and no formula reads it"), right123+ }124+ room := m.width - ansi.StringWidth(right) - 3125+ if room < 40 {126+ right, room = "", m.width127+ }128+ // Labels past what the line can show aren't made: a cell read by a129+ // thousand formulas shows a few and counts the rest.130+ shown := screenArea(m)131+ labels := func(links []sheet.Link) []string {132+ out := make([]string, min(len(links), room/3+1))133+ for i := range out {134+ out[i] = linkLabel(m.sheet, links[i], m.sheet) + shown.arrow(m.sheet, links[i])135+ }136+ return out137+ }138+ var b strings.Builder139+ if len(v.prec) > 0 {140+ lead := name + " reads "141+ fit := room - len(lead)142+ if len(v.deps) > 0 {143+ fit = room/2 - len(lead)144+ }145+ b.WriteString(th.Muted.Render(lead) + listFit(th, labels(v.prec), len(v.prec), fit, false))146+ }147+ if len(v.deps) > 0 {148+ lead := name + " is read by "149+ if b.Len() > 0 {150+ lead = "; read by "151+ }152+ fit := room - ansi.StringWidth(b.String()) - len(lead)153+ b.WriteString(th.Muted.Render(lead) + listFit(th, labels(v.deps), len(v.deps), fit, v.more))154+ }155+ return b.String(), right156+}157+158+// area is the rows and columns on screen, frozen ones included.159+type area struct {160+ top, bottom, left, right int161+ ok bool162+}163+164+func screenArea(m *Model) area {165+ rows := slices.DeleteFunc(m.screenRows(), func(r int) bool { return r == divider })166+ cols := slices.DeleteFunc(m.screenCols(), func(c int) bool { return c == divider })167+ if len(rows) == 0 || len(cols) == 0 {168+ return area{}169+ }170+ return area{slices.Min(rows), slices.Max(rows), slices.Min(cols), slices.Max(cols), true}171+}172+173+// arrow points toward a link on the sheet shown that is off screen, or174+// is "".175+func (sa area) arrow(shown *sheet.Sheet, l sheet.Link) string {176+ if l.Sheet != shown || !sa.ok {177+ return ""178+ }179+ r := l.Range180+ switch {181+ case r.To.Row < sa.top:182+ return "↑"183+ case r.From.Row > sa.bottom:184+ return "↓"185+ case r.To.Col < sa.left:186+ return "←"187+ case r.From.Col > sa.right:188+ return "→"189+ }190+ return ""191+}192+193+// openTraceList opens a picker of the active cell's precedents, then its194+// dependents; picking one goes to it.195+func (m *Model) openTraceList() {196+ prec := m.sheet.PrecedentLinks(m.cur)197+ deps, more := m.sheet.DependentLinks(m.cur, maxListed)198+ if len(prec) == 0 && len(deps) == 0 {199+ m.note = m.cur.String() + " reads no cells and no formula reads it"200+ return201+ }202+ var items []picker.Item203+ for i, links := range [][]sheet.Link{prec, deps} {204+ for _, l := range links {205+ items = append(items, m.traceItem(l, i == 1))206+ }207+ }208+ title := "Precedents and dependents of " + m.cur.String()209+ if more {210+ title += ", the first " + strconv.Itoa(maxListed) + " dependents"211+ }212+ p := m.newPicker(title, "Type a cell, range or name", 72, items)213+ p.Action = "go to"214+ m.openOverlay(p)215+}216+217+// traceItem is a picker row for a link: its label, and what it is to218+// the active cell.219+func (m *Model) traceItem(l sheet.Link, dependent bool) picker.Item {220+ label := linkLabel(m.sheet, l, m.sheet)221+ detail := "dependent"222+ switch {223+ case dependent && l.Kind == sheet.LinkSpill:224+ detail = "dependent, the cells it spills into"225+ case !dependent:226+ detail = "precedent" + map[sheet.LinkKind]string{sheet.LinkName: ", named range " + l.Range.String(),227+ sheet.LinkRegion: ", a region's table", sheet.LinkTable: ", a table's " + l.Range.String(), sheet.LinkSpill: ", the formula that spilled it",228+ sheet.LinkFile: ", the linked file its rows come from", sheet.LinkOutput: ", the notebook cell its rows come from"}[l.Kind]229+ }230+ if l.Sheet != m.sheet && l.OnGrid() {231+ detail += ", on " + l.Sheet.Name()232+ }233+ return picker.Item{Title: label, Name: len(label), Detail: detail, Desc: "Go to " + label,234+ Pick: func() tea.Cmd {235+ m.closeOverlay()236+ m.goToLink(l)237+ return nil238+ }}239+}240+241+// goToLink shows a link: its cells selected, or a notebook's cell.242+func (m *Model) goToLink(l sheet.Link) {243+ if m.refuseHidden(l.Sheet) {244+ return245+ }246+ m.showSheet(l.Sheet)247+ if !l.OnGrid() {248+ if v := m.viewOf(l.Sheet); v != nil {249+ v.Select(l.Range.From.Row, false)250+ }251+ return252+ }253+ if l.Range.From == l.Range.To {254+ m.clearSelection()255+ m.cur = l.Range.From256+ return257+ }258+ m.selectRect(l.Range)259+}@@ -0,0 +1,116 @@
1+package ui2+3+import (4+ "strings"5+ "testing"6+7+ "github.com/FelineStateMachine/012/internal/sheet"8+)9+10+func TestTraceView(t *testing.T) {11+ m := traceModel(t)12+ m.sheet.Set(addr("A40"), "=B1")13+ m.cur = addr("B1")14+ press(t, m, "<alt+;>")15+ if m.tview == nil {16+ t.Fatal("tracing isn't on")17+ }18+ for a, want := range map[string]string{"A1": "precedent", "A3": "precedent", "C1": "dependent", "C2": "dependent", "A4": "", "D1": ""} {19+ got := ""20+ switch m.traceRole(addr(a)) {21+ case &m.th.Precedent:22+ got = "precedent"23+ case &m.th.Dependent:24+ got = "dependent"25+ }26+ if got != want {27+ t.Errorf("%s: %q, want %q", a, got, want)28+ }29+ }30+ // Off-screen dependents are listed with an arrow toward them.31+ if l := line(m, contextLine); !strings.HasPrefix(l, "B1 reads A1, A2:A3; read by C1, C2, A40↓") || !strings.Contains(l, "Alt+' list") {32+ t.Errorf("context %q", l)33+ }34+ // It follows the pointer.35+ press(t, m, "<right>")36+ if l := line(m, contextLine); !strings.HasPrefix(l, "C1 reads B1") || m.traceRole(addr("B1")) != &m.th.Precedent {37+ t.Errorf("after moving: %q", l)38+ }39+ press(t, m, "<left>", "<left>")40+ if l := line(m, contextLine); !strings.HasPrefix(l, "A1 is read by B1") {41+ t.Errorf("a number: %q", l)42+ }43+ press(t, m, "<down>", "<down>", "<down>")44+ if l := line(m, contextLine); !strings.Contains(l, "A4 reads no cells and no formula reads it") {45+ t.Errorf("nothing: %q", l)46+ }47+ // Stepping through a trace shows that trace, and Esc comes back.48+ m.cur = addr("B1")49+ press(t, m, "<alt+.>")50+ if m.cur != addr("C1") || !strings.HasPrefix(line(m, contextLine), "3 dependents of B1") {51+ t.Errorf("stepping: %v %q", m.cur, line(m, contextLine))52+ }53+ press(t, m, "<esc>", "<alt+;>")54+ if m.tview != nil || m.traceRole(addr("A1")) != nil {55+ t.Error("tracing stayed on")56+ }57+}58+59+func TestTraceViewEdits(t *testing.T) {60+ m := traceModel(t)61+ m.cur = addr("A1")62+ run(m, m.runCommand("view.trace"))63+ m.cur = addr("D1")64+ press(t, m, "=A1*3", "<enter>", "<up>")65+ if m.traceRole(addr("A1")) != &m.th.Precedent {66+ t.Error("a new formula's precedent isn't marked")67+ }68+ m.cur = addr("A1")69+ press(t, m, "<right>", "<left>")70+ if m.traceRole(addr("D1")) != &m.th.Dependent {71+ t.Error("a new dependent isn't marked")72+ }73+}74+75+func TestTraceList(t *testing.T) {76+ m := traceModel(t)77+ other, _ := m.book().AddSheet("Data", 1)78+ other.Set(addr("A1"), "=Sheet1!B1")79+ m.sheet.DefineName("Pair", rectOf("A2:A3"))80+ m.cur = addr("B1")81+ press(t, m, "<alt+'>")82+ scr := screen(m)83+ for _, want := range []string{"Precedents and dependents of B1", "A1", "Pair", "C2", "Data!A1", "dependent, on Data"} {84+ if !strings.Contains(scr, want) {85+ t.Errorf("missing %q in\n%s", want, scr)86+ }87+ }88+ press(t, m, "Data", "<enter>")89+ if m.sheet != other || m.cur != addr("A1") {90+ t.Errorf("went to %s!%v", m.sheet.Name(), m.cur)91+ }92+ m.cur = addr("B5")93+ press(t, m, "<alt+'>")94+ if !strings.Contains(line(m, contextLine), "B5 reads no cells") {95+ t.Errorf("nothing to list: %q", line(m, contextLine))96+ }97+}98+99+func TestTraceViewSpill(t *testing.T) {100+ m := newModel()101+ m.sheet.Set(addr("A1"), "=SEQUENCE(3)")102+ m.sheet.Set(addr("B1"), "=SUM(A2:A3)")103+ m.cur = addr("A2")104+ run(m, m.runCommand("view.trace"))105+ if l := line(m, contextLine); !strings.HasPrefix(l, "A2 reads A1; read by B1") {106+ t.Errorf("a spilled cell: %q", l)107+ }108+ m.cur = addr("A1")109+ send(m, teaSize(100, 30))110+ if m.traceRole(addr("A3")) != &m.th.Dependent || m.traceRole(addr("B1")) != &m.th.Dependent {111+ t.Error("the anchor's spill and what reads it aren't dependents")112+ }113+ if got := m.sheet.Value(addr("A3")); got.Kind != sheet.Number {114+ t.Errorf("A3 = %v", got)115+ }116+}@@ -238,8 +238,9 @@ func (m *Model) cellRole(lc *lineCtx, a sheet.Addr, sp *rowtext.Span, spills boo
238238 return &m.th.Selection, true239239 case m.found(a):240240 return &m.th.Found, true241- case m.trace.covers(m.sheet, a):242- return &m.th.Traced, true241+ }242+ if r := m.traceRole(a); r != nil {243+ return r, true243244 }244245 switch v := m.sheet.Value(a); {245246 case sheet.IsPending(v):