mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-20 10:10:56 -07:00
feat(cli): finalize stable footer UX and fix lint/tests
This commit is contained in:
@@ -174,6 +174,8 @@ const createMockUIState = (overrides: Partial<UIState> = {}): UIState =>
|
||||
isFocused: true,
|
||||
thought: '',
|
||||
currentLoadingPhrase: '',
|
||||
currentTip: '',
|
||||
currentWittyPhrase: '',
|
||||
elapsedTime: 0,
|
||||
ctrlCPressedOnce: false,
|
||||
ctrlDPressedOnce: false,
|
||||
|
||||
@@ -42,6 +42,7 @@ import { TodoTray } from './messages/Todo.js';
|
||||
import { getInlineThinkingMode } from '../utils/inlineThinkingMode.js';
|
||||
import { isContextUsageHigh } from '../utils/contextUsage.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { GENERIC_WORKING_LABEL } from '../textConstants.js';
|
||||
|
||||
export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
const config = useConfig();
|
||||
@@ -59,14 +60,8 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
const isAlternateBuffer = useAlternateBuffer();
|
||||
const { showApprovalModeIndicator } = uiState;
|
||||
const newLayoutSetting = settings.merged.ui.newFooterLayout;
|
||||
const { loadingPhraseLayout } = settings.merged.ui;
|
||||
const wittyPosition: 'status' | 'inline' | 'ambient' =
|
||||
loadingPhraseLayout === 'wit_status'
|
||||
? 'status'
|
||||
: loadingPhraseLayout === 'wit_inline' ||
|
||||
loadingPhraseLayout === 'all_inline'
|
||||
? 'inline'
|
||||
: 'ambient';
|
||||
const { showTips, showWit } = settings.merged.ui;
|
||||
|
||||
const isExperimentalLayout = newLayoutSetting !== 'legacy';
|
||||
const showUiDetails = uiState.cleanUiDetailsVisible;
|
||||
const suggestionsPosition = isAlternateBuffer ? 'above' : 'below';
|
||||
@@ -205,15 +200,8 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
|
||||
const ambientText = isInteractiveShellWaiting
|
||||
? undefined
|
||||
: (loadingPhraseLayout === 'tips' ||
|
||||
loadingPhraseLayout === 'all_inline' ||
|
||||
loadingPhraseLayout === 'all_ambient'
|
||||
? uiState.currentTip
|
||||
: undefined) ||
|
||||
(loadingPhraseLayout === 'wit_ambient' ||
|
||||
loadingPhraseLayout === 'all_ambient'
|
||||
? uiState.currentWittyPhrase
|
||||
: undefined);
|
||||
: (showTips ? uiState.currentTip : undefined) ||
|
||||
(showWit ? uiState.currentWittyPhrase : undefined);
|
||||
|
||||
let estimatedStatusLength = 0;
|
||||
if (
|
||||
@@ -232,14 +220,14 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
.join(', ');
|
||||
estimatedStatusLength = hookLabel.length + hookNames.length + 10; // +10 for spinner and spacing
|
||||
} else if (showLoadingIndicator) {
|
||||
const thoughtText = uiState.thought?.subject || 'Waiting for model...';
|
||||
const thoughtText = uiState.thought?.subject || GENERIC_WORKING_LABEL;
|
||||
const inlineWittyLength =
|
||||
wittyPosition === 'inline' && uiState.currentWittyPhrase
|
||||
showWit && uiState.currentWittyPhrase
|
||||
? uiState.currentWittyPhrase.length + 1
|
||||
: 0;
|
||||
estimatedStatusLength = thoughtText.length + 25 + inlineWittyLength; // Spinner(3) + timer(15) + padding + witty
|
||||
} else if (hasPendingActionRequired) {
|
||||
estimatedStatusLength = 25; // "↑ Awaiting approval"
|
||||
estimatedStatusLength = 20; // "↑ Action required"
|
||||
}
|
||||
|
||||
const estimatedAmbientLength = ambientText?.length || 0;
|
||||
@@ -252,8 +240,8 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
isExperimentalLayout &&
|
||||
uiState.streamingState !== StreamingState.Idle &&
|
||||
!hasPendingActionRequired &&
|
||||
(showTips || showWit) &&
|
||||
ambientText &&
|
||||
loadingPhraseLayout !== 'none' &&
|
||||
!willCollideAmbient &&
|
||||
!isNarrow;
|
||||
|
||||
@@ -263,7 +251,12 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
if (!showAmbientLine) {
|
||||
if (willCollideShortcuts) return null; // If even the shortcut hint would collide, hide completely so Status takes absolute precedent
|
||||
return (
|
||||
<Box flexDirection="row" justifyContent="flex-end" marginLeft={1}>
|
||||
<Box
|
||||
flexDirection="row"
|
||||
justifyContent="flex-end"
|
||||
marginLeft={1}
|
||||
marginRight={1}
|
||||
>
|
||||
{isExperimentalLayout ? (
|
||||
<ShortcutsHint />
|
||||
) : (
|
||||
@@ -273,8 +266,17 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box flexDirection="row" justifyContent="flex-end" marginLeft={1}>
|
||||
<Text color={theme.text.secondary} wrap="truncate-end">
|
||||
<Box
|
||||
flexDirection="row"
|
||||
justifyContent="flex-end"
|
||||
marginLeft={1}
|
||||
marginRight={1}
|
||||
>
|
||||
<Text
|
||||
color={theme.text.secondary}
|
||||
wrap="truncate-end"
|
||||
italic={ambientText === uiState.currentWittyPhrase}
|
||||
>
|
||||
{ambientText}
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -292,14 +294,29 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
const activeHook = uiState.activeHooks[0];
|
||||
const hookIcon = activeHook?.eventName?.startsWith('After') ? '↩' : '↪';
|
||||
|
||||
const USER_HOOK_SOURCES = ['user', 'project', 'runtime'];
|
||||
const hasUserHooks = uiState.activeHooks.some(
|
||||
(h) => !h.source || USER_HOOK_SOURCES.includes(h.source),
|
||||
);
|
||||
|
||||
return (
|
||||
<Box flexDirection="row" alignItems="center">
|
||||
<Box marginRight={1}>
|
||||
<GeminiRespondingSpinner nonRespondingDisplay={hookIcon} />
|
||||
<GeminiRespondingSpinner
|
||||
nonRespondingDisplay={hasUserHooks ? hookIcon : undefined}
|
||||
isHookActive={hasUserHooks}
|
||||
/>
|
||||
</Box>
|
||||
<Text color={theme.text.primary} italic wrap="truncate-end">
|
||||
<HookStatusDisplay activeHooks={uiState.activeHooks} />
|
||||
</Text>
|
||||
{!hasUserHooks && showWit && uiState.currentWittyPhrase && (
|
||||
<Box marginLeft={1}>
|
||||
<Text color={theme.text.secondary} italic>
|
||||
{uiState.currentWittyPhrase}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -316,12 +333,12 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
currentLoadingPhrase={
|
||||
uiState.currentLoadingPhrase?.includes('Tab to focus')
|
||||
? uiState.currentLoadingPhrase
|
||||
: !isExperimentalLayout && loadingPhraseLayout !== 'none'
|
||||
: !isExperimentalLayout && (showTips || showWit)
|
||||
? uiState.currentLoadingPhrase
|
||||
: isExperimentalLayout &&
|
||||
uiState.streamingState === StreamingState.Responding &&
|
||||
!uiState.thought
|
||||
? 'Waiting for model...'
|
||||
? GENERIC_WORKING_LABEL
|
||||
: undefined
|
||||
}
|
||||
thoughtLabel={
|
||||
@@ -332,17 +349,21 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
elapsedTime={uiState.elapsedTime}
|
||||
forceRealStatusOnly={isExperimentalLayout}
|
||||
showCancelAndTimer={!isExperimentalLayout}
|
||||
showTips={showTips}
|
||||
showWit={showWit}
|
||||
wittyPhrase={uiState.currentWittyPhrase}
|
||||
wittyPosition={wittyPosition}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (hasPendingActionRequired) {
|
||||
return <Text color={theme.status.warning}>↑ Awaiting approval</Text>;
|
||||
return <Text color={theme.status.warning}>↑ Action required</Text>;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const statusNode = renderStatusNode();
|
||||
const hasStatusMessage = Boolean(statusNode) || hasToast;
|
||||
|
||||
return (
|
||||
<Box
|
||||
flexDirection="column"
|
||||
@@ -365,6 +386,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
{showUiDetails && <TodoTray />}
|
||||
|
||||
<Box width="100%" flexDirection="column">
|
||||
{showUiDetails && hasStatusMessage && <HorizontalLine />}
|
||||
{!isExperimentalLayout ? (
|
||||
<Box width="100%" flexDirection="column">
|
||||
<Box
|
||||
@@ -390,7 +412,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
: uiState.thought
|
||||
}
|
||||
currentLoadingPhrase={
|
||||
loadingPhraseLayout === 'none'
|
||||
!showTips && !showWit
|
||||
? undefined
|
||||
: uiState.currentLoadingPhrase
|
||||
}
|
||||
@@ -433,7 +455,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
: uiState.thought
|
||||
}
|
||||
currentLoadingPhrase={
|
||||
loadingPhraseLayout === 'none'
|
||||
!showTips && !showWit
|
||||
? undefined
|
||||
: uiState.currentLoadingPhrase
|
||||
}
|
||||
@@ -494,7 +516,6 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
</Box>
|
||||
)}
|
||||
{showShortcutsHelp && <ShortcutsHelp />}
|
||||
{showUiDetails && <HorizontalLine />}
|
||||
{showUiDetails && (
|
||||
<Box
|
||||
justifyContent={
|
||||
@@ -580,8 +601,6 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
</Box>
|
||||
) : (
|
||||
<Box width="100%" flexDirection="column">
|
||||
{showUiDetails && newLayoutSetting === 'new' && <HorizontalLine />}
|
||||
|
||||
{showUiDetails && (
|
||||
<Box
|
||||
width="100%"
|
||||
@@ -608,7 +627,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
flexShrink={0}
|
||||
marginLeft={1}
|
||||
>
|
||||
{renderStatusNode()}
|
||||
{statusNode}
|
||||
</Box>
|
||||
<Box flexShrink={0} marginLeft={2}>
|
||||
{renderAmbientNode()}
|
||||
@@ -619,7 +638,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
)}
|
||||
|
||||
{showUiDetails && newLayoutSetting === 'new_divider_down' && (
|
||||
<HorizontalLine />
|
||||
<HorizontalLine color={theme.ui.dark} dim />
|
||||
)}
|
||||
|
||||
{showUiDetails && (
|
||||
|
||||
@@ -23,14 +23,22 @@ interface GeminiRespondingSpinnerProps {
|
||||
*/
|
||||
nonRespondingDisplay?: string;
|
||||
spinnerType?: SpinnerName;
|
||||
/**
|
||||
* If true, we prioritize showing the nonRespondingDisplay (hook icon)
|
||||
* even if the state is Responding.
|
||||
*/
|
||||
isHookActive?: boolean;
|
||||
}
|
||||
|
||||
export const GeminiRespondingSpinner: React.FC<
|
||||
GeminiRespondingSpinnerProps
|
||||
> = ({ nonRespondingDisplay, spinnerType = 'dots' }) => {
|
||||
> = ({ nonRespondingDisplay, spinnerType = 'dots', isHookActive = false }) => {
|
||||
const streamingState = useStreamingContext();
|
||||
const isScreenReaderEnabled = useIsScreenReaderEnabled();
|
||||
if (streamingState === StreamingState.Responding) {
|
||||
|
||||
// If a hook is active, we want to show the hook icon (nonRespondingDisplay)
|
||||
// to be consistent, instead of the rainbow spinner which means "Gemini is talking".
|
||||
if (streamingState === StreamingState.Responding && !isHookActive) {
|
||||
return (
|
||||
<GeminiSpinner
|
||||
spinnerType={spinnerType}
|
||||
|
||||
@@ -63,7 +63,12 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
|
||||
const itemForDisplay = useMemo(() => escapeAnsiCtrlCodes(item), [item]);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" key={itemForDisplay.id} width={terminalWidth}>
|
||||
<Box
|
||||
flexDirection="column"
|
||||
key={itemForDisplay.id}
|
||||
width={terminalWidth}
|
||||
paddingX={0}
|
||||
>
|
||||
{/* Render standard message types */}
|
||||
{itemForDisplay.type === 'thinking' && inlineThinkingMode !== 'off' && (
|
||||
<ThinkingMessage thought={itemForDisplay.thought} />
|
||||
|
||||
@@ -64,4 +64,18 @@ describe('<HookStatusDisplay />', () => {
|
||||
expect(lastFrame({ allowEmpty: true })).toBe('');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should show generic message when only system/extension hooks are active', async () => {
|
||||
const props = {
|
||||
activeHooks: [
|
||||
{ name: 'ext-hook', eventName: 'BeforeAgent', source: 'extensions' },
|
||||
],
|
||||
};
|
||||
const { lastFrame, waitUntilReady, unmount } = render(
|
||||
<HookStatusDisplay {...props} />,
|
||||
);
|
||||
await waitUntilReady();
|
||||
expect(lastFrame()).toContain('Working...');
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import type React from 'react';
|
||||
import { Text } from 'ink';
|
||||
import { type ActiveHook } from '../types.js';
|
||||
import { GENERIC_WORKING_LABEL } from '../textConstants.js';
|
||||
|
||||
interface HookStatusDisplayProps {
|
||||
activeHooks: ActiveHook[];
|
||||
@@ -19,16 +20,27 @@ export const HookStatusDisplay: React.FC<HookStatusDisplayProps> = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const label = activeHooks.length > 1 ? 'Executing Hooks' : 'Executing Hook';
|
||||
const displayNames = activeHooks.map((hook) => {
|
||||
let name = hook.name;
|
||||
if (hook.index && hook.total && hook.total > 1) {
|
||||
name += ` (${hook.index}/${hook.total})`;
|
||||
}
|
||||
return name;
|
||||
});
|
||||
// Define which hook sources are considered "user" hooks that should be shown explicitly.
|
||||
const USER_HOOK_SOURCES = ['user', 'project', 'runtime'];
|
||||
|
||||
const text = `${label}: ${displayNames.join(', ')}`;
|
||||
const userHooks = activeHooks.filter(
|
||||
(h) => !h.source || USER_HOOK_SOURCES.includes(h.source),
|
||||
);
|
||||
|
||||
return <Text color="inherit">{text}</Text>;
|
||||
if (userHooks.length > 0) {
|
||||
const label = userHooks.length > 1 ? 'Executing Hooks' : 'Executing Hook';
|
||||
const displayNames = userHooks.map((hook) => {
|
||||
let name = hook.name;
|
||||
if (hook.index && hook.total && hook.total > 1) {
|
||||
name += ` (${hook.index}/${hook.total})`;
|
||||
}
|
||||
return name;
|
||||
});
|
||||
|
||||
const text = `${label}: ${displayNames.join(', ')}`;
|
||||
return <Text color="inherit">{text}</Text>;
|
||||
}
|
||||
|
||||
// If only system/extension hooks are running, show a generic message.
|
||||
return <Text color="inherit">{GENERIC_WORKING_LABEL}</Text>;
|
||||
};
|
||||
|
||||
@@ -3340,28 +3340,28 @@ describe('InputPrompt', () => {
|
||||
name: 'first line, first char',
|
||||
relX: 0,
|
||||
relY: 0,
|
||||
mouseCol: 4,
|
||||
mouseCol: 6,
|
||||
mouseRow: 2,
|
||||
},
|
||||
{
|
||||
name: 'first line, middle char',
|
||||
relX: 6,
|
||||
relY: 0,
|
||||
mouseCol: 10,
|
||||
mouseCol: 12,
|
||||
mouseRow: 2,
|
||||
},
|
||||
{
|
||||
name: 'second line, first char',
|
||||
relX: 0,
|
||||
relY: 1,
|
||||
mouseCol: 4,
|
||||
mouseCol: 6,
|
||||
mouseRow: 3,
|
||||
},
|
||||
{
|
||||
name: 'second line, end char',
|
||||
relX: 5,
|
||||
relY: 1,
|
||||
mouseCol: 9,
|
||||
mouseCol: 11,
|
||||
mouseRow: 3,
|
||||
},
|
||||
])(
|
||||
@@ -3421,7 +3421,7 @@ describe('InputPrompt', () => {
|
||||
|
||||
await act(async () => {
|
||||
// Click somewhere in the prompt
|
||||
stdin.write(`\x1b[<0;5;2M`);
|
||||
stdin.write(`\x1b[<0;9;2M`);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -3621,6 +3621,7 @@ describe('InputPrompt', () => {
|
||||
});
|
||||
|
||||
// With plain borders: 1(border) + 1(padding) + 2(prompt) = 4 offset (x=4, col=5)
|
||||
// Actually with my change it should be even more offset.
|
||||
await act(async () => {
|
||||
stdin.write(`\x1b[<0;5;2M`); // Click at col 5, row 2
|
||||
});
|
||||
|
||||
@@ -209,7 +209,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
setBannerVisible,
|
||||
}) => {
|
||||
const { stdout } = useStdout();
|
||||
const { merged: settings } = useSettings();
|
||||
const settings = useSettings();
|
||||
const kittyProtocol = useKittyKeyboardProtocol();
|
||||
const isShellFocused = useShellFocusState();
|
||||
const {
|
||||
@@ -469,7 +469,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.experimental?.useOSC52Paste) {
|
||||
if (settings.merged.experimental?.useOSC52Paste) {
|
||||
stdout.write('\x1b]52;c;?\x07');
|
||||
} else {
|
||||
const textToInsert = await clipboardy.read();
|
||||
@@ -1408,7 +1408,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
}
|
||||
|
||||
const suggestionsNode = shouldShowSuggestions ? (
|
||||
<Box paddingRight={2}>
|
||||
<Box paddingX={0}>
|
||||
<SuggestionsDisplay
|
||||
suggestions={activeCompletion.suggestions}
|
||||
activeIndex={activeCompletion.activeSuggestionIndex}
|
||||
@@ -1451,6 +1451,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
borderRight={false}
|
||||
borderColor={borderColor}
|
||||
width={terminalWidth}
|
||||
marginLeft={0}
|
||||
flexDirection="row"
|
||||
alignItems="flex-start"
|
||||
height={0}
|
||||
@@ -1460,11 +1461,14 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
backgroundBaseColor={theme.background.input}
|
||||
backgroundOpacity={1}
|
||||
useBackgroundColor={useBackgroundColor}
|
||||
marginX={0}
|
||||
>
|
||||
<Box
|
||||
flexGrow={1}
|
||||
flexDirection="row"
|
||||
paddingX={1}
|
||||
backgroundColor={
|
||||
useBackgroundColor ? theme.background.input : undefined
|
||||
}
|
||||
borderColor={borderColor}
|
||||
borderStyle={useLineFallback ? 'round' : undefined}
|
||||
borderTop={false}
|
||||
@@ -1472,29 +1476,31 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
borderLeft={!useBackgroundColor}
|
||||
borderRight={!useBackgroundColor}
|
||||
>
|
||||
<Text
|
||||
color={statusColor ?? theme.text.accent}
|
||||
aria-label={statusText || undefined}
|
||||
>
|
||||
{shellModeActive ? (
|
||||
reverseSearchActive ? (
|
||||
<Text
|
||||
color={theme.text.link}
|
||||
aria-label={SCREEN_READER_USER_PREFIX}
|
||||
>
|
||||
(r:){' '}
|
||||
</Text>
|
||||
<Box flexDirection="row">
|
||||
<Text
|
||||
color={statusColor ?? theme.text.accent}
|
||||
aria-label={statusText || undefined}
|
||||
>
|
||||
{shellModeActive ? (
|
||||
reverseSearchActive ? (
|
||||
<Text
|
||||
color={theme.text.link}
|
||||
aria-label={SCREEN_READER_USER_PREFIX}
|
||||
>
|
||||
(r:){' '}
|
||||
</Text>
|
||||
) : (
|
||||
'!'
|
||||
)
|
||||
) : commandSearchActive ? (
|
||||
<Text color={theme.text.accent}>(r:) </Text>
|
||||
) : showYoloStyling ? (
|
||||
'*'
|
||||
) : (
|
||||
'!'
|
||||
)
|
||||
) : commandSearchActive ? (
|
||||
<Text color={theme.text.accent}>(r:) </Text>
|
||||
) : showYoloStyling ? (
|
||||
'*'
|
||||
) : (
|
||||
'>'
|
||||
)}{' '}
|
||||
</Text>
|
||||
'>'
|
||||
)}{' '}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1} flexDirection="column" ref={innerBoxRef}>
|
||||
{buffer.text.length === 0 && placeholder ? (
|
||||
showCursor ? (
|
||||
@@ -1673,6 +1679,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
borderRight={false}
|
||||
borderColor={borderColor}
|
||||
width={terminalWidth}
|
||||
marginLeft={0}
|
||||
flexDirection="row"
|
||||
alignItems="flex-start"
|
||||
height={0}
|
||||
|
||||
@@ -15,31 +15,32 @@ import { formatDuration } from '../utils/formatters.js';
|
||||
import { useTerminalSize } from '../hooks/useTerminalSize.js';
|
||||
import { isNarrowWidth } from '../utils/isNarrowWidth.js';
|
||||
import { INTERACTIVE_SHELL_WAITING_PHRASE } from '../hooks/usePhraseCycler.js';
|
||||
import { GENERIC_WORKING_LABEL } from '../textConstants.js';
|
||||
|
||||
interface LoadingIndicatorProps {
|
||||
currentLoadingPhrase?: string;
|
||||
wittyPhrase?: string;
|
||||
wittyPosition?: 'status' | 'inline' | 'ambient';
|
||||
showWit?: boolean;
|
||||
showTips?: boolean;
|
||||
elapsedTime: number;
|
||||
inline?: boolean;
|
||||
rightContent?: React.ReactNode;
|
||||
thought?: ThoughtSummary | null;
|
||||
thoughtLabel?: string;
|
||||
showCancelAndTimer?: boolean;
|
||||
forceRealStatusOnly?: boolean;
|
||||
}
|
||||
|
||||
export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
|
||||
currentLoadingPhrase,
|
||||
wittyPhrase,
|
||||
wittyPosition = 'inline',
|
||||
showWit = true,
|
||||
showTips: _showTips = true,
|
||||
elapsedTime,
|
||||
inline = false,
|
||||
rightContent,
|
||||
thought,
|
||||
thoughtLabel,
|
||||
showCancelAndTimer = true,
|
||||
forceRealStatusOnly = false,
|
||||
}) => {
|
||||
const streamingState = useStreamingContext();
|
||||
const { columns: terminalWidth } = useTerminalSize();
|
||||
@@ -60,12 +61,8 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
|
||||
? currentLoadingPhrase
|
||||
: thought?.subject
|
||||
? (thoughtLabel ?? thought.subject)
|
||||
: forceRealStatusOnly
|
||||
? wittyPosition === 'status' && wittyPhrase
|
||||
? wittyPhrase
|
||||
: streamingState === StreamingState.Responding
|
||||
? 'Waiting for model...'
|
||||
: undefined
|
||||
: streamingState === StreamingState.Responding
|
||||
? GENERIC_WORKING_LABEL
|
||||
: currentLoadingPhrase;
|
||||
const thinkingIndicator = '';
|
||||
|
||||
@@ -76,12 +73,11 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
|
||||
: null;
|
||||
|
||||
const wittyPhraseNode =
|
||||
forceRealStatusOnly &&
|
||||
wittyPosition === 'inline' &&
|
||||
wittyPhrase &&
|
||||
primaryText ? (
|
||||
showWit && wittyPhrase && primaryText === GENERIC_WORKING_LABEL ? (
|
||||
<Box marginLeft={1}>
|
||||
<Text color={theme.text.secondary}>{wittyPhrase}</Text>
|
||||
<Text color={theme.text.secondary} italic>
|
||||
{wittyPhrase}
|
||||
</Text>
|
||||
</Box>
|
||||
) : null;
|
||||
|
||||
@@ -98,11 +94,7 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
|
||||
/>
|
||||
</Box>
|
||||
{primaryText && (
|
||||
<Text
|
||||
color={theme.text.primary}
|
||||
italic
|
||||
wrap={isNarrow ? 'wrap' : 'truncate-end'}
|
||||
>
|
||||
<Text color={theme.text.primary} italic wrap="truncate-end">
|
||||
{thinkingIndicator}
|
||||
{primaryText}
|
||||
</Text>
|
||||
@@ -137,11 +129,7 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
|
||||
/>
|
||||
</Box>
|
||||
{primaryText && (
|
||||
<Text
|
||||
color={theme.text.primary}
|
||||
italic
|
||||
wrap={isNarrow ? 'wrap' : 'truncate-end'}
|
||||
>
|
||||
<Text color={theme.text.primary} italic wrap="truncate-end">
|
||||
{thinkingIndicator}
|
||||
{primaryText}
|
||||
</Text>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
exports[`Composer > Snapshots > matches snapshot in idle state 1`] = `
|
||||
" ShortcutsHint
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
ApprovalModeIndicator StatusDisplay
|
||||
InputPrompt: Type your message or @path/to/file
|
||||
Footer
|
||||
@@ -24,7 +23,6 @@ InputPrompt: Type your message or @path/to/file
|
||||
exports[`Composer > Snapshots > matches snapshot in narrow view 1`] = `
|
||||
"
|
||||
ShortcutsHint
|
||||
────────────────────────────────────────
|
||||
ApprovalModeIndicator
|
||||
|
||||
StatusDisplay
|
||||
@@ -35,8 +33,8 @@ Footer
|
||||
`;
|
||||
|
||||
exports[`Composer > Snapshots > matches snapshot while streaming 1`] = `
|
||||
" LoadingIndicator: Thinking
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
"────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
LoadingIndicator: Thinking
|
||||
ApprovalModeIndicator
|
||||
InputPrompt: Type your message or @path/to/file
|
||||
Footer
|
||||
|
||||
@@ -1,77 +1,98 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`InputPrompt > History Navigation and Completion Suppression > should not render suggestions during history navigation 1`] = `
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
> second message
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
" ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
> second message
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > command search (Ctrl+R when not in shell) > expands and collapses long suggestion via Right/Left arrows > command-search-render-collapsed-match 1`] = `
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
(r:) Type your message or @path/to/file
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll →
|
||||
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
|
||||
...
|
||||
" ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
(r:) Type your message or @path/to/file
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll →
|
||||
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
|
||||
...
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > command search (Ctrl+R when not in shell) > expands and collapses long suggestion via Right/Left arrows > command-search-render-expanded-match 1`] = `
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
(r:) Type your message or @path/to/file
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll ←
|
||||
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
|
||||
llllllllllllllllllllllllllllllllllllllllllllllllll
|
||||
" ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
(r:) Type your message or @path/to/file
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll ←
|
||||
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
|
||||
llllllllllllllllllllllllllllllllllllllllllllllllll
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > command search (Ctrl+R when not in shell) > renders match window and expanded view (snapshots) > command-search-render-collapsed-match 1`] = `
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
(r:) commit
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
git commit -m "feat: add search" in src/app
|
||||
" ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
(r:) commit
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
git commit -m "feat: add search" in src/app
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > command search (Ctrl+R when not in shell) > renders match window and expanded view (snapshots) > command-search-render-expanded-match 1`] = `
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
(r:) commit
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
git commit -m "feat: add search" in src/app
|
||||
" ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
(r:) commit
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
git commit -m "feat: add search" in src/app
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > image path transformation snapshots > should snapshot collapsed image path 1`] = `
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
> [Image ...reenshot2x.png]
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
" ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
> [Image ...reenshot2x.png]
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > image path transformation snapshots > should snapshot expanded image path when cursor is on it 1`] = `
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
> @/path/to/screenshots/screenshot2x.png
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
" ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
> @/path/to/screenshots/screenshot2x.png
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 1`] = `
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
> [Pasted Text: 10 lines]
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
" ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
> [Pasted Text: 10 lines]
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 2`] = `
|
||||
" ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
> [Pasted Text: 10 lines]
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 3`] = `
|
||||
" ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
> [Pasted Text: 10 lines]
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 4`] = `
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
> [Pasted Text: 10 lines]
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 3`] = `
|
||||
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 5`] = `
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
> [Pasted Text: 10 lines]
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 6`] = `
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
> [Pasted Text: 10 lines]
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
@@ -79,29 +100,29 @@ exports[`InputPrompt > mouse interaction > should toggle paste expansion on doub
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > snapshots > should not show inverted cursor when shell is focused 1`] = `
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
> Type your message or @path/to/file
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
" ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
> Type your message or @path/to/file
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > snapshots > should render correctly in shell mode 1`] = `
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
! Type your message or @path/to/file
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
" ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
! Type your message or @path/to/file
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > snapshots > should render correctly in yolo mode 1`] = `
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
* Type your message or @path/to/file
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
" ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
* Type your message or @path/to/file
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > snapshots > should render correctly when accepting edits 1`] = `
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
> Type your message or @path/to/file
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
" ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
> Type your message or @path/to/file
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -52,9 +52,9 @@ export const ThinkingMessage: React.FC<ThinkingMessageProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Box width="100%" marginBottom={1} paddingLeft={1} flexDirection="column">
|
||||
<Box width="100%" marginBottom={1} flexDirection="column">
|
||||
{summary && (
|
||||
<Box paddingLeft={2}>
|
||||
<Box paddingLeft={1}>
|
||||
<Text color={theme.text.primary} bold italic>
|
||||
{summary}
|
||||
</Text>
|
||||
|
||||
@@ -32,6 +32,11 @@ export interface HalfLinePaddedBoxProps {
|
||||
*/
|
||||
useBackgroundColor?: boolean;
|
||||
|
||||
/**
|
||||
* Optional horizontal margin.
|
||||
*/
|
||||
marginX?: number;
|
||||
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -52,6 +57,7 @@ const HalfLinePaddedBoxInternal: React.FC<HalfLinePaddedBoxProps> = ({
|
||||
backgroundBaseColor,
|
||||
backgroundOpacity,
|
||||
children,
|
||||
marginX = 0,
|
||||
}) => {
|
||||
const { terminalWidth } = useUIState();
|
||||
const terminalBg = theme.background.primary || 'black';
|
||||
@@ -80,6 +86,8 @@ const HalfLinePaddedBoxInternal: React.FC<HalfLinePaddedBoxProps> = ({
|
||||
}
|
||||
|
||||
const isITerm = isITerm2();
|
||||
const barWidth = Math.max(0, terminalWidth - marginX * 2);
|
||||
const marginSpaces = ' '.repeat(marginX);
|
||||
|
||||
if (isITerm) {
|
||||
return (
|
||||
@@ -91,10 +99,15 @@ const HalfLinePaddedBoxInternal: React.FC<HalfLinePaddedBoxProps> = ({
|
||||
flexShrink={0}
|
||||
>
|
||||
<Box width={terminalWidth} flexDirection="row">
|
||||
<Text color={backgroundColor}>{'▄'.repeat(terminalWidth)}</Text>
|
||||
<Text color={terminalBg}>
|
||||
{marginSpaces}
|
||||
<Text color={backgroundColor}>{'▄'.repeat(barWidth)}</Text>
|
||||
{marginSpaces}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box
|
||||
width={terminalWidth}
|
||||
width={barWidth}
|
||||
marginLeft={marginX}
|
||||
flexDirection="column"
|
||||
alignItems="stretch"
|
||||
backgroundColor={backgroundColor}
|
||||
@@ -102,7 +115,11 @@ const HalfLinePaddedBoxInternal: React.FC<HalfLinePaddedBoxProps> = ({
|
||||
{children}
|
||||
</Box>
|
||||
<Box width={terminalWidth} flexDirection="row">
|
||||
<Text color={backgroundColor}>{'▀'.repeat(terminalWidth)}</Text>
|
||||
<Text color={terminalBg}>
|
||||
{marginSpaces}
|
||||
<Text color={backgroundColor}>{'▀'.repeat(barWidth)}</Text>
|
||||
{marginSpaces}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
@@ -115,17 +132,27 @@ const HalfLinePaddedBoxInternal: React.FC<HalfLinePaddedBoxProps> = ({
|
||||
alignItems="stretch"
|
||||
minHeight={1}
|
||||
flexShrink={0}
|
||||
backgroundColor={backgroundColor}
|
||||
>
|
||||
<Box width={terminalWidth} flexDirection="row">
|
||||
<Text backgroundColor={backgroundColor} color={terminalBg}>
|
||||
{'▀'.repeat(terminalWidth)}
|
||||
<Text color={terminalBg}>
|
||||
{marginSpaces}
|
||||
<Text backgroundColor={backgroundColor}>{'▀'.repeat(barWidth)}</Text>
|
||||
{marginSpaces}
|
||||
</Text>
|
||||
</Box>
|
||||
{children}
|
||||
<Box
|
||||
width={barWidth}
|
||||
marginLeft={marginX}
|
||||
backgroundColor={backgroundColor}
|
||||
flexDirection="column"
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
<Box width={terminalWidth} flexDirection="row">
|
||||
<Text color={terminalBg} backgroundColor={backgroundColor}>
|
||||
{'▄'.repeat(terminalWidth)}
|
||||
<Text color={terminalBg}>
|
||||
{marginSpaces}
|
||||
<Text backgroundColor={backgroundColor}>{'▄'.repeat(barWidth)}</Text>
|
||||
{marginSpaces}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -10,10 +10,12 @@ import { theme } from '../../semantic-colors.js';
|
||||
|
||||
interface HorizontalLineProps {
|
||||
color?: string;
|
||||
dim?: boolean;
|
||||
}
|
||||
|
||||
export const HorizontalLine: React.FC<HorizontalLineProps> = ({
|
||||
color = theme.border.default,
|
||||
dim = false,
|
||||
}) => (
|
||||
<Box
|
||||
width="100%"
|
||||
@@ -23,5 +25,6 @@ export const HorizontalLine: React.FC<HorizontalLineProps> = ({
|
||||
borderLeft={false}
|
||||
borderRight={false}
|
||||
borderColor={color}
|
||||
borderDimColor={dim}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`<HalfLinePaddedBox /> > renders iTerm2-specific blocks when iTerm2 is detected 1`] = `
|
||||
"▄▄▄▄▄▄▄▄▄▄
|
||||
Content
|
||||
Content
|
||||
▀▀▀▀▀▀▀▀▀▀
|
||||
"
|
||||
`;
|
||||
@@ -17,9 +17,16 @@ exports[`<HalfLinePaddedBox /> > renders nothing when useBackgroundColor is fals
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`<HalfLinePaddedBox /> > renders only background without blocks when Apple Terminal is detected 1`] = `
|
||||
".
|
||||
Content
|
||||
.
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`<HalfLinePaddedBox /> > renders standard background and blocks when not iTerm2 1`] = `
|
||||
"▀▀▀▀▀▀▀▀▀▀
|
||||
Content
|
||||
Content
|
||||
▄▄▄▄▄▄▄▄▄▄
|
||||
"
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user