Added global option to override foreground color, added VSCode warning for color issues

This commit is contained in:
Matthew Breedlove
2025-08-11 16:33:19 -04:00
parent 6a60501d63
commit 46436ea5b0
6 changed files with 127 additions and 43 deletions
+5
View File
@@ -120,12 +120,17 @@ Configure global formatting preferences that apply to all status items:
- Press **(i)** to toggle
- **Global Bold** - Apply bold formatting to all text regardless of individual item settings
- Press **(o)** to toggle
- **Override Foreground Color** - Force all items to use the same text color
- Press **(f)** to cycle through colors
- Press **(v)** to clear override
- **Override Background Color** - Force all items to use the same background color
- Press **(b)** to cycle through colors
- Press **(c)** to clear override
> 💡 **Note:** These settings are applied during rendering and don't add items to your widget list. They provide a consistent look across your entire status line without modifying individual item configurations.
> ⚠️ **VSCode Users:** If colors appear incorrect in the VSCode integrated terminal, your VSCode theme may be overriding the ANSI color codes. This is a known limitation of VSCode's terminal rendering. The status line will display correctly in standalone terminals.
### 🔤 Raw Value Mode
Some items support "raw value" mode which displays just the value without a label:
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ccstatusline",
"version": "1.1.0",
"version": "1.1.1",
"description": "A customizable status line formatter for Claude Code CLI",
"module": "src/ccstatusline.ts",
"type": "module",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 KiB

After

Width:  |  Height:  |  Size: 312 KiB

