cat / 012
clone

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

main
012 / internal / sheet / historyrestore.go
historyrestore.go 5.1K
1package sheet23import "slices"45// restore puts back the before-images a step holds and returns the step6// that would put back what they replaced, keeping st's ID (on the redo7// stack an ID names the state the step leads back to), and the cells8// whose values may have changed. Undo and redo swap steps with it; a9// change taken back inside another's step (Try) restores its own.10func (w *Workbook) restore(st *step) (inv *step, changed []loc) {11 inv = newStep(st.label, st.sheet, st.focus)12 inv.id, inv.shifts = st.id, st.shifts13 // The sheet list goes first, so cells land on attached sheets.14 if st.sheets != nil {15 inv.sheets = w.sheetList()16 w.setSheets(st.sheets)17 }18 for s, r := range st.regions {19 changed = append(changed, s.emptyMoved(r)...)20 }21 for s, img := range st.cells {22 changed = append(changed, s.emptyUnder(img)...)23 }24 for s, img := range st.cells {25 img.each(func(a Addr, c *Cell) {26 inv.keep(s, a)27 s.place(a, c)28 changed = append(changed, loc{s, a})29 })30 }31 for k, width := range st.widths {32 inv.widths[k] = k.s.widths[k.col]33 k.s.setWidth(k.col, width)34 }35 for k, h := range st.heights {36 inv.keepHeight(k, k.s.heights[k.row])37 k.s.setHeight(k.row, h)38 }39 for k, l := range st.lines {40 inv.lines[k] = k.s.line(k.row, k.n)41 k.s.setLine(k.row, k.n, l)42 changed = append(changed, k.s.lineChanged(k.row, k.n)...)43 }44 for k, n := range st.names {45 inv.names[k] = w.namePtr(k)46 changed = append(changed, w.putName(k, n)...)47 }48 changed = append(changed, w.restoreSheetParts(st, inv)...)49 return inv, changed50}5152// restoreSheetParts is restore's part for what a sheet keeps whole: its53// charts, pivot, rules and view, and the workbook's settings and macros.54// It returns the formulas reading the tables it changed.55func (w *Workbook) restoreSheetParts(st, inv *step) (changed []loc) {56 for s, charts := range st.charts {57 inv.charts[s] = s.charts58 s.charts = slices.Clone(charts)59 }60 for s, p := range st.pivots {61 inv.pivots[s] = s.pivot.def62 s.pivot.def, s.pivot.stale = p, true63 }64 for s, r := range st.regions {65 inv.regions[s] = s.regions66 s.putRegionsBack(r)67 }68 for s, r := range st.rules {69 inv.rules[s] = s.rules70 s.rules = r71 s.looks.reset()72 }73 if st.settings != nil {74 cur := w.settings75 inv.settings, w.settings = &cur, *st.settings76 // Every formula computes differently in the other arithmetic.77 w.structural = w.structural || cur.decimal != w.decimal78 }79 if st.macros != nil {80 cur := w.macros81 inv.macros, w.macros = &cur, slices.Clone(*st.macros)82 }83 for s, v := range st.views {84 cur := s.view85 inv.views[s] = &cur86 changed = append(changed, w.tableUsers(cur.tables, v.tables)...)87 s.view = *v88 s.hidden.valid = false89 if !slices.Equal(cur.merges, v.merges) {90 s.version++91 s.respill(Rect{To: Addr{Col: MaxCols - 1, Row: MaxRows - 1}})92 }93 }94 return changed95}9697// Try runs fn, a change, so that it can be taken back whole: after fn98// and a recalculation, takeBack says whether to. Outside any step, fn's99// change is a step of its own and taking it back discards it, as100// Discard does; inside a step another change opened (a macro's run),101// what fn changed is put back within that step, which goes on. It102// reports whether fn's change stayed, and fn's error. A paste or fill103// checked against validation after the fact runs through it.104func (w *Workbook) Try(fn func() error, takeBack func() bool) (bool, error) {105 h := &w.hist106 if h.open == nil {107 before := w.StateID()108 err := fn()109 if err == nil && w.StateID() != before && takeBack() {110 w.Discard()111 return false, nil112 }113 return true, err114 }115 outer := h.open116 h.open = newStep(outer.label, outer.sheet, outer.focus)117 err := fn()118 child := h.open119 h.open = outer120 w.Settle()121 if err == nil && takeBack() {122 _, changed := w.restore(child)123 w.recalcSwapped(changed)124 return false, nil125 }126 outer.join(child)127 return true, err128}129130// join adds to st the before-images of child, a step taken inside it,131// that st doesn't hold: st began earlier, so its own come first.132func (st *step) join(child *step) {133 for s, img := range child.cells {134 img.each(func(a Addr, c *Cell) {135 mine := st.cells[s]136 if mine == nil {137 mine = &image{}138 st.cells[s] = mine139 st.cellBytes += imageBytes140 }141 switch {142 case mine.seen(a):143 case c == nil:144 st.cellBytes += mine.put(a, slot{}, true)145 default:146 st.cellBytes += mine.keepRich(a, c)147 }148 })149 }150 joinMap(st.widths, child.widths)151 if child.heights != nil {152 if st.heights == nil {153 st.heights = map[rowKey]int{}154 }155 joinMap(st.heights, child.heights)156 }157 joinMap(st.lines, child.lines)158 joinMap(st.names, child.names)159 joinMap(st.views, child.views)160 joinMap(st.charts, child.charts)161 joinMap(st.pivots, child.pivots)162 joinMap(st.rules, child.rules)163 joinMap(st.regions, child.regions)164 st.sheets = cmpOrPtr(st.sheets, child.sheets)165 st.settings = cmpOrPtr(st.settings, child.settings)166 st.macros = cmpOrPtr(st.macros, child.macros)167}168169// joinMap adds child's entries to st where st has none.170func joinMap[K comparable, V any](st, child map[K]V) {171 for k, v := range child {172 if _, ok := st[k]; !ok {173 st[k] = v174 }175 }176}177178// cmpOrPtr is a, or b when a is nil.179func cmpOrPtr[T any](a, b *T) *T {180 if a != nil {181 return a182 }183 return b184}