mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 12:54:07 -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({
|
const buffer = useTextBuffer({
|
||||||
initialText: '',
|
initialText: '',
|
||||||
viewport: { height: 10, width: inputWidth },
|
viewport: { height: 10, width: inputWidth },
|
||||||
@@ -398,6 +403,7 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||||||
setRawMode,
|
setRawMode,
|
||||||
isValidPath,
|
isValidPath,
|
||||||
shellModeActive,
|
shellModeActive,
|
||||||
|
getPreferredEditor,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize input history from logger (past sessions)
|
// 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) => {
|
const onCancelSubmit = useCallback((shouldRestorePrompt?: boolean) => {
|
||||||
if (shouldRestorePrompt) {
|
if (shouldRestorePrompt) {
|
||||||
setPendingRestorePrompt(true);
|
setPendingRestorePrompt(true);
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ import {
|
|||||||
CoreEvent,
|
CoreEvent,
|
||||||
debugLogger,
|
debugLogger,
|
||||||
unescapePath,
|
unescapePath,
|
||||||
|
type EditorType,
|
||||||
|
getEditorCommand,
|
||||||
|
isGuiEditor,
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
import {
|
import {
|
||||||
toCodePoints,
|
toCodePoints,
|
||||||
@@ -566,6 +569,7 @@ interface UseTextBufferProps {
|
|||||||
shellModeActive?: boolean; // Whether the text buffer is in shell mode
|
shellModeActive?: boolean; // Whether the text buffer is in shell mode
|
||||||
inputFilter?: (text: string) => string; // Optional filter for input text
|
inputFilter?: (text: string) => string; // Optional filter for input text
|
||||||
singleLine?: boolean;
|
singleLine?: boolean;
|
||||||
|
getPreferredEditor?: () => EditorType | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UndoHistoryEntry {
|
interface UndoHistoryEntry {
|
||||||
@@ -1826,6 +1830,7 @@ export function useTextBuffer({
|
|||||||
shellModeActive = false,
|
shellModeActive = false,
|
||||||
inputFilter,
|
inputFilter,
|
||||||
singleLine = false,
|
singleLine = false,
|
||||||
|
getPreferredEditor,
|
||||||
}: UseTextBufferProps): TextBuffer {
|
}: UseTextBufferProps): TextBuffer {
|
||||||
const initialState = useMemo((): TextBufferState => {
|
const initialState = useMemo((): TextBufferState => {
|
||||||
const lines = initialText.split('\n');
|
const lines = initialText.split('\n');
|
||||||
@@ -2152,55 +2157,67 @@ export function useTextBuffer({
|
|||||||
dispatch({ type: 'vim_escape_insert_mode' });
|
dispatch({ type: 'vim_escape_insert_mode' });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const openInExternalEditor = useCallback(
|
const openInExternalEditor = useCallback(async (): Promise<void> => {
|
||||||
async (opts: { editor?: string } = {}): Promise<void> => {
|
const tmpDir = fs.mkdtempSync(pathMod.join(os.tmpdir(), 'gemini-edit-'));
|
||||||
const editor =
|
const filePath = pathMod.join(tmpDir, 'buffer.txt');
|
||||||
opts.editor ??
|
fs.writeFileSync(filePath, text, 'utf8');
|
||||||
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');
|
|
||||||
|
|
||||||
dispatch({ type: 'create_undo_snapshot' });
|
let command: string | undefined = undefined;
|
||||||
|
const args = [filePath];
|
||||||
|
|
||||||
const wasRaw = stdin?.isRaw ?? false;
|
const preferredEditorType = getPreferredEditor?.();
|
||||||
try {
|
if (!command && preferredEditorType) {
|
||||||
setRawMode?.(false);
|
command = getEditorCommand(preferredEditorType);
|
||||||
const { status, error } = spawnSync(editor, [filePath], {
|
if (isGuiEditor(preferredEditorType)) {
|
||||||
stdio: 'inherit',
|
args.unshift('--wait');
|
||||||
});
|
|
||||||
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],
|
|
||||||
);
|
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(
|
const handleInput = useCallback(
|
||||||
(key: Key): void => {
|
(key: Key): void => {
|
||||||
@@ -2616,7 +2633,7 @@ export interface TextBuffer {
|
|||||||
* continuing. This mirrors Git's behaviour and simplifies downstream
|
* continuing. This mirrors Git's behaviour and simplifies downstream
|
||||||
* control‑flow (callers can simply `await` the Promise).
|
* control‑flow (callers can simply `await` the Promise).
|
||||||
*/
|
*/
|
||||||
openInExternalEditor: (opts?: { editor?: string }) => Promise<void>;
|
openInExternalEditor: () => Promise<void>;
|
||||||
|
|
||||||
replaceRangeByOffset: (
|
replaceRangeByOffset: (
|
||||||
startOffset: number,
|
startOffset: number,
|
||||||
|
|||||||
@@ -112,6 +112,16 @@ export function checkHasEditorType(editor: EditorType): boolean {
|
|||||||
return commands.some((cmd) => commandExists(cmd));
|
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 {
|
export function allowEditorTypeInSandbox(editor: EditorType): boolean {
|
||||||
const notUsingSandbox = !process.env['SANDBOX'];
|
const notUsingSandbox = !process.env['SANDBOX'];
|
||||||
if (isGuiEditor(editor)) {
|
if (isGuiEditor(editor)) {
|
||||||
@@ -143,12 +153,7 @@ export function getDiffCommand(
|
|||||||
if (!isValidEditorType(editor)) {
|
if (!isValidEditorType(editor)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const commandConfig = editorCommands[editor];
|
const command = getEditorCommand(editor);
|
||||||
const commands =
|
|
||||||
process.platform === 'win32' ? commandConfig.win32 : commandConfig.default;
|
|
||||||
const command =
|
|
||||||
commands.slice(0, -1).find((cmd) => commandExists(cmd)) ||
|
|
||||||
commands[commands.length - 1];
|
|
||||||
|
|
||||||
switch (editor) {
|
switch (editor) {
|
||||||
case 'vscode':
|
case 'vscode':
|
||||||
|
|||||||
Reference in New Issue
Block a user