forked from ultraworkers/claw-code
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcsi.ts
More file actions
319 lines (261 loc) · 8.47 KB
/
csi.ts
File metadata and controls
319 lines (261 loc) · 8.47 KB
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
/**
* CSI (Control Sequence Introducer) Types
*
* Enums and types for CSI command parameters.
*/
import { ESC, ESC_TYPE, SEP } from './ansi.js'
export const CSI_PREFIX = ESC + String.fromCharCode(ESC_TYPE.CSI)
/**
* CSI parameter byte ranges
*/
export const CSI_RANGE = {
PARAM_START: 0x30,
PARAM_END: 0x3f,
INTERMEDIATE_START: 0x20,
INTERMEDIATE_END: 0x2f,
FINAL_START: 0x40,
FINAL_END: 0x7e,
} as const
/** Check if a byte is a CSI parameter byte */
export function isCSIParam(byte: number): boolean {
return byte >= CSI_RANGE.PARAM_START && byte <= CSI_RANGE.PARAM_END
}
/** Check if a byte is a CSI intermediate byte */
export function isCSIIntermediate(byte: number): boolean {
return (
byte >= CSI_RANGE.INTERMEDIATE_START && byte <= CSI_RANGE.INTERMEDIATE_END
)
}
/** Check if a byte is a CSI final byte (@ through ~) */
export function isCSIFinal(byte: number): boolean {
return byte >= CSI_RANGE.FINAL_START && byte <= CSI_RANGE.FINAL_END
}
/**
* Generate a CSI sequence: ESC [ p1;p2;...;pN final
* Single arg: treated as raw body
* Multiple args: last is final byte, rest are params joined by ;
*/
export function csi(...args: (string | number)[]): string {
if (args.length === 0) return CSI_PREFIX
if (args.length === 1) return `${CSI_PREFIX}${args[0]}`
const params = args.slice(0, -1)
const final = args[args.length - 1]
return `${CSI_PREFIX}${params.join(SEP)}${final}`
}
/**
* CSI final bytes - the command identifier
*/
export const CSI = {
// Cursor movement
CUU: 0x41, // A - Cursor Up
CUD: 0x42, // B - Cursor Down
CUF: 0x43, // C - Cursor Forward
CUB: 0x44, // D - Cursor Back
CNL: 0x45, // E - Cursor Next Line
CPL: 0x46, // F - Cursor Previous Line
CHA: 0x47, // G - Cursor Horizontal Absolute
CUP: 0x48, // H - Cursor Position
CHT: 0x49, // I - Cursor Horizontal Tab
VPA: 0x64, // d - Vertical Position Absolute
HVP: 0x66, // f - Horizontal Vertical Position
// Erase
ED: 0x4a, // J - Erase in Display
EL: 0x4b, // K - Erase in Line
ECH: 0x58, // X - Erase Character
// Insert/Delete
IL: 0x4c, // L - Insert Lines
DL: 0x4d, // M - Delete Lines
ICH: 0x40, // @ - Insert Characters
DCH: 0x50, // P - Delete Characters
// Scroll
SU: 0x53, // S - Scroll Up
SD: 0x54, // T - Scroll Down
// Modes
SM: 0x68, // h - Set Mode
RM: 0x6c, // l - Reset Mode
// SGR
SGR: 0x6d, // m - Select Graphic Rendition
// Other
DSR: 0x6e, // n - Device Status Report
DECSCUSR: 0x71, // q - Set Cursor Style (with space intermediate)
DECSTBM: 0x72, // r - Set Top and Bottom Margins
SCOSC: 0x73, // s - Save Cursor Position
SCORC: 0x75, // u - Restore Cursor Position
CBT: 0x5a, // Z - Cursor Backward Tabulation
} as const
/**
* Erase in Display regions (ED command parameter)
*/
export const ERASE_DISPLAY = ['toEnd', 'toStart', 'all', 'scrollback'] as const
/**
* Erase in Line regions (EL command parameter)
*/
export const ERASE_LINE_REGION = ['toEnd', 'toStart', 'all'] as const
/**
* Cursor styles (DECSCUSR)
*/
export type CursorStyle = 'block' | 'underline' | 'bar'
export const CURSOR_STYLES: Array<{ style: CursorStyle; blinking: boolean }> = [
{ style: 'block', blinking: true }, // 0 - default
{ style: 'block', blinking: true }, // 1
{ style: 'block', blinking: false }, // 2
{ style: 'underline', blinking: true }, // 3
{ style: 'underline', blinking: false }, // 4
{ style: 'bar', blinking: true }, // 5
{ style: 'bar', blinking: false }, // 6
]
// Cursor movement generators
/** Move cursor up n lines (CSI n A) */
export function cursorUp(n = 1): string {
return n === 0 ? '' : csi(n, 'A')
}
/** Move cursor down n lines (CSI n B) */
export function cursorDown(n = 1): string {
return n === 0 ? '' : csi(n, 'B')
}
/** Move cursor forward n columns (CSI n C) */
export function cursorForward(n = 1): string {
return n === 0 ? '' : csi(n, 'C')
}
/** Move cursor back n columns (CSI n D) */
export function cursorBack(n = 1): string {
return n === 0 ? '' : csi(n, 'D')
}
/** Move cursor to column n (1-indexed) (CSI n G) */
export function cursorTo(col: number): string {
return csi(col, 'G')
}
/** Move cursor to column 1 (CSI G) */
export const CURSOR_LEFT = csi('G')
/** Move cursor to row, col (1-indexed) (CSI row ; col H) */
export function cursorPosition(row: number, col: number): string {
return csi(row, col, 'H')
}
/** Move cursor to home position (CSI H) */
export const CURSOR_HOME = csi('H')
/**
* Move cursor relative to current position
* Positive x = right, negative x = left
* Positive y = down, negative y = up
*/
export function cursorMove(x: number, y: number): string {
let result = ''
// Horizontal first (matches ansi-escapes behavior)
if (x < 0) {
result += cursorBack(-x)
} else if (x > 0) {
result += cursorForward(x)
}
// Then vertical
if (y < 0) {
result += cursorUp(-y)
} else if (y > 0) {
result += cursorDown(y)
}
return result
}
// Save/restore cursor position
/** Save cursor position (CSI s) */
export const CURSOR_SAVE = csi('s')
/** Restore cursor position (CSI u) */
export const CURSOR_RESTORE = csi('u')
// Erase generators
/** Erase from cursor to end of line (CSI K) */
export function eraseToEndOfLine(): string {
return csi('K')
}
/** Erase from cursor to start of line (CSI 1 K) */
export function eraseToStartOfLine(): string {
return csi(1, 'K')
}
/** Erase entire line (CSI 2 K) */
export function eraseLine(): string {
return csi(2, 'K')
}
/** Erase entire line - constant form */
export const ERASE_LINE = csi(2, 'K')
/** Erase from cursor to end of screen (CSI J) */
export function eraseToEndOfScreen(): string {
return csi('J')
}
/** Erase from cursor to start of screen (CSI 1 J) */
export function eraseToStartOfScreen(): string {
return csi(1, 'J')
}
/** Erase entire screen (CSI 2 J) */
export function eraseScreen(): string {
return csi(2, 'J')
}
/** Erase entire screen - constant form */
export const ERASE_SCREEN = csi(2, 'J')
/** Erase scrollback buffer (CSI 3 J) */
export const ERASE_SCROLLBACK = csi(3, 'J')
/**
* Erase n lines starting from cursor line, moving cursor up
* This erases each line and moves up, ending at column 1
*/
export function eraseLines(n: number): string {
if (n <= 0) return ''
let result = ''
for (let i = 0; i < n; i++) {
result += ERASE_LINE
if (i < n - 1) {
result += cursorUp(1)
}
}
result += CURSOR_LEFT
return result
}
// Scroll
/** Scroll up n lines (CSI n S) */
export function scrollUp(n = 1): string {
return n === 0 ? '' : csi(n, 'S')
}
/** Scroll down n lines (CSI n T) */
export function scrollDown(n = 1): string {
return n === 0 ? '' : csi(n, 'T')
}
/** Set scroll region (DECSTBM, CSI top;bottom r). 1-indexed, inclusive. */
export function setScrollRegion(top: number, bottom: number): string {
return csi(top, bottom, 'r')
}
/** Reset scroll region to full screen (DECSTBM, CSI r). Homes the cursor. */
export const RESET_SCROLL_REGION = csi('r')
// Bracketed paste markers (input from terminal, not output)
// These are sent by the terminal to delimit pasted content when
// bracketed paste mode is enabled (via DEC mode 2004)
/** Sent by terminal before pasted content (CSI 200 ~) */
export const PASTE_START = csi('200~')
/** Sent by terminal after pasted content (CSI 201 ~) */
export const PASTE_END = csi('201~')
// Focus event markers (input from terminal, not output)
// These are sent by the terminal when focus changes while
// focus events mode is enabled (via DEC mode 1004)
/** Sent by terminal when it gains focus (CSI I) */
export const FOCUS_IN = csi('I')
/** Sent by terminal when it loses focus (CSI O) */
export const FOCUS_OUT = csi('O')
// Kitty keyboard protocol (CSI u)
// Enables enhanced key reporting with modifier information
// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
/**
* Enable Kitty keyboard protocol with basic modifier reporting
* CSI > 1 u - pushes mode with flags=1 (disambiguate escape codes)
* This makes Shift+Enter send CSI 13;2 u instead of just CR
*/
export const ENABLE_KITTY_KEYBOARD = csi('>1u')
/**
* Disable Kitty keyboard protocol
* CSI < u - pops the keyboard mode stack
*/
export const DISABLE_KITTY_KEYBOARD = csi('<u')
/**
* Enable xterm modifyOtherKeys level 2.
* tmux accepts this (not the kitty stack) to enable extended keys — when
* extended-keys-format is csi-u, tmux then emits keys in kitty format.
*/
export const ENABLE_MODIFY_OTHER_KEYS = csi('>4;2m')
/**
* Disable xterm modifyOtherKeys (reset to default).
*/
export const DISABLE_MODIFY_OTHER_KEYS = csi('>4m')