1/**
2 * Internal ANSI escape sequence helpers used by the unstable CLI rendering
3 * implementation. This module centralizes the control codes for text styling,
4 * cursor movement, line erasing, and terminal notifications so renderers can
5 * compose terminal output without scattering raw escape sequences throughout
6 * the CLI internals.
7 *
8 * The helpers deliberately work with plain strings because terminal rendering
9 * is sensitive to ordering: style codes must be reset after annotated text,
10 * cursor operations must account for one-based terminal coordinates, and line
11 * clearing usually needs to preserve the cursor position expected by the next
12 * frame.
13 */
14const ESC = "\x1B["
15const BEL = "\x07"
16const SEP = ";"
17
18/**
19 * @internal
20 */
21export const reset = `${ESC}0m`
22
23/**
24 * @internal
25 */
26export const bold = `${ESC}1m`
27
28/**
29 * @internal
30 */
31export const italicized = `${ESC}3m`
32
33/**
34 * @internal
35 */
36export const underlined = `${ESC}4m`
37
38/**
39 * @internal
40 */
41export const strikethrough = `${ESC}9m`
42
43/**
44 * @internal
45 */
46export const cursorShow = `${ESC}?25h`
47
48/**
49 * @internal
50 */
51export const cursorHide = `${ESC}?25l`
52
53/**
54 * @internal
55 */
56export const cursorLeft = `${ESC}G`
57
58/**
59 * @internal
60 */
61export const cursorSavePosition = `${ESC}s`
62
63/**
64 * @internal
65 */
66export const cursorRestorePosition = `${ESC}u`
67
68/**
69 * @internal
70 */
71export const eraseLine = `${ESC}2K`
72
73/**
74 * @internal
75 */
76export const beep = BEL
77
78/**
79 * @internal
80 */
81export const red = `${ESC}31m`
82
83/**
84 * @internal
85 */
86export const green = `${ESC}32m`
87
88/**
89 * @internal
90 */
91export const magenta = `${ESC}35m`
92
93/**
94 * @internal
95 */
96export const white = `${ESC}37m`
97
98/**
99 * @internal
100 */
101export const blackBright = `${ESC}90m`
102
103/**
104 * @internal
105 */
106export const cyanBright = `${ESC}96m`
107
108export const annotate = (text: string, ...styles: Array<string | Array<string>>) => {
109 const flat = styles.flat()
110 return `${flat.join("")}${text}${reset}`
111}
112
113export const combine = (...styles: Array<string>): Array<string> => styles
114
115/**
116 * @internal
117 */
118export const cursorTo = (column: number, row?: number): string => {
119 if (row === undefined) {
120 return `${ESC}${Math.max(column + 1, 0)}G`
121 }
122 return `${ESC}${row + 1}${SEP}${Math.max(column + 1, 0)}H`
123}
124
125/**
126 * @internal
127 */
128export const cursorDown = (lines: number = 1): string => {
129 return `${ESC}${lines}B`
130}
131
132/**
133 * @internal
134 */
135export const cursorMove = (column: number, row: number = 0): string => {
136 let command = ""
137 if (row < 0) {
138 command += `${ESC}${-row}A`
139 }
140 if (row > 0) {
141 command += `${ESC}${row}B`
142 }
143 if (column > 0) {
144 command += `${ESC}${column}C`
145 }
146 if (column < 0) {
147 command += `${ESC}${-column}D`
148 }
149
150 return command
151}
152
153/**
154 * @internal
155 */
156export const eraseLines = (rows: number): string => {
157 let command = ""
158 for (let i = 0; i < rows; i++) {
159 command += `${ESC}2K` + (i < rows - 1 ? `${ESC}1A` : "")
160 }
161 if (rows > 0) {
162 command += `${ESC}G`
163 }
164
165 return command
166}