From 3dc5a89459aff928bfe697f33295e722be876259 Mon Sep 17 00:00:00 2001 From: filipriec Date: Thu, 3 Apr 2025 23:23:25 +0200 Subject: [PATCH] splitting modes into functions and how it works vs logic to handle it all --- client/src/functions/modes/edit.rs | 4 + client/src/functions/modes/edit/auth_e.rs | 0 client/src/functions/modes/edit/form_e.rs | 431 ++++++++++++++++++++++ client/src/modes/canvas/edit.rs | 427 +-------------------- 4 files changed, 442 insertions(+), 420 deletions(-) create mode 100644 client/src/functions/modes/edit/auth_e.rs create mode 100644 client/src/functions/modes/edit/form_e.rs diff --git a/client/src/functions/modes/edit.rs b/client/src/functions/modes/edit.rs index e69de29..dd8db60 100644 --- a/client/src/functions/modes/edit.rs +++ b/client/src/functions/modes/edit.rs @@ -0,0 +1,4 @@ +// src/functions/modes/edit.rs + +pub mod form_e; +pub mod auth_e; diff --git a/client/src/functions/modes/edit/auth_e.rs b/client/src/functions/modes/edit/auth_e.rs new file mode 100644 index 0000000..e69de29 diff --git a/client/src/functions/modes/edit/form_e.rs b/client/src/functions/modes/edit/form_e.rs new file mode 100644 index 0000000..6e4c730 --- /dev/null +++ b/client/src/functions/modes/edit/form_e.rs @@ -0,0 +1,431 @@ +// src/functions/modes/edit/form_e.rs + +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::functions::common::form::{revert, save}; +use crossterm::event::{KeyCode, KeyEvent}; +use std::any::Any; + +pub async fn execute_common_action( + action: &str, + state: &mut S, + grpc_client: &mut GrpcClient, + is_saved: &mut bool, + 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" => { + 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(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, + _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); + 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 = 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_down" => { + let num_fields = state.fields().len(); + if num_fields > 0 { + let new_field = (state.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()) + } + + "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 0a0a4ae..fb712e2 100644 --- a/client/src/modes/canvas/edit.rs +++ b/client/src/modes/canvas/edit.rs @@ -1,8 +1,10 @@ +// src/modes/canvas/edit.rs + 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::functions::common::form::{revert, save}; +use crate::functions::modes::edit::form_e; use crossterm::event::{KeyCode, KeyEvent}; use std::any::Any; @@ -34,7 +36,7 @@ pub async fn handle_edit_event_internal( key.modifiers, ) { if action == "save" || action == "revert" { - return execute_common_action( + return form_e::execute_common_action( action, state, grpc_client, @@ -46,10 +48,8 @@ pub async fn handle_edit_event_internal( } } - if let Some(action) = - config.get_edit_action_for_key(key.code, key.modifiers) - { - return execute_edit_action( + if let Some(action) = config.get_edit_action_for_key(key.code, key.modifiers) { + return form_e::execute_edit_action( action, key, state, @@ -63,7 +63,7 @@ pub async fn handle_edit_event_internal( } if let KeyCode::Char(c) = key.code { - return execute_edit_action( + return form_e::execute_edit_action( "insert_char", key, state, @@ -78,416 +78,3 @@ pub async fn handle_edit_event_internal( Ok(command_message.clone()) } - -async fn execute_common_action( - action: &str, - state: &mut S, - grpc_client: &mut GrpcClient, - is_saved: &mut bool, - 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" => { - 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(format!("Common action '{}' not handled here.", action)), - } -} - -async fn execute_edit_action( - action: &str, - key: KeyEvent, - state: &mut S, - ideal_cursor_column: &mut usize, - _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); - 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 = 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_down" => { - let num_fields = state.fields().len(); - if num_fields > 0 { - let new_field = (state.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()) - } - "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); - - // If already at word end, jump to next word's end - 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 already at whitespace, find next word first - if get_char_type(chars[pos]) == CharType::Whitespace { - pos = find_next_word_start(text, pos); - } - - // Now find end of current/next word - 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; - } - - // Return last character of the word - 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 - } -} -