Adding powerline widget auto-alignment toggle

This commit is contained in:
Matthew Breedlove
2025-08-24 00:22:22 -04:00
parent bbf50be1b0
commit b46319c4e2
9 changed files with 123 additions and 21 deletions
+6 -1
View File
@@ -93,13 +93,18 @@ async function renderMultipleLines(data: StatusJSON) {
isPreview: false
};
// Check if powerline mode is enabled and autoAlign is on
const isPowerlineMode = settings.powerline.enabled;
const autoAlign = settings.powerline.autoAlign;
const allLinesWidgets = (isPowerlineMode && autoAlign) ? lines : undefined;
// Render each line
let globalSeparatorIndex = 0;
for (let i = 0; i < lines.length; i++) {
const lineItems = lines[i];
if (lineItems && lineItems.length > 0) {
const lineContext = { ...context, lineIndex: i, globalSeparatorIndex };
const line = renderStatusLine(lineItems, settings, lineContext);
const line = renderStatusLine(lineItems, settings, lineContext, allLinesWidgets);
// Only output the line if it has content (not just ANSI codes)
// Strip ANSI codes to check if there's actual text
+21 -7
View File
@@ -183,6 +183,10 @@ export const PowerlineSetup: React.FC<PowerlineSetupProps> = ({
} else if (input === 'i' || input === 'I') {
// Show font installation consent prompt
setConfirmingFontInstall(true);
} else if ((input === 'a' || input === 'A') && powerlineConfig.enabled) {
// Toggle autoAlign when powerline is enabled
const newConfig = { ...powerlineConfig, autoAlign: !powerlineConfig.autoAlign };
onUpdate({ ...settings, powerline: newConfig });
}
}
});
@@ -367,7 +371,7 @@ export const PowerlineSetup: React.FC<PowerlineSetupProps> = ({
<>
<Box flexDirection='column'>
<Text>
{' Font Status: '}
{' Font Status: '}
{powerlineFontStatus.installed ? (
<>
<Text color='green'> Installed</Text>
@@ -383,7 +387,7 @@ export const PowerlineSetup: React.FC<PowerlineSetupProps> = ({
</Box>
<Box>
<Text>Powerline Mode: </Text>
<Text> Powerline Mode: </Text>
<Text color={powerlineConfig.enabled ? 'green' : 'red'}>
{powerlineConfig.enabled ? '✓ Enabled ' : '✗ Disabled '}
</Text>
@@ -391,11 +395,21 @@ export const PowerlineSetup: React.FC<PowerlineSetupProps> = ({
</Box>
{powerlineConfig.enabled && (
<Box flexDirection='column' marginTop={1}>
<Text dimColor>
When enabled, global overrides are disabled and powerline separators are used
</Text>
</Box>
<>
<Box>
<Text> Align Widgets: </Text>
<Text color={powerlineConfig.autoAlign ? 'green' : 'red'}>
{powerlineConfig.autoAlign ? '✓ Enabled ' : '✗ Disabled '}
</Text>
<Text dimColor> - Press (a) to toggle</Text>
</Box>
<Box flexDirection='column' marginTop={1}>
<Text dimColor>
When enabled, global overrides are disabled and powerline separators are used
</Text>
</Box>
</>
)}
<Box marginTop={1} flexDirection='column'>
+9 -3
View File
@@ -27,7 +27,8 @@ const renderSingleLine = (
widthDetectionAvailable: boolean,
settings: Settings,
lineIndex: number,
globalSeparatorIndex: number
globalSeparatorIndex: number,
allLinesWidgets?: WidgetItem[][]
): RenderResult => {
// Create render context for preview
const context: RenderContext = {
@@ -37,7 +38,7 @@ const renderSingleLine = (
globalSeparatorIndex
};
return renderStatusLineWithInfo(widgets, settings, context);
return renderStatusLineWithInfo(widgets, settings, context, allLinesWidgets);
};
export const StatusLinePreview: React.FC<StatusLinePreviewProps> = ({ lines, terminalWidth, settings, onTruncationChange }) => {
@@ -49,6 +50,11 @@ export const StatusLinePreview: React.FC<StatusLinePreviewProps> = ({ lines, ter
if (!settings)
return { renderedLines: [], anyTruncated: false };
// Check if powerline mode is enabled and autoAlign is on
const isPowerlineMode = settings.powerline.enabled;
const autoAlign = settings.powerline.autoAlign;
const allLinesWidgets = (isPowerlineMode && autoAlign) ? lines : undefined;
let globalSeparatorIndex = 0;
const result: string[] = [];
let truncated = false;
@@ -56,7 +62,7 @@ export const StatusLinePreview: React.FC<StatusLinePreviewProps> = ({ lines, ter
for (let i = 0; i < lines.length; i++) {
const lineItems = lines[i];
if (lineItems && lineItems.length > 0) {
const renderResult = renderSingleLine(lineItems, terminalWidth, widthDetectionAvailable, settings, i, globalSeparatorIndex);
const renderResult = renderSingleLine(lineItems, terminalWidth, widthDetectionAvailable, settings, i, globalSeparatorIndex, allLinesWidgets);
result.push(renderResult.line);
if (renderResult.wasTruncated) {
truncated = true;
+2 -1
View File
@@ -7,7 +7,8 @@ export const PowerlineConfigSchema = z.object({
separatorInvertBackground: z.array(z.boolean()).default([false]),
startCaps: z.array(z.string()).default([]),
endCaps: z.array(z.string()).default([]),
theme: z.string().optional()
theme: z.string().optional(),
autoAlign: z.boolean().default(false)
});
// Inferred type from schema
+2 -1
View File
@@ -55,7 +55,8 @@ export const SettingsSchema = z.object({
separatorInvertBackground: [false],
startCaps: [],
endCaps: [],
theme: undefined
theme: undefined,
autoAlign: false
}),
updatemessage: z.object({
message: z.string().nullable().optional(),
+76 -7
View File
@@ -45,7 +45,8 @@ function renderPowerlineStatusLine(
settings: Settings,
context: RenderContext,
lineIndex = 0, // Which line we're rendering (for theme color cycling)
globalSeparatorOffset = 0 // Starting separator index for this line
globalSeparatorOffset = 0, // Starting separator index for this line
allLinesWidgets?: WidgetItem[][] // All lines widgets for alignment calculation
): string {
const powerlineConfig = settings.powerline as Record<string, unknown> | undefined;
const config = powerlineConfig ?? {};
@@ -209,6 +210,72 @@ function renderPowerlineStatusLine(
if (widgetElements.length === 0)
return '';
// Apply auto-alignment if enabled
const autoAlign = config.autoAlign as boolean | undefined;
if (autoAlign && allLinesWidgets) {
// Calculate max width for each widget position across all lines
const maxWidths: number[] = [];
// First, collect all widget contents for each line
const allLinesElements: { content: string; bgColor?: string; fgColor?: string; widget: WidgetItem }[][] = [];
for (const lineWidgets of allLinesWidgets) {
const filteredLineWidgets = lineWidgets.filter(w => w.type !== 'separator' && w.type !== 'flex-separator');
const lineElements: { content: string; bgColor?: string; fgColor?: string; widget: WidgetItem }[] = [];
for (const widget of filteredLineWidgets) {
let widgetText = '';
try {
const widgetImpl = getWidget(widget.type);
widgetText = widgetImpl.render(widget, context, settings) ?? '';
} catch {
continue;
}
if (widgetText) {
const padding = settings.defaultPadding ?? '';
const paddedText = `${padding}${widgetText}${padding}`;
lineElements.push({
content: paddedText,
bgColor: widget.backgroundColor,
fgColor: widget.color,
widget: widget
});
}
}
allLinesElements.push(lineElements);
}
// Calculate max width for each position
for (let pos = 0; pos < Math.max(...allLinesElements.map(line => line.length)); pos++) {
let maxWidth = 0;
for (const lineElements of allLinesElements) {
const element = lineElements[pos];
if (element) {
// Get the plain text length (without ANSI codes)
const plainLength = element.content.replace(ANSI_REGEX, '').length;
maxWidth = Math.max(maxWidth, plainLength);
}
}
maxWidths.push(maxWidth);
}
// Apply padding to current line's widgets
for (let i = 0; i < widgetElements.length; i++) {
const element = widgetElements[i];
const maxWidth = maxWidths[i];
if (element && maxWidth !== undefined) {
const currentLength = element.content.replace(ANSI_REGEX, '').length;
const paddingNeeded = maxWidth - currentLength;
if (paddingNeeded > 0) {
// Add spaces to the right of the content
element.content += ' '.repeat(paddingNeeded);
}
}
}
}
// Build the final powerline string
let result = '';
@@ -444,9 +511,10 @@ export interface RenderResult {
export function renderStatusLineWithInfo(
widgets: WidgetItem[],
settings: Settings,
context: RenderContext
context: RenderContext,
allLinesWidgets?: WidgetItem[][]
): RenderResult {
const line = renderStatusLine(widgets, settings, context);
const line = renderStatusLine(widgets, settings, context, allLinesWidgets);
// Check if line contains the truncation ellipsis
const wasTruncated = line.includes('...');
return { line, wasTruncated };
@@ -455,7 +523,8 @@ export function renderStatusLineWithInfo(
export function renderStatusLine(
widgets: WidgetItem[],
settings: Settings,
context: RenderContext
context: RenderContext,
allLinesWidgets?: WidgetItem[][]
): string {
// Force 24-bit color for non-preview statusline rendering
// Chalk level is now set globally in ccstatusline.ts and tui.tsx
@@ -469,9 +538,9 @@ export function renderStatusLine(
const isPowerlineMode = Boolean(powerlineSettings?.enabled);
// If powerline mode is enabled, use powerline renderer
if (isPowerlineMode) {
return renderPowerlineStatusLine(widgets, settings, context, context.lineIndex ?? 0, context.globalSeparatorIndex ?? 0);
}
if (isPowerlineMode)
return renderPowerlineStatusLine(widgets, settings, context, context.lineIndex ?? 0, context.globalSeparatorIndex ?? 0, allLinesWidgets);
// Helper to apply colors with optional background and bold override
const applyColorsWithOverride = (text: string, foregroundColor?: string, backgroundColor?: string, bold?: boolean): string => {
// Override foreground color takes precedence over EVERYTHING, including passed foreground color