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.FG256(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 row := func(content string) string {
67 visible := visibleWidth(content)
68 if visible < bubbleW {
69 content += strings.Repeat(" ", bubbleW-visible)
70 }
71 bg := sgrBGColor(th.UserBubbleBG)
72 bar := bg + th.FG256(th.Accent, "▌ ")
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
85func CursorColor256(index int) string {
86 r, g, b := xterm256RGB(index)
87 return "\x1b]12;rgb:" + hexByte(r) + "/" + hexByte(g) + "/" + hexByte(b) + "\x07"
88}
89
90func ResetCursorColor() string { return "\x1b]112\x07" }
91
92func CursorShapeBlock() string { return "\x1b[1 q" }
93
94func ResetCursorShape() string { return "\x1b[0 q" }
95
96func xterm256RGB(index int) (int, int, int) {
97 if index < 0 {
98 index = 0
99 }
100 if index > 255 {
101 index = 255
102 }
103 ansi := [16][3]int{
104 {0, 0, 0}, {128, 0, 0}, {0, 128, 0}, {128, 128, 0},
105 {0, 0, 128}, {128, 0, 128}, {0, 128, 128}, {192, 192, 192},
106 {128, 128, 128}, {255, 0, 0}, {0, 255, 0}, {255, 255, 0},
107 {0, 0, 255}, {255, 0, 255}, {0, 255, 255}, {255, 255, 255},
108 }
109 if index < 16 {
110 c := ansi[index]
111 return c[0], c[1], c[2]
112 }
113 if index < 232 {
114 idx := index - 16
115 levels := [6]int{0, 95, 135, 175, 215, 255}
116 return levels[idx/36], levels[(idx/6)%6], levels[idx%6]
117 }
118 gray := 8 + (index-232)*10
119 return gray, gray, gray
120}
121
122func hexByte(v int) string {
123 const digits = "0123456789abcdef"
124 if v < 0 {
125 v = 0
126 }
127 if v > 255 {
128 v = 255
129 }
130 return string([]byte{digits[v>>4], digits[v&0xf]})
131}