@owloops/claude-powerline
Version:
Beautiful vim-style powerline statusline for Claude Code with real-time usage tracking, git integration, and custom themes
185 lines (161 loc) • 4.68 kB
text/typescript
import type { BoxChars } from "./types";
import { visibleLength, stripAnsi, ESC, ANSI_SPLIT } from "../utils/terminal";
export function colorize(
text: string,
fgColor: string,
reset: string,
bold = false,
): string {
const useBold = bold && reset !== "";
if (!fgColor && !useBold) {
return text;
}
if (!useBold) {
return `${fgColor}${text}${reset}`;
}
return `${fgColor}\x1b[1m${text}\x1b[22m${reset}`;
}
export function padRight(text: string, width: number): string {
const visible = visibleLength(text);
if (visible >= width) {
return text;
}
return text + " ".repeat(width - visible);
}
export function padLeft(text: string, width: number): string {
const visible = visibleLength(text);
if (visible >= width) {
return text;
}
return " ".repeat(width - visible) + text;
}
export function padCenter(text: string, width: number): string {
const visible = visibleLength(text);
if (visible >= width) {
return text;
}
const totalPad = width - visible;
const leftPad = Math.floor(totalPad / 2);
const rightPad = totalPad - leftPad;
return " ".repeat(leftPad) + text + " ".repeat(rightPad);
}
export function truncateAnsi(text: string, maxWidth: number): string {
if (stripAnsi(text).length <= maxWidth) {
return text;
}
let width = 0;
let result = "";
const parts = text.split(ANSI_SPLIT);
for (const part of parts) {
if (part.startsWith(ESC)) {
result += part;
continue;
}
for (const char of part) {
if (width >= maxWidth - 1) {
result += "…\x1b[0m";
return result;
}
result += char;
width++;
}
}
return result;
}
export function contentRow(
box: BoxChars,
content: string,
innerWidth: number,
): string {
const maxContent = innerWidth - 2;
const truncated = truncateAnsi(content, maxContent);
const padded = padRight(truncated, maxContent);
return box.vertical + " " + padded + " " + box.vertical;
}
export function divider(box: BoxChars, innerWidth: number): string {
return box.teeLeft + box.horizontal.repeat(innerWidth) + box.teeRight;
}
export function bottomBorder(
box: BoxChars,
innerWidth: number,
leftText?: string,
rightText?: string,
): string {
if (!leftText && !rightText) {
return box.bottomLeft + box.horizontal.repeat(innerWidth) + box.bottomRight;
}
let left = leftText ? ` ${leftText} ` : "";
let right = rightText ? ` ${rightText} ` : "";
let leftLen = visibleLength(left);
let rightLen = visibleLength(right);
// Truncate if combined text exceeds innerWidth
if (leftLen + rightLen > innerWidth) {
const maxLeft = Math.max(0, innerWidth - rightLen);
if (leftLen > maxLeft) {
left = truncateAnsi(left, maxLeft);
leftLen = visibleLength(left);
}
if (leftLen + rightLen > innerWidth) {
const maxRight = Math.max(0, innerWidth - leftLen);
right = truncateAnsi(right, maxRight);
rightLen = visibleLength(right);
}
}
const fillCount = innerWidth - leftLen - rightLen;
return (
box.bottomLeft +
left +
box.horizontal.repeat(Math.max(0, fillCount)) +
right +
box.bottomRight
);
}
export function spreadEven(parts: string[], totalWidth: number): string {
if (parts.length === 0) {
return "";
}
if (parts.length === 1) {
return parts[0] ?? "";
}
const widths = parts.map((p) => visibleLength(p));
const totalContentWidth = widths.reduce((sum, w) => sum + w, 0);
const totalGap = totalWidth - totalContentWidth;
const gapPerSlot = Math.max(2, Math.floor(totalGap / (parts.length - 1)));
const suffixWidths = Array.from<number>({ length: parts.length });
suffixWidths[parts.length - 1] = widths[parts.length - 1] ?? 0;
for (let i = parts.length - 2; i >= 0; i--) {
suffixWidths[i] = (suffixWidths[i + 1] ?? 0) + (widths[i] ?? 0);
}
let result = parts[0] ?? "";
let usedWidth = widths[0] ?? 0;
for (let i = 1; i < parts.length; i++) {
const remaining =
totalWidth -
usedWidth -
(suffixWidths[i] ?? 0) -
(parts.length - 1 - i) * 2;
const gap = Math.max(2, Math.min(gapPerSlot, remaining));
result += " ".repeat(gap) + (parts[i] ?? "");
usedWidth += gap + (widths[i] ?? 0);
}
return result;
}
export function spreadTwo(
left: string,
right: string,
totalWidth: number,
): string {
if (!right) {
return left;
}
if (!left) {
return right;
}
const leftLen = visibleLength(left);
const rightLen = visibleLength(right);
const gap = totalWidth - leftLen - rightLen;
if (gap < 2) {
return `${left} ${right}`;
}
return left + " ".repeat(gap) + right;
}