From cd3c6fd71fcb969d5baec2c585e2f0b1f250d2d2 Mon Sep 17 00:00:00 2001 From: filipriec Date: Fri, 28 Feb 2025 20:51:42 +0100 Subject: [PATCH] config toml is now fully split between command mode, read only mode and edit mode --- client/config.toml | 15 +- client/src/config/config.rs | 110 ++++++++++--- client/src/modes/handlers/command_mode.rs | 189 +++++++++++++++------- 3 files changed, 223 insertions(+), 91 deletions(-) diff --git a/client/config.toml b/client/config.toml index 922e2c0..fbb583f 100644 --- a/client/config.toml +++ b/client/config.toml @@ -1,12 +1,13 @@ # config.toml [keybindings] -# GENERAL + +[keybindings.common] save = [":w", "ctrl+s"] quit = [":q", "ctrl+q"] force_quit = [":q!", "ctrl+shift+q"] save_and_quit = [":wq", "ctrl+shift+s"] - -# [keybindings.common] +move_up = ["Up"] +move_down = ["Down"] # MODE SPECIFIC # READ ONLY MODE @@ -18,8 +19,8 @@ next_entry = ["right","1"] move_left = ["h"] move_right = ["l"] -move_up = ["k", "Up"] -move_down = ["j", "Down"] +move_up = ["k"] +move_down = ["j"] move_word_next = ["w"] move_word_end = ["e"] move_word_prev = ["b"] @@ -35,12 +36,14 @@ delete_char_forward = ["delete"] delete_char_backward = ["backspace"] next_field = ["tab", "enter"] prev_field = ["shift+tab", "backtab"] +move_left = ["left"] +move_right = ["right"] [keybindings.command] -enter_command_mode = [":", "ctrl+;"] exit_command_mode = ["ctrl+g", "esc"] command_execute = ["enter"] command_backspace = ["backspace"] +enter_command_mode = [":", "ctrl+;"] [colors] theme = "dark" diff --git a/client/src/config/config.rs b/client/src/config/config.rs index cd01f0e..da2b9ba 100644 --- a/client/src/config/config.rs +++ b/client/src/config/config.rs @@ -31,7 +31,9 @@ pub struct ModeKeybindings { pub edit: HashMap>, #[serde(default)] pub command: HashMap>, - // Add other fields for standalone global keybindings as needed + #[serde(default)] + pub common: HashMap>, + // Store top-level keybindings that aren't in a specific mode section #[serde(flatten)] pub global: HashMap>, } @@ -47,19 +49,25 @@ impl Config { Ok(config) } - /// Gets an action for a key in Read-Only mode. + /// Gets an action for a key in Read-Only mode, also checking common keybindings. pub fn get_read_only_action_for_key(&self, key: KeyCode, modifiers: KeyModifiers) -> Option<&str> { self.get_action_for_key_in_mode(&self.keybindings.read_only, key, modifiers) + .or_else(|| self.get_action_for_key_in_mode(&self.keybindings.common, key, modifiers)) + .or_else(|| self.get_action_for_key_in_mode(&self.keybindings.global, key, modifiers)) } - /// Gets an action for a key in Edit mode. + /// Gets an action for a key in Edit mode, also checking common keybindings. pub fn get_edit_action_for_key(&self, key: KeyCode, modifiers: KeyModifiers) -> Option<&str> { self.get_action_for_key_in_mode(&self.keybindings.edit, key, modifiers) + .or_else(|| self.get_action_for_key_in_mode(&self.keybindings.common, key, modifiers)) + .or_else(|| self.get_action_for_key_in_mode(&self.keybindings.global, key, modifiers)) } - /// Gets an action for a key in Command mode. + /// Gets an action for a key in Command mode, also checking common keybindings. pub fn get_command_action_for_key(&self, key: KeyCode, modifiers: KeyModifiers) -> Option<&str> { self.get_action_for_key_in_mode(&self.keybindings.command, key, modifiers) + .or_else(|| self.get_action_for_key_in_mode(&self.keybindings.common, key, modifiers)) + .or_else(|| self.get_action_for_key_in_mode(&self.keybindings.global, key, modifiers)) } /// Helper function to get an action for a key in a specific mode. @@ -98,8 +106,7 @@ impl Config { return None; } - // Check if this sequence matches any binding across all modes. - // First check read_only mode + // Check if this sequence matches any binding in the mode-specific sections. for (action, bindings) in &self.keybindings.read_only { for binding in bindings { if binding == &sequence_str { @@ -107,7 +114,7 @@ impl Config { } } } - // Then check edit mode + for (action, bindings) in &self.keybindings.edit { for binding in bindings { if binding == &sequence_str { @@ -115,7 +122,7 @@ impl Config { } } } - // Then check command mode + for (action, bindings) in &self.keybindings.command { for binding in bindings { if binding == &sequence_str { @@ -123,6 +130,16 @@ impl Config { } } } + + // Check common keybindings + for (action, bindings) in &self.keybindings.common { + for binding in bindings { + if binding == &sequence_str { + return Some(action); + } + } + } + // Finally check global bindings for (action, bindings) in &self.keybindings.global { for binding in bindings { @@ -150,6 +167,10 @@ impl Config { "down" => key == KeyCode::Down, "esc" => key == KeyCode::Esc, "enter" => key == KeyCode::Enter, + "delete" => key == KeyCode::Delete, + "backspace" => key == KeyCode::Backspace, + "tab" => key == KeyCode::Tab, + "backtab" => key == KeyCode::BackTab, _ => false, }; } @@ -169,6 +190,10 @@ impl Config { "down" => expected_key = Some(KeyCode::Down), "esc" => expected_key = Some(KeyCode::Esc), "enter" => expected_key = Some(KeyCode::Enter), + "delete" => expected_key = Some(KeyCode::Delete), + "backspace" => expected_key = Some(KeyCode::Backspace), + "tab" => expected_key = Some(KeyCode::Tab), + "backtab" => expected_key = Some(KeyCode::BackTab), ":" => expected_key = Some(KeyCode::Char(':')), part => { if part.len() == 1 { @@ -184,6 +209,7 @@ impl Config { /// Gets an action for a command string. pub fn get_action_for_command(&self, command: &str) -> Option<&str> { + // First check command mode bindings for (action, bindings) in &self.keybindings.command { for binding in bindings { if binding.starts_with(':') && binding.trim_start_matches(':') == command { @@ -191,6 +217,25 @@ impl Config { } } } + + // Then check common bindings + for (action, bindings) in &self.keybindings.common { + for binding in bindings { + if binding.starts_with(':') && binding.trim_start_matches(':') == command { + return Some(action); + } + } + } + + // Finally check global bindings + for (action, bindings) in &self.keybindings.global { + for binding in bindings { + if binding.starts_with(':') && binding.trim_start_matches(':') == command { + return Some(action); + } + } + } + None } @@ -263,7 +308,7 @@ impl Config { key == KeyCode::Backspace && modifiers.is_empty() } } - + /// Checks if a key is bound to a specific action. pub fn has_key_for_action(&self, action: &str, key_char: char) -> bool { // Check all mode-specific keybindings for the action @@ -272,25 +317,31 @@ impl Config { return true; } } - + if let Some(bindings) = self.keybindings.edit.get(action) { if bindings.iter().any(|binding| binding == &key_char.to_string()) { return true; } } - + if let Some(bindings) = self.keybindings.command.get(action) { if bindings.iter().any(|binding| binding == &key_char.to_string()) { return true; } } - + + if let Some(bindings) = self.keybindings.common.get(action) { + if bindings.iter().any(|binding| binding == &key_char.to_string()) { + return true; + } + } + if let Some(bindings) = self.keybindings.global.get(action) { if bindings.iter().any(|binding| binding == &key_char.to_string()) { return true; } } - + false } @@ -317,17 +368,22 @@ impl Config { if let Some(action) = self.check_bindings_for_sequence(&self.keybindings.read_only, &sequence_str, &sequence_plus, sequence) { return Some(action); } - + // Then check edit mode if let Some(action) = self.check_bindings_for_sequence(&self.keybindings.edit, &sequence_str, &sequence_plus, sequence) { return Some(action); } - + // Then check command mode if let Some(action) = self.check_bindings_for_sequence(&self.keybindings.command, &sequence_str, &sequence_plus, sequence) { return Some(action); } - + + // Then check common keybindings + if let Some(action) = self.check_bindings_for_sequence(&self.keybindings.common, &sequence_str, &sequence_plus, sequence) { + return Some(action); + } + // Finally check global bindings if let Some(action) = self.check_bindings_for_sequence(&self.keybindings.global, &sequence_str, &sequence_plus, sequence) { return Some(action); @@ -335,13 +391,13 @@ impl Config { None } - + /// Helper method to check a specific mode's bindings against a key sequence fn check_bindings_for_sequence<'a>( - &self, - mode_bindings: &'a HashMap>, - sequence_str: &str, - sequence_plus: &str, + &self, + mode_bindings: &'a HashMap>, + sequence_str: &str, + sequence_plus: &str, sequence: &[KeyCode] ) -> Option<&'a str> { for (action, bindings) in mode_bindings { @@ -392,22 +448,26 @@ impl Config { if self.is_prefix_in_mode(&self.keybindings.read_only, &sequence_str, sequence) { return true; } - + if self.is_prefix_in_mode(&self.keybindings.edit, &sequence_str, sequence) { return true; } - + if self.is_prefix_in_mode(&self.keybindings.command, &sequence_str, sequence) { return true; } - + + if self.is_prefix_in_mode(&self.keybindings.common, &sequence_str, sequence) { + return true; + } + if self.is_prefix_in_mode(&self.keybindings.global, &sequence_str, sequence) { return true; } false } - + /// Helper method to check if a sequence is a prefix in a specific mode fn is_prefix_in_mode( &self, diff --git a/client/src/modes/handlers/command_mode.rs b/client/src/modes/handlers/command_mode.rs index a80bd4f..97937d7 100644 --- a/client/src/modes/handlers/command_mode.rs +++ b/client/src/modes/handlers/command_mode.rs @@ -69,74 +69,143 @@ async fn process_command( current_position: &mut u64, total_count: u64, ) -> Result<(bool, String, bool), Box> { - let command = command_input.trim(); + // Clone the trimmed command to avoid borrow issues + let command = command_input.trim().to_string(); if command.is_empty() { *command_message = "Empty command".to_string(); return Ok((false, command_message.clone(), false)); } - let action = config.get_action_for_command(command) + // Get the action for the command (now checks global and common bindings too) + let action = config.get_action_for_command(&command) .unwrap_or("unknown"); - if action == "save" { - let is_new = *current_position == total_count + 1; + // For debugging + eprintln!("Command: '{}', Action: '{}'", command, action); - let message = if is_new { - let post_request = PostAdresarRequest { - firma: form_state.values[0].clone(), - kz: form_state.values[1].clone(), - drc: form_state.values[2].clone(), - ulica: form_state.values[3].clone(), - psc: form_state.values[4].clone(), - mesto: form_state.values[5].clone(), - stat: form_state.values[6].clone(), - banka: form_state.values[7].clone(), - ucet: form_state.values[8].clone(), - skladm: form_state.values[9].clone(), - ico: form_state.values[10].clone(), - kontakt: form_state.values[11].clone(), - telefon: form_state.values[12].clone(), - skladu: form_state.values[13].clone(), - fax: form_state.values[14].clone(), - }; - let response = app_terminal.post_adresar(post_request).await?; - let new_total = app_terminal.get_adresar_count().await?; - *current_position = new_total; - form_state.id = response.into_inner().id; - "New entry created".to_string() - } else { - let put_request = PutAdresarRequest { - id: form_state.id, - firma: form_state.values[0].clone(), - kz: form_state.values[1].clone(), - drc: form_state.values[2].clone(), - ulica: form_state.values[3].clone(), - psc: form_state.values[4].clone(), - mesto: form_state.values[5].clone(), - stat: form_state.values[6].clone(), - banka: form_state.values[7].clone(), - ucet: form_state.values[8].clone(), - skladm: form_state.values[9].clone(), - ico: form_state.values[10].clone(), - kontakt: form_state.values[11].clone(), - telefon: form_state.values[12].clone(), - skladu: form_state.values[13].clone(), - fax: form_state.values[14].clone(), - }; - let _ = app_terminal.put_adresar(put_request).await?; - "Entry updated".to_string() - }; + match action { + "save" => { + let is_new = *current_position == total_count + 1; - *is_saved = true; - form_state.has_unsaved_changes = false; - command_input.clear(); - return Ok((false, message, true)); - } else { - let (should_exit, message) = app_terminal - .handle_command(action, is_saved) - .await?; - *command_message = message; - command_input.clear(); - return Ok((should_exit, command_message.clone(), true)); + let message = if is_new { + let post_request = PostAdresarRequest { + firma: form_state.values[0].clone(), + kz: form_state.values[1].clone(), + drc: form_state.values[2].clone(), + ulica: form_state.values[3].clone(), + psc: form_state.values[4].clone(), + mesto: form_state.values[5].clone(), + stat: form_state.values[6].clone(), + banka: form_state.values[7].clone(), + ucet: form_state.values[8].clone(), + skladm: form_state.values[9].clone(), + ico: form_state.values[10].clone(), + kontakt: form_state.values[11].clone(), + telefon: form_state.values[12].clone(), + skladu: form_state.values[13].clone(), + fax: form_state.values[14].clone(), + }; + let response = app_terminal.post_adresar(post_request).await?; + let new_total = app_terminal.get_adresar_count().await?; + *current_position = new_total; + form_state.id = response.into_inner().id; + "New entry created".to_string() + } else { + let put_request = PutAdresarRequest { + id: form_state.id, + firma: form_state.values[0].clone(), + kz: form_state.values[1].clone(), + drc: form_state.values[2].clone(), + ulica: form_state.values[3].clone(), + psc: form_state.values[4].clone(), + mesto: form_state.values[5].clone(), + stat: form_state.values[6].clone(), + banka: form_state.values[7].clone(), + ucet: form_state.values[8].clone(), + skladm: form_state.values[9].clone(), + ico: form_state.values[10].clone(), + kontakt: form_state.values[11].clone(), + telefon: form_state.values[12].clone(), + skladu: form_state.values[13].clone(), + fax: form_state.values[14].clone(), + }; + let _ = app_terminal.put_adresar(put_request).await?; + "Entry updated".to_string() + }; + + *is_saved = true; + form_state.has_unsaved_changes = false; + command_input.clear(); + return Ok((false, message, true)); + }, + "quit" => { + if form_state.has_unsaved_changes { + command_input.clear(); + return Ok((false, "Unsaved changes! Use :q! to force quit or :w to save".to_string(), true)); + } + command_input.clear(); + return Ok((true, "Exiting application".to_string(), true)); + }, + "force_quit" => { + command_input.clear(); + return Ok((true, "Force quitting application".to_string(), true)); + }, + "save_and_quit" => { + let is_new = *current_position == total_count + 1; + + if is_new { + let post_request = PostAdresarRequest { + firma: form_state.values[0].clone(), + kz: form_state.values[1].clone(), + drc: form_state.values[2].clone(), + ulica: form_state.values[3].clone(), + psc: form_state.values[4].clone(), + mesto: form_state.values[5].clone(), + stat: form_state.values[6].clone(), + banka: form_state.values[7].clone(), + ucet: form_state.values[8].clone(), + skladm: form_state.values[9].clone(), + ico: form_state.values[10].clone(), + kontakt: form_state.values[11].clone(), + telefon: form_state.values[12].clone(), + skladu: form_state.values[13].clone(), + fax: form_state.values[14].clone(), + }; + let _ = app_terminal.post_adresar(post_request).await?; + } else { + let put_request = PutAdresarRequest { + id: form_state.id, + firma: form_state.values[0].clone(), + kz: form_state.values[1].clone(), + drc: form_state.values[2].clone(), + ulica: form_state.values[3].clone(), + psc: form_state.values[4].clone(), + mesto: form_state.values[5].clone(), + stat: form_state.values[6].clone(), + banka: form_state.values[7].clone(), + ucet: form_state.values[8].clone(), + skladm: form_state.values[9].clone(), + ico: form_state.values[10].clone(), + kontakt: form_state.values[11].clone(), + telefon: form_state.values[12].clone(), + skladu: form_state.values[13].clone(), + fax: form_state.values[14].clone(), + }; + let _ = app_terminal.put_adresar(put_request).await?; + } + + command_input.clear(); + return Ok((true, "Saved and exiting application".to_string(), true)); + }, + "unknown" => { + let message = format!("Unknown command: {}", command); + command_input.clear(); + return Ok((false, message, true)); + }, + _ => { + let message = format!("Unhandled action: {}", action); + command_input.clear(); + return Ok((false, message, true)); + } } }