+31 -19
View File
@@ -16,6 +16,7 @@ chalk.level = 3;
function applyColors(text: string, foregroundColor?: string, backgroundColor?: string, bold?: boolean): string {
let result = text;
// Standard color application
// Ignore 'dim' color - it causes issues with terminal rendering
if (foregroundColor && foregroundColor !== 'dim') {
const fgFunc = (chalk as any)[foregroundColor];
@@ -335,11 +336,20 @@ async function getTokenMetrics(transcriptPath: string): Promise<{
function renderSingleLine(items: StatusItem[], settings: any, data: StatusJSON, tokenMetrics: any, sessionDuration: string | null): string {
// Helper to apply colors with optional background and bold override
const applyColorsWithOverride = (text: string, foregroundColor?: string, backgroundColor?: string, bold?: boolean): string => {
const bgColor = settings.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none'
? settings.overrideBackgroundColor
: backgroundColor;
// Override foreground color takes precedence over EVERYTHING, including passed foreground color
let fgColor = foregroundColor;
if (settings.overrideForegroundColor && settings.overrideForegroundColor !== 'none') {
fgColor = settings.overrideForegroundColor;
}
// Override background color takes precedence over EVERYTHING, including passed background color
let bgColor = backgroundColor;
if (settings.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none') {
bgColor = settings.overrideBackgroundColor;
}
const shouldBold = settings.globalBold || bold;
return applyColors(text, foregroundColor, bgColor, shouldBold);
return applyColors(text, fgColor, bgColor, shouldBold);
};
const detectedWidth = getTerminalWidth();
// Calculate terminal width based on flex mode settings
@@ -384,7 +394,9 @@ function renderSingleLine(items: StatusItem[], settings: any, data: StatusJSON,
case 'model':
if (data.model) {
const text = item.rawValue ? data.model.display_name : `Model: ${data.model.display_name}`;
elements.push({ content: applyColorsWithOverride(text, item.color || settings.colors.model, item.backgroundColor, item.bold), type: 'model', item });
// Use item.color first, then fall back to settings.colors.model
const defaultColor = item.color || settings.colors.model;
elements.push({ content: applyColorsWithOverride(text, defaultColor, item.backgroundColor, item.bold), type: 'model', item });
}
break;
@@ -602,8 +614,9 @@ function renderSingleLine(items: StatusItem[], settings: any, data: StatusJSON,
} else {
finalElements.push(defaultSep);
}
} else if (settings.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none') {
// Apply override background even when not inheriting colors
} else if ((settings.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none') ||
(settings.overrideForegroundColor && settings.overrideForegroundColor !== 'none')) {
// Apply override colors even when not inheriting colors
const coloredSep = applyColorsWithOverride(defaultSep, undefined, undefined);
finalElements.push(coloredSep);
} else {
@@ -615,19 +628,18 @@ function renderSingleLine(items: StatusItem[], settings: any, data: StatusJSON,
if (elem.type === 'separator' || elem.type === 'flex-separator') {
finalElements.push(elem.content);
} else {
// Apply padding with the same background color as the item (or override)
const bgColor = settings.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none'
? settings.overrideBackgroundColor
: elem.item?.backgroundColor;
// Apply padding with colors (using overrides if set)
const hasColorOverride = (settings.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none') ||
(settings.overrideForegroundColor && settings.overrideForegroundColor !== 'none');
if (padding && bgColor) {
// Apply background color to padding
const paddedContent = applyColorsWithOverride(padding, undefined, bgColor) +
if (padding && (elem.item?.backgroundColor || hasColorOverride)) {
// Apply colors to padding - applyColorsWithOverride will handle the overrides
const paddedContent = applyColorsWithOverride(padding, undefined, elem.item?.backgroundColor) +
elem.content +
applyColorsWithOverride(padding, undefined, bgColor);
applyColorsWithOverride(padding, undefined, elem.item?.backgroundColor);
finalElements.push(paddedContent);
} else {
// No background color or no padding
// No colors or no padding
finalElements.push(padding + elem.content + padding);
}
}
@@ -643,11 +655,11 @@ function renderSingleLine(items: StatusItem[], settings: any, data: StatusJSON,
for (let i = 0; i < finalElements.length; i++) {
const elem = finalElements[i];
if (elem === 'FLEX' || (elements[i] && elements[i].type === 'flex-separator')) {
if (elem === 'FLEX' || (elements[i] && elements[i]?.type === 'flex-separator')) {
currentPart++;
parts[currentPart] = [];
} else {
parts[currentPart]!.push(elem);
parts[currentPart]?.push(elem!);
}
}
@@ -770,7 +782,7 @@ async function renderStatusLine(data: StatusJSON) {
// Render each line
for (let i = 0; i < lines.length; i++) {
const lineItems = lines[i];
if (lineItems.length > 0) {
if (lineItems && lineItems.length > 0) {
const line = renderSingleLine(lineItems, settings, data, tokenMetrics, sessionDuration);
// Add zero-width non-joiner at start of second+ lines to prevent trimming
const outputLine = i > 0 ? '\u200C' + line : line;
+1
View File
@@ -42,6 +42,7 @@ export interface Settings {
defaultPadding?: string; // Default padding to add around all items
inheritSeparatorColors?: boolean; // Whether default separators inherit colors from preceding widget
overrideBackgroundColor?: string; // Override background color for all items (e.g., 'none', 'bgRed', etc.)
overrideForegroundColor?: string; // Override foreground color for all items (e.g., 'red', 'cyan', etc.)
globalBold?: boolean; // Apply bold formatting to all items
}
+89 -23
View File
@@ -96,13 +96,22 @@ const applyColors = (text: string, foregroundColor?: string, backgroundColor?: s
};
const renderSingleLine = (items: StatusItem[], terminalWidth: number, widthDetectionAvailable: boolean, settings?: Settings): string => {
// Helper to apply colors with optional background override
// Helper to apply colors with optional background and foreground override
const applyItemColors = (text: string, item: StatusItem, defaultColor?: string): string => {
const bgColor = settings?.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none'
? settings.overrideBackgroundColor
: item.backgroundColor;
// Override foreground color takes precedence over EVERYTHING
let fgColor = item.color || defaultColor;
if (settings?.overrideForegroundColor && settings.overrideForegroundColor !== 'none') {
fgColor = settings.overrideForegroundColor;
}
// Override background color takes precedence over EVERYTHING
let bgColor = item.backgroundColor;
if (settings?.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none') {
bgColor = settings.overrideBackgroundColor;
}
const shouldBold = settings?.globalBold || item.bold;
return applyColors(text, item.color || defaultColor, bgColor, shouldBold);
return applyColors(text, fgColor, bgColor, shouldBold);
};
// Calculate effective width based on flex mode settings
let effectiveWidth: number | null = null;
@@ -228,18 +237,26 @@ const renderSingleLine = (items: StatusItem[], terminalWidth: number, widthDetec
if (settings?.inheritSeparatorColors && index > 0) {
const prevElem = rawElements[index - 1];
if (prevElem && prevElem.item) {
// Apply the previous element's colors to the separator (with override)
// Apply the previous element's colors to the separator (with overrides)
const fgColor = settings?.overrideForegroundColor && settings.overrideForegroundColor !== 'none'
? settings.overrideForegroundColor
: prevElem.item.color;
const bgColor = settings?.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none'
? settings.overrideBackgroundColor
: prevElem.item.backgroundColor;
const coloredSep = applyColors(defaultSep, prevElem.item.color, bgColor, prevElem.item.bold);
const shouldBold = settings?.globalBold || prevElem.item.bold;
const coloredSep = applyColors(defaultSep, fgColor, bgColor, shouldBold);
elements.push(coloredSep);
} else {
elements.push(defaultSep);
}
} else if (settings?.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none') {
// Apply override background even when not inheriting colors
const coloredSep = applyColors(defaultSep, undefined, settings.overrideBackgroundColor);
} else if (settings?.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none' ||
settings?.overrideForegroundColor && settings.overrideForegroundColor !== 'none') {
// Apply override colors even when not inheriting colors
const fgColor = settings?.overrideForegroundColor && settings.overrideForegroundColor !== 'none'
? settings.overrideForegroundColor
: undefined;
const coloredSep = applyColors(defaultSep, fgColor, settings.overrideBackgroundColor, settings?.globalBold);
elements.push(coloredSep);
} else {
elements.push(defaultSep);
@@ -250,19 +267,22 @@ const renderSingleLine = (items: StatusItem[], terminalWidth: number, widthDetec
if (elem.type === 'separator' || elem.type === 'flex-separator') {
elements.push(elem.content);
} else {
// Apply padding with the same background color as the item (or override)
// Apply padding with the same colors as the item (or overrides)
const fgColor = settings?.overrideForegroundColor && settings.overrideForegroundColor !== 'none'
? settings.overrideForegroundColor
: undefined;
const bgColor = settings?.overrideBackgroundColor && settings.overrideBackgroundColor !== 'none'
? settings.overrideBackgroundColor
: elem.item?.backgroundColor;
if (padding && bgColor) {
// Apply background color to padding
const paddedContent = applyColors(padding, undefined, bgColor) +
if (padding && (bgColor || fgColor)) {
// Apply colors to padding
const paddedContent = applyColors(padding, fgColor, bgColor, settings?.globalBold) +
elem.content +
applyColors(padding, undefined, bgColor);
applyColors(padding, fgColor, bgColor, settings?.globalBold);
elements.push(paddedContent);
} else {
// No background color or no padding
// No colors or no padding
elements.push(padding + elem.content + padding);
}
}
@@ -1219,6 +1239,10 @@ const ColorMenu: React.FC<ColorMenuProps> = ({ items, onUpdate, onBack }) => {
initialIndex={0}
/>
</Box>
<Box marginTop={1}>
<Text color='yellow'> VSCode users: </Text>
<Text dimColor>If colors appear incorrect, your VSCode theme may be overriding them.</Text>
</Box>
</Box>
);
};
@@ -1393,6 +1417,12 @@ const GlobalOptionsMenu: React.FC<GlobalOptionsMenuProps> = ({ settings, onUpdat
'bgCyan', 'bgWhite', 'bgGray', 'bgRedBright', 'bgGreenBright', 'bgYellowBright',
'bgBlueBright', 'bgMagentaBright', 'bgCyanBright', 'bgWhiteBright'];
const currentBgIndex = bgColors.indexOf(settings.overrideBackgroundColor || 'none');
// Foreground color override
const fgColors = ['none', 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white',
'gray', 'redBright', 'greenBright', 'yellowBright', 'blueBright',
'magentaBright', 'cyanBright', 'whiteBright'];
const currentFgIndex = fgColors.indexOf(settings.overrideForegroundColor || 'none');
useInput((input, key) => {
if (editingPadding) {
@@ -1467,6 +1497,22 @@ const GlobalOptionsMenu: React.FC<GlobalOptionsMenuProps> = ({ settings, onUpdat
globalBold: newGlobalBold
};
onUpdate(updatedSettings);
} else if (input === 'f' || input === 'F') {
// Cycle through foreground colors
const nextIndex = (currentFgIndex + 1) % fgColors.length;
const nextFgColor = fgColors[nextIndex];
const updatedSettings = {
...settings,
overrideForegroundColor: nextFgColor === 'none' ? undefined : nextFgColor
};
onUpdate(updatedSettings);
} else if (input === 'g' || input === 'G') {
// Clear override foreground color
const updatedSettings = {
...settings,
overrideForegroundColor: undefined
};
onUpdate(updatedSettings);
}
}
});
@@ -1495,31 +1541,31 @@ const GlobalOptionsMenu: React.FC<GlobalOptionsMenuProps> = ({ settings, onUpdat
</Box>
) : (
<>
<Box marginTop={1}>
<Box>
<Text> Default Padding: </Text>
<Text color="cyan">{settings.defaultPadding ? `"${settings.defaultPadding}"` : '(none)'}</Text>
<Text dimColor> - Press (p) to edit</Text>
</Box>
<Box marginTop={1}>
<Box>
<Text>Default Separator: </Text>
<Text color="cyan">{settings.defaultSeparator ? `"${settings.defaultSeparator}"` : '(none)'}</Text>
<Text dimColor> - Press (s) to edit</Text>
</Box>
<Box marginTop={1}>
<Box>
<Text> Inherit Colors: </Text>
<Text color={inheritColors ? "green" : "red"}>{inheritColors ? '✓ Enabled' : '✗ Disabled'}</Text>
<Text dimColor> - Press (i) to toggle</Text>
</Box>
<Box marginTop={1}>
<Box>
<Text> Global Bold: </Text>
<Text color={globalBold ? "green" : "red"}>{globalBold ? '✓ Enabled' : '✗ Disabled'}</Text>
<Text dimColor> - Press (o) to toggle</Text>
</Box>
<Box marginTop={1}>
<Box>
<Text>Override BG Color: </Text>
{(() => {
const bgColor = settings.overrideBackgroundColor || 'none';
@@ -1535,6 +1581,21 @@ const GlobalOptionsMenu: React.FC<GlobalOptionsMenuProps> = ({ settings, onUpdat
<Text dimColor> - (b) cycle, (c) clear</Text>
</Box>
<Box>
<Text>Override FG Color: </Text>
{(() => {
const fgColor = settings.overrideForegroundColor || 'none';
if (fgColor === 'none') {
return <Text color="gray">(none)</Text>;
} else {
const fgFunc = (chalk as any)[fgColor];
const display = fgFunc ? fgFunc(fgColor) : fgColor;
return <Text>{display}</Text>;
}
})()}
<Text dimColor> - (f) cycle, (g) clear</Text>
</Box>
<Box marginTop={2}>
<Text dimColor>Press ESC to go back</Text>
</Box>
@@ -1550,8 +1611,12 @@ const GlobalOptionsMenu: React.FC<GlobalOptionsMenuProps> = ({ settings, onUpdat
• Global Bold: Makes all text bold regardless of individual settings
</Text>
<Text dimColor wrap='wrap'>
• Override BG: All items will use this background color instead of their configured colors
• Override colors: All items will use these colors instead of their configured colors
</Text>
<Box marginTop={1}>
<Text color='yellow'>⚠ VSCode users: </Text>
<Text dimColor>If colors appear incorrect, your VSCode theme may be overriding them.</Text>
</Box>
</Box>
</>
)}
@@ -1735,7 +1800,8 @@ const App: React.FC = () => {
onBack={() => {
// Save that we came from 'lines' menu (index 0)
// Clear the line selection so it resets next time we enter
setMenuSelections({ ...menuSelections, main: 0, lines: undefined });
const { lines: _, ...restMenuSelections } = menuSelections;
setMenuSelections({ ...restMenuSelections, main: 0 });
setScreen('main');
}}
initialSelection={menuSelections.lines || 0}