From 07aeecbbfc54c4aff894f6a35e457e47c1811e30 Mon Sep 17 00:00:00 2001 From: filipriec Date: Thu, 3 Apr 2025 19:43:59 +0200 Subject: [PATCH] functions created and now working, lets move modes functions here now --- client/src/functions/modes.rs | 7 + client/src/functions/modes/edit.rs | 0 client/src/functions/modes/read_only.rs | 4 + .../src/functions/modes/read_only/auth_ro.rs | 292 ++++++++++++++++++ .../src/functions/modes/read_only/form_ro.rs | 260 ++++++++++++++++ 5 files changed, 563 insertions(+) create mode 100644 client/src/functions/modes/edit.rs create mode 100644 client/src/functions/modes/read_only.rs create mode 100644 client/src/functions/modes/read_only/auth_ro.rs create mode 100644 client/src/functions/modes/read_only/form_ro.rs diff --git a/client/src/functions/modes.rs b/client/src/functions/modes.rs index 6cdb28b..5618a06 100644 --- a/client/src/functions/modes.rs +++ b/client/src/functions/modes.rs @@ -1 +1,8 @@ // src/functions/modes.rs + +pub mod read_only; +pub mod edit; + + +pub use read_only::*; +pub use edit::*; diff --git a/client/src/functions/modes/edit.rs b/client/src/functions/modes/edit.rs new file mode 100644 index 0000000..e69de29 diff --git a/client/src/functions/modes/read_only.rs b/client/src/functions/modes/read_only.rs new file mode 100644 index 0000000..04920b4 --- /dev/null +++ b/client/src/functions/modes/read_only.rs @@ -0,0 +1,4 @@ +// src/functions/modes/read_only.rs + +pub mod auth_ro; +pub mod form_ro; diff --git a/client/src/functions/modes/read_only/auth_ro.rs b/client/src/functions/modes/read_only/auth_ro.rs new file mode 100644 index 0000000..c0892f7 --- /dev/null +++ b/client/src/functions/modes/read_only/auth_ro.rs @@ -0,0 +1,292 @@ +// src/functions/modes/read_only/auth_fun.rs + +use crate::config::binds::key_sequences::KeySequenceTracker; +use crate::state::canvas_state::CanvasState; +use crate::state::pages::auth::AuthState; + +// --- Context-Specific Actions Handled Here --- +const CONTEXT_ACTIONS_LOGIN: &[&str] = &[ + "move_up", + "move_down", + "move_first_line", + "move_last_line", +]; + +pub async fn handle_action( + action: &str, + auth_state: &mut AuthState, + ideal_cursor_column: &mut usize, + key_sequence_tracker: &mut KeySequenceTracker, + command_message: &mut String, +) -> Result> { + if CONTEXT_ACTIONS_LOGIN.contains(&action) { + // Delegate context-specific actions to the original handler + // (or implement the logic directly here if preferred) + crate::tui::functions::login::handle_action( + action, + auth_state, + ideal_cursor_column, + ) + .await + } else { + // Handle generic actions using the local execute_action + execute_action( + action, + auth_state, + ideal_cursor_column, + key_sequence_tracker, + command_message, + ) + .await + } +} + +// --- Generic Action Implementation (Copied and made private) --- + +#[derive(PartialEq)] +enum CharType { + Whitespace, + Alphanumeric, + Punctuation, +} + +async fn execute_action( + action: &str, + state: &mut S, + ideal_cursor_column: &mut usize, + key_sequence_tracker: &mut KeySequenceTracker, + command_message: &mut String, +) -> Result> { + match action { + // Context actions are handled above + "move_up" | "move_down" | "move_first_line" | "move_last_line" => { + key_sequence_tracker.reset(); + Ok(format!( + "Action '{}' should be handled by context-specific logic", + action + )) + } + // These form-specific actions don't apply to login + "previous_entry" | "next_entry" => { + key_sequence_tracker.reset(); + Ok(format!("Action '{}' not applicable in login context", action)) + } + "exit_edit_mode" => { + key_sequence_tracker.reset(); + command_message.clear(); + Ok("".to_string()) + } + "move_left" => { + let current_pos = state.current_cursor_pos(); + let new_pos = current_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(); + // Login fields often allow cursor at the very end for appending + if current_pos < current_input.len() { + let new_pos = current_pos + 1; + state.set_current_cursor_pos(new_pos); + *ideal_cursor_column = new_pos; + } + // Original logic (prevents going one past the end): + // if !current_input.is_empty() + // && current_pos < current_input.len().saturating_sub(1) + // { + // let new_pos = current_pos + 1; + // state.set_current_cursor_pos(new_pos); + // *ideal_cursor_column = new_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()); + // Allow cursor at end for login fields + let final_pos = new_pos.min(current_input.len()); + // Original: + // let final_pos = new_pos.min(current_input.len().saturating_sub(1)); + 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 { + new_pos + } else { + find_word_end(current_input, new_pos + 1) + }; + + // Allow cursor at end for login fields + let max_valid_index = current_input.len(); + // Original: + // 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()) // Maybe clear msg? + } + "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(); + // Allow cursor at end for login fields + let new_pos = current_input.len(); + // Original: + // if !current_input.is_empty() { + // let new_pos = current_input.len().saturating_sub(1); + // state.set_current_cursor_pos(new_pos); + // *ideal_cursor_column = new_pos; + // } else { + // state.set_current_cursor_pos(0); + // *ideal_cursor_column = 0; + // } + state.set_current_cursor_pos(new_pos); + *ideal_cursor_column = new_pos; + Ok("".to_string()) + } + _ => { + key_sequence_tracker.reset(); + Ok(format!("Unknown read-only action: {}", action)) + } + } +} + + +fn get_char_type(c: char) -> CharType { + if c.is_whitespace() { + CharType::Whitespace + } else if c.is_alphanumeric() { + CharType::Alphanumeric + } else { + CharType::Punctuation + } +} + +// --- (Copy all find_* helper functions here too) --- +fn find_next_word_start(text: &str, current_pos: usize) -> usize { + let chars: Vec = text.chars().collect(); + if chars.is_empty() { return 0; } + let current_pos = current_pos.min(chars.len()); + if 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_next_word_end(text: &str, current_pos: usize) -> usize { + let chars: Vec = text.chars().collect(); + if chars.is_empty() { return 0; } + let next_start = find_next_word_start(text, current_pos); + // Allow end position for login + if next_start >= chars.len() { return chars.len(); } + // Original: if next_start >= chars.len() { return chars.len().saturating_sub(1); } + let mut pos = next_start; + let word_type = get_char_type(chars[pos]); + while pos < chars.len() && get_char_type(chars[pos]) == word_type { pos += 1; } + // Allow end position for login + pos.min(chars.len()) + // Original: pos.saturating_sub(1).min(chars.len().saturating_sub(1)) +} + +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; } + // Allow end position for login + let mut pos = current_pos.min(len); + // Original: let mut pos = current_pos.min(len.saturating_sub(1)); + // Handle case where cursor is already at the end + if pos == len { return len; } + + let current_type = get_char_type(chars[pos]); + if current_type != CharType::Whitespace { + while pos < len && get_char_type(chars[pos]) == current_type { pos += 1; } + // Allow end position for login + return pos; + // Original: return pos.saturating_sub(1); + } + pos = find_next_word_start(text, pos); + // Allow end position for login + if pos >= len { return len; } + // Original: 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; } + // Allow end position for login + pos.min(len) + // Original: 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 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[0]) == CharType::Whitespace { 0 } else { pos } +} + +fn find_prev_word_end(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[0]) == CharType::Whitespace { return 0; } + if pos == 0 && get_char_type(chars[0]) != CharType::Whitespace { return 0; } // End of the first word is pos 0 + let word_type = get_char_type(chars[pos]); + // Find the start of the word the cursor is in or just passed + while pos > 0 && get_char_type(chars[pos - 1]) == word_type { pos -= 1; } + // Now pos is at the start of that word. We need the end of the *previous* word. + // Skip backwards over any whitespace before this word. + while pos > 0 && get_char_type(chars[pos - 1]) == CharType::Whitespace { pos -= 1; } + // If we are at the beginning, return 0 + if pos == 0 { return 0; } + // Now pos is at the end of the previous word. + pos - 1 +} + diff --git a/client/src/functions/modes/read_only/form_ro.rs b/client/src/functions/modes/read_only/form_ro.rs new file mode 100644 index 0000000..88434d1 --- /dev/null +++ b/client/src/functions/modes/read_only/form_ro.rs @@ -0,0 +1,260 @@ +// src/functions/modes/read_only/form_ro.rs + +use crate::config::binds::key_sequences::KeySequenceTracker; +use crate::services::grpc_client::GrpcClient; +use crate::state::canvas_state::CanvasState; +use crate::state::pages::form::FormState; + +// --- Context-Specific Actions Handled Here --- +const CONTEXT_ACTIONS_FORM: &[&str] = &[ + "previous_entry", + "next_entry", + "move_up", + "move_down", + "move_first_line", + "move_last_line", +]; + +pub async fn handle_action( + action: &str, + form_state: &mut FormState, + grpc_client: &mut GrpcClient, + current_position: &mut u64, + total_count: u64, + ideal_cursor_column: &mut usize, + key_sequence_tracker: &mut KeySequenceTracker, + command_message: &mut String, +) -> Result> { + if CONTEXT_ACTIONS_FORM.contains(&action) { + // Delegate context-specific actions to the original handler + // (or implement the logic directly here if preferred) + crate::tui::functions::form::handle_action( + action, + form_state, + grpc_client, + current_position, + total_count, + ideal_cursor_column, + ) + .await + } else { + // Handle generic actions using the local execute_action + execute_action( + action, + form_state, + ideal_cursor_column, + key_sequence_tracker, + command_message, + ) + .await + } +} + +// --- Generic Action Implementation (Copied and made private) --- + +#[derive(PartialEq)] +enum CharType { + Whitespace, + Alphanumeric, + Punctuation, +} + +async fn execute_action( + action: &str, + state: &mut S, + ideal_cursor_column: &mut usize, + key_sequence_tracker: &mut KeySequenceTracker, + command_message: &mut String, +) -> Result> { + match action { + // Context actions are handled above, this case should ideally not be hit + // for these actions, but included for robustness. + "previous_entry" | "next_entry" | "move_up" | "move_down" | + "move_first_line" | "move_last_line" => { + key_sequence_tracker.reset(); + Ok(format!( + "Action '{}' should be handled by context-specific logic", + action + )) + } + "exit_edit_mode" => { + // This action might not make sense here if only called from read-only + key_sequence_tracker.reset(); + command_message.clear(); + Ok("".to_string()) + } + "move_left" => { + let current_pos = state.current_cursor_pos(); + let new_pos = current_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_input.is_empty() + && current_pos < current_input.len().saturating_sub(1) + { + let new_pos = current_pos + 1; + state.set_current_cursor_pos(new_pos); + *ideal_cursor_column = new_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().saturating_sub(1)); + 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 { + new_pos + } else { + find_word_end(current_input, new_pos + 1) + }; + + 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()) // Maybe clear msg? + } + "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(); + if !current_input.is_empty() { + let new_pos = current_input.len().saturating_sub(1); + state.set_current_cursor_pos(new_pos); + *ideal_cursor_column = new_pos; + } else { + state.set_current_cursor_pos(0); + *ideal_cursor_column = 0; + } + Ok("".to_string()) + } + _ => { + key_sequence_tracker.reset(); + Ok(format!("Unknown read-only action: {}", action)) + } + } +} + +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() { return 0; } + let current_pos = current_pos.min(chars.len()); + if 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_next_word_end(...) // Keep this helper if needed by find_word_end +// ... (keep all find_* helper functions: find_next_word_end, find_word_end, find_prev_word_start, find_prev_word_end) +fn find_next_word_end(text: &str, current_pos: usize) -> usize { + let chars: Vec = text.chars().collect(); + if chars.is_empty() { return 0; } + let next_start = find_next_word_start(text, current_pos); + if next_start >= chars.len() { return chars.len().saturating_sub(1); } + let mut pos = next_start; + let word_type = get_char_type(chars[pos]); + while pos < chars.len() && get_char_type(chars[pos]) == word_type { pos += 1; } + pos.saturating_sub(1).min(chars.len().saturating_sub(1)) +} + +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.saturating_sub(1)); // Use saturating_sub + let current_type = get_char_type(chars[pos]); + if current_type != CharType::Whitespace { + while pos < len && get_char_type(chars[pos]) == current_type { pos += 1; } + return pos.saturating_sub(1); + } + 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 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[0]) == CharType::Whitespace { 0 } else { pos } +} + +fn find_prev_word_end(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[0]) == CharType::Whitespace { return 0; } + if pos == 0 && get_char_type(chars[0]) != 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 } +} +