diff options
| author | Leo <[email protected]> | 2025-06-08 18:56:58 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-06-08 10:56:58 -0700 |
| commit | 9efca40dae2e75477af1a20df4e3e65bf8dfe93d (patch) | |
| tree | 39e10eef42ddfd4b9c73b7c2410dd5bccb5ed900 /packages/cli/src | |
| parent | 584286cfd9b53eee8f1189a3ad98462c77eb8fb9 (diff) | |
feat: Add flow to allow modifying edits during edit tool call (#808)
Diffstat (limited to 'packages/cli/src')
| -rw-r--r-- | packages/cli/src/config/config.ts | 1 | ||||
| -rw-r--r-- | packages/cli/src/config/settings.ts | 1 | ||||
| -rw-r--r-- | packages/cli/src/ui/App.tsx | 2 | ||||
| -rw-r--r-- | packages/cli/src/ui/components/HistoryItemDisplay.tsx | 4 | ||||
| -rw-r--r-- | packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx | 45 | ||||
| -rw-r--r-- | packages/cli/src/ui/components/messages/ToolGroupMessage.tsx | 4 | ||||
| -rw-r--r-- | packages/cli/src/ui/components/shared/text-buffer.ts | 4 |
7 files changed, 57 insertions, 4 deletions
diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 49004776..009da59f 100644 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -172,6 +172,7 @@ export async function loadCliConfig( fileFilteringRespectGitIgnore: settings.fileFiltering?.respectGitIgnore, fileFilteringAllowBuildArtifacts: settings.fileFiltering?.allowBuildArtifacts, + enableModifyWithExternalEditors: settings.enableModifyWithExternalEditors, }); } diff --git a/packages/cli/src/config/settings.ts b/packages/cli/src/config/settings.ts index c56a9767..57b9b4fc 100644 --- a/packages/cli/src/config/settings.ts +++ b/packages/cli/src/config/settings.ts @@ -37,6 +37,7 @@ export interface Settings { contextFileName?: string; accessibility?: AccessibilitySettings; telemetry?: boolean; + enableModifyWithExternalEditors?: boolean; // Git-aware file filtering settings fileFiltering?: { diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index d0e9efb7..365266f8 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -355,6 +355,7 @@ export const App = ({ config, settings, startupWarnings = [] }: AppProps) => { key={h.id} item={h} isPending={false} + config={config} /> )), ]} @@ -370,6 +371,7 @@ export const App = ({ config, settings, startupWarnings = [] }: AppProps) => { // HistoryItemDisplay. Refactor later. Use a fake id for now. item={{ ...item, id: 0 }} isPending={true} + config={config} /> ))} </Box> diff --git a/packages/cli/src/ui/components/HistoryItemDisplay.tsx b/packages/cli/src/ui/components/HistoryItemDisplay.tsx index 39cc5308..5ab6b3c9 100644 --- a/packages/cli/src/ui/components/HistoryItemDisplay.tsx +++ b/packages/cli/src/ui/components/HistoryItemDisplay.tsx @@ -15,17 +15,20 @@ import { ToolGroupMessage } from './messages/ToolGroupMessage.js'; import { GeminiMessageContent } from './messages/GeminiMessageContent.js'; import { Box } from 'ink'; import { AboutBox } from './AboutBox.js'; +import { Config } from '@gemini-cli/core'; interface HistoryItemDisplayProps { item: HistoryItem; availableTerminalHeight: number; isPending: boolean; + config?: Config; } export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({ item, availableTerminalHeight, isPending, + config, }) => ( <Box flexDirection="column" key={item.id}> {/* Render standard message types */} @@ -60,6 +63,7 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({ toolCalls={item.tools} groupId={item.id} availableTerminalHeight={availableTerminalHeight} + config={config} /> )} </Box> diff --git a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx index 01372290..c46d36f7 100644 --- a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx @@ -13,6 +13,8 @@ import { ToolConfirmationOutcome, ToolExecuteConfirmationDetails, ToolMcpConfirmationDetails, + checkHasEditor, + Config, } from '@gemini-cli/core'; import { RadioButtonSelect, @@ -21,11 +23,12 @@ import { export interface ToolConfirmationMessageProps { confirmationDetails: ToolCallConfirmationDetails; + config?: Config; } export const ToolConfirmationMessage: React.FC< ToolConfirmationMessageProps -> = ({ confirmationDetails }) => { +> = ({ confirmationDetails, config }) => { const { onConfirm } = confirmationDetails; useInput((_, key) => { @@ -44,6 +47,24 @@ export const ToolConfirmationMessage: React.FC< >(); if (confirmationDetails.type === 'edit') { + if (confirmationDetails.isModifying) { + return ( + <Box + minWidth="90%" + borderStyle="round" + borderColor={Colors.Gray} + justifyContent="space-around" + padding={1} + overflow="hidden" + > + <Text>Modify in progress: </Text> + <Text color={Colors.AccentGreen}> + Save and close external editor to continue + </Text> + </Box> + ); + } + // Body content is now the DiffRenderer, passing filename to it // The bordered box is removed from here and handled within DiffRenderer bodyContent = ( @@ -63,8 +84,28 @@ export const ToolConfirmationMessage: React.FC< label: 'Yes, allow always', value: ToolConfirmationOutcome.ProceedAlways, }, - { label: 'No (esc)', value: ToolConfirmationOutcome.Cancel }, ); + + // Conditionally add editor options if editors are installed + const notUsingSandbox = !process.env.SANDBOX; + const externalEditorsEnabled = + config?.getEnableModifyWithExternalEditors() ?? false; + + if (checkHasEditor('vscode') && notUsingSandbox && externalEditorsEnabled) { + options.push({ + label: 'Modify with VS Code', + value: ToolConfirmationOutcome.ModifyVSCode, + }); + } + + if (checkHasEditor('vim') && externalEditorsEnabled) { + options.push({ + label: 'Modify with vim', + value: ToolConfirmationOutcome.ModifyVim, + }); + } + + options.push({ label: 'No (esc)', value: ToolConfirmationOutcome.Cancel }); } else if (confirmationDetails.type === 'exec') { const executionProps = confirmationDetails as ToolExecuteConfirmationDetails; diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx index 71e6a59b..46fcecff 100644 --- a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx @@ -10,17 +10,20 @@ import { IndividualToolCallDisplay, ToolCallStatus } from '../../types.js'; import { ToolMessage } from './ToolMessage.js'; import { ToolConfirmationMessage } from './ToolConfirmationMessage.js'; import { Colors } from '../../colors.js'; +import { Config } from '@gemini-cli/core'; interface ToolGroupMessageProps { groupId: number; toolCalls: IndividualToolCallDisplay[]; availableTerminalHeight: number; + config?: Config; } // Main component renders the border and maps the tools using ToolMessage export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({ toolCalls, availableTerminalHeight, + config, }) => { const hasPending = !toolCalls.every( (t) => t.status === ToolCallStatus.Success, @@ -80,6 +83,7 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({ tool.confirmationDetails && ( <ToolConfirmationMessage confirmationDetails={tool.confirmationDetails} + config={config} /> )} </Box> diff --git a/packages/cli/src/ui/components/shared/text-buffer.ts b/packages/cli/src/ui/components/shared/text-buffer.ts index 27565324..e5957c7d 100644 --- a/packages/cli/src/ui/components/shared/text-buffer.ts +++ b/packages/cli/src/ui/components/shared/text-buffer.ts @@ -1440,7 +1440,7 @@ export interface TextBuffer { key: Record<string, boolean>, ) => boolean; /** - * Opens the current buffer contents in the user’s preferred terminal text + * Opens the current buffer contents in the user's preferred terminal text * editor ($VISUAL or $EDITOR, falling back to "vi"). The method blocks * until the editor exits, then reloads the file and replaces the in‑memory * buffer with whatever the user saved. @@ -1451,7 +1451,7 @@ export interface TextBuffer { * * Note: We purposefully rely on the *synchronous* spawn API so that the * calling process genuinely waits for the editor to close before - * 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). */ openInExternalEditor: (opts?: { editor?: string }) => Promise<void>; |
