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
[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"

View File

@@ -31,7 +31,9 @@ pub struct ModeKeybindings {
pub edit: HashMap<String, Vec<String>>,
#[serde(default)]
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)]
pub global: HashMap<String, Vec<String>>,
}
@@ -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
}
@@ -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 bindings.iter().any(|binding| binding == &key_char.to_string()) {
return true;
@@ -328,6 +379,11 @@ impl Config {
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);
@@ -401,6 +457,10 @@ impl Config {
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;
}

View File

@@ -69,16 +69,22 @@ async fn process_command(
current_position: &mut u64,
total_count: u64,
) -> 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() {
*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" {
// For debugging
eprintln!("Command: '{}', Action: '{}'", command, action);
match action {
"save" => {
let is_new = *current_position == total_count + 1;
let message = if is_new {
@@ -131,12 +137,75 @@ async fn process_command(
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;
},
"quit" => {
if form_state.has_unsaved_changes {
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));
}
}
}