mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
Fix: make ctrl+x use preferred editor (#16556)
This commit is contained in:
committed by
GitHub
parent
778de55fd8
commit
8dbaa2bcea
@@ -391,6 +391,11 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getPreferredEditor = useCallback(
|
||||
() => settings.merged.general?.preferredEditor as EditorType,
|
||||
[settings.merged.general?.preferredEditor],
|
||||
);
|
||||
|
||||
const buffer = useTextBuffer({
|
||||
initialText: '',
|
||||
viewport: { height: 10, width: inputWidth },
|
||||
@@ -398,6 +403,7 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
setRawMode,
|
||||
isValidPath,
|
||||
shellModeActive,
|
||||
getPreferredEditor,
|
||||
});
|
||||
|
||||
// Initialize input history from logger (past sessions)
|
||||
@@ -758,11 +764,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
() => {},
|
||||
);
|
||||
|
||||
const getPreferredEditor = useCallback(
|
||||
() => settings.merged.general?.preferredEditor as EditorType,
|
||||
[settings.merged.general?.preferredEditor],
|
||||
);
|
||||
|
||||
const onCancelSubmit = useCallback((shouldRestorePrompt?: boolean) => {
|
||||
if (shouldRestorePrompt) {
|
||||
setPendingRestorePrompt(true);
|
||||
|
||||
@@ -15,6 +15,9 @@ import {
|
||||
CoreEvent,
|
||||
debugLogger,
|
||||
unescapePath,
|
||||
type EditorType,
|
||||
getEditorCommand,
|
||||
isGuiEditor,
|
||||
} from '@google/gemini-cli-core';
|
||||
import {
|
||||
toCodePoints,
|
||||
@@ -566,6 +569,7 @@ interface UseTextBufferProps {
|
||||
shellModeActive?: boolean; // Whether the text buffer is in shell mode
|
||||
inputFilter?: (text: string) => string; // Optional filter for input text
|
||||
singleLine?: boolean;
|
||||
getPreferredEditor?: () => EditorType | undefined;
|
||||
}
|
||||
|
||||
interface UndoHistoryEntry {
|
||||
@@ -1826,6 +1830,7 @@ export function useTextBuffer({
|
||||
shellModeActive = false,
|
||||
inputFilter,
|
||||
singleLine = false,
|
||||
getPreferredEditor,
|
||||
}: UseTextBufferProps): TextBuffer {
|
||||
const initialState = useMemo((): TextBufferState => {
|
||||
const lines = initialText.split('\n');
|
||||
@@ -2152,55 +2157,67 @@ export function useTextBuffer({
|
||||
dispatch({ type: 'vim_escape_insert_mode' });
|
||||
}, []);
|
||||
|
||||
const openInExternalEditor = useCallback(
|
||||
async (opts: { editor?: string } = {}): Promise<void> => {
|
||||
const editor =
|
||||
opts.editor ??
|
||||
process.env['VISUAL'] ??
|
||||
process.env['EDITOR'] ??
|
||||
(process.platform === 'win32' ? 'notepad' : 'vi');
|
||||
const tmpDir = fs.mkdtempSync(pathMod.join(os.tmpdir(), 'gemini-edit-'));
|
||||
const filePath = pathMod.join(tmpDir, 'buffer.txt');
|
||||
fs.writeFileSync(filePath, text, 'utf8');
|
||||
const openInExternalEditor = useCallback(async (): Promise<void> => {
|
||||
const tmpDir = fs.mkdtempSync(pathMod.join(os.tmpdir(), 'gemini-edit-'));
|
||||
const filePath = pathMod.join(tmpDir, 'buffer.txt');
|
||||
fs.writeFileSync(filePath, text, 'utf8');
|
||||
|
||||
dispatch({ type: 'create_undo_snapshot' });
|
||||
let command: string | undefined = undefined;
|
||||
const args = [filePath];
|
||||
|
||||
const wasRaw = stdin?.isRaw ?? false;
|
||||
try {
|
||||
setRawMode?.(false);
|
||||
const { status, error } = spawnSync(editor, [filePath], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
if (error) throw error;
|
||||
if (typeof status === 'number' && status !== 0)
|
||||
throw new Error(`External editor exited with status ${status}`);
|
||||
|
||||
let newText = fs.readFileSync(filePath, 'utf8');
|
||||
newText = newText.replace(/\r\n?/g, '\n');
|
||||
dispatch({ type: 'set_text', payload: newText, pushToUndo: false });
|
||||
} catch (err) {
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
'[useTextBuffer] external editor error',
|
||||
err,
|
||||
);
|
||||
} finally {
|
||||
coreEvents.emit(CoreEvent.ExternalEditorClosed);
|
||||
if (wasRaw) setRawMode?.(true);
|
||||
try {
|
||||
fs.unlinkSync(filePath);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
try {
|
||||
fs.rmdirSync(tmpDir);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
const preferredEditorType = getPreferredEditor?.();
|
||||
if (!command && preferredEditorType) {
|
||||
command = getEditorCommand(preferredEditorType);
|
||||
if (isGuiEditor(preferredEditorType)) {
|
||||
args.unshift('--wait');
|
||||
}
|
||||
},
|
||||
[text, stdin, setRawMode],
|
||||
);
|
||||
}
|
||||
|
||||
if (!command) {
|
||||
command =
|
||||
(process.env['VISUAL'] ??
|
||||
process.env['EDITOR'] ??
|
||||
process.platform === 'win32')
|
||||
? 'notepad'
|
||||
: 'vi';
|
||||
}
|
||||
|
||||
dispatch({ type: 'create_undo_snapshot' });
|
||||
|
||||
const wasRaw = stdin?.isRaw ?? false;
|
||||
try {
|
||||
setRawMode?.(false);
|
||||
const { status, error } = spawnSync(command, args, {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
if (error) throw error;
|
||||
if (typeof status === 'number' && status !== 0)
|
||||
throw new Error(`External editor exited with status ${status}`);
|
||||
|
||||
let newText = fs.readFileSync(filePath, 'utf8');
|
||||
newText = newText.replace(/\r\n?/g, '\n');
|
||||
dispatch({ type: 'set_text', payload: newText, pushToUndo: false });
|
||||
} catch (err) {
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
'[useTextBuffer] external editor error',
|
||||
err,
|
||||
);
|
||||
} finally {
|
||||
coreEvents.emit(CoreEvent.ExternalEditorClosed);
|
||||
if (wasRaw) setRawMode?.(true);
|
||||
try {
|
||||
fs.unlinkSync(filePath);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
try {
|
||||
fs.rmdirSync(tmpDir);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}, [text, stdin, setRawMode, getPreferredEditor]);
|
||||
|
||||
const handleInput = useCallback(
|
||||
(key: Key): void => {
|
||||
@@ -2616,7 +2633,7 @@ export interface TextBuffer {
|
||||
* continuing. This mirrors Git's behaviour and simplifies downstream
|
||||
* control‑flow (callers can simply `await` the Promise).
|
||||
*/
|
||||
openInExternalEditor: (opts?: { editor?: string }) => Promise<void>;
|
||||
openInExternalEditor: () => Promise<void>;
|
||||
|
||||
replaceRangeByOffset: (
|
||||
startOffset: number,
|
||||
|
||||
@@ -112,6 +112,16 @@ export function checkHasEditorType(editor: EditorType): boolean {
|
||||
return commands.some((cmd) => commandExists(cmd));
|
||||
}
|
||||
|
||||
export function getEditorCommand(editor: EditorType): string {
|
||||
const commandConfig = editorCommands[editor];
|
||||
const commands =
|
||||
process.platform === 'win32' ? commandConfig.win32 : commandConfig.default;
|
||||
return (
|
||||
commands.slice(0, -1).find((cmd) => commandExists(cmd)) ||
|
||||
commands[commands.length - 1]
|
||||
);
|
||||
}
|
||||
|
||||
export function allowEditorTypeInSandbox(editor: EditorType): boolean {
|
||||
const notUsingSandbox = !process.env['SANDBOX'];
|
||||
if (isGuiEditor(editor)) {
|
||||
@@ -143,12 +153,7 @@ export function getDiffCommand(
|
||||
if (!isValidEditorType(editor)) {
|
||||
return null;
|
||||
}
|
||||
const commandConfig = editorCommands[editor];
|
||||
const commands =
|
||||
process.platform === 'win32' ? commandConfig.win32 : commandConfig.default;
|
||||
const command =
|
||||
commands.slice(0, -1).find((cmd) => commandExists(cmd)) ||
|
||||
commands[commands.length - 1];
|
||||
const command = getEditorCommand(editor);
|
||||
|
||||
switch (editor) {
|
||||
case 'vscode':
|
||||
|
||||
Reference in New Issue
Block a user