Infer modifyOtherKeys support (#16270)

This commit is contained in:
Tommaso Sciortino
2026-01-09 13:10:15 -08:00
committed by GitHub
parent c7d17dda49
commit ea7393f7fd
4 changed files with 43 additions and 31 deletions

View File

@@ -7,6 +7,10 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { TerminalCapabilityManager } from './terminalCapabilityManager.js';
import { EventEmitter } from 'node:events';
import {
enableKittyKeyboardProtocol,
enableModifyOtherKeys,
} from '@google/gemini-cli-core';
// Mock fs
vi.mock('node:fs', () => ({
@@ -190,7 +194,8 @@ describe('TerminalCapabilityManager', () => {
stdin.emit('data', Buffer.from('\x1b[?62c'));
await promise;
expect(manager.isModifyOtherKeysEnabled()).toBe(true);
expect(enableModifyOtherKeys).toHaveBeenCalled();
});
it('should not enable modifyOtherKeys for level 0', async () => {
@@ -203,7 +208,8 @@ describe('TerminalCapabilityManager', () => {
stdin.emit('data', Buffer.from('\x1b[?62c'));
await promise;
expect(manager.isModifyOtherKeysEnabled()).toBe(false);
expect(enableModifyOtherKeys).not.toHaveBeenCalled();
});
it('should prefer Kitty over modifyOtherKeys', async () => {
@@ -218,7 +224,9 @@ describe('TerminalCapabilityManager', () => {
await promise;
expect(manager.isKittyProtocolEnabled()).toBe(true);
expect(manager.isModifyOtherKeysEnabled()).toBe(false);
expect(enableKittyKeyboardProtocol).toHaveBeenCalled();
expect(enableModifyOtherKeys).not.toHaveBeenCalled();
});
it('should enable modifyOtherKeys when Kitty not supported', async () => {
@@ -231,8 +239,9 @@ describe('TerminalCapabilityManager', () => {
stdin.emit('data', Buffer.from('\x1b[?62c'));
await promise;
expect(manager.isModifyOtherKeysEnabled()).toBe(true);
expect(manager.isKittyProtocolEnabled()).toBe(false);
expect(enableModifyOtherKeys).toHaveBeenCalled();
});
it('should handle split modifyOtherKeys response chunks', async () => {
@@ -246,7 +255,8 @@ describe('TerminalCapabilityManager', () => {
stdin.emit('data', Buffer.from('\x1b[?62c'));
await promise;
expect(manager.isModifyOtherKeysEnabled()).toBe(true);
expect(enableModifyOtherKeys).toHaveBeenCalled();
});
it('should detect modifyOtherKeys with other capabilities', async () => {
@@ -263,7 +273,23 @@ describe('TerminalCapabilityManager', () => {
expect(manager.getTerminalBackgroundColor()).toBe('#1a1a1a');
expect(manager.getTerminalName()).toBe('tmux');
expect(manager.isModifyOtherKeysEnabled()).toBe(true);
expect(enableModifyOtherKeys).toHaveBeenCalled();
});
it('should infer modifyOtherKeys support from Device Attributes (DA1) alone', async () => {
const manager = TerminalCapabilityManager.getInstance();
const promise = manager.detectCapabilities();
// Simulate only DA1 response (no specific MOK or Kitty response)
stdin.emit('data', Buffer.from('\x1b[?62c'));
await promise;
expect(manager.isKittyProtocolEnabled()).toBe(false);
// It should fall back to modifyOtherKeys because DA1 proves it's an ANSI terminal
expect(enableModifyOtherKeys).toHaveBeenCalled();
});
});
});

View File

@@ -43,14 +43,13 @@ export class TerminalCapabilityManager {
// eslint-disable-next-line no-control-regex
private static readonly MODIFY_OTHER_KEYS_REGEX = /\x1b\[>4;(\d+)m/;
private detectionComplete = false;
private terminalBackgroundColor: TerminalBackgroundColor;
private kittySupported = false;
private kittyEnabled = false;
private detectionComplete = false;
private terminalName: string | undefined;
private modifyOtherKeysSupported = false;
private modifyOtherKeysEnabled = false;
private bracketedPasteEnabled = false;
private modifyOtherKeysSupported?: boolean;
private deviceAttributesSupported = false;
private constructor() {}
@@ -187,6 +186,7 @@ export class TerminalCapabilityManager {
);
if (match) {
deviceAttributesReceived = true;
this.deviceAttributesSupported = true;
cleanup();
}
}
@@ -215,13 +215,17 @@ export class TerminalCapabilityManager {
if (this.kittySupported) {
enableKittyKeyboardProtocol();
this.kittyEnabled = true;
} else if (this.modifyOtherKeysSupported) {
} else if (
this.modifyOtherKeysSupported === true ||
// If device attributes were received it's safe to try enabling
// anyways, since it will be ignored if unsupported
(this.modifyOtherKeysSupported === undefined &&
this.deviceAttributesSupported)
) {
enableModifyOtherKeys();
this.modifyOtherKeysEnabled = true;
}
// Always enable bracketed paste since it'll be ignored if unsupported.
enableBracketedPasteMode();
this.bracketedPasteEnabled = true;
} catch (e) {
debugLogger.warn('Failed to enable keyboard protocols:', e);
}
@@ -239,14 +243,6 @@ export class TerminalCapabilityManager {
return this.kittyEnabled;
}
isBracketedPasteEnabled(): boolean {
return this.bracketedPasteEnabled;
}
isModifyOtherKeysEnabled(): boolean {
return this.modifyOtherKeysEnabled;
}
private parseColor(rHex: string, gHex: string, bHex: string): string {
const parseComponent = (hex: string) => {
const val = parseInt(hex, 16);