|
| 1 | +import type { Theme } from "@mariozechner/pi-coding-agent"; |
| 2 | +import { truncateToWidth } from "@mariozechner/pi-tui"; |
| 3 | +import { ansiFg, ansiStyle } from "./ansi.js"; |
| 4 | +import type { RenderSegment, TokyoNightBlockName } from "./types.js"; |
| 5 | + |
| 6 | +interface TokyoNightBlock { |
| 7 | + name: TokyoNightBlockName; |
| 8 | + segments: RenderSegment[]; |
| 9 | +} |
| 10 | + |
| 11 | +interface BlockColors { |
| 12 | + fg: string; |
| 13 | + bg: string; |
| 14 | +} |
| 15 | + |
| 16 | +const TOKYO_NIGHT_COLORS = { |
| 17 | + lead: "#a3aed2", |
| 18 | + header: { fg: "#090c0c", bg: "#a3aed2" }, |
| 19 | + directory: { fg: "#e3e5e5", bg: "#769ff0" }, |
| 20 | + git: { fg: "#769ff0", bg: "#394260" }, |
| 21 | + runtime: { fg: "#769ff0", bg: "#212736" }, |
| 22 | + meter: { fg: "#a0a9cb", bg: "#1d2230" }, |
| 23 | + extensionSeparator: "#394260", |
| 24 | +} as const satisfies Record<string, string | BlockColors>; |
| 25 | + |
| 26 | +const TOKYO_NIGHT_BLOCK_ORDER: TokyoNightBlockName[] = [ |
| 27 | + "header", |
| 28 | + "directory", |
| 29 | + "git", |
| 30 | + "runtime", |
| 31 | + "meter", |
| 32 | +]; |
| 33 | + |
| 34 | +export function renderTokyoNightStatusline(width: number, segments: RenderSegment[]): string { |
| 35 | + return truncateToWidth(joinTokyoNightSegments(segments), width, ""); |
| 36 | +} |
| 37 | + |
| 38 | +export function tokyoNightExtensionSeparator(_theme: Theme): string { |
| 39 | + return ansiFg(TOKYO_NIGHT_COLORS.extensionSeparator, " "); |
| 40 | +} |
| 41 | + |
| 42 | +function joinTokyoNightSegments(segments: RenderSegment[]): string { |
| 43 | + const blocks = groupTokyoNightBlocks(segments); |
| 44 | + let line = ansiFg(TOKYO_NIGHT_COLORS.lead, "░▒▓"); |
| 45 | + |
| 46 | + for (const [index, block] of blocks.entries()) { |
| 47 | + const colors = getTokyoNightBlockColors(block.name); |
| 48 | + const previous = |
| 49 | + index === 0 ? undefined : getTokyoNightBlockColors(blocks[index - 1]?.name ?? "header"); |
| 50 | + if (previous) line += ansiStyle("", { fg: previous.bg, bg: colors.bg }); |
| 51 | + line += ansiStyle(formatTokyoNightBlockText(block), colors); |
| 52 | + } |
| 53 | + |
| 54 | + const lastBlock = blocks.at(-1); |
| 55 | + if (lastBlock) line += ansiFg(getTokyoNightBlockColors(lastBlock.name).bg, ""); |
| 56 | + |
| 57 | + return line; |
| 58 | +} |
| 59 | + |
| 60 | +function groupTokyoNightBlocks(segments: RenderSegment[]): TokyoNightBlock[] { |
| 61 | + const blocksByName = new Map<TokyoNightBlockName, RenderSegment[]>(); |
| 62 | + for (const segment of segments) { |
| 63 | + const blockSegments = blocksByName.get(segment.block) ?? []; |
| 64 | + blockSegments.push(segment); |
| 65 | + blocksByName.set(segment.block, blockSegments); |
| 66 | + } |
| 67 | + |
| 68 | + return TOKYO_NIGHT_BLOCK_ORDER.flatMap((name) => { |
| 69 | + const blockSegments = blocksByName.get(name); |
| 70 | + return blockSegments ? [{ name, segments: blockSegments }] : []; |
| 71 | + }); |
| 72 | +} |
| 73 | + |
| 74 | +function formatTokyoNightBlockText(block: TokyoNightBlock): string { |
| 75 | + return ` ${block.segments.map(formatTokyoNightSegmentText).join(" ")}`; |
| 76 | +} |
| 77 | + |
| 78 | +function formatTokyoNightSegmentText(segment: RenderSegment): string { |
| 79 | + return segment.emphasis ? `\u001b[1m${segment.text}\u001b[22m` : segment.text; |
| 80 | +} |
| 81 | + |
| 82 | +function getTokyoNightBlockColors(block: TokyoNightBlockName): BlockColors { |
| 83 | + return TOKYO_NIGHT_COLORS[block]; |
| 84 | +} |
0 commit comments