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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
|
// Package theme is glint's single source of color truth. Every styled span in
// every theme gets an explicit foreground — no terminal-default fallbacks — so
// the editor reads cleanly on both light and dark terminals.
package theme
import (
"math"
"strconv"
"strings"
"github.com/charmbracelet/lipgloss"
)
// Theme holds every color glint paints, plus its name and the glamour style the
// read-preview should use to stay visually in sync.
type Theme struct {
Name string
GlamourStyle string
// Markdown element colors.
Text lipgloss.Color // base prose
Emphasis lipgloss.Color // bold/italic — higher contrast than Text
Heading lipgloss.Color // heading text (bold)
Code lipgloss.Color // inline + fenced code
Link lipgloss.Color // links, URLs, and wikilink targets
Wikilink lipgloss.Color // retained == Link (kept for the all-colors check)
ListMarker lipgloss.Color // list bullets / numbers
Blockquote lipgloss.Color // blockquote marker + border (muted tone)
Comment lipgloss.Color // HTML / %% comments — visible, not dimmed
Accent lipgloss.Color // frontmatter keys, selection
Highlight lipgloss.Color // ==highlight== background tint
Spell lipgloss.Color // misspelled-word undercurl (red)
Grammar lipgloss.Color // grammar-issue undercurl (green; Harper, TASK-043)
TK lipgloss.Color // 'TK'/'tk' placeholder badge background (fill-in marker, TASK-045)
// Merge-conflict highlighting (git markers <<<<<<< ||||||| ======= >>>>>>>).
ConflictMarker lipgloss.Color // bold marker lines
ConflictOurs lipgloss.Color // background tint for the "ours"/base block
ConflictTheirs lipgloss.Color // background tint for the "theirs" block
// UI colors.
Background lipgloss.Color
Muted lipgloss.Color // markup punctuation, dimmed
StatusFg lipgloss.Color
StatusBg lipgloss.Color
SelFg lipgloss.Color
SelBg lipgloss.Color
Pointer lipgloss.Color
}
// CycleOrder is the order Ctrl+T steps through themes.
var CycleOrder = []string{"flexoki-light", "flexoki-dark", "charm"}
func registry() map[string]Theme {
return map[string]Theme{
"flexoki-light": FlexokiLight(),
"flexoki-dark": FlexokiDark(),
"charm": Charm(),
}
}
// ByName looks up a registered theme.
func ByName(name string) (Theme, bool) {
t, ok := registry()[name]
return t, ok
}
// Next returns the theme after name in CycleOrder, wrapping around. An unknown
// name yields the first theme in the cycle.
func Next(name string) Theme {
idx := 0
for i, n := range CycleOrder {
if n == name {
idx = (i + 1) % len(CycleOrder)
break
}
}
t, _ := ByName(CycleOrder[idx])
return t
}
// Resolve picks a theme from a config value: "auto"/"" → OS detection; a known
// name → that theme; an unknown name → OS detection. It never errors and never
// returns an empty theme.
func Resolve(configValue string) Theme {
if configValue == "" || configValue == "auto" {
t, _ := ByName(Detect())
return t
}
if t, ok := ByName(configValue); ok {
return t
}
t, _ := ByName(Detect())
return t
}
// HexToRGB converts "#RRGGBB" to the "R;G;B" decimal form used in SGR codes.
// A malformed color yields "" so callers can fall back to unstyled output.
func HexToRGB(hex string) string {
h := strings.TrimPrefix(hex, "#")
if len(h) != 6 {
return ""
}
r, err1 := strconv.ParseInt(h[0:2], 16, 0)
g, err2 := strconv.ParseInt(h[2:4], 16, 0)
b, err3 := strconv.ParseInt(h[4:6], 16, 0)
if err1 != nil || err2 != nil || err3 != nil {
return ""
}
return strconv.FormatInt(r, 10) + ";" + strconv.FormatInt(g, 10) + ";" + strconv.FormatInt(b, 10)
}
// LegibleText returns a near-black or near-paper text color, whichever
// contrasts better with the background hex, so heading text stays readable on
// any heading color.
func LegibleText(bgHex string) string {
if RelLuminance(bgHex) > 0.5 {
return "#100F0F"
}
return "#FFFCF0"
}
// RelLuminance is the WCAG relative luminance of an "#RRGGBB" color, 0 for a
// malformed one.
func RelLuminance(hex string) float64 {
h := strings.TrimPrefix(hex, "#")
if len(h) != 6 {
return 0
}
chan8 := func(s string) float64 {
n, _ := strconv.ParseInt(s, 16, 0)
c := float64(n) / 255
if c <= 0.03928 {
return c / 12.92
}
return math.Pow((c+0.055)/1.055, 2.4)
}
return 0.2126*chan8(h[0:2]) + 0.7152*chan8(h[2:4]) + 0.0722*chan8(h[4:6])
}
|