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 / move.go
move.go 5.9K
1package sheet23import "src.rest/cat/012/internal/formula"45// Moving cells, as cut and paste does: formulas that read the moved cells6// follow them.78// Move moves the cells in src so its top-left corner lands on to, as9// cut and paste does in Sheets, with the formatting they show; whole10// columns or rows take their line formats along. Formulas anywhere that referred to the11// moved cells follow them; references to cells the move overwrote become12// #REF!. It returns the destination range.13func (s *Sheet) Move(src Rect, to Addr) (Rect, error) {14 dc, dr := to.Col-src.From.Col, to.Row-src.From.Row15 dst := Rect{From: to, To: Addr{Col: src.To.Col + dc, Row: src.To.Row + dr}}16 if !dst.To.Valid() {17 return Rect{}, ErrPasteEdge18 }19 shift := func(a Addr) Addr { return Addr{Col: a.Col + dc, Row: a.Row + dr} }20 cell := func(a Addr) (Addr, bool) {21 switch {22 case src.Contains(a):23 return shift(a), true24 case dst.Contains(a):25 return a, false26 }27 return a, true28 }29 rng := func(r Rect) (Rect, bool) {30 if src.Contains(r.From) && src.Contains(r.To) {31 return Rect{From: shift(r.From), To: shift(r.To)}, true32 }33 return r, true34 }35 label := "move " + src.String() + " to " + dst.String()36 f := s.copyFormats(src)37 s.change(label, dst, func() {38 s.remap(label, dst, cell, rng)39 s.remapNames(rng) // a name for exactly the moved cells follows them40 s.remapTables(src, rng)41 s.moveFormats(s, &f, src, dst)42 s.moveMerges(s, src, dst)43 s.moveRules(src, dst, cell, rng) // rulemove.go44 })45 return dst, nil46}4748// MoveTo moves the cells in src to sheet dst, src's top-left corner49// landing on to, as cutting on one sheet and pasting on another does in50// Sheets. Formulas anywhere that read the moved cells follow them to dst,51// naming its sheet where they need to; the moved formulas keep reading52// what they read, naming this sheet for cells that stayed behind.53// References to cells the move overwrote become #REF!.54func (s *Sheet) MoveTo(dst *Sheet, src Rect, to Addr) (Rect, error) {55 if dst == s {56 return s.Move(src, to)57 }58 dc, dr := to.Col-src.From.Col, to.Row-src.From.Row59 d := Rect{From: to, To: Addr{Col: src.To.Col + dc, Row: src.To.Row + dr}}60 if !d.To.Valid() {61 return Rect{}, ErrPasteEdge62 }63 mv := sheetMove{from: s, to: dst, src: src, d: d}64 moved, readers := mv.movedCells(), mv.readers()65 f := s.copyFormats(src)66 label := "move " + src.String() + " to " + formula.QuoteSheet(dst.name) + "!" + d.String()67 dst.change(label, d, func() {68 mv.apply(moved, readers)69 s.moveTablesTo(dst, src, mv.shift)70 dst.moveFormats(s, &f, src, d)71 dst.moveMerges(s, src, d)72 mv.moveRules() // rulemove.go73 })74 return d, nil75}7677// sheetMove is the cells in src on one sheet moving to d on another.78type sheetMove struct {79 from, to *Sheet80 src, d Rect81}8283func (mv sheetMove) shift(a Addr) Addr {84 return Addr{Col: a.Col + mv.d.From.Col - mv.src.From.Col, Row: a.Row + mv.d.From.Row - mv.src.From.Row}85}8687// movedCells are the cells that move, by where they land, with their88// references rewritten.89func (mv sheetMove) movedCells() map[Addr]*Cell {90 moved := map[Addr]*Cell{}91 for _, a := range mv.from.cellsIn(mv.src) {92 moved[mv.shift(a)] = mv.from.cells.get(a).rewritten(mv.rewrite(mv.from, mv.to))93 }94 return moved95}9697// readers are the formulas that stay where they are, rewritten, among98// those whose references the move changes: on either sheet, or on another99// sheet naming one of them.100func (mv sheetMove) readers() map[loc]*Cell {101 out := map[loc]*Cell{}102 add := func(l loc) {103 if mv.src.Contains(l.a) && l.s == mv.from || mv.d.Contains(l.a) && l.s == mv.to {104 return // moved or overwritten105 }106 c := l.s.cells.get(l.a)107 if nc := c.rewritten(mv.rewrite(l.s, l.s)); nc != c {108 out[l] = nc109 }110 }111 for _, t := range []*Sheet{mv.from, mv.to} {112 for a := range t.cells.richCells() {113 add(loc{t, a})114 }115 }116 for _, l := range mv.from.wb.crossList() {117 if l.s != mv.from && l.s != mv.to {118 add(l)119 }120 }121 return out122}123124// apply clears the source and destination, places the moved cells and125// the rewritten readers, and moves the named ranges wholly inside the126// source along with it.127func (mv sheetMove) apply(moved map[Addr]*Cell, readers map[loc]*Cell) {128 for _, a := range mv.from.cellsIn(mv.src) {129 mv.from.place(a, nil)130 }131 for _, a := range mv.to.cellsIn(mv.d) {132 mv.to.place(a, nil)133 }134 for a, c := range moved {135 mv.to.place(a, c)136 }137 for l, c := range readers {138 l.s.place(l.a, c)139 }140 w := mv.from.wb141 for k, n := range w.names {142 if n.Sheet == mv.from && !n.Lost && mv.src.Contains(n.Range.From) && mv.src.Contains(n.Range.To) {143 n.Sheet, n.Range = mv.to, Rect{From: mv.shift(n.Range.From), To: mv.shift(n.Range.To)}144 w.putName(k, &n)145 }146 }147}148149// rewrite maps the references of a formula that was on orig and is on150// home after the move. A reference names its sheet unless it points at151// home.152func (mv sheetMove) rewrite(orig, home *Sheet) formula.Rewriter {153 w := mv.from.wb154 // written is how a reference names now, having named was as written.155 written := func(as string, was, now *Sheet) string {156 switch {157 case now == home:158 return ""159 case now == was && as != "":160 return as161 }162 return now.name163 }164 return formula.Rewriter{165 Ref: func(n formula.Ref) Node {166 t := w.resolve(orig, n.Sheet)167 switch {168 case t == nil:169 return n170 case t == mv.from && mv.src.Contains(n.Addr):171 return formula.Ref{Addr: mv.shift(n.Addr), Abs: n.Abs, Sheet: written(n.Sheet, t, mv.to)}172 case t == mv.to && mv.d.Contains(n.Addr):173 return formula.RefErr{}174 }175 return formula.Ref{Addr: n.Addr, Abs: n.Abs, Sheet: written(n.Sheet, t, t)}176 },177 Range: func(n formula.Range) Node {178 t := w.resolve(orig, n.Sheet)179 switch {180 case t == nil:181 return n182 case t == mv.from && mv.src.Contains(n.Rect.From) && mv.src.Contains(n.Rect.To):183 r := Rect{From: mv.shift(n.Rect.From), To: mv.shift(n.Rect.To)}184 return formula.Range{Rect: r, Abs: n.Abs, Sheet: written(n.Sheet, t, mv.to)}185 }186 return formula.Range{Rect: n.Rect, Abs: n.Abs, Sheet: written(n.Sheet, t, t)}187 },188 }189}