anvilsign in

collin/strudel-claude

main / node-tui / ui / StatusBar.ts
1import { Box, Text } from 'ink';
2import type { ReactElement, ReactNode } from 'react';
3import { html } from './html.ts';
4import type { Mode } from '../vim.ts';
5
6function Field({ label, children, color }: {
7 label: string;
8 children: ReactNode;
9 color?: string;
10}): ReactElement {
11 return html`
12 <${Box} marginRight=${2}>
13 <${Text} dimColor>${label} <//>
14 <${Text} color=${color}>${children}<//>
15 <//>
16 `;
17}
18
19const MODE_COLORS: Record<Mode, string> = {
20 normal: 'blue',
21 insert: 'green',
22 visual: 'yellow',
23 'visual-line': 'yellow',
24};
25
26const MODE_LABELS: Record<Mode, string> = {
27 normal: 'NORMAL',
28 insert: 'INSERT',
29 visual: 'VISUAL',
30 'visual-line': 'V-LINE',
31};
32
33export interface StatusBarProps {
34 playing: boolean;
35 cps: number;
36 cycle: number;
37 /** True when the buffer has edits that haven't been evaluated yet. */
38 dirty: boolean;
39 mode: Mode;
40 /** Keys accumulated toward a command — vim's bottom-right indicator. */
41 pending: string;
42 session: string | null;
43 error: string | null;
44}
45
46export function StatusBar({
47 playing, cps, cycle, dirty, mode, pending, session, error,
48}: StatusBarProps): ReactElement {
49 const modeChip = html`
50 <${Text} backgroundColor=${MODE_COLORS[mode]} color="black" bold> ${MODE_LABELS[mode]} <//>
51 `;
52 // An eval error is the one thing worth losing the whole status line over —
53 // it's why the pattern you just heard isn't the pattern on screen.
54 if (error) {
55 return html`
56 <${Box}>
57 ${modeChip}
58 <${Text} backgroundColor="red" color="white" bold> ERROR <//>
59 <${Text} color="red" wrap="truncate-end"> ${error}<//>
60 <//>
61 `;
62 }
63
64 return html`
65 <${Box}>
66 ${modeChip}
67 <${Text}> <//>
68 <${Text} color=${playing ? 'green' : 'gray'} bold>
69 ${playing ? '▶ playing' : '■ stopped'}
70 <//>
71 <${Text}> <//>
72 <${Field} label="cpm">${(cps * 60).toFixed(0)}<//>
73 <${Field} label="cycle">${cycle.toFixed(1)}<//>
74 ${session ? html`<${Field} label="♫" color="magenta">${session}<//>` : null}
75 ${dirty ? html`<${Text} color="yellow">● unevaluated<//>` : null}
76 <${Box} flexGrow=${1} justifyContent="flex-end">
77 <${Text} dimColor>${pending}<//>
78 <//>
79 <//>
80 `;
81}