344 lines
11 KiB
Rust
344 lines
11 KiB
Rust
// src/functions/modes/read_only/auth_ro.rs
|
|
|
|
use crate::config::binds::key_sequences::KeySequenceTracker;
|
|
use crate::state::pages::canvas_state::CanvasState;
|
|
use crate::state::app::state::AppState;
|
|
use std::error::Error;
|
|
|
|
#[derive(PartialEq)]
|
|
enum CharType {
|
|
Whitespace,
|
|
Alphanumeric,
|
|
Punctuation,
|
|
}
|
|
|
|
pub async fn execute_action<S: CanvasState>(
|
|
action: &str,
|
|
app_state: &mut AppState,
|
|
state: &mut S,
|
|
ideal_cursor_column: &mut usize,
|
|
key_sequence_tracker: &mut KeySequenceTracker,
|
|
command_message: &mut String,
|
|
) -> Result<String, Box<dyn Error>> {
|
|
match action {
|
|
"previous_entry" | "next_entry" => {
|
|
key_sequence_tracker.reset();
|
|
Ok(format!(
|
|
"Action '{}' should be handled by context-specific logic",
|
|
action
|
|
))
|
|
}
|
|
"move_up" => {
|
|
key_sequence_tracker.reset();
|
|
let num_fields = state.fields().len();
|
|
if num_fields == 0 {
|
|
return Ok("No fields to navigate.".to_string());
|
|
}
|
|
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_cursor_pos = if current_input.is_empty() {
|
|
0
|
|
} else {
|
|
current_input.len().saturating_sub(1)
|
|
};
|
|
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
|
|
state.set_current_cursor_pos(new_pos);
|
|
|
|
Ok("move up from functions/modes/read_only/auth_ro.rs".to_string())
|
|
}
|
|
"move_down" => {
|
|
key_sequence_tracker.reset();
|
|
let num_fields = state.fields().len();
|
|
if num_fields == 0 {
|
|
return Ok("No fields to navigate.".to_string());
|
|
}
|
|
let current_field = state.current_field();
|
|
let last_field_index = num_fields - 1;
|
|
|
|
if current_field == last_field_index {
|
|
// Already on the last field, move focus outside
|
|
app_state.ui.focus_outside_canvas = true;
|
|
app_state.focused_button_index= 0;
|
|
key_sequence_tracker.reset();
|
|
Ok("Focus moved below canvas".to_string())
|
|
} else {
|
|
// Move to the next field within the canvas
|
|
let new_field = (current_field + 1).min(last_field_index);
|
|
state.set_current_field(new_field);
|
|
let current_input = state.get_current_input();
|
|
let max_cursor_pos = if current_input.is_empty() {
|
|
0
|
|
} else {
|
|
current_input.len().saturating_sub(1)
|
|
};
|
|
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
|
|
state.set_current_cursor_pos(new_pos);
|
|
Ok("".to_string()) // Clear previous debug message
|
|
}
|
|
}
|
|
"move_first_line" => {
|
|
key_sequence_tracker.reset();
|
|
let num_fields = state.fields().len();
|
|
if num_fields == 0 {
|
|
return Ok("No fields to navigate to.".to_string());
|
|
}
|
|
state.set_current_field(0);
|
|
let current_input = state.get_current_input();
|
|
let max_cursor_pos = if current_input.is_empty() {
|
|
0
|
|
} else {
|
|
current_input.len().saturating_sub(1)
|
|
};
|
|
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
|
|
state.set_current_cursor_pos(new_pos);
|
|
*ideal_cursor_column = new_pos;
|
|
Ok("".to_string())
|
|
}
|
|
"move_last_line" => {
|
|
key_sequence_tracker.reset();
|
|
let num_fields = state.fields().len();
|
|
if num_fields == 0 {
|
|
return Ok("No fields to navigate to.".to_string());
|
|
}
|
|
let last_field_index = num_fields - 1;
|
|
state.set_current_field(last_field_index);
|
|
let current_input = state.get_current_input();
|
|
let max_cursor_pos = if current_input.is_empty() {
|
|
0
|
|
} else {
|
|
current_input.len().saturating_sub(1)
|
|
};
|
|
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
|
|
state.set_current_cursor_pos(new_pos);
|
|
*ideal_cursor_column = new_pos;
|
|
Ok("".to_string())
|
|
}
|
|
"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();
|
|
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())
|
|
}
|
|
"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<char> = 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_word_end(text: &str, current_pos: usize) -> usize {
|
|
let chars: Vec<char> = text.chars().collect();
|
|
let len = chars.len();
|
|
if len == 0 {
|
|
return 0;
|
|
}
|
|
|
|
let mut pos = current_pos.min(len - 1);
|
|
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<char> = 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<char> = 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
|
|
}
|
|
}
|