Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions packages/cli/src/config/settingsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -614,14 +614,21 @@ const SETTINGS_SCHEMA = {
category: 'UI',
requiresRestart: false,
default: undefined as
| {
type: 'command';
command: string;
refreshInterval?: number;
}
| (
| {
type: 'command';
command: string;
refreshInterval?: number;
}
| {
type: 'preset';
items: string[];
useThemeColors?: boolean;
}
)
| undefined,
description:
'Custom status line display configuration. Optional `refreshInterval` (seconds, >= 1) re-runs the command on a timer so external data stays fresh.',
'Status line display configuration. Use `type: "preset"` with built-in item ids, or `type: "command"` with a shell command. Optional command `refreshInterval` (seconds, >= 1) re-runs the command on a timer so external data stays fresh.',
showInDialog: false,
},
customThemes: {
Expand Down
36 changes: 36 additions & 0 deletions packages/cli/src/ui/AppContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ import { useMessageQueue } from './hooks/useMessageQueue.js';
import { useAutoAcceptIndicator } from './hooks/useAutoAcceptIndicator.js';
import { useSessionStats } from './contexts/SessionContext.js';
import { useGitBranchName } from './hooks/useGitBranchName.js';
import type { StatusLinePresetConfig } from './statusLinePresets.js';
import {
useExtensionUpdates,
useConfirmUpdateRequests,
Expand Down Expand Up @@ -675,6 +676,26 @@ export const AppContainer = (props: AppContainerProps) => {

const { isSettingsDialogOpen, openSettingsDialog, closeSettingsDialog } =
useSettingsCommand();
const [isStatusLineDialogOpen, setStatusLineDialogOpen] = useState(false);
const openStatusLineDialog = useCallback(
() => setStatusLineDialogOpen(true),
[],
);
const closeStatusLineDialog = useCallback(
() => setStatusLineDialogOpen(false),
[],
Comment thread
DragonnZhang marked this conversation as resolved.
);
const [statusLineSettingsVersion, setStatusLineSettingsVersion] = useState(0);
const [statusLineConfigOverride, setStatusLineConfigOverride] = useState<
StatusLinePresetConfig | undefined
>(undefined);
const notifyStatusLineSettingsChanged = useCallback(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] notifyStatusLineSettingsChanged sets statusLineConfigOverride but never clears it. If the user later switches to a command-type statusLine, the stale override persists in React state. Consider clearing it when the settings config type no longer matches.

Suggested change
const notifyStatusLineSettingsChanged = useCallback(
// Add to the settings-watch effect:
if (settingsStatusLineConfig?.type !== 'preset') {
setStatusLineConfigOverride(undefined);
}

— DeepSeek/deepseek-v4-pro via Qwen Code /review

(newConfig: StatusLinePresetConfig) => {
setStatusLineConfigOverride(newConfig);
setStatusLineSettingsVersion((version) => version + 1);
},
[],
);
const { isMemoryDialogOpen, openMemoryDialog, closeMemoryDialog } =
useMemoryDialog();

Expand Down Expand Up @@ -769,6 +790,7 @@ export const AppContainer = (props: AppContainerProps) => {
openEditorDialog,
openMemoryDialog,
openSettingsDialog,
openStatusLineDialog,
openModelDialog,
openManageModelsDialog,
openTrustDialog,
Expand Down Expand Up @@ -803,6 +825,7 @@ export const AppContainer = (props: AppContainerProps) => {
openEditorDialog,
openMemoryDialog,
openSettingsDialog,
openStatusLineDialog,
openModelDialog,
openManageModelsDialog,
openArenaDialog,
Expand Down Expand Up @@ -1846,6 +1869,7 @@ export const AppContainer = (props: AppContainerProps) => {
!!loopDetectionConfirmationRequest ||
isThemeDialogOpen ||
isSettingsDialogOpen ||
isStatusLineDialogOpen ||
isMemoryDialogOpen ||
isModelDialogOpen ||
isManageModelsDialogOpen ||
Expand Down Expand Up @@ -2191,6 +2215,8 @@ export const AppContainer = (props: AppContainerProps) => {
exitEditorDialog,
isSettingsDialogOpen,
closeSettingsDialog,
isStatusLineDialogOpen,
closeStatusLineDialog,
isMemoryDialogOpen,
closeMemoryDialog,
activeArenaDialog,
Expand Down Expand Up @@ -2622,6 +2648,9 @@ export const AppContainer = (props: AppContainerProps) => {
debugMessage,
quittingMessages,
isSettingsDialogOpen,
isStatusLineDialogOpen,
statusLineSettingsVersion,
statusLineConfigOverride,
isMemoryDialogOpen,
isModelDialogOpen,
isFastModelMode,
Expand Down Expand Up @@ -2740,6 +2769,9 @@ export const AppContainer = (props: AppContainerProps) => {
debugMessage,
quittingMessages,
isSettingsDialogOpen,
isStatusLineDialogOpen,
statusLineSettingsVersion,
statusLineConfigOverride,
isMemoryDialogOpen,
isModelDialogOpen,
isFastModelMode,
Expand Down Expand Up @@ -2863,6 +2895,8 @@ export const AppContainer = (props: AppContainerProps) => {
handleEditorSelect,
exitEditorDialog,
closeSettingsDialog,
closeStatusLineDialog,
notifyStatusLineSettingsChanged,
closeMemoryDialog,
closeModelDialog,
openModelDialog,
Expand Down Expand Up @@ -2937,6 +2971,8 @@ export const AppContainer = (props: AppContainerProps) => {
handleEditorSelect,
exitEditorDialog,
closeSettingsDialog,
closeStatusLineDialog,
notifyStatusLineSettingsChanged,
closeMemoryDialog,
closeModelDialog,
openModelDialog,
Expand Down
25 changes: 8 additions & 17 deletions packages/cli/src/ui/commands/statuslineCommand.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,17 @@ describe('statuslineCommand', () => {
expect(statuslineCommand.description).toBeDefined();
});

it('should return submit_prompt with default prompt when no args', () => {
it('should open the preset dialog when no args are provided', () => {
if (!statuslineCommand.action) {
throw new Error('statusline command must have an action');
}

const result = statuslineCommand.action(mockContext, '');

expect(result).toEqual({
type: 'submit_prompt',
content: [
{
text: expect.stringContaining('statusline-setup'),
},
],
type: 'dialog',
dialog: 'statusline',
});
// Default prompt should mention PS1
expect(result).toHaveProperty(
'content.0.text',
expect.stringContaining('PS1'),
);
});

it('should use user-provided args as the prompt', () => {
Expand All @@ -63,16 +54,16 @@ describe('statuslineCommand', () => {
});
});

it('should trim whitespace-only args and use default prompt', () => {
it('should open the preset dialog when args are whitespace only', () => {
if (!statuslineCommand.action) {
throw new Error('statusline command must have an action');
}

const result = statuslineCommand.action(mockContext, ' ');

expect(result).toHaveProperty(
'content.0.text',
expect.stringContaining('PS1'),
);
expect(result).toEqual({
type: 'dialog',
dialog: 'statusline',
});
});
});
21 changes: 17 additions & 4 deletions packages/cli/src/ui/commands/statuslineCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
* SPDX-License-Identifier: Apache-2.0
*/

import type { SlashCommand, SubmitPromptActionReturn } from './types.js';
import type {
OpenDialogActionReturn,
SlashCommand,
SubmitPromptActionReturn,
} from './types.js';
import { CommandKind } from './types.js';
import { t } from '../../i18n/index.js';

Expand All @@ -15,9 +19,18 @@ export const statuslineCommand: SlashCommand = {
},
kind: CommandKind.BUILT_IN,
supportedModes: ['interactive'] as const,
action: (_context, args): SubmitPromptActionReturn => {
const prompt =
args.trim() || 'Configure my statusLine from my shell PS1 configuration';
action: (
_context,
args,
): OpenDialogActionReturn | SubmitPromptActionReturn => {
const prompt = args.trim();
if (!prompt) {
return {
type: 'dialog',
dialog: 'statusline',
};
}

return {
type: 'submit_prompt',
content: [
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/ui/commands/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ export interface OpenDialogActionReturn {
| 'theme'
| 'editor'
| 'settings'
| 'statusline'
| 'memory'
| 'model'
| 'fast-model'
Expand Down
14 changes: 14 additions & 0 deletions packages/cli/src/ui/components/DialogManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { SettingInputPrompt } from './SettingInputPrompt.js';
import { PluginChoicePrompt } from './PluginChoicePrompt.js';
import { ThemeDialog } from './ThemeDialog.js';
import { SettingsDialog } from './SettingsDialog.js';
import { StatusLineDialog } from './StatusLineDialog.js';
import { QwenOAuthProgress } from './QwenOAuthProgress.js';
import { ExternalAuthProgress } from './ExternalAuthProgress.js';
import { AuthDialog } from '../auth/AuthDialog.js';
Expand Down Expand Up @@ -254,6 +255,19 @@ export const DialogManager = ({
</Box>
);
}
if (uiState.isStatusLineDialogOpen) {
return (
<StatusLineDialog
settings={settings}
config={config}
uiState={uiState}
addItem={addItem}
onSaved={uiActions.notifyStatusLineSettingsChanged}
onClose={uiActions.closeStatusLineDialog}
availableTerminalHeight={terminalHeight - staticExtraHeight}
/>
);
}
if (uiState.isMemoryDialogOpen) {
return <MemoryDialog onClose={uiActions.closeMemoryDialog} />;
}
Expand Down
9 changes: 7 additions & 2 deletions packages/cli/src/ui/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const Footer: React.FC = () => {
const uiState = useUIState();
const config = useConfig();
const { vimEnabled, vimMode } = useVimMode();
const { lines: statusLineLines } = useStatusLine();
const { lines: statusLineLines, useThemeColors } = useStatusLine();
const configInitMessage = useConfigInitMessage(uiState.isConfigInitialized);

const { promptTokenCount, showAutoAcceptIndicator } = {
Expand Down Expand Up @@ -141,7 +141,12 @@ export const Footer: React.FC = () => {
!uiState.ctrlCPressedOnce &&
!uiState.ctrlDPressedOnce &&
statusLineLines.map((line, i) => (
<Text key={`status-line-${i}`} dimColor wrap="truncate">
<Text
key={`status-line-${i}`}
color={useThemeColors ? theme.text.accent : undefined}
dimColor={!useThemeColors}
wrap="truncate"
>
{line}
</Text>
))}
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/ui/components/MainContent.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ const createUIState = (overrides: Partial<UIState> = {}): UIState =>
debugMessage: '',
quittingMessages: null,
isSettingsDialogOpen: false,
isStatusLineDialogOpen: false,
isMemoryDialogOpen: false,
isModelDialogOpen: false,
isFastModelMode: false,
Expand Down
Loading
Loading