config toml is now fully split between command mode, read only mode and edit mode

This commit is contained in:
filipriec
2025-02-28 20:51:42 +01:00
parent 55895cd4c5
commit cd3c6fd71f
3 changed files with 223 additions and 91 deletions

View File

@@ -1,12 +1,13 @@
# config.toml # config.toml
[keybindings] [keybindings]
# GENERAL
[keybindings.common]
save = [":w", "ctrl+s"] save = [":w", "ctrl+s"]
quit = [":q", "ctrl+q"] quit = [":q", "ctrl+q"]
force_quit = [":q!", "ctrl+shift+q"] force_quit = [":q!", "ctrl+shift+q"]
save_and_quit = [":wq", "ctrl+shift+s"] save_and_quit = [":wq", "ctrl+shift+s"]
move_up = ["Up"]
# [keybindings.common] move_down = ["Down"]
# MODE SPECIFIC # MODE SPECIFIC
# READ ONLY MODE # READ ONLY MODE
@@ -18,8 +19,8 @@ next_entry = ["right","1"]
move_left = ["h"] move_left = ["h"]
move_right = ["l"] move_right = ["l"]
move_up = ["k", "Up"] move_up = ["k"]
move_down = ["j", "Down"] move_down = ["j"]
move_word_next = ["w"] move_word_next = ["w"]
move_word_end = ["e"] move_word_end = ["e"]
move_word_prev = ["b"] move_word_prev = ["b"]
@@ -35,12 +36,14 @@ delete_char_forward = ["delete"]
delete_char_backward = ["backspace"] delete_char_backward = ["backspace"]
next_field = ["tab", "enter"] next_field = ["tab", "enter"]
prev_field = ["shift+tab", "backtab"] prev_field = ["shift+tab", "backtab"]
move_left = ["left"]
move_right = ["right"]
[keybindings.command] [keybindings.command]
enter_command_mode = [":", "ctrl+;"]
exit_command_mode = ["ctrl+g", "esc"] exit_command_mode = ["ctrl+g", "esc"]
command_execute = ["enter"] command_execute = ["enter"]
command_backspace = ["backspace"] command_backspace = ["backspace"]
enter_command_mode = [":", "ctrl+;"]
[colors] [colors]
theme = "dark" theme = "dark"

View File

