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 / regionshape.go
regionshape.go 4.3K
1package sheet23import (4 "src.rest/cat/012/internal/formula"5 "src.rest/cat/012/internal/functions"6)78// Where regions are (see region.go): the cells they cover and their9// tables, finding them by cell and by the name formulas use, and keeping10// them in step with inserted and deleted lines.1112// noRect contains no cell.13var noRect = Rect{From: Addr{Col: -1, Row: -1}, To: Addr{Col: -1, Row: -1}}1415// RegionTable returns the cells of a region's table, header row16// included, when it has one.17func (s *Sheet) RegionTable(name string) (Rect, bool) {18 i := s.regionIndex(nameKey(name))19 if i < 0 {20 return Rect{}, false21 }22 r := s.regions.list[i]23 if r.File.Paged {24 t, _, ok := s.sourceTable(r)25 return t, ok26 }27 me := s.regionMeta[nameKey(r.Name)]28 if me == nil || me.why != "" || me.rows == 0 || me.cols == 0 {29 return Rect{}, false30 }31 o := r.At32 return Rect{From: o, To: Addr{Col: min(o.Col+me.cols-1, MaxCols-1), Row: min(o.Row+me.rows-1, MaxRows-1)}}, true33}3435// covered is the cells a region holds: those it wrote, or while it's36// yet to write them, its anchor.37func (s *Sheet) covered(r Region) Rect {38 if me := s.regionMeta[nameKey(r.Name)]; me != nil && me.has {39 return me.written40 }41 return Rect{From: r.At, To: r.At}42}4344// RegionAt returns the region covering the cell at a.45func (s *Sheet) RegionAt(a Addr) (Region, bool) {46 for _, r := range s.regions.list {47 if s.covered(r).Contains(a) {48 return r, true49 }50 }51 return Region{}, false52}5354// InRegion returns a cell of r a region covers, and the region.55func (s *Sheet) InRegion(r Rect) (Addr, Region, bool) {56 for _, reg := range s.regions.list {57 if overlap, ok := intersectRect(s.covered(reg), r); ok {58 return overlap.From, reg, true59 }60 }61 return Addr{}, Region{}, false62}6364// shiftRegions keeps the sheet's regions in step with rows or columns65// inserted or deleted, before the cells move: a region the lines reach66// is emptied first, so cells moving where it was aren't taken for its67// own, and its anchor moves as a cell would; one whose anchor is deleted68// goes. Once the change ends, the regions emptied are fed again.69func (s *Sheet) shiftRegions(rows bool, sp formula.Span) {70 if len(s.regions.list) == 0 {71 return72 }73 cell, _ := formula.AxisMaps(rows, sp)74 st := s.regionsCopy()75 st.list = st.list[:0]76 for _, r := range s.regions.list {77 area := s.covered(r)78 end := area.To.Col79 if rows {80 end = area.To.Row81 }82 if end < sp.At {83 st.list = append(st.list, r) // before the lines: untouched84 continue85 }86 for _, c := range s.emptyRegion(r, s.meta(nameKey(r.Name))) {87 s.wb.markDirty(c)88 }89 at, ok := cell(r.At)90 if !ok {91 continue92 }93 r.At = at94 st.list = append(st.list, r)95 }96 if !sameRegions(st, s.regions) {97 s.putRegions(st)98 }99 s.regionsStale = true100}101102// regionName resolves a name formulas use, nu.sales, to the region's103// sheet and table.104func (w *Workbook) regionName(k string) (*Sheet, Rect, bool) {105 if len(k) <= len(regionPrefix) || k[:len(regionPrefix)] != nameKey(regionPrefix) {106 return nil, Rect{}, false107 }108 for _, s := range w.sheets {109 if i := s.regionIndex(k[len(regionPrefix):]); i >= 0 {110 r, ok := s.RegionTable(s.regions.list[i].Name)111 if !ok {112 return s, Rect{}, true113 }114 return s, r, true115 }116 }117 return nil, Rect{}, false118}119120// regionsInUse are the regions formulas name, with their tables, for121// finding what reads a changed cell.122func (w *Workbook) regionsInUse() []namedUsers {123 var out []namedUsers124 for _, s := range w.sheets {125 for _, r := range s.regions.list {126 users := w.nameUsers[nameKey(r.FormulaName())]127 if len(users) == 0 {128 continue129 }130 if t, ok := s.RegionTable(r.Name); ok {131 out = append(out, namedUsers{s: s, r: t, users: users, region: true})132 }133 }134 }135 return out136}137138// boundRegion is a name in a formula that isn't a named range: a139// region's table (nu.r1), #REF! while it has none, or the name as140// written, which shows #NAME?.141func (s *Sheet) boundRegion(nn formula.Name) Node {142 t, r, ok := s.wb.regionName(nameKey(nn.Name))143 switch {144 case !ok:145 return nn146 case r == (Rect{}) && t.IsSource() && t.sourceErr(t.regions.list[0]) == "":147 return functions.Const(Pending) // its host is opening it148 case r == (Rect{}):149 return formula.RefErr{}150 }151 sheet := ""152 if t != s {153 sheet = t.name154 }155 const fixed = formula.AbsCol | formula.AbsRow156 return formula.Range{Rect: r, Abs: [2]formula.Abs{fixed, fixed}, Sheet: sheet}157}