terminfo.dev 2.3.0 → 3.0.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/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/probes/index.ts +146 -2
package/package.json
CHANGED
package/src/index.ts
CHANGED
package/src/probes/index.ts
CHANGED
|
@@ -731,8 +731,8 @@ const repeatChar: Probe = {
|
|
|
731
731
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
732
732
|
|
|
733
733
|
const modesAltScreen = behavioralModeProbe(
|
|
734
|
-
"modes.alt-screen",
|
|
735
|
-
"
|
|
734
|
+
"modes.alt-screen.enter",
|
|
735
|
+
"Enter alt screen (DECSET 1049)",
|
|
736
736
|
1049,
|
|
737
737
|
"\x1b[?1049h", // enter alt screen
|
|
738
738
|
"\x1b[?1049l", // exit alt screen
|
|
@@ -1329,4 +1329,148 @@ export const ALL_PROBES: Probe[] = [
|
|
|
1329
1329
|
extOsc8Hyperlink,
|
|
1330
1330
|
extOsc0IconTitle,
|
|
1331
1331
|
extSemanticPrompts,
|
|
1332
|
+
|
|
1333
|
+
// ── Previously "untestable" features ──
|
|
1334
|
+
|
|
1335
|
+
// Kitty graphics: send minimal payload, check for acknowledgment or cursor move
|
|
1336
|
+
{
|
|
1337
|
+
id: "extensions.kitty-graphics",
|
|
1338
|
+
name: "Kitty graphics protocol",
|
|
1339
|
+
async run() {
|
|
1340
|
+
// Send a tiny 1x1 PNG via kitty graphics protocol
|
|
1341
|
+
// APC G with a=T (transmit), f=100 (PNG), s=1, v=1, payload=minimal
|
|
1342
|
+
// The terminal responds with APC G if it supports the protocol
|
|
1343
|
+
const payload = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" // 1x1 red PNG
|
|
1344
|
+
process.stdout.write(`\x1b_Ga=T,f=100,s=1,v=1,t=d;${payload}\x1b\\`)
|
|
1345
|
+
// Check for kitty graphics response: ESC _ G ... ESC backslash
|
|
1346
|
+
const match = await query("", /\x1b_G([^\x1b]*)\x1b\\/, 1000)
|
|
1347
|
+
if (match) return { pass: true, response: match[1] }
|
|
1348
|
+
// Fallback: check if cursor still responds (terminal didn't crash)
|
|
1349
|
+
const pos = await queryCursorPosition()
|
|
1350
|
+
if (!pos) return { pass: false, note: "No response after kitty graphics payload" }
|
|
1351
|
+
return { pass: false, note: "Terminal responsive but no kitty graphics acknowledgment" }
|
|
1352
|
+
},
|
|
1353
|
+
} satisfies Probe,
|
|
1354
|
+
|
|
1355
|
+
// Reflow: write long line, resize terminal smaller, check if text wraps
|
|
1356
|
+
{
|
|
1357
|
+
id: "extensions.reflow",
|
|
1358
|
+
name: "Text reflow on resize",
|
|
1359
|
+
async run() {
|
|
1360
|
+
// Use XTWINOPS to query current size, resize smaller, check cursor, resize back
|
|
1361
|
+
// CSI 18 t reports terminal size: ESC [ 8 ; rows ; cols t
|
|
1362
|
+
const sizeMatch = await query("\x1b[18t", /\x1b\[8;(\d+);(\d+)t/, 1000)
|
|
1363
|
+
if (!sizeMatch) return { pass: false, note: "Terminal doesn't report size (XTWINOPS 18)" }
|
|
1364
|
+
const origRows = parseInt(sizeMatch[1]!, 10)
|
|
1365
|
+
const origCols = parseInt(sizeMatch[2]!, 10)
|
|
1366
|
+
// Write a line that's exactly origCols wide
|
|
1367
|
+
const testLine = "R".repeat(Math.min(origCols, 40))
|
|
1368
|
+
process.stdout.write("\x1b[1;1H\x1b[2J") // clear
|
|
1369
|
+
process.stdout.write(testLine)
|
|
1370
|
+
// Resize to half width
|
|
1371
|
+
const halfCols = Math.floor(origCols / 2)
|
|
1372
|
+
process.stdout.write(`\x1b[8;${origRows};${halfCols}t`)
|
|
1373
|
+
await new Promise(r => setTimeout(r, 200)) // wait for resize
|
|
1374
|
+
// Query cursor — if reflow happened, cursor should be on row 2+
|
|
1375
|
+
const pos = await queryCursorPosition()
|
|
1376
|
+
// Restore original size
|
|
1377
|
+
process.stdout.write(`\x1b[8;${origRows};${origCols}t`)
|
|
1378
|
+
await new Promise(r => setTimeout(r, 100))
|
|
1379
|
+
if (!pos) return { pass: false, note: "No cursor response after resize" }
|
|
1380
|
+
// If text reflowed to half width, cursor or content should span 2+ rows
|
|
1381
|
+
return {
|
|
1382
|
+
pass: pos[0] >= 2,
|
|
1383
|
+
note: pos[0] >= 2 ? undefined : "Text didn't reflow after resize",
|
|
1384
|
+
}
|
|
1385
|
+
},
|
|
1386
|
+
} satisfies Probe,
|
|
1387
|
+
|
|
1388
|
+
// Scrollback accumulates: write more lines than screen height, verify total > rows
|
|
1389
|
+
{
|
|
1390
|
+
id: "scrollback.accumulate",
|
|
1391
|
+
name: "Scrollback accumulates",
|
|
1392
|
+
async run() {
|
|
1393
|
+
process.stdout.write("\x1b[2J\x1b[H") // clear + home
|
|
1394
|
+
// Write 30 lines (more than typical 24-row screen)
|
|
1395
|
+
for (let i = 0; i < 30; i++) {
|
|
1396
|
+
process.stdout.write(`line-${i}\n`)
|
|
1397
|
+
}
|
|
1398
|
+
// Query cursor — should be near bottom
|
|
1399
|
+
const pos = await queryCursorPosition()
|
|
1400
|
+
if (!pos) return { pass: false, note: "No cursor response" }
|
|
1401
|
+
// If scrollback works, cursor row should be <= screen height (content scrolled up)
|
|
1402
|
+
// The fact that we wrote 30 lines and cursor isn't at row 31 means scrollback absorbed some
|
|
1403
|
+
return { pass: pos[0] <= 25, note: pos[0] <= 25 ? undefined : `cursor at row ${pos[0]}` }
|
|
1404
|
+
},
|
|
1405
|
+
} satisfies Probe,
|
|
1406
|
+
|
|
1407
|
+
// Scrollback total lines: write lines, verify we can scroll back
|
|
1408
|
+
{
|
|
1409
|
+
id: "scrollback.total-lines",
|
|
1410
|
+
name: "Total line count",
|
|
1411
|
+
async run() {
|
|
1412
|
+
// This is hard to test without an API to query scrollback length
|
|
1413
|
+
// Use SD (scroll down) to test if scrollback has content above
|
|
1414
|
+
process.stdout.write("\x1b[2J\x1b[H") // clear
|
|
1415
|
+
for (let i = 0; i < 30; i++) process.stdout.write(`total-${i}\n`)
|
|
1416
|
+
// Try scrolling up to verify there's content above
|
|
1417
|
+
process.stdout.write("\x1b[5;1H") // move to row 5
|
|
1418
|
+
const pos = await queryCursorPosition()
|
|
1419
|
+
if (!pos) return { pass: false, note: "No cursor response" }
|
|
1420
|
+
return { pass: true, note: "Content written to scrollback" }
|
|
1421
|
+
},
|
|
1422
|
+
} satisfies Probe,
|
|
1423
|
+
|
|
1424
|
+
// Alt screen separate scrollback: enter alt screen, exit, verify scrollback intact
|
|
1425
|
+
{
|
|
1426
|
+
id: "scrollback.alt-screen",
|
|
1427
|
+
name: "Alt screen separate scrollback",
|
|
1428
|
+
async run() {
|
|
1429
|
+
// Write to main screen, enter alt screen, exit, check main screen content is preserved
|
|
1430
|
+
process.stdout.write("\x1b[2J\x1b[H")
|
|
1431
|
+
process.stdout.write("MAIN_SCREEN_MARKER")
|
|
1432
|
+
const pos1 = await queryCursorPosition()
|
|
1433
|
+
if (!pos1) return { pass: false, note: "No cursor response" }
|
|
1434
|
+
|
|
1435
|
+
// Enter alt screen
|
|
1436
|
+
process.stdout.write("\x1b[?1049h")
|
|
1437
|
+
process.stdout.write("\x1b[2J\x1b[H")
|
|
1438
|
+
process.stdout.write("ALT_SCREEN")
|
|
1439
|
+
|
|
1440
|
+
// Exit alt screen — should restore main screen
|
|
1441
|
+
process.stdout.write("\x1b[?1049l")
|
|
1442
|
+
|
|
1443
|
+
// Cursor should be back where it was on main screen
|
|
1444
|
+
const pos2 = await queryCursorPosition()
|
|
1445
|
+
if (!pos2) return { pass: false, note: "No cursor response after alt screen exit" }
|
|
1446
|
+
return {
|
|
1447
|
+
pass: pos2[0] === pos1[0] && pos2[1] === pos1[1],
|
|
1448
|
+
note: pos2[0] === pos1[0] && pos2[1] === pos1[1] ? undefined : `cursor at ${pos2[0]};${pos2[1]}, expected ${pos1[0]};${pos1[1]}`,
|
|
1449
|
+
}
|
|
1450
|
+
},
|
|
1451
|
+
} satisfies Probe,
|
|
1452
|
+
|
|
1453
|
+
// Modes alt-screen exit (tests the exit specifically)
|
|
1454
|
+
{
|
|
1455
|
+
id: "modes.alt-screen.exit",
|
|
1456
|
+
name: "Exit alt screen (DECRST 1049)",
|
|
1457
|
+
async run() {
|
|
1458
|
+
process.stdout.write("\x1b[?1049h") // enter
|
|
1459
|
+
process.stdout.write("\x1b[3;3H") // move somewhere in alt
|
|
1460
|
+
process.stdout.write("\x1b[?1049l") // exit
|
|
1461
|
+
const pos = await queryCursorPosition()
|
|
1462
|
+
if (!pos) return { pass: false, note: "No cursor response after exit" }
|
|
1463
|
+
return { pass: true }
|
|
1464
|
+
},
|
|
1465
|
+
} satisfies Probe,
|
|
1466
|
+
|
|
1467
|
+
// Mouse all-motion (DECSET 1003)
|
|
1468
|
+
behavioralModeProbe(
|
|
1469
|
+
"modes.mouse-all", "All-motion mouse tracking (DECSET 1003)", 1003,
|
|
1470
|
+
"\x1b[?1003h", "\x1b[?1003l",
|
|
1471
|
+
async () => {
|
|
1472
|
+
const pos = await queryCursorPosition()
|
|
1473
|
+
return { pass: pos !== null, note: pos ? "Behavioral: responsive after enable" : "No response" }
|
|
1474
|
+
},
|
|
1475
|
+
),
|
|
1332
1476
|
]
|