1+ import { styleText } from 'node:util' ;
12import { AutocompletePrompt , settings } from '@clack/core' ;
2- import color from 'picocolors' ;
33import {
44 type CommonOptions ,
55 S_BAR ,
@@ -98,7 +98,7 @@ export const autocomplete = <Value>(opts: AutocompleteOptions<Value>) => {
9898 const hasGuide = opts . withGuide ?? settings . withGuide ;
9999 // Title and message display
100100 const headings = hasGuide
101- ? [ `${ color . gray ( S_BAR ) } ` , `${ symbol ( this . state ) } ${ opts . message } ` ]
101+ ? [ `${ styleText ( 'gray' , S_BAR ) } ` , `${ symbol ( this . state ) } ${ opts . message } ` ]
102102 : [ `${ symbol ( this . state ) } ${ opts . message } ` ] ;
103103 const userInput = this . userInput ;
104104 const options = this . options ;
@@ -107,14 +107,16 @@ export const autocomplete = <Value>(opts: AutocompleteOptions<Value>) => {
107107 const opt = ( option : Option < Value > , state : 'inactive' | 'active' | 'disabled' ) => {
108108 const label = getLabel ( option ) ;
109109 const hint =
110- option . hint && option . value === this . focusedValue ? color . dim ( ` (${ option . hint } )` ) : '' ;
110+ option . hint && option . value === this . focusedValue
111+ ? styleText ( 'dim' , ` (${ option . hint } )` )
112+ : '' ;
111113 switch ( state ) {
112114 case 'active' :
113- return `${ color . green ( S_RADIO_ACTIVE ) } ${ label } ${ hint } ` ;
115+ return `${ styleText ( 'green' , S_RADIO_ACTIVE ) } ${ label } ${ hint } ` ;
114116 case 'inactive' :
115- return `${ color . dim ( S_RADIO_INACTIVE ) } ${ color . dim ( label ) } ` ;
117+ return `${ styleText ( 'dim' , S_RADIO_INACTIVE ) } ${ styleText ( 'dim' , label ) } ` ;
116118 case 'disabled' :
117- return `${ color . gray ( S_RADIO_INACTIVE ) } ${ color . strikethrough ( color . gray ( label ) ) } ` ;
119+ return `${ styleText ( 'gray' , S_RADIO_INACTIVE ) } ${ styleText ( [ 'strikethrough' , ' gray' ] , label ) } ` ;
118120 }
119121 } ;
120122
@@ -124,61 +126,64 @@ export const autocomplete = <Value>(opts: AutocompleteOptions<Value>) => {
124126 // Show selected value
125127 const selected = getSelectedOptions ( this . selectedValues , options ) ;
126128 const label =
127- selected . length > 0 ? ` ${ color . dim ( selected . map ( getLabel ) . join ( ', ' ) ) } ` : '' ;
128- const submitPrefix = hasGuide ? color . gray ( S_BAR ) : '' ;
129+ selected . length > 0 ? ` ${ styleText ( 'dim' , selected . map ( getLabel ) . join ( ', ' ) ) } ` : '' ;
130+ const submitPrefix = hasGuide ? styleText ( 'gray' , S_BAR ) : '' ;
129131 return `${ headings . join ( '\n' ) } \n${ submitPrefix } ${ label } ` ;
130132 }
131133
132134 case 'cancel' : {
133- const userInputText = userInput ? ` ${ color . strikethrough ( color . dim ( userInput ) ) } ` : '' ;
134- const cancelPrefix = hasGuide ? color . gray ( S_BAR ) : '' ;
135+ const userInputText = userInput
136+ ? ` ${ styleText ( [ 'strikethrough' , 'dim' ] , userInput ) } `
137+ : '' ;
138+ const cancelPrefix = hasGuide ? styleText ( 'gray' , S_BAR ) : '' ;
135139 return `${ headings . join ( '\n' ) } \n${ cancelPrefix } ${ userInputText } ` ;
136140 }
137141
138142 default : {
139- const barColor = this . state === 'error' ? color . yellow : color . cyan ;
140- const guidePrefix = hasGuide ? `${ barColor ( S_BAR ) } ` : '' ;
141- const guidePrefixEnd = hasGuide ? barColor ( S_BAR_END ) : '' ;
143+ const barStyle = this . state === 'error' ? ' yellow' : ' cyan' ;
144+ const guidePrefix = hasGuide ? `${ styleText ( barStyle , S_BAR ) } ` : '' ;
145+ const guidePrefixEnd = hasGuide ? styleText ( barStyle , S_BAR_END ) : '' ;
142146 // Display cursor position - show plain text in navigation mode
143147 let searchText = '' ;
144148 if ( this . isNavigating || showPlaceholder ) {
145149 const searchTextValue = showPlaceholder ? placeholder : userInput ;
146- searchText = searchTextValue !== '' ? ` ${ color . dim ( searchTextValue ) } ` : '' ;
150+ searchText = searchTextValue !== '' ? ` ${ styleText ( 'dim' , searchTextValue ) } ` : '' ;
147151 } else {
148152 searchText = ` ${ this . userInputWithCursor } ` ;
149153 }
150154
151155 // Show match count if filtered
152156 const matches =
153157 this . filteredOptions . length !== options . length
154- ? color . dim (
158+ ? styleText (
159+ 'dim' ,
155160 ` (${ this . filteredOptions . length } match${ this . filteredOptions . length === 1 ? '' : 'es' } )`
156161 )
157162 : '' ;
158163
159164 // No matches message
160165 const noResults =
161166 this . filteredOptions . length === 0 && userInput
162- ? [ `${ guidePrefix } ${ color . yellow ( 'No matches found' ) } ` ]
167+ ? [ `${ guidePrefix } ${ styleText ( 'yellow' , 'No matches found' ) } ` ]
163168 : [ ] ;
164169
165170 const validationError =
166- this . state === 'error' ? [ `${ guidePrefix } ${ color . yellow ( this . error ) } ` ] : [ ] ;
171+ this . state === 'error' ? [ `${ guidePrefix } ${ styleText ( 'yellow' , this . error ) } ` ] : [ ] ;
167172
168173 if ( hasGuide ) {
169174 headings . push ( `${ guidePrefix . trimEnd ( ) } ` ) ;
170175 }
171176 headings . push (
172- `${ guidePrefix } ${ color . dim ( 'Search:' ) } ${ searchText } ${ matches } ` ,
177+ `${ guidePrefix } ${ styleText ( 'dim' , 'Search:' ) } ${ searchText } ${ matches } ` ,
173178 ...noResults ,
174179 ...validationError
175180 ) ;
176181
177182 // Show instructions
178183 const instructions = [
179- `${ color . dim ( '↑/↓' ) } to select` ,
180- `${ color . dim ( 'Enter:' ) } confirm` ,
181- `${ color . dim ( 'Type:' ) } to search` ,
184+ `${ styleText ( 'dim' , '↑/↓' ) } to select` ,
185+ `${ styleText ( 'dim' , 'Enter:' ) } confirm` ,
186+ `${ styleText ( 'dim' , 'Type:' ) } to search` ,
182187 ] ;
183188
184189 const footers = [ `${ guidePrefix } ${ instructions . join ( ' • ' ) } ` , guidePrefixEnd ] ;
@@ -243,17 +248,19 @@ export const autocompleteMultiselect = <Value>(opts: AutocompleteMultiSelectOpti
243248 const label = option . label ?? String ( option . value ?? '' ) ;
244249 const hint =
245250 option . hint && focusedValue !== undefined && option . value === focusedValue
246- ? color . dim ( ` (${ option . hint } )` )
251+ ? styleText ( 'dim' , ` (${ option . hint } )` )
247252 : '' ;
248- const checkbox = isSelected ? color . green ( S_CHECKBOX_SELECTED ) : color . dim ( S_CHECKBOX_INACTIVE ) ;
253+ const checkbox = isSelected
254+ ? styleText ( 'green' , S_CHECKBOX_SELECTED )
255+ : styleText ( 'dim' , S_CHECKBOX_INACTIVE ) ;
249256
250257 if ( option . disabled ) {
251- return `${ color . gray ( S_CHECKBOX_INACTIVE ) } ${ color . strikethrough ( color . gray ( label ) ) } ` ;
258+ return `${ styleText ( 'gray' , S_CHECKBOX_INACTIVE ) } ${ styleText ( [ 'strikethrough' , ' gray' ] , label ) } ` ;
252259 }
253260 if ( active ) {
254261 return `${ checkbox } ${ label } ${ hint } ` ;
255262 }
256- return `${ checkbox } ${ color . dim ( label ) } ` ;
263+ return `${ checkbox } ${ styleText ( 'dim' , label ) } ` ;
257264 } ;
258265
259266 // Create text prompt which we'll use as foundation
@@ -277,7 +284,7 @@ export const autocompleteMultiselect = <Value>(opts: AutocompleteMultiSelectOpti
277284 output : opts . output ,
278285 render ( ) {
279286 // Title and symbol
280- const title = `${ color . gray ( S_BAR ) } \n${ symbol ( this . state ) } ${ opts . message } \n` ;
287+ const title = `${ styleText ( 'gray' , S_BAR ) } \n${ symbol ( this . state ) } ${ opts . message } \n` ;
281288
282289 // Selection counter
283290 const userInput = this . userInput ;
@@ -287,55 +294,58 @@ export const autocompleteMultiselect = <Value>(opts: AutocompleteMultiSelectOpti
287294 // Search input display
288295 const searchText =
289296 this . isNavigating || showPlaceholder
290- ? color . dim ( showPlaceholder ? placeholder : userInput ) // Just show plain text when in navigation mode
297+ ? styleText ( 'dim' , showPlaceholder ? placeholder : userInput ) // Just show plain text when in navigation mode
291298 : this . userInputWithCursor ;
292299
293300 const options = this . options ;
294301
295302 const matches =
296303 this . filteredOptions . length !== options . length
297- ? color . dim (
304+ ? styleText (
305+ 'dim' ,
298306 ` (${ this . filteredOptions . length } match${ this . filteredOptions . length === 1 ? '' : 'es' } )`
299307 )
300308 : '' ;
301309
302310 // Render prompt state
303311 switch ( this . state ) {
304312 case 'submit' : {
305- return `${ title } ${ color . gray ( S_BAR ) } ${ color . dim ( `${ this . selectedValues . length } items selected` ) } ` ;
313+ return `${ title } ${ styleText ( 'gray' , S_BAR ) } ${ styleText ( 'dim' , `${ this . selectedValues . length } items selected` ) } ` ;
306314 }
307315 case 'cancel' : {
308- return `${ title } ${ color . gray ( S_BAR ) } ${ color . strikethrough ( color . dim ( userInput ) ) } ` ;
316+ return `${ title } ${ styleText ( 'gray' , S_BAR ) } ${ styleText ( [ 'strikethrough' , ' dim' ] , userInput ) } ` ;
309317 }
310318 default : {
311- const barColor = this . state === 'error' ? color . yellow : color . cyan ;
319+ const barStyle = this . state === 'error' ? ' yellow' : ' cyan' ;
312320 // Instructions
313321 const instructions = [
314- `${ color . dim ( '↑/↓' ) } to navigate` ,
315- `${ color . dim ( this . isNavigating ? 'Space/Tab:' : 'Tab:' ) } select` ,
316- `${ color . dim ( 'Enter:' ) } confirm` ,
317- `${ color . dim ( 'Type:' ) } to search` ,
322+ `${ styleText ( 'dim' , '↑/↓' ) } to navigate` ,
323+ `${ styleText ( 'dim' , this . isNavigating ? 'Space/Tab:' : 'Tab:' ) } select` ,
324+ `${ styleText ( 'dim' , 'Enter:' ) } confirm` ,
325+ `${ styleText ( 'dim' , 'Type:' ) } to search` ,
318326 ] ;
319327
320328 // No results message
321329 const noResults =
322330 this . filteredOptions . length === 0 && userInput
323- ? [ `${ barColor ( S_BAR ) } ${ color . yellow ( 'No matches found' ) } ` ]
331+ ? [ `${ styleText ( barStyle , S_BAR ) } ${ styleText ( 'yellow' , 'No matches found' ) } ` ]
324332 : [ ] ;
325333
326334 const errorMessage =
327- this . state === 'error' ? [ `${ barColor ( S_BAR ) } ${ color . yellow ( this . error ) } ` ] : [ ] ;
335+ this . state === 'error'
336+ ? [ `${ styleText ( barStyle , S_BAR ) } ${ styleText ( 'yellow' , this . error ) } ` ]
337+ : [ ] ;
328338
329339 // Calculate header and footer line counts for rowPadding
330340 const headerLines = [
331- ...`${ title } ${ barColor ( S_BAR ) } ` . split ( '\n' ) ,
332- `${ barColor ( S_BAR ) } ${ color . dim ( 'Search:' ) } ${ searchText } ${ matches } ` ,
341+ ...`${ title } ${ styleText ( barStyle , S_BAR ) } ` . split ( '\n' ) ,
342+ `${ styleText ( barStyle , S_BAR ) } ${ styleText ( 'dim' , 'Search:' ) } ${ searchText } ${ matches } ` ,
333343 ...noResults ,
334344 ...errorMessage ,
335345 ] ;
336346 const footerLines = [
337- `${ barColor ( S_BAR ) } ${ instructions . join ( ' • ' ) } ` ,
338- ` ${ barColor ( S_BAR_END ) } ` ,
347+ `${ styleText ( barStyle , S_BAR ) } ${ instructions . join ( ' • ' ) } ` ,
348+ styleText ( barStyle , S_BAR_END ) ,
339349 ] ;
340350
341351 // Get limited options for display
@@ -352,7 +362,7 @@ export const autocompleteMultiselect = <Value>(opts: AutocompleteMultiSelectOpti
352362 // Build the prompt display
353363 return [
354364 ...headerLines ,
355- ...displayOptions . map ( ( option ) => `${ barColor ( S_BAR ) } ${ option } ` ) ,
365+ ...displayOptions . map ( ( option ) => `${ styleText ( barStyle , S_BAR ) } ${ option } ` ) ,
356366 ...footerLines ,
357367 ] . join ( '\n' ) ;
358368 }
0 commit comments