1
1
use crate :: migrate;
2
2
use crate :: opt:: ConnectOpts ;
3
- use console:: style;
4
- use promptly :: { prompt , ReadlineError } ;
3
+ use console:: { style, Term } ;
4
+ use dialoguer :: Confirm ;
5
5
use sqlx:: any:: Any ;
6
6
use sqlx:: migrate:: MigrateDatabase ;
7
+ use std:: { io, mem} ;
8
+ use tokio:: task;
7
9
8
10
pub async fn create ( connect_opts : & ConnectOpts ) -> anyhow:: Result < ( ) > {
9
11
// NOTE: only retry the idempotent action.
@@ -24,7 +26,7 @@ pub async fn create(connect_opts: &ConnectOpts) -> anyhow::Result<()> {
24
26
}
25
27
26
28
pub async fn drop ( connect_opts : & ConnectOpts , confirm : bool , force : bool ) -> anyhow:: Result < ( ) > {
27
- if confirm && !ask_to_continue_drop ( connect_opts. required_db_url ( ) ?) {
29
+ if confirm && !ask_to_continue_drop ( connect_opts. required_db_url ( ) ?. to_owned ( ) ) . await {
28
30
return Ok ( ( ) ) ;
29
31
}
30
32
@@ -58,27 +60,44 @@ pub async fn setup(migration_source: &str, connect_opts: &ConnectOpts) -> anyhow
58
60
migrate:: run ( migration_source, connect_opts, false , false , None ) . await
59
61
}
60
62
61
- fn ask_to_continue_drop ( db_url : & str ) -> bool {
62
- loop {
63
- let r: Result < String , ReadlineError > =
64
- prompt ( format ! ( "Drop database at {}? (y/n)" , style( db_url) . cyan( ) ) ) ;
65
- match r {
66
- Ok ( response) => {
67
- if response == "n" || response == "N" {
68
- return false ;
69
- } else if response == "y" || response == "Y" {
70
- return true ;
71
- } else {
72
- println ! (
73
- "Response not recognized: {}\n Please type 'y' or 'n' and press enter." ,
74
- response
75
- ) ;
76
- }
77
- }
78
- Err ( e) => {
79
- println ! ( "{e}" ) ;
80
- return false ;
63
+ async fn ask_to_continue_drop ( db_url : String ) -> bool {
64
+ struct RestoreCursorGuard {
65
+ disarmed : bool ,
66
+ }
67
+
68
+ impl Drop for RestoreCursorGuard {
69
+ fn drop ( & mut self ) {
70
+ if !self . disarmed {
71
+ Term :: stderr ( ) . show_cursor ( ) . unwrap ( )
81
72
}
82
73
}
83
74
}
75
+
76
+ let mut guard = RestoreCursorGuard { disarmed : false } ;
77
+
78
+ let decision_result = task:: spawn_blocking ( move || {
79
+ Confirm :: new ( )
80
+ . with_prompt ( format ! ( "Drop database at {}?" , style( & db_url) . cyan( ) ) )
81
+ . wait_for_newline ( true )
82
+ . default ( false )
83
+ . show_default ( true )
84
+ . interact ( )
85
+ } )
86
+ . await
87
+ . expect ( "Confirm thread panicked" ) ;
88
+ match decision_result {
89
+ Ok ( decision) => {
90
+ guard. disarmed = true ;
91
+ decision
92
+ }
93
+ Err ( dialoguer:: Error :: IO ( err) ) if err. kind ( ) == io:: ErrorKind :: Interrupted => {
94
+ // Sometimes CTRL + C causes this error to be returned
95
+ mem:: drop ( guard) ;
96
+ false
97
+ }
98
+ Err ( err) => {
99
+ mem:: drop ( guard) ;
100
+ panic ! ( "Confirm dialog failed with {err}" )
101
+ }
102
+ }
84
103
}
0 commit comments