summaryrefslogtreecommitdiffstats
path: root/src/term.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/term.rs')
-rw-r--r--src/term.rs236
1 files changed, 116 insertions, 120 deletions
diff --git a/src/term.rs b/src/term.rs
index 9ce9e53..038c92b 100644
--- a/src/term.rs
+++ b/src/term.rs
@@ -3,10 +3,8 @@ 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, Stderr, Write};
-use termimad::{mad_write_inline, MadSkin};
+use std::io::{stderr, Write};
+use termimad::{CompoundStyle, MadSkin};
use tokio::sync::{
oneshot,
oneshot::{error::TryRecvError, Sender},
@@ -14,7 +12,7 @@ use tokio::sync::{
use tokio::task::JoinHandle;
use tokio::time;
-use crate::error::{Error, Result};
+use crate::error::Result;
const LOADING_SPINNER_DELAY: u64 = 40;
const LOADING_SPINNER_DOTS: [&str; 56] = [
@@ -24,135 +22,133 @@ 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,
- _ => (),
- }
+pub struct Term {
+ skin: MadSkin,
+}
+
+impl Default for Term {
+ fn default() -> Self {
+ Term::new()
}
- 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();
+impl Term {
+ pub fn new() -> Self {
+ let mut skin = MadSkin::default();
+ skin.inline_code = CompoundStyle::with_fg(Color::Cyan);
+ skin.code_block.compound_style = CompoundStyle::with_fg(Color::Cyan);
+ Term { skin }
+ }
- let result = future.await;
+ /// Print text to stdout
+ pub fn print(&self, text: &str) {
+ self.skin.print_text(text)
+ }
- // Stop spinner
- tx.send(()).ok();
- spinner_handle.await??;
+ /// Print text with error styling to stderr
+ /// Needs mut to temporarily modify styling (e.g. red fg)
+ pub fn print_error(&mut self, text: &str) -> Result<()> {
+ self.print_with_style(Color::Red, "✖ ", text)
+ }
- Ok(result)
-}
+ /// Print text with notice styling to stderr
+ /// Needs mut to temporarily modify styling (e.g. yellow fg)
+ pub fn print_notice(&mut self, text: &str) -> Result<()> {
+ self.print_with_style(Color::Yellow, "➜ ", text)
+ }
-/// 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();
+ fn print_with_style(&mut self, fg: Color, prefix: &str, text: &str) -> Result<()> {
+ let mut styled_text = String::from(prefix);
+ styled_text.push_str(text);
+ // Set fg
+ self.skin.paragraph.set_fg(fg);
+ self.skin
+ .write_text_on(&mut std::io::stderr(), &styled_text)?;
+ // Unset fg
+ self.skin
+ .paragraph
+ .compound_style
+ .object_style
+ .foreground_color = None;
+ Ok(())
+ }
+
+ /// 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()?;
- execute!(
- stderr(),
- cursor::SavePosition,
- cursor::Hide,
- terminal::Clear(ClearType::CurrentLine),
- )?;
- let mut interval = time::interval(time::Duration::from_millis(LOADING_SPINNER_DELAY));
- while let Err(TryRecvError::Empty) = rx.try_recv() {
- execute!(
- stderr(),
- cursor::MoveToColumn(0),
- terminal::Clear(ClearType::CurrentLine),
- Print(dots.next().unwrap())
- )?;
- interval.tick().await;
+ loop {
+ match read()? {
+ Event::Key(KeyEvent {
+ code: KeyCode::Char(ch),
+ ..
+ }) if ch == c => {
+ pressed = true;
+ break;
+ }
+ Event::Key(_) => break,
+ _ => (),
+ }
}
- execute!(
- stderr(),
- terminal::Clear(ClearType::CurrentLine),
- cursor::RestorePosition,
- cursor::Show,
- )?;
terminal::disable_raw_mode()?;
- Ok(())
- });
- (tx, spinner_handle)
-}
+ Ok(pressed)
+ }
-/// 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>,
-{
- let err = &mut std::io::stderr();
- let p = skin.paragraph.clone();
- skin.paragraph.set_fg(Color::Red);
- mad_write_inline!(err, skin, "✖ ")?;
- let r: R = f(&skin, err)?;
- skin.paragraph = p;
- Ok::<R, Error>(r)
-}
+ /// 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) = Self::spinner();
+
+ let result = future.await;
-/// This makes code much more convenient, but would require each style to own
-/// its own skin clone. Not sure if it is worth it.
-pub fn mk_print_error(skin: &MadSkin) -> impl FnMut(&str) -> Result<()> + 'static {
- let mut skin = skin.clone();
- move |text: &str| {
- with_error_style(&mut skin, |err_skin, stderr| {
- err_skin.write_text_on(stderr, text)
- })
+ // Stop spinner
+ tx.send(()).ok();
+ spinner_handle.await??;
+
+ Ok(result)
}
-}
-#[macro_export]
-macro_rules! print_error {
- ($skin: expr, $md: literal $(, $value: expr )* $(,)? ) => {{
- use lazy_static::lazy_static;
- use minimad::mad_inline;
- use crate::error::Error;
- let err = &mut std::io::stderr();
- let p = $skin.paragraph.clone();
- $skin.paragraph.set_fg(crossterm::style::Color::Red);
- termimad::mad_write_inline!(err, $skin, "✖ ").map_err(Error::from)?;
- $skin.write_composite(err, mad_inline!($md $(, $value)*)).map_err(Error::from)?;
- $skin.paragraph = p;
- Ok::<(), Error>(())
- }};
+ /// 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`.
+ 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));
+ while let Err(TryRecvError::Empty) = rx.try_recv() {
+ execute!(
+ stderr(),
+ cursor::MoveToColumn(0),
+ terminal::Clear(ClearType::CurrentLine),
+ Print(dots.next().unwrap())
+ )?;
+ interval.tick().await;
+ }
+ execute!(
+ stderr(),
+ terminal::Clear(ClearType::CurrentLine),
+ cursor::RestorePosition,
+ cursor::Show,
+ )?;
+ terminal::disable_raw_mode()?;
+ Ok(())
+ });
+ (tx, spinner_handle)
+ }
}
-#[macro_export]
-macro_rules! print_notice {
- ($skin: expr, $md: literal $(, $value: expr )* $(,)? ) => {{
- use lazy_static::lazy_static;
- use minimad::mad_inline;
- use crate::error::Error;
- let err = &mut std::io::stderr();
- let p = $skin.paragraph.clone();
- $skin.paragraph.set_fg(crossterm::style::Color::Yellow);
- termimad::mad_write_inline!(err, $skin, "➜ ").map_err(Error::from)?;
- $skin.write_composite(err, mad_inline!($md $(, $value)*)).map_err(Error::from)?;
- $skin.paragraph = p;
- Ok::<(), Error>(())
- }};
+pub fn print_error(text: &str) -> Result<()> {
+ Term::new().print_error(text)
}