diff --git a/client/src/modes/canvas/edit.rs b/client/src/modes/canvas/edit.rs index 4afd96f..449b685 100644 --- a/client/src/modes/canvas/edit.rs +++ b/client/src/modes/canvas/edit.rs @@ -1,14 +1,10 @@ -// 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::services::grpc_client::GrpcClient; use crate::state::canvas_state::CanvasState; use crate::state::pages::form::FormState; -use crate::services::grpc_client::GrpcClient; -use crate::tui::functions::common::form::{save, revert}; +use crate::tui::functions::common::form::{revert, save}; +use crossterm::event::{KeyCode, KeyEvent}; +use std::any::Any; pub async fn handle_edit_event_internal( key: KeyEvent, @@ -21,35 +17,65 @@ pub async fn handle_edit_event_internal( total_count: u64, 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) { - handle_edit_specific_input(key, state, ideal_cursor_column); - return Ok(command_message.clone()); + if let Some(action) = config.get_action_for_key_in_mode( + &config.keybindings.global, + key.code, + key.modifiers, + ) { + if action == "enter_command_mode" { + *command_message = "Switching to Command Mode...".to_string(); + return Ok(command_message.clone()); + } } - if let Some(action) = config.get_action_for_key_in_mode(&config.keybindings.common, key.code, key.modifiers) { - return execute_common_action( - action, - state, - grpc_client, - is_saved, - current_position, - total_count, - ).await; + if let Some(action) = config.get_action_for_key_in_mode( + &config.keybindings.common, + key.code, + key.modifiers, + ) { + if action == "save" || action == "revert" { + return execute_common_action( + action, + state, + grpc_client, + is_saved, + current_position, + total_count, + ) + .await; + } } - if let Some(action) = config.get_edit_action_for_key(key.code, key.modifiers) { + if let Some(action) = + config.get_edit_action_for_key(key.code, key.modifiers) + { return execute_edit_action( action, + key, state, ideal_cursor_column, grpc_client, is_saved, current_position, total_count, - ).await; + ) + .await; + } + + if let KeyCode::Char(c) = key.code { + return execute_edit_action( + "insert_char", + key, + state, + ideal_cursor_column, + grpc_client, + is_saved, + current_position, + total_count, + ) + .await; } - handle_edit_specific_input(key, state, ideal_cursor_column); Ok(command_message.clone()) } @@ -62,127 +88,139 @@ async fn execute_common_action( total_count: u64, ) -> Result> { match action { - "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!(), - }; + "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" => { + save( + form_state, + grpc_client, + is_saved, + current_position, + total_count, + ) + .await + } + "revert" => { + revert( + form_state, + grpc_client, + current_position, + total_count, + ) + .await + } + _ => unreachable!(), + } + } else { + Ok(format!( + "Action '{}' not implemented for this state type.", + action + )) } - Ok("Action not available in this context".to_string()) } - "move_up" | "move_down" => { - execute_edit_action( - action, - state, - &mut 0, - grpc_client, - is_saved, - current_position, - total_count, - ).await - } - _ => Ok(format!("Common action not handled: {}", action)), + _ => Ok(format!("Common action '{}' not handled here.", action)), } } -fn handle_edit_specific_input( // No Any needed here +async fn execute_edit_action( + action: &str, key: KeyEvent, state: &mut S, ideal_cursor_column: &mut usize, -) { - match key.code { - KeyCode::Char(c) => { - 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(); - // 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 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(); - // 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 = 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(); - // 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 => { - 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 { - (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)); - } - } - KeyCode::Enter => { - 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( // No Any needed here - action: &str, - state: &mut S, - ideal_cursor_column: &mut usize, - // 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 { + "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); - // Use trait setter state.set_current_cursor_pos(new_pos); *ideal_cursor_column = new_pos; Ok("".to_string()) @@ -190,10 +228,8 @@ async fn execute_edit_action( // No Any needed here "move_right" => { 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; } @@ -203,31 +239,34 @@ async fn execute_edit_action( // No Any needed here 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 + 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(); - // Use trait setter - state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos)); + 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 { + 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)); + state.set_current_cursor_pos( + (*ideal_cursor_column).min(max_pos), + ); } Ok("".to_string()) } "move_line_start" => { - // Use trait setter state.set_current_cursor_pos(0); *ideal_cursor_column = 0; Ok("".to_string()) @@ -235,42 +274,43 @@ async fn execute_edit_action( // No Any needed here "move_line_end" => { 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" => { - let num_fields = state.fields().len(); - if num_fields > 0 { - // Use trait setter + 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(); - // Use trait setter - state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos)); + state.set_current_cursor_pos( + (*ideal_cursor_column).min(max_pos), + ); } - Ok("Moved to first line".to_string()) + Ok("Moved to first field".to_string()) } "move_last_line" => { let num_fields = state.fields().len(); - if num_fields > 0 { + 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)); + state.set_current_cursor_pos( + (*ideal_cursor_column).min(max_pos), + ); } - Ok("Moved to last line".to_string()) + 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 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; } @@ -279,9 +319,9 @@ async fn execute_edit_action( // No Any needed here "move_word_end" => { let current_input = state.get_current_input(); if !current_input.is_empty() { - let new_pos = find_word_end(current_input, 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; } @@ -290,8 +330,10 @@ async fn execute_edit_action( // No Any needed here "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()); - // Use trait setter + 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; } @@ -300,77 +342,17 @@ async fn execute_edit_action( // No Any needed here "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()); - // Use trait setter + 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()) } - "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(); // Use mut for modification - if cursor_pos < chars.len() { - 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 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(); - 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 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" => { - 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" => { - 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)); - } - Ok("".to_string()) - } - _ => Ok(format!("Unknown edit action: {}", action)), + + _ => Ok(format!("Unknown or unhandled edit action: {}", action)), } } @@ -393,17 +375,19 @@ fn get_char_type(c: char) -> CharType { fn find_next_word_start(text: &str, current_pos: usize) -> usize { let chars: Vec = text.chars().collect(); - if chars.is_empty() || current_pos >= chars.len() { - return current_pos; + 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 < chars.len() && get_char_type(chars[pos]) == initial_type { + + while pos < len && get_char_type(chars[pos]) == initial_type { pos += 1; } - while pos < chars.len() && get_char_type(chars[pos]) == CharType::Whitespace { + while pos < len && get_char_type(chars[pos]) == CharType::Whitespace { pos += 1; } @@ -412,40 +396,35 @@ fn find_next_word_start(text: &str, current_pos: usize) -> usize { fn find_word_end(text: &str, current_pos: usize) -> usize { let chars: Vec = text.chars().collect(); - if chars.is_empty() { + let len = chars.len(); + if len == 0 { return 0; } - - if current_pos >= chars.len() - 1 { - return chars.len() - 1; + if current_pos >= len { + return len; } let mut pos = current_pos; if get_char_type(chars[pos]) == CharType::Whitespace { - while pos < chars.len() && get_char_type(chars[pos]) == CharType::Whitespace { + while pos < len && get_char_type(chars[pos]) == CharType::Whitespace { pos += 1; } - } else { - let current_type = get_char_type(chars[pos]); - if pos + 1 < chars.len() && get_char_type(chars[pos + 1]) != current_type { - pos += 1; - while pos < chars.len() && get_char_type(chars[pos]) == CharType::Whitespace { - pos += 1; - } + if pos == len { + return len; } } - if pos >= chars.len() { - return chars.len() - 1; - } - let word_type = get_char_type(chars[pos]); - while pos + 1 < chars.len() && get_char_type(chars[pos + 1]) == word_type { + while pos < len && get_char_type(chars[pos]) == word_type { pos += 1; } - pos + if pos > current_pos && pos > 0 { + pos - 1 + } else { + current_pos.min(len.saturating_sub(1)) + } } fn find_prev_word_start(text: &str, current_pos: usize) -> usize { @@ -460,11 +439,13 @@ fn find_prev_word_start(text: &str, current_pos: usize) -> usize { pos -= 1; } - if get_char_type(chars[pos]) != CharType::Whitespace { - let word_type = get_char_type(chars[pos]); - while pos > 0 && get_char_type(chars[pos - 1]) == word_type { - 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 @@ -472,7 +453,8 @@ fn find_prev_word_start(text: &str, current_pos: usize) -> usize { fn find_prev_word_end(text: &str, current_pos: usize) -> usize { let chars: Vec = text.chars().collect(); - if chars.is_empty() || current_pos <= 1 { + let len = chars.len(); + if len == 0 || current_pos == 0 { return 0; } @@ -482,30 +464,26 @@ fn find_prev_word_end(text: &str, current_pos: usize) -> usize { pos -= 1; } - if pos > 0 && get_char_type(chars[pos]) != CharType::Whitespace { - 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; - let prev_word_type = get_char_type(chars[pos]); - while pos > 0 && get_char_type(chars[pos - 1]) == prev_word_type { - pos -= 1; - } - - while pos < chars.len() - 1 && - get_char_type(chars[pos + 1]) == prev_word_type { - 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; } - pos + 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 + } } +