1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
package editor
import (
"regexp"
"github.com/charmbracelet/lipgloss"
)
// tkRe matches the journalism "TK" placeholder ("to come"): a whole word made of
// one or more "tk" pairs, any case โ "TK", "tk", "TKTK". The \b anchors keep it
// from firing inside real words (catkin, Atkins, outkast), where no word boundary
// precedes the "tk".
var tkRe = regexp.MustCompile(`(?i)\b(?:tk)+\b`)
// tkPass paints TK placeholder markers as a bold filled badge so they pop as
// fill-in reminders. It runs after the spell and grammar passes (and wins over
// them on the same runes), splitting each prose span so only the "TK" text is
// restyled; the concatenation of span texts still equals the raw line, preserving
// the markup-visible invariant. Non-prose spans (code, URLs, markup) are untouched.
func (e *Editor) tkPass(all [][]Span) [][]Span {
for li, spans := range all {
var out []Span
changed := false
for _, sp := range spans {
if !sp.Prose {
out = append(out, sp)
continue
}
split := e.splitTK(sp)
if len(split) != 1 {
changed = true
}
out = append(out, split...)
}
if changed {
all[li] = out
}
}
return all
}
// splitTK partitions one prose span's text, re-emitting each TK marker as a badge
// span (page-colored bold text on the loud theme.TK background). URLs are skipped.
// When no marker is present the original span is returned unchanged.
func (e *Editor) splitTK(sp Span) []Span {
text := sp.Text
locs := tkRe.FindAllStringIndex(text, -1)
if len(locs) == 0 {
return []Span{sp}
}
skips := urlRe.FindAllStringIndex(text, -1)
badge := lipgloss.NewStyle().Foreground(e.theme.Background).Background(e.theme.TK).Bold(true)
var out []Span
last := 0
emit := func(s string) {
if s == "" {
return
}
ns := sp
ns.Text = s
out = append(out, ns)
}
for _, m := range locs {
if overlapsAny(m, skips) {
continue
}
emit(text[last:m[0]])
badgeSpan := sp
badgeSpan.Text = text[m[0]:m[1]]
badgeSpan.Style = badge
badgeSpan.Wavy = false // a badge never also carries a spell/grammar underline
badgeSpan.Prose = false // and is inert to any later prose pass
out = append(out, badgeSpan)
last = m[1]
}
if last == 0 {
return []Span{sp} // every match was inside a URL
}
emit(text[last:])
return out
}
|