diff --git a/Cargo.lock b/Cargo.lock index 66d1bd7a60..e90d383a66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1183,17 +1183,6 @@ dependencies = [ "serde", ] -[[package]] -name = "dialoguer" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" -dependencies = [ - "console", - "shell-words", - "thiserror 1.0.40", -] - [[package]] name = "difflib" version = "0.4.0" @@ -3366,12 +3355,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shell-words" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" - [[package]] name = "shlex" version = "1.3.0" @@ -3528,7 +3511,6 @@ dependencies = [ "clap", "clap_complete", "console", - "dialoguer", "dotenvy", "filetime", "futures-util", diff --git a/sqlx-cli/Cargo.toml b/sqlx-cli/Cargo.toml index f283ba747b..396fdadc99 100644 --- a/sqlx-cli/Cargo.toml +++ b/sqlx-cli/Cargo.toml @@ -34,7 +34,6 @@ clap_complete = { version = "4.3.1", optional = true } chrono = { version = "0.4.34", default-features = false, features = ["clock"] } anyhow = "1.0.58" console = "0.15.0" -dialoguer = { version = "0.11", default-features = false } serde_json = "1.0.142" glob = "0.3.0" openssl = { version = "0.10.46", optional = true } diff --git a/sqlx-cli/src/database.rs b/sqlx-cli/src/database.rs index 44d58eecca..6735ef24d8 100644 --- a/sqlx-cli/src/database.rs +++ b/sqlx-cli/src/database.rs @@ -1,10 +1,9 @@ use crate::opt::{ConnectOpts, MigrationSourceOpt}; use crate::{migrate, Config}; -use console::{style, Term}; -use dialoguer::Confirm; +use console::style; use sqlx::any::Any; use sqlx::migrate::MigrateDatabase; -use std::{io, mem}; +use std::io::{self, BufRead, Write}; use tokio::task; pub async fn create(connect_opts: &ConnectOpts) -> anyhow::Result<()> { @@ -75,45 +74,30 @@ pub async fn setup( } async fn ask_to_continue_drop(db_url: String) -> bool { - // If the setup operation is cancelled while we are waiting for the user to decide whether - // or not to drop the database, this will restore the terminal's cursor to its normal state. - struct RestoreCursorGuard { - disarmed: bool, - } - - impl Drop for RestoreCursorGuard { - fn drop(&mut self) { - if !self.disarmed { - Term::stderr().show_cursor().unwrap() - } - } - } + // Plain line-based prompt rather than dialoguer::Confirm. dialoguer puts + // the terminal into raw mode for the y/N toggle even with + // wait_for_newline(true), which means keypresses never echo and the whole + // prompt gets repainted on every flip. That's confusing in general and + // hostile to screen-reader users (#4236). Reading a line of stdin echoes + // input the way users expect and doesn't need a cursor-restore guard. + let prompt = format!("Drop database at {}? (y/N): ", style(&db_url).cyan()); - let mut guard = RestoreCursorGuard { disarmed: false }; + let decision = task::spawn_blocking(move || -> io::Result { + let mut stderr = io::stderr().lock(); + stderr.write_all(prompt.as_bytes())?; + stderr.flush()?; - let decision_result = task::spawn_blocking(move || { - Confirm::new() - .with_prompt(format!("Drop database at {}?", style(&db_url).cyan())) - .wait_for_newline(true) - .default(false) - .show_default(true) - .interact() + let mut line = String::new(); + io::stdin().lock().read_line(&mut line)?; + let answer = line.trim(); + Ok(answer.eq_ignore_ascii_case("y") || answer.eq_ignore_ascii_case("yes")) }) .await - .expect("Confirm thread panicked"); - match decision_result { - Ok(decision) => { - guard.disarmed = true; - decision - } - Err(dialoguer::Error::IO(err)) if err.kind() == io::ErrorKind::Interrupted => { - // Sometimes CTRL + C causes this error to be returned - mem::drop(guard); - false - } - Err(err) => { - mem::drop(guard); - panic!("Confirm dialog failed with {err}") - } + .expect("drop-confirm thread panicked"); + + match decision { + Ok(d) => d, + Err(err) if err.kind() == io::ErrorKind::Interrupted => false, + Err(err) => panic!("Confirm dialog failed with {err}"), } } diff --git a/sqlx-cli/src/lib.rs b/sqlx-cli/src/lib.rs index 9abd689e96..912ea460e3 100644 --- a/sqlx-cli/src/lib.rs +++ b/sqlx-cli/src/lib.rs @@ -59,8 +59,6 @@ pub fn maybe_apply_dotenv() { pub async fn run(opt: Opt) -> anyhow::Result<()> { // This `select!` is here so that when the process receives a `SIGINT` (CTRL + C), // the futures currently running on this task get dropped before the program exits. - // This is currently necessary for the consumers of the `dialoguer` crate to restore - // the user's terminal if the process is interrupted while a dialog is being displayed. let ctrlc_fut = signal::ctrl_c(); let do_run_fut = do_run(opt);