diff --git a/client/src/functions/modes.rs b/client/src/functions/modes.rs index 853187f..1b94400 100644 --- a/client/src/functions/modes.rs +++ b/client/src/functions/modes.rs @@ -1,7 +1,5 @@ // src/functions/modes.rs -pub mod edit; pub mod navigation; -pub use edit::*; pub use navigation::*; diff --git a/client/src/functions/modes/edit.rs b/client/src/functions/modes/edit.rs deleted file mode 100644 index 3bda6b2..0000000 --- a/client/src/functions/modes/edit.rs +++ /dev/null @@ -1,6 +0,0 @@ -// src/functions/modes/edit.rs - -pub mod form_e; -pub mod auth_e; -pub mod add_table_e; -pub mod add_logic_e; diff --git a/client/src/functions/modes/edit/add_logic_e.rs b/client/src/functions/modes/edit/add_logic_e.rs deleted file mode 100644 index 98c006b..0000000 --- a/client/src/functions/modes/edit/add_logic_e.rs +++ /dev/null @@ -1,135 +0,0 @@ -// src/functions/modes/edit/add_logic_e.rs -use crate::state::pages::add_logic::AddLogicState; -use canvas::canvas::CanvasState; -use anyhow::Result; -use crossterm::event::{KeyCode, KeyEvent}; - -pub async fn execute_edit_action( - action: &str, - key: KeyEvent, // Keep key for insert_char - state: &mut AddLogicState, - ideal_cursor_column: &mut usize, -) -> Result { - let mut message = String::new(); - - match action { - "next_field" => { - let current_field = state.current_field(); - let next_field = (current_field + 1) % AddLogicState::INPUT_FIELD_COUNT; - state.set_current_field(next_field); - *ideal_cursor_column = state.current_cursor_pos(); - message = format!("Focus on field {}", state.fields()[next_field]); - } - "prev_field" => { - let current_field = state.current_field(); - let prev_field = if current_field == 0 { - AddLogicState::INPUT_FIELD_COUNT - 1 - } else { - current_field - 1 - }; - state.set_current_field(prev_field); - *ideal_cursor_column = state.current_cursor_pos(); - message = format!("Focus on field {}", state.fields()[prev_field]); - } - "delete_char_forward" => { - let current_pos = state.current_cursor_pos(); - let current_input_mut = state.get_current_input_mut(); - if current_pos < current_input_mut.len() { - current_input_mut.remove(current_pos); - state.set_has_unsaved_changes(true); - if state.current_field() == 1 { state.update_target_column_suggestions(); } - } - } - "delete_char_backward" => { - let current_pos = state.current_cursor_pos(); - if current_pos > 0 { - let new_pos = current_pos - 1; - state.get_current_input_mut().remove(new_pos); - state.set_current_cursor_pos(new_pos); - *ideal_cursor_column = new_pos; - state.set_has_unsaved_changes(true); - if state.current_field() == 1 { state.update_target_column_suggestions(); } - } - } - "move_left" => { - let current_pos = state.current_cursor_pos(); - if current_pos > 0 { - let new_pos = current_pos - 1; - state.set_current_cursor_pos(new_pos); - *ideal_cursor_column = new_pos; - } - } - "move_right" => { - let current_pos = state.current_cursor_pos(); - let input_len = state.get_current_input().len(); - if current_pos < input_len { - let new_pos = current_pos + 1; - state.set_current_cursor_pos(new_pos); - *ideal_cursor_column = new_pos; - } - } - "insert_char" => { - if let KeyCode::Char(c) = key.code { - let current_pos = state.current_cursor_pos(); - state.get_current_input_mut().insert(current_pos, c); - let new_pos = current_pos + 1; - state.set_current_cursor_pos(new_pos); - *ideal_cursor_column = new_pos; - state.set_has_unsaved_changes(true); - if state.current_field() == 1 { - state.update_target_column_suggestions(); - } - } - } - "suggestion_down" => { - if state.in_target_column_suggestion_mode && !state.target_column_suggestions.is_empty() { - let current_selection = state.selected_target_column_suggestion_index.unwrap_or(0); - let next_selection = (current_selection + 1) % state.target_column_suggestions.len(); - state.selected_target_column_suggestion_index = Some(next_selection); - } - } - "suggestion_up" => { - if state.in_target_column_suggestion_mode && !state.target_column_suggestions.is_empty() { - let current_selection = state.selected_target_column_suggestion_index.unwrap_or(0); - let prev_selection = if current_selection == 0 { - state.target_column_suggestions.len() - 1 - } else { - current_selection - 1 - }; - state.selected_target_column_suggestion_index = Some(prev_selection); - } - } - "select_suggestion" => { - if state.in_target_column_suggestion_mode { - let mut selected_suggestion_text: Option = None; - - if let Some(selected_idx) = state.selected_target_column_suggestion_index { - if let Some(suggestion) = state.target_column_suggestions.get(selected_idx) { - selected_suggestion_text = Some(suggestion.clone()); - } - } - - if let Some(suggestion_text) = selected_suggestion_text { - state.target_column_input = suggestion_text.clone(); - state.target_column_cursor_pos = state.target_column_input.len(); - *ideal_cursor_column = state.target_column_cursor_pos; - state.set_has_unsaved_changes(true); - message = format!("Selected column: '{}'", suggestion_text); - } - - state.in_target_column_suggestion_mode = false; - state.show_target_column_suggestions = false; - state.selected_target_column_suggestion_index = None; - state.update_target_column_suggestions(); - } else { - let current_field = state.current_field(); - let next_field = (current_field + 1) % AddLogicState::INPUT_FIELD_COUNT; - state.set_current_field(next_field); - *ideal_cursor_column = state.current_cursor_pos(); - message = format!("Focus on field {}", state.fields()[next_field]); - } - } - _ => {} - } - Ok(message) -} diff --git a/client/src/functions/modes/edit/add_table_e.rs b/client/src/functions/modes/edit/add_table_e.rs deleted file mode 100644 index 7da0c70..0000000 --- a/client/src/functions/modes/edit/add_table_e.rs +++ /dev/null @@ -1,341 +0,0 @@ -// src/functions/modes/edit/add_table_e.rs -use crate::state::pages::add_table::AddTableState; -use crossterm::event::{KeyCode, KeyEvent}; -use canvas::canvas::CanvasState; -use anyhow::Result; - -#[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 - } -} - -/// Executes edit actions for the AddTable view canvas. -pub async fn execute_edit_action( - action: &str, - key: KeyEvent, // Needed for insert_char - state: &mut AddTableState, - ideal_cursor_column: &mut usize, - // Add other params like grpc_client if needed for future actions (e.g., validation) -) -> Result { - // Use the CanvasState trait methods implemented for AddTableState - 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()) // No message needed for char insertion - } - "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 = AddTableState::INPUT_FIELD_COUNT; - if num_fields > 0 { - let current_field = state.current_field(); - let last_field_index = num_fields - 1; - // Prevent cycling forward - if current_field < last_field_index { - state.set_current_field(current_field + 1); - } - 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 = AddTableState::INPUT_FIELD_COUNT; - if num_fields > 0 { - let current_field = state.current_field(); - if current_field > 0 { - state.set_current_field(current_field - 1); - } - 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 current_field = state.current_field(); - // Prevent moving up from the first field - if current_field > 0 { - let new_field = 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("ahoj".to_string()) - } - "move_down" => { - let num_fields = AddTableState::INPUT_FIELD_COUNT; - if num_fields > 0 { - let current_field = state.current_field(); - let last_field_index = num_fields - 1; - if current_field < last_field_index { - let new_field = 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_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" => { - if AddTableState::INPUT_FIELD_COUNT > 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("".to_string()) - } - "move_last_line" => { - let num_fields = AddTableState::INPUT_FIELD_COUNT; - 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("".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("".to_string()) - } - // Actions handled by main event loop (mode changes, save, revert) - "exit_edit_mode" | "save" | "revert" => { - Ok("Action handled by main loop".to_string()) - } - _ => Ok(format!("Unknown or unhandled edit action: {}", action)), - } -} diff --git a/client/src/functions/modes/edit/auth_e.rs b/client/src/functions/modes/edit/auth_e.rs deleted file mode 100644 index b9febb1..0000000 --- a/client/src/functions/modes/edit/auth_e.rs +++ /dev/null @@ -1,466 +0,0 @@ -// 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 - } -} diff --git a/client/src/functions/modes/edit/form_e.rs b/client/src/functions/modes/edit/form_e.rs deleted file mode 100644 index 0eefc4a..0000000 --- a/client/src/functions/modes/edit/form_e.rs +++ /dev/null @@ -1,435 +0,0 @@ -// 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 - } -} diff --git a/client/src/modes/canvas/edit.rs b/client/src/modes/canvas/edit.rs index ab14c60..9db2f2a 100644 --- a/client/src/modes/canvas/edit.rs +++ b/client/src/modes/canvas/edit.rs @@ -1,8 +1,5 @@ // src/modes/canvas/edit.rs use crate::config::binds::config::Config; -use crate::functions::modes::edit::{ - add_logic_e, add_table_e, form_e, -}; use crate::modes::handlers::event::EventHandler; use crate::services::grpc_client::GrpcClient; use crate::state::app::state::AppState; @@ -127,6 +124,23 @@ pub async fn handle_form_edit_with_canvas( Ok(String::new()) } +/// Helper function to execute a specific action using canvas library +async fn execute_canvas_action( + action: &str, + key: KeyEvent, + form_state: &mut FormState, + ideal_cursor_column: &mut usize, +) -> Result { + let canvas_action = CanvasAction::from_string(action); + match ActionDispatcher::dispatch(canvas_action, form_state, ideal_cursor_column).await { + Ok(ActionResult::Success(msg)) => Ok(msg.unwrap_or_default()), + Ok(ActionResult::HandledByFeature(msg)) => Ok(msg), + Ok(ActionResult::Error(msg)) => Ok(format!("Error: {}", msg)), + Ok(ActionResult::RequiresContext(msg)) => Ok(format!("Context needed: {}", msg)), + Err(e) => Ok(format!("Action failed: {}", e)), + } +} + /// NEW: Unified canvas action handler for any CanvasState (LoginState, RegisterState, etc.) /// This replaces the old auth_e::execute_edit_action calls with the new canvas library async fn handle_canvas_state_edit( @@ -291,8 +305,8 @@ pub async fn handle_edit_event( } else { "insert_char" }; - // FIX: Pass &mut event_handler.ideal_cursor_column - form_e::execute_edit_action( + // FIXED: Use canvas library instead of form_e::execute_edit_action + execute_canvas_action( action, key, form_state, @@ -321,8 +335,8 @@ pub async fn handle_edit_event( { // Handle Enter key (next field) if action_str == "enter_decider" { - // FIX: Pass &mut event_handler.ideal_cursor_column - let msg = form_e::execute_edit_action( + // FIXED: Use canvas library instead of form_e::execute_edit_action + let msg = execute_canvas_action( "next_field", key, form_state, @@ -375,8 +389,8 @@ pub async fn handle_edit_event( ) .await? } else { - // FIX: Pass &mut event_handler.ideal_cursor_column - form_e::execute_edit_action( + // FIXED: Use canvas library instead of form_e::execute_edit_action + execute_canvas_action( action_str, key, form_state, @@ -426,8 +440,8 @@ pub async fn handle_edit_event( ) .await? } else { - // FIX: Pass &mut event_handler.ideal_cursor_column - form_e::execute_edit_action( + // FIXED: Use canvas library instead of form_e::execute_edit_action + execute_canvas_action( "insert_char", key, form_state, diff --git a/client/src/modes/canvas/read_only.rs b/client/src/modes/canvas/read_only.rs index afd0058..e456c0f 100644 --- a/client/src/modes/canvas/read_only.rs +++ b/client/src/modes/canvas/read_only.rs @@ -53,6 +53,37 @@ async fn dispatch_to_active_state( } } +/// Helper function to handle context-specific actions that need special treatment +async fn handle_context_action( + action: &str, + app_state: &AppState, + form_state: &mut FormState, + grpc_client: &mut GrpcClient, + ideal_cursor_column: &mut usize, +) -> Result> { + const CONTEXT_ACTIONS_FORM: &[&str] = &[ + "previous_entry", + "next_entry", + ]; + const CONTEXT_ACTIONS_LOGIN: &[&str] = &[ + "previous_entry", + "next_entry", + ]; + + if app_state.ui.show_form && CONTEXT_ACTIONS_FORM.contains(&action) { + Ok(Some(crate::tui::functions::form::handle_action( + action, + form_state, + grpc_client, + ideal_cursor_column, + ).await?)) + } else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { + Ok(Some(crate::tui::functions::login::handle_action(action).await?)) + } else { + Ok(None) // Not a context action, use regular canvas dispatch + } +} + pub async fn handle_form_readonly_with_canvas( key_event: KeyEvent, config: &Config, @@ -171,32 +202,21 @@ pub async fn handle_read_only_event( return Ok((false, command_message.clone())); } - const CONTEXT_ACTIONS_FORM: &[&str] = &[ - "previous_entry", - "next_entry", - ]; - const CONTEXT_ACTIONS_LOGIN: &[&str] = &[ - "previous_entry", - "next_entry", - ]; - if key.modifiers.is_empty() { key_sequence_tracker.add_key(key.code); let sequence = key_sequence_tracker.get_sequence(); if let Some(action) = config.matches_key_sequence_generalized(&sequence).as_deref() { - let result = if app_state.ui.show_form && CONTEXT_ACTIONS_FORM.contains(&action) { - crate::tui::functions::form::handle_action( - action, - form_state, - grpc_client, - ideal_cursor_column, - ) - .await? - } else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { - crate::tui::functions::login::handle_action(action).await? + // Try context-specific actions first, otherwise use canvas dispatch + let result = if let Some(context_result) = handle_context_action( + action, + app_state, + form_state, + grpc_client, + ideal_cursor_column, + ).await? { + context_result } else { - // All canvas states handled uniformly dispatch_to_active_state( action, app_state, @@ -218,18 +238,16 @@ pub async fn handle_read_only_event( 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).as_deref() { - let result = if app_state.ui.show_form && CONTEXT_ACTIONS_FORM.contains(&action) { - crate::tui::functions::form::handle_action( - action, - form_state, - grpc_client, - ideal_cursor_column, - ) - .await? - } else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { - crate::tui::functions::login::handle_action(action).await? + // Try context-specific actions first, otherwise use canvas dispatch + let result = if let Some(context_result) = handle_context_action( + action, + app_state, + form_state, + grpc_client, + ideal_cursor_column, + ).await? { + context_result } else { - // All canvas states handled uniformly dispatch_to_active_state( action, app_state, @@ -250,18 +268,16 @@ pub async fn handle_read_only_event( key_sequence_tracker.reset(); if let Some(action) = config.get_read_only_action_for_key(key.code, key.modifiers).as_deref() { - let result = if app_state.ui.show_form && CONTEXT_ACTIONS_FORM.contains(&action) { - crate::tui::functions::form::handle_action( - action, - form_state, - grpc_client, - ideal_cursor_column, - ) - .await? - } else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { - crate::tui::functions::login::handle_action(action).await? + // Try context-specific actions first, otherwise use canvas dispatch + let result = if let Some(context_result) = handle_context_action( + action, + app_state, + form_state, + grpc_client, + ideal_cursor_column, + ).await? { + context_result } else { - // All canvas states handled uniformly dispatch_to_active_state( action, app_state,