diff --git a/README.md b/README.md index f95fc5e..e11811a 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/package.json b/package.json index 5b15f4d..6bdd0ab 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/screenshots/global.png b/screenshots/global.png index 8d2f204..951774f 100644 Binary files a/screenshots/global.png and b/screenshots/global.png differ diff --git a/src/ccstatusline.ts b/src/ccstatusline.ts index 7c5dc13..1b6a5ce 100644 --- a/src/ccstatusline.ts +++ b/src/ccstatusline.ts @@ -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; diff --git a/src/config.ts b/src/config.ts index 8d6f892..775b6e0 100644 --- a/src/config.ts +++ b/src/config.ts @@ -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 } diff --git a/src/tui.tsx b/src/tui.tsx index 45dd338..d8a1e32 100644 --- a/src/tui.tsx +++ b/src/tui.tsx @@ -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 = ({ items, onUpdate, onBack }) => { initialIndex={0} /> + + ⚠ VSCode users: + If colors appear incorrect, your VSCode theme may be overriding them. + ); }; @@ -1393,6 +1417,12 @@ const GlobalOptionsMenu: React.FC = ({ 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 = ({ 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 = ({ settings, onUpdat ) : ( <> - + Default Padding: {settings.defaultPadding ? `"${settings.defaultPadding}"` : '(none)'} - Press (p) to edit - + Default Separator: {settings.defaultSeparator ? `"${settings.defaultSeparator}"` : '(none)'} - Press (s) to edit - + Inherit Colors: {inheritColors ? '✓ Enabled' : '✗ Disabled'} - Press (i) to toggle - + Global Bold: {globalBold ? '✓ Enabled' : '✗ Disabled'} - Press (o) to toggle - + Override BG Color: {(() => { const bgColor = settings.overrideBackgroundColor || 'none'; @@ -1535,6 +1581,21 @@ const GlobalOptionsMenu: React.FC = ({ settings, onUpdat - (b) cycle, (c) clear + + Override FG Color: + {(() => { + const fgColor = settings.overrideForegroundColor || 'none'; + if (fgColor === 'none') { + return (none); + } else { + const fgFunc = (chalk as any)[fgColor]; + const display = fgFunc ? fgFunc(fgColor) : fgColor; + return {display}; + } + })()} + - (f) cycle, (g) clear + + Press ESC to go back @@ -1550,8 +1611,12 @@ const GlobalOptionsMenu: React.FC = ({ settings, onUpdat • Global Bold: Makes all text bold regardless of individual settings - • 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 + + ⚠ VSCode users: + If colors appear incorrect, your VSCode theme may be overriding them. + )} @@ -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}