From 55eb05d74cd0c8bb13a06e4838df3c8b76cd2f1e Mon Sep 17 00:00:00 2001 From: anthonykim1 Date: Wed, 25 Mar 2026 21:18:56 -0700 Subject: [PATCH 1/5] Fix ctrl+/- not working on Windows Copilot CLI --- .../contrib/terminal/common/terminal.ts | 3 + .../test/browser/terminalInstance.test.ts | 68 +++++++++++++++++-- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index c5951189a8000..f113bb4360be7 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -623,6 +623,9 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ 'workbench.action.togglePanel', 'workbench.action.quickOpenView', 'workbench.action.toggleMaximizedPanel', + 'workbench.action.zoomIn', + 'workbench.action.zoomOut', + 'workbench.action.zoomReset', 'notification.acceptPrimaryAction', 'runCommands', 'workbench.action.terminal.chat.start', diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts index b5f566ce0de85..b6d7a47d69119 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { deepStrictEqual, strictEqual } from 'assert'; +import { timeout } from '../../../../../base/common/async.js'; import { Event } from '../../../../../base/common/event.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../../base/common/network.js'; @@ -13,6 +14,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/tes import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { ResultKind } from '../../../../../platform/keybinding/common/keybindingResolver.js'; import { TerminalCapability, type ICwdDetectionCapability } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; import { TerminalCapabilityStore } from '../../../../../platform/terminal/common/capabilities/terminalCapabilityStore.js'; import { GeneralShellType, ITerminalChildProcess, ITerminalProfile, TitleEventSource, type IShellLaunchConfig, type ITerminalBackend, type ITerminalProcessOptions } from '../../../../../platform/terminal/common/terminal.js'; @@ -23,7 +25,7 @@ import { TerminalConfigurationService } from '../../browser/terminalConfiguratio import { parseExitResult, TerminalInstance, TerminalLabelComputer } from '../../browser/terminalInstance.js'; import { IEnvironmentVariableService } from '../../common/environmentVariable.js'; import { EnvironmentVariableService } from '../../common/environmentVariableService.js'; -import { ITerminalProfileResolverService, ProcessState } from '../../common/terminal.js'; +import { ITerminalProfileResolverService, ProcessState, DEFAULT_COMMANDS_TO_SKIP_SHELL } from '../../common/terminal.js'; import { TestViewDescriptorService } from './xterm/xtermTerminal.test.js'; import { fixPath } from '../../../../services/search/test/browser/queryBuilder.test.js'; import { TestTerminalProfileResolverService, workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; @@ -123,7 +125,8 @@ suite('Workbench - TerminalInstance', () => { suite('TerminalInstance', () => { let terminalInstance: ITerminalInstance; - test('should create an instance of TerminalInstance with env from default profile', async () => { + + async function createTerminalInstance(): Promise { const instantiationService = workbenchInstantiationService({ configurationService: () => new TestConfigurationService({ files: {}, @@ -147,9 +150,25 @@ suite('Workbench - TerminalInstance', () => { instantiationService.stub(IEnvironmentVariableService, store.add(instantiationService.createInstance(EnvironmentVariableService))); instantiationService.stub(ITerminalInstanceService, store.add(new TestTerminalInstanceService())); instantiationService.stub(ITerminalService, { setNextCommandId: async () => { } } as Partial); - terminalInstance = store.add(instantiationService.createInstance(TerminalInstance, terminalShellTypeContextKey, {})); - // //Wait for the teminalInstance._xtermReadyPromise to resolve - await new Promise(resolve => setTimeout(resolve, 100)); + const instance = store.add(instantiationService.createInstance(TerminalInstance, terminalShellTypeContextKey, {})); + await instance.xtermReadyPromise; + return instance; + } + + async function waitForShellLaunchConfigEnv(instance: ITerminalInstance): Promise { + for (let i = 0; i < 50; i++) { + if (instance.shellLaunchConfig.env) { + return; + } + await timeout(0); + } + + throw new Error('Timed out waiting for shell launch config env'); + } + + test('should create an instance of TerminalInstance with env from default profile', async () => { + terminalInstance = await createTerminalInstance(); + await waitForShellLaunchConfigEnv(terminalInstance); deepStrictEqual(terminalInstance.shellLaunchConfig.env, { TEST: 'TEST' }); }); @@ -193,6 +212,45 @@ suite('Workbench - TerminalInstance', () => { // Verify that the task name is preserved strictEqual(taskTerminal.title, 'Test Task Name', 'Task terminal should preserve API-set title'); }); + + test('custom key event handler should intercept Meta-modified keys that resolve to a command when sendKeybindingsToShell is disabled', async () => { + const instance = await createTerminalInstance(); + const keybindingService = instance['_keybindingService']; + const originalSoftDispatch = keybindingService.softDispatch; + // Simulate Cmd+= resolving to zoomIn. This command is deliberately NOT in + // DEFAULT_COMMANDS_TO_SKIP_SHELL, so only the event.metaKey check can intercept it. + keybindingService.softDispatch = () => ({ kind: ResultKind.KbFound, commandId: 'workbench.action.zoomIn', commandArgs: undefined, isBubble: false }); + + // Capture the inline handler by intercepting its registration on xterm.raw, + // then attach + show the terminal so the handler gets registered. + let capturedHandler: ((e: KeyboardEvent) => boolean) | undefined; + instance.xterm!.raw.attachCustomKeyEventHandler = handler => { capturedHandler = handler; }; + const container = document.createElement('div'); + document.body.appendChild(container); + instance.attachToElement(container); + instance.setVisible(true); + + const event = new KeyboardEvent('keydown', { key: '=', metaKey: true, cancelable: true }); + try { + deepStrictEqual( + { result: capturedHandler?.(event), defaultPrevented: event.defaultPrevented }, + { result: false, defaultPrevented: true } + ); + } finally { + keybindingService.softDispatch = originalSoftDispatch; + container.remove(); + } + }); + }); + suite('DEFAULT_COMMANDS_TO_SKIP_SHELL', () => { + test('should include zoom commands so they are not consumed by kitty keyboard protocol', () => { + deepStrictEqual( + ['workbench.action.zoomIn', 'workbench.action.zoomOut', 'workbench.action.zoomReset'].every( + cmd => DEFAULT_COMMANDS_TO_SKIP_SHELL.includes(cmd) + ), + true + ); + }); }); suite('parseExitResult', () => { test('should return no message for exit code = undefined', () => { From 774697897ae46a595c50e9017a43eecf65a7c531 Mon Sep 17 00:00:00 2001 From: anthonykim1 Date: Wed, 25 Mar 2026 21:42:32 -0700 Subject: [PATCH 2/5] remove unncessary --- .../test/browser/terminalInstance.test.ts | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts index b6d7a47d69119..b00fbeb6652c7 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { deepStrictEqual, strictEqual } from 'assert'; -import { timeout } from '../../../../../base/common/async.js'; import { Event } from '../../../../../base/common/event.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../../base/common/network.js'; @@ -155,20 +154,10 @@ suite('Workbench - TerminalInstance', () => { return instance; } - async function waitForShellLaunchConfigEnv(instance: ITerminalInstance): Promise { - for (let i = 0; i < 50; i++) { - if (instance.shellLaunchConfig.env) { - return; - } - await timeout(0); - } - - throw new Error('Timed out waiting for shell launch config env'); - } - test('should create an instance of TerminalInstance with env from default profile', async () => { terminalInstance = await createTerminalInstance(); - await waitForShellLaunchConfigEnv(terminalInstance); + // Wait for the terminal instance to resolve shell launch config env. + await new Promise(resolve => setTimeout(resolve, 100)); deepStrictEqual(terminalInstance.shellLaunchConfig.env, { TEST: 'TEST' }); }); From 9c08b1cb8c9b19316810dda64a1897811132bb8d Mon Sep 17 00:00:00 2001 From: anthonykim1 Date: Wed, 25 Mar 2026 22:27:18 -0700 Subject: [PATCH 3/5] Changes --- .../terminal/test/browser/terminalInstance.test.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts index b00fbeb6652c7..a046fbd527094 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts @@ -202,16 +202,12 @@ suite('Workbench - TerminalInstance', () => { strictEqual(taskTerminal.title, 'Test Task Name', 'Task terminal should preserve API-set title'); }); - test('custom key event handler should intercept Meta-modified keys that resolve to a command when sendKeybindingsToShell is disabled', async () => { + test('custom key event handler should handle commands in DEFAULT_COMMANDS_TO_SKIP_SHELL in VS Code and not xterm when sendKeybindingsToShell is disabled', async () => { const instance = await createTerminalInstance(); const keybindingService = instance['_keybindingService']; const originalSoftDispatch = keybindingService.softDispatch; - // Simulate Cmd+= resolving to zoomIn. This command is deliberately NOT in - // DEFAULT_COMMANDS_TO_SKIP_SHELL, so only the event.metaKey check can intercept it. keybindingService.softDispatch = () => ({ kind: ResultKind.KbFound, commandId: 'workbench.action.zoomIn', commandArgs: undefined, isBubble: false }); - // Capture the inline handler by intercepting its registration on xterm.raw, - // then attach + show the terminal so the handler gets registered. let capturedHandler: ((e: KeyboardEvent) => boolean) | undefined; instance.xterm!.raw.attachCustomKeyEventHandler = handler => { capturedHandler = handler; }; const container = document.createElement('div'); @@ -219,7 +215,7 @@ suite('Workbench - TerminalInstance', () => { instance.attachToElement(container); instance.setVisible(true); - const event = new KeyboardEvent('keydown', { key: '=', metaKey: true, cancelable: true }); + const event = new KeyboardEvent('keydown', { key: '=', cancelable: true }); try { deepStrictEqual( { result: capturedHandler?.(event), defaultPrevented: event.defaultPrevented }, From f727c875c1a4c7bc7d75efb3d52ddce94b909b55 Mon Sep 17 00:00:00 2001 From: anthonykim1 Date: Wed, 25 Mar 2026 22:30:41 -0700 Subject: [PATCH 4/5] more test --- .../test/browser/terminalInstance.test.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts index a046fbd527094..9f6b3e3cfbac0 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts @@ -226,6 +226,32 @@ suite('Workbench - TerminalInstance', () => { container.remove(); } }); + + test('custom key event handler should intercept Meta-modified keys that resolve to a command when sendKeybindingsToShell is disabled', async () => { + const instance = await createTerminalInstance(); + const keybindingService = instance['_keybindingService']; + const originalSoftDispatch = keybindingService.softDispatch; + strictEqual(DEFAULT_COMMANDS_TO_SKIP_SHELL.includes('test.metaKeyInterceptCommand'), false); + keybindingService.softDispatch = () => ({ kind: ResultKind.KbFound, commandId: 'test.metaKeyInterceptCommand', commandArgs: undefined, isBubble: false }); + + let capturedHandler: ((e: KeyboardEvent) => boolean) | undefined; + instance.xterm!.raw.attachCustomKeyEventHandler = handler => { capturedHandler = handler; }; + const container = document.createElement('div'); + document.body.appendChild(container); + instance.attachToElement(container); + instance.setVisible(true); + + const event = new KeyboardEvent('keydown', { key: '=', metaKey: true, cancelable: true }); + try { + deepStrictEqual( + { result: capturedHandler?.(event), defaultPrevented: event.defaultPrevented }, + { result: false, defaultPrevented: true } + ); + } finally { + keybindingService.softDispatch = originalSoftDispatch; + container.remove(); + } + }); }); suite('DEFAULT_COMMANDS_TO_SKIP_SHELL', () => { test('should include zoom commands so they are not consumed by kitty keyboard protocol', () => { From 83a5132dabb19ae831aeb8ee9ea9f73bd308b594 Mon Sep 17 00:00:00 2001 From: anthonykim1 Date: Thu, 26 Mar 2026 15:12:34 -0700 Subject: [PATCH 5/5] lint --- .../contrib/terminal/test/browser/terminalInstance.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts index b3ce89de68b7e..834904a62033c 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts @@ -28,7 +28,7 @@ import { TerminalConfigurationService } from '../../browser/terminalConfiguratio import { parseExitResult, TerminalInstance, TerminalInstanceColorProvider, TerminalLabelComputer } from '../../browser/terminalInstance.js'; import { IEnvironmentVariableService } from '../../common/environmentVariable.js'; import { EnvironmentVariableService } from '../../common/environmentVariableService.js'; -import { ITerminalProfileResolverService, ProcessState, DEFAULT_COMMANDS_TO_SKIP_SHELL } from '../../common/terminal.js'; +import { ITerminalProfileResolverService, ProcessState, DEFAULT_COMMANDS_TO_SKIP_SHELL } from '../../common/terminal.js'; import { TERMINAL_BACKGROUND_COLOR } from '../../common/terminalColorRegistry.js'; import { TestViewDescriptorService } from './xterm/xtermTerminal.test.js'; import { fixPath } from '../../../../services/search/test/browser/queryBuilder.test.js';