cat / 012
clone

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

Keep sort, undo and store tests under the complexity limit

Dami committed 877f0be
commit
877f0bee302d6ea47d494863769600bf002ae33e
parent
4474bac87501fcbbc9336dd9dc81f0816e8e31ad
author
Dami <dami.etoile@gmail.com>
3 files changed, +113 -80
Minternal/sheet/history.go26++++---
Minternal/sheet/sort.go50+++++++------
Minternal/sheet/store_test.go117++++++++++++++++++------------
M internal/sheet/history.go +16 -10
M internal/sheet/sort.go +28 -22
@@ -97,8 +97,33 @@ func (s *Sheet) SortRange(r Rect, keys []SortKey) {
9797 rows = append(rows, a.Row)9898 }9999 }100- // Rows with a key sort to the top; the others (blank keys, and blank101- // rows) keep their order below them.100+ dst := s.sortedRows(r, rows, keys)101+ next := make(map[Addr]*Cell, len(old))102+ for a, c := range old {103+ to := Addr{Col: a.Col, Row: dst[a.Row]}104+ if to.Row != a.Row {105+ c = c.rewritten(formula.Shift(0, to.Row-a.Row))106+ }107+ next[to] = c108+ }109+ s.change("sort "+r.String(), r, func() {110+ for a := range old {111+ if next[a] == nil {112+ s.place(a, nil)113+ }114+ }115+ for a, c := range next {116+ if old[a] != c {117+ s.place(a, c)118+ }119+ }120+ })121+}122+123+// sortedRows maps each of rows, the rows of r with cells, to where the124+// sort puts it. Rows with a key sort to the top; the others (blank keys,125+// and blank rows) keep their order below them.126+func (s *Sheet) sortedRows(r Rect, rows []int, keys []SortKey) map[int]int {102127 var keyed, rest []int103128 for _, row := range rows {104129 if s.keysBlank(keys, row) {
@@ -122,26 +147,7 @@ func (s *Sheet) SortRange(r Rect, keys []SortKey) {
122147 }123148 dst[row] = row + len(keyed) - n124149 }125- next := make(map[Addr]*Cell, len(old))126- for a, c := range old {127- to := Addr{Col: a.Col, Row: dst[a.Row]}128- if to.Row != a.Row {129- c = c.rewritten(formula.Shift(0, to.Row-a.Row))130- }131- next[to] = c132- }133- s.change("sort "+r.String(), r, func() {134- for a := range old {135- if next[a] == nil {136- s.place(a, nil)137- }138- }139- for a, c := range next {140- if old[a] != c {141- s.place(a, c)142- }143- }144- })150+ return dst145151 }146152 147153 // keysBlank reports whether every key of row is blank.
M internal/sheet/store_test.go +69 -48
@@ -42,60 +42,81 @@ func checkStore(t *testing.T, st *cellStore, rng *rand.Rand) {
4242 t.Helper()4343 for range 20 {4444 r := NewRect(Addr{Col: rng.IntN(45), Row: rng.IntN(80000)}, Addr{Col: rng.IntN(45), Row: rng.IntN(MaxRows)})45- var want []Addr46- for a := range st.all() {47- if r.Contains(a) {48- want = append(want, a)49- }50- }51- sortAddrs(want)52- var got []Addr53- for a := range st.inRange(r) {54- got = append(got, a)55- }56- if !slices.Equal(got, want) {57- t.Fatalf("inRange(%v): %d cells, want %d", r, len(got), len(want))58- }59- b, ok := st.bounds(r)60- if ok != (len(want) > 0) {61- t.Fatalf("bounds(%v) found %v with %d cells", r, ok, len(want))45+ checkRange(t, st, r)46+ checkFilled(t, st, r)47+ checkNextRow(t, st, rng.IntN(40), rng.IntN(MaxRows))48+ }49+}50+51+// checkRange compares inRange and bounds with the map.52+func checkRange(t *testing.T, st *cellStore, r Rect) {53+ t.Helper()54+ var want []Addr55+ for a := range st.all() {56+ if r.Contains(a) {57+ want = append(want, a)6258 }63- for _, a := range want {64- if !b.Contains(a) {65- t.Fatalf("bounds(%v) = %v misses %v", r, b, a)66- }59+ }60+ sortAddrs(want)61+ var got []Addr62+ for a := range st.inRange(r) {63+ got = append(got, a)64+ }65+ if !slices.Equal(got, want) {66+ t.Fatalf("inRange(%v): %d cells, want %d", r, len(got), len(want))67+ }68+ b, ok := st.bounds(r)69+ if ok != (len(want) > 0) {70+ t.Fatalf("bounds(%v) found %v with %d cells", r, ok, len(want))71+ }72+ for _, a := range want {73+ if !b.Contains(a) {74+ t.Fatalf("bounds(%v) = %v misses %v", r, b, a)6775 }68- if ok && (!slices.ContainsFunc(want, func(a Addr) bool { return a.Row == b.From.Row }) ||69- !slices.ContainsFunc(want, func(a Addr) bool { return a.Col == b.To.Col })) {70- t.Fatalf("bounds(%v) = %v isn't tight", r, b)76+ }77+ if ok && (!slices.ContainsFunc(want, func(a Addr) bool { return a.Row == b.From.Row }) ||78+ !slices.ContainsFunc(want, func(a Addr) bool { return a.Col == b.To.Col })) {79+ t.Fatalf("bounds(%v) = %v isn't tight", r, b)80+ }81+}82+83+// checkFilled compares the index of cells with contents with the map.84+func checkFilled(t *testing.T, st *cellStore, r Rect) {85+ t.Helper()86+ var filled []Addr87+ for a, c := range st.all() {88+ if r.Contains(a) && !c.Blank() {89+ filled = append(filled, a)7190 }72- var filled []Addr73- for a, c := range st.all() {74- if r.Contains(a) && !c.Blank() {75- filled = append(filled, a)76- }77- if st.filled.has(a) == c.Blank() {78- t.Fatalf("filled index says %v for %v", st.filled.has(a), a)79- }91+ if st.filled.has(a) == c.Blank() {92+ t.Fatalf("filled index says %v for %v", st.filled.has(a), a)8093 }81- if fb, ok := st.filled.bounds(r); ok != (len(filled) > 0) || ok && !slices.ContainsFunc(filled, func(a Addr) bool { return a.Row == fb.To.Row }) {82- t.Fatalf("filled bounds(%v) = %v %v", r, fb, ok)94+ }95+ fb, ok := st.filled.bounds(r)96+ if ok != (len(filled) > 0) || ok && !slices.ContainsFunc(filled, func(a Addr) bool { return a.Row == fb.To.Row }) {97+ t.Fatalf("filled bounds(%v) = %v %v", r, fb, ok)98+ }99+}100+101+// checkNextRow compares nextRow both ways with the map.102+func checkNextRow(t *testing.T, st *cellStore, c, row int) {103+ t.Helper()104+ next, ok := st.stored.nextRow(c, row, 1)105+ prev, okp := st.stored.nextRow(c, row, -1)106+ want1, ok1 := MaxRows, false107+ want2, ok2 := -1, false108+ for a := range st.all() {109+ if a.Col != c {110+ continue83111 }84- c, row := rng.IntN(40), rng.IntN(MaxRows)85- next, ok := st.stored.nextRow(c, row, 1)86- want1, ok1 := MaxRows, false87- prev, okp := st.stored.nextRow(c, row, -1)88- want2, ok2 := -1, false89- for a := range st.all() {90- if a.Col == c && a.Row >= row && a.Row < want1 {91- want1, ok1 = a.Row, true92- }93- if a.Col == c && a.Row <= row && a.Row > want2 {94- want2, ok2 = a.Row, true95- }112+ if a.Row >= row && a.Row < want1 {113+ want1, ok1 = a.Row, true96114 }97- if ok != ok1 || ok && next != want1 || okp != ok2 || okp && prev != want2 {98- t.Fatalf("nextRow(%d, %d) = %d %v / %d %v, want %d %v / %d %v", c, row, next, ok, prev, okp, want1, ok1, want2, ok2)115+ if a.Row <= row && a.Row > want2 {116+ want2, ok2 = a.Row, true99117 }100118 }119+ if ok != ok1 || ok && next != want1 || okp != ok2 || okp && prev != want2 {120+ t.Fatalf("nextRow(%d, %d) = %d %v / %d %v, want %d %v / %d %v", c, row, next, ok, prev, okp, want1, ok1, want2, ok2)121+ }101122 }