cat / 012
clone

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

Pivot editor: macros record and replay pivots made and edited

Dami committed 0c96387
Keeping the editor records run("data.pivot", answer={...}) for a new
pivot and run("data.pivot_edit", answer={...}) for a changed one, with
the definition as a line of the file; scripts answer both commands.
Dialog answers no longer record the selection after them, which their
command recorded before it ran: a pivot's new sheet is the command's
own doing. The filter button and chart moves, which no command opens,
record the selection first.
commit
0c96387a2b14f4b98b8c002f086628e491a68387
parent
dffc28fa05af0aaf38c649fbd30b1f9584105c98
author
Dami <dami.etoile@gmail.com>
kind
feat risk score 2.0 of 3, Jev's estimate
8 files changed, +106 -17
Minternal/ui/charteditor.go7+++--
Minternal/ui/chartmacro.go11+++++--
Minternal/ui/chartsel.go2+-
Minternal/ui/macrodialog_test.go33++++++++++++++++++++++
Minternal/ui/macrorec.go5++-
Minternal/ui/mouse.go1+
Minternal/ui/pivot.go18+++++++++++-
Minternal/ui/pivoteditor.go46+++++++++++++++++++++++++-----
M internal/ui/charteditor.go +4 -3
M internal/ui/chartmacro.go +8 -3
M internal/ui/chartsel.go +1 -1
M internal/ui/macrodialog_test.go +33 -0
@@ -210,3 +210,36 @@ run("chart.edit", answer={"chart": 1, "at": "H2", "legend": "none"})`)
210210 t.Fatalf("warn %q overlay %T", m.warn, m.overlay)211211 }212212 }213+214+func TestRecordPivotEditor(t *testing.T) {215+ m := wideSales()216+ got := recordDo(t, m, "Pivot", func() {217+ run(m, m.runCommand("data.pivot"))218+ press(t, m, "<space>", "reg", "<enter>")219+ press(t, m, "<down>", "<down>", "<space>", "units", "<enter>", "<enter>")220+ // Edited again: the grand total row off. Unchanged, it records221+ // nothing.222+ run(m, m.runCommand("data.pivot_edit"))223+ press(t, m, "<enter>")224+ run(m, m.runCommand("data.pivot_edit"))225+ press(t, m, "<end>", "<up>", "<space>", "<enter>")226+ })227+ want := `run("data.pivot", answer={"source": "Sheet1!A1:C5", "rows": [{"column": "A"}], "values": [{"column": "B", "summarize": "sum"}], "rowTotals": True, "columnTotals": True})228+run("data.pivot_edit", answer={"source": "Sheet1!A1:C5", "rows": [{"column": "A"}], "values": [{"column": "B", "summarize": "sum"}], "rowTotals": False, "columnTotals": True})`229+ if got != want {230+ t.Fatalf("recorded:\n%s\nwant:\n%s", got, want)231+ }232+ replays(t, m, "Pivot", wideSales)233+}234+235+func TestScriptAnswersPivotCommands(t *testing.T) {236+ m := wideSales()237+ script(t, m, `run("data.pivot_edit", answer={"source": "Sheet1!A1:C5"})`)238+ if !strings.Contains(m.warn, "Sheet1 has no pivot table") {239+ t.Fatalf("warn %q", m.warn)240+ }241+ script(t, m, `run("data.pivot", answer={"source": "Sheet1!A1:C5", "rows": [{"column": "A"}], "values": [{"column": "C", "summarize": "max"}]})`)242+ if m.sheet.Name() != "Pivot Table 1" || shows(m, "B1") != "MAX of Price" || m.overlay != nil {243+ t.Fatalf("on %s, B1 %q, overlay %T, warn %q", m.sheet.Name(), shows(m, "B1"), m.overlay, m.warn)244+ }245+}
M internal/ui/macrorec.go +3 -2
M internal/ui/mouse.go +1 -0
M internal/ui/pivot.go +17 -1
M internal/ui/pivoteditor.go +39 -7
@@ -23,10 +23,11 @@ import (
2323 // it. Enter keeps the changes; Esc undoes them, removing a pivot just2424 // created.2525 type pivotEditor struct {26- m pivotHost // the model, through what the editor needs of it27- start int // the workbook's state before editing, to undo back to28- back *sheet.Sheet // for a new pivot, the sheet it summarizes, shown again on Esc29- msg string // why the last change was refused, for the status line26+ m pivotHost // the model, through what the editor needs of it27+ start int // the workbook's state before editing, to undo back to28+ back *sheet.Sheet // for a new pivot, the sheet it summarizes, shown again on Esc29+ before string // the pivot when the editor opened, as a macro answers it30+ msg string // why the last change was refused, for the status line3031 overlay.List3132 }3233
@@ -55,6 +56,9 @@ type pivotHost interface {
5556 pointRange(label string, done func(sheet.Rect), cancel func())5657 // askText asks for text, such as a value's name.5758 askText(label, initial string, done func(string), cancel func())59+ // recordDialog records the pivot made, as the command id answered60+ // with its definition.61+ recordDialog(id, answer string)5862 }5963 6064 // Sections of the editor, in order.
@@ -97,11 +101,13 @@ const pivotEditorID = "pivot"
97101 98102 // openPivotEditor edits the shown sheet's pivot. start is the state Esc99103 // returns to; back, for a pivot just created, the sheet to show then.100-func (m *Model) openPivotEditor(start int, back *sheet.Sheet) {104+func (m *Model) openPivotEditor(start int, back *sheet.Sheet) *pivotEditor {101105 e := &pivotEditor{m: m, start: start, back: back}106+ e.before = e.pivot(m).JSON()102107 e.Sel = 2 // the Rows section103108 m.clearSelection()104109 m.openOverlay(e)110+ return e105111 }106112 107113 func (e *pivotEditor) Indicator() string { return "PIVOT" }
@@ -183,7 +189,7 @@ func (e *pivotEditor) Key(k tea.KeyPressMsg) tea.Cmd {
183189 it := e.current(m)184190 switch key := k.String(); key {185191 case "enter":186- m.closeOverlay()192+ e.keep(m)187193 case "esc":188194 e.cancel(m)189195 case "up", "ctrl+p", "shift+tab":
@@ -225,6 +231,32 @@ func (e *pivotEditor) cancel(m pivotHost) {
225231 m.syncChanged()226232 }227233 234+// keep closes the editor keeping the changes, and records the pivot235+// made: a new one as Data > Pivot table, a changed one as Edit pivot236+// table, answered with its definition.237+func (e *pivotEditor) keep(m pivotHost) {238+ m.closeOverlay()239+ id, now := "data.pivot_edit", e.pivot(m).JSON()240+ if e.back != nil {241+ id = "data.pivot"242+ } else if now == e.before {243+ return244+ }245+ m.recordDialog(id, now)246+}247+248+// answer makes the pivot a macro's answer defines, as a line of the file249+// writes it, and closes the editor.250+func (e *pivotEditor) answer(m pivotHost, text string) error {251+ p, err := sheet.ParsePivot(text)252+ if err == nil {253+ err = m.sheetShown().SetPivot(p, "")254+ }255+ m.syncChanged()256+ m.closeOverlay()257+ return err258+}259+228260 // reopen returns to the editor after a picker or prompt.229261 func (e *pivotEditor) reopen() { e.m.openOverlay(e) }230262
@@ -232,7 +264,7 @@ func (e *pivotEditor) Mouse(ev overlay.MouseEvent) tea.Cmd {
232264 m := e.m233265 if ev.Box != pivotEditorID {234266 if ev.Kind == overlay.MousePress {235- m.closeOverlay() // a click elsewhere keeps the changes267+ e.keep(m) // a click elsewhere keeps the changes236268 }237269 return nil238270 }