// src/functions/modes/edit/auth_e.rs use crate::services::grpc_client::GrpcClient; use crate::state::pages::form::FormState; use crate::state::pages::auth::RegisterState; use crate::state::app::state::AppState; use crate::tui::functions::common::form::{revert, save}; use crossterm::event::{KeyCode, KeyEvent}; use canvas::autocomplete::AutocompleteCanvasState; 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, current_position: &mut u64, total_count: u64, ) -> Result { match action { "save" | "revert" => { if !state.has_unsaved_changes() { return 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 outcome = save( app_state, form_state, grpc_client, ) .await?; let message = format!("Save successful: {:?}", outcome); // Simple message for now Ok(message) } "revert" => { revert( form_state, grpc_client, ) .await } _ => unreachable!(), } } else { Ok(format!( "Action '{}' not implemented for this state type.", action )) } } _ => 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("working?".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).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()) } "prev_field" => { 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_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()) } // --- Autocomplete Actions --- "suggestion_down" | "suggestion_up" | "select_suggestion" | "exit_suggestion_mode" => { // Attempt to downcast to RegisterState to handle suggestion logic here if let Some(register_state) = (state as &mut dyn Any).downcast_mut::() { // Only handle if it's the role field (index 4) and autocomplete is active if register_state.current_field() == 4 && register_state.is_autocomplete_active() { match action { "suggestion_down" => { if let Some(autocomplete_state) = register_state.autocomplete_state_mut() { autocomplete_state.select_next(); Ok("Suggestion changed down".to_string()) } else { Ok("No autocomplete state".to_string()) } } "suggestion_up" => { if let Some(autocomplete_state) = register_state.autocomplete_state_mut() { autocomplete_state.select_previous(); Ok("Suggestion changed up".to_string()) } else { Ok("No autocomplete state".to_string()) } } "select_suggestion" => { if let Some(message) = register_state.apply_autocomplete_selection() { Ok(message) } else { Ok("No suggestion selected".to_string()) } } "exit_suggestion_mode" => { register_state.deactivate_autocomplete(); Ok("Suggestions hidden".to_string()) } _ => Ok("Suggestion action ignored: State mismatch.".to_string()) } } else { Ok("Suggestion action ignored: Not on role field or autocomplete not active.".to_string()) } } else { Ok(format!("Action '{}' not applicable for this state type.", action)) } } // --- End Autocomplete Actions --- _ => 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 } }