mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 21:03:05 -07:00
Restore keyboard mode when exiting the editor (#13350)
This commit is contained in:
committed by
GitHub
parent
3f8d636501
commit
84573992b4
@@ -110,6 +110,7 @@ import { isWorkspaceTrusted } from '../config/trustedFolders.js';
|
|||||||
import { disableMouseEvents, enableMouseEvents } from './utils/mouse.js';
|
import { disableMouseEvents, enableMouseEvents } from './utils/mouse.js';
|
||||||
import { useAlternateBuffer } from './hooks/useAlternateBuffer.js';
|
import { useAlternateBuffer } from './hooks/useAlternateBuffer.js';
|
||||||
import { useSettings } from './contexts/SettingsContext.js';
|
import { useSettings } from './contexts/SettingsContext.js';
|
||||||
|
import { enableSupportedProtocol } from './utils/kittyProtocolDetector.js';
|
||||||
|
|
||||||
const WARNING_PROMPT_DURATION_MS = 1000;
|
const WARNING_PROMPT_DURATION_MS = 1000;
|
||||||
const QUEUE_ERROR_DISPLAY_DURATION_MS = 3000;
|
const QUEUE_ERROR_DISPLAY_DURATION_MS = 3000;
|
||||||
@@ -381,6 +382,11 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||||||
setHistoryRemountKey((prev) => prev + 1);
|
setHistoryRemountKey((prev) => prev + 1);
|
||||||
}, [setHistoryRemountKey, stdout, isAlternateBuffer]);
|
}, [setHistoryRemountKey, stdout, isAlternateBuffer]);
|
||||||
|
|
||||||
|
const handleEditorClose = useCallback(() => {
|
||||||
|
enableSupportedProtocol();
|
||||||
|
refreshStatic();
|
||||||
|
}, [refreshStatic]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isThemeDialogOpen,
|
isThemeDialogOpen,
|
||||||
openThemeDialog,
|
openThemeDialog,
|
||||||
@@ -687,7 +693,7 @@ Logging in with Google... Please restart Gemini CLI to continue.
|
|||||||
performMemoryRefresh,
|
performMemoryRefresh,
|
||||||
modelSwitchedFromQuotaError,
|
modelSwitchedFromQuotaError,
|
||||||
setModelSwitchedFromQuotaError,
|
setModelSwitchedFromQuotaError,
|
||||||
refreshStatic,
|
handleEditorClose,
|
||||||
onCancelSubmit,
|
onCancelSubmit,
|
||||||
setEmbeddedShellFocused,
|
setEmbeddedShellFocused,
|
||||||
terminalWidth,
|
terminalWidth,
|
||||||
|
|||||||
@@ -207,7 +207,6 @@ describe('InputPrompt', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
mockedUseKittyKeyboardProtocol.mockReturnValue({
|
mockedUseKittyKeyboardProtocol.mockReturnValue({
|
||||||
supported: false,
|
|
||||||
enabled: false,
|
enabled: false,
|
||||||
checking: false,
|
checking: false,
|
||||||
});
|
});
|
||||||
@@ -1244,7 +1243,6 @@ describe('InputPrompt', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
mockedUseKittyKeyboardProtocol.mockReturnValue({
|
mockedUseKittyKeyboardProtocol.mockReturnValue({
|
||||||
supported: false,
|
|
||||||
enabled: false,
|
enabled: false,
|
||||||
checking: false,
|
checking: false,
|
||||||
});
|
});
|
||||||
@@ -1328,7 +1326,6 @@ describe('InputPrompt', () => {
|
|||||||
name: 'kitty',
|
name: 'kitty',
|
||||||
setup: () =>
|
setup: () =>
|
||||||
mockedUseKittyKeyboardProtocol.mockReturnValue({
|
mockedUseKittyKeyboardProtocol.mockReturnValue({
|
||||||
supported: true,
|
|
||||||
enabled: true,
|
enabled: true,
|
||||||
checking: false,
|
checking: false,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -404,7 +404,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
|
|
||||||
if (key.paste) {
|
if (key.paste) {
|
||||||
// Record paste time to prevent accidental auto-submission
|
// Record paste time to prevent accidental auto-submission
|
||||||
if (!isTerminalPasteTrusted(kittyProtocol.supported)) {
|
if (!isTerminalPasteTrusted(kittyProtocol.enabled)) {
|
||||||
setRecentUnsafePasteTime(Date.now());
|
setRecentUnsafePasteTime(Date.now());
|
||||||
|
|
||||||
// Clear any existing paste timeout
|
// Clear any existing paste timeout
|
||||||
@@ -820,7 +820,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
recentUnsafePasteTime,
|
recentUnsafePasteTime,
|
||||||
commandSearchActive,
|
commandSearchActive,
|
||||||
commandSearchCompletion,
|
commandSearchCompletion,
|
||||||
kittyProtocol.supported,
|
kittyProtocol.enabled,
|
||||||
tryLoadQueuedMessages,
|
tryLoadQueuedMessages,
|
||||||
setBannerVisible,
|
setBannerVisible,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
import type { Key } from '../../contexts/KeypressContext.js';
|
import type { Key } from '../../contexts/KeypressContext.js';
|
||||||
import type { VimAction } from './vim-buffer-actions.js';
|
import type { VimAction } from './vim-buffer-actions.js';
|
||||||
import { handleVimAction } from './vim-buffer-actions.js';
|
import { handleVimAction } from './vim-buffer-actions.js';
|
||||||
|
import { enableSupportedProtocol } from '../../utils/kittyProtocolDetector.js';
|
||||||
|
|
||||||
export type Direction =
|
export type Direction =
|
||||||
| 'left'
|
| 'left'
|
||||||
@@ -1891,6 +1892,7 @@ export function useTextBuffer({
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[useTextBuffer] external editor error', err);
|
console.error('[useTextBuffer] external editor error', err);
|
||||||
} finally {
|
} finally {
|
||||||
|
enableSupportedProtocol();
|
||||||
if (wasRaw) setRawMode?.(true);
|
if (wasRaw) setRawMode?.(true);
|
||||||
try {
|
try {
|
||||||
fs.unlinkSync(filePath);
|
fs.unlinkSync(filePath);
|
||||||
|
|||||||
@@ -5,13 +5,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import {
|
import { isKittyProtocolEnabled } from '../utils/kittyProtocolDetector.js';
|
||||||
isKittyProtocolEnabled,
|
|
||||||
isKittyProtocolSupported,
|
|
||||||
} from '../utils/kittyProtocolDetector.js';
|
|
||||||
|
|
||||||
export interface KittyProtocolStatus {
|
export interface KittyProtocolStatus {
|
||||||
supported: boolean;
|
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
checking: boolean;
|
checking: boolean;
|
||||||
}
|
}
|
||||||
@@ -22,7 +18,6 @@ export interface KittyProtocolStatus {
|
|||||||
*/
|
*/
|
||||||
export function useKittyKeyboardProtocol(): KittyProtocolStatus {
|
export function useKittyKeyboardProtocol(): KittyProtocolStatus {
|
||||||
const [status] = useState<KittyProtocolStatus>({
|
const [status] = useState<KittyProtocolStatus>({
|
||||||
supported: isKittyProtocolSupported(),
|
|
||||||
enabled: isKittyProtocolEnabled(),
|
enabled: isKittyProtocolEnabled(),
|
||||||
checking: false,
|
checking: false,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,8 +5,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
let detectionComplete = false;
|
let detectionComplete = false;
|
||||||
let protocolSupported = false;
|
|
||||||
let protocolEnabled = false;
|
let kittySupported = false;
|
||||||
|
let sgrMouseSupported = false;
|
||||||
|
|
||||||
|
let kittyEnabled = false;
|
||||||
let sgrMouseEnabled = false;
|
let sgrMouseEnabled = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -14,15 +17,15 @@ let sgrMouseEnabled = false;
|
|||||||
* Definitive document about this protocol lives at https://sw.kovidgoyal.net/kitty/keyboard-protocol/
|
* Definitive document about this protocol lives at https://sw.kovidgoyal.net/kitty/keyboard-protocol/
|
||||||
* This function should be called once at app startup.
|
* This function should be called once at app startup.
|
||||||
*/
|
*/
|
||||||
export async function detectAndEnableKittyProtocol(): Promise<boolean> {
|
export async function detectAndEnableKittyProtocol(): Promise<void> {
|
||||||
if (detectionComplete) {
|
if (detectionComplete) {
|
||||||
return protocolSupported;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
||||||
detectionComplete = true;
|
detectionComplete = true;
|
||||||
resolve(false);
|
resolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,14 +38,24 @@ export async function detectAndEnableKittyProtocol(): Promise<boolean> {
|
|||||||
let progressiveEnhancementReceived = false;
|
let progressiveEnhancementReceived = false;
|
||||||
let timeoutId: NodeJS.Timeout | undefined;
|
let timeoutId: NodeJS.Timeout | undefined;
|
||||||
|
|
||||||
const onTimeout = () => {
|
const finish = () => {
|
||||||
timeoutId = undefined;
|
if (timeoutId !== undefined) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = undefined;
|
||||||
|
}
|
||||||
process.stdin.removeListener('data', handleData);
|
process.stdin.removeListener('data', handleData);
|
||||||
if (!originalRawMode) {
|
if (!originalRawMode) {
|
||||||
process.stdin.setRawMode(false);
|
process.stdin.setRawMode(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (kittySupported || sgrMouseSupported) {
|
||||||
|
enableSupportedProtocol();
|
||||||
|
process.on('exit', disableAllProtocols);
|
||||||
|
process.on('SIGTERM', disableAllProtocols);
|
||||||
|
}
|
||||||
|
|
||||||
detectionComplete = true;
|
detectionComplete = true;
|
||||||
resolve(false);
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleData = (data: Buffer) => {
|
const handleData = (data: Buffer) => {
|
||||||
@@ -59,37 +72,20 @@ export async function detectAndEnableKittyProtocol(): Promise<boolean> {
|
|||||||
// indication the terminal probably supports kitty and we just need to
|
// indication the terminal probably supports kitty and we just need to
|
||||||
// wait a bit longer for a response.
|
// wait a bit longer for a response.
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
timeoutId = setTimeout(onTimeout, 1000);
|
timeoutId = setTimeout(finish, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for device attributes response (CSI ? <attrs> c)
|
// Check for device attributes response (CSI ? <attrs> c)
|
||||||
if (responseBuffer.includes('\x1b[?') && responseBuffer.includes('c')) {
|
if (responseBuffer.includes('\x1b[?') && responseBuffer.includes('c')) {
|
||||||
clearTimeout(timeoutId);
|
|
||||||
timeoutId = undefined;
|
|
||||||
process.stdin.removeListener('data', handleData);
|
|
||||||
|
|
||||||
if (!originalRawMode) {
|
|
||||||
process.stdin.setRawMode(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progressiveEnhancementReceived) {
|
if (progressiveEnhancementReceived) {
|
||||||
// Enable the protocol
|
kittySupported = true;
|
||||||
process.stdout.write('\x1b[>1u');
|
|
||||||
protocolSupported = true;
|
|
||||||
protocolEnabled = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broaden mouse support by enabling SGR mode if we get any device
|
// Broaden mouse support by enabling SGR mode if we get any device
|
||||||
// attribute response, which is a strong signal of a modern terminal.
|
// attribute response, which is a strong signal of a modern terminal.
|
||||||
process.stdout.write('\x1b[?1006h');
|
sgrMouseSupported = true;
|
||||||
sgrMouseEnabled = true;
|
|
||||||
|
|
||||||
// Set up cleanup on exit for all enabled protocols
|
finish();
|
||||||
process.on('exit', disableAllProtocols);
|
|
||||||
process.on('SIGTERM', disableAllProtocols);
|
|
||||||
|
|
||||||
detectionComplete = true;
|
|
||||||
resolve(protocolSupported);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -102,14 +98,18 @@ export async function detectAndEnableKittyProtocol(): Promise<boolean> {
|
|||||||
// Timeout after 200ms
|
// Timeout after 200ms
|
||||||
// When a iterm2 terminal does not have focus this can take over 90s on a
|
// When a iterm2 terminal does not have focus this can take over 90s on a
|
||||||
// fast macbook so we need a somewhat longer threshold than would be ideal.
|
// fast macbook so we need a somewhat longer threshold than would be ideal.
|
||||||
timeoutId = setTimeout(onTimeout, 200);
|
timeoutId = setTimeout(finish, 200);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isKittyProtocolEnabled(): boolean {
|
||||||
|
return kittyEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
function disableAllProtocols() {
|
function disableAllProtocols() {
|
||||||
if (protocolEnabled) {
|
if (kittyEnabled) {
|
||||||
process.stdout.write('\x1b[<u');
|
process.stdout.write('\x1b[<u');
|
||||||
protocolEnabled = false;
|
kittyEnabled = false;
|
||||||
}
|
}
|
||||||
if (sgrMouseEnabled) {
|
if (sgrMouseEnabled) {
|
||||||
process.stdout.write('\x1b[?1006l'); // Disable SGR Mouse
|
process.stdout.write('\x1b[?1006l'); // Disable SGR Mouse
|
||||||
@@ -117,10 +117,17 @@ function disableAllProtocols() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isKittyProtocolEnabled(): boolean {
|
/**
|
||||||
return protocolEnabled;
|
* This is exported so we can reenable this after exiting an editor which might
|
||||||
}
|
* change the mode.
|
||||||
|
*/
|
||||||
export function isKittyProtocolSupported(): boolean {
|
export function enableSupportedProtocol(): void {
|
||||||
return protocolSupported;
|
if (kittySupported) {
|
||||||
|
process.stdout.write('\x1b[>1u');
|
||||||
|
kittyEnabled = true;
|
||||||
|
}
|
||||||
|
if (sgrMouseSupported) {
|
||||||
|
process.stdout.write('\x1b[?1006h');
|
||||||
|
sgrMouseEnabled = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user