summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-28 22:31:18 +0100
committersalmonik <salmonik@tuta.io>2026-10-28 22:31:18 +0100
commit828470137cb4e806c31a26fa741d5d67c64f98c2 (patch)
tree45dbe92af0f61166559d73c21fabd7759eca4b11 /src
parentv0.2.3: add ClientCore and fix the game board rendering (diff)
downloadzagan-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.rs44
-rw-r--r--src/client/core.rs7
-rw-r--r--src/client/ui.rs27
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
422fn read_line(prompt: &str) -> String { 422fn 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
469fn read_password(prompt: &str) -> String { 483fn 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 @@
1use crate::client::games::manager::GameManager; 1use crate::client::games::manager::GameManager;
2use crate::client::games::response::parse_spin_response; 2use crate::client::games::response::parse_spin_response;
3use crate::client::ui::{ 3use 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.
106pub 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.
106pub fn record_history(history: &mut Vec<String>, line: &str) { 133pub fn record_history(history: &mut Vec<String>, line: &str) {
107 let line = line.trim(); 134 let line = line.trim();