Merge branch 'main' into fix/shift-tab-windows-powershell

This commit is contained in:
LaZzyMan 2026-02-02 19:51:32 +08:00
commit 86ba86e297
352 changed files with 38309 additions and 6463 deletions

View file

@ -6,22 +6,21 @@
import { Text } from 'ink';
import { theme } from '../semantic-colors.js';
import { tokenLimit } from '@qwen-code/qwen-code-core';
export const ContextUsageDisplay = ({
promptTokenCount,
model,
terminalWidth,
contextWindowSize,
}: {
promptTokenCount: number;
model: string;
terminalWidth: number;
contextWindowSize: number;
}) => {
if (promptTokenCount === 0) {
return null;
}
const percentage = promptTokenCount / tokenLimit(model);
const percentage = promptTokenCount / contextWindowSize;
const percentageUsed = (percentage * 100).toFixed(1);
const label = terminalWidth < 100 ? '% used' : '% context used';

View file

@ -23,6 +23,7 @@ const defaultProps = {
const createMockConfig = (overrides = {}) => ({
getModel: vi.fn(() => defaultProps.model),
getDebugMode: vi.fn(() => false),
getContentGeneratorConfig: vi.fn(() => ({ contextWindowSize: 131072 })),
getMcpServers: vi.fn(() => ({})),
getBlockedMcpServers: vi.fn(() => []),
...overrides,

View file

@ -26,13 +26,11 @@ export const Footer: React.FC = () => {
const { vimEnabled, vimMode } = useVimMode();
const {
model,
errorCount,
showErrorDetails,
promptTokenCount,
showAutoAcceptIndicator,
} = {
model: config.getModel(),
errorCount: uiState.errorCount,
showErrorDetails: uiState.showErrorDetails,
promptTokenCount: uiState.sessionStats.lastPromptTokenCount,
@ -57,6 +55,9 @@ export const Footer: React.FC = () => {
// Check if debug mode is enabled
const debugMode = config.getDebugMode();
const contextWindowSize =
config.getContentGeneratorConfig()?.contextWindowSize;
// Left section should show exactly ONE thing at any time, in priority order.
const leftContent = uiState.ctrlCPressedOnce ? (
<Text color={theme.status.warning}>{t('Press Ctrl+C again to exit.')}</Text>
@ -88,15 +89,15 @@ export const Footer: React.FC = () => {
node: <Text color={theme.status.warning}>Debug Mode</Text>,
});
}
if (promptTokenCount > 0) {
if (promptTokenCount > 0 && contextWindowSize) {
rightItems.push({
key: 'context',
node: (
<Text color={theme.text.accent}>
<ContextUsageDisplay
promptTokenCount={promptTokenCount}
model={model}
terminalWidth={terminalWidth}
contextWindowSize={contextWindowSize}
/>
</Text>
),

View file

@ -36,6 +36,11 @@ vi.mock('../utils/clipboardUtils.js');
vi.mock('../contexts/UIStateContext.js', () => ({
useUIState: vi.fn(() => ({ isFeedbackDialogOpen: false })),
}));
vi.mock('../contexts/UIActionsContext.js', () => ({
useUIActions: vi.fn(() => ({
temporaryCloseFeedbackDialog: vi.fn(),
})),
}));
const mockSlashCommands: SlashCommand[] = [
{
@ -376,7 +381,7 @@ describe('InputPrompt', () => {
it('should handle Ctrl+V when clipboard has an image', async () => {
vi.mocked(clipboardUtils.clipboardHasImage).mockResolvedValue(true);
vi.mocked(clipboardUtils.saveClipboardImage).mockResolvedValue(
'/test/.gemini-clipboard/clipboard-123.png',
'/test/.qwen-clipboard/clipboard-123.png',
);
const { stdin, unmount } = renderWithProviders(
@ -436,7 +441,7 @@ describe('InputPrompt', () => {
it('should insert image path at cursor position with proper spacing', async () => {
const imagePath = path.join(
'test',
'.gemini-clipboard',
'.qwen-clipboard',
'clipboard-456.png',
);
vi.mocked(clipboardUtils.clipboardHasImage).mockResolvedValue(true);

View file

@ -37,6 +37,7 @@ import * as path from 'node:path';
import { SCREEN_READER_USER_PREFIX } from '../textConstants.js';
import { useShellFocusState } from '../contexts/ShellFocusContext.js';
import { useUIState } from '../contexts/UIStateContext.js';
import { useUIActions } from '../contexts/UIActionsContext.js';
import { FEEDBACK_DIALOG_KEYS } from '../FeedbackDialog.js';
export interface InputPromptProps {
buffer: TextBuffer;
@ -109,6 +110,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
}) => {
const isShellFocused = useShellFocusState();
const uiState = useUIState();
const uiActions = useUIActions();
const [justNavigatedHistory, setJustNavigatedHistory] = useState(false);
const [escPressCount, setEscPressCount] = useState(0);
const [showEscapePrompt, setShowEscapePrompt] = useState(false);
@ -337,12 +339,16 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
return;
}
// Intercept feedback dialog option keys (1, 2) when dialog is open
if (
uiState.isFeedbackDialogOpen &&
(FEEDBACK_DIALOG_KEYS as readonly string[]).includes(key.name)
) {
return;
// Handle feedback dialog keyboard interactions when dialog is open
if (uiState.isFeedbackDialogOpen) {
// If it's one of the feedback option keys (1-4), let FeedbackDialog handle it
if ((FEEDBACK_DIALOG_KEYS as readonly string[]).includes(key.name)) {
return;
} else {
// For any other key, close feedback dialog temporarily and continue with normal processing
uiActions.temporaryCloseFeedbackDialog();
// Continue processing the key for normal input handling
}
}
// Reset ESC count and hide prompt on any non-ESC key
@ -712,6 +718,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
onToggleShortcuts,
showShortcuts,
uiState,
uiActions,
],
);

View file

@ -1368,7 +1368,7 @@ describe('SettingsDialog', () => {
enabled: true,
},
context: {
loadMemoryFromIncludeDirectories: true,
loadFromIncludeDirectories: true,
fileFiltering: {
respectGitIgnore: true,
respectQwenIgnore: true,
@ -1540,7 +1540,7 @@ describe('SettingsDialog', () => {
enableRecursiveFileSearch: false,
disableFuzzySearch: true,
},
loadMemoryFromIncludeDirectories: true,
loadFromIncludeDirectories: true,
},
});
const onSelect = vi.fn();
@ -1605,7 +1605,7 @@ describe('SettingsDialog', () => {
enabled: false,
},
context: {
loadMemoryFromIncludeDirectories: false,
loadFromIncludeDirectories: false,
fileFiltering: {
respectGitIgnore: false,
respectQwenIgnore: false,