cat / 012
clone

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

Move trace, clipboard and cell drawing helpers off Model

Dami committed 62af88b
Whether a cell is traced or copied, and how a trace or a copied range is
described, are questions for trace and clipboard, given the sheet shown.
Drawing a span of cell text needs only the theme, and decorating it
with links and error marks only the grid.
commit
62af88b0fb5d0702ec13d4afe6fd8cf956fec5ea
parent
cd1b078290d5f134d5223fcf288ba73f8f271312
author
Dami <dami.etoile@gmail.com>
9 files changed, +55 -53
Minternal/ui/clipboard.go28+++++++++++-----------
Minternal/ui/commands.go2+-
Minternal/ui/edit_test.go4++--
Minternal/ui/links.go17+++++++------
Minternal/ui/model.go2+-
Minternal/ui/panel.go2+-
Minternal/ui/trace.go37+++++++++++++++---------------
Minternal/ui/trace_test.go4++--
Minternal/ui/view.go12+++++-----
M internal/ui/clipboard.go +14 -14
@@ -88,7 +88,7 @@ func (m *Model) paste(values bool) tea.Cmd {
8888 m.selectRect(r)8989 switch {9090 case c.cut && !values:91- m.note = "Moved " + m.clipLabel(c) + " to " + r.String()91+ m.note = "Moved " + c.label(m.sheet) + " to " + r.String()9292 case values:9393 m.note = "Pasted values into " + countCells(r) + " at " + r.String()9494 default:
@@ -97,27 +97,27 @@ func (m *Model) paste(values bool) tea.Cmd {
9797 return nil9898 }9999 100-// copyMarked reports whether a shows the copy marker.101-func (m *Model) copyMarked(a sheet.Addr) bool {102- return m.copied.marked && m.copied.sheet == m.sheet && m.copied.clip.Src.Contains(a)100+// marks reports whether a, on the sheet shown, shows the copy marker.101+func (c *clipboard) marks(shown *sheet.Sheet, a sheet.Addr) bool {102+ return c.marked && c.sheet == shown && c.clip.Src.Contains(a)103103 }104104 105-// clipLabel names the copied range, with its sheet when that isn't the105+// label names the copied range, with its sheet when that isn't the106106 // one shown: A1:B3, or Sheet1!A1:B3.107-func (m *Model) clipLabel(c clipboard) string {108- if c.sheet != m.sheet {107+func (c *clipboard) label(shown *sheet.Sheet) string {108+ if c.sheet != shown {109109 return sheet.Qualified(c.sheet.Name(), c.clip.Src)110110 }111111 return c.clip.Src.String()112112 }113113 114-// clearCopyMark hides the copy marker; a pending cut is cancelled, as in114+// clearMark hides the copy marker; a pending cut is cancelled, as in115115 // Sheets, since the cells it would move may have changed.116-func (m *Model) clearCopyMark() {117- if m.copied.cut {118- m.copied = clipboard{}116+func (c *clipboard) clearMark() {117+ if c.cut {118+ *c = clipboard{}119119 }120- m.copied.marked = false120+ c.marked = false121121 }122122 123123 // pasteText fills a block of cells from pasted text with tabs or line
@@ -225,9 +225,9 @@ func (m *Model) readyLine() string {
225225 case m.note != "":226226 return m.note227227 case m.copied.marked && m.copied.cut:228- return "Cut " + m.clipLabel(m.copied) + " " + m.th.KeyHints("Ctrl+V", "move here", "Esc", "cancel")228+ return "Cut " + m.copied.label(m.sheet) + " " + m.th.KeyHints("Ctrl+V", "move here", "Esc", "cancel")229229 case m.copied.marked:230- text := "Copied " + m.clipLabel(m.copied) + " "230+ text := "Copied " + m.copied.label(m.sheet) + " "231231 full := text + m.th.KeyHints("Ctrl+V", "paste", "Ctrl+Shift+V", "paste values", "Esc", "clear")232232 if ansi.StringWidth(full) <= m.width {233233 return full
M internal/ui/commands.go +1 -1
M internal/ui/edit_test.go +2 -2
M internal/ui/links.go +9 -8
@@ -4,6 +4,7 @@ import (
44 "charm.land/lipgloss/v2"55 66 "github.com/FelineStateMachine/012/internal/sheet"7+ "github.com/FelineStateMachine/012/internal/ui/theme"78 )89 910 // Cells whose text is a URL, and HYPERLINK formulas, are drawn as OSC 8
@@ -13,31 +14,31 @@ import (
1314 1415 // decorate marks a span's text as a link or an error, from the cell the1516 // text belongs to.16-func (m *Model) decorate(sp *span, row int) {17+func (g *grid) decorate(sp *span, row int) {1718 if sp.text == "" {1819 return1920 }2021 a := sheet.Addr{Col: sp.owner, Row: row}21- switch v := m.sheet.Value(a); {22+ switch v := g.sheet.Value(a); {2223 case v.Kind == sheet.Error && !sheet.IsPending(v):2324 sp.error = true2425 case v.Kind == sheet.Text:25- sp.link = m.sheet.Link(a)26+ sp.link = g.sheet.Link(a)2627 }2728 }2829 29-// textStyle is the style of a span's text on base: the cell's text style,30+// spanStyle is the style of a span's text on base: the cell's text style,3031 // then the link or error mark. On a colored role (the pointer, the3132 // selection) a link keeps the role's colors and adds its underline.32-func (m *Model) textStyle(base lipgloss.Style, sp span) lipgloss.Style {33+func spanStyle(th *theme.Theme, base lipgloss.Style, sp span) lipgloss.Style {3334 st := sp.style3435 st.Align = sheet.AlignAuto35- s := m.th.Text(base, st)36+ s := th.Text(base, st)3637 switch {3738 case sp.link != "":38- s = s.Inherit(m.th.Link).Hyperlink(sp.link)39+ s = s.Inherit(th.Link).Hyperlink(sp.link)3940 case sp.error:40- s = s.Inherit(m.th.ErrorMark)41+ s = s.Inherit(th.ErrorMark)4142 }4243 return s4344 }
M internal/ui/model.go +1 -1
M internal/ui/panel.go +1 -1
M internal/ui/trace.go +19 -18
@@ -8,6 +8,7 @@ import (
88 "github.com/charmbracelet/x/ansi"99 1010 "github.com/FelineStateMachine/012/internal/sheet"11+ "github.com/FelineStateMachine/012/internal/ui/theme"1112 )1213 1314 // Tracing precedents and dependents, after Excel's Ctrl+[ and Ctrl+]: the
@@ -88,23 +89,23 @@ func (m *Model) traceKey(k tea.KeyPressMsg) bool {
8889 return false8990 }9091 91-// traced reports whether a is in a range being traced.92-func (m *Model) traced(a sheet.Addr) bool {93- if m.trace == nil {92+// covers reports whether a, on the sheet shown, is in a range being93+// traced.94+func (t *trace) covers(shown *sheet.Sheet, a sheet.Addr) bool {95+ if t == nil {9496 return false9597 }96- for _, t := range m.trace.targets {97- if t.Sheet == m.sheet && t.Range.Contains(a) {98+ for _, tg := range t.targets {99+ if tg.Sheet == shown && tg.Range.Contains(a) {98100 return true99101 }100102 }101103 return false102104 }103105 104-// traceLine is the context line during a trace, e.g. "2 precedents of106+// line is the context line during a trace, e.g. "2 precedents of105107 // C7: B3, Sales", the current one emphasized, and the keys.106-func (m *Model) traceLine() (left, right string) {107- t := m.trace108+func (t *trace) line(th *theme.Theme, width int, shown *sheet.Sheet) (left, right string) {108109 noun, id := "precedent", "data.precedents"109110 if t.dependents {110111 noun, id = "dependent", "data.dependents"
@@ -112,29 +113,29 @@ func (m *Model) traceLine() (left, right string) {
112113 if len(t.targets) != 1 {113114 noun += "s"114115 }115- right = m.th.KeyHints(shortcut(id), "next", "Esc", "back")116+ right = th.KeyHints(shortcut(id), "next", "Esc", "back")116117 origin := t.origin.String()117- if t.home != m.sheet {118+ if t.home != shown {118119 origin = sheet.Qualified(t.home.Name(), sheet.Rect{From: t.origin, To: t.origin})119120 }120121 left = strconv.Itoa(len(t.targets)) + " " + noun + " of " + origin + ": "121- room := m.width - ansi.StringWidth(right) - 3122+ room := width - ansi.StringWidth(right) - 3122123 if room < ansi.StringWidth(left)+12 {123- right, room = "", m.width124+ right, room = "", width124125 }125126 var b strings.Builder126127 b.WriteString(left)127128 for i, tg := range t.targets {128- part := m.rangeLabel(tg, t.home)129+ part := targetLabel(shown, tg, t.home)129130 if i == t.at {130- part = m.th.Key.Render(part)131+ part = th.Key.Render(part)131132 }132133 if i > 0 {133134 part = ", " + part134135 }135136 more := " +" + strconv.Itoa(len(t.targets)-i) + " more"136137 if ansi.StringWidth(b.String()+part)+len(more) > room && i < len(t.targets)-1 {137- b.WriteString(m.th.Muted.Render(more))138+ b.WriteString(th.Muted.Render(more))138139 break139140 }140141 b.WriteString(part)
@@ -142,10 +143,10 @@ func (m *Model) traceLine() (left, right string) {
142143 return b.String(), right143144 }144145 145-// rangeLabel names a range the way a formula on from refers to it: by146+// targetLabel names a range the way a formula on from refers to it: by146147 // its name if it has one, with its sheet if it's on another.147-func (m *Model) rangeLabel(t sheet.Target, from *sheet.Sheet) string {148- for _, n := range m.sheet.Names() {148+func targetLabel(shown *sheet.Sheet, t sheet.Target, from *sheet.Sheet) string {149+ for _, n := range shown.Names() {149150 if !n.Gone() && n.Sheet == t.Sheet && n.Range == t.Range && t.Range.From != t.Range.To {150151 return n.Name151152 }
M internal/ui/trace_test.go +2 -2
M internal/ui/view.go +6 -6
@@ -156,7 +156,7 @@ func (m *Model) cellsText(row, first int, spans []span, focus sheet.Addr, sel sh
156156 base = m.th.Selection157157 case m.found(a):158158 base = m.th.Found159- case m.traced(a):159+ case m.trace.covers(m.sheet, a):160160 base = m.th.Traced161161 case sheet.IsPending(m.sheet.Value(a)):162162 base = m.th.Muted
@@ -166,7 +166,7 @@ func (m *Model) cellsText(row, first int, spans []span, focus sheet.Addr, sel sh
166166 colored = false167167 }168168 // The copy marker is layered on the cell's own colors.169- if m.copyMarked(a) {169+ if m.copied.marks(m.sheet, a) {170170 base, colored = base.Inherit(m.th.Copied), true171171 }172172 if a == m.cur && (m.mode == modeEnter || m.mode == modeEdit) && !m.away() {
@@ -174,7 +174,7 @@ func (m *Model) cellsText(row, first int, spans []span, focus sheet.Addr, sel sh
174174 } else {175175 m.decorate(&sp, row) // links and error marks, see links.go176176 }177- text := m.renderSpan(sp, base, colored)177+ text := renderSpan(&m.th, sp, base, colored)178178 if m.showFillHandle(a) {179179 w := m.sheet.ColWidth(a.Col)180180 text = ansi.Truncate(text, w-1, "") + base.Render("▟")
@@ -201,7 +201,7 @@ func (m *Model) dividerRow() string {
201201 202202 // renderSpan draws a span on base, one of the cell roles. Plain cells203203 // with no text style are written without escape codes.204-func (m *Model) renderSpan(sp span, base lipgloss.Style, colored bool) string {204+func renderSpan(th *theme.Theme, sp span, base lipgloss.Style, colored bool) string {205205 lead, trail := strings.Repeat(" ", sp.lead), strings.Repeat(" ", sp.trail)206206 st := sp.style207207 st.Align = sheet.AlignAuto
@@ -212,9 +212,9 @@ func (m *Model) renderSpan(sp span, base lipgloss.Style, colored bool) string {
212212 case plain:213213 return base.Render(lead + sp.text + trail)214214 case !colored:215- return lead + m.textStyle(base, sp).Render(sp.text) + trail215+ return lead + spanStyle(th, base, sp).Render(sp.text) + trail216216 }217- return base.Render(lead) + m.textStyle(base, sp).Render(sp.text) + base.Render(trail)217+ return base.Render(lead) + spanStyle(th, base, sp).Render(sp.text) + base.Render(trail)218218 }219219 220220 // inCellText shows the entry being typed inside the cell, keeping the end