// src/functions/modes/edit/form_e.rs use crate::services::grpc_client::GrpcClient; use crate::state::pages::form::FormState; use crate::state::app::state::AppState; use crate::tui::functions::common::form::{revert, save}; use crate::tui::functions::common::form::SaveOutcome; use crate::modes::handlers::event::EventOutcome; use crossterm::event::{KeyCode, KeyEvent}; use canvas::canvas::CanvasState; use std::any::Any; use anyhow::Result; pub async fn execute_common_action( action: &str, state: &mut S, grpc_client: &mut GrpcClient, app_state: &AppState, ) -> Result { match action { "save" | "revert" => { if !state.has_unsaved_changes() { return Ok(EventOutcome::Ok("No changes to save or revert.".to_string())); } if let Some(form_state) = (state as &mut dyn Any).downcast_mut::() { match action { "save" => { let save_result = save( app_state, form_state, grpc_client, ).await; match save_result { Ok(save_outcome) => { let message = match save_outcome { SaveOutcome::NoChange => "No changes to save.".to_string(), SaveOutcome::UpdatedExisting => "Entry updated.".to_string(), SaveOutcome::CreatedNew(_) => "New entry created.".to_string(), }; Ok(EventOutcome::DataSaved(save_outcome, message)) } Err(e) => Err(e), } } "revert" => { let revert_result = revert( form_state, grpc_client, ).await; match revert_result { Ok(message) => Ok(EventOutcome::Ok(message)), Err(e) => Err(e), } } _ => unreachable!(), } } else { Ok(EventOutcome::Ok(format!( "Action '{}' not implemented for this state type.", action ))) } } _ => Ok(EventOutcome::Ok(format!("Common action '{}' not handled here.", action))), } } pub async fn execute_edit_action( action: &str, key: KeyEvent, state: &mut S, ideal_cursor_column: &mut usize, ) -> Result { match action { "insert_char" => { if let KeyCode::Char(c) = key.code { 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(); state.set_current_cursor_pos(cursor_pos + 1); state.set_has_unsaved_changes(true); *ideal_cursor_column = state.current_cursor_pos(); } } else { return Ok("Error: insert_char called without a char key." .to_string()); } Ok("".to_string()) } "delete_char_backward" => { 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() { chars.remove(cursor_pos - 1); *field_value = chars.into_iter().collect(); let new_pos = cursor_pos - 1; state.set_current_cursor_pos(new_pos); state.set_has_unsaved_changes(true); *ideal_cursor_column = new_pos; } } Ok("".to_string()) } "delete_char_forward" => { 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.remove(cursor_pos); *field_value = chars.into_iter().collect(); state.set_has_unsaved_changes(true); *ideal_cursor_column = cursor_pos; } Ok("".to_string()) } "next_field" => { let num_fields = state.fields().len(); if num_fields > 0 { let current_field = state.current_field(); let new_field = (current_field + 1) % num_fields; state.set_current_field(new_field); let current_input = state.get_current_input(); let max_pos = current_input.len(); state.set_current_cursor_pos( (*ideal_cursor_column).min(max_pos), ); } Ok("".to_string()) } "prev_field" => { 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 }; state.set_current_field(new_field); let current_input = state.get_current_input(); let max_pos = current_input.len(); state.set_current_cursor_pos( (*ideal_cursor_column).min(max_pos), ); } Ok("".to_string()) } "move_left" => { let new_pos = state.current_cursor_pos().saturating_sub(1); state.set_current_cursor_pos(new_pos); *ideal_cursor_column = new_pos; Ok("".to_string()) } "move_right" => { let current_input = state.get_current_input(); let current_pos = state.current_cursor_pos(); if current_pos < current_input.len() { let new_pos = current_pos + 1; state.set_current_cursor_pos(new_pos); *ideal_cursor_column = new_pos; } Ok("".to_string()) } "move_up" => { let num_fields = state.fields().len(); if num_fields > 0 { let current_field = state.current_field(); let new_field = current_field.saturating_sub(1); state.set_current_field(new_field); let current_input = state.get_current_input(); let max_pos = current_input.len(); state.set_current_cursor_pos( (*ideal_cursor_column).min(max_pos), ); } Ok("".to_string()) } "move_down" => { let num_fields = state.fields().len(); if num_fields > 0 { let new_field = (state.current_field() + 1).min(num_fields - 1); state.set_current_field(new_field); let current_input = state.get_current_input(); let max_pos = current_input.len(); state.set_current_cursor_pos( (*ideal_cursor_column).min(max_pos), ); } Ok("".to_string()) } "move_line_start" => { state.set_current_cursor_pos(0); *ideal_cursor_column = 0; Ok("".to_string()) } "move_line_end" => { let current_input = state.get_current_input(); let new_pos = current_input.len(); state.set_current_cursor_pos(new_pos); *ideal_cursor_column = new_pos; Ok("".to_string()) } "move_first_line" => { let num_fields = state.fields().len(); if num_fields > 0 { state.set_current_field(0); let current_input = state.get_current_input(); let max_pos = current_input.len(); state.set_current_cursor_pos( (*ideal_cursor_column).min(max_pos), ); } Ok("Moved to first field".to_string()) } "move_last_line" => { let num_fields = state.fields().len(); if num_fields > 0 { let new_field = num_fields - 1; state.set_current_field(new_field); let current_input = state.get_current_input(); let max_pos = current_input.len(); state.set_current_cursor_pos( (*ideal_cursor_column).min(max_pos), ); } Ok("Moved to last field".to_string()) } "move_word_next" => { let current_input = state.get_current_input(); if !current_input.is_empty() { let new_pos = find_next_word_start( current_input, state.current_cursor_pos(), ); let final_pos = new_pos.min(current_input.len()); state.set_current_cursor_pos(final_pos); *ideal_cursor_column = final_pos; } Ok("".to_string()) } "move_word_end" => { let current_input = state.get_current_input(); if !current_input.is_empty() { let current_pos = state.current_cursor_pos(); let new_pos = find_word_end(current_input, current_pos); let final_pos = if new_pos == current_pos { find_word_end(current_input, new_pos + 1) } else { new_pos }; let max_valid_index = current_input.len().saturating_sub(1); let clamped_pos = final_pos.min(max_valid_index); state.set_current_cursor_pos(clamped_pos); *ideal_cursor_column = clamped_pos; } Ok("".to_string()) } "move_word_prev" => { let current_input = state.get_current_input(); if !current_input.is_empty() { let new_pos = find_prev_word_start( current_input, state.current_cursor_pos(), ); state.set_current_cursor_pos(new_pos); *ideal_cursor_column = new_pos; } Ok("".to_string()) } "move_word_end_prev" => { let current_input = state.get_current_input(); if !current_input.is_empty() { let new_pos = find_prev_word_end( current_input, state.current_cursor_pos(), ); state.set_current_cursor_pos(new_pos); *ideal_cursor_column = new_pos; } Ok("Moved to previous word end".to_string()) } _ => Ok(format!("Unknown or unhandled edit action: {}", action)), } } #[derive(PartialEq)] enum CharType { Whitespace, Alphanumeric, Punctuation, } fn get_char_type(c: char) -> CharType { if c.is_whitespace() { CharType::Whitespace } else if c.is_alphanumeric() { CharType::Alphanumeric } else { CharType::Punctuation } } fn find_next_word_start(text: &str, current_pos: usize) -> usize { let chars: Vec = text.chars().collect(); let len = chars.len(); if len == 0 || current_pos >= len { return len; } let mut pos = current_pos; let initial_type = get_char_type(chars[pos]); while pos < len && get_char_type(chars[pos]) == initial_type { pos += 1; } while pos < len && get_char_type(chars[pos]) == CharType::Whitespace { pos += 1; } pos } fn find_word_end(text: &str, current_pos: usize) -> usize { let chars: Vec = text.chars().collect(); let len = chars.len(); if len == 0 { return 0; } let mut pos = current_pos.min(len - 1); if get_char_type(chars[pos]) == CharType::Whitespace { pos = find_next_word_start(text, pos); } if pos >= len { return len.saturating_sub(1); } let word_type = get_char_type(chars[pos]); while pos < len && get_char_type(chars[pos]) == word_type { pos += 1; } pos.saturating_sub(1).min(len.saturating_sub(1)) } fn find_prev_word_start(text: &str, current_pos: usize) -> usize { let chars: Vec = text.chars().collect(); if chars.is_empty() || current_pos == 0 { return 0; } let mut pos = current_pos.saturating_sub(1); while pos > 0 && get_char_type(chars[pos]) == CharType::Whitespace { pos -= 1; } if pos == 0 && get_char_type(chars[pos]) == CharType::Whitespace { return 0; } let word_type = get_char_type(chars[pos]); while pos > 0 && get_char_type(chars[pos - 1]) == word_type { pos -= 1; } pos } fn find_prev_word_end(text: &str, current_pos: usize) -> usize { let chars: Vec = text.chars().collect(); let len = chars.len(); if len == 0 || current_pos == 0 { return 0; } let mut pos = current_pos.saturating_sub(1); while pos > 0 && get_char_type(chars[pos]) == CharType::Whitespace { pos -= 1; } if pos == 0 && get_char_type(chars[pos]) == CharType::Whitespace { return 0; } if pos == 0 && get_char_type(chars[pos]) != CharType::Whitespace { return 0; } let word_type = get_char_type(chars[pos]); while pos > 0 && get_char_type(chars[pos - 1]) == word_type { pos -= 1; } while pos > 0 && get_char_type(chars[pos - 1]) == CharType::Whitespace { pos -= 1; } if pos > 0 { pos - 1 } else { 0 } }