11import { truncateToWidth , visibleWidth } from "@earendil-works/pi-tui" ;
22
3- import {
4- ACTIVE_FOOTER_VARIANT ,
5- BLOCK_COLORS ,
6- BRANCH_ICON ,
7- PLAIN_COLORS ,
8- PLAIN_SEPARATOR_COLORS ,
9- } from "./constants.ts" ;
3+ import { ACTIVE_FOOTER_VARIANT , BRANCH_ICON } from "./constants.ts" ;
104import { getFooterSlotSnapshots , subscribeFooterSlotUpdates } from "./footer-slot-api.ts" ;
115import { DEFAULT_FOOTER_CONFIG , type FooterConfig } from "./settings.ts" ;
126import type {
@@ -19,10 +13,12 @@ import type {
1913 FooterSlotSnapshot ,
2014 FooterSide ,
2115 FooterVariant ,
22- Rgb ,
23- SegmentColors ,
2416} from "./types.ts" ;
2517
18+ export type PlainFooterTheme = {
19+ fg ( role : "muted" | "dim" , text : string ) : string ;
20+ } ;
21+
2622function sanitizeStatusText ( text : string ) : string {
2723 return text
2824 . replace ( / [ \r \n \t ] / g, " " )
@@ -46,45 +42,21 @@ function collapseHome(path: string): string {
4642 return path ;
4743}
4844
49- function hexToRgb ( hex : string ) : Rgb {
50- const value = hex . replace ( / ^ # / , "" ) ;
51- if ( ! / ^ [ 0 - 9 a - f A - F ] { 6 } $ / . test ( value ) ) {
52- throw new Error ( `Invalid hex color: ${ hex } ` ) ;
53- }
54- return [
55- parseInt ( value . slice ( 0 , 2 ) , 16 ) ,
56- parseInt ( value . slice ( 2 , 4 ) , 16 ) ,
57- parseInt ( value . slice ( 4 , 6 ) , 16 ) ,
58- ] ;
59- }
60-
61- function ansiColor ( text : string , options : { fg ?: string ; bg ?: string ; bold ?: boolean } ) : string {
62- const codes : string [ ] = [ ] ;
63-
64- if ( options . bold === true ) codes . push ( "1" ) ;
65- if ( options . fg !== undefined && options . fg . length > 0 ) {
66- const [ r , g , b ] = hexToRgb ( options . fg ) ;
67- codes . push ( `38;2;${ r } ;${ g } ;${ b } ` ) ;
68- }
69- if ( options . bg !== undefined && options . bg . length > 0 ) {
70- const [ r , g , b ] = hexToRgb ( options . bg ) ;
71- codes . push ( `48;2;${ r } ;${ g } ;${ b } ` ) ;
72- }
73-
74- if ( codes . length === 0 ) return text ;
75- return `\x1b[${ codes . join ( ";" ) } m${ text } \x1b[0m` ;
76- }
77-
78- function renderColoredText ( text : string , colors : SegmentColors ) : string {
79- return ansiColor ( text , { fg : colors . fg , bg : colors . bg } ) ;
45+ function renderBlockItem ( item : FooterItem ) : string {
46+ return ` ${ item . text } ` ;
8047}
8148
82- function renderBlockItem ( item : FooterItem ) : string {
83- return renderColoredText ( ` ${ item . text } ` , item . colors ) ;
49+ function renderThemeText (
50+ text : string ,
51+ role : "muted" | "dim" ,
52+ theme : PlainFooterTheme | undefined ,
53+ ) : string {
54+ if ( theme === undefined ) return text ;
55+ return theme . fg ( role , text ) ;
8456}
8557
86- function renderPlainItem ( item : FooterItem ) : string {
87- return renderColoredText ( item . text , PLAIN_COLORS ) ;
58+ function renderPlainItem ( item : FooterItem , theme : PlainFooterTheme | undefined ) : string {
59+ return renderThemeText ( item . text , "muted" , theme ) ;
8860}
8961
9062function getFallbackProviderDisplayName ( provider : string ) : string {
@@ -114,32 +86,6 @@ function getProviderDisplayName(ctx: FooterContext, provider: string): string {
11486 return getFallbackProviderDisplayName ( provider ) ;
11587}
11688
117- function getThinkingColors ( level : string ) : SegmentColors {
118- switch ( level ) {
119- case "minimal" :
120- return { bg : "#4338ca" , fg : "#ffffff" } ;
121- case "low" :
122- return { bg : "#0369a1" , fg : "#ffffff" } ;
123- case "medium" :
124- return { bg : "#0891b2" , fg : "#062b33" } ;
125- case "high" :
126- return { bg : "#8b5cf6" , fg : "#ffffff" } ;
127- case "xhigh" :
128- return { bg : "#dc2626" , fg : "#ffffff" } ;
129- case "off" :
130- default :
131- return { bg : "#374151" , fg : "#e5e7eb" } ;
132- }
133- }
134-
135- function getContextColors ( percent : number | null | undefined ) : SegmentColors {
136- if ( percent !== null && percent !== undefined ) {
137- if ( percent > 90 ) return { bg : "#dc2626" , fg : "#ffffff" } ;
138- if ( percent > 70 ) return { bg : "#f59e0b" , fg : "#1f1300" } ;
139- }
140- return { bg : "#06b6d4" , fg : "#062b33" } ;
141- }
142-
14389function getContextText ( usage : ContextUsage , fallbackWindow ?: number ) : string {
14490 const contextWindow = usage ?. contextWindow ?? fallbackWindow ?? 0 ;
14591 const contextPercent = usage ?. percent ;
@@ -165,28 +111,38 @@ function getMcpText(ctx: FooterContext, footerData: FooterData): string | null {
165111 return null ;
166112}
167113
168- function getSeparator ( variant : FooterVariant , side : FooterSide , config : FooterConfig ) : string {
114+ function getSeparator (
115+ variant : FooterVariant ,
116+ side : FooterSide ,
117+ config : FooterConfig ,
118+ theme : PlainFooterTheme | undefined ,
119+ ) : string {
169120 if ( variant === "blocks" ) return "" ;
170121 if ( side === "left" ) {
171- return renderColoredText ( ` ${ config . separator } ` , PLAIN_SEPARATOR_COLORS ) ;
122+ return renderThemeText ( ` ${ config . separator } ` , "dim" , theme ) ;
172123 }
173- return renderColoredText ( " " , PLAIN_SEPARATOR_COLORS ) ;
124+ return renderThemeText ( " " , "dim" , theme ) ;
174125}
175126
176- function renderItem ( item : FooterItem , variant : FooterVariant ) : string {
127+ function renderItem (
128+ item : FooterItem ,
129+ variant : FooterVariant ,
130+ theme : PlainFooterTheme | undefined ,
131+ ) : string {
177132 if ( variant === "blocks" ) {
178133 return renderBlockItem ( item ) ;
179134 }
180- return renderPlainItem ( item ) ;
135+ return renderPlainItem ( item , theme ) ;
181136}
182137
183138function joinRenderedItems (
184139 rendered : string [ ] ,
185140 variant : FooterVariant ,
186141 side : FooterSide ,
187142 config : FooterConfig ,
143+ theme : PlainFooterTheme | undefined ,
188144) : string {
189- return rendered . join ( getSeparator ( variant , side , config ) ) ;
145+ return rendered . join ( getSeparator ( variant , side , config , theme ) ) ;
190146}
191147
192148function buildSideVariants (
@@ -195,6 +151,7 @@ function buildSideVariants(
195151 variant : FooterVariant ,
196152 side : FooterSide ,
197153 config : FooterConfig ,
154+ theme : PlainFooterTheme | undefined ,
198155) : string [ ] {
199156 const items = keys
200157 . map ( ( key ) => itemsByKey . get ( key ) )
@@ -209,10 +166,11 @@ function buildSideVariants(
209166 if ( side === "left" ) {
210167 for ( let count = items . length ; count >= 1 ; count -- ) {
211168 const rendered = joinRenderedItems (
212- items . slice ( 0 , count ) . map ( ( item ) => renderItem ( item , variant ) ) ,
169+ items . slice ( 0 , count ) . map ( ( item ) => renderItem ( item , variant , theme ) ) ,
213170 variant ,
214171 side ,
215172 config ,
173+ theme ,
216174 ) ;
217175 if ( ! seen . has ( rendered ) ) {
218176 seen . add ( rendered ) ;
@@ -222,10 +180,11 @@ function buildSideVariants(
222180 } else {
223181 for ( let start = 0 ; start < items . length ; start ++ ) {
224182 const rendered = joinRenderedItems (
225- items . slice ( start ) . map ( ( item ) => renderItem ( item , variant ) ) ,
183+ items . slice ( start ) . map ( ( item ) => renderItem ( item , variant , theme ) ) ,
226184 variant ,
227185 side ,
228186 config ,
187+ theme ,
229188 ) ;
230189 if ( ! seen . has ( rendered ) ) {
231190 seen . add ( rendered ) ;
@@ -238,11 +197,15 @@ function buildSideVariants(
238197 return variants ;
239198}
240199
241- function renderPadding ( width : number , variant : FooterVariant ) : string {
200+ function renderPadding (
201+ width : number ,
202+ variant : FooterVariant ,
203+ theme : PlainFooterTheme | undefined ,
204+ ) : string {
242205 if ( width <= 0 ) return "" ;
243206 const padding = " " . repeat ( width ) ;
244207 if ( variant === "plain" ) {
245- return renderColoredText ( padding , PLAIN_COLORS ) ;
208+ return renderThemeText ( padding , "muted" , theme ) ;
246209 }
247210 return padding ;
248211}
@@ -266,42 +229,42 @@ function buildFooterItems(
266229 items . set ( "path" , {
267230 key : "path" ,
268231 text : pathText ,
269- colors : BLOCK_COLORS . path ,
232+ colors : { bg : "" , fg : "" } ,
270233 } ) ;
271234 items . set ( "provider" , {
272235 key : "provider" ,
273236 text : providerLabel ,
274- colors : BLOCK_COLORS . provider ,
237+ colors : { bg : "" , fg : "" } ,
275238 } ) ;
276239 items . set ( "model" , {
277240 key : "model" ,
278241 text : modelLabel ,
279- colors : BLOCK_COLORS . model ,
242+ colors : { bg : "" , fg : "" } ,
280243 } ) ;
281244 items . set ( "thinking" , {
282245 key : "thinking" ,
283246 text : thinkingLevel ,
284- colors : getThinkingColors ( thinkingLevel ) ,
247+ colors : { bg : "" , fg : "" } ,
285248 } ) ;
286249 items . set ( "context" , {
287250 key : "context" ,
288251 text : contextText ,
289- colors : getContextColors ( usage ?. percent ) ,
252+ colors : { bg : "" , fg : "" } ,
290253 } ) ;
291254
292255 if ( branch !== null && branch . length > 0 ) {
293256 items . set ( "branch" , {
294257 key : "branch" ,
295258 text : `${ BRANCH_ICON } ${ branch } ` ,
296- colors : BLOCK_COLORS . branch ,
259+ colors : { bg : "" , fg : "" } ,
297260 } ) ;
298261 }
299262
300263 if ( mcpText !== null && mcpText . length > 0 ) {
301264 items . set ( "mcp" , {
302265 key : "mcp" ,
303266 text : mcpText ,
304- colors : BLOCK_COLORS . mcp ,
267+ colors : { bg : "" , fg : "" } ,
305268 } ) ;
306269 }
307270
@@ -357,6 +320,7 @@ export function createFooterComponent(
357320 getThinkingLevel : ( ) => string ,
358321 requestRender : ( ) => void ,
359322 config : FooterConfig = DEFAULT_FOOTER_CONFIG ,
323+ theme ?: PlainFooterTheme ,
360324) {
361325 const unsubscribeBranchChange = footerData . onBranchChange ( ( ) => requestRender ( ) ) ;
362326 const unsubscribeSlotUpdates = subscribeFooterSlotUpdates ( ( ) => requestRender ( ) ) ;
@@ -384,13 +348,15 @@ export function createFooterComponent(
384348 variant ,
385349 "left" ,
386350 config ,
351+ theme ,
387352 ) ;
388353 const rightVariants = buildSideVariants (
389354 itemsByKey ,
390355 layout . right ,
391356 variant ,
392357 "right" ,
393358 config ,
359+ theme ,
394360 ) ;
395361
396362 for ( const left of leftVariants ) {
@@ -413,7 +379,7 @@ export function createFooterComponent(
413379 minimumInnerGap ,
414380 renderWidth - edgePaddingWidth - leftWidth - rightWidth ,
415381 ) ;
416- const padding = renderPadding ( paddingWidth , variant ) ;
382+ const padding = renderPadding ( paddingWidth , variant , theme ) ;
417383 if ( right . length > 0 ) {
418384 return [ truncateToWidth ( ` ${ left } ${ padding } ${ right } ` , renderWidth , "" ) ] ;
419385 }
0 commit comments