1package tui
2
3import "strings"
4
5const (
6 InputStylePlain = "plain"
7 InputStyleLines = "lines"
8 InputStyleBlock = "block"
9
10 StatusPositionAboveInput = "above_input"
11 StatusPositionBelowInput = "below_input"
12
13 WorkingPositionAboveInput = "above_input"
14 WorkingPositionBelowInput = "below_input"
15)
16
17func NormalizeInputStyle(v string) string {
18 switch strings.ToLower(strings.TrimSpace(v)) {
19 case InputStyleLines, "line":
20 return InputStyleLines
21 case InputStyleBlock, "bubble", "boxed", "box":
22 return InputStyleBlock
23 default:
24 return InputStylePlain
25 }
26}
27
28func NormalizeStatusPosition(v string) string {
29 switch strings.ToLower(strings.TrimSpace(v)) {
30 case StatusPositionBelowInput, "below", "bottom":
31 return StatusPositionBelowInput
32 default:
33 return StatusPositionAboveInput
34 }
35}
36
37func NormalizeWorkingPosition(v string) string {
38 switch strings.ToLower(strings.TrimSpace(v)) {
39 case WorkingPositionBelowInput, "below", "bottom":
40 return WorkingPositionBelowInput
41 default:
42 return WorkingPositionAboveInput
43 }
44}
45
46func InputLines(th Theme, lines []string, width int) []string {
47 if width < 1 {
48 width = 1
49 }
50 rule := th.FGColor(th.Muted, strings.Repeat("─", width))
51 out := make([]string, 0, len(lines)+2)
52 out = append(out, rule)
53 out = append(out, lines...)
54 out = append(out, rule)
55 return out
56}
57
58func InputBlock(th Theme, lines []string, width int) []string {
59 if width < 1 {
60 width = 1
61 }
62 bubbleW := width - 2
63 if bubbleW < 1 {
64 bubbleW = 1
65 }
66 bg := th.sgrBGColor(th.UserBubbleBG)
67 bar := bg + th.FGColor(th.Accent, "▌ ")
68 row := func(content string) string {
69 visible := visibleWidth(content)
70 if visible < bubbleW {
71 content += strings.Repeat(" ", bubbleW-visible)
72 }
73 body := bg + strings.ReplaceAll(content, reset, reset+bg) + reset
74 return bar + body
75 }
76 out := make([]string, 0, len(lines)+2)
77 out = append(out, row(""))
78 for _, line := range lines {
79 out = append(out, row(line))
80 }
81 out = append(out, row(""))
82 return out
83}
84
85// CursorColor returns OSC 12 to set the terminal cursor color. Unlike SGR
86// styling, the cursor is terminal-owned, so modal backdrops must set this
87// separately from their dimmed text rows.
88func CursorColor(color TerminalColor) string {
89 rgb, ok := rgbForTerminalColor(color)
90 if !ok {
91 return ""
92 }
93 return "\x1b]12;rgb:" + hexByte(rgb[0]) + "/" + hexByte(rgb[1]) + "/" + hexByte(rgb[2]) + "\x07"
94}
95
96func CursorColor256(index int) string { return CursorColor(Color256(index)) }
97
98func ResetCursorColor() string { return "\x1b]112\x07" }
99
100func CursorShapeBlock() string { return "\x1b[1 q" }
101
102func ResetCursorShape() string { return "\x1b[0 q" }
103
104func xterm256RGB(index int) (int, int, int) {
105 if index < 0 {
106 index = 0
107 }
108 if index > 255 {
109 index = 255
110 }
111 ansi := [16][3]int{
112 {0, 0, 0}, {128, 0, 0}, {0, 128, 0}, {128, 128, 0},
113 {0, 0, 128}, {128, 0, 128}, {0, 128, 128}, {192, 192, 192},
114 {128, 128, 128}, {255, 0, 0}, {0, 255, 0}, {255, 255, 0},
115 {0, 0, 255}, {255, 0, 255}, {0, 255, 255}, {255, 255, 255},
116 }
117 if index < 16 {
118 c := ansi[index]
119 return c[0], c[1], c[2]
120 }
121 if index < 232 {
122 idx := index - 16
123 levels := [6]int{0, 95, 135, 175, 215, 255}
124 return levels[idx/36], levels[(idx/6)%6], levels[idx%6]
125 }
126 gray := 8 + (index-232)*10
127 return gray, gray, gray
128}
129
130func hexByte(v int) string {
131 const digits = "0123456789abcdef"
132 if v < 0 {
133 v = 0
134 }
135 if v > 255 {
136 v = 255
137 }
138 return string([]byte{digits[v>>4], digits[v&0xf]})
139}