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 / nav.go
nav.go 3.7K
1package sheet23// UsedRange returns the smallest range from A1 covering every non-blank4// cell, and false if the sheet is empty.5func (s *Sheet) UsedRange() (Rect, bool) {6 b, ok := s.cells.filled.bounds(Rect{To: Addr{Col: MaxCols - 1, Row: MaxRows - 1}})7 return Rect{To: b.To}, ok8}910// FilledBounds returns the smallest range holding every non-blank cell11// of r, and false if there is none.12func (s *Sheet) FilledBounds(r Rect) (Rect, bool) { return s.cells.filled.bounds(r) }1314// Edge returns where a data-edge jump (Ctrl+arrow in Excel, End+arrow in15// 1-2-3) from a in direction (dc, dr) lands. Inside a block of filled cells16// it stops at the block's last cell; from a blank cell, or at the end of a17// block, it goes to the next filled cell. With nothing filled ahead it18// stops at the edge of the worksheet. Rows the filter hides are skipped.19// Blank stretches are jumped over through the index of filled cells, so20// a jump costs what the block it walks holds, not the empty rows past it.21func (s *Sheet) Edge(a Addr, dc, dr int) Addr {22 filled := func(a Addr) bool { return s.cells.filled.has(a) }23 step := func(a Addr) Addr {24 if dr != 0 {25 r, _ := s.NextShownRow(a.Row, dr) // off the sheet when there is none26 return Addr{Col: a.Col, Row: r}27 }28 return Addr{Col: a.Col + dc, Row: a.Row}29 }30 next := step(a)31 if !next.Valid() {32 return a33 }34 if filled(a) && filled(next) {35 for {36 n := step(next)37 if !n.Valid() || !filled(n) {38 return next39 }40 next = n41 }42 }43 if f, ok := s.nextFilled(a, dc, dr); ok {44 return f45 }46 return s.sheetEdge(a, dc, dr)47}4849// nextFilled is the first filled cell past a in direction (dc, dr), on50// a row the filter doesn't hide.51func (s *Sheet) nextFilled(a Addr, dc, dr int) (Addr, bool) {52 fl := &s.cells.filled53 if dr != 0 {54 for row := a.Row + dr; ; {55 r, ok := fl.nextRow(a.Col, row, dr)56 if !ok {57 return Addr{}, false58 }59 if !s.RowHidden(r) {60 return Addr{Col: a.Col, Row: r}, true61 }62 row = r + dr63 }64 }65 limit := MaxCols - 166 if dc < 0 {67 limit = 068 }69 c, ok := s.NextFilledCol(a.Row, a.Col+dc, dc, limit)70 return Addr{Col: c, Row: a.Row}, ok71}7273// NextFilledCol returns the nearest column of row, from col on in74// direction dir (1 or -1) and no further than limit, whose cell has75// contents, looking only at the columns that hold any.76func (s *Sheet) NextFilledCol(row, col, dir, limit int) (int, bool) {77 fl := &s.cells.filled78 lo, hi := col, limit79 if dir < 0 {80 lo, hi = limit, col81 }82 if lo > hi {83 return 0, false84 }85 cols := fl.colsIn(max(lo, 0), min(hi, MaxCols-1))86 for i := range cols {87 c := cols[i]88 if dir < 0 {89 c = cols[len(cols)-1-i]90 }91 if fl.has(Addr{Col: c, Row: row}) {92 return c, true93 }94 }95 return 0, false96}9798// sheetEdge is the last cell from a in direction (dc, dr): the sheet's99// last (or first) column, or its last (or first) row the filter doesn't100// hide, or a itself when every row past it is hidden.101func (s *Sheet) sheetEdge(a Addr, dc, dr int) Addr {102 switch {103 case dc > 0:104 a.Col = MaxCols - 1105 case dc < 0:106 a.Col = 0107 case dr > 0:108 if r, ok := s.NextShownRow(MaxRows, -1); ok && r > a.Row {109 a.Row = r110 }111 default:112 if r, ok := s.NextShownRow(-1, 1); ok && r < a.Row {113 a.Row = r114 }115 }116 return a117}118119// colRect is the range covering whole columns from..to.120func colRect(from, to int) Rect {121 return Rect{From: Addr{Col: from}, To: Addr{Col: to, Row: MaxRows - 1}}122}123124// rowRect is the range covering whole rows from..to.125func rowRect(from, to int) Rect {126 return Rect{From: Addr{Row: from}, To: Addr{Col: MaxCols - 1, Row: to}}127}128129func union(a, b Rect) Rect {130 return Rect{131 From: Addr{Col: min(a.From.Col, b.From.Col), Row: min(a.From.Row, b.From.Row)},132 To: Addr{Col: max(a.To.Col, b.To.Col), Row: max(a.To.Row, b.To.Row)},133 }134}