diff --git a/cli/src/git_util.rs b/cli/src/git_util.rs index 1c98c6f5d8..d753f3eca3 100644 --- a/cli/src/git_util.rs +++ b/cli/src/git_util.rs @@ -70,7 +70,8 @@ pub fn is_colocated_git_workspace(workspace: &Workspace, repo: &ReadonlyRepo) -> } fn terminal_get_username(ui: &Ui, url: &str) -> Option { - ui.prompt(&format!("Username for {url}")).ok() + ui.prompt(&mut ui.stdout(), &format!("Username for {url}")) + .ok() } fn terminal_get_pw(ui: &Ui, url: &str) -> Option { diff --git a/cli/src/movement_util.rs b/cli/src/movement_util.rs index ce197f7e6c..b3ab4bf111 100644 --- a/cli/src/movement_util.rs +++ b/cli/src/movement_util.rs @@ -214,6 +214,7 @@ fn choose_commit<'a>( drop(formatter); let choice = ui.prompt_choice( + &mut ui.stdout(), "enter the index of the commit you want to target", &choices, None, diff --git a/cli/src/ui.rs b/cli/src/ui.rs index be604b87d0..f69a8cdd19 100644 --- a/cli/src/ui.rs +++ b/cli/src/ui.rs @@ -566,35 +566,33 @@ impl Ui { .unwrap_or(false) } - #[allow(unknown_lints)] // XXX FIXME (aseipp): nightly bogons; re-test this occasionally - #[allow(clippy::assigning_clones)] - pub fn prompt(&self, prompt: &str) -> io::Result { + pub fn prompt(&self, writer: &mut dyn Write, prompt: &str) -> io::Result { if !Self::can_prompt() { return Err(io::Error::new( io::ErrorKind::Unsupported, "Cannot prompt for input since the output is not connected to a terminal", )); } - write!(self.stdout(), "{prompt}: ")?; - self.stdout().flush()?; + write!(writer, "{prompt}: ")?; + writer.flush()?; let mut buf = String::new(); io::stdin().read_line(&mut buf)?; - if let Some(trimmed) = buf.strip_suffix('\n') { - buf = trimmed.to_owned(); - } else if buf.is_empty() { - return Err(io::Error::new( + let buf = buf.strip_suffix('\n').map(|s| s.to_owned()).unwrap_or(buf); + if !buf.is_empty() { + Ok(buf) + } else { + Err(io::Error::new( io::ErrorKind::UnexpectedEof, "Prompt cancelled by EOF", - )); + )) } - - Ok(buf) } /// Repeat the given prompt until the input is one of the specified choices. pub fn prompt_choice( &self, + writer: &mut dyn Write, prompt: &str, choices: &[impl AsRef], default: Option<&str>, @@ -602,13 +600,13 @@ impl Ui { if !Self::can_prompt() { if let Some(default) = default { // Choose the default automatically without waiting. - writeln!(self.stdout(), "{prompt}: {default}")?; + writeln!(writer, "{prompt}: {default}")?; return Ok(default.to_owned()); } } loop { - let choice = self.prompt(prompt)?.trim().to_owned(); + let choice = self.prompt(writer, prompt)?.trim().to_owned(); if choice.is_empty() { if let Some(default) = default { return Ok(default.to_owned()); @@ -623,7 +621,12 @@ impl Ui { } /// Prompts for a yes-or-no response, with yes = true and no = false. - pub fn prompt_yes_no(&self, prompt: &str, default: Option) -> io::Result { + pub fn prompt_yes_no( + &self, + writer: &mut dyn Write, + prompt: &str, + default: Option, + ) -> io::Result { let default_str = match &default { Some(true) => "(Yn)", Some(false) => "(yN)", @@ -632,6 +635,7 @@ impl Ui { let default_choice = default.map(|c| if c { "Y" } else { "N" }); let choice = self.prompt_choice( + writer, &format!("{} {}", prompt, default_str), &["y", "n", "yes", "no", "Yes", "No", "YES", "NO"], default_choice,