summaryrefslogtreecommitdiffstats
path: root/src/term.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/term.rs')
-rw-r--r--src/term.rs107
1 files changed, 104 insertions, 3 deletions
diff --git a/src/term.rs b/src/term.rs
index 8297a6a..9ad880b 100644
--- a/src/term.rs
+++ b/src/term.rs
@@ -1,10 +1,111 @@
-use crate::error::{Error, Result};
-use crossterm::style::Color;
+use crossterm::event::{read, Event, KeyCode, KeyEvent};
+use crossterm::style::{Color, Print};
+use crossterm::terminal::ClearType;
+use crossterm::{cursor, execute, terminal};
+use futures::Future;
use lazy_static::lazy_static;
use minimad::mad_inline;
-use std::io::Stderr;
+use std::io::{stderr, Stderr, Write};
use termimad::{mad_write_inline, MadSkin};
+use tokio::sync::{
+ oneshot,
+ oneshot::{error::TryRecvError, Sender},
+};
+use tokio::task::JoinHandle;
+use tokio::time;
+
+use crate::error::{Error, Result};
+
+const LOADING_SPINNER_DELAY: u64 = 40;
+const LOADING_SPINNER_DOTS: [&str; 56] = [
+ "⢀⠀", "⡀⠀", "⠄⠀", "⢂⠀", "⡂⠀", "⠅⠀", "⢃⠀", "⡃⠀", "⠍⠀", "⢋⠀", "⡋⠀", "⠍⠁", "⢋⠁", "⡋⠁", "⠍⠉", "⠋⠉",
+ "⠋⠉", "⠉⠙", "⠉⠙", "⠉⠩", "⠈⢙", "⠈⡙", "⢈⠩", "⡀⢙", "⠄⡙", "⢂⠩", "⡂⢘", "⠅⡘", "⢃⠨", "⡃⢐", "⠍⡐", "⢋⠠",
+ "⡋⢀", "⠍⡁", "⢋⠁", "⡋⠁", "⠍⠉", "⠋⠉", "⠋⠉", "⠉⠙", "⠉⠙", "⠉⠩", "⠈⢙", "⠈⡙", "⠈⠩", "⠀⢙", "⠀⡙", "⠀⠩",
+ "⠀⢘", "⠀⡘", "⠀⠨", "⠀⢐", "⠀⡐", "⠀⠠", "⠀⢀", "⠀⡀",
+];
+
+/// Blocks and waits for the user to press any key. Returns whether or not that key is the
+/// character key `c`.
+pub fn wait_for_char(c: char) -> Result<bool> {
+ let mut pressed = false;
+ terminal::enable_raw_mode()?;
+ loop {
+ match read()? {
+ Event::Key(KeyEvent {
+ code: KeyCode::Char(ch),
+ ..
+ }) if ch == c => {
+ pressed = true;
+ break;
+ }
+ Event::Key(_) => break,
+ _ => (),
+ }
+ }
+ terminal::disable_raw_mode()?;
+ Ok(pressed)
+}
+
+/// As it sounds, takes a future and shows a CLI spinner until it's output is ready
+pub async fn wrap_spinner<F>(future: F) -> Result<F::Output>
+where
+ F: Future,
+{
+ // Start spinner
+ let (tx, spinner_handle) = spinner();
+
+ let result = future.await;
+
+ // Stop spinner
+ tx.send(()).ok();
+ spinner_handle.await??;
+
+ Ok(result)
+}
+
+/// Start a CLI spinner on the current cursor line. To stop it, call `send` on the `Sender`. To
+/// wait until it's done cleaning up it's current action (which is very important), await it's
+/// `JoinHandle`.
+pub fn spinner() -> (Sender<()>, JoinHandle<Result<()>>) {
+ let (tx, mut rx) = oneshot::channel();
+ let spinner_handle = tokio::spawn(async move {
+ let mut dots = LOADING_SPINNER_DOTS.iter().cycle();
+ terminal::enable_raw_mode()?;
+ execute!(
+ stderr(),
+ cursor::SavePosition,
+ cursor::Hide,
+ terminal::Clear(ClearType::CurrentLine),
+ )?;
+ let mut interval = time::interval(time::Duration::from_millis(LOADING_SPINNER_DELAY));
+ loop {
+ match rx.try_recv() {
+ Err(TryRecvError::Empty) => {
+ execute!(
+ stderr(),
+ cursor::MoveToColumn(0),
+ terminal::Clear(ClearType::CurrentLine),
+ Print(dots.next().unwrap())
+ )?;
+ interval.tick().await;
+ }
+ _ => break,
+ }
+ }
+ execute!(
+ stderr(),
+ terminal::Clear(ClearType::CurrentLine),
+ cursor::RestorePosition,
+ cursor::Show,
+ )?;
+ terminal::disable_raw_mode()?;
+ Ok(())
+ });
+ (tx, spinner_handle)
+}
+/// Temporarily modifies a skin with error styles (e.g. red fg) for use with the given closure.
+/// Once the closure finishes, the skin is returned to original state.
pub fn with_error_style<R, F>(skin: &mut MadSkin, f: F) -> Result<R>
where
F: FnOnce(&MadSkin, &mut Stderr) -> Result<R, termimad::Error>,