344 lines
12 KiB
Rust
344 lines
12 KiB
Rust
// src/functions/modes/edit/add_table_e.rs
|
|
use crate::state::pages::add_table::AddTableState;
|
|
use crate::state::pages::canvas_state::CanvasState; // Use trait
|
|
use crossterm::event::{KeyCode, KeyEvent};
|
|
use std::error::Error;
|
|
|
|
#[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<char> = 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<char> = 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<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[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<char> = 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
|
|
}
|
|
}
|
|
|
|
/// Executes edit actions for the AddTable view canvas.
|
|
pub async fn execute_edit_action(
|
|
action: &str,
|
|
key: KeyEvent, // Needed for insert_char
|
|
state: &mut AddTableState,
|
|
ideal_cursor_column: &mut usize,
|
|
// Add other params like grpc_client if needed for future actions (e.g., validation)
|
|
) -> Result<String, Box<dyn Error>> {
|
|
// Use the CanvasState trait methods implemented for AddTableState
|
|
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<char> = 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()) // No message needed for char insertion
|
|
}
|
|
"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<char> = 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<char> = 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 = AddTableState::INPUT_FIELD_COUNT;
|
|
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 = AddTableState::INPUT_FIELD_COUNT;
|
|
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 = AddTableState::INPUT_FIELD_COUNT;
|
|
if num_fields > 0 {
|
|
let current_field = state.current_field();
|
|
if current_field > 0 {
|
|
let new_field = 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 = AddTableState::INPUT_FIELD_COUNT;
|
|
if num_fields > 0 {
|
|
let current_field = state.current_field();
|
|
let last_field_index = num_fields - 1;
|
|
if current_field < last_field_index {
|
|
let new_field = 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_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" => {
|
|
if AddTableState::INPUT_FIELD_COUNT > 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("".to_string())
|
|
}
|
|
"move_last_line" => {
|
|
let num_fields = AddTableState::INPUT_FIELD_COUNT;
|
|
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("".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("".to_string())
|
|
}
|
|
// Actions handled by main event loop (mode changes, save, revert)
|
|
"exit_edit_mode" | "save" | "revert" => {
|
|
Ok("Action handled by main loop".to_string())
|
|
}
|
|
_ => Ok(format!("Unknown or unhandled edit action: {}", action)),
|
|
}
|
|
}
|