From 828470137cb4e806c31a26fa741d5d67c64f98c2 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 28 Oct 2026 22:31:18 +0100 Subject: v0.2.4: fix command UX, completion wrapping and auth prompt cancellation --- src/bin/client/repl.rs | 44 ++++++++++++++++++++++++++++++++++++-------- src/client/core.rs | 7 +++---- src/client/ui.rs | 27 +++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 12 deletions(-) (limited to 'src') 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 { fn do_login(&mut self) { loop { - let username = read_line("Username: "); + let username = read_line("Username: ", &self.cleanup_done); if username.is_empty() { break; } - let password = read_password("Password: "); + let password = read_password("Password: ", &self.cleanup_done); if password.is_empty() { break; } @@ -329,11 +329,11 @@ impl ClientState { } fn do_register(&mut self) { - let username = read_line("Username: "); + let username = read_line("Username: ", &self.cleanup_done); if username.is_empty() { return; } - let password = read_password("Password: "); + let password = read_password("Password: ", &self.cleanup_done); if password.is_empty() { return; } @@ -357,11 +357,11 @@ impl ClientState { println!("No host connected. Use 'connect :' first."); return; } - let username = read_line("Username: "); + let username = read_line("Username: ", &self.cleanup_done); if username.is_empty() { return; } - let password = read_password("Password: "); + let password = read_password("Password: ", &self.cleanup_done); if password.is_empty() { return; } @@ -419,7 +419,7 @@ fn restore_terminal(cleanup_done: &Arc>) { } } -fn read_line(prompt: &str) -> String { +fn read_line(prompt: &str, cleanup_done: &Arc>) -> String { execute!(stdout(), Show).unwrap(); printsame!("{}", prompt); stdout().flush().unwrap(); @@ -451,6 +451,20 @@ fn read_line(prompt: &str) -> String { stdout().flush().unwrap(); } } + KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => { + println!(); + restore_terminal(cleanup_done); + std::process::exit(0); + } + KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => { + println!(); + restore_terminal(cleanup_done); + std::process::exit(0); + } + KeyCode::Esc => { + println!(); + return String::new(); + } KeyCode::Char(c) => { input.push(c); print!("{}", c); @@ -466,7 +480,7 @@ fn read_line(prompt: &str) -> String { input.trim().to_string() } -fn read_password(prompt: &str) -> String { +fn read_password(prompt: &str, cleanup_done: &Arc>) -> String { execute!(stdout(), Show).unwrap(); print!("{}", prompt); stdout().flush().unwrap(); @@ -498,6 +512,20 @@ fn read_password(prompt: &str) -> String { stdout().flush().unwrap(); } } + KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => { + println!(); + restore_terminal(cleanup_done); + std::process::exit(0); + } + KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => { + println!(); + restore_terminal(cleanup_done); + std::process::exit(0); + } + KeyCode::Esc => { + println!(); + return String::new(); + } KeyCode::Char(c) => { input.push(c); 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 @@ use crate::client::games::manager::GameManager; use crate::client::games::response::parse_spin_response; use crate::client::ui::{ - command_names, help_lines, input_cursor_column, parse_bet_arg, record_history, + command_names, completion_lines, help_lines, input_cursor_column, parse_bet_arg, + record_history, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -439,9 +440,7 @@ impl ClientCore { if matches.len() == 1 { self.input = matches[0].to_string(); } else if matches.len() > 1 { - let mut lines = vec!["Possible completions:".to_string()]; - lines.push(matches.join(" ")); - effects.push(Effect::Print(lines)); + effects.push(Effect::Print(completion_lines(&matches, 76))); } if let Some(prompt) = self.prompt_effect() { 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 { lines } +/// Wrap a completion list to `width` columns, one space between names. +pub fn completion_lines(commands: &[&str], width: usize) -> Vec { + let mut lines = vec!["Possible completions:".to_string()]; + let mut current = String::new(); + + for command in commands { + let needed = if current.is_empty() { + command.len() + } else { + current.len() + 1 + command.len() + }; + if !current.is_empty() && needed > width { + lines.push(current.clone()); + current.clear(); + } + if !current.is_empty() { + current.push(' '); + } + current.push_str(command); + } + + if !current.is_empty() { + lines.push(current); + } + lines +} + /// Add a command to the history, skipping blanks and consecutive duplicates. pub fn record_history(history: &mut Vec, line: &str) { let line = line.trim(); -- cgit v1.2.3