mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 11:41:04 +00:00
The "Compact Mode" label is more intuitive than "Verbose Mode" for users, as it directly describes the default compact view experience. This change inverts the boolean semantics (compactMode=false means show full output) and exposes the setting in the /settings dialog (showInDialog: true). - Rename ui.verboseMode → ui.compactMode with inverted default (false) - Rename VerboseModeContext → CompactModeContext (file and exports) - Rename TOGGLE_VERBOSE_MODE → TOGGLE_COMPACT_MODE in key bindings - Update all consumer components with inverted logic - Update i18n keys across 6 locales (verbose → compact) - Update VS Code settings schema - Add ui.compactMode documentation to settings.md - Fix Ctrl+O description in keyboard-shortcuts.md
144 lines
4.9 KiB
TypeScript
144 lines
4.9 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Qwen
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import type React from 'react';
|
|
import { Box, Text } from 'ink';
|
|
import { theme } from '../semantic-colors.js';
|
|
import { ContextUsageDisplay } from './ContextUsageDisplay.js';
|
|
import { useTerminalSize } from '../hooks/useTerminalSize.js';
|
|
import { AutoAcceptIndicator } from './AutoAcceptIndicator.js';
|
|
import { ShellModeIndicator } from './ShellModeIndicator.js';
|
|
import { isNarrowWidth } from '../utils/isNarrowWidth.js';
|
|
|
|
import { useStatusLine } from '../hooks/useStatusLine.js';
|
|
import { useUIState } from '../contexts/UIStateContext.js';
|
|
import { useConfig } from '../contexts/ConfigContext.js';
|
|
import { useVimMode } from '../contexts/VimModeContext.js';
|
|
import { useCompactMode } from '../contexts/CompactModeContext.js';
|
|
import { ApprovalMode } from '@qwen-code/qwen-code-core';
|
|
import { t } from '../../i18n/index.js';
|
|
|
|
export const Footer: React.FC = () => {
|
|
const uiState = useUIState();
|
|
const config = useConfig();
|
|
const { vimEnabled, vimMode } = useVimMode();
|
|
const { text: statusLineText } = useStatusLine();
|
|
const { compactMode } = useCompactMode();
|
|
|
|
const { promptTokenCount, showAutoAcceptIndicator } = {
|
|
promptTokenCount: uiState.sessionStats.lastPromptTokenCount,
|
|
showAutoAcceptIndicator: uiState.showAutoAcceptIndicator,
|
|
};
|
|
|
|
const { columns: terminalWidth } = useTerminalSize();
|
|
const isNarrow = isNarrowWidth(terminalWidth);
|
|
|
|
// Determine sandbox info from environment
|
|
const sandboxEnv = process.env['SANDBOX'];
|
|
const sandboxInfo = sandboxEnv
|
|
? sandboxEnv === 'sandbox-exec'
|
|
? 'seatbelt'
|
|
: sandboxEnv.startsWith('qwen-code')
|
|
? 'docker'
|
|
: sandboxEnv
|
|
: null;
|
|
|
|
// Check if debug mode is enabled
|
|
const debugMode = config.getDebugMode();
|
|
|
|
const contextWindowSize =
|
|
config.getContentGeneratorConfig()?.contextWindowSize;
|
|
|
|
// Hide "? for shortcuts" when a custom status line is active (it already
|
|
// occupies the top row, so the hint is redundant). Matches upstream behavior.
|
|
const suppressHint = !!statusLineText;
|
|
|
|
// Left bottom row: high-priority messages > approval mode > hint.
|
|
const leftBottomContent = uiState.ctrlCPressedOnce ? (
|
|
<Text color={theme.status.warning}>{t('Press Ctrl+C again to exit.')}</Text>
|
|
) : uiState.ctrlDPressedOnce ? (
|
|
<Text color={theme.status.warning}>{t('Press Ctrl+D again to exit.')}</Text>
|
|
) : uiState.showEscapePrompt ? (
|
|
<Text color={theme.text.secondary}>{t('Press Esc again to clear.')}</Text>
|
|
) : vimEnabled && vimMode === 'INSERT' ? (
|
|
<Text color={theme.text.secondary}>-- INSERT --</Text>
|
|
) : uiState.shellModeActive ? (
|
|
<ShellModeIndicator />
|
|
) : showAutoAcceptIndicator !== undefined &&
|
|
showAutoAcceptIndicator !== ApprovalMode.DEFAULT ? (
|
|
<AutoAcceptIndicator approvalMode={showAutoAcceptIndicator} />
|
|
) : suppressHint ? null : (
|
|
<Text color={theme.text.secondary}>{t('? for shortcuts')}</Text>
|
|
);
|
|
|
|
const rightItems: Array<{ key: string; node: React.ReactNode }> = [];
|
|
if (sandboxInfo) {
|
|
rightItems.push({
|
|
key: 'sandbox',
|
|
node: <Text color={theme.status.success}>🔒 {sandboxInfo}</Text>,
|
|
});
|
|
}
|
|
if (debugMode) {
|
|
rightItems.push({
|
|
key: 'debug',
|
|
node: <Text color={theme.status.warning}>Debug Mode</Text>,
|
|
});
|
|
}
|
|
if (promptTokenCount > 0 && contextWindowSize) {
|
|
rightItems.push({
|
|
key: 'context',
|
|
node: (
|
|
<Text color={theme.text.accent}>
|
|
<ContextUsageDisplay
|
|
promptTokenCount={promptTokenCount}
|
|
terminalWidth={terminalWidth}
|
|
contextWindowSize={contextWindowSize}
|
|
/>
|
|
</Text>
|
|
),
|
|
});
|
|
}
|
|
if (compactMode) {
|
|
rightItems.push({
|
|
key: 'compact',
|
|
node: <Text color={theme.text.accent}>{t('compact')}</Text>,
|
|
});
|
|
}
|
|
|
|
// Layout matches upstream: left column has status line (top) + hints/mode
|
|
// (bottom), right section has indicators. Status line and hints coexist.
|
|
return (
|
|
<Box
|
|
flexDirection={isNarrow ? 'column' : 'row'}
|
|
justifyContent={isNarrow ? 'flex-start' : 'space-between'}
|
|
width="100%"
|
|
paddingX={2}
|
|
gap={isNarrow ? 0 : 1}
|
|
>
|
|
{/* Left column — status line on top, hints/mode on bottom */}
|
|
<Box flexDirection="column" flexShrink={isNarrow ? 0 : 1}>
|
|
{statusLineText &&
|
|
!uiState.ctrlCPressedOnce &&
|
|
!uiState.ctrlDPressedOnce && (
|
|
<Text dimColor wrap="truncate">
|
|
{statusLineText}
|
|
</Text>
|
|
)}
|
|
<Text wrap="truncate">{leftBottomContent}</Text>
|
|
</Box>
|
|
|
|
{/* Right Section — never compressed */}
|
|
<Box flexShrink={0} gap={1}>
|
|
{rightItems.map(({ key, node }, index) => (
|
|
<Box key={key} alignItems="center">
|
|
{index > 0 && <Text color={theme.text.secondary}> | </Text>}
|
|
{node}
|
|
</Box>
|
|
))}
|
|
</Box>
|
|
</Box>
|
|
);
|
|
};
|