package main import ( "fmt" "strings" "tildegit.org/sloum/tally/qline" ) type sheet struct { name string selection point cells [][]cell cols int rows int zoom int rowOff int colOff int yankBuff cell yankPoint point } func (s sheet) Draw(termCols, termRows int) { var selected bool rowDigits := DigitCount(len(s.cells)) width := (termCols - rowDigits) / s.zoom if width * s.zoom + rowDigits + 1 > termCols { width -= 1 } // Print column header fmt.Printf("%s ", strings.Repeat(" ", rowDigits)) for i := s.colOff+1; i <= s.colOff + s.zoom; i++ { if i > s.cols { break } pre := "\033[7m" if i == s.selection.col { pre = "" } fmt.Printf("%s%-*c\033[0m", pre, width, i+64) } fmt.Print("\n") // Print Rows for row, r := range s.cells[s.rowOff:] { if row >= termRows-5 { break } mod := "\033[7m" if row + s.rowOff == s.selection.row-1 { mod = "" } fmt.Printf("%s%-*d \033[0m", mod, rowDigits, row+1+s.rowOff) // Print Cells for col, cell := range r[s.colOff:] { if col >= s.zoom { break } selected = false if s.selection.row-1 == row + s.rowOff && s.selection.col-1 == col + s.colOff { selected = true } fmt.Printf(cell.String(width, selected)) } fmt.Print("\n\033[2K") } } func (s sheet) CurrentValue(raw bool) string { if raw { return s.cells[s.selection.row-1][s.selection.col-1].rawVal } return s.cells[s.selection.row-1][s.selection.col-1].mask } func (s *sheet) Recalculate() { for row, _ := range s.cells { for col, _ := range s.cells[row] { // TODO do this concurrently s.cells[row][col].Calculate() } } } func (s *sheet) AddRows(count int) { for ;count > 0; count-- { s.cells = append(s.cells, make([]cell, s.cols)) s.rows++ } } func (s *sheet) AddCols(count int) { for ;count > 0; count-- { for i, _ := range s.cells { s.cells[i] = append(s.cells[i], cell{}) } s.cols++ } } func (s *sheet) Yank() { c := s.cells[s.selection.row-1][s.selection.col-1] s.yankBuff = cell{} s.yankBuff.kind = c.kind s.yankBuff.rawVal = c.rawVal s.yankBuff.num = c.num s.yankBuff.mask = c.mask s.yankBuff.expr = append(s.yankBuff.expr, c.expr...) s.yankBuff.mods = append(s.yankBuff.mods, c.mods...) s.yankPoint = point{row: s.selection.row-1, col: s.selection.col-1} } func (s *sheet) Paste() { c := cell{} c.kind = s.yankBuff.kind c.rawVal = s.yankBuff.rawVal c.num = s.yankBuff.num c.mask = s.yankBuff.mask c.expr = append(c.expr, s.yankBuff.expr...) c.mods = append(c.mods, s.yankBuff.mods...) s.cells[s.selection.row-1][s.selection.col-1] = c } func (s *sheet) PasteRelative() { c := cell{} c.kind = s.yankBuff.kind c.rawVal = s.yankBuff.rawVal c.num = s.yankBuff.num c.mask = s.yankBuff.mask c.expr = append(c.expr, s.yankBuff.expr...) c.mods = append(c.mods, s.yankBuff.mods...) if c.kind == Expr { for i, v := range c.expr { if IsAddr(v) { refPoint, lockRow, lockCol := Addr2Point(v) diff := point{row: s.yankPoint.row-s.selection.row+1, col: s.yankPoint.col-s.selection.col+1} diffApply := point{row: refPoint.row, col: refPoint.col} if !lockRow { diffApply.row = refPoint.row-diff.row } if !lockCol { diffApply.col = refPoint.col-diff.col } p2a := Point2Addr(diffApply, lockRow, lockCol) c.expr[i] = p2a } else if IsRange(v) { op := v[len(v)-1] points := strings.Split(v[:len(v)-1], ":") startRefPoint, lockStartRow, lockStartCol := Addr2Point(points[0]) endRefPoint, lockEndRow, lockEndCol := Addr2Point(points[1]) diff := point{row: s.yankPoint.row-s.selection.row+1, col: s.yankPoint.col-s.selection.col+1} startApply := point{row: startRefPoint.row, col: startRefPoint.col} endApply := point{row: endRefPoint.row, col: endRefPoint.col} if !lockStartRow { startApply.row = startRefPoint.row-diff.row } if !lockStartCol { startApply.col = startRefPoint.col-diff.col } if !lockEndRow { endApply.row = endRefPoint.row-diff.row } if !lockEndCol { endApply.col = endRefPoint.col-diff.col } c.expr[i] = fmt.Sprintf("%s:%s%c", Point2Addr(startApply, lockStartRow, lockStartCol), Point2Addr(endApply, lockEndRow, lockEndCol), op) } } c.rawVal = strings.Join(c.expr, " ") } s.cells[s.selection.row-1][s.selection.col-1] = c } func (s *sheet) moveSelection(dir rune, termRows int) { switch dir { case Left, qline.LeftArrow: if s.selection.col > 1 { s.selection.col-- } if s.selection.col <= s.colOff { s.colOff-- } case Right, qline.RightArrow: s.selection.col++ if s.selection.col > s.cols && s.selection.col < 27 { s.AddCols(1) } if s.selection.col > s.colOff + s.zoom && s.cols > s.zoom { s.colOff++ } if s.selection.col > 26 { s.selection.col = 26 } case Up, qline.UpArrow: if s.selection.row > 1 { s.selection.row-- } if s.selection.row <= s.rowOff { s.rowOff-- } case Down, qline.DownArrow: s.selection.row++ if s.selection.row > s.rows { s.AddRows(1) } if s.selection.row > s.rowOff + termRows - 5 && s.rows > termRows - 5 { s.rowOff++ } case RowStart, qline.Home: s.selection.col = 1 s.colOff = 0 case ToTop, qline.PageUp: s.selection.row = 1 s.rowOff = 0 case RowEnd, qline.End: s.selection.col = s.cols s.colOff = s.cols - s.zoom if s.colOff < 0 { s.colOff = 0 } case ToBottom, qline.PageDown: s.selection.row = s.rows s.rowOff = s.rows - termRows + 5 if s.rowOff < 0 { s.rowOff = 0 } } } func (s *sheet) ZoomIn() { if s.zoom == 1 { return } s.zoom-- // Zooming in reduces number of cols viewed } func (s *sheet) ZoomOut() { s.zoom++ // Zooming out increases number of cols viewed } func (s *sheet) TrimSheet() { trimmed := false for rowIndex := s.rows-1; rowIndex > 0; rowIndex-- { remove := true for _, cell := range s.cells[rowIndex] { if cell.kind != Empty { remove = false break } } if !remove { break } s.cells = s.cells[:rowIndex] s.rows-- trimmed = true } for colIndex := s.cols-1; colIndex > 0; colIndex-- { remove := true for _, row := range s.cells { if row[colIndex].kind != Empty { remove = false break } } if !remove { break } for i, _ := range s.cells { s.cells[i] = s.cells[i][:len(s.cells[i])-1] } s.cols-- trimmed = true } if trimmed { s.selection = point{1,1} s.rowOff = 0 s.colOff = 0 } } func makeSheet(name string) sheet { if name == "" { name = "Sheet1" } row := make([][]cell,1, 26) row[0] = make([]cell, 1, 26) return sheet{name, point{1,1}, row, 1, 1, 6, 0, 0, cell{}, point{}} }