@@ -31,7 +31,9 @@ pub struct ModeKeybindings {
pub edit: HashMap<String, Vec<String>>, pub edit: HashMap<String, Vec<String>>,
#[serde(default)] #[serde(default)]
pub command: HashMap<String, Vec<String>>, pub command: HashMap<String, Vec<String>>,
// Add other fields for standalone global keybindings as needed #[serde(default)]
pub common: HashMap<String, Vec<String>>,
// Store top-level keybindings that aren't in a specific mode section
#[serde(flatten)] #[serde(flatten)]
pub global: HashMap<String, Vec<String>>, pub global: HashMap<String, Vec<String>>,
} }
@@ -47,19 +49,25 @@ impl Config {
Ok(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> { 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) 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> { 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) 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> { 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) 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. /// Helper function to get an action for a key in a specific mode.
@@ -98,8 +106,7 @@ impl Config {
return None; return None;
} }
// Check if this sequence matches any binding across all modes. // Check if this sequence matches any binding in the mode-specific sections.
// First check read_only mode
for (action, bindings) in &self.keybindings.read_only { for (action, bindings) in &self.keybindings.read_only {
for binding in bindings { for binding in bindings {
if binding == &sequence_str { if binding == &sequence_str {
@@ -107,7 +114,7 @@ impl Config {
} }
} }
} }
// Then check edit mode
for (action, bindings) in &self.keybindings.edit { for (action, bindings) in &self.keybindings.edit {
for binding in bindings { for binding in bindings {
if binding == &sequence_str { if binding == &sequence_str {
@@ -115,7 +122,7 @@ impl Config {
} }
} }
} }
// Then check command mode
for (action, bindings) in &self.keybindings.command { for (action, bindings) in &self.keybindings.command {
for binding in bindings { for binding in bindings {
if binding == &sequence_str { 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 // Finally check global bindings
for (action, bindings) in &self.keybindings.global { for (action, bindings) in &self.keybindings.global {
for binding in bindings { for binding in bindings {
@@ -150,6 +167,10 @@ impl Config {
"down" => key == KeyCode::Down, "down" => key == KeyCode::Down,
"esc" => key == KeyCode::Esc, "esc" => key == KeyCode::Esc,
"enter" => key == KeyCode::Enter, "enter" => key == KeyCode::Enter,
"delete" => key == KeyCode::Delete,
"backspace" => key == KeyCode::Backspace,
"tab" => key == KeyCode::Tab,
"backtab" => key == KeyCode::BackTab,
_ => false, _ => false,
}; };
} }
@@ -169,6 +190,10 @@ impl Config {
"down" => expected_key = Some(KeyCode::Down), "down" => expected_key = Some(KeyCode::Down),
"esc" => expected_key = Some(KeyCode::Esc), "esc" => expected_key = Some(KeyCode::Esc),
"enter" => expected_key = Some(KeyCode::Enter), "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(':')), ":" => expected_key = Some(KeyCode::Char(':')),
part => { part => {
if part.len() == 1 { if part.len() == 1 {
@@ -184,6 +209,7 @@ impl Config {
/// Gets an action for a command string. /// Gets an action for a command string.
pub fn get_action_for_command(&self, command: &str) -> Option<&str> { pub fn get_action_for_command(&self, command: &str) -> Option<&str> {
// First check command mode bindings
for (action, bindings) in &self.keybindings.command { for (action, bindings) in &self.keybindings.command {
for binding in bindings { for binding in bindings {
if binding.starts_with(':') && binding.trim_start_matches(':') == command { 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 None
} }
@@ -285,6 +330,12 @@ impl Config {
} }
} }
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 let Some(bindings) = self.keybindings.global.get(action) {
if bindings.iter().any(|binding| binding == &key_char.to_string()) { if bindings.iter().any(|binding| binding == &key_char.to_string()) {
return true; return true;
@@ -328,6 +379,11 @@ impl Config {
return Some(action); 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 // Finally check global bindings
if let Some(action) = self.check_bindings_for_sequence(&self.keybindings.global, &sequence_str, &sequence_plus, sequence) { if let Some(action) = self.check_bindings_for_sequence(&self.keybindings.global, &sequence_str, &sequence_plus, sequence) {
return Some(action); return Some(action);
@@ -401,6 +457,10 @@ impl Config {
return true; 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) { if self.is_prefix_in_mode(&self.keybindings.global, &sequence_str, sequence) {
return true; return true;
} }

View File

@@ -69,16 +69,22 @@ async fn process_command(
current_position: &mut u64, current_position: &mut u64,
total_count: u64, total_count: u64,
) -> Result<(bool, String, bool), Box<dyn std::error::Error>> { ) -> Result<(bool, String, bool), Box<dyn std::error::Error>> {
let command = command_input.trim(); // Clone the trimmed command to avoid borrow issues
let command = command_input.trim().to_string();
if command.is_empty() { if command.is_empty() {
*command_message = "Empty command".to_string(); *command_message = "Empty command".to_string();
return Ok((false, command_message.clone(), false)); 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"); .unwrap_or("unknown");
if action == "save" { // For debugging
eprintln!("Command: '{}', Action: '{}'", command, action);
match action {
"save" => {
let is_new = *current_position == total_count + 1; let is_new = *current_position == total_count + 1;
let message = if is_new { let message = if is_new {
@@ -131,12 +137,75 @@ async fn process_command(
form_state.has_unsaved_changes = false; form_state.has_unsaved_changes = false;
command_input.clear(); command_input.clear();
return Ok((false, message, true)); return Ok((false, message, true));
} else { },
let (should_exit, message) = app_terminal "quit" => {
.handle_command(action, is_saved) if form_state.has_unsaved_changes {
.await?;
*command_message = message;
command_input.clear(); command_input.clear();
return Ok((should_exit, command_message.clone(), true)); 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));
}
} }
} }