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>
| M | internal/ | 28 | +++++++++++----------- |
| M | internal/ | 2 | +- |
| M | internal/ | 4 | ++-- |
| M | internal/ | 17 | +++++++------ |
| M | internal/ | 2 | +- |
| M | internal/ | 2 | +- |
| M | internal/ | 37 | +++++++++++++++--------------- |
| M | internal/ | 4 | ++-- |
| M | internal/ | 12 | +++++----- |
@@ -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@@ -133,7 +133,7 @@ func init() {
133133 }},134134 &command{id: "select.none", title: "Deselect", desc: "Collapse the selection and clear the copy marker", run: func(m *Model) tea.Cmd {135135 m.clearSelection()136- m.clearCopyMark()136+ m.copied.clearMark()137137 return nil138138 }},139139 &command{id: "select.all", title: "Select all", desc: "Select the data, then the whole sheet", run: (*Model).selectAll},@@ -85,8 +85,8 @@ func TestCopyPaste(t *testing.T) {
8585 if got := clipboardText(t, m, "edit.copy"); got != "1\t2" {8686 t.Errorf("system clipboard %q", got)8787 }88- if l := line(m, 2); !m.copyMarked(addr("B1")) || !strings.HasPrefix(l, "Copied A1:B1") || !strings.Contains(l, "Ctrl+V") {89- t.Fatalf("marker %v, line %q", m.copyMarked(addr("B1")), line(m, 2))88+ if l := line(m, 2); !m.copied.marks(m.sheet, addr("B1")) || !strings.HasPrefix(l, "Copied A1:B1") || !strings.Contains(l, "Ctrl+V") {89+ t.Fatalf("marker %v, line %q", m.copied.marks(m.sheet, addr("B1")), line(m, 2))9090 }9191 press(t, m, "<down>", "<ctrl+v>")9292 if input(m, "B2") != "=A2*2" || m.sheet.Value(addr("B2")).Num != 2 || m.selection().String() != "A2:B2" {@@ -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 }@@ -189,7 +189,7 @@ func (m *Model) endUpdate(state int) {
189189 // An edit, a sort or a filter may hide the active cell's row.190190 m.cur.Row = m.visibleRow(m.cur.Row)191191 if !m.copied.keep {192- m.clearCopyMark()192+ m.copied.clearMark()193193 }194194 }195195 m.copied.keep = false@@ -152,7 +152,7 @@ func (m *Model) contextLineText() string {
152152 case m.entry.hint != "":153153 left = m.th.Warning.Render(m.entry.hint)154154 case m.mode == modeReady && m.trace != nil:155- left, right = m.traceLine()155+ left, right = m.trace.line(&m.th, m.width, m.sheet)156156 case m.mode == modeReady:157157 if left = m.readyLine(); left == "" {158158 left = m.jev.line(&m.th, m.sheet.RemoteCalls(m.cur))@@ -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 }@@ -19,7 +19,7 @@ func TestTracePrecedents(t *testing.T) {
1919 m := traceModel(t)2020 m.cur = addr("B1")2121 press(t, m, "<alt+,>")22- if m.cur != addr("A1") || !m.traced(addr("A3")) || m.traced(addr("B1")) {22+ if m.cur != addr("A1") || !m.trace.covers(m.sheet, addr("A3")) || m.trace.covers(m.sheet, addr("B1")) {2323 t.Fatalf("cur %v", m.cur)2424 }2525 if l := line(m, contextLine); !strings.HasPrefix(l, "2 precedents of B1: A1, A2:A3") || !strings.Contains(l, "Alt+, next") {@@ -44,7 +44,7 @@ func TestTraceDependents(t *testing.T) {
4444 m := traceModel(t)4545 m.cur = addr("B1")4646 press(t, m, "<alt+.>")47- if m.cur != addr("C1") || !m.traced(addr("C2")) {47+ if m.cur != addr("C1") || !m.trace.covers(m.sheet, addr("C2")) {4848 t.Fatalf("cur %v", m.cur)4949 }5050 // Any other key ends the trace and does its own thing.@@ -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