cat / 012
clone

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

Edit macro scripts only where an editor is allowed, and test recording and replay end to end

Dami committed 11d6dfd
commit
11d6dfd478eba30fbf396d23a74abf920981dc64
parent
a2b5f24702c6984d504954d84ee01050251233cb
author
Dami <dami.etoile@gmail.com>
10 files changed, +164 -15
Mcmd/012/main.go1+
Me2e/harness_test.go8++
Ae2e/macros_test.go100++++++++++++++++++++++++++++++
Minternal/ui/file.go2+-
Minternal/ui/macro.go13+++-
Minternal/ui/macro_test.go17+++++
Minternal/ui/macromanage.go31+++++++---
Minternal/ui/macrorec.go1-
Minternal/ui/macrorun.go4+
Minternal/ui/theme/theme.go2+-
M cmd/012/main.go +1 -0
M e2e/harness_test.go +8 -0
A e2e/macros_test.go +100 -0
@@ -0,0 +1,100 @@
1+package e2e2+3+import (4+ "os"5+ "path/filepath"6+ "strings"7+ "testing"8+)9+10+// recordTotals records a macro with relative references at A1: a label,11+// a formula to its right, and bold on both. It's saved as Totals with12+// the shortcut Ctrl+Alt+Shift+1.13+func recordTotals(s *session) {14+ s.keys("<ctrl+k>", "relative references")15+ s.waitFor("Record macro with relative references")16+ s.keys("<enter>")17+ s.waitFor(" REC ")18+ s.waitFor("Recording a macro with relative references")19+ s.keys("Total", "<tab>", "=SUM(2,3)", "<enter>")20+ s.keys("<up>", "<shift+right>", "<ctrl+b>")21+ s.waitFor("Bold on for A1:B1")22+ s.keys("<ctrl+k>", "stop and save", "<enter>")23+ s.waitFor("Save macro as: Macro 1")24+ s.keys("Totals", "<enter>")25+ s.waitFor("Shortcut Ctrl+Alt+Shift+ (a digit, or empty for none): 1")26+ s.keys("<enter>")27+ s.waitFor("Saved macro Totals; Ctrl+Alt+Shift+1 runs it")28+}29+30+func TestMacroRecordAndReplay(t *testing.T) {31+ dir := t.TempDir()32+ s := start(t, dir, "book.012")33+ recordTotals(s)34+ if strings.Contains(s.line(0), "REC") {35+ t.Errorf("still shows REC: %q", s.line(0))36+ }37+38+ // The shortcut replays it from wherever the active cell is.39+ s.keys("<f5>", "D5", "<enter>")40+ s.waitForBar("D5", "")41+ s.keys("<ctrl+alt+shift+1>")42+ s.waitFor("Ran Totals")43+ s.keys("<right>")44+ s.waitForBar("E5", "=SUM(2,3)")45+46+ // One Ctrl+Z undoes the whole run.47+ s.keys("<ctrl+z>")48+ s.waitFor("Undid: run macro Totals")49+ s.waitForBar("D5", "")50+51+ // Saved in the file, as a script.52+ s.keys("<ctrl+s>")53+ s.eventually("saved", func() bool {54+ data, err := os.ReadFile(filepath.Join(dir, "book.012"))55+ return err == nil && strings.Contains(string(data), `"macros": [`) &&56+ strings.Contains(string(data), `{"name": "Totals", "key": "1", "api": 1, "source": "# Recorded with relative references`) &&57+ strings.Contains(string(data), `enter(\"=SUM(2,3)\", origin=\"B1\")`)58+ })59+ s.keys("<ctrl+q>")60+ s.waitExit()61+62+ // Opening the file runs nothing; on this computer the macro runs63+ // without asking.64+ s = start(t, dir, "book.012")65+ s.waitForBar("A1", "Total")66+ s.keys("<f5>", "A3", "<enter>")67+ s.waitForBar("A3", "")68+ s.keys("<ctrl+k>", "totals")69+ s.waitFor("Ctrl+Alt+Shift+1")70+ s.keys("<enter>")71+ s.waitFor("Ran Totals")72+ s.waitForBar("A3:B3", "Total")73+}74+75+func TestMacroFromAnotherComputerAsks(t *testing.T) {76+ dir := t.TempDir()77+ s := start(t, dir, "book.012")78+ recordTotals(s)79+ s.keys("<ctrl+s>")80+ s.eventually("saved", func() bool {81+ data, err := os.ReadFile(filepath.Join(dir, "book.012"))82+ return err == nil && strings.Contains(string(data), "macroOrigin")83+ })84+ s.keys("<ctrl+q>")85+ s.waitExit()86+87+ s = startWith(t, options{dir: dir, config: t.TempDir()}, "book.012")88+ s.waitForBar("A1", "Total")89+ s.keys("<f5>", "C3", "<enter>", "<ctrl+alt+shift+1>")90+ s.waitFor("This file's macros were made on another computer")91+ s.keys("<esc>")92+ s.waitForBar("C3", "")93+ s.keys("<ctrl+alt+shift+1>")94+ s.waitFor("made on another computer")95+ s.keys("<enter>")96+ s.waitForBar("C3:D3", "Total") // the macro ends selecting the pair97+ // Trusted now: no second question.98+ s.keys("<down>", "<ctrl+alt+shift+1>")99+ s.waitForBar("C4:D4", "Total")100+}
M internal/ui/file.go +1 -1
M internal/ui/macro.go +10 -3
M internal/ui/macro_test.go +17 -0
M internal/ui/macromanage.go +22 -9
@@ -2,6 +2,7 @@ package ui
22 33 import (44 "cmp"5+ "slices"56 "fmt"67 "os"78 "os/exec"
@@ -42,13 +43,16 @@ func (m *Model) openMacros(sel string) {
4243 4344 // macroManageItems lists "Write a macro" and then every macro.4445 func macroManageItems(m *Model) []pickItem {45- items := []pickItem{{46- title: writeMacroTitle, name: len(writeMacroTitle), desc: "Write a new macro script in your editor",47- pick: func(m *Model) tea.Cmd {48- m.closeOverlay()49- return m.newMacro()50- },51- }}46+ var items []pickItem47+ if m.macros.editor {48+ items = append(items, pickItem{49+ title: writeMacroTitle, name: len(writeMacroTitle), desc: "Write a new macro script in your editor",50+ pick: func(m *Model) tea.Cmd {51+ m.closeOverlay()52+ return m.newMacro()53+ },54+ })55+ }5256 for _, mc := range m.book().Macros() {5357 lines := strings.Count(strings.TrimRight(mc.Source, "\n"), "\n") + 15458 items = append(items, pickItem{
@@ -90,6 +94,10 @@ func (p *macrosPicker) key(m *Model, k tea.KeyPressMsg) tea.Cmd {
9094 m.closeOverlay()9195 m.setMacroShortcut(mc)9296 case "f4":97+ if !m.macros.editor {98+ p.msg = "No editor in this session"99+ return nil100+ }93101 m.closeOverlay()94102 return m.editScript(mc.Name, mc.Source, false)95103 case "ctrl+d":
@@ -104,10 +112,15 @@ func (p *macrosPicker) key(m *Model, k tea.KeyPressMsg) tea.Cmd {
104112 }105113 106114 func (p *macrosPicker) status(m *Model) (string, string) {107- keys := m.th.KeyHints("Enter", "run", "F2", "rename", "F3", "shortcut", "F4", "edit", "Ctrl+D", "delete", "Esc", "close")115+ pairs := []string{"Enter", "run", "F2", "rename", "F3", "shortcut", "F4", "edit", "Ctrl+D", "delete", "Esc", "close"}116+ if !m.macros.editor {117+ pairs = slices.Delete(pairs, 6, 8)118+ }108119 if m.width < 100 {109- keys = m.th.KeyHints("Enter", "run", "F2", "rename", "F3", "key", "F4", "edit", "Ctrl+D", "delete")120+ pairs = slices.DeleteFunc(pairs[:len(pairs)-2], func(s string) bool { return s == "shortcut" })121+ pairs = slices.Insert(pairs, slices.Index(pairs, "F3")+1, "key")110122 }123+ keys := m.th.KeyHints(pairs...)111124 if _, ok := p.current(m); !ok {112125 keys = m.th.KeyHints("Enter", "write", "Esc", "close")113126 }
M internal/ui/macrorec.go +0 -1
M internal/ui/macrorun.go +4 -0
M internal/ui/theme/theme.go +1 -1