From d251b54fe40f6f8f95261b4df65bced2fb728c63 Mon Sep 17 00:00:00 2001 From: salmonik Date: Sun, 1 Nov 2026 19:00:04 +0100 Subject: v4.0.6: move the prompt editor and key mapping out of the core --- src/client/input.rs | 183 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 src/client/input.rs (limited to 'src/client/input.rs') diff --git a/src/client/input.rs b/src/client/input.rs new file mode 100644 index 0000000..0c9d99d --- /dev/null +++ b/src/client/input.rs @@ -0,0 +1,183 @@ +//! Terminal input for the adapter: the command-line editor and key mapping. +//! +//! `ClientCore` knows nothing about keys or the prompt; this module turns +//! keystrokes into `Command`s or a submitted line. + +use crate::client::games::roulette::Direction; +use crate::client::ui::{ + command_names, completion_lines, display_width, input_cursor_column, record_history, +}; +use crate::client::view::Command; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Key { + Char(char), + Enter, + Backspace, + Up, + Down, + Left, + Right, + Tab, + Esc, + CtrlC, +} + +/// What the prompt wants the adapter to do after a key. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Input { + /// Nothing changed. + None, + /// Redraw the prompt. + Prompt, + /// Run this command line. + Submit(String), + /// Print lines above the prompt. + Print(Vec), + Quit, +} + +/// What a key means inside a running game. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum GameInput { + None, + Quit, + Command(Command), +} + +/// The prompt buffer, its history and its cursor. +pub struct Editor { + input: String, + history: Vec, + history_index: usize, +} + +impl Default for Editor { + fn default() -> Self { + Self::new() + } +} + +impl Editor { + pub fn new() -> Self { + Self { + input: String::new(), + history: Vec::new(), + history_index: 0, + } + } + + pub fn input(&self) -> &str { + &self.input + } + + pub fn clear(&mut self) { + self.input.clear(); + } + + /// Column of the cursor after the `> ` prompt. + pub fn cursor_column(&self) -> u16 { + input_cursor_column(2, display_width(&self.input)) + } + + pub fn handle(&mut self, key: Key) -> Input { + match key { + Key::CtrlC => Input::Quit, + Key::Enter => { + let line = self.input.trim().to_string(); + if line.is_empty() { + return Input::None; + } + record_history(&mut self.history, &line); + self.history_index = self.history.len(); + self.input.clear(); + Input::Submit(line) + } + Key::Backspace => { + self.input.pop(); + Input::Prompt + } + Key::Char(c) => { + self.input.push(c); + Input::Prompt + } + Key::Up => { + if self.history_index > 0 { + self.history_index -= 1; + self.input = self.history[self.history_index].clone(); + } + Input::Prompt + } + Key::Down => { + if self.history_index < self.history.len() { + self.history_index += 1; + self.input = if self.history_index == self.history.len() { + String::new() + } else { + self.history[self.history_index].clone() + }; + } + Input::Prompt + } + Key::Tab => { + let prefix = self.input.trim(); + let matches: Vec<&'static str> = command_names() + .into_iter() + .filter(|command| command.starts_with(prefix)) + .collect(); + match matches.len() { + 1 => { + self.input = matches[0].to_string(); + Input::Prompt + } + 0 => Input::None, + _ => Input::Print(completion_lines(&matches, 76)), + } + } + // Esc, Left and Right do nothing on the prompt line. + Key::Esc | Key::Left | Key::Right => Input::None, + } + } +} + +/// Map a key to a command inside `game`. +/// +/// Invalid combinations simply produce a command the core ignores (for +/// example `h` when it is not the player's turn), so this stays stateless. +pub fn game_input(game: &str, key: Key) -> GameInput { + match key { + Key::CtrlC => GameInput::Quit, + Key::Char('q') => GameInput::Command(Command::Leave), + Key::Char('+') | Key::Char('=') => GameInput::Command(Command::AdjustBet(10)), + Key::Char('-') => GameInput::Command(Command::AdjustBet(-10)), + _ => match game { + "roulette" => match key { + Key::Up => GameInput::Command(Command::MoveCursor(Direction::Up)), + Key::Down => GameInput::Command(Command::MoveCursor(Direction::Down)), + Key::Left => GameInput::Command(Command::MoveCursor(Direction::Left)), + Key::Right => GameInput::Command(Command::MoveCursor(Direction::Right)), + Key::Enter => GameInput::Command(Command::PlaceBet), + Key::Char(' ') => GameInput::Command(Command::Spin), + Key::Char('c') => GameInput::Command(Command::ClearBets), + _ => GameInput::None, + }, + "blackjack" => match key { + Key::Char(' ') => GameInput::Command(Command::Deal), + Key::Char('h') => GameInput::Command(Command::Hit), + Key::Char('s') => GameInput::Command(Command::Stand), + Key::Char('d') => GameInput::Command(Command::Double), + _ => GameInput::None, + }, + "crash" => match key { + Key::Char(' ') => GameInput::Command(Command::Start), + Key::Char('c') => GameInput::Command(Command::CashOut), + _ => GameInput::None, + }, + _ => match key { + Key::Char(' ') => GameInput::Command(Command::Spin), + Key::Esc => GameInput::Command(Command::Leave), + _ => GameInput::None, + }, + }, + } +} -- cgit v1.2.3