@rlabs-inc/tui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -0
- package/index.ts +45 -0
- package/package.json +59 -0
- package/src/api/index.ts +7 -0
- package/src/api/mount.ts +230 -0
- package/src/engine/arrays/core.ts +60 -0
- package/src/engine/arrays/dimensions.ts +68 -0
- package/src/engine/arrays/index.ts +166 -0
- package/src/engine/arrays/interaction.ts +112 -0
- package/src/engine/arrays/layout.ts +175 -0
- package/src/engine/arrays/spacing.ts +100 -0
- package/src/engine/arrays/text.ts +55 -0
- package/src/engine/arrays/visual.ts +140 -0
- package/src/engine/index.ts +25 -0
- package/src/engine/inheritance.ts +138 -0
- package/src/engine/registry.ts +180 -0
- package/src/pipeline/frameBuffer.ts +473 -0
- package/src/pipeline/layout/index.ts +105 -0
- package/src/pipeline/layout/titan-engine.ts +798 -0
- package/src/pipeline/layout/types.ts +194 -0
- package/src/pipeline/layout/utils/hierarchy.ts +202 -0
- package/src/pipeline/layout/utils/math.ts +134 -0
- package/src/pipeline/layout/utils/text-measure.ts +160 -0
- package/src/pipeline/layout.ts +30 -0
- package/src/primitives/box.ts +312 -0
- package/src/primitives/index.ts +12 -0
- package/src/primitives/text.ts +199 -0
- package/src/primitives/types.ts +222 -0
- package/src/primitives/utils.ts +37 -0
- package/src/renderer/ansi.ts +625 -0
- package/src/renderer/buffer.ts +667 -0
- package/src/renderer/index.ts +40 -0
- package/src/renderer/input.ts +518 -0
- package/src/renderer/output.ts +451 -0
- package/src/state/cursor.ts +176 -0
- package/src/state/focus.ts +241 -0
- package/src/state/index.ts +43 -0
- package/src/state/keyboard.ts +771 -0
- package/src/state/mouse.ts +524 -0
- package/src/state/scroll.ts +341 -0
- package/src/state/theme.ts +687 -0
- package/src/types/color.ts +401 -0
- package/src/types/index.ts +316 -0
- package/src/utils/text.ts +471 -0
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI Framework - ANSI Escape Codes
|
|
3
|
+
*
|
|
4
|
+
* Complete terminal control sequences.
|
|
5
|
+
* Based on ansi-escapes library but zero dependencies.
|
|
6
|
+
*
|
|
7
|
+
* References:
|
|
8
|
+
* - https://www2.ccs.neu.edu/research/gpc/VonaUtils/vona/terminal/vtansi.htm
|
|
9
|
+
* - https://terminalguide.namepad.de/
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { RGBA, CellAttrs, CursorShape } from '../types'
|
|
13
|
+
import { Attr } from '../types'
|
|
14
|
+
import { isTerminalDefault, isAnsiColor, getAnsiIndex } from '../types/color'
|
|
15
|
+
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Constants & Terminal Detection
|
|
18
|
+
// =============================================================================
|
|
19
|
+
|
|
20
|
+
export const ESC = '\u001B'
|
|
21
|
+
export const CSI = '\u001B['
|
|
22
|
+
export const OSC = '\u001B]'
|
|
23
|
+
export const BEL = '\u0007'
|
|
24
|
+
export const SEP = ';'
|
|
25
|
+
|
|
26
|
+
/** Detect Apple Terminal (needs different save/restore sequences) */
|
|
27
|
+
const isAppleTerminal = process.env.TERM_PROGRAM === 'Apple_Terminal'
|
|
28
|
+
|
|
29
|
+
/** Detect Windows */
|
|
30
|
+
const isWindows = process.platform === 'win32'
|
|
31
|
+
|
|
32
|
+
/** Detect tmux (needs OSC wrapping) */
|
|
33
|
+
const isTmux = process.env.TERM?.startsWith('screen') ||
|
|
34
|
+
process.env.TERM?.startsWith('tmux') ||
|
|
35
|
+
process.env.TMUX !== undefined
|
|
36
|
+
|
|
37
|
+
/** Wrap OSC sequences for tmux compatibility */
|
|
38
|
+
function wrapOsc(sequence: string): string {
|
|
39
|
+
if (isTmux) {
|
|
40
|
+
// Tmux requires OSC sequences to be wrapped with DCS tmux; <sequence> ST
|
|
41
|
+
// and all ESCs in <sequence> to be replaced with ESC ESC
|
|
42
|
+
return '\u001BPtmux;' + sequence.replaceAll('\u001B', '\u001B\u001B') + '\u001B\\'
|
|
43
|
+
}
|
|
44
|
+
return sequence
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// =============================================================================
|
|
48
|
+
// Cursor Movement
|
|
49
|
+
// =============================================================================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Move cursor to absolute position.
|
|
53
|
+
* @param x Column (0-indexed)
|
|
54
|
+
* @param y Row (0-indexed, optional)
|
|
55
|
+
*/
|
|
56
|
+
export function cursorTo(x: number, y?: number): string {
|
|
57
|
+
if (typeof y !== 'number') {
|
|
58
|
+
return CSI + (x + 1) + 'G'
|
|
59
|
+
}
|
|
60
|
+
return CSI + (y + 1) + SEP + (x + 1) + 'H'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Move cursor relative to current position.
|
|
65
|
+
* @param x Columns to move (negative = left)
|
|
66
|
+
* @param y Rows to move (negative = up)
|
|
67
|
+
*/
|
|
68
|
+
export function cursorMove(x: number, y?: number): string {
|
|
69
|
+
let result = ''
|
|
70
|
+
|
|
71
|
+
if (x < 0) {
|
|
72
|
+
result += CSI + (-x) + 'D'
|
|
73
|
+
} else if (x > 0) {
|
|
74
|
+
result += CSI + x + 'C'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (typeof y === 'number') {
|
|
78
|
+
if (y < 0) {
|
|
79
|
+
result += CSI + (-y) + 'A'
|
|
80
|
+
} else if (y > 0) {
|
|
81
|
+
result += CSI + y + 'B'
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return result
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Move cursor up n rows (default 1) */
|
|
89
|
+
export function cursorUp(count: number = 1): string {
|
|
90
|
+
return CSI + count + 'A'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Move cursor down n rows (default 1) */
|
|
94
|
+
export function cursorDown(count: number = 1): string {
|
|
95
|
+
return CSI + count + 'B'
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Move cursor forward (right) n columns (default 1) */
|
|
99
|
+
export function cursorForward(count: number = 1): string {
|
|
100
|
+
return CSI + count + 'C'
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Move cursor backward (left) n columns (default 1) */
|
|
104
|
+
export function cursorBackward(count: number = 1): string {
|
|
105
|
+
return CSI + count + 'D'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Move cursor to first column */
|
|
109
|
+
export const cursorLeft = CSI + 'G'
|
|
110
|
+
|
|
111
|
+
/** Move cursor to next line */
|
|
112
|
+
export const cursorNextLine = CSI + 'E'
|
|
113
|
+
|
|
114
|
+
/** Move cursor to previous line */
|
|
115
|
+
export const cursorPrevLine = CSI + 'F'
|
|
116
|
+
|
|
117
|
+
/** Save cursor position (terminal-aware) */
|
|
118
|
+
export const cursorSavePosition = isAppleTerminal ? '\u001B7' : CSI + 's'
|
|
119
|
+
|
|
120
|
+
/** Restore cursor position (terminal-aware) */
|
|
121
|
+
export const cursorRestorePosition = isAppleTerminal ? '\u001B8' : CSI + 'u'
|
|
122
|
+
|
|
123
|
+
/** Query cursor position - terminal responds with CSI row;col R */
|
|
124
|
+
export const cursorGetPosition = CSI + '6n'
|
|
125
|
+
|
|
126
|
+
/** Hide cursor */
|
|
127
|
+
export const cursorHide = CSI + '?25l'
|
|
128
|
+
|
|
129
|
+
/** Show cursor */
|
|
130
|
+
export const cursorShow = CSI + '?25h'
|
|
131
|
+
|
|
132
|
+
// =============================================================================
|
|
133
|
+
// Legacy cursor names (for compatibility with our existing code)
|
|
134
|
+
// =============================================================================
|
|
135
|
+
|
|
136
|
+
/** @deprecated Use cursorTo() */
|
|
137
|
+
export function moveTo(x: number, y: number): string {
|
|
138
|
+
return CSI + y + SEP + x + 'H' // 1-indexed for legacy compatibility
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** @deprecated Use cursorUp() */
|
|
142
|
+
export function moveUp(n: number = 1): string {
|
|
143
|
+
return n > 0 ? CSI + n + 'A' : ''
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** @deprecated Use cursorDown() */
|
|
147
|
+
export function moveDown(n: number = 1): string {
|
|
148
|
+
return n > 0 ? CSI + n + 'B' : ''
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** @deprecated Use cursorForward() */
|
|
152
|
+
export function moveRight(n: number = 1): string {
|
|
153
|
+
return n > 0 ? CSI + n + 'C' : ''
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** @deprecated Use cursorBackward() */
|
|
157
|
+
export function moveLeft(n: number = 1): string {
|
|
158
|
+
return n > 0 ? CSI + n + 'D' : ''
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** @deprecated Use cursorTo(x) */
|
|
162
|
+
export function moveToColumn(x: number): string {
|
|
163
|
+
return CSI + x + 'G'
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Move cursor to beginning of line */
|
|
167
|
+
export const carriageReturn = '\r'
|
|
168
|
+
|
|
169
|
+
/** @deprecated Use cursorSavePosition */
|
|
170
|
+
export const saveCursor = cursorSavePosition
|
|
171
|
+
|
|
172
|
+
/** @deprecated Use cursorRestorePosition */
|
|
173
|
+
export const restoreCursor = cursorRestorePosition
|
|
174
|
+
|
|
175
|
+
/** Save cursor position (DEC private - more robust) */
|
|
176
|
+
export const saveCursorDEC = '\u001B7'
|
|
177
|
+
|
|
178
|
+
/** Restore cursor position (DEC private - more robust) */
|
|
179
|
+
export const restoreCursorDEC = '\u001B8'
|
|
180
|
+
|
|
181
|
+
/** @deprecated Use cursorGetPosition */
|
|
182
|
+
export const queryCursorPosition = cursorGetPosition
|
|
183
|
+
|
|
184
|
+
/** @deprecated Use cursorHide */
|
|
185
|
+
export const hideCursor = cursorHide
|
|
186
|
+
|
|
187
|
+
/** @deprecated Use cursorShow */
|
|
188
|
+
export const showCursor = cursorShow
|
|
189
|
+
|
|
190
|
+
// =============================================================================
|
|
191
|
+
// Cursor Shape
|
|
192
|
+
// =============================================================================
|
|
193
|
+
|
|
194
|
+
/** Set cursor shape */
|
|
195
|
+
export function setCursorShape(shape: CursorShape, blinking: boolean = true): string {
|
|
196
|
+
// DECSCUSR (cursor shape)
|
|
197
|
+
// 0 = blinking block, 1 = blinking block, 2 = steady block
|
|
198
|
+
// 3 = blinking underline, 4 = steady underline
|
|
199
|
+
// 5 = blinking bar, 6 = steady bar
|
|
200
|
+
const base = shape === 'block' ? 1 : shape === 'underline' ? 3 : 5
|
|
201
|
+
const code = blinking ? base : base + 1
|
|
202
|
+
return CSI + code + ' q'
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// =============================================================================
|
|
206
|
+
// Erase Functions
|
|
207
|
+
// =============================================================================
|
|
208
|
+
|
|
209
|
+
/** Erase from cursor to end of line */
|
|
210
|
+
export const eraseEndLine = CSI + 'K'
|
|
211
|
+
|
|
212
|
+
/** Erase from cursor to start of line */
|
|
213
|
+
export const eraseStartLine = CSI + '1K'
|
|
214
|
+
|
|
215
|
+
/** Erase entire line */
|
|
216
|
+
export const eraseLine = CSI + '2K'
|
|
217
|
+
|
|
218
|
+
/** Erase screen from cursor down */
|
|
219
|
+
export const eraseDown = CSI + 'J'
|
|
220
|
+
|
|
221
|
+
/** Erase screen from cursor up */
|
|
222
|
+
export const eraseUp = CSI + '1J'
|
|
223
|
+
|
|
224
|
+
/** Erase entire screen */
|
|
225
|
+
export const eraseScreen = CSI + '2J'
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Erase from current cursor position up the specified amount of rows.
|
|
229
|
+
* This is THE key function for inline mode rendering.
|
|
230
|
+
* Matches ansi-escapes exactly.
|
|
231
|
+
*/
|
|
232
|
+
export function eraseLines(count: number): string {
|
|
233
|
+
let clear = ''
|
|
234
|
+
|
|
235
|
+
for (let i = 0; i < count; i++) {
|
|
236
|
+
clear += eraseLine + (i < count - 1 ? cursorUp() : '')
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (count) {
|
|
240
|
+
clear += cursorLeft
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return clear
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// TODO: Test this optimized version - uses array.join() instead of += in loop
|
|
247
|
+
// export function eraseLinesOptimized(count: number): string {
|
|
248
|
+
// if (count === 0) return ''
|
|
249
|
+
// if (count === 1) return eraseLine + cursorLeft
|
|
250
|
+
// const lineAndUp = eraseLine + CSI + '1A'
|
|
251
|
+
// const parts: string[] = []
|
|
252
|
+
// for (let i = 0; i < count - 1; i++) parts.push(lineAndUp)
|
|
253
|
+
// parts.push(eraseLine)
|
|
254
|
+
// parts.push(cursorLeft)
|
|
255
|
+
// return parts.join('')
|
|
256
|
+
// }
|
|
257
|
+
|
|
258
|
+
// Legacy aliases
|
|
259
|
+
/** @deprecated Use eraseLine */
|
|
260
|
+
export const clearEntireLine = eraseLine
|
|
261
|
+
|
|
262
|
+
/** @deprecated Use eraseEndLine */
|
|
263
|
+
export const clearLine = eraseEndLine
|
|
264
|
+
|
|
265
|
+
/** @deprecated Use eraseDown */
|
|
266
|
+
export const clearToEnd = eraseDown
|
|
267
|
+
|
|
268
|
+
// =============================================================================
|
|
269
|
+
// Screen Control
|
|
270
|
+
// =============================================================================
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Clear only the visible terminal screen (viewport).
|
|
274
|
+
* Does NOT affect scrollback buffer or terminal state.
|
|
275
|
+
* SAFE for inline/append modes.
|
|
276
|
+
*/
|
|
277
|
+
export const clearViewport = eraseScreen + CSI + 'H'
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Clear the terminal screen.
|
|
281
|
+
* WARNING: Uses RIS (Reset to Initial State) which may:
|
|
282
|
+
* - Clear scrollback buffer in some terminals
|
|
283
|
+
* - Reset terminal modes and state
|
|
284
|
+
* Consider using clearViewport for safer clearing.
|
|
285
|
+
*/
|
|
286
|
+
export const clearScreen = '\u001Bc'
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Clear the whole terminal including scrollback buffer.
|
|
290
|
+
* Use for fullscreen mode cleanup.
|
|
291
|
+
*/
|
|
292
|
+
export const clearTerminal = isWindows
|
|
293
|
+
? eraseScreen + CSI + '0f'
|
|
294
|
+
: eraseScreen + CSI + '3J' + CSI + 'H'
|
|
295
|
+
|
|
296
|
+
/** Clear scrollback buffer only (xterm) */
|
|
297
|
+
export const clearScrollback = CSI + '3J'
|
|
298
|
+
|
|
299
|
+
/** Scroll display up one line */
|
|
300
|
+
export const scrollUp = CSI + 'S'
|
|
301
|
+
|
|
302
|
+
/** Scroll display down one line */
|
|
303
|
+
export const scrollDown = CSI + 'T'
|
|
304
|
+
|
|
305
|
+
// =============================================================================
|
|
306
|
+
// Alternative Screen Buffer
|
|
307
|
+
// =============================================================================
|
|
308
|
+
|
|
309
|
+
/** Enter alternative screen buffer (fullscreen mode) */
|
|
310
|
+
export const enterAlternativeScreen = CSI + '?1049h'
|
|
311
|
+
|
|
312
|
+
/** Exit alternative screen buffer */
|
|
313
|
+
export const exitAlternativeScreen = CSI + '?1049l'
|
|
314
|
+
|
|
315
|
+
// Legacy aliases
|
|
316
|
+
/** @deprecated Use enterAlternativeScreen */
|
|
317
|
+
export const enterAltScreen = enterAlternativeScreen
|
|
318
|
+
|
|
319
|
+
/** @deprecated Use exitAlternativeScreen */
|
|
320
|
+
export const exitAltScreen = exitAlternativeScreen
|
|
321
|
+
|
|
322
|
+
// =============================================================================
|
|
323
|
+
// Synchronized Output (flicker-free)
|
|
324
|
+
// =============================================================================
|
|
325
|
+
|
|
326
|
+
/** Begin synchronized update - terminal buffers all output */
|
|
327
|
+
export const beginSync = CSI + '?2026h'
|
|
328
|
+
|
|
329
|
+
/** End synchronized update - terminal flushes buffered output */
|
|
330
|
+
export const endSync = CSI + '?2026l'
|
|
331
|
+
|
|
332
|
+
// =============================================================================
|
|
333
|
+
// Colors
|
|
334
|
+
// =============================================================================
|
|
335
|
+
|
|
336
|
+
/** Reset all attributes and colors */
|
|
337
|
+
export const reset = CSI + '0m'
|
|
338
|
+
|
|
339
|
+
/** Foreground color (true color or ANSI palette) */
|
|
340
|
+
export function fg(color: RGBA): string {
|
|
341
|
+
if (isTerminalDefault(color)) {
|
|
342
|
+
return CSI + '39m' // Default foreground
|
|
343
|
+
}
|
|
344
|
+
if (isAnsiColor(color)) {
|
|
345
|
+
const index = getAnsiIndex(color)
|
|
346
|
+
// Standard colors 0-7: use 30-37
|
|
347
|
+
if (index >= 0 && index <= 7) {
|
|
348
|
+
return CSI + (30 + index) + 'm'
|
|
349
|
+
}
|
|
350
|
+
// Bright colors 8-15: use 90-97
|
|
351
|
+
if (index >= 8 && index <= 15) {
|
|
352
|
+
return CSI + (90 + index - 8) + 'm'
|
|
353
|
+
}
|
|
354
|
+
// Extended 256-color palette: use 38;5;n
|
|
355
|
+
return CSI + '38;5;' + index + 'm'
|
|
356
|
+
}
|
|
357
|
+
return CSI + '38;2;' + color.r + ';' + color.g + ';' + color.b + 'm'
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** Background color (true color or ANSI palette) */
|
|
361
|
+
export function bg(color: RGBA): string {
|
|
362
|
+
if (isTerminalDefault(color)) {
|
|
363
|
+
return CSI + '49m' // Default background
|
|
364
|
+
}
|
|
365
|
+
if (isAnsiColor(color)) {
|
|
366
|
+
const index = getAnsiIndex(color)
|
|
367
|
+
// Standard colors 0-7: use 40-47
|
|
368
|
+
if (index >= 0 && index <= 7) {
|
|
369
|
+
return CSI + (40 + index) + 'm'
|
|
370
|
+
}
|
|
371
|
+
// Bright colors 8-15: use 100-107
|
|
372
|
+
if (index >= 8 && index <= 15) {
|
|
373
|
+
return CSI + (100 + index - 8) + 'm'
|
|
374
|
+
}
|
|
375
|
+
// Extended 256-color palette: use 48;5;n
|
|
376
|
+
return CSI + '48;5;' + index + 'm'
|
|
377
|
+
}
|
|
378
|
+
return CSI + '48;2;' + color.r + ';' + color.g + ';' + color.b + 'm'
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** Reset foreground to default */
|
|
382
|
+
export const resetFg = CSI + '39m'
|
|
383
|
+
|
|
384
|
+
/** Reset background to default */
|
|
385
|
+
export const resetBg = CSI + '49m'
|
|
386
|
+
|
|
387
|
+
// =============================================================================
|
|
388
|
+
// Text Attributes
|
|
389
|
+
// =============================================================================
|
|
390
|
+
|
|
391
|
+
/** Apply text attributes from bitfield */
|
|
392
|
+
export function attrs(a: CellAttrs): string {
|
|
393
|
+
if (a === Attr.NONE) return ''
|
|
394
|
+
|
|
395
|
+
const codes: number[] = []
|
|
396
|
+
if (a & Attr.BOLD) codes.push(1)
|
|
397
|
+
if (a & Attr.DIM) codes.push(2)
|
|
398
|
+
if (a & Attr.ITALIC) codes.push(3)
|
|
399
|
+
if (a & Attr.UNDERLINE) codes.push(4)
|
|
400
|
+
if (a & Attr.BLINK) codes.push(5)
|
|
401
|
+
if (a & Attr.INVERSE) codes.push(7)
|
|
402
|
+
if (a & Attr.HIDDEN) codes.push(8)
|
|
403
|
+
if (a & Attr.STRIKETHROUGH) codes.push(9)
|
|
404
|
+
|
|
405
|
+
return codes.length > 0 ? CSI + codes.join(';') + 'm' : ''
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** Bold on */
|
|
409
|
+
export const bold = CSI + '1m'
|
|
410
|
+
/** Dim on */
|
|
411
|
+
export const dim = CSI + '2m'
|
|
412
|
+
/** Italic on */
|
|
413
|
+
export const italic = CSI + '3m'
|
|
414
|
+
/** Underline on */
|
|
415
|
+
export const underline = CSI + '4m'
|
|
416
|
+
/** Blink on */
|
|
417
|
+
export const blink = CSI + '5m'
|
|
418
|
+
/** Inverse on */
|
|
419
|
+
export const inverse = CSI + '7m'
|
|
420
|
+
/** Hidden on */
|
|
421
|
+
export const hidden = CSI + '8m'
|
|
422
|
+
/** Strikethrough on */
|
|
423
|
+
export const strikethrough = CSI + '9m'
|
|
424
|
+
|
|
425
|
+
/** Bold off */
|
|
426
|
+
export const boldOff = CSI + '22m'
|
|
427
|
+
/** Dim off */
|
|
428
|
+
export const dimOff = CSI + '22m'
|
|
429
|
+
/** Italic off */
|
|
430
|
+
export const italicOff = CSI + '23m'
|
|
431
|
+
/** Underline off */
|
|
432
|
+
export const underlineOff = CSI + '24m'
|
|
433
|
+
/** Blink off */
|
|
434
|
+
export const blinkOff = CSI + '25m'
|
|
435
|
+
/** Inverse off */
|
|
436
|
+
export const inverseOff = CSI + '27m'
|
|
437
|
+
/** Hidden off */
|
|
438
|
+
export const hiddenOff = CSI + '28m'
|
|
439
|
+
/** Strikethrough off */
|
|
440
|
+
export const strikethroughOff = CSI + '29m'
|
|
441
|
+
|
|
442
|
+
// =============================================================================
|
|
443
|
+
// Hyperlinks
|
|
444
|
+
// =============================================================================
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Create a clickable hyperlink.
|
|
448
|
+
* Supported terminals: iTerm2, VTE (GNOME Terminal, etc.), Windows Terminal
|
|
449
|
+
* @see https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
|
|
450
|
+
*/
|
|
451
|
+
export function link(text: string, url: string): string {
|
|
452
|
+
const openLink = wrapOsc(OSC + '8' + SEP + SEP + url + BEL)
|
|
453
|
+
const closeLink = wrapOsc(OSC + '8' + SEP + SEP + BEL)
|
|
454
|
+
return openLink + text + closeLink
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// =============================================================================
|
|
458
|
+
// Images (iTerm2)
|
|
459
|
+
// =============================================================================
|
|
460
|
+
|
|
461
|
+
export interface ImageOptions {
|
|
462
|
+
/** Width: number (cells), `${n}px`, `${n}%`, or 'auto' */
|
|
463
|
+
width?: string | number
|
|
464
|
+
/** Height: number (cells), `${n}px`, `${n}%`, or 'auto' */
|
|
465
|
+
height?: string | number
|
|
466
|
+
/** Preserve aspect ratio (default: true) */
|
|
467
|
+
preserveAspectRatio?: boolean
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Display an inline image (iTerm2 only).
|
|
472
|
+
* @param data Image buffer
|
|
473
|
+
* @param options Size options
|
|
474
|
+
*/
|
|
475
|
+
export function image(data: Buffer | Uint8Array, options: ImageOptions = {}): string {
|
|
476
|
+
let result = OSC + '1337;File=inline=1'
|
|
477
|
+
|
|
478
|
+
if (options.width) {
|
|
479
|
+
result += ';width=' + options.width
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (options.height) {
|
|
483
|
+
result += ';height=' + options.height
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (options.preserveAspectRatio === false) {
|
|
487
|
+
result += ';preserveAspectRatio=0'
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data)
|
|
491
|
+
result += ';size=' + buffer.byteLength + ':' + buffer.toString('base64') + BEL
|
|
492
|
+
|
|
493
|
+
return wrapOsc(result)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// =============================================================================
|
|
497
|
+
// Terminal Bells & Sounds
|
|
498
|
+
// =============================================================================
|
|
499
|
+
|
|
500
|
+
/** Output a beeping sound */
|
|
501
|
+
export const beep = BEL
|
|
502
|
+
|
|
503
|
+
// =============================================================================
|
|
504
|
+
// Mouse Tracking (SGR protocol)
|
|
505
|
+
// =============================================================================
|
|
506
|
+
|
|
507
|
+
/** Enable SGR mouse tracking (better protocol with proper button release) */
|
|
508
|
+
export const enableMouse = CSI + '?1000h' + CSI + '?1002h' + CSI + '?1003h' + CSI + '?1006h'
|
|
509
|
+
|
|
510
|
+
/** Disable SGR mouse tracking */
|
|
511
|
+
export const disableMouse = CSI + '?1006l' + CSI + '?1003l' + CSI + '?1002l' + CSI + '?1000l'
|
|
512
|
+
|
|
513
|
+
// =============================================================================
|
|
514
|
+
// Keyboard (Kitty protocol)
|
|
515
|
+
// =============================================================================
|
|
516
|
+
|
|
517
|
+
/** Enable Kitty keyboard protocol (enhanced key reporting) */
|
|
518
|
+
export const enableKittyKeyboard = CSI + '>1u'
|
|
519
|
+
|
|
520
|
+
/** Disable Kitty keyboard protocol */
|
|
521
|
+
export const disableKittyKeyboard = CSI + '<u'
|
|
522
|
+
|
|
523
|
+
// =============================================================================
|
|
524
|
+
// Bracketed Paste
|
|
525
|
+
// =============================================================================
|
|
526
|
+
|
|
527
|
+
/** Enable bracketed paste mode */
|
|
528
|
+
export const enableBracketedPaste = CSI + '?2004h'
|
|
529
|
+
|
|
530
|
+
/** Disable bracketed paste mode */
|
|
531
|
+
export const disableBracketedPaste = CSI + '?2004l'
|
|
532
|
+
|
|
533
|
+
// =============================================================================
|
|
534
|
+
// Focus Events
|
|
535
|
+
// =============================================================================
|
|
536
|
+
|
|
537
|
+
/** Enable focus reporting */
|
|
538
|
+
export const enableFocusReporting = CSI + '?1004h'
|
|
539
|
+
|
|
540
|
+
/** Disable focus reporting */
|
|
541
|
+
export const disableFocusReporting = CSI + '?1004l'
|
|
542
|
+
|
|
543
|
+
// =============================================================================
|
|
544
|
+
// Window/Title
|
|
545
|
+
// =============================================================================
|
|
546
|
+
|
|
547
|
+
/** Set terminal window title */
|
|
548
|
+
export function setTitle(title: string): string {
|
|
549
|
+
return OSC + '0;' + title + BEL
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// =============================================================================
|
|
553
|
+
// Terminal Queries
|
|
554
|
+
// =============================================================================
|
|
555
|
+
|
|
556
|
+
/** Query terminal size (response: CSI 8 ; height ; width t) */
|
|
557
|
+
export const querySize = CSI + '18t'
|
|
558
|
+
|
|
559
|
+
// =============================================================================
|
|
560
|
+
// iTerm2 Specific
|
|
561
|
+
// =============================================================================
|
|
562
|
+
|
|
563
|
+
export const iTerm = {
|
|
564
|
+
/** Set current working directory (enables Cmd-click on relative paths) */
|
|
565
|
+
setCwd: (cwd: string = process.cwd()): string => {
|
|
566
|
+
return wrapOsc(OSC + '50;CurrentDir=' + cwd + BEL)
|
|
567
|
+
},
|
|
568
|
+
|
|
569
|
+
/** Create an annotation (tooltip/note on text) */
|
|
570
|
+
annotation: (message: string, options: {
|
|
571
|
+
length?: number
|
|
572
|
+
x?: number
|
|
573
|
+
y?: number
|
|
574
|
+
isHidden?: boolean
|
|
575
|
+
} = {}): string => {
|
|
576
|
+
let result = OSC + '1337;'
|
|
577
|
+
|
|
578
|
+
const hasX = options.x !== undefined
|
|
579
|
+
const hasY = options.y !== undefined
|
|
580
|
+
if ((hasX || hasY) && !(hasX && hasY && options.length !== undefined)) {
|
|
581
|
+
throw new Error('`x`, `y` and `length` must be defined when `x` or `y` is defined')
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const cleanMessage = message.replaceAll('|', '')
|
|
585
|
+
result += options.isHidden ? 'AddHiddenAnnotation=' : 'AddAnnotation='
|
|
586
|
+
|
|
587
|
+
if (options.length && options.length > 0) {
|
|
588
|
+
result += hasX
|
|
589
|
+
? [cleanMessage, options.length, options.x, options.y].join('|')
|
|
590
|
+
: [options.length, cleanMessage].join('|')
|
|
591
|
+
} else {
|
|
592
|
+
result += cleanMessage
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return wrapOsc(result + BEL)
|
|
596
|
+
},
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// =============================================================================
|
|
600
|
+
// ConEmu Specific
|
|
601
|
+
// =============================================================================
|
|
602
|
+
|
|
603
|
+
export const ConEmu = {
|
|
604
|
+
/** Set current working directory */
|
|
605
|
+
setCwd: (cwd: string = process.cwd()): string => {
|
|
606
|
+
return wrapOsc(OSC + '9;9;' + cwd + BEL)
|
|
607
|
+
},
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/** Set CWD for both iTerm2 and ConEmu */
|
|
611
|
+
export function setCwd(cwd: string = process.cwd()): string {
|
|
612
|
+
return iTerm.setCwd(cwd) + ConEmu.setCwd(cwd)
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// =============================================================================
|
|
616
|
+
// Legacy compatibility - moveToClear
|
|
617
|
+
// =============================================================================
|
|
618
|
+
|
|
619
|
+
/** Move cursor to absolute position AND clear from there to end of screen */
|
|
620
|
+
export function moveToClear(row: number, col: number): string {
|
|
621
|
+
return CSI + row + ';' + col + 'H' + CSI + '0J'
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/** SS3 prefix for function keys */
|
|
625
|
+
export const SS3 = '\u001BO'
|