cat / 012
clone

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

main
012 / internal / ui / nbview / outwin.go
outwin.go 3.5K
1package nbview23import (4 "github.com/charmbracelet/x/ansi"56 "src.rest/cat/012/internal/locale"7 "src.rest/cat/012/internal/ui/theme"8)910// An output's window: at most window of its rows (a grid's rows under11// its column header, a list's items, text's lines) from the row it's12// scrolled to, then a line saying what's left out. Whole shows every row13// instead, and hidden folds the output to one line, as Jupyter's14// collapsed outputs. The window's height doesn't change as it scrolls,15// so nothing under it moves.1617// fold is how a cell's output shows.18type fold struct {19 hidden bool // o: folded to one line20 whole bool // O: every row, no window21 scroll int // the window's first row, but a grid's, which the grid keeps22}2324// items is how many rows the output scrolls through at width.25func (sh *shown) items(width int) int {26 switch sh.kind {27 case outList:28 return sh.total29 case outText, outError:30 return len(sh.wrapped(width))31 case outNone:32 return 033 }34 return 135}3637// rowsShown is how many rows show, and whether a line saying what's left38// out follows them.39func (sh *shown) rowsShown(whole bool, width int) (n int, footer bool) {40 n = sh.items(width)41 if !whole && n > window {42 n, footer = window, true43 }44 return n, footer45}4647// height is how many lines the output takes at width.48func (sh *shown) height(whole bool, width int) int {49 switch {50 case sh.kind == outNone:51 return 052 case sh.isGrid():53 _, n := sh.gridRows(whole)54 lines := 1 + max(n, 1)55 if n < sh.grid.Rows() || sh.grid.Hidden(width) > 0 {56 lines++57 }58 return lines59 }60 n, footer := sh.rowsShown(whole, width)61 if footer {62 n++63 }64 return n65}6667// maxScroll is how far the window scrolls: 0 when it shows every row.68func (sh *shown) maxScroll(whole bool, width int) int {69 n, _ := sh.rowsShown(whole, width)70 return max(sh.items(width)-n, 0)71}7273// line draws line i of the output as f shows it, at width.74func (sh *shown) line(th *theme.Theme, loc *locale.Locale, i int, f fold, width int) string {75 if sh.isGrid() {76 return sh.gridLine(th, i, f, width)77 }78 n, _ := sh.rowsShown(f.whole, width)79 scroll := min(max(f.scroll, 0), sh.maxScroll(f.whole, width))80 if i < n {81 return sh.item(th, loc, scroll+i, width)82 }83 return sh.footer(th, scroll, n, width)84}8586// item draws row i: a list's item, a line of text, or the one value.87func (sh *shown) item(th *theme.Theme, loc *locale.Locale, i, width int) string {88 switch sh.kind {89 case outList:90 return sh.fieldLine(th, loc, i, width)91 case outText, outError:92 return sh.textLine(th, i, width)93 case outValue:94 return ansi.Truncate(cellText(sh.rows[0][0], loc), width, "…")95 case outUnsaved:96 return th.Muted.Render("not saved; run to see")97 case outBad:98 return th.Warning.Render(ansi.Truncate(sh.text[0], width, "…"))99 }100 return ""101}102103// footer says what the window leaves out: the rows under it, or where104// it's scrolled to.105func (sh *shown) footer(th *theme.Theme, scroll, n, width int) string {106 unit := "line"107 if sh.kind == outList {108 unit = "item"109 }110 total := sh.items(width)111 part := more(total-n, "more "+unit)112 if scroll > 0 {113 part = unit + "s " + grouped(scroll+1) + " to " + grouped(scroll+n) + " of " + grouped(total)114 }115 return ansi.Truncate(th.Muted.Render("… "+part+" (O shows all)"), width, "…")116}117118// textLine is line i of text or an error, wrapped at width.119func (sh *shown) textLine(th *theme.Theme, i, width int) string {120 lines := sh.wrapped(width)121 if i >= len(lines) {122 return ""123 }124 if sh.kind == outError {125 if i == 0 {126 return th.Warning.Render(lines[i])127 }128 return th.Muted.Render(lines[i])129 }130 return lines[i]131}