use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use crate::terminal::insights;
use colored::*;
use indicatif::ProgressStyle;
use tracing::Span;
use tracing_indicatif::{IndicatifLayer, span_ext::IndicatifSpanExt};
use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt};
use crate::terminal::{colors, logging};
const CYCLE_MS: u128 = 5000;
const STATUS_MS: u128 = 2000;
pub fn init_logging(verbosity: u8) {
#[cfg(target_os = "windows")]
let _ = colored::control::set_virtual_terminal(true);
let indicatif_layer = IndicatifLayer::new().with_progress_style(
ProgressStyle::with_template("{spinner:.blue} {msg}")
.unwrap()
.tick_strings(&[
"▁▁▁▁▁",
"▁▂▂▂▁",
"▁▄▂▄▁",
"▂▄▆▄▂",
"▄▆█▆▄",
"▂▄▆▄▂",
"▁▄▂▄▁",
"▁▂▂▂▁",
]),
);
let filter_layer = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("info,zond=debug,mio=error"));
let formatting_layer = tracing_subscriber::fmt::layer()
.event_format(logging::ZondFormatter {
max_verbosity: verbosity,
})
.with_writer(indicatif_layer.get_stderr_writer());
tracing_subscriber::registry()
.with(filter_layer)
.with(formatting_layer)
.with(indicatif_layer)
.init();
}
async fn run_spinner_loop<F>(span: Span, running: Arc<AtomicBool>, status_fn: Option<F>)
where
F: Fn() -> ColoredString + Send + Sync + 'static,
{
let mut interval = tokio::time::interval(Duration::from_millis(100));
let start_time = tokio::time::Instant::now();
let mut last_text = String::new();
let active_insights = insights::get_shuffled_insights();
while running.load(Ordering::Relaxed) {
interval.tick().await;
let elapsed_ms = start_time.elapsed().as_millis();
let cycle_time = elapsed_ms % CYCLE_MS;
let tip_index = (elapsed_ms / CYCLE_MS) as usize % active_insights.len();
let show_status = status_fn.is_some() && cycle_time < STATUS_MS;
let colored_msg: ColoredString = if show_status {
(status_fn.as_ref().unwrap())()
} else {
active_insights[tip_index]
.italic()
.color(colors::TEXT_DEFAULT)
};
let current_text = colored_msg.to_string();
if current_text != last_text {
span.pb_set_message(¤t_text);
last_text = current_text;
}
}
}
pub struct SpinnerGuard {
running: Arc<AtomicBool>,
handle: tokio::task::JoinHandle<()>,
}
impl SpinnerGuard {
pub fn with_status<F>(span: tracing::Span, status_fn: F) -> Self
where
F: Fn() -> ColoredString + Send + Sync + 'static,
{
Self::start(span, Some(status_fn))
}
fn start<F>(span: tracing::Span, status_fn: Option<F>) -> Self
where
F: Fn() -> ColoredString + Send + Sync + 'static,
{
let running = Arc::new(AtomicBool::new(true));
let run_clone = running.clone();
let handle = tokio::spawn(async move {
run_spinner_loop(span, run_clone, status_fn).await;
});
Self { running, handle }
}
}
impl Drop for SpinnerGuard {
fn drop(&mut self) {
self.running.store(false, Ordering::Relaxed);
self.handle.abort();
}
}