cat / 012
clone

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

Docs: Tables page, structured references, suggestions and the XLSX and macro references

Dami committed 48dd84c
commit
48dd84cb9b781231067b55803ed2f5a880bb12c1
parent
aefc926f7f198a47c8f7728f82e99ea3661cdf22
author
Dami <dami.etoile@gmail.com>
kind
docs risk score 1.0 of 3, Jev's estimate
11 files changed, +229 -34
Mdocs/contributing/ux.md1+
Mdocs/formulas/building.md9++-
Mdocs/formulas/references.md39+++++++++++
Mdocs/nushell/notebooks.md9++-
Mdocs/reference/macro-api.md3+-
Mdocs/sheets/README.md1+
Mdocs/sheets/sort-filter.md6+-
Adocs/sheets/tables.md107++++++++++++++++++++++++++++++
Minternal/fileio/snapshot.go23++++---
Minternal/sheet/view.go44+++++++-----
Minternal/ui/tables_test.go21++++++
M docs/contributing/ux.md +1 -0
M docs/formulas/building.md +8 -1
@@ -8,13 +8,20 @@ sidebar_position: 3
88 - After an operator or `(`, arrow keys (or a click) pick a cell and insert99 its reference; Shift+arrows (or a drag) pick a range. Keep typing to go on.1010 - While you type a name, suggestions drop down: named ranges first, then11- the other sheets, then functions with their arguments. Tab or Enter11+ [tables](../sheets/tables.md), the other sheets, then functions with12+ their arguments. Tab or Enter1213 inserts `NAME(`, or a sheet with its `!`, quoted when it needs to be1314 (`=Su` offers `Summary!`; `=Q3` or `='Q3` offers `'Q3 plan'!`). An arrow1415 after it points into that sheet, as clicking its tab does. Hidden1516 sheets aren't offered, though formulas still read them.1617 - Inside a function's parentheses the context line shows its signature with1718 the current argument marked, e.g. `SUMIF(range, criterion, [sum_range])`.19+- Inside a table's brackets, `=SUM(Sales[a`, suggestions offer its20+ columns (after `#`, the items `#All`, `#Data`, `#Headers`, `#This Row`;21+ after `@`, the columns again), and Tab or Enter inserts one with its22+ `]`. The context line lists the table's columns, the one being typed23+ marked: `Sales[Region, Units, Amount]`24+ ([Tables by column name](references.md#tables-by-column-name)).1825 - Alt+, and Alt+. trace precedents and dependents: the cells a formula reads1926 and the formulas that read a cell. Press again to step through them.2027 Cells on hidden sheets are skipped; when that leaves nothing, the
M docs/formulas/references.md +39 -0
@@ -16,6 +16,8 @@ sidebar_position: 2
1616 - Named ranges: `=SUM(Sales)`. Define them from Data > Named ranges or Data >1717 Define named range. Names are case-insensitive, follow the selection when1818 rows move, and renaming one rewrites the formulas that use it.19+- Tables by column name: `=SUM(Sales[Amount])`, `=Sales[@Amount]*2`;20+ see [Tables by column name](#tables-by-column-name).1921 - Other sheets: `=Sheet2!A1`, `=SUM('Q3 plan'!B2:B9)`; names with spaces or2022 punctuation, or that look like a cell, go in single quotes. While typing a2123 formula, Ctrl+PgDn or clicking a tab points into another sheet and inserts
@@ -31,6 +33,43 @@ sidebar_position: 2
3133 ten cells, and `ROWS(A:A)` is still 1,048,576. See [Bounds of support](../contributing/limits.md)3234 for what that means in practice.3335 36+## Tables by column name37+38+A [table](../sheets/tables.md), or a notebook output sent to a sheet, is39+read by its columns' names with a structured reference: the table's40+name, then in brackets what to read of it.41+42+| Reference | Reads |43+|---|---|44+| `Sales[Amount]` | The Amount column's data rows, under the header |45+| `Sales` or `Sales[]` | Every data row, header left out |46+| `Sales[#All]` | The whole table, header row included |47+| `Sales[#Headers]`, `Sales[#Data]` | The header row; the data rows |48+| `Sales[@Amount]` | The Amount cell in the formula's own row (`Sales[[#This Row],[Amount]]`, as Excel's files write it); outside the table's rows, `#REF!` |49+| `Sales[[#Headers],[Amount]]` | The Amount column's header cell |50+| `Sales[[Units]:[Amount]]` | The columns from Units to Amount |51+| `Sales[[#Headers],[#Data],[Units]]` | The Units column, header and data |52+53+Names match in any case. A column whose name has characters other than54+letters, digits, `_` and spaces goes in brackets of its own,55+`Sales[[Total, EUR]]`, and `[`, `]`, `#` and `'` in a name take a `'`56+before them: `Sales['#Items]`. In a locale that separates arguments with57+`;`, so are the items: `Sales[[#Headers];[Amount]]`.58+59+A structured reference is kept as written and read when the formula60+computes, so it follows the table: rows added at its end, columns61+inserted or moved, the table moved or resized. Renaming the table or a62+column's header rewrites the formulas that read it; a deleted column, or63+a deleted table, reads as `#REF!`. Copying, pasting and filling keep64+structured references as they are, since they name columns rather than65+positions: filled down a table, `Sales[@Units]*Sales[@Amount]` reads66+each row's own cells.67+68+While typing, the table's name is offered with the named ranges, and69+after its `[` its columns, or after `#` the items; the context line lists70+the columns with the one being typed marked71+([Building formulas](building.md)).72+3473 ## A range where one value is wanted3574 3675 A range used where a formula wants one value (with an operator, or as
M docs/nushell/notebooks.md +6 -3
M docs/reference/macro-api.md +2 -1
@@ -86,7 +86,7 @@ width, a name, a confirmation) needs `answer`: the text you'd type, a range
8686 for a range question, the key of a choice (`"enter"`, `"d"`) or its8787 label, or the item picked from a list (View > Hidden sheets takes the8888 sheet's name). A command that opens a dialog takes what you'd choose in89-it as a dict (see [Dialogs](#dialogs)). Other pickers (Named ranges) can't89+it as a dict (see [Dialogs](#dialogs)). Other pickers (Named ranges, Tables) can't9090 run in a script; nor can files, menus, help, undo or macros. The ids are9191 those in the recorded scripts; a few:9292
@@ -102,6 +102,7 @@ those in the recorded scripts; a few:
102102 | `column.width` (answer: the width), `column.reset` | Column widths |103103 | `data.sort_sheet_az`, `data.sort_range_az`, `data.sort_range_za`, `data.filter`, `data.filter_remove` | Sorting and filters |104104 | `data.define_name` (answer: the name) | Name the selection |105+| `table.create` (answer: the name), `table.rename` (answer: the name), `table.resize` (answer: the range), `table.banded`, `table.header`, `table.remove` | [Tables](../sheets/tables.md): make the selection one, or change the one the active cell is in |105106 | `format.conditional_add`, `data.validation_add` (answer: the rule as a line of the file, see [The .012 format](../files/format.md#conditional-formats-and-data-validation)), `insert.checkbox`, `format.conditional_clear`, `data.validation_clear`, `data.checkbox_toggle` | Conditional formats, data validation and checkboxes |106107 | `format.conditional_set`, `data.validation_set` (answer: `{"rule": 2, ...}`, the rule's number and its new line), `format.conditional_remove`, `data.validation_remove` (answer: the rule's number), `format.conditional_move` (answer: `{"rule": 2, "to": 1}`) | Change the rules of the sheet shown, numbered from 1 as the panel lists them |107108 | `sheet.new`, `sheet.duplicate`, `sheet.rename` (answer: the name), `sheet.delete` (answer: `"enter"` when it asks), `sheet.hide`, `sheet.unhide` (answer: the sheet's name) | Sheets |
M docs/sheets/README.md +1 -0
M docs/sheets/sort-filter.md +4 -2
A docs/sheets/tables.md +107 -0
@@ -0,0 +1,107 @@
1+---2+title: "Tables"3+sidebar_position: 134+---5+6+# Tables7+8+A table is a named range whose first row names its columns, as in9+Sheets' and Excel's tables. Formulas read it by those names,10+`=SUM(Sales[Amount])`, so they keep reading the right cells as the table11+grows and its columns move ([Structured references](../formulas/references.md#tables-by-column-name)).12+13+## Making one14+15+Format > Convert to table (Ctrl+Alt+T) makes a table of the selection, or16+of the data around the active cell, and asks for its name on the context17+line (`Table1` to start). A name follows a named range's rules, letters,18+digits, `_` and `.`, except that `Table1` is fine: only names Excel reads19+as a cell are refused. Tables, named ranges and20+[notebook outputs](#notebook-outputs-and-linked-files) share one set of21+names, so a table can't take a name one of them has.22+23+The first row is the header row: each cell's text is its column's name.24+A blank header is named after its place (`Column3`) and a repeat25+numbered (`Amount2`), in the cell too, so the header shows what formulas26+use. A selection of one row gains an empty row of data below it.27+28+```29+ A B C30+ 1 Region Units Amount31+ 2 North 2 1032+ 3 South 3 2033+ 4 East 5 3034+```35+36+Made a table named `Sales`, this reads `Sales[Amount]` as C2:C4 and37+`Sales[#All]` as A1:C4; a value typed in row 5 joins it.38+39+On a table's cell the context line names the table and the column, and40+how a formula reads it: `Table Sales, column Amount: Sales[Amount] in41+formulas`.42+43+## Growing, resizing and removing44+45+A table grows by the rows typed or pasted just below it, in the same46+undo step as the entry, so Ctrl+Z takes the row back out. It grows by47+rows only: a column added at its right joins it through Data > Table >48+Resize table, which asks for the new range, pointed at from the49+table's own, header row first.50+51+Rows and columns inserted inside a table widen it, and deleting some52+narrows it; a column inserted comes in named from its (blank) header,53+`Column2`. Deleting a table's header row removes the table. Cutting the54+whole table and pasting it elsewhere, on this sheet or another, moves55+it.56+57+Changing a header renames the column in every formula that reads it:58+typing `Revenue` over `Amount` turns `Sales[Amount]` into59+`Sales[Revenue]`. Columns moved, inserted or deleted rename nothing;60+formulas reading a deleted column show `#REF!`.61+62+Data > Table > Rename table renames the table and every formula that63+reads it. Data > Table > Remove table makes it plain cells again: the64+cells stay, and formulas that read it read the same cells by address65+(`Sales[Amount]` becomes `$C$2:$C$4`, `Sales[@Amount]` the cell in the66+formula's own row, `$C3`). Data > Table > Tables lists every table and67+notebook output: Enter goes to one, F2 renames it and Ctrl+D removes it.68+69+## Banding and the header style70+71+Data > Table > Banded rows shades every other data row, and Header style72+draws the header row bold, underlined and in the theme's accent; a new73+table has both. They are the `TableBand` and `TableHeader` roles of the74+[theme](../terminal/themes.md): a scheme draws the band a shade off its75+background. The header's bold and underline read without color76+([UX](../contributing/ux.md#reading-without-color)); the band is77+decoration only.78+79+## Sorting and filtering a table80+81+Sorting or filtering with the active cell in a table takes the table's82+range, its header row kept in place: Data > Sort range sorts its rows by83+the active column, and Data > Create a filter puts the filter on it. A84+filter on a table's range follows the table as it grows or is resized.85+86+## Notebook outputs and linked files87+88+A notebook cell's output sent to a sheet, and a linked file, is a table89+by the region's name, with no table of its own to make: `app[status]`90+reads the `status` column of the output of the cell `app`, and `app`91+alone its rows under the header. `nu.app` stays what it was, the whole92+table header row included, the same cells as `app[#All]`. Its source93+names and shapes it, so the table commands leave it alone, and a table94+can't take its name ([Notebooks](../nushell/notebooks.md#send-to-a-sheet)).95+96+## Where 012 differs from Sheets and Excel97+98+- Copying, pasting and filling keep structured references as written:99+ they name columns, not positions. Excel shifts `[Amount]` to the next100+ column when a formula is filled right.101+- A table has no totals row and no calculated columns: a formula typed in102+ a table's column isn't filled down the column for you. An Excel table's103+ totals row comes in as cells below the table ([Excel files](../files/excel.md#tables-in-excel)).104+- A structured reference names its table, `Sales[@Amount]`, even inside105+ it; Excel's `[@Amount]` alone isn't read.106+- `[@Amount]` outside the table's data rows is `#REF!`, where Excel gives107+ `#VALUE!`.
M internal/fileio/snapshot.go +14 -9
@@ -105,15 +105,7 @@ func Snap(s *sheet.Sheet, r sheet.Rect, name string) *Snapshot {
105105 CondFormats: s.CondFormats(), Validations: s.Validations()}106106 snap.FrozenRows, snap.FrozenCols = s.Frozen()107107 snap.Heights, snap.Merges = heightsMergesIn(s, r)108- for _, t := range s.Tables() {109- if whole {110- // A whole sheet's range holds its tables, blank rows and all.111- snap.Range.To.Col, snap.Range.To.Row = max(snap.Range.To.Col, t.Range.To.Col), max(snap.Range.To.Row, t.Range.To.Row)112- }113- if snap.Range.Contains(t.Range.From) && snap.Range.Contains(t.Range.To) {114- snap.Tables = append(snap.Tables, t)115- }116- }108+ snapTables(snap, s, whole)117109 for _, a := range s.NotesIn(notes) {118110 if snap.Notes == nil {119111 snap.Notes = map[sheet.Addr]string{}
@@ -153,6 +145,19 @@ func Snap(s *sheet.Sheet, r sheet.Rect, name string) *Snapshot {
153145 return snap154146 }155147 148+// snapTables adds the sheet's tables in the snapshot's range to it; a149+// whole sheet's range grows to hold its tables, blank rows and all.150+func snapTables(snap *Snapshot, s *sheet.Sheet, whole bool) {151+ for _, t := range s.Tables() {152+ if whole {153+ snap.Range.To.Col, snap.Range.To.Row = max(snap.Range.To.Col, t.Range.To.Col), max(snap.Range.To.Row, t.Range.To.Row)154+ }155+ if snap.Range.Contains(t.Range.From) && snap.Range.Contains(t.Range.To) {156+ snap.Tables = append(snap.Tables, t)157+ }158+ }159+}160+156161 // heightsMergesIn are the heights of the rows of r and the merges inside157162 // it.158163 func heightsMergesIn(s *sheet.Sheet, r sheet.Rect) (map[int]int, []sheet.Rect) {
M internal/sheet/view.go +26 -18
@@ -107,24 +107,7 @@ func (s *Sheet) shiftView(rows bool, sp formula.Span) {
107107 }108108 *frozen = min(*frozen, MaxFrozen)109109 }110- if f := v.filter; f != nil {111- _, rng := formula.AxisMaps(rows, sp)112- if r, ok := rng(f.Range); ok {113- nf := &Filter{Range: r, Cols: map[int]Criteria{}}114- for c, cr := range f.Cols {115- if !rows {116- var keep bool117- if c, keep = sp.Point(c); !keep || c < r.From.Col || c > r.To.Col {118- continue119- }120- }121- nf.Cols[c] = cr122- }123- v.filter = nf124- } else {125- v.filter = nil126- }127- }110+ v.filter = shiftFilter(v.filter, rows, sp)128111 if !v.equal(s.view) {129112 s.recordView()130113 if w := s.wb; w.hist.open != nil {
@@ -134,6 +117,31 @@ func (s *Sheet) shiftView(rows bool, sp formula.Span) {
134117 }135118 }136119 120+// shiftFilter is f once rows or columns are inserted or deleted: its121+// range moved like a range reference, with its columns' criteria, or nil122+// when all of it is deleted.123+func shiftFilter(f *Filter, rows bool, sp formula.Span) *Filter {124+ if f == nil {125+ return nil126+ }127+ _, rng := formula.AxisMaps(rows, sp)128+ r, ok := rng(f.Range)129+ if !ok {130+ return nil131+ }132+ nf := &Filter{Range: r, Cols: map[int]Criteria{}}133+ for c, cr := range f.Cols {134+ if !rows {135+ var keep bool136+ if c, keep = sp.Point(c); !keep || c < r.From.Col || c > r.To.Col {137+ continue138+ }139+ }140+ nf.Cols[c] = cr141+ }142+ return nf143+}144+137145 // fileView is the view state in the file (version 3), e.g.138146 //139147 // "freeze": {"rows": 1},
M internal/ui/tables_test.go +21 -0