@tuidom/terminal-backend 0.23.2 → 0.25.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.23.2",
3
+ "version": "0.25.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.23.2"
31
+ "@tuidom/core": "0.25.0"
32
32
  }
33
33
  }