Skip to content

Commit 4853472

Browse files
feat: add a spinner while waiting for results
1 parent 8581357 commit 4853472

3 files changed

Lines changed: 68 additions & 33 deletions

File tree

src/local_logger/mod.rs

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ use crate::logger::{GroupEvent, JsonEvent, get_group_event, get_json_event};
1919
pub 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

2427
lazy_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+
265291
pub fn clean_logger() {
266292
let mut spinner = SPINNER.lock().unwrap();
267293
if let Some(spinner) = spinner.as_mut() {

src/local_logger/rolling_buffer/mod.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ use std::sync::Mutex;
33
use std::sync::atomic::{AtomicBool, Ordering};
44
use std::time::{Duration, Instant};
55

6-
use super::{CODSPEED_U8_COLOR_CODE, IS_TTY, SPINNER, format_checkmark};
6+
use super::{
7+
CODSPEED_U8_COLOR_CODE, IS_TTY, SPINNER, SPINNER_TICKS, TICK_INTERVAL_MS, format_checkmark,
8+
};
79
use console::{Term, style};
810
use lazy_static::lazy_static;
911

1012
const INDENT: &str = " ";
11-
const SPINNER_TICKS: &[&str] = &[" ", ". ", "..", " ."];
12-
const TICK_INTERVAL_MS: u128 = 300;
1313

1414
lazy_static! {
1515
/// Global shared rolling buffer, set by `activate_rolling_buffer` and
@@ -19,6 +19,11 @@ lazy_static! {
1919
}
2020

2121
/// Stop signal for the tick thread.
22+
///
23+
/// The rolling buffer manages its own background tick thread rather than using
24+
/// `ProgressBar` because it renders a multi-line frame (title + bordered log box)
25+
/// via direct terminal cursor manipulation. `ProgressBar` only manages a single
26+
/// line and would conflict with the rolling buffer's cursor movements.
2227
static TICK_STOP: AtomicBool = AtomicBool::new(false);
2328

2429
pub struct RollingBuffer {
@@ -100,7 +105,7 @@ impl RollingBuffer {
100105

101106
fn spinner_tick(&self) -> &'static str {
102107
let elapsed_ms = self.start.elapsed().as_millis();
103-
let idx = (elapsed_ms / TICK_INTERVAL_MS) as usize % SPINNER_TICKS.len();
108+
let idx = (elapsed_ms / TICK_INTERVAL_MS as u128) as usize % SPINNER_TICKS.len();
104109
SPINNER_TICKS[idx]
105110
}
106111

@@ -297,7 +302,7 @@ pub fn activate_rolling_buffer(title: &str) {
297302
TICK_STOP.store(false, Ordering::Relaxed);
298303
std::thread::spawn(|| {
299304
while !TICK_STOP.load(Ordering::Relaxed) {
300-
std::thread::sleep(Duration::from_millis(TICK_INTERVAL_MS as u64));
305+
std::thread::sleep(Duration::from_millis(TICK_INTERVAL_MS));
301306
if TICK_STOP.load(Ordering::Relaxed) {
302307
break;
303308
}

src/upload/poll_results.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use console::style;
22

33
use crate::api_client::CodSpeedAPIClient;
44
use crate::cli::run::helpers::benchmark_display::{build_benchmark_table, build_detailed_summary};
5+
use crate::local_logger::{start_spinner, stop_spinner};
56
use crate::prelude::*;
67

78
use super::{UploadResult, poll_run_report};
@@ -42,7 +43,10 @@ pub async fn poll_results(
4243
upload_result: &UploadResult,
4344
options: &PollResultsOptions,
4445
) -> Result<()> {
45-
let response = poll_run_report(api_client, upload_result).await?;
46+
start_spinner("Waiting for results");
47+
let response = poll_run_report(api_client, upload_result).await;
48+
stop_spinner();
49+
let response = response?;
4650

4751
if options.show_impact {
4852
let report = response.run.head_reports.into_iter().next();

0 commit comments

Comments
 (0)