From 7303687402b093fcda82abae5211918978cc8cff Mon Sep 17 00:00:00 2001 From: rami3l Date: Mon, 3 Jul 2023 09:52:14 +0800 Subject: [PATCH 1/2] api: add `Error::JoinError` --- src/error.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/error.rs b/src/error.rs index 9cecb75acf..5d0b6fe5de 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,6 +3,7 @@ use std::{ fmt::{self, Debug}, process::{ExitCode, Termination}, + string::FromUtf8Error, }; use thiserror::Error; @@ -29,7 +30,7 @@ pub enum Error { ConfigError(#[from] figment::Error), /// A [`Cmd`](crate::exec::Cmd) failed to finish. - #[error("Failed to get exit code of subprocess: {0}")] + #[error("Failed to finish subprocess execution: {0}")] CmdJoinError(JoinError), /// A [`Cmd`](crate::exec::Cmd) failed to spawn. @@ -57,7 +58,7 @@ pub enum Error { /// Error while converting a [`Vec`] to a [`String`]. #[error(transparent)] - FromUtf8Error(#[from] std::string::FromUtf8Error), + FromUtf8Error(#[from] FromUtf8Error), /// Error while rendering a dialog. #[error(transparent)] @@ -67,12 +68,16 @@ pub enum Error { #[error(transparent)] IoError(#[from] io::Error), + /// A generic [`JoinError`]. + #[error(transparent)] + JoinError(#[from] JoinError), + /// A [`Pm`](crate::pm::Pm) operation is not implemented. #[error("Operation `{op}` is unimplemented for `{pm}`")] #[allow(missing_docs)] OperationUnimplementedError { op: String, pm: String }, - /// Miscellaneous other error. + /// An error from a non-specified category. #[error("{0}")] OtherError(String), } From 57e1f1fab3ed074ad696b4b43e50b71ff5cb7625 Mon Sep 17 00:00:00 2001 From: rami3l Date: Tue, 4 Jul 2023 10:16:32 +0800 Subject: [PATCH 2/2] feat(exec): impl async prompts --- src/exec.rs | 59 +++++++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/src/exec.rs b/src/exec.rs index 36b8a002f9..1f88e7ac4b 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -1,20 +1,19 @@ //! APIs for spawning subprocesses and handling their results. -use std::{ - process::Stdio, - sync::atomic::{AtomicBool, Ordering}, -}; +use std::process::Stdio; use bytes::{Bytes, BytesMut}; use dialoguer::FuzzySelect; use futures::prelude::*; use indoc::indoc; use itertools::{chain, Itertools}; +use once_cell::sync::Lazy; use regex::{RegexSet, RegexSetBuilder}; use tap::prelude::*; use tokio::{ io::{self, AsyncRead, AsyncWrite}, process::Command as Exec, + sync::Mutex, task::JoinHandle, }; #[allow(clippy::wildcard_imports)] @@ -318,32 +317,38 @@ impl Cmd { #[doc = docs_errors_exec!()] async fn exec_prompt(self, mute: bool) -> Result { /// If the user has skipped all the prompts with `yes`. - static ALL: AtomicBool = AtomicBool::new(false); + /// + /// The [`Mutex`] is required to mutually exclude the prompt. + static ALL: Lazy> = Lazy::new(|| Mutex::new(false)); // The answer obtained from the prompt. - // The only Atomic* we're dealing with is `ALL`, so `Ordering::Relaxed` is fine. - // See: - let proceed = ALL.load(Ordering::Relaxed) || { - println_quoted(&*prompt::PENDING, &self); - let answer = tokio::task::block_in_place(move || { - prompt( - "Proceed", - "with the previous command?", - &["Yes", "All", "No"], - ) - })?; - match answer { - // The default answer is `Yes`. - 0 => true, - // You can also say `All` to answer `Yes` to all the other questions that follow. - 1 => { - ALL.store(true, Ordering::Relaxed); - true + let proceed = { + let mut all = ALL.lock().await; + *all || { + println_quoted(&*prompt::PENDING, &self); + let answer = tokio::task::spawn_blocking(move || { + prompt( + "Proceed", + "with the previous command?", + &["Yes", "All", "No"], + ) + }) + .await??; + match answer { + // The default answer is `Yes`. + 0 => true, + // You can also say `All` to answer `Yes` to all the other questions that + // follow. + 1 => { + *all = true; + true + } + // Or you can say `No`. + 2 => false, + // TODO: This might need some changes. + // ! I didn't put a `None` option because you can just Ctrl-C it if you want. + _ => unreachable!(), } - // Or you can say `No`. - 2 => false, - // ! I didn't put a `None` option because you can just Ctrl-C it if you want. - _ => unreachable!(), } }; if !proceed {