From 36baf70beafa1554695140962383268a4a981aa8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 31 Jan 2026 21:05:36 -0800 Subject: [PATCH 01/17] Unskip supported InputHandler tests --- test/playwright/InputHandler.test.ts | 66 ++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/test/playwright/InputHandler.test.ts b/test/playwright/InputHandler.test.ts index 55b63dc9..ea342ebc 100644 --- a/test/playwright/InputHandler.test.ts +++ b/test/playwright/InputHandler.test.ts @@ -316,14 +316,16 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.write('abcdefg\x1b[3D\x1b[10b#\x1b[3b'); await pollFor(ctx.page, () => getLinesAsArray(3), ['#', ' #', 'abcd####']); }); - test.skip('CSI Ps c - ', async () => { - // TODO: Implement + test('CSI Ps c - ', async () => { + await ctx.proxy.write('\x1b[c'); + await pollFor(ctx.page, () => recordedData, ['\x1b[?1;2c']); }); test.skip('CSI = Ps c - ', async () => { // TODO: Implement }); - test.skip('CSI > Ps c - ', async () => { - // TODO: Implement + test('CSI > Ps c - ', async () => { + await ctx.proxy.write('\x1b[>c'); + await pollFor(ctx.page, () => recordedData, ['\x1b[>0;276;0c']); }); test('CSI Ps d - VPA: Line Position Absolute [row] (default = [1,column])', async () => { // Default @@ -425,8 +427,14 @@ test.describe('InputHandler Integration Tests', () => { test.skip('Ps = 1 0 - Show toolbar (rxvt)', async () => { // TODO: Implement }); - test.skip('Ps = 1 2 - Start blinking cursor (AT&T 610)', async () => { - // TODO: Implement + test('Ps = 1 2 - Start blinking cursor (AT&T 610)', async () => { + const previousQuirks = await ctx.proxy.getOption('quirks'); + const previousCursorBlink = await ctx.proxy.getOption('cursorBlink'); + await ctx.proxy.setOption('quirks', { ...(previousQuirks ?? {}), allowSetCursorBlink: true }); + await ctx.proxy.write('\x1b[?12h'); + await pollFor(ctx.page, async () => await ctx.proxy.getOption('cursorBlink'), true); + await ctx.proxy.setOption('quirks', previousQuirks); + await ctx.proxy.setOption('cursorBlink', previousCursorBlink); }); test.skip('Ps = 1 3 - Start blinking cursor (set only via resource or menu)', async () => { // TODO: Implement @@ -655,8 +663,18 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.write('\x1b[?1048l'); await pollFor(ctx.page, () => getCursor(), { col: 4, row: 3 }); }); - test.skip('Ps = 1 0 4 9 - Save cursor as in DECSC, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 4 9 - Save cursor as in DECSC, xterm', async () => { + await ctx.proxy.write('main'); + await pollFor(ctx.page, () => getLinesAsArray(1), ['main']); + await ctx.proxy.write('\x1b[4;6H'); + await ctx.proxy.write('\x1b[?1049h'); + await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'alternate'); + await ctx.proxy.write('\x1b[Halt'); + await pollFor(ctx.page, () => getLinesAsArray(1), ['alt']); + await ctx.proxy.write('\x1b[?1049l'); + await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'normal'); + await pollFor(ctx.page, () => getLinesAsArray(1), ['main']); + await pollFor(ctx.page, () => getCursor(), { col: 5, row: 3 }); }); test.skip('Ps = 1 0 5 0 - Set terminfo/termcap function-key mode, xterm', async () => { // TODO: Implement @@ -727,8 +745,15 @@ test.describe('InputHandler Integration Tests', () => { test.skip('Ps = 2 - Designate VT52 mode (DECANM), VT100.', async () => { // TODO: Implement }); - test.skip('Ps = 3 - 80 Column Mode (DECCOLM), VT100.', async () => { - // TODO: Implement + test('Ps = 3 - 80 Column Mode (DECCOLM), VT100.', async () => { + const windowOptions = await ctx.proxy.getOption('windowOptions'); + await ctx.proxy.setOption('windowOptions', { ...windowOptions, setWinLines: true }); + await ctx.proxy.write('\x1b[?3h'); + await pollFor(ctx.page, async () => await ctx.proxy.cols, 132); + await ctx.proxy.write('\x1b[?3l'); + await pollFor(ctx.page, async () => await ctx.proxy.cols, 80); + await ctx.proxy.resize(80, 24); + await ctx.proxy.setOption('windowOptions', windowOptions); }); test.skip('Ps = 4 - Jump (Fast) Scroll (DECSCLM), VT100.', async () => { // TODO: Implement @@ -770,8 +795,15 @@ test.describe('InputHandler Integration Tests', () => { test.skip('Ps = 1 0 - Hide toolbar (rxvt).', async () => { // TODO: Implement }); - test.skip('Ps = 1 2 - Stop blinking cursor (AT&T 610).', async () => { - // TODO: Implement + test('Ps = 1 2 - Stop blinking cursor (AT&T 610).', async () => { + const previousQuirks = await ctx.proxy.getOption('quirks'); + const previousCursorBlink = await ctx.proxy.getOption('cursorBlink'); + await ctx.proxy.setOption('quirks', { ...(previousQuirks ?? {}), allowSetCursorBlink: true }); + await ctx.proxy.setOption('cursorBlink', true); + await ctx.proxy.write('\x1b[?12l'); + await pollFor(ctx.page, async () => await ctx.proxy.getOption('cursorBlink'), false); + await ctx.proxy.setOption('quirks', previousQuirks); + await ctx.proxy.setOption('cursorBlink', previousCursorBlink); }); test.skip('Ps = 1 3 - Disable blinking cursor (reset only via resource or menu).', async () => { // TODO: Implement @@ -1492,8 +1524,14 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.write('\x1b[0 q'); deepStrictEqual(await getCursorMode(), { style: undefined, blink: undefined }); }); - test.skip('CSI Ps " q - Select character protection attribute (DECSCA), VT220.', async () => { - // TODO: Implement + test('CSI Ps " q - Select character protection attribute (DECSCA), VT220.', async () => { + await ctx.proxy.write('\x1b[1"q'); + await ctx.proxy.write('PROT'); + await ctx.proxy.write('\x1b[2"q'); + await ctx.proxy.write('open'); + await pollFor(ctx.page, () => getLinesAsArray(1), ['PROTopen']); + await ctx.proxy.write('\x1b[1;1H\x1b[?2K'); + await pollFor(ctx.page, () => getLinesAsArray(1), ['PROT']); }); test.skip('CSI # q - Pop video attributes from stack (XTPOPSGR), xterm.', async () => { // TODO: Implement From dc487dfd6f5e05b53fc0bd43bda4e82ff9b37597 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 31 Jan 2026 22:01:35 -0800 Subject: [PATCH 02/17] Assert no mode changes and unskip --- test/playwright/InputHandler.test.ts | 452 ++++++++++++++------------- 1 file changed, 239 insertions(+), 213 deletions(-) diff --git a/test/playwright/InputHandler.test.ts b/test/playwright/InputHandler.test.ts index ea342ebc..a62d1fbc 100644 --- a/test/playwright/InputHandler.test.ts +++ b/test/playwright/InputHandler.test.ts @@ -227,9 +227,9 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.write('\x1b[2S'); await pollFor(ctx.page, () => getLinesAsArray(5), ['3', '4', '5', '', '']); }); - test.skip('CSI ? Pi ; Pa ; Pv S - XTSMGRAPHICS: Set or request graphics attribute, xterm', async () => { - // TODO: Implement - }); + // This is intentionally not implemented here are image support lives within an addon + // test.skip('CSI ? Pi ; Pa ; Pv S - XTSMGRAPHICS: Set or request graphics attribute, xterm', async () => { + // }); test('CSI Ps T - SD: Scroll down Ps lines (default = 1), VT420', async () => { await ctx.proxy.write('1\r\n2\r\n3\r\n4\r\n5'); await pollFor(ctx.page, () => getLinesAsArray(5), ['1', '2', '3', '4', '5']); @@ -386,11 +386,11 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.resize(80, 24); await ctx.proxy.setOption('windowOptions', windowOptions); }); - test.skip('Ps = 4 - Smooth (Slow) Scroll (DECSCLM), VT100', async () => { - // TODO: Implement + test('Ps = 4 - Smooth (Slow) Scroll (DECSCLM), VT100', async () => { + await assertNoModeChange('\x1b[?4h'); }); - test.skip('Ps = 5 - Reverse Video (DECSCNM), VT100', async () => { - // TODO: Implement + test('Ps = 5 - Reverse Video (DECSCNM), VT100', async () => { + await assertNoModeChange('\x1b[?5h'); }); test('Ps = 6 - Origin Mode (DECOM), VT100', async () => { await ctx.proxy.write('\x1b[?6h'); @@ -413,8 +413,8 @@ test.describe('InputHandler Integration Tests', () => { await pollFor(ctx.page, () => getLinesAsArray(1), ['1234X']); await ctx.proxy.resize(80, 24); }); - test.skip('Ps = 8 - Auto-Repeat Keys (DECARM), VT100', async () => { - // TODO: Implement + test('Ps = 8 - Auto-Repeat Keys (DECARM), VT100', async () => { + await assertNoModeChange('\x1b[?8h'); }); test('Ps = 9 - Send Mouse X & Y on button press', async () => { const selectionBefore = await dragSelection(); @@ -424,8 +424,8 @@ test.describe('InputHandler Integration Tests', () => { const selectionAfter = await dragSelection(); ok(selectionAfter === 0); }); - test.skip('Ps = 1 0 - Show toolbar (rxvt)', async () => { - // TODO: Implement + test('Ps = 1 0 - Show toolbar (rxvt)', async () => { + await assertNoModeChange('\x1b[?10h'); }); test('Ps = 1 2 - Start blinking cursor (AT&T 610)', async () => { const previousQuirks = await ctx.proxy.getOption('quirks'); @@ -436,17 +436,17 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.setOption('quirks', previousQuirks); await ctx.proxy.setOption('cursorBlink', previousCursorBlink); }); - test.skip('Ps = 1 3 - Start blinking cursor (set only via resource or menu)', async () => { - // TODO: Implement + test('Ps = 1 3 - Start blinking cursor (set only via resource or menu)', async () => { + await assertNoModeChange('\x1b[?13h'); }); - test.skip('Ps = 1 4 - Enable XOR of blinking cursor control sequence and menu', async () => { - // TODO: Implement + test('Ps = 1 4 - Enable XOR of blinking cursor control sequence and menu', async () => { + await assertNoModeChange('\x1b[?14h'); }); - test.skip('Ps = 1 8 - Print Form Feed (DECPFF), VT220', async () => { - // TODO: Implement + test('Ps = 1 8 - Print Form Feed (DECPFF), VT220', async () => { + await assertNoModeChange('\x1b[?18h'); }); - test.skip('Ps = 1 9 - Set print extent to full screen (DECPEX), VT220', async () => { - // TODO: Implement + test('Ps = 1 9 - Set print extent to full screen (DECPEX), VT220', async () => { + await assertNoModeChange('\x1b[?19h'); }); test('Ps = 2 5 - Show cursor (DECTCEM), VT220', async () => { await ctx.proxy.write('\x1b[?25l'); @@ -454,32 +454,32 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.write('\x1b[?25h'); await pollFor(ctx.page, async () => (await ctx.proxy.modes).showCursor, true); }); - test.skip('Ps = 3 0 - Show scrollbar (rxvt)', async () => { - // TODO: Implement + test('Ps = 3 0 - Show scrollbar (rxvt)', async () => { + await assertNoModeChange('\x1b[?30h'); }); - test.skip('Ps = 3 5 - Enable font-shifting functions (rxvt)', async () => { - // TODO: Implement + test('Ps = 3 5 - Enable font-shifting functions (rxvt)', async () => { + await assertNoModeChange('\x1b[?35h'); }); - test.skip('Ps = 3 8 - Enter Tektronix mode (DECTEK), VT240, xterm', async () => { - // TODO: Implement + test('Ps = 3 8 - Enter Tektronix mode (DECTEK), VT240, xterm', async () => { + await assertNoModeChange('\x1b[?38h'); }); - test.skip('Ps = 4 0 - Allow 80 ⇒ 132 mode, xterm', async () => { - // TODO: Implement + test('Ps = 4 0 - Allow 80 ⇒ 132 mode, xterm', async () => { + await assertNoModeChange('\x1b[?40h'); }); - test.skip('Ps = 4 1 - more(1) fix (see curses resource)', async () => { - // TODO: Implement + test('Ps = 4 1 - more(1) fix (see curses resource)', async () => { + await assertNoModeChange('\x1b[?41h'); }); - test.skip('Ps = 4 2 - Enable National Replacement Character sets (DECNRCM), VT220', async () => { - // TODO: Implement + test('Ps = 4 2 - Enable National Replacement Character sets (DECNRCM), VT220', async () => { + await assertNoModeChange('\x1b[?42h'); }); - test.skip('Ps = 4 3 - Enable Graphic Expanded Print Mode (DECGEPM), VT340', async () => { - // TODO: Implement + test('Ps = 4 3 - Enable Graphic Expanded Print Mode (DECGEPM), VT340', async () => { + await assertNoModeChange('\x1b[?43h'); }); - test.skip('Ps = 4 4 - Turn on margin bell, xterm', async () => { - // TODO: Implement + test('Ps = 4 4 - Turn on margin bell, xterm', async () => { + await assertNoModeChange('\x1b[?44h'); }); - test.skip('Ps = 4 4 - Enable Graphic Print Color Mode (DECGPCM), VT340', async () => { - // TODO: Implement + test('Ps = 4 4 - Enable Graphic Print Color Mode (DECGPCM), VT340', async () => { + await assertNoModeChange('\x1b[?44h'); }); test('Ps = 4 5 - Reverse-wraparound mode (XTREVWRAP), xterm', async () => { await ctx.proxy.write('\x1b[?45h'); @@ -492,13 +492,12 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.resize(80, 24); }); test.skip('Ps = 4 5 - Enable Graphic Print Color Syntax (DECGPCS), VT340', async () => { - // TODO: Implement }); - test.skip('Ps = 4 6 - Start logging (XTLOGGING), xterm', async () => { - // TODO: Implement + test('Ps = 4 6 - Start logging (XTLOGGING), xterm', async () => { + await assertNoModeChange('\x1b[?46h'); }); - test.skip('Ps = 4 6 - Graphic Print Background Mode, VT340', async () => { - // TODO: Implement + test('Ps = 4 6 - Graphic Print Background Mode, VT340', async () => { + await assertNoModeChange('\x1b[?46h'); }); test('Ps = 4 7 - Use Alternate Screen Buffer, xterm', async () => { await ctx.proxy.write('main'); @@ -513,23 +512,21 @@ test.describe('InputHandler Integration Tests', () => { await pollFor(ctx.page, () => getLinesAsArray(1), ['main']); }); test.skip('Ps = 4 7 - Enable Graphic Rotated Print Mode (DECGRPM), VT340', async () => { - // TODO: Implement }); test('Ps = 6 6 - Application keypad mode (DECNKM), VT320', async () => { await ctx.proxy.write('\x1b[?66h'); await pollFor(ctx.page, async () => (await ctx.proxy.modes).applicationKeypadMode, true); }); - test.skip('Ps = 6 7 - Backarrow key sends backspace (DECBKM), VT340, VT420', async () => { - // TODO: Implement + test('Ps = 6 7 - Backarrow key sends backspace (DECBKM), VT340, VT420', async () => { + await assertNoModeChange('\x1b[?67h'); }); - test.skip('Ps = 6 9 - Enable left and right margin mode (DECLRMM), VT420 and up', async () => { - // TODO: Implement + test('Ps = 6 9 - Enable left and right margin mode (DECLRMM), VT420 and up', async () => { + await assertNoModeChange('\x1b[?69h'); }); - test.skip('Ps = 8 0 - Enable Sixel Display Mode (DECSDM), VT330, VT340, VT382', async () => { - // TODO: Implement - }); - test.skip('Ps = 9 5 - Do not clear screen when DECCOLM is set/reset (DECNCSM), VT510 and up', async () => { - // TODO: Implement + // test.skip('Ps = 8 0 - Enable Sixel Display Mode (DECSDM), VT330, VT340, VT382', async () => { + // }); + test('Ps = 9 5 - Do not clear screen when DECCOLM is set/reset (DECNCSM), VT510 and up', async () => { + await assertNoModeChange('\x1b[?95h'); }); test('Ps = 1 0 0 0 - Send Mouse X & Y on button press and release', async () => { const selectionBefore = await dragSelection(); @@ -539,8 +536,8 @@ test.describe('InputHandler Integration Tests', () => { const selectionAfter = await dragSelection(); ok(selectionAfter === 0); }); - test.skip('Ps = 1 0 0 1 - Use Hilite Mouse Tracking, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 0 1 - Use Hilite Mouse Tracking, xterm', async () => { + await assertNoModeChange('\x1b[?1001h'); }); test('Ps = 1 0 0 2 - Use Cell Motion Mouse Tracking, xterm', async () => { const selectionBefore = await dragSelection(); @@ -585,64 +582,70 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.blur(); await pollFor(ctx.page, () => recordedData, ['\x1b[I', '\x1b[O']); }); - test.skip('Ps = 1 0 0 5 - Enable UTF-8 Mouse Mode, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 0 5 - Enable UTF-8 Mouse Mode, xterm', async () => { + await ctx.proxy.write('\x1b[?1006h'); + await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR'); + await ctx.proxy.write('\x1b[?1005h'); + await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR'); }); test('Ps = 1 0 0 6 - Enable SGR Mouse Mode, xterm', async () => { await ctx.proxy.write('\x1b[?1006h'); await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR'); }); - test.skip('Ps = 1 0 0 7 - Enable Alternate Scroll Mode, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 0 7 - Enable Alternate Scroll Mode, xterm', async () => { + await assertNoModeChange('\x1b[?1007h'); }); - test.skip('Ps = 1 0 1 0 - Scroll to bottom on tty output (rxvt)', async () => { - // TODO: Implement + test('Ps = 1 0 1 0 - Scroll to bottom on tty output (rxvt)', async () => { + await assertNoModeChange('\x1b[?1010h'); }); - test.skip('Ps = 1 0 1 1 - Scroll to bottom on key press (rxvt)', async () => { - // TODO: Implement + test('Ps = 1 0 1 1 - Scroll to bottom on key press (rxvt)', async () => { + await assertNoModeChange('\x1b[?1011h'); }); - test.skip('Ps = 1 0 1 5 - Enable urxvt Mouse Mode', async () => { - // TODO: Implement + test('Ps = 1 0 1 5 - Enable urxvt Mouse Mode', async () => { + await ctx.proxy.write('\x1b[?1006h'); + await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR'); + await ctx.proxy.write('\x1b[?1015h'); + await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR'); }); test('Ps = 1 0 1 6 - Enable SGR Mouse PixelMode, xterm', async () => { await ctx.proxy.write('\x1b[?1016h'); await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR_PIXELS'); }); - test.skip('Ps = 1 0 3 4 - Interpret "meta" key, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 3 4 - Interpret "meta" key, xterm', async () => { + await assertNoModeChange('\x1b[?1034h'); }); - test.skip('Ps = 1 0 3 5 - Enable special modifiers for Alt and NumLock keys, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 3 5 - Enable special modifiers for Alt and NumLock keys, xterm', async () => { + await assertNoModeChange('\x1b[?1035h'); }); - test.skip('Ps = 1 0 3 6 - Send ESC when Meta modifies a key, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 3 6 - Send ESC when Meta modifies a key, xterm', async () => { + await assertNoModeChange('\x1b[?1036h'); }); - test.skip('Ps = 1 0 3 7 - Send DEL from the editing-keypad Delete key, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 3 7 - Send DEL from the editing-keypad Delete key, xterm', async () => { + await assertNoModeChange('\x1b[?1037h'); }); - test.skip('Ps = 1 0 3 9 - Send ESC when Alt modifies a key, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 3 9 - Send ESC when Alt modifies a key, xterm', async () => { + await assertNoModeChange('\x1b[?1039h'); }); - test.skip('Ps = 1 0 4 0 - Keep selection even if not highlighted, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 4 0 - Keep selection even if not highlighted, xterm', async () => { + await assertNoModeChange('\x1b[?1040h'); }); - test.skip('Ps = 1 0 4 1 - Use the CLIPBOARD selection, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 4 1 - Use the CLIPBOARD selection, xterm', async () => { + await assertNoModeChange('\x1b[?1041h'); }); - test.skip('Ps = 1 0 4 2 - Enable Urgency window manager hint when Control-G is received, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 4 2 - Enable Urgency window manager hint when Control-G is received, xterm', async () => { + await assertNoModeChange('\x1b[?1042h'); }); - test.skip('Ps = 1 0 4 3 - Enable raising of the window when Control-G is received, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 4 3 - Enable raising of the window when Control-G is received, xterm', async () => { + await assertNoModeChange('\x1b[?1043h'); }); - test.skip('Ps = 1 0 4 4 - Reuse the most recent data copied to CLIPBOARD, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 4 4 - Reuse the most recent data copied to CLIPBOARD, xterm', async () => { + await assertNoModeChange('\x1b[?1044h'); }); - test.skip('Ps = 1 0 4 5 - XTREVWRAP2: Extended Reverse-wraparound mode, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 4 5 - XTREVWRAP2: Extended Reverse-wraparound mode, xterm', async () => { + await assertNoModeChange('\x1b[?1045h'); }); - test.skip('Ps = 1 0 4 6 - Enable switching to/from Alternate Screen Buffer, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 4 6 - Enable switching to/from Alternate Screen Buffer, xterm', async () => { + await assertNoModeChange('\x1b[?1046h'); }); test('Ps = 1 0 4 7 - Use Alternate Screen Buffer, xterm', async () => { await ctx.proxy.write('main'); @@ -676,32 +679,32 @@ test.describe('InputHandler Integration Tests', () => { await pollFor(ctx.page, () => getLinesAsArray(1), ['main']); await pollFor(ctx.page, () => getCursor(), { col: 5, row: 3 }); }); - test.skip('Ps = 1 0 5 0 - Set terminfo/termcap function-key mode, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 5 0 - Set terminfo/termcap function-key mode, xterm', async () => { + await assertNoModeChange('\x1b[?1050h'); }); - test.skip('Ps = 1 0 5 1 - Set Sun function-key mode, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 5 1 - Set Sun function-key mode, xterm', async () => { + await assertNoModeChange('\x1b[?1051h'); }); - test.skip('Ps = 1 0 5 2 - Set HP function-key mode, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 5 2 - Set HP function-key mode, xterm', async () => { + await assertNoModeChange('\x1b[?1052h'); }); - test.skip('Ps = 1 0 5 3 - Set SCO function-key mode, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 5 3 - Set SCO function-key mode, xterm', async () => { + await assertNoModeChange('\x1b[?1053h'); }); - test.skip('Ps = 1 0 6 0 - Set legacy keyboard emulation, i.e, X11R6, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 6 0 - Set legacy keyboard emulation, i.e, X11R6, xterm', async () => { + await assertNoModeChange('\x1b[?1060h'); }); - test.skip('Ps = 1 0 6 1 - Set VT220 keyboard emulation, xterm', async () => { - // TODO: Implement + test('Ps = 1 0 6 1 - Set VT220 keyboard emulation, xterm', async () => { + await assertNoModeChange('\x1b[?1061h'); }); - test.skip('Ps = 2 0 0 1 - Enable readline mouse button-1, xterm', async () => { - // TODO: Implement + test('Ps = 2 0 0 1 - Enable readline mouse button-1, xterm', async () => { + await assertNoModeChange('\x1b[?2001h'); }); - test.skip('Ps = 2 0 0 2 - Enable readline mouse button-2, xterm', async () => { - // TODO: Implement + test('Ps = 2 0 0 2 - Enable readline mouse button-2, xterm', async () => { + await assertNoModeChange('\x1b[?2002h'); }); - test.skip('Ps = 2 0 0 3 - Enable readline mouse button-3, xterm', async () => { - // TODO: Implement + test('Ps = 2 0 0 3 - Enable readline mouse button-3, xterm', async () => { + await assertNoModeChange('\x1b[?2003h'); }); test('Pm = 2 0 0 4, Set bracketed paste mode', async () => { if (ctx.browser.browserType().name() !== 'chromium') { @@ -714,11 +717,11 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.write('\x1b[?2004l'); await pollFor(ctx.page, () => simulatePaste('baz'), 'baz'); }); - test.skip('Ps = 2 0 0 5 - Enable readline character-quoting, xterm', async () => { - // TODO: Implement + test('Ps = 2 0 0 5 - Enable readline character-quoting, xterm', async () => { + await assertNoModeChange('\x1b[?2005h'); }); - test.skip('Ps = 2 0 0 6 - Enable readline newline pasting, xterm', async () => { - // TODO: Implement + test('Ps = 2 0 0 6 - Enable readline newline pasting, xterm', async () => { + await assertNoModeChange('\x1b[?2006h'); }); }); test.skip('CSI Ps i - MC: Media Copy', async () => { @@ -742,8 +745,8 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.write('\x1b[?1l'); await pollFor(ctx.page, async () => (await ctx.proxy.modes).applicationCursorKeysMode, false); }); - test.skip('Ps = 2 - Designate VT52 mode (DECANM), VT100.', async () => { - // TODO: Implement + test('Ps = 2 - Designate VT52 mode (DECANM), VT100.', async () => { + await assertNoModeChange('\x1b[?2l'); }); test('Ps = 3 - 80 Column Mode (DECCOLM), VT100.', async () => { const windowOptions = await ctx.proxy.getOption('windowOptions'); @@ -755,11 +758,11 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.resize(80, 24); await ctx.proxy.setOption('windowOptions', windowOptions); }); - test.skip('Ps = 4 - Jump (Fast) Scroll (DECSCLM), VT100.', async () => { - // TODO: Implement + test('Ps = 4 - Jump (Fast) Scroll (DECSCLM), VT100.', async () => { + await assertNoModeChange('\x1b[?4l'); }); - test.skip('Ps = 5 - Normal Video (DECSCNM), VT100.', async () => { - // TODO: Implement + test('Ps = 5 - Normal Video (DECSCNM), VT100.', async () => { + await assertNoModeChange('\x1b[?5l'); }); test('Ps = 6 - Normal Cursor Mode (DECOM), VT100.', async () => { await ctx.proxy.write('\x1b[?6h'); @@ -783,8 +786,8 @@ test.describe('InputHandler Integration Tests', () => { await pollFor(ctx.page, () => getLinesAsArray(2), ['12345', 'X']); await ctx.proxy.resize(80, 24); }); - test.skip('Ps = 8 - No Auto-Repeat Keys (DECARM), VT100.', async () => { - // TODO: Implement + test('Ps = 8 - No Auto-Repeat Keys (DECARM), VT100.', async () => { + await assertNoModeChange('\x1b[?8l'); }); test('Ps = 9 - Don\'t send Mouse X & Y on button press, xterm.', async () => { await ctx.proxy.write('\x1b[?9h'); @@ -792,8 +795,8 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.write('\x1b[?9l'); await pollFor(ctx.page, async () => (await ctx.proxy.modes).mouseTrackingMode, 'none'); }); - test.skip('Ps = 1 0 - Hide toolbar (rxvt).', async () => { - // TODO: Implement + test('Ps = 1 0 - Hide toolbar (rxvt).', async () => { + await assertNoModeChange('\x1b[?10l'); }); test('Ps = 1 2 - Stop blinking cursor (AT&T 610).', async () => { const previousQuirks = await ctx.proxy.getOption('quirks'); @@ -805,17 +808,17 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.setOption('quirks', previousQuirks); await ctx.proxy.setOption('cursorBlink', previousCursorBlink); }); - test.skip('Ps = 1 3 - Disable blinking cursor (reset only via resource or menu).', async () => { - // TODO: Implement + test('Ps = 1 3 - Disable blinking cursor (reset only via resource or menu).', async () => { + await assertNoModeChange('\x1b[?13l'); }); - test.skip('Ps = 1 4 - Disable XOR of blinking cursor control sequence and menu.', async () => { - // TODO: Implement + test('Ps = 1 4 - Disable XOR of blinking cursor control sequence and menu.', async () => { + await assertNoModeChange('\x1b[?14l'); }); - test.skip('Ps = 1 8 - Don\'t Print Form Feed (DECPFF), VT220.', async () => { - // TODO: Implement + test('Ps = 1 8 - Don\'t Print Form Feed (DECPFF), VT220.', async () => { + await assertNoModeChange('\x1b[?18l'); }); - test.skip('Ps = 1 9 - Limit print to scrolling region (DECPEX), VT220.', async () => { - // TODO: Implement + test('Ps = 1 9 - Limit print to scrolling region (DECPEX), VT220.', async () => { + await assertNoModeChange('\x1b[?19l'); }); test('Ps = 2 5 - Hide cursor (DECTCEM), VT220.', async () => { await ctx.proxy.write('\x1b[?25h'); @@ -823,29 +826,29 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.write('\x1b[?25l'); await pollFor(ctx.page, async () => (await ctx.proxy.modes).showCursor, false); }); - test.skip('Ps = 3 0 - Don\'t show scrollbar (rxvt).', async () => { - // TODO: Implement + test('Ps = 3 0 - Don\'t show scrollbar (rxvt).', async () => { + await assertNoModeChange('\x1b[?30l'); }); - test.skip('Ps = 3 5 - Disable font-shifting functions (rxvt).', async () => { - // TODO: Implement + test('Ps = 3 5 - Disable font-shifting functions (rxvt).', async () => { + await assertNoModeChange('\x1b[?35l'); }); - test.skip('Ps = 4 0 - Disallow 80 ⇒ 132 mode, xterm.', async () => { - // TODO: Implement + test('Ps = 4 0 - Disallow 80 ⇒ 132 mode, xterm.', async () => { + await assertNoModeChange('\x1b[?40l'); }); - test.skip('Ps = 4 1 - No more(1) fix (see curses resource).', async () => { - // TODO: Implement + test('Ps = 4 1 - No more(1) fix (see curses resource).', async () => { + await assertNoModeChange('\x1b[?41l'); }); - test.skip('Ps = 4 2 - Disable National Replacement Character sets (DECNRCM), VT220.', async () => { - // TODO: Implement + test('Ps = 4 2 - Disable National Replacement Character sets (DECNRCM), VT220.', async () => { + await assertNoModeChange('\x1b[?42l'); }); - test.skip('Ps = 4 3 - Disable Graphic Expanded Print Mode (DECGEPM), VT340.', async () => { - // TODO: Implement + test('Ps = 4 3 - Disable Graphic Expanded Print Mode (DECGEPM), VT340.', async () => { + await assertNoModeChange('\x1b[?43l'); }); - test.skip('Ps = 4 4 - Turn off margin bell, xterm.', async () => { - // TODO: Implement + test('Ps = 4 4 - Turn off margin bell, xterm.', async () => { + await assertNoModeChange('\x1b[?44l'); }); - test.skip('Ps = 4 4 - Disable Graphic Print Color Mode (DECGPCM), VT340.', async () => { - // TODO: Implement + test('Ps = 4 4 - Disable Graphic Print Color Mode (DECGPCM), VT340.', async () => { + await assertNoModeChange('\x1b[?44l'); }); test('Ps = 4 5 - No Reverse-wraparound mode (XTREVWRAP), xterm.', async () => { await ctx.proxy.write('\x1b[?45h'); @@ -854,10 +857,9 @@ test.describe('InputHandler Integration Tests', () => { await pollFor(ctx.page, async () => (await ctx.proxy.modes).reverseWraparoundMode, false); }); test.skip('Ps = 4 5 - Disable Graphic Print Color Syntax (DECGPCS), VT340.', async () => { - // TODO: Implement }); - test.skip('Ps = 4 6 - Stop logging (XTLOGGING), xterm. This is normally disabled by a compile-time option.', async () => { - // TODO: Implement + test('Ps = 4 6 - Stop logging (XTLOGGING), xterm. This is normally disabled by a compile-time option.', async () => { + await assertNoModeChange('\x1b[?46l'); }); test('Ps = 4 7 - Use Normal Screen Buffer, xterm.', async () => { await ctx.proxy.write('\x1b[?47h'); @@ -866,7 +868,6 @@ test.describe('InputHandler Integration Tests', () => { await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'normal'); }); test.skip('Ps = 4 7 - Disable Graphic Rotated Print Mode (DECGRPM), VT340.', async () => { - // TODO: Implement }); test('Ps = 6 6 - Numeric keypad mode (DECNKM), VT320.', async () => { await ctx.proxy.write('\x1b[?66h'); @@ -874,17 +875,17 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.write('\x1b[?66l'); await pollFor(ctx.page, async () => (await ctx.proxy.modes).applicationKeypadMode, false); }); - test.skip('Ps = 6 7 - Backarrow key sends delete (DECBKM), VT340, VT420. This sets the backarrowKey resource to "false".', async () => { - // TODO: Implement + test('Ps = 6 7 - Backarrow key sends delete (DECBKM), VT340, VT420. This sets the backarrowKey resource to "false".', async () => { + await assertNoModeChange('\x1b[?67l'); }); - test.skip('Ps = 6 9 - Disable left and right margin mode (DECLRMM), VT420 and up.', async () => { - // TODO: Implement + test('Ps = 6 9 - Disable left and right margin mode (DECLRMM), VT420 and up.', async () => { + await assertNoModeChange('\x1b[?69l'); }); - test.skip('Ps = 8 0 - Disable Sixel Display Mode (DECSDM), VT330, VT340, VT382. Turns on "Sixel Scrolling". See the section Sixel Graphics and mode 8 4 5 2 .', async () => { - // TODO: Implement - }); - test.skip('Ps = 9 5 - Clear screen when DECCOLM is set/reset (DECNCSM), VT510 and up.', async () => { - // TODO: Implement + // This is intentionally not implemented here are image support lives within an addon + // test.skip('Ps = 8 0 - Disable Sixel Display Mode (DECSDM), VT330, VT340, VT382. Turns on "Sixel Scrolling". See the section Sixel Graphics and mode 8 4 5 2 .', async () => { + // }); + test('Ps = 9 5 - Clear screen when DECCOLM is set/reset (DECNCSM), VT510 and up.', async () => { + await assertNoModeChange('\x1b[?95l'); }); test('Ps = 1 0 0 0 - Don\'t send Mouse X & Y on button press and release. See the section Mouse Tracking.', async () => { await ctx.proxy.write('\x1b[?1000h'); @@ -892,8 +893,8 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.write('\x1b[?1000l'); await pollFor(ctx.page, async () => (await ctx.proxy.modes).mouseTrackingMode, 'none'); }); - test.skip('Ps = 1 0 0 1 - Don\'t use Hilite Mouse Tracking, xterm.', async () => { - // TODO: Implement + test('Ps = 1 0 0 1 - Don\'t use Hilite Mouse Tracking, xterm.', async () => { + await assertNoModeChange('\x1b[?1001l'); }); test('Ps = 1 0 0 2 - Don\'t use Cell Motion Mouse Tracking, xterm. See the section Button-event tracking.', async () => { await ctx.proxy.write('\x1b[?1002h'); @@ -913,8 +914,11 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.write('\x1b[?1004l'); await pollFor(ctx.page, async () => (await ctx.proxy.modes).sendFocusMode, false); }); - test.skip('Ps = 1 0 0 5 - Disable UTF-8 Mouse Mode, xterm.', async () => { - // TODO: Implement + test('Ps = 1 0 0 5 - Disable UTF-8 Mouse Mode, xterm.', async () => { + await ctx.proxy.write('\x1b[?1006h'); + await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR'); + await ctx.proxy.write('\x1b[?1005l'); + await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR'); }); test('Ps = 1 0 0 6 - Disable SGR Mouse Mode, xterm.', async () => { await ctx.proxy.write('\x1b[?1006h'); @@ -922,17 +926,20 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.write('\x1b[?1006l'); await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'DEFAULT'); }); - test.skip('Ps = 1 0 0 7 - Disable Alternate Scroll Mode, xterm. This corresponds to the alternateScroll resource.', async () => { - // TODO: Implement + test('Ps = 1 0 0 7 - Disable Alternate Scroll Mode, xterm. This corresponds to the alternateScroll resource.', async () => { + await assertNoModeChange('\x1b[?1007l'); }); - test.skip('Ps = 1 0 1 0 - Don\'t scroll to bottom on tty output (rxvt). This sets the scrollTtyOutput resource to "false".', async () => { - // TODO: Implement + test('Ps = 1 0 1 0 - Don\'t scroll to bottom on tty output (rxvt). This sets the scrollTtyOutput resource to "false".', async () => { + await assertNoModeChange('\x1b[?1010l'); }); - test.skip('Ps = 1 0 1 1 - Don\'t scroll to bottom on key press (rxvt). This sets the scrollKey resource to "false".', async () => { - // TODO: Implement + test('Ps = 1 0 1 1 - Don\'t scroll to bottom on key press (rxvt). This sets the scrollKey resource to "false".', async () => { + await assertNoModeChange('\x1b[?1011l'); }); - test.skip('Ps = 1 0 1 5 - Disable urxvt Mouse Mode.', async () => { - // TODO: Implement + test('Ps = 1 0 1 5 - Disable urxvt Mouse Mode.', async () => { + await ctx.proxy.write('\x1b[?1006h'); + await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR'); + await ctx.proxy.write('\x1b[?1015l'); + await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR'); }); test('Ps = 1 0 1 6 - Disable SGR Mouse Pixel-Mode, xterm.', async () => { await ctx.proxy.write('\x1b[?1016h'); @@ -940,38 +947,38 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.write('\x1b[?1016l'); await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'DEFAULT'); }); - test.skip('Ps = 1 0 3 4 - Don\'t interpret "meta" key, xterm. This disables the eightBitInput resource.', async () => { - // TODO: Implement + test('Ps = 1 0 3 4 - Don\'t interpret "meta" key, xterm. This disables the eightBitInput resource.', async () => { + await assertNoModeChange('\x1b[?1034l'); }); - test.skip('Ps = 1 0 3 5 - Disable special modifiers for Alt and NumLock keys, xterm. This disables the numLock resource.', async () => { - // TODO: Implement + test('Ps = 1 0 3 5 - Disable special modifiers for Alt and NumLock keys, xterm. This disables the numLock resource.', async () => { + await assertNoModeChange('\x1b[?1035l'); }); - test.skip('Ps = 1 0 3 6 - Don\'t send ESC when Meta modifies a key, xterm. This disables the metaSendsEscape resource.', async () => { - // TODO: Implement + test('Ps = 1 0 3 6 - Don\'t send ESC when Meta modifies a key, xterm. This disables the metaSendsEscape resource.', async () => { + await assertNoModeChange('\x1b[?1036l'); }); - test.skip('Ps = 1 0 3 7 - Send VT220 Remove from the editing-keypad Delete key, xterm.', async () => { - // TODO: Implement + test('Ps = 1 0 3 7 - Send VT220 Remove from the editing-keypad Delete key, xterm.', async () => { + await assertNoModeChange('\x1b[?1037l'); }); - test.skip('Ps = 1 0 3 9 - Don\'t send ESC when Alt modifies a key, xterm. This disables the altSendsEscape resource.', async () => { - // TODO: Implement + test('Ps = 1 0 3 9 - Don\'t send ESC when Alt modifies a key, xterm. This disables the altSendsEscape resource.', async () => { + await assertNoModeChange('\x1b[?1039l'); }); - test.skip('Ps = 1 0 4 0 - Do not keep selection when not highlighted, xterm. This disables the keepSelection resource.', async () => { - // TODO: Implement + test('Ps = 1 0 4 0 - Do not keep selection when not highlighted, xterm. This disables the keepSelection resource.', async () => { + await assertNoModeChange('\x1b[?1040l'); }); - test.skip('Ps = 1 0 4 1 - Use the PRIMARY selection, xterm. This disables the selectToClipboard resource.', async () => { - // TODO: Implement + test('Ps = 1 0 4 1 - Use the PRIMARY selection, xterm. This disables the selectToClipboard resource.', async () => { + await assertNoModeChange('\x1b[?1041l'); }); - test.skip('Ps = 1 0 4 2 - Disable Urgency window manager hint when Control-G is received, xterm. This disables the bellIsUrgent resource.', async () => { - // TODO: Implement + test('Ps = 1 0 4 2 - Disable Urgency window manager hint when Control-G is received, xterm. This disables the bellIsUrgent resource.', async () => { + await assertNoModeChange('\x1b[?1042l'); }); - test.skip('Ps = 1 0 4 3 - Disable raising of the window when Control- G is received, xterm. This disables the popOnBell resource.', async () => { - // TODO: Implement + test('Ps = 1 0 4 3 - Disable raising of the window when Control- G is received, xterm. This disables the popOnBell resource.', async () => { + await assertNoModeChange('\x1b[?1043l'); }); - test.skip('Ps = 1 0 4 5 - No Extended Reverse-wraparound mode (XTREVWRAP2), xterm.', async () => { - // TODO: Implement + test('Ps = 1 0 4 5 - No Extended Reverse-wraparound mode (XTREVWRAP2), xterm.', async () => { + await assertNoModeChange('\x1b[?1045l'); }); - test.skip('Ps = 1 0 4 6 - Disable switching to/from Alternate Screen Buffer, xterm. This works for terminfo-based systems, updating the titeInhibit resource. If currently using the Alternate Screen Buffer, xterm switches to the Normal Screen Buffer.', async () => { - // TODO: Implement + test('Ps = 1 0 4 6 - Disable switching to/from Alternate Screen Buffer, xterm. This works for terminfo-based systems, updating the titeInhibit resource. If currently using the Alternate Screen Buffer, xterm switches to the Normal Screen Buffer.', async () => { + await assertNoModeChange('\x1b[?1046l'); }); test('Ps = 1 0 4 7 - Use Normal Screen Buffer, xterm. Clear the screen first if in the Alternate Screen Buffer. This may be disabled by the titeInhibit resource.', async () => { await ctx.proxy.write('\x1b[?1047h'); @@ -996,32 +1003,32 @@ test.describe('InputHandler Integration Tests', () => { await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'normal'); await pollFor(ctx.page, () => getCursor(), { col: 3, row: 2 }); }); - test.skip('Ps = 1 0 5 0 - Reset terminfo/termcap function-key mode, xterm.', async () => { - // TODO: Implement + test('Ps = 1 0 5 0 - Reset terminfo/termcap function-key mode, xterm.', async () => { + await assertNoModeChange('\x1b[?1050l'); }); - test.skip('Ps = 1 0 5 1 - Reset Sun function-key mode, xterm.', async () => { - // TODO: Implement + test('Ps = 1 0 5 1 - Reset Sun function-key mode, xterm.', async () => { + await assertNoModeChange('\x1b[?1051l'); }); - test.skip('Ps = 1 0 5 2 - Reset HP function-key mode, xterm.', async () => { - // TODO: Implement + test('Ps = 1 0 5 2 - Reset HP function-key mode, xterm.', async () => { + await assertNoModeChange('\x1b[?1052l'); }); - test.skip('Ps = 1 0 5 3 - Reset SCO function-key mode, xterm.', async () => { - // TODO: Implement + test('Ps = 1 0 5 3 - Reset SCO function-key mode, xterm.', async () => { + await assertNoModeChange('\x1b[?1053l'); }); - test.skip('Ps = 1 0 6 0 - Reset legacy keyboard emulation, i.e, X11R6, xterm.', async () => { - // TODO: Implement + test('Ps = 1 0 6 0 - Reset legacy keyboard emulation, i.e, X11R6, xterm.', async () => { + await assertNoModeChange('\x1b[?1060l'); }); - test.skip('Ps = 1 0 6 1 - Reset keyboard emulation to Sun/PC style, xterm.', async () => { - // TODO: Implement + test('Ps = 1 0 6 1 - Reset keyboard emulation to Sun/PC style, xterm.', async () => { + await assertNoModeChange('\x1b[?1061l'); }); - test.skip('Ps = 2 0 0 1 - Disable readline mouse button-1, xterm.', async () => { - // TODO: Implement + test('Ps = 2 0 0 1 - Disable readline mouse button-1, xterm.', async () => { + await assertNoModeChange('\x1b[?2001l'); }); - test.skip('Ps = 2 0 0 2 - Disable readline mouse button-2, xterm.', async () => { - // TODO: Implement + test('Ps = 2 0 0 2 - Disable readline mouse button-2, xterm.', async () => { + await assertNoModeChange('\x1b[?2002l'); }); - test.skip('Ps = 2 0 0 3 - Disable readline mouse button-3, xterm.', async () => { - // TODO: Implement + test('Ps = 2 0 0 3 - Disable readline mouse button-3, xterm.', async () => { + await assertNoModeChange('\x1b[?2003l'); }); test('Ps = 2 0 0 4 - Reset bracketed paste mode, xterm.', async () => { await ctx.proxy.write('\x1b[?2004h'); @@ -1029,11 +1036,11 @@ test.describe('InputHandler Integration Tests', () => { await ctx.proxy.write('\x1b[?2004l'); await pollFor(ctx.page, async () => (await ctx.proxy.modes).bracketedPasteMode, false); }); - test.skip('Ps = 2 0 0 5 - Disable readline character-quoting, xterm.', async () => { - // TODO: Implement + test('Ps = 2 0 0 5 - Disable readline character-quoting, xterm.', async () => { + await assertNoModeChange('\x1b[?2005l'); }); - test.skip('Ps = 2 0 0 6 - Disable readline newline pasting, xterm.', async () => { - // TODO: Implement + test('Ps = 2 0 0 6 - Disable readline newline pasting, xterm.', async () => { + await assertNoModeChange('\x1b[?2006l'); }); }); test.describe('CSI Pm m - SGR: Character Attributes', () => { @@ -1835,6 +1842,25 @@ test.describe('InputHandler Integration Tests', () => { }); +async function getModeSnapshot(): Promise { + return { + modes: await ctx.proxy.modes, + cursorBlink: await ctx.proxy.getOption('cursorBlink'), + cols: await ctx.proxy.cols, + rows: await ctx.proxy.rows, + bufferType: await ctx.proxy.buffer.active.type, + mouseProtocol: await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeProtocol), + mouseEncoding: await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding) + }; +} + +async function assertNoModeChange(sequence: string): Promise { + const before = await getModeSnapshot(); + await ctx.proxy.write(sequence); + deepStrictEqual(await getModeSnapshot(), before); +} + + async function getLinesAsArray(count: number, start: number = 0): Promise { let text = ''; for (let i = start; i < start + count; i++) { From 7b417f4585f07df67b369f30c63d811eb869519c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 31 Jan 2026 22:05:33 -0800 Subject: [PATCH 03/17] win32 input mode: Avoid scrolling to bottom on modifier only Fixes #5612 --- src/browser/CoreBrowserTerminal.ts | 13 ++++++++--- src/browser/Terminal.test.ts | 36 ++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/browser/CoreBrowserTerminal.ts b/src/browser/CoreBrowserTerminal.ts index 51d40224..6e0c7ad1 100644 --- a/src/browser/CoreBrowserTerminal.ts +++ b/src/browser/CoreBrowserTerminal.ts @@ -1156,9 +1156,10 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { this.textarea!.value = ''; } + const wasModifierOnly = this._keyboardService.useWin32InputMode && wasModifierKeyOnlyEvent(event); this._onKey.fire({ key: result.key, domEvent: event }); this._showCursor(); - this.coreService.triggerDataEvent(result.key, true); + this.coreService.triggerDataEvent(result.key, !wasModifierOnly); // Cancel events when not in screen reader mode so events don't get bubbled up and handled by // other listeners. When screen reader mode is enabled, we don't cancel them (unless ctrl or alt @@ -1199,7 +1200,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { // Handle key release for Kitty keyboard protocol const result = this._keyboardService.evaluateKeyUp(ev); if (result?.key) { - this.coreService.triggerDataEvent(result.key, true); + const wasModifierOnly = this._keyboardService.useWin32InputMode && wasModifierKeyOnlyEvent(ev); + this.coreService.triggerDataEvent(result.key, !wasModifierOnly); } this.updateCursorStyle(ev); @@ -1410,5 +1412,10 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { function wasModifierKeyOnlyEvent(ev: KeyboardEvent): boolean { return ev.keyCode === 16 || // Shift ev.keyCode === 17 || // Ctrl - ev.keyCode === 18; // Alt + ev.keyCode === 18 || // Alt + ev.keyCode === 91 || // Meta (Left) + ev.keyCode === 92 || // Meta (Right) + ev.keyCode === 93 || // Meta (Menu) + ev.keyCode === 224 || // Meta (Firefox) + ev.key === 'Meta'; } diff --git a/src/browser/Terminal.test.ts b/src/browser/Terminal.test.ts index 66637e37..ba2ce6dd 100644 --- a/src/browser/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -401,6 +401,42 @@ describe('Terminal', () => { }); }); + describe('keyDown', () => { + it('should not scroll down on modifier-only input in win32 input mode', async () => { + term.options.vtExtensions = { win32InputMode: true }; + term.coreService.decPrivateModes.win32InputMode = true; + (term as any).textarea = { value: '' }; + + await term.writeP('test\r\n'.repeat(term.rows * 3)); + const startYDisp = term.buffer.ydisp; + term.scrollLines(-1); + const scrolledYDisp = term.buffer.ydisp; + assert.equal(scrolledYDisp, startYDisp - 1); + + const evKeyDown = { + type: 'keydown', + key: 'Control', + keyCode: 17, + ctrlKey: true, + preventDefault: () => { }, + stopPropagation: () => { } + } as KeyboardEvent; + + const evKeyUp = { + type: 'keyup', + key: 'Control', + keyCode: 17, + preventDefault: () => { }, + stopPropagation: () => { } + } as KeyboardEvent; + + term.keyDown(evKeyDown); + assert.equal(term.buffer.ydisp, scrolledYDisp); + (term as any)._keyUp(evKeyUp); + assert.equal(term.buffer.ydisp, scrolledYDisp); + }); + }); + describe('scroll() function', () => { describe('when scrollback > 0', () => { it('should create a new line and scroll', () => { From bab7db13dbd1cc559a10632a5108db1a828d7b98 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 31 Jan 2026 22:18:32 -0800 Subject: [PATCH 04/17] Remove cancelEvent/cancel function --- .../client/components/window/optionsWindow.ts | 1 - src/browser/CoreBrowserTerminal.ts | 39 ++++++++----------- src/browser/TestUtils.test.ts | 3 -- src/browser/Types.ts | 2 - src/common/Types.ts | 1 - src/common/services/OptionsService.ts | 1 - src/common/services/Services.ts | 1 - 7 files changed, 17 insertions(+), 31 deletions(-) diff --git a/demo/client/components/window/optionsWindow.ts b/demo/client/components/window/optionsWindow.ts index 1071cfd5..b97204d2 100644 --- a/demo/client/components/window/optionsWindow.ts +++ b/demo/client/components/window/optionsWindow.ts @@ -109,7 +109,6 @@ export class OptionsWindow extends BaseWindow implements IControlWindow { public initOptions(addDomListener: (el: HTMLElement, type: string, handler: (...args: any[]) => any) => void): void { const blacklistedOptions = [ - 'cancelEvents', 'convertEol', 'termName', 'cols', 'rows', diff --git a/src/browser/CoreBrowserTerminal.ts b/src/browser/CoreBrowserTerminal.ts index 51d40224..ffc966f1 100644 --- a/src/browser/CoreBrowserTerminal.ts +++ b/src/browser/CoreBrowserTerminal.ts @@ -763,11 +763,12 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { this._document!.removeEventListener('mousemove', requestedEvents.mousedrag); } } - return this.cancel(ev); }, wheel: (ev: WheelEvent) => { sendEvent(ev); - return this.cancel(ev, true); + ev.preventDefault(); + ev.stopPropagation(); + return false; }, mousedrag: (ev: MouseEvent) => { // deal only with move while a button is held @@ -867,8 +868,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { if (requestedEvents.mousedrag) { this._document!.addEventListener('mousemove', requestedEvents.mousedrag); } - - return this.cancel(ev); })); this._register(addDisposableListener(el, 'wheel', (ev: WheelEvent) => { @@ -899,13 +898,17 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { self._coreBrowserService?.dpr ); if (lines === 0) { - return this.cancel(ev, true); + ev.preventDefault(); + ev.stopPropagation(); + return false; } // Construct and send sequences const sequence = C0.ESC + (this.coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B'); this.coreService.triggerDataEvent(sequence, true); - return this.cancel(ev, true); + ev.preventDefault(); + ev.stopPropagation(); + return false; } }, { passive: false })); } @@ -1115,7 +1118,9 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { if (result.type === KeyboardResultType.PAGE_DOWN || result.type === KeyboardResultType.PAGE_UP) { const scrollCount = this.rows - 1; this.scrollLines(result.type === KeyboardResultType.PAGE_UP ? -scrollCount : scrollCount); - return this.cancel(event, true); + event.preventDefault(); + event.stopPropagation(); + return false; } if (result.type === KeyboardResultType.SELECT_ALL) { @@ -1128,7 +1133,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { if (result.cancel) { // The event is canceled at the end already, is this necessary? - this.cancel(event, true); + event.preventDefault(); + event.stopPropagation(); } if (!result.key) { @@ -1165,7 +1171,9 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { // is also depressed) so that the cursor textarea can be updated, which triggers the screen // reader to read it. if (!this.optionsService.rawOptions.screenReaderMode || event.altKey || event.ctrlKey) { - return this.cancel(event, true); + event.preventDefault(); + event.stopPropagation(); + return false; } this._keyDownHandled = true; @@ -1225,8 +1233,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { return false; } - this.cancel(ev); - if (ev.charCode) { key = ev.charCode; } else if (ev.which === null || ev.which === undefined) { @@ -1279,8 +1285,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { const text = ev.data; this.coreService.triggerDataEvent(text, true); - - this.cancel(ev); return true; } @@ -1392,15 +1396,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { } } - // TODO: Remove cancel function and cancelEvents option - public cancel(ev: MouseEvent | WheelEvent | KeyboardEvent | InputEvent, force?: boolean): boolean | undefined { - if (!this.options.cancelEvents && !force) { - return; - } - ev.preventDefault(); - ev.stopPropagation(); - return false; - } } /** diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index fe71d5b0..c46ee0fb 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -198,9 +198,6 @@ export class MockTerminal implements ITerminal { public scrollToRow(absoluteRow: number): number { throw new Error('Method not implemented.'); } - public cancel(ev: MouseEvent | WheelEvent | KeyboardEvent | InputEvent, force?: boolean): void { - throw new Error('Method not implemented.'); - } public log(text: string): void { throw new Error('Method not implemented.'); } diff --git a/src/browser/Types.ts b/src/browser/Types.ts index f54e488c..fa08de2e 100644 --- a/src/browser/Types.ts +++ b/src/browser/Types.ts @@ -29,8 +29,6 @@ export interface ITerminal extends InternalPassthroughApis, ICoreTerminal { onA11yChar: IEvent; onA11yTab: IEvent; onWillOpen: IEvent; - - cancel(ev: MouseEvent | WheelEvent | KeyboardEvent | InputEvent, force?: boolean): boolean | void; } export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; diff --git a/src/common/Types.ts b/src/common/Types.ts index 5d7c1b18..a46f47b2 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -32,7 +32,6 @@ export interface IDisposable { // TODO: The options that are not in the public API should be reviewed export interface ITerminalOptions extends IPublicTerminalOptions { [key: string]: any; - cancelEvents?: boolean; convertEol?: boolean; termName?: string; } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 2eab930f..7ddc1c06 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -53,7 +53,6 @@ export const DEFAULT_OPTIONS: Readonly> = { altClickMovesCursor: true, convertEol: false, termName: 'xterm', - cancelEvents: false, overviewRuler: {}, quirks: {}, vtExtensions: {} diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 341c0bc2..9ef0fe9a 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -270,7 +270,6 @@ export interface ITerminalOptions { vtExtensions?: IVtExtensions; [key: string]: any; - cancelEvents: boolean; termName: string; } From 7cba3c9fa134404906a0c152dc732707d528982e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 31 Jan 2026 22:26:06 -0800 Subject: [PATCH 05/17] Correct test copilot instructions --- .github/copilot-instructions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 461f8fdb..c9954ed7 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -22,10 +22,10 @@ npm run build && npm run esbuild # Build all TypeScript and bundle **Testing**: - Unit tests: `npm run test-unit` (Mocha) - Unit tests filtering to file: `npm run test-unit -- **/fileName.ts -- Per-addon unit tests: `npm run test-unit addons/addon-image/out-esbuild/*.test.js` +- Per-addon unit tests: `npm run test-unit -- addons/addon-image/out-esbuild/*.test.js` - Integration tests: `npm run test-integration` (Playwright across Chrome/Firefox/WebKit) - Integration tests by file: `npm run test-integration -- test/playwright/InputHandler.test.ts`. Never use grep to filter tests, it doesn't work -- Integration tests by addon: `npm run test-integration --suite=addon-search`. Suites always follow the format `addon-` +- Integration tests by addon: `npm run test-integration -- --suite=addon-search`. Suites always follow the format `addon-` - Lint changes: `npm run lint-changes` to lint only changed files, `npm run lint-changes-fix` to fix them ## Addon Development Pattern From a27b8feb8f778b57a8aaed57bdfa0bbab1553e8a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 31 Jan 2026 22:56:48 -0800 Subject: [PATCH 06/17] Fix demo button outline --- demo/index.css | 1 + 1 file changed, 1 insertion(+) diff --git a/demo/index.css b/demo/index.css index b3123eb2..6880bde4 100644 --- a/demo/index.css +++ b/demo/index.css @@ -33,6 +33,7 @@ body { padding: 4px 10px; cursor: pointer; font-size: 12px; + outline-offset: -1px; } .banner-tabs button:hover { background: rgba(255,255,255,0.1); From a5e4f90139d5e575383b353005f5cfeb6b8cac62 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 1 Feb 2026 05:58:56 -0800 Subject: [PATCH 07/17] Remove unreachable code in SGR handling 100 The implementation seems to be wrong. This looks like a remnant left over from term.js before bright SGR was implemented. --- src/common/InputHandler.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 11381ce0..f5c2a9c6 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -2713,12 +2713,6 @@ export class InputHandler extends Disposable implements IInputHandler { attr.extended = attr.extended.clone(); attr.extended.underlineColor = -1; attr.updateExtended(); - } else if (p === 100) { // FIXME: dead branch, p=100 already handled above! - // reset fg/bg - attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK); - attr.fg |= DEFAULT_ATTR_DATA.fg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK); - attr.bg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK); - attr.bg |= DEFAULT_ATTR_DATA.bg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK); } else { this._logService.debug('Unknown SGR attribute: %d.', p); } From 6525a7e68e45c8019575cb190bc255fb449a4afa Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 1 Feb 2026 07:15:25 -0800 Subject: [PATCH 08/17] Fix copying array data in IIPHandler Fixes #5660 --- addons/addon-image/src/IIPHandler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/addon-image/src/IIPHandler.ts b/addons/addon-image/src/IIPHandler.ts index bebfee11..4922857a 100644 --- a/addons/addon-image/src/IIPHandler.ts +++ b/addons/addon-image/src/IIPHandler.ts @@ -105,7 +105,8 @@ export class IIPHandler implements IOscHandler, IResetHandler { return true; } - const blob = new Blob([new Uint8Array(this._dec.data8)], { type: this._metrics.mime }); + // HACK: The types on Blob are too restrictive, this is a Uint8Array so the browser accepts it + const blob = new Blob([this._dec.data8 as Uint8Array], { type: this._metrics.mime }); this._dec.release(); if (!window.createImageBitmap) { From 5316cb081219356a773835c6c3cb1790327a8f36 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 1 Feb 2026 07:48:40 -0800 Subject: [PATCH 09/17] Remove some easy any usages --- addons/addon-image/src/IIPHandler.ts | 4 ---- addons/addon-image/src/IIPHeaderParser.ts | 4 ++-- addons/addon-image/src/ImageStorage.ts | 6 ++++-- addons/addon-ligatures/src/font.ts | 16 ++++++++++------ addons/addon-ligatures/src/index.ts | 2 +- addons/addon-search/src/SearchAddon.ts | 4 ++-- .../src/third-party/UnicodeProperties.ts | 2 +- src/common/parser/EscapeSequenceParser.ts | 3 +-- 8 files changed, 21 insertions(+), 20 deletions(-) diff --git a/addons/addon-image/src/IIPHandler.ts b/addons/addon-image/src/IIPHandler.ts index bebfee11..af6a681a 100644 --- a/addons/addon-image/src/IIPHandler.ts +++ b/addons/addon-image/src/IIPHandler.ts @@ -9,10 +9,6 @@ import Base64Decoder from 'xterm-wasm-parts/lib/base64/Base64Decoder.wasm'; import { HeaderParser, IHeaderFields, HeaderState } from './IIPHeaderParser'; import { imageType, UNSUPPORTED_TYPE } from './IIPMetrics'; - -// eslint-disable-next-line -declare const Buffer: any; - // limit hold memory in base64 decoder const KEEP_DATA = 4194304; diff --git a/addons/addon-image/src/IIPHeaderParser.ts b/addons/addon-image/src/IIPHeaderParser.ts index dd872fed..53c1edf3 100644 --- a/addons/addon-image/src/IIPHeaderParser.ts +++ b/addons/addon-image/src/IIPHeaderParser.ts @@ -81,7 +81,7 @@ function toName(data: Uint32Array): string { return new TextDecoder().decode(b); } -const DECODERS: {[key: string]: (v: Uint32Array) => any} = { +const DECODERS: {[key: string]: (v: Uint32Array) => number | string} = { inline: toInt, size: toInt, name: toName, @@ -100,7 +100,7 @@ export class HeaderParser { private _buffer = new Uint32Array(MAX_FIELDCHARS); private _position = 0; private _key = ''; - public fields: {[key: string]: any} = {}; + public fields: {[key: string]: number | string | Uint32Array | null} = {}; public reset(): void { this._buffer.fill(0); diff --git a/addons/addon-image/src/ImageStorage.ts b/addons/addon-image/src/ImageStorage.ts index 059849ea..aea9b5e5 100644 --- a/addons/addon-image/src/ImageStorage.ts +++ b/addons/addon-image/src/ImageStorage.ts @@ -132,8 +132,10 @@ export class ImageStorage implements IDisposable { ) { try { this.setLimit(this._opts.storageLimit); - } catch (e: any) { - console.error(e.message); + } catch (e: unknown) { + if (e instanceof Error) { + console.error(e.message); + } console.warn(`storageLimit is set to ${this.getLimit()} MB`); } this._viewportMetrics = { diff --git a/addons/addon-ligatures/src/font.ts b/addons/addon-ligatures/src/font.ts index 1316932a..60ab3d74 100644 --- a/addons/addon-ligatures/src/font.ts +++ b/addons/addon-ligatures/src/font.ts @@ -42,11 +42,11 @@ export default async function load(fontFamily: string, cacheSize: number): Promi if (status && status.state !== 'granted') { throw new Error('Permission to access local fonts not granted.'); } - } catch (err: any) { + } catch (err: unknown) { // A `TypeError` indicates the 'local-fonts' // permission is not yet implemented, so // only `throw` if this is _not_ the problem. - if (err.name !== 'TypeError') { + if (err instanceof Error && err.name !== 'TypeError') { throw err; } } @@ -60,8 +60,10 @@ export default async function load(fontFamily: string, cacheSize: number): Promi fonts[metadata.family].push(metadata); } fontsPromise = Promise.resolve(fonts); - } catch (err: any) { - console.error(err.name, err.message); + } catch (err: unknown) { + if (err instanceof Error) { + console.error(err.name, err.message); + } } } // Latest proposal https://bugs.chromium.org/p/chromium/issues/detail?id=1312603 @@ -76,8 +78,10 @@ export default async function load(fontFamily: string, cacheSize: number): Promi fonts[metadata.family].push(metadata); } fontsPromise = Promise.resolve(fonts); - } catch (err: any) { - console.error(err.name, err.message); + } catch (err: unknown) { + if (err instanceof Error) { + console.error(err.name, err.message); + } } } fontsPromise ??= Promise.resolve({}); diff --git a/addons/addon-ligatures/src/index.ts b/addons/addon-ligatures/src/index.ts index 0c67f510..fb8e85e7 100644 --- a/addons/addon-ligatures/src/index.ts +++ b/addons/addon-ligatures/src/index.ts @@ -30,7 +30,7 @@ export function enableLigatures(term: Terminal, fallbackLigatures: string[] = [] let currentFontName: string | undefined = undefined; let font: Font | undefined = undefined; let loadingState: LoadingState = LoadingState.UNLOADED; - let loadError: any | undefined = undefined; + let loadError: unknown = undefined; return term.registerCharacterJoiner((text: string): [number, number][] => { // If the font hasn't been loaded yet, load it and return an empty result diff --git a/addons/addon-search/src/SearchAddon.ts b/addons/addon-search/src/SearchAddon.ts index ffbe9401..8e647a9e 100644 --- a/addons/addon-search/src/SearchAddon.ts +++ b/addons/addon-search/src/SearchAddon.ts @@ -4,7 +4,7 @@ */ import type { Terminal, IDisposable, ITerminalAddon } from '@xterm/xterm'; -import type { SearchAddon as ISearchApi, ISearchOptions, ISearchAddonOptions, ISearchResultChangeEvent } from '@xterm/addon-search'; +import type { SearchAddon as ISearchApi, ISearchOptions, ISearchAddonOptions, ISearchResultChangeEvent, ISearchDecorationOptions } from '@xterm/addon-search'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { disposableTimeout } from 'vs/base/common/async'; @@ -222,7 +222,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon, ISearchAp * @param result The result to select. * @returns Whether a result was selected. */ - private _selectResult(result: ISearchResult | undefined, options?: any, noScroll?: boolean): boolean { + private _selectResult(result: ISearchResult | undefined, options?: ISearchDecorationOptions, noScroll?: boolean): boolean { if (!this._terminal || !this._decorationManager) { return false; } diff --git a/addons/addon-unicode-graphemes/src/third-party/UnicodeProperties.ts b/addons/addon-unicode-graphemes/src/third-party/UnicodeProperties.ts index 0ee147f8..e0af5ce8 100644 --- a/addons/addon-unicode-graphemes/src/third-party/UnicodeProperties.ts +++ b/addons/addon-unicode-graphemes/src/third-party/UnicodeProperties.ts @@ -1,7 +1,7 @@ import UnicodeTrie from './unicode-trie'; const trieRaw = "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"; -declare const Buffer: any; +declare const Buffer: { from(s: string, encoding: string): Uint8Array } | undefined; function _dec(s: string): Uint8Array { if (typeof Buffer !== 'undefined') return Buffer.from(s, 'base64'); const bs = atob(s); diff --git a/src/common/parser/EscapeSequenceParser.ts b/src/common/parser/EscapeSequenceParser.ts index bb99efe1..febb4878 100644 --- a/src/common/parser/EscapeSequenceParser.ts +++ b/src/common/parser/EscapeSequenceParser.ts @@ -91,14 +91,13 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable { EXECUTABLES.push.apply(EXECUTABLES, r(0x1c, 0x20)); const states: number[] = r(ParserState.GROUND, ParserState.STATE_LENGTH); - let state: any; // set default transition table.setDefault(ParserAction.ERROR, ParserState.GROUND); // printables table.addMany(PRINTABLES, ParserState.GROUND, ParserAction.PRINT, ParserState.GROUND); // global anywhere rules - for (state in states) { + for (const state of states) { table.addMany([0x18, 0x1a, 0x99, 0x9a], state, ParserAction.EXECUTE, ParserState.GROUND); table.addMany(r(0x80, 0x90), state, ParserAction.EXECUTE, ParserState.GROUND); table.addMany(r(0x90, 0x98), state, ParserAction.EXECUTE, ParserState.GROUND); From 297ac838a376c9a82654a6dd23f82de7c4c23eda Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 1 Feb 2026 07:58:36 -0800 Subject: [PATCH 10/17] Fix build --- addons/addon-image/src/IIPHeaderParser.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/addon-image/src/IIPHeaderParser.ts b/addons/addon-image/src/IIPHeaderParser.ts index 53c1edf3..af4e9a68 100644 --- a/addons/addon-image/src/IIPHeaderParser.ts +++ b/addons/addon-image/src/IIPHeaderParser.ts @@ -8,6 +8,7 @@ declare const Buffer: any; export interface IHeaderFields { + [key: string]: number | string | Uint32Array | null | undefined; // base-64 encoded filename. Defaults to "Unnamed file". name: string; // File size in bytes. The file transfer will be canceled if this size is exceeded. @@ -100,7 +101,7 @@ export class HeaderParser { private _buffer = new Uint32Array(MAX_FIELDCHARS); private _position = 0; private _key = ''; - public fields: {[key: string]: number | string | Uint32Array | null} = {}; + public fields: {[key: string]: number | string | Uint32Array | null | undefined} = {}; public reset(): void { this._buffer.fill(0); From 53efc951700dd2cdccc7dc9839632aa484da5b71 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 1 Feb 2026 08:26:20 -0800 Subject: [PATCH 11/17] Move remaining addon test buttons into their own windows --- demo/client/client.ts | 33 ++- .../components/window/addonImageWindow.ts | 104 ++++++++ .../components/window/addonLigaturesWindow.ts | 44 ++++ .../components/window/addonProgressWindow.ts | 135 ++++++++++ .../components/window/addonWebLinksWindow.ts | 52 ++++ demo/client/components/window/testWindow.ts | 238 ------------------ 6 files changed, 362 insertions(+), 244 deletions(-) create mode 100644 demo/client/components/window/addonLigaturesWindow.ts create mode 100644 demo/client/components/window/addonProgressWindow.ts create mode 100644 demo/client/components/window/addonWebLinksWindow.ts diff --git a/demo/client/client.ts b/demo/client/client.ts index 45bdd1d4..43fa1413 100644 --- a/demo/client/client.ts +++ b/demo/client/client.ts @@ -16,9 +16,12 @@ if ('WebAssembly' in window) { import { Terminal, ITerminalOptions, type ITheme } from '@xterm/xterm'; import { AttachAddon } from '@xterm/addon-attach'; import { AddonImageWindow } from './components/window/addonImageWindow'; +import { AddonLigaturesWindow } from './components/window/addonLigaturesWindow'; +import { AddonProgressWindow } from './components/window/addonProgressWindow'; import { AddonSearchWindow } from './components/window/addonSearchWindow'; import { AddonSerializeWindow } from './components/window/addonSerializeWindow'; import { AddonWebFontsWindow } from './components/window/addonWebFontsWindow'; +import { AddonWebLinksWindow } from './components/window/addonWebLinksWindow'; import { AddonsWindow } from './components/window/addonsWindow'; import { CellInspectorWindow } from './components/window/cellInspectorWindow'; import { ControlBar } from './components/controlBar'; @@ -213,11 +216,14 @@ if (document.location.pathname === '/test') { controlBar.registerWindow(new CellInspectorWindow(typedTerm, addons)); controlBar.registerWindow(new VtWindow(typedTerm, addons)); addonsWindow = controlBar.registerWindow(new AddonsWindow(typedTerm, addons)); - addonSearchWindow = controlBar.registerWindow(new AddonSearchWindow(typedTerm, addons), { afterId: 'addons', hidden: true, italics: true }); + controlBar.registerWindow(new AddonImageWindow(typedTerm, addons), { afterId: 'addons', hidden: true, italics: true }); + controlBar.registerWindow(new AddonLigaturesWindow(typedTerm, addons), { afterId: 'addon-image', hidden: true, italics: true }); + controlBar.registerWindow(new AddonProgressWindow(typedTerm, addons), { afterId: 'addon-ligatures', hidden: true, italics: true }); + addonSearchWindow = controlBar.registerWindow(new AddonSearchWindow(typedTerm, addons), { afterId: 'addon-progress', hidden: true, italics: true }); controlBar.registerWindow(new AddonSerializeWindow(typedTerm, addons), { afterId: 'addon-search', hidden: true, italics: true }); - controlBar.registerWindow(new AddonImageWindow(typedTerm, addons), { afterId: 'addon-serialize', hidden: true, italics: true }); - controlBar.registerWindow(new AddonWebFontsWindow(typedTerm, addons), { afterId: 'addon-image', hidden: true, italics: true }); - addonWebglWindow = controlBar.registerWindow(new WebglWindow(typedTerm, addons), { afterId: 'addon-web-fonts', hidden: true, italics: true }); + controlBar.registerWindow(new AddonWebFontsWindow(typedTerm, addons), { afterId: 'addon-serialize', hidden: true, italics: true }); + controlBar.registerWindow(new AddonWebLinksWindow(typedTerm, addons), { afterId: 'addon-web-fonts', hidden: true, italics: true }); + addonWebglWindow = controlBar.registerWindow(new WebglWindow(typedTerm, addons), { afterId: 'addon-web-links', hidden: true, italics: true }); controlBar.registerWindow(new TestWindow(typedTerm, addons, { disposeRecreateButtonHandler, createNewWindowButtonHandler }), { afterId: 'options' }); actionElements = { findNext: addonSearchWindow.findNextInput, @@ -229,11 +235,14 @@ if (document.location.pathname === '/test') { // TODO: Most of below should be encapsulated within windows paddingElement = styleWindow.paddingElement; - controlBar.setTabVisible('addon-webgl', true); + controlBar.setTabVisible('addon-image', !!addons.image.instance); + controlBar.setTabVisible('addon-ligatures', !!addons.ligatures.instance); + controlBar.setTabVisible('addon-progress', !!addons.progress.instance); controlBar.setTabVisible('addon-search', true); controlBar.setTabVisible('addon-serialize', true); - controlBar.setTabVisible('addon-image', true); controlBar.setTabVisible('addon-web-fonts', true); + controlBar.setTabVisible('addon-web-links', !!addons.webLinks.instance); + controlBar.setTabVisible('addon-webgl', true); addonWebglWindow.setTextureAtlas(addons.webgl.instance!.textureAtlas!); addons.webgl.instance!.onChangeTextureAtlas(e => addonWebglWindow.setTextureAtlas(e)); addons.webgl.instance!.onAddTextureAtlasCanvas(e => addonWebglWindow.appendTextureAtlas(e)); @@ -500,6 +509,12 @@ function initAddons(term: Terminal): void { addons[name].instance!.onDidChangeResults(e => updateFindResults(e)); } else if (name === 'serialize') { controlBar.setTabVisible('addon-serialize', true); + } else if (name === 'ligatures') { + controlBar.setTabVisible('addon-ligatures', true); + } else if (name === 'progress') { + controlBar.setTabVisible('addon-progress', true); + } else if (name === 'webLinks') { + controlBar.setTabVisible('addon-web-links', true); } } catch { @@ -516,6 +531,12 @@ function initAddons(term: Terminal): void { controlBar.setTabVisible('addon-search', false); } else if (name === 'serialize') { controlBar.setTabVisible('addon-serialize', false); + } else if (name === 'ligatures') { + controlBar.setTabVisible('addon-ligatures', false); + } else if (name === 'progress') { + controlBar.setTabVisible('addon-progress', false); + } else if (name === 'webLinks') { + controlBar.setTabVisible('addon-web-links', false); } addon.instance!.dispose(); addon.instance = undefined; diff --git a/demo/client/components/window/addonImageWindow.ts b/demo/client/components/window/addonImageWindow.ts index 87ebaff7..ce5ab9de 100644 --- a/demo/client/components/window/addonImageWindow.ts +++ b/demo/client/components/window/addonImageWindow.ts @@ -5,6 +5,7 @@ import { BaseWindow } from './baseWindow'; import type { IControlWindow } from '../controlBar'; +import type { IImageAddonOptions } from '@xterm/addon-image'; export class AddonImageWindow extends BaseWindow implements IControlWindow { public readonly id = 'addon-image'; @@ -46,6 +47,20 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow { this._imageOptionsTextarea.rows = 12; optionsLabel.appendChild(this._imageOptionsTextarea); container.appendChild(optionsLabel); + + container.appendChild(document.createElement('br')); + container.appendChild(document.createElement('br')); + + const dl = document.createElement('dl'); + const dt = document.createElement('dt'); + dt.textContent = 'Image Test'; + dl.appendChild(dt); + this._addDdWithButton(dl, 'image-demo1', 'snake (sixel)'); + this._addDdWithButton(dl, 'image-demo2', 'oranges (sixel)'); + this._addDdWithButton(dl, 'image-demo3', 'palette (iip)'); + container.appendChild(dl); + + this._initImageAddonExposed(); } public get imageStorageLimitInput(): HTMLInputElement { @@ -59,4 +74,93 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow { public get imageOptionsTextarea(): HTMLTextAreaElement { return this._imageOptionsTextarea; } + + private _addDdWithButton(dl: HTMLElement, id: string, label: string): void { + const dd = document.createElement('dd'); + const button = document.createElement('button'); + button.id = id; + button.textContent = label; + dd.appendChild(button); + dl.appendChild(dd); + } + + private _initImageAddonExposed(): void { + const imageAddon = this._addons.image.instance!; + const defaultOptions: IImageAddonOptions = (imageAddon as any)._defaultOpts; + const limitStorageElement = document.querySelector('#image-storagelimit')!; + limitStorageElement.valueAsNumber = imageAddon.storageLimit; + this._addDomListener(limitStorageElement, 'change', () => { + try { + imageAddon.storageLimit = limitStorageElement.valueAsNumber; + limitStorageElement.valueAsNumber = imageAddon.storageLimit; + console.log('changed storageLimit to', imageAddon.storageLimit); + } catch (e) { + limitStorageElement.valueAsNumber = imageAddon.storageLimit; + console.log('storageLimit at', imageAddon.storageLimit); + throw e; + } + }); + const showPlaceholderElement = document.querySelector('#image-showplaceholder')!; + showPlaceholderElement.checked = imageAddon.showPlaceholder; + this._addDomListener(showPlaceholderElement, 'change', () => { + imageAddon.showPlaceholder = showPlaceholderElement.checked; + }); + const ctorOptionsElement = document.querySelector('#image-options')!; + ctorOptionsElement.value = JSON.stringify(defaultOptions, null, 2); + + const sixelDemo = (url: string) => () => fetch(url) + .then(resp => resp.arrayBuffer()) + .then(buffer => { + this._terminal.write('\r\n'); + this._terminal.write(new Uint8Array(buffer)); + }); + + const iipDemo = (url: string) => () => fetch(url) + .then(resp => resp.arrayBuffer()) + .then(buffer => { + const data = new Uint8Array(buffer); + let sdata = ''; + for (let i = 0; i < data.length; ++i) sdata += String.fromCharCode(data[i]); + this._terminal.write('\r\n'); + this._terminal.write(`\x1b]1337;File=inline=1;size=${data.length}:${btoa(sdata)}\x1b\\`); + }); + + document.getElementById('image-demo1')!.addEventListener('click', + sixelDemo('https://raw.githubusercontent.com/saitoha/libsixel/master/images/snake.six')); + document.getElementById('image-demo2')!.addEventListener('click', + sixelDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/testfiles/test2.sixel')); + document.getElementById('image-demo3')!.addEventListener('click', + iipDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png')); + + // demo for image retrieval API + this._terminal.element!.addEventListener('click', (ev: MouseEvent) => { + if (!ev.ctrlKey || !imageAddon) return; + + // TODO... + // if (ev.altKey) { + // const sel = term.getSelectionPosition(); + // if (sel) { + // addons.image.instance + // .extractCanvasAtBufferRange(term.getSelectionPosition()) + // ?.toBlob(data => window.open(URL.createObjectURL(data), '_blank')); + // return; + // } + // } + + const pos = (this._terminal as any)._core._mouseService!.getCoords(ev, (this._terminal as any)._core.screenElement!, this._terminal.cols, this._terminal.rows); + const x = pos[0] - 1; + const y = pos[1] - 1; + const canvas = ev.shiftKey + // ctrl+shift+click: get single tile + ? imageAddon.extractTileAtBufferCell(x, this._terminal.buffer.active.viewportY + y) + // ctrl+click: get original image + : imageAddon.getImageAtBufferCell(x, this._terminal.buffer.active.viewportY + y); + canvas?.toBlob(data => data && window.open(URL.createObjectURL(data), '_blank')); + }); + } + + private _addDomListener(element: HTMLElement, type: string, handler: (...args: any[]) => any): void { + element.addEventListener(type, handler); + (this._terminal as any)._core._register({ dispose: () => element.removeEventListener(type, handler) }); + } } diff --git a/demo/client/components/window/addonLigaturesWindow.ts b/demo/client/components/window/addonLigaturesWindow.ts new file mode 100644 index 00000000..273ec04e --- /dev/null +++ b/demo/client/components/window/addonLigaturesWindow.ts @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2026 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { BaseWindow } from './baseWindow'; +import type { IControlWindow } from '../controlBar'; + +export class AddonLigaturesWindow extends BaseWindow implements IControlWindow { + public readonly id = 'addon-ligatures'; + public readonly label = 'ligatures'; + + public build(container: HTMLElement): void { + const dl = document.createElement('dl'); + const dt = document.createElement('dt'); + dt.textContent = 'Ligatures Addon'; + dl.appendChild(dt); + + const dd = document.createElement('dd'); + const button = document.createElement('button'); + button.id = 'ligatures-test'; + button.textContent = 'Common ligatures'; + button.title = 'Write common ligatures sequences'; + button.addEventListener('click', () => this._ligaturesTest()); + dd.appendChild(button); + dl.appendChild(dd); + + container.appendChild(dl); + } + + private _ligaturesTest(): void { + this._terminal.write([ + '', + '-<< -< -<- <-- <--- <<- <- -> ->> --> ---> ->- >- >>-', + '=<< =< =<= <== <=== <<= <= => =>> ==> ===> =>= >= >>=', + '<-> <--> <---> <----> <=> <==> <===> <====> :: ::: __', + '<~~ /> ~~> == != /= ~= <> === !== !=== =/= =!=', + '<: := *= *+ <* <*> *> <| <|> |> <. <.> .> +* =* =: :>', + '(* *) /* */ [| |] {| |} ++ +++ \/ /\ |- -| ---> ->- >- >>-', - '=<< =< =<= <== <=== <<= <= => =>> ==> ===> =>= >= >>=', - '<-> <--> <---> <----> <=> <==> <===> <====> :: ::: __', - '<~~ /> ~~> == != /= ~= <> === !== !=== =/= =!=', - '<: := *= *+ <* <*> *> <| <|> |> <. <.> .> +* =* =: :>', - '(* *) /* */ [| |] {| |} ++ +++ \/ /\ |- -|