diff options
| author | salmonik <salmonik@tuta.io> | 2026-10-28 22:31:18 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-10-28 22:31:18 +0100 |
| commit | 828470137cb4e806c31a26fa741d5d67c64f98c2 (patch) | |
| tree | 45dbe92af0f61166559d73c21fabd7759eca4b11 /src | |
| parent | v0.2.3: add ClientCore and fix the game board rendering (diff) | |
| download | zagan-bet-828470137cb4e806c31a26fa741d5d67c64f98c2.tar.gz zagan-bet-828470137cb4e806c31a26fa741d5d67c64f98c2.zip | |
v0.2.4: fix command UX, completion wrapping and auth prompt cancellation
Diffstat (limited to '')
| -rw-r--r-- | src/bin/client/repl.rs | 44 | ||||
| -rw-r--r-- | src/client/core.rs | 7 | ||||
| -rw-r--r-- | src/client/ui.rs | 27 |
3 files changed, 66 insertions, 12 deletions
diff --git a/src/bin/client/repl.rs b/src/bin/client/repl.rs index e9d32a0..3fa49b8 100644 --- a/src/bin/client/repl.rs +++ b/src/bin/client/repl.rs | |||
| @@ -290,11 +290,11 @@ impl ClientState { | |||
| 290 | 290 | ||
| 291 | fn do_login(&mut self) { | 291 | fn do_login(&mut self) { |
| 292 | loop { | 292 | loop { |
| 293 | let username = read_line("Username: "); | 293 | let username = read_line("Username: ", &self.cleanup_done); |
| 294 | if username.is_empty() { | 294 | if username.is_empty() { |
| 295 | break; | 295 | break; |
| 296 | } | 296 | } |
| 297 | let password = read_password("Password: "); | 297 | let password = read_password("Password: ", &self.cleanup_done); |
| 298 | if password.is_empty() { | 298 | if password.is_empty() { |
| 299 | break; | 299 | break; |
| 300 | } | 300 | } |
| @@ -329,11 +329,11 @@ impl ClientState { | |||
| 329 | } | 329 | } |
| 330 | 330 | ||
| 331 | fn do_register(&mut self) { | 331 | fn do_register(&mut self) { |
| 332 | let username = read_line("Username: "); | 332 | let username = read_line("Username: ", &self.cleanup_done); |
| 333 | if username.is_empty() { | 333 | if username.is_empty() { |
| 334 | return; | 334 | return; |
| 335 | } | 335 | } |
| 336 | let password = read_password("Password: "); | 336 | let password = read_password("Password: ", &self.cleanup_done); |
| 337 | if password.is_empty() { | 337 | if password.is_empty() { |
| 338 | return; | 338 | return; |
| 339 | } | 339 | } |
| @@ -357,11 +357,11 @@ impl ClientState { | |||
| 357 | println!("No host connected. Use 'connect <ip>:<port>' first."); | 357 | println!("No host connected. Use 'connect <ip>:<port>' first."); |
| 358 | return; | 358 | return; |
| 359 | } | 359 | } |
| 360 | let username = read_line("Username: "); | 360 | let username = read_line("Username: ", &self.cleanup_done); |
| 361 | if username.is_empty() { | 361 | if username.is_empty() { |
| 362 | return; | 362 | return; |
| 363 | } | 363 | } |
| 364 | let password = read_password("Password: "); | 364 | let password = read_password("Password: ", &self.cleanup_done); |
| 365 | if password.is_empty() { | 365 | if password.is_empty() { |
| 366 | return; | 366 | return; |
| 367 | } | 367 | } |
| @@ -419,7 +419,7 @@ fn restore_terminal(cleanup_done: &Arc<Mutex<bool>>) { | |||
| 419 | } | 419 | } |
| 420 | } | 420 | } |
| 421 | 421 | ||
| 422 | fn read_line(prompt: &str) -> String { | 422 | fn read_line(prompt: &str, cleanup_done: &Arc<Mutex<bool>>) -> String { |
| 423 | execute!(stdout(), Show).unwrap(); | 423 | execute!(stdout(), Show).unwrap(); |
| 424 | printsame!("{}", prompt); | 424 | printsame!("{}", prompt); |
| 425 | stdout().flush().unwrap(); | 425 | stdout().flush().unwrap(); |
| @@ -451,6 +451,20 @@ fn read_line(prompt: &str) -> String { | |||
| 451 | stdout().flush().unwrap(); | 451 | stdout().flush().unwrap(); |
| 452 | } | 452 | } |
| 453 | } | 453 | } |
| 454 | KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => { | ||
| 455 | println!(); | ||
| 456 | restore_terminal(cleanup_done); | ||
| 457 | std::process::exit(0); | ||
| 458 | } | ||
| 459 | KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => { | ||
| 460 | println!(); | ||
| 461 | restore_terminal(cleanup_done); | ||
| 462 | std::process::exit(0); | ||
| 463 | } | ||
| 464 | KeyCode::Esc => { | ||
| 465 | println!(); | ||
| 466 | return String::new(); | ||
| 467 | } | ||
| 454 | KeyCode::Char(c) => { | 468 | KeyCode::Char(c) => { |
| 455 | input.push(c); | 469 | input.push(c); |
| 456 | print!("{}", c); | 470 | print!("{}", c); |
| @@ -466,7 +480,7 @@ fn read_line(prompt: &str) -> String { | |||
| 466 | input.trim().to_string() | 480 | input.trim().to_string() |
| 467 | } | 481 | } |
| 468 | 482 | ||
| 469 | fn read_password(prompt: &str) -> String { | 483 | fn read_password(prompt: &str, cleanup_done: &Arc<Mutex<bool>>) -> String { |
| 470 | execute!(stdout(), Show).unwrap(); | 484 | execute!(stdout(), Show).unwrap(); |
| 471 | print!("{}", prompt); | 485 | print!("{}", prompt); |
| 472 | stdout().flush().unwrap(); | 486 | stdout().flush().unwrap(); |
| @@ -498,6 +512,20 @@ fn read_password(prompt: &str) -> String { | |||
| 498 | stdout().flush().unwrap(); | 512 | stdout().flush().unwrap(); |
| 499 | } | 513 | } |
| 500 | } | 514 | } |
| 515 | KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => { | ||
| 516 | println!(); | ||
| 517 | restore_terminal(cleanup_done); | ||
| 518 | std::process::exit(0); | ||
| 519 | } | ||
| 520 | KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => { | ||
| 521 | println!(); | ||
| 522 | restore_terminal(cleanup_done); | ||
| 523 | std::process::exit(0); | ||
| 524 | } | ||
| 525 | KeyCode::Esc => { | ||
| 526 | println!(); | ||
| 527 | return String::new(); | ||
| 528 | } | ||
| 501 | KeyCode::Char(c) => { | 529 | KeyCode::Char(c) => { |
| 502 | input.push(c); | 530 | input.push(c); |
| 503 | print!("*"); | 531 | print!("*"); |
diff --git a/src/client/core.rs b/src/client/core.rs index 0e54fba..a910a68 100644 --- a/src/client/core.rs +++ b/src/client/core.rs | |||
| @@ -1,7 +1,8 @@ | |||
| 1 | use crate::client::games::manager::GameManager; | 1 | use crate::client::games::manager::GameManager; |
| 2 | use crate::client::games::response::parse_spin_response; | 2 | use crate::client::games::response::parse_spin_response; |
| 3 | use crate::client::ui::{ | 3 | use crate::client::ui::{ |
| 4 | command_names, help_lines, input_cursor_column, parse_bet_arg, record_history, | 4 | command_names, completion_lines, help_lines, input_cursor_column, parse_bet_arg, |
| 5 | record_history, | ||
| 5 | }; | 6 | }; |
| 6 | 7 | ||
| 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| @@ -439,9 +440,7 @@ impl ClientCore { | |||
| 439 | if matches.len() == 1 { | 440 | if matches.len() == 1 { |
| 440 | self.input = matches[0].to_string(); | 441 | self.input = matches[0].to_string(); |
| 441 | } else if matches.len() > 1 { | 442 | } else if matches.len() > 1 { |
| 442 | let mut lines = vec!["Possible completions:".to_string()]; | 443 | effects.push(Effect::Print(completion_lines(&matches, 76))); |
| 443 | lines.push(matches.join(" ")); | ||
| 444 | effects.push(Effect::Print(lines)); | ||
| 445 | } | 444 | } |
| 446 | if let Some(prompt) = self.prompt_effect() { | 445 | if let Some(prompt) = self.prompt_effect() { |
| 447 | effects.push(prompt); | 446 | effects.push(prompt); |
diff --git a/src/client/ui.rs b/src/client/ui.rs index 036c36a..6fc2f62 100644 --- a/src/client/ui.rs +++ b/src/client/ui.rs | |||
| @@ -102,6 +102,33 @@ pub fn help_lines() -> Vec<String> { | |||
| 102 | lines | 102 | lines |
| 103 | } | 103 | } |
| 104 | 104 | ||
| 105 | /// Wrap a completion list to `width` columns, one space between names. | ||
| 106 | pub fn completion_lines(commands: &[&str], width: usize) -> Vec<String> { | ||
| 107 | let mut lines = vec!["Possible completions:".to_string()]; | ||
| 108 | let mut current = String::new(); | ||
| 109 | |||
| 110 | for command in commands { | ||
| 111 | let needed = if current.is_empty() { | ||
| 112 | command.len() | ||
| 113 | } else { | ||
| 114 | current.len() + 1 + command.len() | ||
| 115 | }; | ||
| 116 | if !current.is_empty() && needed > width { | ||
| 117 | lines.push(current.clone()); | ||
| 118 | current.clear(); | ||
| 119 | } | ||
| 120 | if !current.is_empty() { | ||
| 121 | current.push(' '); | ||
| 122 | } | ||
| 123 | current.push_str(command); | ||
| 124 | } | ||
| 125 | |||
| 126 | if !current.is_empty() { | ||
| 127 | lines.push(current); | ||
| 128 | } | ||
| 129 | lines | ||
| 130 | } | ||
| 131 | |||
| 105 | /// Add a command to the history, skipping blanks and consecutive duplicates. | 132 | /// Add a command to the history, skipping blanks and consecutive duplicates. |
| 106 | pub fn record_history(history: &mut Vec<String>, line: &str) { | 133 | pub fn record_history(history: &mut Vec<String>, line: &str) { |
| 107 | let line = line.trim(); | 134 | let line = line.trim(); |
