feat(coding-agent): add external editor setting

Closes #6122
This commit is contained in:
Armin Ronacher
2026-06-27 23:57:46 +02:00
parent f2e9d75388
commit 5a073885b5
9 changed files with 91 additions and 9 deletions
@@ -90,6 +90,7 @@ export interface Settings {
branchSummary?: BranchSummarySettings;
retry?: RetrySettings;
hideThinkingBlock?: boolean;
externalEditor?: string; // Command for Ctrl+G external editor; takes precedence over VISUAL/EDITOR
shellPath?: string; // Custom shell path (e.g., for Cygwin users on Windows)
quietStartup?: boolean;
defaultProjectTrust?: DefaultProjectTrust; // default: "ask"; global setting only
@@ -841,6 +842,18 @@ export class SettingsManager {
return this.settings.hideThinkingBlock ?? false;
}
getExternalEditorCommand(): string | undefined {
const configuredEditor = this.settings.externalEditor;
if (typeof configuredEditor === "string" && configuredEditor.trim() !== "") {
return configuredEditor;
}
const environmentEditor = process.env.VISUAL || process.env.EDITOR;
if (environmentEditor) {
return environmentEditor;
}
return process.platform === "win32" ? "notepad" : "nano";
}
setHideThinkingBlock(hide: boolean): void {
this.globalSettings.hideThinkingBlock = hide;
this.markModified("hideThinkingBlock");
@@ -28,6 +28,7 @@ export class ExtensionEditorComponent extends Container implements Focusable {
private onCancelCallback: () => void;
private tui: TUI;
private keybindings: KeybindingsManager;
private externalEditorCommand: string | undefined;
private _focused = false;
get focused(): boolean {
@@ -46,11 +47,13 @@ export class ExtensionEditorComponent extends Container implements Focusable {
onSubmit: (value: string) => void,
onCancel: () => void,
options?: EditorOptions,
externalEditorCommand?: string,
) {
super();
this.tui = tui;
this.keybindings = keybindings;
this.externalEditorCommand = externalEditorCommand;
this.onSubmitCallback = onSubmit;
this.onCancelCallback = onCancel;
@@ -76,7 +79,7 @@ export class ExtensionEditorComponent extends Container implements Focusable {
this.addChild(new Spacer(1));
// Add hint
const hasExternalEditor = !!(process.env.VISUAL || process.env.EDITOR);
const hasExternalEditor = !!this.getExternalEditorCommand();
const hint =
keyHint("tui.select.confirm", "submit") +
" " +
@@ -110,8 +113,16 @@ export class ExtensionEditorComponent extends Container implements Focusable {
this.editor.handleInput(keyData);
}
private getExternalEditorCommand(): string | undefined {
const editorCmd = this.externalEditorCommand || process.env.VISUAL || process.env.EDITOR;
if (editorCmd) {
return editorCmd;
}
return process.platform === "win32" ? "notepad" : "nano";
}
private async openExternalEditor(): Promise<void> {
const editorCmd = process.env.VISUAL || process.env.EDITOR;
const editorCmd = this.getExternalEditorCommand();
if (!editorCmd) {
return;
}
@@ -2239,6 +2239,8 @@ export class InteractiveMode {
this.hideExtensionEditor();
resolve(undefined);
},
undefined,
this.settingsManager.getExternalEditorCommand(),
);
this.editorContainer.clear();
@@ -3647,10 +3649,9 @@ export class InteractiveMode {
}
private async openExternalEditor(): Promise<void> {
// Determine editor (respect $VISUAL, then $EDITOR)
const editorCmd = process.env.VISUAL || process.env.EDITOR;
const editorCmd = this.settingsManager.getExternalEditorCommand();
if (!editorCmd) {
this.showWarning("No editor configured. Set $VISUAL or $EDITOR environment variable.");
this.showWarning("No editor configured. Set externalEditor in settings.json or $VISUAL/$EDITOR.");
return;
}