cat / 012
clone

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

Commands on a source's tab are recorded in macros and wait for their turn to write in a room

Dami committed b60592b
commit
b60592be62d0d706430c85404ae934f721ce18e3
parent
26f7f33e6744e4ab5ac8fa9ef175369286dfb755
author
Dami <dami.etoile@gmail.com>
kind
fix risk score 2.0 of 3, Jev's estimate
2 files changed, +61 -9
Minternal/ui/sources_test.go34++++++++++++++++++++++++++++
Minternal/ui/srccmds.go36+++++++++++++++++++++++--------
M internal/ui/sources_test.go +34 -0
@@ -169,6 +169,16 @@ func TestFormulasReadASourceTab(t *testing.T) {
169169 }170170 }171171 172+func TestSourceSortRecorded(t *testing.T) {173+ t.Chdir(t.TempDir())174+ m := newModel()175+ linkSales(t, m, 100)176+ src := record(t, m, false, "Top", "", "<right>", "<right>", "<ctrl+k>", "sort sheet z to a", "<enter>")177+ if !strings.Contains(src, `run("data.sort_sheet_za")`) {178+ t.Errorf("the macro:\n%s", src)179+ }180+}181+172182 func TestSourceCommandsOnTheTab(t *testing.T) {173183 t.Chdir(t.TempDir())174184 m := newModel()
@@ -235,6 +245,30 @@ func TestSharedSources(t *testing.T) {
235245 }236246 }237247 248+// In a room of one writer, whoever follows may scroll a source's tab249+// but not sort it.250+func TestSharedSourceOneWriter(t *testing.T) {251+ r := newRooms(t, room.View)252+ writeSource(t, filepath.Join(r.dir, "sales.parquet"), 100)253+ ann := r.open("ann", "@one")254+ bob := r.open("bob", "@one")255+ ann.run(ann.m.runCommand("data.link_source"))256+ ann.press("sales.parquet", "<enter>")257+ for range 3 {258+ r.sync()259+ }260+ bob.press("<ctrl+pgdown>", "<down>")261+ bob.run(bob.m.runCommand("data.sort_sheet_za"))262+ var order *sheet.SourceOrder263+ bob.sh.turn(func() {264+ info, _ := bob.m.book().LookupSource("sales")265+ order = info.Source.Order266+ })267+ if order != nil || !strings.Contains(bob.screen(), "writes here and you follow") {268+ t.Errorf("a follower sorted the source: %+v\n%s", order, bob.screen())269+ }270+}271+238272 func TestMonochromeSource(t *testing.T) {239273 t.Chdir(t.TempDir())240274 m := newModel()
M internal/ui/srccmds.go +27 -9
@@ -11,6 +11,7 @@ import (
1111 tea "charm.land/bubbletea/v2"1212 1313 "github.com/FelineStateMachine/012/internal/fileio"14+ "github.com/FelineStateMachine/012/internal/macro"1415 "github.com/FelineStateMachine/012/internal/sheet"1516 "github.com/FelineStateMachine/012/internal/ui/picker"1617 )
@@ -60,7 +61,7 @@ var sourceTakes = map[string]bool{
6061 func (m *Model) sourceCommand(c *command) (tea.Cmd, bool) {6162 info, ok := m.sheet.Source()6263 switch {63- case !ok || notebookSafe(c.id) || c.id == "data.source_all" || c.id == "data.source_reload" || c.id == "data.link_source" || c.id == "data.link":64+ case !ok || sourceOwn(c.id) || c.id == "data.link_source" || c.id == "data.link":6465 return nil, false6566 case !sourceTakes[c.id]:6667 m.note = c.title + " works on a sheet's cells: this tab is a linked source, read-only"
@@ -68,33 +69,50 @@ func (m *Model) sourceCommand(c *command) (tea.Cmd, bool) {
6869 case !info.Known:6970 m.note = "The source isn't open yet"7071 return nil, true72+ case edits(c) && !m.mayEdit():73+ return nil, true // another writes in the room74+ case m.rec != nil && c.macro == macroRecord && c.id != "data.filter" && c.id != "data.filter_column":75+ m.rec.flush(m)76+ cmd := m.sourceRun(c, info)77+ m.rec.add(m, macro.Call("run", c.id)) // replayed on the tab, as here78+ return cmd, true7179 }80+ return m.sourceRun(c, info), true81+}82+83+// sourceRun runs c on the tab of the source info, acting on the source.84+func (m *Model) sourceRun(c *command, info sheet.SourceInfo) tea.Cmd {7285 v := m.srcView()7386 _, col := v.Active()7487 switch c.id {7588 case "edit.copy":7689 _, text := m.sourceCell(v)7790 m.note = "Copied " + text78- return tea.SetClipboard(text), true91+ return tea.SetClipboard(text)7992 case "data.sort_sheet_az", "data.sort_range_az", "data.sort_sheet_za", "data.sort_range_za":8093 desc := c.id == "data.sort_sheet_za" || c.id == "data.sort_range_za"8194 o := copyOrder(info.Source.Order)8295 o.Sort = []sheet.SourceSort{{Col: col, Desc: desc}}83- return m.setSourceOrder(info, o), true96+ return m.setSourceOrder(info, o)8497 case "data.filter", "data.filter_column":8598 m.openSourceFilter(info, col)86- return nil, true8799 case "data.filter_remove":88100 o := copyOrder(info.Source.Order)89101 o.Filter = nil90- return m.setSourceOrder(info, o), true102+ return m.setSourceOrder(info, o)91103 case "data.pivot", "data.frequency":92- return m.sourcePivot(info, col, c.id == "data.frequency"), true104+ return m.sourcePivot(info, col, c.id == "data.frequency")93105 case "goto":94106 m.openText("Go to a row, or a cell such as C5000000:", "", (*Model).sourceGoto)95- return nil, true96107 }97- return nil, false108+ return nil109+}110+111+// sourceOwn reports whether a command runs on a source's tab as it runs112+// anywhere: the session's (files, sheets, settings, macros, undo) and113+// the source's own commands.114+func sourceOwn(id string) bool {115+ return notebookSafe(id) || strings.HasPrefix(id, "macro.") || id == "data.source_all" || id == "data.source_reload"98116 }99117 100118 // sourceEnabled says whether a command runs on a source's tab, when
@@ -102,7 +120,7 @@ func (m *Model) sourceCommand(c *command) (tea.Cmd, bool) {
102120 // acting on the source do, whatever they say of a sheet.103121 func sourceEnabled(m *Model, id string) (on, decided bool) {104122 switch {105- case notebookSafe(id), id == "data.source_all", id == "data.source_reload":123+ case sourceOwn(id):106124 return false, false107125 case !sourceTakes[id]:108126 return false, true