cat / 012
clone

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

cat/module-path
012 / internal / sheet / regionwrite.go
regionwrite.go 10.4K
1package sheet23// Writing regions' cells (see region.go). Every region's cells go4// through writeTable: a header and rows under it, written as spilled5// cells are, only those that differ, the region's cells it no longer6// needs cleared. Rows arrive from live operations (live.go). A table7// that would overwrite other contents isn't shown: the region's first8// cell says where it's blocked, and clearing that cell has its source9// send it again.1011// meta is the region with key k's meta, made when missing.12func (s *Sheet) meta(k string) *regionMeta {13 if s.regionMeta == nil {14 s.regionMeta = map[string]*regionMeta{}15 }16 me := s.regionMeta[k]17 if me == nil {18 me = &regionMeta{}19 s.regionMeta[k] = me20 }21 return me22}2324// regionTouched notes that c is being placed at a: a region blocked by25// it tries again, and one whose table it lands in (a blank cell of it,26// pasted over) finds it in its way, its source sending its rows again27// either way.28func (s *Sheet) regionTouched(a Addr, c *Cell) {29 for _, r := range s.regions.list {30 me := s.regionMeta[nameKey(r.Name)]31 switch {32 case me == nil:33 case me.why != "" && me.need.Contains(a):34 me.stale = true35 case me.why == "" && me.has && me.written.Contains(a) && !c.Blank():36 me.stale, me.placed = true, true37 }38 }39}4041// ownerOf is the region whose cell the cell at a is, and its meta.42func (s *Sheet) ownerOf(a Addr) (Region, *regionMeta, bool) {43 for _, r := range s.regions.list {44 if me := s.regionMeta[nameKey(r.Name)]; me != nil && s.regionOwns(a, me) {45 return r, me, true46 }47 }48 return Region{}, nil, false49}5051// settleRegions writes the cells of the sheets whose regions changed,52// then recalculates what reads them.53func (w *Workbook) settleRegions() {54 if w.settling {55 return56 }57 w.settling = true58 defer func() { w.settling = false }()59 var changed []loc60 for _, s := range w.sheets {61 if s.regionsStale {62 s.regionsStale = false63 changed = append(changed, s.writeRegions()...)64 }65 }66 if len(changed) > 0 {67 w.recalc(changed)68 }69}7071// writeRegions empties the regions that are to be fed again and clears72// the cells of regions gone, returning the cells that changed and the73// formulas naming regions gone.74func (s *Sheet) writeRegions() []loc {75 var changed []loc76 for k, me := range s.regionMeta {77 if s.regionIndex(k) < 0 {78 changed = append(changed, s.clearOwned(me, noRect)...)79 if t, ok := me.table(); ok {80 changed = append(changed, s.regionMoved(t, noRect, true)...)81 }82 delete(s.regionMeta, k)83 s.wakeAllRegions()84 for u := range s.wb.nameUsers[nameKey(regionPrefix+k)] {85 changed = append(changed, u) // the name reads nothing now86 }87 }88 }89 for _, r := range s.regions.list {90 if me := s.meta(nameKey(r.Name)); me.reread && !r.File.Paged {91 me.reread = false92 changed = append(changed, s.emptyRegion(r, me)...)93 }94 }95 return changed96}9798// emptyRegion clears the region r's cells, outside the undo history,99// to be fed again whole, which it is marked stale for. The formulas100// naming it read #REF! until then, so they're among the cells returned.101func (s *Sheet) emptyRegion(r Region, me *regionMeta) []loc {102 changed := s.clearOwned(me, noRect)103 if t, ok := me.table(); ok {104 changed = append(changed, s.regionMoved(t, noRect, true)...)105 }106 if me.has {107 changed = s.regionReaders(r, changed)108 }109 me.has, me.rows, me.cols, me.why = false, 0, 0, ""110 me.data, me.stale = 0, true111 return changed112}113114// writeTable makes the region r show header and rows under it: with115// reset, rows replace every row; otherwise they follow those shown, and116// nil rows show already. It writes only the cells that differ and117// clears the region's cells it no longer needs; when a cell it needs118// holds something else, it shows only why. It returns the cells that119// changed, with the formulas naming the region when its table moved or120// changed shape.121func (s *Sheet) writeTable(r Region, me *regionMeta, header LiveRow, rows []LiveRow, reset bool) []loc {122 cols, nrows := tableSize(me, header, rows, reset)123 was, had, shape := me.written, me.has, [2]int{me.rows, me.cols}124 before, _ := me.table()125 need := s.tableArea(r, cols, nrows)126 changed := s.makeRoomAt(r)127 whole := me.why != "" || me.placed // it may not hold every cell it wrote128 me.why, me.placed = "", false129 switch {130 case need.To.Row >= MaxRows || need.To.Col >= MaxCols:131 me.why = "can't show: it would go past the edge of the sheet"132 default:133 at, blocked, later := s.inTheWay(r, need, me, whole)134 if blocked {135 me.why = "can't show: it would overwrite data in " + at.String()136 break137 }138 for _, q := range later {139 changed = append(changed, s.giveWay(q, need)...)140 }141 }142 if me.why != "" {143 me.need, me.rows, me.cols = need, 0, 0144 need, header, rows, cols, nrows = Rect{From: r.At, To: r.At}, nil, nil, 0, 0145 }146 changed = append(changed, s.clearOwned(me, need)...)147 me.written, me.has = need, true148 if me.why != "" || nrows == 0 && me.err != "" {149 changed = s.writeRegionCell(r.At, LiveCell{V: ErrRef}, me, changed)150 }151 o := r.At152 if cols > 0 {153 changed = s.writeRow(o, header, cols, true, me, changed)154 }155 for i, row := range rows {156 // A row written over one that showed clears what it doesn't157 // fill; one below the rows shown has nothing to clear.158 if row != nil {159 changed = s.writeRow(Addr{Col: o.Col, Row: o.Row + 1 + i}, row, cols, i+1 < me.rows || reset, me, changed)160 }161 }162 me.rows, me.cols = nrows, cols163 if !me.fitted && nrows > 1 {164 me.fitted = true165 s.fitTable(o, nrows, cols)166 }167 for _, c := range changed {168 s.freedFor(c.a)169 }170 // Blocked, what's in its way may have gone but for arrays, which then171 // find it (heldByArrays).172 if after, _ := me.table(); !had || after != before || me.why != "" {173 changed = append(changed, s.regionMoved(before, after, had)...)174 }175 if !had || was != me.written || shape != [2]int{me.rows, me.cols} { // moved, or its table changed shape176 changed = s.regionReaders(r, changed)177 }178 return changed179}180181// regionReaders adds the formulas naming the region r to changed.182func (s *Sheet) regionReaders(r Region, changed []loc) []loc {183 for _, k := range []string{nameKey(r.FormulaName()), nameKey(r.Name)} {184 for u := range s.wb.nameUsers[k] {185 changed = append(changed, u)186 }187 }188 return changed189}190191// tableSize is how many columns and rows, header included, a table192// takes to show header and rows: none when there's nothing to show.193func tableSize(me *regionMeta, header LiveRow, rows []LiveRow, reset bool) (cols, nrows int) {194 cols = len(header)195 for _, r := range rows {196 cols = max(cols, len(r))197 }198 if !reset {199 cols = max(cols, me.cols)200 }201 if cols > 0 {202 nrows = 1 + len(rows)203 }204 return cols, nrows205}206207// tableArea is the cells r takes to show a table of cols by nrows, or208// its anchor alone for an empty one.209func (s *Sheet) tableArea(r Region, cols, nrows int) Rect {210 end := r.At.Row + max(nrows, 1) - 1211 return Rect{From: r.At, To: Addr{Col: r.At.Col + max(cols, 1) - 1, Row: end}}212}213214// writeRow writes a table's row at at, cols cells wide, adding the cells215// that changed to changed. With all, the cells the row leaves blank are216// cleared; otherwise they're blank already.217func (s *Sheet) writeRow(at Addr, row LiveRow, cols int, all bool, me *regionMeta, changed []loc) []loc {218 for c := range cols {219 var lc LiveCell220 if c < len(row) {221 lc = row[c]222 }223 if lc.V.Kind == Empty && !all {224 continue225 }226 changed = s.writeRegionCell(Addr{Col: at.Col + c, Row: at.Row}, lc, me, changed)227 }228 return changed229}230231// writeRegionCell makes the cell at a show c as the region's, keeping232// its formatting and note, when it's free for the region to write,233// adding it to changed if it changed.234func (s *Sheet) writeRegionCell(a Addr, c LiveCell, me *regionMeta, changed []loc) []loc {235 if !s.writable(a, me) {236 return changed237 }238 if s.writeSpilled(a, c.V, c.F) {239 changed = append(changed, loc{s, a})240 }241 return changed242}243244// regionCell reports whether a region holds the cell at a.245func (s *Sheet) regionCell(a Addr) bool {246 if len(s.regions.list) == 0 {247 return false248 }249 _, _, ok := s.ownerOf(a)250 return ok251}252253// fresh is the parts of need the region with meta me doesn't hold: all254// of it, or when need grows from what it holds, the strips to its right255// and below, so rows arriving cost what they add.256func fresh(need Rect, me *regionMeta) []Rect {257 w := me.written258 if !me.has || w.From != need.From {259 return []Rect{need}260 }261 var out []Rect262 if need.To.Col > w.To.Col {263 out = append(out, Rect{From: Addr{Col: w.To.Col + 1, Row: need.From.Row}, To: need.To})264 }265 if need.To.Row > w.To.Row {266 out = append(out, Rect{From: Addr{Col: need.From.Col, Row: w.To.Row + 1}, To: Addr{Col: min(need.To.Col, w.To.Col), Row: need.To.Row}})267 }268 return out269}270271// writable reports whether the cell at a is free for the region with272// meta me to write.273func (s *Sheet) writable(a Addr, me *regionMeta) bool {274 if s.regionOwns(a, me) {275 return true276 }277 _, spilled := s.SpillAnchor(a) // an array's blank cell is its own278 return !spilled && !s.cells.filledAt(a)279}280281// regionsGiveWay empties the regions whose tables are in area, where an282// array spills: an array spills over a region's table, whichever came283// first, as it does when the file opens and arrays spill before regions284// are sent their rows. Sent again, a region finds the array in its way.285func (s *Sheet) regionsGiveWay(area Rect) []loc {286 var changed []loc287 for _, r := range s.regions.list {288 if me := s.regionMeta[nameKey(r.Name)]; me != nil && me.has && overlaps(me.written, area) {289 changed = append(changed, s.emptyRegion(r, me)...)290 }291 }292 return changed293}294295// regionOwns reports whether the cell at a is one the region with meta296// me wrote: a derived cell of what it wrote last that no formula297// spilled.298func (s *Sheet) regionOwns(a Addr, me *regionMeta) bool {299 if !me.has || !me.written.Contains(a) || s.cells.derivedAt(a) != slotSpill {300 return false301 }302 _, spilled := s.SpillAnchor(a)303 return !spilled304}305306// clearOwned clears the cells the region with meta me wrote outside307// keep, turning them back into what they were: blank, or formatting308// only.309func (s *Sheet) clearOwned(me *regionMeta, keep Rect) []loc {310 if !me.has || keep.Contains(me.written.From) && keep.Contains(me.written.To) {311 return nil312 }313 var gone []Addr314 for a := range s.cells.anyKeysIn(me.written) {315 if !keep.Contains(a) && s.regionOwns(a, me) {316 gone = append(gone, a)317 }318 }319 changed := make([]loc, len(gone))320 for i, a := range gone {321 s.setDerived(a, s.cells.get(a).leftover())322 changed[i] = loc{s, a}323 }324 s.wakeRegions(me, gone)325 return changed326}