diff --git a/Cargo.lock b/Cargo.lock index c964c11..7d545e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -170,9 +170,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.87" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", @@ -423,6 +423,7 @@ dependencies = [ name = "client" version = "0.2.5" dependencies = [ + "async-trait", "common", "crossterm", "dirs 6.0.0", diff --git a/client/Cargo.toml b/client/Cargo.toml index 732454d..4b26413 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true license.workspace = true [dependencies] +async-trait = "0.1.88" common = { path = "../common" } crossterm = "0.28.1" diff --git a/client/src/modes/canvas/read_only.rs b/client/src/modes/canvas/read_only.rs index 163b3b2..b158714 100644 --- a/client/src/modes/canvas/read_only.rs +++ b/client/src/modes/canvas/read_only.rs @@ -2,6 +2,7 @@ use crossterm::event::{KeyEvent}; use crate::config::binds::config::Config; +use crate::modes::handlers::read_only::ReadOnlyHandler; use crate::state::pages::form::FormState; use crate::config::binds::key_sequences::KeySequenceTracker; use crate::tui::terminal::grpc_client::GrpcClient; @@ -16,7 +17,6 @@ enum CharType { pub async fn handle_read_only_event( key: KeyEvent, config: &Config, - form_state: &mut FormState, key_sequence_tracker: &mut KeySequenceTracker, current_position: &mut u64, total_count: u64, @@ -24,8 +24,9 @@ pub async fn handle_read_only_event( command_message: &mut String, edit_mode_cooldown: &mut bool, ideal_cursor_column: &mut usize, + handler: &mut dyn ReadOnlyHandler, ) -> Result<(bool, String), Box> { - // Check for entering Edit mode from Read-Only mode + // Check edit mode transitions first if config.is_enter_edit_mode_before(key.code, key.modifiers) { *edit_mode_cooldown = true; *command_message = "Entering Edit mode".to_string(); @@ -33,88 +34,26 @@ pub async fn handle_read_only_event( } if config.is_enter_edit_mode_after(key.code, key.modifiers) { - let current_input = form_state.get_current_input(); - if !current_input.is_empty() && form_state.current_cursor_pos < current_input.len() { - form_state.current_cursor_pos += 1; - *ideal_cursor_column = form_state.current_cursor_pos; - } + handler.adjust_cursor_position(ideal_cursor_column); *edit_mode_cooldown = true; *command_message = "Entering Edit mode (after cursor)".to_string(); return Ok((false, command_message.clone())); } - // Handle Read-Only mode keybindings - if key.modifiers.is_empty() { - key_sequence_tracker.add_key(key.code); - let sequence = key_sequence_tracker.get_sequence(); + // Process key bindings + let sequence = key_sequence_tracker.get_sequence(); + let action = config.get_read_only_action_for_key(key.code, key.modifiers) + .or_else(|| config.matches_key_sequence_generalized(&sequence)) + .unwrap_or("unknown"); - // Try to match the current sequence against Read-Only mode bindings - if let Some(action) = config.matches_key_sequence_generalized(&sequence) { - let result = execute_action( - action, - form_state, - ideal_cursor_column, - key_sequence_tracker, - command_message, - current_position, - total_count, - grpc_client, - ).await?; - key_sequence_tracker.reset(); - return Ok((false, result)); - } + let result = handler.handle_read_only_action( + &action, + grpc_client, + current_position, + total_count + ).await?; - // Check if this might be a prefix of a longer sequence - if config.is_key_sequence_prefix(&sequence) { - return Ok((false, command_message.clone())); - } - - // Since it's not part of a multi-key sequence, check for a direct action - if sequence.len() == 1 && !config.is_key_sequence_prefix(&sequence) { - if let Some(action) = config.get_read_only_action_for_key(key.code, key.modifiers) { - let result = execute_action( - action, - form_state, - ideal_cursor_column, - key_sequence_tracker, - command_message, - current_position, - total_count, - grpc_client, - ).await?; - key_sequence_tracker.reset(); - return Ok((false, result)); - } - } - } else { - // If modifiers are pressed, check for direct key bindings - key_sequence_tracker.reset(); - - if let Some(action) = config.get_read_only_action_for_key(key.code, key.modifiers) { - let result = execute_action( - action, - form_state, - ideal_cursor_column, - key_sequence_tracker, - command_message, - current_position, - total_count, - grpc_client, - ).await?; - return Ok((false, result)); - } - } - - // Show a helpful message when no binding was found - if !*edit_mode_cooldown { - let default_key = "i".to_string(); - let edit_key = config.keybindings.read_only.get("enter_edit_mode_before") - .and_then(|keys| keys.first()) - .unwrap_or(&default_key); - *command_message = format!("Read-only mode - press {} to edit", edit_key); - } - - Ok((false, command_message.clone())) + Ok((false, result)) } async fn execute_action( diff --git a/client/src/modes/handlers.rs b/client/src/modes/handlers.rs index 8210779..baaef19 100644 --- a/client/src/modes/handlers.rs +++ b/client/src/modes/handlers.rs @@ -1,3 +1,4 @@ // src/client/modes/handlers.rs pub mod event; pub mod mode_manager; +pub mod read_only; diff --git a/client/src/modes/handlers/event.rs b/client/src/modes/handlers/event.rs index 6e8faac..4df3fab 100644 --- a/client/src/modes/handlers/event.rs +++ b/client/src/modes/handlers/event.rs @@ -143,7 +143,6 @@ impl EventHandler { return read_only::handle_read_only_event( key, config, - form_state, &mut self.key_sequence_tracker, current_position, total_count, @@ -151,6 +150,7 @@ impl EventHandler { &mut self.command_message, &mut self.edit_mode_cooldown, &mut self.ideal_cursor_column, + form_state, ).await; }, diff --git a/client/src/modes/handlers/read_only.rs b/client/src/modes/handlers/read_only.rs new file mode 100644 index 0000000..d7d56a6 --- /dev/null +++ b/client/src/modes/handlers/read_only.rs @@ -0,0 +1,17 @@ +// src/modes/handlers/read_only.rs +use async_trait::async_trait; +use crate::tui::terminal::grpc_client::GrpcClient; + +#[async_trait] +pub trait ReadOnlyHandler { + async fn handle_read_only_action( + &mut self, + action: &str, + grpc_client: &mut GrpcClient, + current_position: &mut u64, + total_count: u64, + ) -> Result>; + + // Add this method + fn adjust_cursor_position(&mut self, ideal_column: &mut usize); +} diff --git a/client/src/state/pages/form.rs b/client/src/state/pages/form.rs index d3dbd2a..f63b5af 100644 --- a/client/src/state/pages/form.rs +++ b/client/src/state/pages/form.rs @@ -2,6 +2,9 @@ use crate::config::colors::themes::Theme; use ratatui::layout::Rect; use ratatui::Frame; +use async_trait::async_trait; +use crate::modes::handlers::read_only::ReadOnlyHandler; +use crate::tui::terminal::GrpcClient; pub struct FormState { pub id: i64, @@ -12,6 +15,50 @@ pub struct FormState { pub current_cursor_pos: usize, } +#[async_trait::async_trait] +impl ReadOnlyHandler for FormState { + async fn handle_read_only_action( + &mut self, + action: &str, + grpc_client: &mut GrpcClient, + current_position: &mut u64, + total_count: u64, + ) -> Result> { + match action { + "previous_entry" => { + let new_position = current_position.saturating_sub(1); + if new_position >= 1 { + *current_position = new_position; + let response = grpc_client.get_adresar_by_position(*current_position).await?; + self.update_from_response(response); + Ok(format!("Loaded entry {}", *current_position)) + } else { + Ok("Already at first entry".into()) + } + }, + "next_entry" => { + if *current_position < total_count { + *current_position += 1; + let response = grpc_client.get_adresar_by_position(*current_position).await?; + self.update_from_response(response); + Ok(format!("Loaded entry {}", *current_position)) + } else { + self.reset_to_empty(); + Ok("New entry mode".into()) + } + }, + _ => Ok(format!("Unknown action: {}", action)) + } + } + + fn adjust_cursor_position(&mut self, ideal_column: &mut usize) { + let current_input = self.get_current_input(); + let max_pos = current_input.len().saturating_sub(1); + self.current_cursor_pos = (*ideal_column).min(max_pos); + *ideal_column = self.current_cursor_pos; + } +} + impl FormState { /// Create a new FormState with dynamic fields. pub fn new(fields: Vec) -> Self { @@ -70,4 +117,15 @@ impl FormState { .get_mut(self.current_field) .expect("Invalid current_field index") } + + fn update_from_response(&mut self, response: common::proto::multieko2::adresar::AdresarResponse) { + self.id = response.id; + self.values = vec![ + response.firma, response.kz, response.drc, + response.ulica, response.psc, response.mesto, + response.stat, response.banka, response.ucet, + response.skladm, response.ico, response.kontakt, + response.telefon, response.skladu, response.fax, + ]; + } }