Skip to content
Merged
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
12 changes: 6 additions & 6 deletions src/tui/components/PowerlineThemeSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export const PowerlineThemeSelector: React.FC<PowerlineThemeSelectorProps> = ({
}) => {
const isRegular = mode === 'regular';
const themes = useMemo(
() => (isRegular ? ['none', ...getPowerlineThemes().filter((name) => name !== 'custom')] : getPowerlineThemes()),
() => (isRegular ? ['none', ...getPowerlineThemes().filter(name => name !== 'custom')] : getPowerlineThemes()),
[isRegular]
);
const currentTheme = isRegular
Expand Down Expand Up @@ -142,11 +142,11 @@ export const PowerlineThemeSelector: React.FC<PowerlineThemeSelectorProps> = ({
...(isRegular
? { theme: themeName === 'none' ? undefined : themeName }
: {
powerline: {
...latestSettingsRef.current.powerline,
theme: themeName
}
})
powerline: {
...latestSettingsRef.current.powerline,
theme: themeName
}
})
});
}, [selectedIndex, themes, isRegular]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ describe('PowerlineThemeSelector helpers', () => {
});

it('writes settings.theme when navigating in regular mode', async () => {
const firstTheme = getPowerlineThemes().filter((name) => name !== 'custom')[0];
const firstTheme = getPowerlineThemes().find(name => name !== 'custom');
expect(firstTheme).toBeDefined();

const stdin = createMockStdin();
Expand Down
16 changes: 15 additions & 1 deletion src/utils/__tests__/renderer-compact-labels.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import {
type Settings
} from '../../types/Settings';
import type { WidgetItem } from '../../types/Widget';
import { toggleCompactLabel } from '../../widgets/shared/raw-or-labeled';
import {
COMPACT_LABELS,
formatRawOrLabeledValue,
toggleCompactLabel
} from '../../widgets/shared/raw-or-labeled';
import { stripSgrCodes } from '../ansi';
import { migrateConfig } from '../migrations';
import {
Expand Down Expand Up @@ -105,3 +109,13 @@ describe('compact labels rendering', () => {
expect('compactLabels' in SettingsSchema_v1.shape).toBe(false);
});
});

describe('compact label presets', () => {
const compactItem: WidgetItem = { id: 'x', type: 'model', metadata: { compactLabel: 'true' } };
const plainItem: WidgetItem = { id: 'x', type: 'model' };

it.each(Object.entries(COMPACT_LABELS))('maps %j to %j', (full, compact) => {
expect(formatRawOrLabeledValue(compactItem, full, 'V')).toBe(`${compact}V`);
expect(formatRawOrLabeledValue(plainItem, full, 'V')).toBe(`${full}V`);
});
});
13 changes: 7 additions & 6 deletions src/utils/__tests__/renderer-regular-theme.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ describe('renderer regular-mode theme', () => {
// Every shipped theme must behave identically in regular mode: same slot
// rule, same bg[]-as-foreground palette, at all three color levels.
describe('uniform cycle across all themes and levels', () => {
const themeNames = getPowerlineThemes().filter((name) => name !== 'custom');
const themeNames = getPowerlineThemes().filter(name => name !== 'custom');
const levelName = (colorLevel: 1 | 2 | 3): 'ansi16' | 'ansi256' | 'truecolor' => (colorLevel === 1 ? 'ansi16' : colorLevel === 2 ? 'ansi256' : 'truecolor');

it('has a non-empty bg palette at every level for every theme', () => {
Expand All @@ -167,24 +167,25 @@ describe('renderer regular-mode theme', () => {
}
});

it.each(themeNames.flatMap((name) => ([1, 2, 3] as const).map((colorLevel) => ({ name, colorLevel }))))(
it.each(themeNames.flatMap(name => ([1, 2, 3] as const).map(colorLevel => ({ name, colorLevel }))))(
'cycles $name uniformly at colorLevel $colorLevel',
({ name, colorLevel }) => {
const theme = getPowerlineTheme(name);
expect(theme).toBeDefined();
const palette = (theme?.[String(colorLevel) as '1' | '2' | '3']?.bg ?? []).map(bgToFg);
const first = palette[0];
const second = palette[1];
expect(first).toBeDefined();
expect(second).toBeDefined();
if (first === undefined || second === undefined) {
throw new Error(`theme '${name}' has an empty bg palette at ${levelName(colorLevel)}`);
}

const line = renderWidgets(themedSettings(name, colorLevel), [
{ id: '1', type: 'custom-text', customText: 'A' },
{ id: '2', type: 'custom-text', customText: 'B' }
]);

expect(line).toContain(fg(first as string, levelName(colorLevel)));
expect(line).toContain(fg(second as string, levelName(colorLevel)));
expect(line).toContain(fg(first, levelName(colorLevel)));
expect(line).toContain(fg(second, levelName(colorLevel)));
}
);
});
Expand Down
5 changes: 4 additions & 1 deletion src/utils/__tests__/separator-font-fallback.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import {
} from 'vitest';

import type { RenderContext } from '../../types/RenderContext';
import { DEFAULT_SETTINGS, type Settings } from '../../types/Settings';
import {
DEFAULT_SETTINGS,
type Settings
} from '../../types/Settings';
import type { WidgetItem } from '../../types/Widget';
import {
containsPowerlineGlyph,
Expand Down
5 changes: 4 additions & 1 deletion src/utils/__tests__/widgets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,11 @@ describe('compact label keybind reservation', () => {
expect(capable.has('model')).toBe(true);
expect(capable.has('session-cost')).toBe(true);
expect(capable.has('context-bar')).toBe(true);
// Presets now cover every labeled widget, tokens and usage included.
expect(capable.has('tokens-input')).toBe(true);
expect(capable.has('session-usage')).toBe(true);
expect(capable.has('weekly-usage')).toBe(true);
expect(capable.has('git-branch')).toBe(false);
expect(capable.has('tokens-input')).toBe(false);
});
});

Expand Down
3 changes: 1 addition & 2 deletions src/utils/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { WidgetItem } from '../types/Widget';
import { WidgetItemSchema } from '../types/Widget';

import { getPowerlineThemes } from './colors';

import {
getConfigLoadError,
getConfigPath,
Expand Down Expand Up @@ -203,7 +202,7 @@ function getThemeWarnings(settings: Settings): string[] {
if (!settings.theme || settings.theme === 'custom' || getPowerlineThemes().includes(settings.theme)) {
return [];
}
return [`unknown theme '${settings.theme}' (known: ${getPowerlineThemes().filter((name) => name !== 'custom').join(', ')})`];
return [`unknown theme '${settings.theme}' (known: ${getPowerlineThemes().filter(name => name !== 'custom').join(', ')})`];
}

interface SettingsLoad { settings: Settings }
Expand Down
4 changes: 1 addition & 3 deletions src/utils/powerline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,7 @@ export function setCachedPowerlineFontStatus(status: PowerlineFontStatus): void
}

export function getCachedPowerlineFontStatus(): PowerlineFontStatus {
if (cachedFontStatus === null) {
cachedFontStatus = checkPowerlineFonts();
}
cachedFontStatus ??= checkPowerlineFonts();
return cachedFontStatus;
}

Expand Down
8 changes: 4 additions & 4 deletions src/utils/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ import {
isGradientSpec,
parseGradientSpec
} from './gradient';
import { getTerminalWidth } from './terminal';
import { fontSafeSeparator } from './powerline';
import { getTerminalWidth } from './terminal';
import {
getWidget,
widgetPreservesColors
Expand Down Expand Up @@ -163,12 +163,12 @@ function renderPowerlineStatusLine(

// Get separator configuration; PUA glyphs degrade to '|' without a
// Powerline/Nerd Font instead of rendering as mojibake
const separators = ((config.separators as string[] | undefined) ?? ['\uE0B0']).map((separator) => fontSafeSeparator(separator));
const separators = ((config.separators as string[] | undefined) ?? ['\uE0B0']).map(separator => fontSafeSeparator(separator));
const invertBgs = (config.separatorInvertBackground as boolean[] | undefined) ?? separators.map(() => false);

// Get caps arrays or fallback to empty arrays
const startCaps = ((config.startCaps as string[] | undefined) ?? []).map((cap) => fontSafeSeparator(cap));
const endCaps = ((config.endCaps as string[] | undefined) ?? []).map((cap) => fontSafeSeparator(cap));
const startCaps = ((config.startCaps as string[] | undefined) ?? []).map(cap => fontSafeSeparator(cap));
const endCaps = ((config.endCaps as string[] | undefined) ?? []).map(cap => fontSafeSeparator(cap));

// Get the cap for this line (cycle through if more lines than caps)
const capLineIndex = context.lineIndex ?? lineIndex;
Expand Down
7 changes: 4 additions & 3 deletions src/widgets/shared/__tests__/raw-or-labeled.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ describe('formatRawOrLabeledValue', () => {
expect(formatRawOrLabeledValue(compact, 'Cost: ', '$2.45')).toBe('$2.45');
});

it('keeps labels without a preset unchanged in compact mode', () => {
expect(formatRawOrLabeledValue(toggleCompactLabel(ITEM), 'In: ', '15.2k')).toBe('In: 15.2k');
it('keeps unmapped labels unchanged in compact mode', () => {
expect(formatRawOrLabeledValue(toggleCompactLabel(ITEM), 'Branch: ', 'main')).toBe('Branch: main');
});

it('rawValue wins over compact labels', () => {
Expand Down Expand Up @@ -99,7 +99,8 @@ describe('startsWithCompactLabel', () => {
expect(startsWithCompactLabel('Model: Opus')).toBe(true);
expect(startsWithCompactLabel('Context: [bar]')).toBe(true);
expect(startsWithCompactLabel('Cost: $1')).toBe(true);
expect(startsWithCompactLabel('In: 15k')).toBe(false);
expect(startsWithCompactLabel('In: 15k')).toBe(true);
expect(startsWithCompactLabel('Branch: main')).toBe(false);
expect(startsWithCompactLabel('')).toBe(false);
});
});
Expand Down
34 changes: 32 additions & 2 deletions src/widgets/shared/raw-or-labeled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,40 @@ import type {
// label (Cost: $2.46 -> $2.46 — the value's own $ is the glyph). Add entries
// here to cover more widgets — everything routed through the helper picks
// them up automatically.
const COMPACT_LABELS: Record<string, string> = {
export const COMPACT_LABELS: Record<string, string> = {
'Model: ': 'M: ',
'Context: ': 'Ctx: ',
'Cost: ': ''
'Cost: ': '',
'Session: ': 'S: ',
'Weekly: ': 'W: ',
'Weekly Sonnet: ': 'WS: ',
'Weekly Opus: ': 'WO: ',
'Weekly Fable: ': 'WF: ',
'In: ': 'I: ',
'Out: ': 'O: ',
'Total: ': 'T: ',
'Cached: ': 'C: ',
'Turn: ': 'Tr: ',
'Cache: ': 'Ca: ',
'Cache Read: ': 'CR: ',
'Cache Write: ': 'CW: ',
'Cache Hit: ': 'CH: ',
'Block: ': 'B: ',
'Block ': 'B ',
'Reset: ': 'R: ',
'Reset ': 'R ',
'Weekly Reset: ': 'WR: ',
'Weekly Reset ': 'WR ',
'Overage: ': 'Ov: ',
'Overage Used: ': 'OvU: ',
'Overage Left: ': 'OvL: ',
'Ctx Used: ': 'CU: ',
'Ctx Left: ': 'CL: ',
// Both usable-context variants share the plain presets: the (u) marker
// distinguishes widgets that are rarely shown side by side.
'Ctx(u) Used: ': 'CU: ',
'Ctx(u) Left: ': 'CL: ',
'Claude: ': 'CC: '
};

const COMPACT_LABEL_METADATA_KEY = 'compactLabel';
Expand Down
Loading