summaryrefslogtreecommitdiffstats
path: root/src/utils
diff options
context:
space:
mode:
Diffstat (limited to 'src/utils')
-rw-r--r--src/utils/mod.rs2
-rw-r--r--src/utils/spinner.rs119
2 files changed, 67 insertions, 54 deletions
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index 5c34a54..f8bfcc6 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -12,7 +12,7 @@ pub use self::command::*;
pub use self::crypto::*;
pub use self::prompt_input::*;
pub use self::render_prompt::render_prompt;
-pub use self::spinner::run_spinner;
+pub use self::spinner::{create_spinner, Spinner};
use fancy_regex::Regex;
use is_terminal::IsTerminal;
diff --git a/src/utils/spinner.rs b/src/utils/spinner.rs
index 8df4f57..a04a18f 100644
--- a/src/utils/spinner.rs
+++ b/src/utils/spinner.rs
@@ -5,46 +5,34 @@ use std::{
io::{stdout, Write},
time::Duration,
};
-use tokio::{
- sync::{mpsc, oneshot},
- time::interval,
-};
+use tokio::{sync::mpsc, time::interval};
-pub struct Spinner {
+pub struct SpinnerInner {
index: usize,
message: String,
- stopped: bool,
+ is_not_terminal: bool,
}
-impl Spinner {
+impl SpinnerInner {
const DATA: [&'static str; 10] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
- pub fn new(message: &str) -> Self {
- Spinner {
+ fn new(message: &str) -> Self {
+ SpinnerInner {
index: 0,
message: message.to_string(),
- stopped: false,
+ is_not_terminal: !stdout().is_terminal(),
}
}
- pub fn set_message(&mut self, message: &str) {
- self.message = format!(" {message}");
- }
-
- pub fn step(&mut self) -> Result<()> {
- if self.stopped {
+ fn step(&mut self) -> Result<()> {
+ if self.is_not_terminal || self.message.is_empty() {
return Ok(());
}
let mut writer = stdout();
let frame = Self::DATA[self.index % Self::DATA.len()];
let dots = ".".repeat((self.index / 5) % 4);
let line = format!("{frame}{}{:<3}", self.message, dots);
- queue!(
- writer,
- cursor::MoveToColumn(0),
- terminal::Clear(terminal::ClearType::FromCursorDown),
- style::Print(line),
- )?;
+ queue!(writer, cursor::MoveToColumn(0), style::Print(line),)?;
if self.index == 0 {
queue!(writer, cursor::Hide)?;
}
@@ -53,12 +41,20 @@ impl Spinner {
Ok(())
}
- pub fn stop(&mut self) -> Result<()> {
- if self.stopped {
+ fn set_message(&mut self, message: String) -> Result<()> {
+ self.clear_message()?;
+ if !message.is_empty() {
+ self.message = format!(" {message}");
+ }
+ Ok(())
+ }
+
+ fn clear_message(&mut self) -> Result<()> {
+ if self.is_not_terminal || self.message.is_empty() {
return Ok(());
}
+ self.message.clear();
let mut writer = stdout();
- self.stopped = true;
queue!(
writer,
cursor::MoveToColumn(0),
@@ -70,43 +66,60 @@ impl Spinner {
}
}
-pub async fn run_spinner(message: &str) -> (oneshot::Sender<()>, mpsc::UnboundedSender<String>) {
+#[derive(Clone)]
+pub struct Spinner(mpsc::UnboundedSender<SpinnerEvent>);
+
+impl Drop for Spinner {
+ fn drop(&mut self) {
+ self.stop();
+ }
+}
+
+impl Spinner {
+ pub fn set_message(&self, message: String) -> Result<()> {
+ self.0.send(SpinnerEvent::SetMessage(message))?;
+ Ok(())
+ }
+
+ pub fn stop(&self) {
+ let _ = self.0.send(SpinnerEvent::Stop);
+ }
+}
+
+enum SpinnerEvent {
+ SetMessage(String),
+ Stop,
+}
+
+pub async fn create_spinner(message: &str) -> Spinner {
let message = format!(" {message}");
- let (stop_tx, stop_rx) = oneshot::channel();
- let (message_tx, message_rx) = mpsc::unbounded_channel();
- tokio::spawn(run_spinner_inner(message, stop_rx, message_rx));
- (stop_tx, message_tx)
+ let (tx, rx) = mpsc::unbounded_channel();
+ tokio::spawn(run_spinner(message, rx));
+ Spinner(tx)
}
-async fn run_spinner_inner(
- message: String,
- stop_rx: oneshot::Receiver<()>,
- mut message_rx: mpsc::UnboundedReceiver<String>,
-) -> Result<()> {
- let is_stdout_terminal = stdout().is_terminal();
- let mut spinner = Spinner::new(&message);
+async fn run_spinner(message: String, mut rx: mpsc::UnboundedReceiver<SpinnerEvent>) -> Result<()> {
+ let mut spinner = SpinnerInner::new(&message);
let mut interval = interval(Duration::from_millis(50));
- tokio::select! {
- _ = async {
- loop {
- tokio::select! {
- _ = interval.tick() => {
- if is_stdout_terminal {
- let _ = spinner.step();
+ loop {
+ tokio::select! {
+ _ = interval.tick() => {
+ let _ = spinner.step();
+ }
+ evt = rx.recv() => {
+ if let Some(evt) = evt {
+ match evt {
+ SpinnerEvent::SetMessage(message) => {
+ spinner.set_message(message)?;
}
- }
- message = message_rx.recv() => {
- if let Some(message) = message {
- spinner.set_message(&message);
+ SpinnerEvent::Stop => {
+ spinner.clear_message()?;
+ break;
}
}
+
}
}
- } => {}
- _ = stop_rx => {
- if is_stdout_terminal {
- spinner.stop()?;
- }
}
}
Ok(())