// src/modes/handlers/edit.rs use crossterm::event::{KeyEvent, KeyCode, KeyModifiers}; use crate::tui::terminal::AppTerminal; use crate::config::config::Config; use crate::ui::handlers::form::FormState; use crate::config::key_sequences::KeySequenceTracker; use super::common; // This function is called from event.rs and doesn't need to handle mode transitions anymore pub async fn handle_edit_event_internal( key: KeyEvent, config: &Config, form_state: &mut FormState, ideal_cursor_column: &mut usize, command_message: &mut String, app_terminal: &mut AppTerminal, // Add these parameters is_saved: &mut bool, current_position: &mut u64, total_count: u64, ) -> Result> { if let Some(action) = config.get_edit_action_for_key(key.code, key.modifiers) { return execute_edit_action( action, form_state, ideal_cursor_column, app_terminal, // Pass them here is_saved, current_position, total_count, ).await; } // If no Edit mode action is found, handle fallback behavior handle_edit_specific_input( key, form_state, ideal_cursor_column, ); Ok(command_message.clone()) } // Handle edit-specific key input as a fallback (character input, backspace, delete) fn handle_edit_specific_input( key: KeyEvent, form_state: &mut FormState, ideal_cursor_column: &mut usize, ) { // This is now explicitly a fallback function for default edit behavior match key.code { KeyCode::Char(c) => { // Character input 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 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; } } KeyCode::Backspace => { // Delete character 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 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; } } } KeyCode::Delete => { // Delete character 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(); 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; } } KeyCode::Tab => { // Tab key special handling if key.modifiers.contains(KeyModifiers::SHIFT) { 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); } } else { 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); } KeyCode::Enter => { // Enter key moves to 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); } _ => {} // Ignore other keys } } async fn execute_edit_action( action: &str, form_state: &mut FormState, ideal_cursor_column: &mut usize, app_terminal: &mut AppTerminal, is_saved: &mut bool, current_position: &mut u64, total_count: u64, ) -> Result> { match action { "save" => { common::save( form_state, app_terminal, is_saved, current_position, total_count, ).await }, // Navigation actions "move_left" => { form_state.current_cursor_pos = form_state.current_cursor_pos.saturating_sub(1); *ideal_cursor_column = form_state.current_cursor_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; } 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 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); Ok("".to_string()) } "move_line_start" => { form_state.current_cursor_pos = 0; *ideal_cursor_column = form_state.current_cursor_pos; 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; 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); 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); Ok("Moved to last line".to_string()) } // Word movement actions "move_word_next" => { let current_input = form_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; } Ok("".to_string()) } "move_word_end" => { let current_input = form_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; } Ok("".to_string()) } "move_word_prev" => { let current_input = form_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; } Ok("".to_string()) } "move_word_end_prev" => { let current_input = form_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; } Ok("".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(); 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; } 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 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; } } 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()) } "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); 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 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)), } } // Reuse these character and word navigation helper functions #[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(); if chars.is_empty() || current_pos >= chars.len() { return current_pos; } let mut pos = current_pos; let initial_type = get_char_type(chars[pos]); while pos < chars.len() && get_char_type(chars[pos]) == initial_type { pos += 1; } while pos < chars.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(); if chars.is_empty() { return 0; } if current_pos >= chars.len() - 1 { return chars.len() - 1; } let mut pos = current_pos; if get_char_type(chars[pos]) == CharType::Whitespace { while pos < chars.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 >= 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 { pos += 1; } pos } 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 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; } } pos } fn find_prev_word_end(text: &str, current_pos: usize) -> usize { let chars: Vec = text.chars().collect(); if chars.is_empty() || current_pos <= 1 { 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 { 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; } } } pos }