From 8e0fae26eec4dfecad7423c96b4998c1751d7c73 Mon Sep 17 00:00:00 2001 From: filipriec Date: Mon, 31 Mar 2025 22:08:39 +0200 Subject: [PATCH] we compiled --- client/src/modes/canvas/edit.rs | 360 +++++++++++++++++-------------- client/src/state/canvas_state.rs | 40 +--- client/src/state/pages/auth.rs | 17 ++ client/src/state/pages/form.rs | 52 +++++ 4 files changed, 272 insertions(+), 197 deletions(-) diff --git a/client/src/modes/canvas/edit.rs b/client/src/modes/canvas/edit.rs index 94b8cef..4afd96f 100644 --- a/client/src/modes/canvas/edit.rs +++ b/client/src/modes/canvas/edit.rs @@ -1,6 +1,8 @@ // src/modes/canvas/edit.rs // TODO THIS is freaking bloated with functions it never uses REFACTOR 200 LOC can be gone + +use std::any::Any; use crossterm::event::{KeyEvent, KeyCode, KeyModifiers}; use crate::config::binds::config::Config; use crate::state::canvas_state::CanvasState; @@ -8,7 +10,7 @@ use crate::state::pages::form::FormState; use crate::services::grpc_client::GrpcClient; use crate::tui::functions::common::form::{save, revert}; -pub async fn handle_edit_event_internal( +pub async fn handle_edit_event_internal( key: KeyEvent, config: &Config, state: &mut S, @@ -20,16 +22,14 @@ pub async fn handle_edit_event_internal( grpc_client: &mut GrpcClient, ) -> Result> { if let Some("enter_command_mode") = config.get_action_for_key_in_mode(&config.keybindings.global, key.code, key.modifiers) { - // Ignore in edit mode and process as normal input - handle_edit_specific_input(key, form_state, ideal_cursor_column); + handle_edit_specific_input(key, state, ideal_cursor_column); return Ok(command_message.clone()); } - // Check common actions first if let Some(action) = config.get_action_for_key_in_mode(&config.keybindings.common, key.code, key.modifiers) { return execute_common_action( action, - form_state, + state, grpc_client, is_saved, current_position, @@ -40,302 +40,340 @@ pub async fn handle_edit_event_internal( if let Some(action) = config.get_edit_action_for_key(key.code, key.modifiers) { return execute_edit_action( action, - form_state, + state, ideal_cursor_column, - grpc_client, // Changed from AppTerminal + grpc_client, is_saved, current_position, total_count, ).await; } - handle_edit_specific_input( - key, - form_state, - ideal_cursor_column, - ); - + handle_edit_specific_input(key, state, ideal_cursor_column); Ok(command_message.clone()) } -async fn execute_common_action( +async fn execute_common_action( action: &str, - form_state: &mut FormState, + state: &mut S, grpc_client: &mut GrpcClient, is_saved: &mut bool, current_position: &mut u64, total_count: u64, ) -> Result> { match action { - "save" => { - save( - form_state, - grpc_client, - is_saved, - current_position, - total_count, - ).await - }, - "revert" => { - revert( - form_state, - grpc_client, - current_position, - total_count, - ).await - }, + "save" | "revert" if state.has_unsaved_changes() => { + if let Some(form_state) = (state as &mut dyn Any).downcast_mut::() { + return match action { + "save" => save(form_state, grpc_client, is_saved, current_position, total_count).await, + "revert" => revert(form_state, grpc_client, current_position, total_count).await, + _ => unreachable!(), + }; + } + Ok("Action not available in this context".to_string()) + } "move_up" | "move_down" => { - // Reuse edit mode's existing logic execute_edit_action( action, - form_state, - &mut 0, // Dummy ideal_cursor_column (not used here) + state, + &mut 0, grpc_client, is_saved, current_position, total_count, ).await - }, + } _ => Ok(format!("Common action not handled: {}", action)), } } -fn handle_edit_specific_input( +fn handle_edit_specific_input( // No Any needed here key: KeyEvent, - form_state: &mut FormState, + state: &mut S, ideal_cursor_column: &mut usize, ) { match key.code { KeyCode::Char(c) => { - let cursor_pos = form_state.current_cursor_pos; - let field_value = form_state.get_current_input_mut(); + let cursor_pos = state.current_cursor_pos(); + let field_value = state.get_current_input_mut(); let mut chars: Vec = field_value.chars().collect(); if cursor_pos <= chars.len() { chars.insert(cursor_pos, c); *field_value = chars.into_iter().collect(); - form_state.current_cursor_pos = cursor_pos + 1; - *ideal_cursor_column = form_state.current_cursor_pos; - form_state.has_unsaved_changes = true; + // Use trait setters + state.set_current_cursor_pos(cursor_pos + 1); + state.set_has_unsaved_changes(true); + *ideal_cursor_column = state.current_cursor_pos(); // Update ideal column } } KeyCode::Backspace => { - if form_state.current_cursor_pos > 0 { - let cursor_pos = form_state.current_cursor_pos; - let field_value = form_state.get_current_input_mut(); + if state.current_cursor_pos() > 0 { + let cursor_pos = state.current_cursor_pos(); + let field_value = state.get_current_input_mut(); let mut chars: Vec = field_value.chars().collect(); if cursor_pos <= chars.len() && cursor_pos > 0 { chars.remove(cursor_pos - 1); *field_value = chars.into_iter().collect(); - form_state.current_cursor_pos = cursor_pos - 1; - *ideal_cursor_column = form_state.current_cursor_pos; - form_state.has_unsaved_changes = true; + // Use trait setters + state.set_current_cursor_pos(cursor_pos - 1); + state.set_has_unsaved_changes(true); + *ideal_cursor_column = state.current_cursor_pos(); // Update ideal column } } } KeyCode::Delete => { - let cursor_pos = form_state.current_cursor_pos; - let field_value = form_state.get_current_input_mut(); + let cursor_pos = state.current_cursor_pos(); + let field_value = state.get_current_input_mut(); let chars: Vec = field_value.chars().collect(); if cursor_pos < chars.len() { let mut new_chars = chars.clone(); new_chars.remove(cursor_pos); *field_value = new_chars.into_iter().collect(); - form_state.has_unsaved_changes = true; + // Use trait setter + state.set_has_unsaved_changes(true); + // Cursor position doesn't change, but ideal might if text changed + *ideal_cursor_column = state.current_cursor_pos(); } } KeyCode::Tab => { - if key.modifiers.contains(KeyModifiers::SHIFT) { - if form_state.current_field == 0 { - form_state.current_field = form_state.fields.len() - 1; + let num_fields = state.fields().len(); + if num_fields > 0 { // Avoid panic on empty fields + let current_field = state.current_field(); + let new_field = if key.modifiers.contains(KeyModifiers::SHIFT) { + if current_field == 0 { num_fields - 1 } else { current_field - 1 } } else { - form_state.current_field = form_state.current_field.saturating_sub(1); - } - } else { - form_state.current_field = (form_state.current_field + 1) % form_state.fields.len(); + (current_field + 1) % num_fields + }; + // Use trait setter + state.set_current_field(new_field); + let current_input = state.get_current_input(); + let max_cursor_pos = current_input.len(); + // Use trait setter + state.set_current_cursor_pos((*ideal_cursor_column).min(max_cursor_pos)); } - let current_input = form_state.get_current_input(); - let max_cursor_pos = current_input.len(); - form_state.current_cursor_pos = (*ideal_cursor_column).min(max_cursor_pos); } KeyCode::Enter => { - form_state.current_field = (form_state.current_field + 1) % form_state.fields.len(); - let current_input = form_state.get_current_input(); - let max_cursor_pos = current_input.len(); - form_state.current_cursor_pos = (*ideal_cursor_column).min(max_cursor_pos); + let num_fields = state.fields().len(); + if num_fields > 0 { // Avoid panic on empty fields + let new_field = (state.current_field() + 1) % num_fields; + // Use trait setter + state.set_current_field(new_field); + let current_input = state.get_current_input(); + let max_cursor_pos = current_input.len(); + // Use trait setter + state.set_current_cursor_pos((*ideal_cursor_column).min(max_cursor_pos)); + } } _ => {} } } -async fn execute_edit_action( +async fn execute_edit_action( // No Any needed here action: &str, - form_state: &mut FormState, + state: &mut S, ideal_cursor_column: &mut usize, - grpc_client: &mut GrpcClient, // Changed from AppTerminal - is_saved: &mut bool, - current_position: &mut u64, - total_count: u64, + // These parameters are unused if save/revert aren't handled here, + // but keep them for now if execute_common_action calls this + _grpc_client: &mut GrpcClient, + _is_saved: &mut bool, + _current_position: &mut u64, + _total_count: u64, ) -> Result> { match action { - "save" => { - save( - form_state, - grpc_client, // Changed from AppTerminal - is_saved, - current_position, - total_count, - ).await - }, "move_left" => { - form_state.current_cursor_pos = form_state.current_cursor_pos.saturating_sub(1); - *ideal_cursor_column = form_state.current_cursor_pos; + let new_pos = state.current_cursor_pos().saturating_sub(1); + // Use trait setter + state.set_current_cursor_pos(new_pos); + *ideal_cursor_column = new_pos; Ok("".to_string()) } "move_right" => { - let current_input = form_state.get_current_input(); - if form_state.current_cursor_pos < current_input.len() { - form_state.current_cursor_pos += 1; - *ideal_cursor_column = form_state.current_cursor_pos; + let current_input = state.get_current_input(); + let current_pos = state.current_cursor_pos(); + // Allow moving cursor to position *after* last character + if current_pos < current_input.len() { + let new_pos = current_pos + 1; + // Use trait setter + state.set_current_cursor_pos(new_pos); + *ideal_cursor_column = new_pos; } Ok("".to_string()) } "move_up" => { - if form_state.current_field == 0 { - form_state.current_field = form_state.fields.len() - 1; - } else { - form_state.current_field = form_state.current_field.saturating_sub(1); + let num_fields = state.fields().len(); + if num_fields > 0 { + let current_field = state.current_field(); + let new_field = if current_field == 0 { num_fields - 1 } else { current_field - 1 }; + // Use trait setter + state.set_current_field(new_field); + let current_input = state.get_current_input(); + let max_pos = current_input.len(); + // Use trait setter + state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos)); } - let current_input = form_state.get_current_input(); - let max_cursor_pos = current_input.len(); - form_state.current_cursor_pos = (*ideal_cursor_column).min(max_cursor_pos); Ok("".to_string()) } "move_down" => { - form_state.current_field = (form_state.current_field + 1) % form_state.fields.len(); - let current_input = form_state.get_current_input(); - let max_cursor_pos = current_input.len(); - form_state.current_cursor_pos = (*ideal_cursor_column).min(max_cursor_pos); + let num_fields = state.fields().len(); + if num_fields > 0 { + let new_field = (state.current_field() + 1) % num_fields; + // Use trait setter + state.set_current_field(new_field); + let current_input = state.get_current_input(); + let max_pos = current_input.len(); + // Use trait setter + state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos)); + } Ok("".to_string()) } "move_line_start" => { - form_state.current_cursor_pos = 0; - *ideal_cursor_column = form_state.current_cursor_pos; + // Use trait setter + state.set_current_cursor_pos(0); + *ideal_cursor_column = 0; Ok("".to_string()) } "move_line_end" => { - let current_input = form_state.get_current_input(); - form_state.current_cursor_pos = current_input.len(); - *ideal_cursor_column = form_state.current_cursor_pos; + let current_input = state.get_current_input(); + let new_pos = current_input.len(); + // Use trait setter + state.set_current_cursor_pos(new_pos); + *ideal_cursor_column = new_pos; Ok("".to_string()) } "move_first_line" => { - form_state.current_field = 0; - let current_input = form_state.get_current_input(); - let max_cursor_pos = current_input.len(); - form_state.current_cursor_pos = (*ideal_cursor_column).min(max_cursor_pos); + let num_fields = state.fields().len(); + if num_fields > 0 { + // Use trait setter + state.set_current_field(0); + let current_input = state.get_current_input(); + let max_pos = current_input.len(); + // Use trait setter + state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos)); + } Ok("Moved to first line".to_string()) } "move_last_line" => { - form_state.current_field = form_state.fields.len() - 1; - let current_input = form_state.get_current_input(); - let max_cursor_pos = current_input.len(); - form_state.current_cursor_pos = (*ideal_cursor_column).min(max_cursor_pos); + let num_fields = state.fields().len(); + if num_fields > 0 { + let new_field = num_fields - 1; + // Use trait setter + state.set_current_field(new_field); + let current_input = state.get_current_input(); + let max_pos = current_input.len(); + // Use trait setter + state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos)); + } Ok("Moved to last line".to_string()) } - // Word movement actions "move_word_next" => { - let current_input = form_state.get_current_input(); + let current_input = state.get_current_input(); if !current_input.is_empty() { - let new_pos = find_next_word_start(current_input, form_state.current_cursor_pos); - form_state.current_cursor_pos = new_pos.min(current_input.len()); - *ideal_cursor_column = form_state.current_cursor_pos; + let new_pos = find_next_word_start(current_input, state.current_cursor_pos()); + let final_pos = new_pos.min(current_input.len()); + // Use trait setter + state.set_current_cursor_pos(final_pos); + *ideal_cursor_column = final_pos; } Ok("".to_string()) } "move_word_end" => { - let current_input = form_state.get_current_input(); + let current_input = state.get_current_input(); if !current_input.is_empty() { - let new_pos = find_word_end(current_input, form_state.current_cursor_pos); - form_state.current_cursor_pos = new_pos.min(current_input.len()); - *ideal_cursor_column = form_state.current_cursor_pos; + let new_pos = find_word_end(current_input, state.current_cursor_pos()); + let final_pos = new_pos.min(current_input.len()); + // Use trait setter + state.set_current_cursor_pos(final_pos); + *ideal_cursor_column = final_pos; } Ok("".to_string()) } "move_word_prev" => { - let current_input = form_state.get_current_input(); + let current_input = state.get_current_input(); if !current_input.is_empty() { - let new_pos = find_prev_word_start(current_input, form_state.current_cursor_pos); - form_state.current_cursor_pos = new_pos; - *ideal_cursor_column = form_state.current_cursor_pos; + let new_pos = find_prev_word_start(current_input, state.current_cursor_pos()); + // Use trait setter + state.set_current_cursor_pos(new_pos); + *ideal_cursor_column = new_pos; } Ok("".to_string()) } "move_word_end_prev" => { - let current_input = form_state.get_current_input(); + let current_input = state.get_current_input(); if !current_input.is_empty() { - let new_pos = find_prev_word_end(current_input, form_state.current_cursor_pos); - form_state.current_cursor_pos = new_pos; - *ideal_cursor_column = form_state.current_cursor_pos; + let new_pos = find_prev_word_end(current_input, state.current_cursor_pos()); + // Use trait setter + state.set_current_cursor_pos(new_pos); + *ideal_cursor_column = new_pos; } - Ok("".to_string()) + Ok("Moved to previous word end".to_string()) } - // Edit-specific actions that can be bound to keys "delete_char_forward" => { - let cursor_pos = form_state.current_cursor_pos; - let field_value = form_state.get_current_input_mut(); - let chars: Vec = field_value.chars().collect(); + let cursor_pos = state.current_cursor_pos(); + let field_value = state.get_current_input_mut(); + let mut chars: Vec = field_value.chars().collect(); // Use mut for modification if cursor_pos < chars.len() { - let mut new_chars = chars.clone(); - new_chars.remove(cursor_pos); - *field_value = new_chars.into_iter().collect(); - form_state.has_unsaved_changes = true; + chars.remove(cursor_pos); + *field_value = chars.into_iter().collect(); + // Use trait setter + state.set_has_unsaved_changes(true); + // Cursor position doesn't change + *ideal_cursor_column = cursor_pos; } Ok("".to_string()) } "delete_char_backward" => { - if form_state.current_cursor_pos > 0 { - let cursor_pos = form_state.current_cursor_pos; - let field_value = form_state.get_current_input_mut(); - let mut chars: Vec = field_value.chars().collect(); + if state.current_cursor_pos() > 0 { + let cursor_pos = state.current_cursor_pos(); + let field_value = state.get_current_input_mut(); + let mut chars: Vec = field_value.chars().collect(); // Use mut if cursor_pos <= chars.len() && cursor_pos > 0 { chars.remove(cursor_pos - 1); *field_value = chars.into_iter().collect(); - form_state.current_cursor_pos = cursor_pos - 1; - *ideal_cursor_column = form_state.current_cursor_pos; - form_state.has_unsaved_changes = true; + let new_pos = cursor_pos - 1; + // Use trait setters + state.set_current_cursor_pos(new_pos); + state.set_has_unsaved_changes(true); + *ideal_cursor_column = new_pos; } } Ok("".to_string()) } "insert_char" => { - // This could be expanded to allow configurable character insertion - // For now, it's a placeholder that would need additional parameters - Ok("Character insertion requires configuration".to_string()) + // This action doesn't make sense here as char insertion is handled + // directly in handle_edit_specific_input + Ok("Character insertion handled directly".to_string()) } "next_field" => { - form_state.current_field = (form_state.current_field + 1) % form_state.fields.len(); - let current_input = form_state.get_current_input(); - let max_cursor_pos = current_input.len(); - form_state.current_cursor_pos = (*ideal_cursor_column).min(max_cursor_pos); + let num_fields = state.fields().len(); + if num_fields > 0 { + let new_field = (state.current_field() + 1) % num_fields; + // Use trait setter + state.set_current_field(new_field); + let current_input = state.get_current_input(); + let max_pos = current_input.len(); + // Use trait setter + state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos)); + } Ok("".to_string()) } "prev_field" => { - if form_state.current_field == 0 { - form_state.current_field = form_state.fields.len() - 1; - } else { - form_state.current_field = form_state.current_field.saturating_sub(1); + let num_fields = state.fields().len(); + if num_fields > 0 { + let current_field = state.current_field(); + let new_field = if current_field == 0 { num_fields - 1 } else { current_field - 1 }; + // Use trait setter + state.set_current_field(new_field); + let current_input = state.get_current_input(); + let max_pos = current_input.len(); + // Use trait setter + state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos)); } - let current_input = form_state.get_current_input(); - let max_cursor_pos = current_input.len(); - form_state.current_cursor_pos = (*ideal_cursor_column).min(max_cursor_pos); Ok("".to_string()) } - // Fallback for unrecognized actions - _ => Ok(format!("Unknown action: {}", action)), + _ => Ok(format!("Unknown edit action: {}", action)), } } -// Reuse these character and word navigation helper functions #[derive(PartialEq)] enum CharType { Whitespace, diff --git a/client/src/state/canvas_state.rs b/client/src/state/canvas_state.rs index 7ccb188..66a0ae0 100644 --- a/client/src/state/canvas_state.rs +++ b/client/src/state/canvas_state.rs @@ -10,40 +10,8 @@ pub trait CanvasState { fn get_current_input(&self) -> &str; fn get_current_input_mut(&mut self) -> &mut String; fn fields(&self) -> Vec<&str>; -} - -// Implement for FormState (keep existing form.rs code and add this) -impl CanvasState for FormState { - fn current_field(&self) -> usize { - self.current_field - } - - fn current_cursor_pos(&self) -> usize { - self.current_cursor_pos - } - - fn has_unsaved_changes(&self) -> bool { - self.has_unsaved_changes - } - - fn inputs(&self) -> Vec<&String> { - self.values.iter().collect() - } - - fn get_current_input(&self) -> &str { - self.values - .get(self.current_field) - .map(|s| s.as_str()) - .unwrap_or("") - } - - fn get_current_input_mut(&mut self) -> &mut String { - self.values - .get_mut(self.current_field) - .expect("Invalid current_field index") - } - - fn fields(&self) -> Vec<&str> { - self.fields.iter().map(|s| s.as_str()).collect() - } + + fn set_current_field(&mut self, index: usize); + fn set_current_cursor_pos(&mut self, pos: usize); + fn set_has_unsaved_changes(&mut self, changed: bool); } diff --git a/client/src/state/pages/auth.rs b/client/src/state/pages/auth.rs index b684362..4be9eff 100644 --- a/client/src/state/pages/auth.rs +++ b/client/src/state/pages/auth.rs @@ -67,4 +67,21 @@ impl CanvasState for AuthState { fn fields(&self) -> Vec<&str> { vec!["Username/Email", "Password"] } + + // --- Implement the setter methods --- + fn set_current_field(&mut self, index: usize) { + // AuthState only has 2 fields (0 and 1) + if index < 2 { + self.current_field = index; + } + } + + fn set_current_cursor_pos(&mut self, pos: usize) { + // Optional: Add validation based on current input length if needed + self.current_cursor_pos = pos; + } + + fn set_has_unsaved_changes(&mut self, _changed: bool) { + // Auth form doesn't track unsaved changes, so do nothing + } } diff --git a/client/src/state/pages/form.rs b/client/src/state/pages/form.rs index bfa9837..4d98500 100644 --- a/client/src/state/pages/form.rs +++ b/client/src/state/pages/form.rs @@ -2,6 +2,7 @@ use crate::config::colors::themes::Theme; use ratatui::layout::Rect; use ratatui::Frame; +use crate::state::canvas_state::CanvasState; pub struct FormState { pub id: i64, @@ -82,3 +83,54 @@ impl FormState { ]; } } + +impl CanvasState for FormState { + fn current_field(&self) -> usize { + self.current_field + } + + fn current_cursor_pos(&self) -> usize { + self.current_cursor_pos + } + + fn has_unsaved_changes(&self) -> bool { + self.has_unsaved_changes + } + + fn inputs(&self) -> Vec<&String> { + self.values.iter().collect() + } + + fn get_current_input(&self) -> &str { + self.values + .get(self.current_field) + .map(|s| s.as_str()) + .unwrap_or("") + } + + fn get_current_input_mut(&mut self) -> &mut String { + self.values + .get_mut(self.current_field) + .expect("Invalid current_field index") + } + + fn fields(&self) -> Vec<&str> { + self.fields.iter().map(|s| s.as_str()).collect() + } + + // --- Implement the setter methods --- + fn set_current_field(&mut self, index: usize) { + if index < self.fields.len() { // Basic bounds check + self.current_field = index; + } + } + + fn set_current_cursor_pos(&mut self, pos: usize) { + // Optional: Add validation based on current input length if needed + self.current_cursor_pos = pos; + } + + fn set_has_unsaved_changes(&mut self, changed: bool) { + self.has_unsaved_changes = changed; + } +}