mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-30 20:50:34 +00:00
Merge branch 'main' into feat/mcp-tui
This commit is contained in:
commit
7b227a7eb5
298 changed files with 28262 additions and 6219 deletions
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useCallback, useMemo, useEffect, useState } from 'react';
|
||||
import { useCallback, useMemo, useEffect, useRef, useState } from 'react';
|
||||
import { type PartListUnion } from '@google/genai';
|
||||
import type { UseHistoryManagerReturn } from './useHistoryManager.js';
|
||||
import {
|
||||
|
|
@ -35,6 +35,7 @@ import { FileCommandLoader } from '../../services/FileCommandLoader.js';
|
|||
import { McpPromptLoader } from '../../services/McpPromptLoader.js';
|
||||
import { parseSlashCommand } from '../../utils/commands.js';
|
||||
import { clearScreen } from '../../utils/stdioHelpers.js';
|
||||
import { useKeypress } from './useKeypress.js';
|
||||
import {
|
||||
type ExtensionUpdateAction,
|
||||
type ExtensionUpdateStatus,
|
||||
|
|
@ -91,6 +92,7 @@ export const useSlashCommandProcessor = (
|
|||
loadHistory: UseHistoryManagerReturn['loadHistory'],
|
||||
refreshStatic: () => void,
|
||||
toggleVimEnabled: () => Promise<boolean>,
|
||||
isProcessing: boolean,
|
||||
setIsProcessing: (isProcessing: boolean) => void,
|
||||
setGeminiMdFileCount: (count: number) => void,
|
||||
actions: SlashCommandProcessorActions,
|
||||
|
|
@ -132,6 +134,34 @@ export const useSlashCommandProcessor = (
|
|||
null,
|
||||
);
|
||||
|
||||
// AbortController for cancelling async slash commands via ESC
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
|
||||
const cancelSlashCommand = useCallback(() => {
|
||||
if (!abortControllerRef.current) {
|
||||
return;
|
||||
}
|
||||
abortControllerRef.current.abort();
|
||||
addItem(
|
||||
{
|
||||
type: MessageType.INFO,
|
||||
text: 'Command cancelled.',
|
||||
},
|
||||
Date.now(),
|
||||
);
|
||||
setPendingItem(null);
|
||||
setIsProcessing(false);
|
||||
}, [addItem, setIsProcessing]);
|
||||
|
||||
useKeypress(
|
||||
(key) => {
|
||||
if (key.name === 'escape') {
|
||||
cancelSlashCommand();
|
||||
}
|
||||
},
|
||||
{ isActive: isProcessing },
|
||||
);
|
||||
|
||||
const pendingHistoryItems = useMemo(() => {
|
||||
const items: HistoryItemWithoutId[] = [];
|
||||
if (pendingItem != null) {
|
||||
|
|
@ -182,6 +212,11 @@ export const useSlashCommandProcessor = (
|
|||
type: 'summary',
|
||||
summary: message.summary,
|
||||
};
|
||||
} else if (message.type === MessageType.INSIGHT_PROGRESS) {
|
||||
historyItemContent = {
|
||||
type: 'insight_progress',
|
||||
progress: message.progress,
|
||||
};
|
||||
} else {
|
||||
historyItemContent = {
|
||||
type: message.type,
|
||||
|
|
@ -320,6 +355,10 @@ export const useSlashCommandProcessor = (
|
|||
|
||||
setIsProcessing(true);
|
||||
|
||||
// Create a new AbortController for this command execution
|
||||
const abortController = new AbortController();
|
||||
abortControllerRef.current = abortController;
|
||||
|
||||
const userMessageTimestamp = Date.now();
|
||||
addItemWithRecording(
|
||||
{ type: MessageType.USER, text: trimmed },
|
||||
|
|
@ -353,6 +392,7 @@ export const useSlashCommandProcessor = (
|
|||
args,
|
||||
},
|
||||
overwriteConfirmed,
|
||||
abortSignal: abortController.signal,
|
||||
};
|
||||
|
||||
// If a one-time list is provided for a "Proceed" action, temporarily
|
||||
|
|
@ -366,10 +406,27 @@ export const useSlashCommandProcessor = (
|
|||
]),
|
||||
};
|
||||
}
|
||||
const result = await commandToExecute.action(
|
||||
fullCommandContext,
|
||||
args,
|
||||
);
|
||||
// Race the command action against the abort signal so that
|
||||
// ESC cancellation immediately unblocks the await chain.
|
||||
// Without this, commands like /compress whose underlying
|
||||
// operation (tryCompressChat) doesn't accept an AbortSignal
|
||||
// would keep submitQuery stuck until the operation completes.
|
||||
const abortPromise = new Promise<undefined>((resolve) => {
|
||||
abortController.signal.addEventListener(
|
||||
'abort',
|
||||
() => resolve(undefined),
|
||||
{ once: true },
|
||||
);
|
||||
});
|
||||
const result = await Promise.race([
|
||||
commandToExecute.action(fullCommandContext, args),
|
||||
abortPromise,
|
||||
]);
|
||||
|
||||
// If the command was cancelled via ESC while executing, skip result processing
|
||||
if (abortController.signal.aborted) {
|
||||
return { type: 'handled' };
|
||||
}
|
||||
|
||||
if (result) {
|
||||
switch (result.type) {
|
||||
|
|
@ -565,6 +622,10 @@ export const useSlashCommandProcessor = (
|
|||
|
||||
return { type: 'handled' };
|
||||
} catch (e: unknown) {
|
||||
// If cancelled via ESC, the cancelSlashCommand callback already handled cleanup
|
||||
if (abortController.signal.aborted) {
|
||||
return { type: 'handled' };
|
||||
}
|
||||
hasError = true;
|
||||
if (config) {
|
||||
const event = makeSlashCommandEvent({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue