mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 08:47:44 +00:00
feat: Add flow to allow modifying edits during edit tool call (#808)
This commit is contained in:
@@ -172,6 +172,7 @@ export async function loadCliConfig(
|
||||
fileFilteringRespectGitIgnore: settings.fileFiltering?.respectGitIgnore,
|
||||
fileFilteringAllowBuildArtifacts:
|
||||
settings.fileFiltering?.allowBuildArtifacts,
|
||||
enableModifyWithExternalEditors: settings.enableModifyWithExternalEditors,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ export interface Settings {
|
||||
contextFileName?: string;
|
||||
accessibility?: AccessibilitySettings;
|
||||
telemetry?: boolean;
|
||||
enableModifyWithExternalEditors?: boolean;
|
||||
|
||||
// Git-aware file filtering settings
|
||||
fileFiltering?: {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user