@oh-my-pi/pi-tui 14.9.3 → 14.9.7
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/CHANGELOG.md +6 -0
- package/package.json +3 -3
- package/src/tui.ts +51 -31
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
|
|
6
|
+
## [14.9.5] - 2026-05-12
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed rapidly blinking cursor artifact during task execution by consolidating cursor control sequences into the synchronized output buffer ([#992](https://github.com/can1357/oh-my-pi/issues/992))
|
|
10
|
+
|
|
5
11
|
## [14.5.7] - 2026-04-29
|
|
6
12
|
|
|
7
13
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-tui",
|
|
4
|
-
"version": "14.9.
|
|
4
|
+
"version": "14.9.7",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"fmt": "biome format --write ."
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@oh-my-pi/pi-natives": "14.9.
|
|
41
|
-
"@oh-my-pi/pi-utils": "14.9.
|
|
40
|
+
"@oh-my-pi/pi-natives": "14.9.7",
|
|
41
|
+
"@oh-my-pi/pi-utils": "14.9.7",
|
|
42
42
|
"lru-cache": "11.3.6",
|
|
43
43
|
"marked": "^18.0.3"
|
|
44
44
|
},
|
package/src/tui.ts
CHANGED
|
@@ -1018,10 +1018,12 @@ export class TUI extends Container {
|
|
|
1018
1018
|
const line = newLines[i];
|
|
1019
1019
|
buffer += TERMINAL.isImageLine(line) ? line : line + reset;
|
|
1020
1020
|
}
|
|
1021
|
+
this.#cursorRow = Math.max(0, newLines.length - 1);
|
|
1022
|
+
const { seq, toRow } = this.#cursorControlSequence(cursorPos, newLines.length, this.#cursorRow);
|
|
1023
|
+
this.#hardwareCursorRow = toRow;
|
|
1024
|
+
buffer += seq;
|
|
1021
1025
|
buffer += "\x1b[?2026l"; // End synchronized output
|
|
1022
1026
|
this.terminal.write(buffer);
|
|
1023
|
-
this.#cursorRow = Math.max(0, newLines.length - 1);
|
|
1024
|
-
this.#hardwareCursorRow = this.#cursorRow;
|
|
1025
1027
|
// Reset max lines when clearing, otherwise track growth
|
|
1026
1028
|
if (clear) {
|
|
1027
1029
|
this.#maxLinesRendered = newLines.length;
|
|
@@ -1029,7 +1031,6 @@ export class TUI extends Container {
|
|
|
1029
1031
|
this.#maxLinesRendered = Math.max(this.#maxLinesRendered, newLines.length);
|
|
1030
1032
|
}
|
|
1031
1033
|
this.#viewportTopRow = Math.max(0, this.#maxLinesRendered - height);
|
|
1032
|
-
this.#positionHardwareCursor(cursorPos, newLines.length);
|
|
1033
1034
|
this.#previousLines = newLines;
|
|
1034
1035
|
this.#previousWidth = width;
|
|
1035
1036
|
this.#previousHeight = height;
|
|
@@ -1101,7 +1102,7 @@ export class TUI extends Container {
|
|
|
1101
1102
|
|
|
1102
1103
|
// No changes - but still need to update hardware cursor position if it moved
|
|
1103
1104
|
if (firstChanged === -1) {
|
|
1104
|
-
this.#
|
|
1105
|
+
this.#writeCursorPosition(cursorPos, newLines.length);
|
|
1105
1106
|
this.#viewportTopRow = Math.max(0, this.#maxLinesRendered - height);
|
|
1106
1107
|
return;
|
|
1107
1108
|
}
|
|
@@ -1135,12 +1136,13 @@ export class TUI extends Container {
|
|
|
1135
1136
|
if (moveUp > 0) {
|
|
1136
1137
|
buffer += `\x1b[${moveUp}A`;
|
|
1137
1138
|
}
|
|
1139
|
+
this.#cursorRow = targetRow;
|
|
1140
|
+
const { seq, toRow } = this.#cursorControlSequence(cursorPos, newLines.length, targetRow);
|
|
1141
|
+
this.#hardwareCursorRow = toRow;
|
|
1142
|
+
buffer += seq;
|
|
1138
1143
|
buffer += "\x1b[?2026l";
|
|
1139
1144
|
this.terminal.write(buffer);
|
|
1140
|
-
this.#cursorRow = targetRow;
|
|
1141
|
-
this.#hardwareCursorRow = targetRow;
|
|
1142
1145
|
}
|
|
1143
|
-
this.#positionHardwareCursor(cursorPos, newLines.length);
|
|
1144
1146
|
this.#previousLines = newLines;
|
|
1145
1147
|
this.#previousWidth = width;
|
|
1146
1148
|
this.#previousHeight = height;
|
|
@@ -1166,7 +1168,7 @@ export class TUI extends Container {
|
|
|
1166
1168
|
this.#cursorRow = Math.max(0, newLines.length - 1);
|
|
1167
1169
|
this.#maxLinesRendered = newLines.length;
|
|
1168
1170
|
this.#viewportTopRow = Math.max(0, newLines.length - height);
|
|
1169
|
-
this.#
|
|
1171
|
+
this.#writeCursorPosition(cursorPos, newLines.length);
|
|
1170
1172
|
this.#previousLines = newLines;
|
|
1171
1173
|
this.#previousWidth = width;
|
|
1172
1174
|
this.#previousHeight = height;
|
|
@@ -1249,6 +1251,9 @@ export class TUI extends Container {
|
|
|
1249
1251
|
buffer += `\x1b[${extraLines}A`;
|
|
1250
1252
|
}
|
|
1251
1253
|
|
|
1254
|
+
const { seq, toRow } = this.#cursorControlSequence(cursorPos, newLines.length, finalCursorRow);
|
|
1255
|
+
this.#hardwareCursorRow = toRow;
|
|
1256
|
+
buffer += seq;
|
|
1252
1257
|
buffer += "\x1b[?2026l"; // End synchronized output
|
|
1253
1258
|
|
|
1254
1259
|
if ($flag("PI_TUI_DEBUG")) {
|
|
@@ -1262,6 +1267,7 @@ export class TUI extends Container {
|
|
|
1262
1267
|
`height: ${height}`,
|
|
1263
1268
|
`lineDiff: ${lineDiff}`,
|
|
1264
1269
|
`hardwareCursorRow: ${hardwareCursorRow}`,
|
|
1270
|
+
`hardwareCursorRow (post): ${this.#hardwareCursorRow}`,
|
|
1265
1271
|
`renderEnd: ${renderEnd}`,
|
|
1266
1272
|
`finalCursorRow: ${finalCursorRow}`,
|
|
1267
1273
|
`cursorPos: ${JSON.stringify(cursorPos)}`,
|
|
@@ -1283,51 +1289,65 @@ export class TUI extends Container {
|
|
|
1283
1289
|
// Write entire buffer at once
|
|
1284
1290
|
this.terminal.write(buffer);
|
|
1285
1291
|
|
|
1286
|
-
// Track cursor position for next render
|
|
1287
|
-
// cursorRow tracks end of content (for viewport calculation)
|
|
1288
|
-
// hardwareCursorRow
|
|
1292
|
+
// Track cursor position for next render.
|
|
1293
|
+
// cursorRow tracks end of content (for viewport calculation).
|
|
1294
|
+
// #hardwareCursorRow was already updated by #cursorControlSequence above.
|
|
1289
1295
|
this.#cursorRow = Math.max(0, newLines.length - 1);
|
|
1290
|
-
this.#hardwareCursorRow = finalCursorRow;
|
|
1291
1296
|
// Track content height for viewport calculation
|
|
1292
1297
|
this.#maxLinesRendered = newLines.length;
|
|
1293
1298
|
this.#viewportTopRow = Math.max(0, newLines.length - height);
|
|
1294
1299
|
|
|
1295
|
-
// Position hardware cursor for IME
|
|
1296
|
-
this.#positionHardwareCursor(cursorPos, newLines.length);
|
|
1297
|
-
|
|
1298
1300
|
this.#previousLines = newLines;
|
|
1299
1301
|
this.#previousWidth = width;
|
|
1300
1302
|
this.#previousHeight = height;
|
|
1301
1303
|
}
|
|
1302
1304
|
|
|
1303
1305
|
/**
|
|
1304
|
-
*
|
|
1305
|
-
*
|
|
1306
|
-
*
|
|
1306
|
+
* Build cursor control sequences to position the hardware cursor for the IME
|
|
1307
|
+
* candidate window. Returns escape sequences and the resulting cursor row for
|
|
1308
|
+
* the caller to update `#hardwareCursorRow`. The sequences should be appended
|
|
1309
|
+
* into the caller's own synchronized output block to avoid a flicker between
|
|
1310
|
+
* content and cursor frames.
|
|
1307
1311
|
*/
|
|
1308
|
-
#
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1312
|
+
#cursorControlSequence(
|
|
1313
|
+
cursorPos: { row: number; col: number } | null,
|
|
1314
|
+
totalLines: number,
|
|
1315
|
+
fromRow: number,
|
|
1316
|
+
): { seq: string; toRow: number } {
|
|
1317
|
+
// No IME target or no content — hide cursor regardless of preference
|
|
1318
|
+
if (!cursorPos || totalLines <= 0) return { seq: "\x1b[?25l", toRow: fromRow };
|
|
1313
1319
|
|
|
1314
1320
|
// Clamp cursor position to valid range
|
|
1315
1321
|
const targetRow = Math.max(0, Math.min(cursorPos.row, totalLines - 1));
|
|
1316
1322
|
const targetCol = Math.max(0, cursorPos.col);
|
|
1317
1323
|
|
|
1318
1324
|
// Move cursor from current position to target
|
|
1319
|
-
const rowDelta = targetRow -
|
|
1320
|
-
let
|
|
1325
|
+
const rowDelta = targetRow - fromRow;
|
|
1326
|
+
let seq = "";
|
|
1321
1327
|
if (rowDelta > 0) {
|
|
1322
|
-
|
|
1328
|
+
seq += `\x1b[${rowDelta}B`; // Move down
|
|
1323
1329
|
} else if (rowDelta < 0) {
|
|
1324
|
-
|
|
1330
|
+
seq += `\x1b[${-rowDelta}A`; // Move up
|
|
1325
1331
|
}
|
|
1326
1332
|
// Move to absolute column (1-indexed)
|
|
1327
|
-
|
|
1328
|
-
|
|
1333
|
+
seq += `\x1b[${targetCol + 1}G`;
|
|
1334
|
+
seq += this.#showHardwareCursor ? "\x1b[?25h" : "\x1b[?25l";
|
|
1329
1335
|
|
|
1330
|
-
|
|
1331
|
-
|
|
1336
|
+
return { seq, toRow: targetRow };
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
/**
|
|
1340
|
+
* Write the hardware cursor position to the terminal as a standalone
|
|
1341
|
+
* synchronized output block. Use when there is no surrounding render buffer
|
|
1342
|
+
* to embed the sequences into.
|
|
1343
|
+
*/
|
|
1344
|
+
#writeCursorPosition(cursorPos: { row: number; col: number } | null, totalLines: number): void {
|
|
1345
|
+
if (!cursorPos || totalLines <= 0) {
|
|
1346
|
+
this.terminal.hideCursor();
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
const { seq, toRow } = this.#cursorControlSequence(cursorPos, totalLines, this.#hardwareCursorRow);
|
|
1350
|
+
this.#hardwareCursorRow = toRow;
|
|
1351
|
+
this.terminal.write(`\x1b[?2026h${seq}\x1b[?2026l`);
|
|
1332
1352
|
}
|
|
1333
1353
|
}
|