@tuidom/terminal-backend 0.23.2 → 0.24.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.
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { HostColorScheme, HostTerminalColors, ITerminalBackend } from "@tuidom/core/backend/iTerminalBackend";
|
|
2
|
+
import { type CursorStyle } from "@tuidom/core/common/cursorStyle";
|
|
2
3
|
import { Point, Size } from "@tuidom/core/common/geometryPromitives";
|
|
3
4
|
import type { KeyPressEvent } from "@tuidom/core/input/keyEvent";
|
|
4
5
|
import type { MouseToken } from "@tuidom/core/input/rawTerminalToken";
|
|
@@ -50,6 +51,10 @@ export declare class NodeTerminalBackend implements ITerminalBackend {
|
|
|
50
51
|
private readonly frameBuffer;
|
|
51
52
|
private prevGrid;
|
|
52
53
|
private prevCursorPosition;
|
|
54
|
+
/** The cursor shape the terminal shows; `null` — its default, untouched until a frame asks otherwise. */
|
|
55
|
+
private prevCursorStyle;
|
|
56
|
+
/** Some frame has set a non-default cursor shape; teardown owes the terminal a `CSI 0 q`. */
|
|
57
|
+
private cursorStyleTouched;
|
|
53
58
|
constructor(stdin?: NodeJS.ReadStream, stdout?: NodeJS.WriteStream, options?: {
|
|
54
59
|
resizeThrottleMs?: number;
|
|
55
60
|
});
|
|
@@ -115,7 +120,7 @@ export declare class NodeTerminalBackend implements ITerminalBackend {
|
|
|
115
120
|
private clearPartialInputTimer;
|
|
116
121
|
/** Emit resize only if dimensions actually changed */
|
|
117
122
|
private emitResize;
|
|
118
|
-
renderFrame(grid: Grid, cursorPosition: Point | null): void;
|
|
123
|
+
renderFrame(grid: Grid, cursorPosition: Point | null, cursorStyle?: CursorStyle | null): void;
|
|
119
124
|
getSize(): Size;
|
|
120
125
|
setup(): void;
|
|
121
126
|
teardown(): void;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parseXColor } from "@tuidom/core/common/colorUtils";
|
|
2
|
+
import { sameCursorStyle } from "@tuidom/core/common/cursorStyle";
|
|
2
3
|
import { Point, Size } from "@tuidom/core/common/geometryPromitives";
|
|
3
4
|
import { KeyInputParser } from "@tuidom/core/input/keyInputParser";
|
|
4
5
|
import { MOUSE_TRACKING_ALL_ENABLE, MOUSE_TRACKING_DISABLE } from "@tuidom/core/input/mouseTracking";
|
|
@@ -54,6 +55,17 @@ const TITLE_POP = "\x1b[23;0t";
|
|
|
54
55
|
function pointerShapeSequence(shape) {
|
|
55
56
|
return `\x1b]22;${shape}\x1b\\`;
|
|
56
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* DECSCUSR (`CSI n q`) — the text cursor shape: 1/2 block, 3/4 underline, 5/6 bar (odd
|
|
60
|
+
* blinks, even is steady), 0 the terminal's default. tmux understands it and keeps it per
|
|
61
|
+
* pane, so it goes directly; terminals without it drop the sequence.
|
|
62
|
+
*/
|
|
63
|
+
function cursorStyleSequence(style) {
|
|
64
|
+
if (style === null)
|
|
65
|
+
return "\x1b[0 q";
|
|
66
|
+
const base = style.shape === "block" ? 1 : style.shape === "underline" ? 3 : 5;
|
|
67
|
+
return `\x1b[${(style.blinking ? base : base + 1).toString()} q`;
|
|
68
|
+
}
|
|
57
69
|
/**
|
|
58
70
|
* Color-scheme reports (DEC private mode 2031, the Contour protocol): with the mode on the
|
|
59
71
|
* terminal sends `CSI ? 997 ; 1|2 n` whenever its scheme flips; `CSI ? 996 n` asks for the
|
|
@@ -195,6 +207,10 @@ export class NodeTerminalBackend {
|
|
|
195
207
|
frameBuffer = new StringSink();
|
|
196
208
|
prevGrid = null;
|
|
197
209
|
prevCursorPosition = null;
|
|
210
|
+
/** The cursor shape the terminal shows; `null` — its default, untouched until a frame asks otherwise. */
|
|
211
|
+
prevCursorStyle = null;
|
|
212
|
+
/** Some frame has set a non-default cursor shape; teardown owes the terminal a `CSI 0 q`. */
|
|
213
|
+
cursorStyleTouched = false;
|
|
198
214
|
constructor(stdin = process.stdin, stdout = process.stdout, options) {
|
|
199
215
|
this.stdin = stdin;
|
|
200
216
|
this.stdout = stdout;
|
|
@@ -433,7 +449,7 @@ export class NodeTerminalBackend {
|
|
|
433
449
|
cb(size);
|
|
434
450
|
}
|
|
435
451
|
}
|
|
436
|
-
renderFrame(grid, cursorPosition) {
|
|
452
|
+
renderFrame(grid, cursorPosition, cursorStyle = null) {
|
|
437
453
|
this.prevGrid ??= new Grid(grid.size);
|
|
438
454
|
const sizeChanged = this.prevGrid.width !== grid.width || this.prevGrid.height !== grid.height;
|
|
439
455
|
if (sizeChanged) {
|
|
@@ -444,10 +460,13 @@ export class NodeTerminalBackend {
|
|
|
444
460
|
this.renderer.render(grid, this.prevGrid);
|
|
445
461
|
const body = this.frameBuffer.value;
|
|
446
462
|
const cursorChanged = !samePoint(cursorPosition, this.prevCursorPosition);
|
|
463
|
+
// The shape of a hidden cursor doesn't matter: the terminal keeps the last one until
|
|
464
|
+
// the cursor shows again, so a frame without a cursor never writes a shape.
|
|
465
|
+
const styleChanged = cursorPosition !== null && !sameCursorStyle(cursorStyle, this.prevCursorStyle);
|
|
447
466
|
// Nothing to repaint and the cursor hasn't moved → emit absolutely nothing.
|
|
448
467
|
// Without this, every mouse-move event re-sent the frame wrapper (hide/show
|
|
449
468
|
// cursor + reposition), making the terminal cursor flicker on plain motion.
|
|
450
|
-
if (!sizeChanged && body.length === 0 && !cursorChanged) {
|
|
469
|
+
if (!sizeChanged && body.length === 0 && !cursorChanged && !styleChanged) {
|
|
451
470
|
return;
|
|
452
471
|
}
|
|
453
472
|
let out = "\x1b[?2026h"; // begin synchronized output
|
|
@@ -459,6 +478,12 @@ export class NodeTerminalBackend {
|
|
|
459
478
|
out += "\x1b[?25l"; // hide cursor
|
|
460
479
|
}
|
|
461
480
|
out += body;
|
|
481
|
+
if (styleChanged) {
|
|
482
|
+
out += cursorStyleSequence(cursorStyle);
|
|
483
|
+
this.prevCursorStyle = cursorStyle;
|
|
484
|
+
if (cursorStyle !== null)
|
|
485
|
+
this.cursorStyleTouched = true;
|
|
486
|
+
}
|
|
462
487
|
if (cursorPosition !== null) {
|
|
463
488
|
out += `\x1b[${(cursorPosition.y + 1).toString()};${(cursorPosition.x + 1).toString()}H`; // position cursor
|
|
464
489
|
out += "\x1b[?25h"; // show cursor
|
|
@@ -553,6 +578,13 @@ export class NodeTerminalBackend {
|
|
|
553
578
|
}
|
|
554
579
|
// Bring back the default pointer, or the shell keeps the last one (a resize arrow).
|
|
555
580
|
this.setPointerShape("default");
|
|
581
|
+
// Bring back the terminal's own cursor shape — only if a frame ever changed it, so a
|
|
582
|
+
// run that never set a shape leaves the byte stream as it was.
|
|
583
|
+
if (this.cursorStyleTouched) {
|
|
584
|
+
this.writeDirect(cursorStyleSequence(null));
|
|
585
|
+
this.prevCursorStyle = null;
|
|
586
|
+
this.cursorStyleTouched = false;
|
|
587
|
+
}
|
|
556
588
|
// Restore cursor
|
|
557
589
|
this.stdout.write("\x1b[?25h");
|
|
558
590
|
// Restore normal screen buffer
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuidom/terminal-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Experimental terminal UI DOM — real terminal backend for Node (tty, ANSI diffing, Kitty protocol). API is unstable.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,6 +28,6 @@
|
|
|
28
28
|
"./*": "./dist/*.js"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@tuidom/core": "0.
|
|
31
|
+
"@tuidom/core": "0.24.0"
|
|
32
32
|
}
|
|
33
33
|
}
|