mirror of
https://github.com/tiennm99/ccstatusline.git
synced 2026-10-15 06:16:00 +00:00
Adding powerline widget auto-alignment toggle
This commit is contained in:
1 parent
bbf50be1b0
commit
b46319c4e2
9 files changed
+123
-21
No files matched your search
@@ -42,6 +42,12 @@
|
|||||||
|
|
||||||
## 🆕 Recent Updates
|
## 🆕 Recent Updates
|
||||||
|
|
||||||
|
### v2.0.8 - Powerline Auto-Alignment
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **🎯 Widget Alignment** - Auto-align widgets across multiple status lines in Powerline mode for a clean, columnar layout (toggle with 'a' in Powerline Setup)
|
||||||
|
|
||||||
### v2.0.7 - Current Working Directory & Session Cost
|
### v2.0.7 - Current Working Directory & Session Cost
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ccstatusline",
|
"name": "ccstatusline",
|
||||||
"version": "2.0.7",
|
"version": "2.0.8",
|
||||||
"description": "A customizable status line formatter for Claude Code CLI",
|
"description": "A customizable status line formatter for Claude Code CLI",
|
||||||
"module": "src/ccstatusline.ts",
|
"module": "src/ccstatusline.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
+6
-1
@@ -93,13 +93,18 @@ async function renderMultipleLines(data: StatusJSON) {
|
|||||||
isPreview: false
|
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
|
// Render each line
|
||||||
let globalSeparatorIndex = 0;
|
let globalSeparatorIndex = 0;
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const lineItems = lines[i];
|
const lineItems = lines[i];
|
||||||
if (lineItems && lineItems.length > 0) {
|
if (lineItems && lineItems.length > 0) {
|
||||||
const lineContext = { ...context, lineIndex: i, globalSeparatorIndex };
|
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)
|
// Only output the line if it has content (not just ANSI codes)
|
||||||
// Strip ANSI codes to check if there's actual text
|
// Strip ANSI codes to check if there's actual text
|
||||||
|
|||||||
@@ -183,6 +183,10 @@ export const PowerlineSetup: React.FC<PowerlineSetupProps> = ({
|
|||||||
} else if (input === 'i' || input === 'I') {
|
} else if (input === 'i' || input === 'I') {
|
||||||
// Show font installation consent prompt
|
// Show font installation consent prompt
|
||||||
setConfirmingFontInstall(true);
|
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'>
|
<Box flexDirection='column'>
|
||||||
<Text>
|
<Text>
|
||||||
{' Font Status: '}
|
{' Font Status: '}
|
||||||
{powerlineFontStatus.installed ? (
|
{powerlineFontStatus.installed ? (
|
||||||
<>
|
<>
|
||||||
<Text color='green'>✓ Installed</Text>
|
<Text color='green'>✓ Installed</Text>
|
||||||
@@ -383,7 +387,7 @@ export const PowerlineSetup: React.FC<PowerlineSetupProps> = ({
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text>Powerline Mode: </Text>
|
<Text> Powerline Mode: </Text>
|
||||||
<Text color={powerlineConfig.enabled ? 'green' : 'red'}>
|
<Text color={powerlineConfig.enabled ? 'green' : 'red'}>
|
||||||
{powerlineConfig.enabled ? '✓ Enabled ' : '✗ Disabled '}
|
{powerlineConfig.enabled ? '✓ Enabled ' : '✗ Disabled '}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -391,11 +395,21 @@ export const PowerlineSetup: React.FC<PowerlineSetupProps> = ({
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{powerlineConfig.enabled && (
|
{powerlineConfig.enabled && (
|
||||||
<Box flexDirection='column' marginTop={1}>
|
<>
|
||||||
<Text dimColor>
|
<Box>
|
||||||
When enabled, global overrides are disabled and powerline separators are used
|
<Text> Align Widgets: </Text>
|
||||||
</Text>
|
<Text color={powerlineConfig.autoAlign ? 'green' : 'red'}>
|
||||||
</Box>
|
{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'>
|
<Box marginTop={1} flexDirection='column'>
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ const renderSingleLine = (
|
|||||||
widthDetectionAvailable: boolean,
|
widthDetectionAvailable: boolean,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
lineIndex: number,
|
lineIndex: number,
|
||||||
globalSeparatorIndex: number
|
globalSeparatorIndex: number,
|
||||||
|
allLinesWidgets?: WidgetItem[][]
|
||||||
): RenderResult => {
|
): RenderResult => {
|
||||||
// Create render context for preview
|
// Create render context for preview
|
||||||
const context: RenderContext = {
|
const context: RenderContext = {
|
||||||
@@ -37,7 +38,7 @@ const renderSingleLine = (
|
|||||||
globalSeparatorIndex
|
globalSeparatorIndex
|
||||||
};
|
};
|
||||||
|
|
||||||
return renderStatusLineWithInfo(widgets, settings, context);
|
return renderStatusLineWithInfo(widgets, settings, context, allLinesWidgets);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StatusLinePreview: React.FC<StatusLinePreviewProps> = ({ lines, terminalWidth, settings, onTruncationChange }) => {
|
export const StatusLinePreview: React.FC<StatusLinePreviewProps> = ({ lines, terminalWidth, settings, onTruncationChange }) => {
|
||||||
@@ -49,6 +50,11 @@ export const StatusLinePreview: React.FC<StatusLinePreviewProps> = ({ lines, ter
|
|||||||
if (!settings)
|
if (!settings)
|
||||||
return { renderedLines: [], anyTruncated: false };
|
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;
|
let globalSeparatorIndex = 0;
|
||||||
const result: string[] = [];
|
const result: string[] = [];
|
||||||
let truncated = false;
|
let truncated = false;
|
||||||
@@ -56,7 +62,7 @@ export const StatusLinePreview: React.FC<StatusLinePreviewProps> = ({ lines, ter
|
|||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const lineItems = lines[i];
|
const lineItems = lines[i];
|
||||||
if (lineItems && lineItems.length > 0) {
|
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);
|
result.push(renderResult.line);
|
||||||
if (renderResult.wasTruncated) {
|
if (renderResult.wasTruncated) {
|
||||||
truncated = true;
|
truncated = true;
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ export const PowerlineConfigSchema = z.object({
|
|||||||
separatorInvertBackground: z.array(z.boolean()).default([false]),
|
separatorInvertBackground: z.array(z.boolean()).default([false]),
|
||||||
startCaps: z.array(z.string()).default([]),
|
startCaps: z.array(z.string()).default([]),
|
||||||
endCaps: 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
|
// Inferred type from schema
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ export const SettingsSchema = z.object({
|
|||||||
separatorInvertBackground: [false],
|
separatorInvertBackground: [false],
|
||||||
startCaps: [],
|
startCaps: [],
|
||||||
endCaps: [],
|
endCaps: [],
|
||||||
theme: undefined
|
theme: undefined,
|
||||||
|
autoAlign: false
|
||||||
}),
|
}),
|
||||||
updatemessage: z.object({
|
updatemessage: z.object({
|
||||||
message: z.string().nullable().optional(),
|
message: z.string().nullable().optional(),
|
||||||
|
|||||||
+76
-7
@@ -45,7 +45,8 @@ function renderPowerlineStatusLine(
|
|||||||
settings: Settings,
|
settings: Settings,
|
||||||
context: RenderContext,
|
context: RenderContext,
|
||||||
lineIndex = 0, // Which line we're rendering (for theme color cycling)
|
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 {
|
): string {
|
||||||
const powerlineConfig = settings.powerline as Record<string, unknown> | undefined;
|
const powerlineConfig = settings.powerline as Record<string, unknown> | undefined;
|
||||||
const config = powerlineConfig ?? {};
|
const config = powerlineConfig ?? {};
|
||||||
@@ -209,6 +210,72 @@ function renderPowerlineStatusLine(
|
|||||||
if (widgetElements.length === 0)
|
if (widgetElements.length === 0)
|
||||||
return '';
|
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
|
// Build the final powerline string
|
||||||
let result = '';
|
let result = '';
|
||||||
|
|
||||||
@@ -444,9 +511,10 @@ export interface RenderResult {
|
|||||||
export function renderStatusLineWithInfo(
|
export function renderStatusLineWithInfo(
|
||||||
widgets: WidgetItem[],
|
widgets: WidgetItem[],
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
context: RenderContext
|
context: RenderContext,
|
||||||
|
allLinesWidgets?: WidgetItem[][]
|
||||||
): RenderResult {
|
): RenderResult {
|
||||||
const line = renderStatusLine(widgets, settings, context);
|
const line = renderStatusLine(widgets, settings, context, allLinesWidgets);
|
||||||
// Check if line contains the truncation ellipsis
|
// Check if line contains the truncation ellipsis
|
||||||
const wasTruncated = line.includes('...');
|
const wasTruncated = line.includes('...');
|
||||||
return { line, wasTruncated };
|
return { line, wasTruncated };
|
||||||
@@ -455,7 +523,8 @@ export function renderStatusLineWithInfo(
|
|||||||
export function renderStatusLine(
|
export function renderStatusLine(
|
||||||
widgets: WidgetItem[],
|
widgets: WidgetItem[],
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
context: RenderContext
|
context: RenderContext,
|
||||||
|
allLinesWidgets?: WidgetItem[][]
|
||||||
): string {
|
): string {
|
||||||
// Force 24-bit color for non-preview statusline rendering
|
// Force 24-bit color for non-preview statusline rendering
|
||||||
// Chalk level is now set globally in ccstatusline.ts and tui.tsx
|
// Chalk level is now set globally in ccstatusline.ts and tui.tsx
|
||||||
@@ -469,9 +538,9 @@ export function renderStatusLine(
|
|||||||
const isPowerlineMode = Boolean(powerlineSettings?.enabled);
|
const isPowerlineMode = Boolean(powerlineSettings?.enabled);
|
||||||
|
|
||||||
// If powerline mode is enabled, use powerline renderer
|
// If powerline mode is enabled, use powerline renderer
|
||||||
if (isPowerlineMode) {
|
if (isPowerlineMode)
|
||||||
return renderPowerlineStatusLine(widgets, settings, context, context.lineIndex ?? 0, context.globalSeparatorIndex ?? 0);
|
return renderPowerlineStatusLine(widgets, settings, context, context.lineIndex ?? 0, context.globalSeparatorIndex ?? 0, allLinesWidgets);
|
||||||
}
|
|
||||||
// Helper to apply colors with optional background and bold override
|
// Helper to apply colors with optional background and bold override
|
||||||
const applyColorsWithOverride = (text: string, foregroundColor?: string, backgroundColor?: string, bold?: boolean): string => {
|
const applyColorsWithOverride = (text: string, foregroundColor?: string, backgroundColor?: string, bold?: boolean): string => {
|
||||||
// Override foreground color takes precedence over EVERYTHING, including passed foreground color
|
// Override foreground color takes precedence over EVERYTHING, including passed foreground color
|
||||||
|
|||||||
Reference in new issue
Block a user