@@ -19,7 +19,10 @@ use crate::logger::{GroupEvent, JsonEvent, get_group_event, get_json_event};
1919pub const CODSPEED_U8_COLOR_CODE : u8 = 208 ; // #FF8700
2020
2121/// Spinner tick characters - smooth animation for a polished feel
22- const SPINNER_TICKS : & [ & str ] = & [ " " , ". " , ".." , " ." ] ;
22+ pub ( crate ) const SPINNER_TICKS : & [ & str ] = & [ " " , ". " , ".." , " ." ] ;
23+
24+ /// Interval between spinner animation ticks (milliseconds)
25+ pub ( crate ) const TICK_INTERVAL_MS : u64 = 300 ;
2326
2427lazy_static ! {
2528 pub static ref SPINNER : Arc <Mutex <Option <ProgressBar >>> = Arc :: new( Mutex :: new( None ) ) ;
@@ -88,32 +91,7 @@ impl Log for LocalLogger {
8891 * current = Some ( name. clone ( ) ) ;
8992 }
9093
91- if * IS_TTY {
92- let spinner = ProgressBar :: new_spinner ( ) ;
93- let tick_strings: Vec < String > = SPINNER_TICKS
94- . iter ( )
95- . map ( |s| {
96- format ! ( "{}" , style( s) . color256( CODSPEED_U8_COLOR_CODE ) . dim( ) )
97- } )
98- . collect ( ) ;
99- let tick_strs: Vec < & str > =
100- tick_strings. iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
101-
102- spinner. set_style (
103- ProgressStyle :: with_template (
104- & format ! (
105- " {{spinner}} {{wide_msg:.{CODSPEED_U8_COLOR_CODE}}} {{elapsed:.dim}}"
106- ) ,
107- )
108- . unwrap ( )
109- . tick_strings ( & tick_strs) ,
110- ) ;
111- spinner. set_message ( format ! ( "{name}..." ) ) ;
112- spinner. enable_steady_tick ( Duration :: from_millis ( 300 ) ) ;
113- SPINNER . lock ( ) . unwrap ( ) . replace ( spinner) ;
114- } else {
115- eprintln ! ( "{name}..." ) ;
116- }
94+ install_spinner ( & name) ;
11795 }
11896 }
11997 GroupEvent :: End => {
@@ -262,6 +240,54 @@ pub fn init_local_logger() -> Result<()> {
262240 Ok ( ( ) )
263241}
264242
243+ /// Create a styled spinner progress bar with CodSpeed branding.
244+ fn create_spinner ( message : & str ) -> ProgressBar {
245+ let spinner = ProgressBar :: new_spinner ( ) ;
246+ let tick_strings: Vec < String > = SPINNER_TICKS
247+ . iter ( )
248+ . map ( |s| format ! ( "{}" , style( s) . color256( CODSPEED_U8_COLOR_CODE ) . dim( ) ) )
249+ . collect ( ) ;
250+ let tick_strs: Vec < & str > = tick_strings. iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
251+
252+ spinner. set_style (
253+ ProgressStyle :: with_template ( & format ! (
254+ " {{spinner}} {{wide_msg:.{CODSPEED_U8_COLOR_CODE}}} {{elapsed:.dim}}"
255+ ) )
256+ . unwrap ( )
257+ . tick_strings ( & tick_strs) ,
258+ ) ;
259+ spinner. set_message ( { message } . to_string ( ) ) ;
260+ spinner. enable_steady_tick ( Duration :: from_millis ( TICK_INTERVAL_MS ) ) ;
261+ spinner
262+ }
263+
264+ /// Install a spinner into the global slot so log records suspend it.
265+ fn install_spinner ( message : & str ) {
266+ if * IS_TTY {
267+ let spinner = create_spinner ( message) ;
268+ SPINNER . lock ( ) . unwrap ( ) . replace ( spinner) ;
269+ } else {
270+ eprintln ! ( "{message}..." ) ;
271+ }
272+ }
273+
274+ /// Start a standalone spinner with a message (no group header or checkmark).
275+ ///
276+ /// The spinner animates on TTY outputs. On non-TTY, prints the message once.
277+ /// Call [`stop_spinner`] to clear it when done.
278+ pub fn start_spinner ( message : & str ) {
279+ install_spinner ( message) ;
280+ }
281+
282+ /// Stop and clear the current standalone spinner.
283+ pub fn stop_spinner ( ) {
284+ if let Ok ( mut spinner) = SPINNER . lock ( ) {
285+ if let Some ( pb) = spinner. take ( ) {
286+ pb. finish_and_clear ( ) ;
287+ }
288+ }
289+ }
290+
265291pub fn clean_logger ( ) {
266292 let mut spinner = SPINNER . lock ( ) . unwrap ( ) ;
267293 if let Some ( spinner) = spinner. as_mut ( ) {
0 commit comments