@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.
Files changed (44) hide show
  1. package/README.md +141 -0
  2. package/index.ts +45 -0
  3. package/package.json +59 -0
  4. package/src/api/index.ts +7 -0
  5. package/src/api/mount.ts +230 -0
  6. package/src/engine/arrays/core.ts +60 -0
  7. package/src/engine/arrays/dimensions.ts +68 -0
  8. package/src/engine/arrays/index.ts +166 -0
  9. package/src/engine/arrays/interaction.ts +112 -0
  10. package/src/engine/arrays/layout.ts +175 -0
  11. package/src/engine/arrays/spacing.ts +100 -0
  12. package/src/engine/arrays/text.ts +55 -0
  13. package/src/engine/arrays/visual.ts +140 -0
  14. package/src/engine/index.ts +25 -0
  15. package/src/engine/inheritance.ts +138 -0
  16. package/src/engine/registry.ts +180 -0
  17. package/src/pipeline/frameBuffer.ts +473 -0
  18. package/src/pipeline/layout/index.ts +105 -0
  19. package/src/pipeline/layout/titan-engine.ts +798 -0
  20. package/src/pipeline/layout/types.ts +194 -0
  21. package/src/pipeline/layout/utils/hierarchy.ts +202 -0
  22. package/src/pipeline/layout/utils/math.ts +134 -0
  23. package/src/pipeline/layout/utils/text-measure.ts +160 -0
  24. package/src/pipeline/layout.ts +30 -0
  25. package/src/primitives/box.ts +312 -0
  26. package/src/primitives/index.ts +12 -0
  27. package/src/primitives/text.ts +199 -0
  28. package/src/primitives/types.ts +222 -0
  29. package/src/primitives/utils.ts +37 -0
  30. package/src/renderer/ansi.ts +625 -0
  31. package/src/renderer/buffer.ts +667 -0
  32. package/src/renderer/index.ts +40 -0
  33. package/src/renderer/input.ts +518 -0
  34. package/src/renderer/output.ts +451 -0
  35. package/src/state/cursor.ts +176 -0
  36. package/src/state/focus.ts +241 -0
  37. package/src/state/index.ts +43 -0
  38. package/src/state/keyboard.ts +771 -0
  39. package/src/state/mouse.ts +524 -0
  40. package/src/state/scroll.ts +341 -0
  41. package/src/state/theme.ts +687 -0
  42. package/src/types/color.ts +401 -0
  43. package/src/types/index.ts +316 -0
  44. 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'