467 lines
16 KiB
Rust
467 lines
16 KiB
Rust
// src/functions/modes/edit/auth_e.rs
|
|
|
|
use crate::services::grpc_client::GrpcClient;
|
|
use crate::state::pages::form::FormState;
|
|
use crate::state::pages::auth::RegisterState;
|
|
use crate::state::app::state::AppState;
|
|
use crate::tui::functions::common::form::{revert, save};
|
|
use crossterm::event::{KeyCode, KeyEvent};
|
|
use canvas::autocomplete::AutocompleteCanvasState;
|
|
use canvas::canvas::CanvasState;
|
|
use std::any::Any;
|
|
use anyhow::Result;
|
|
|
|
pub async fn execute_common_action<S: CanvasState + Any>(
|
|
action: &str,
|
|
state: &mut S,
|
|
grpc_client: &mut GrpcClient,
|
|
app_state: &AppState,
|
|
current_position: &mut u64,
|
|
total_count: u64,
|
|
) -> Result<String> {
|
|
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::<FormState>()
|
|
{
|
|
match action {
|
|
"save" => {
|
|
let outcome = save(
|
|
app_state,
|
|
form_state,
|
|
grpc_client,
|
|
)
|
|
.await?;
|
|
let message = format!("Save successful: {:?}", outcome); // Simple message for now
|
|
Ok(message)
|
|
}
|
|
"revert" => {
|
|
revert(
|
|
form_state,
|
|
grpc_client,
|
|
)
|
|
.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<S: CanvasState + Any + Send>(
|
|
action: &str,
|
|
key: KeyEvent,
|
|
state: &mut S,
|
|
ideal_cursor_column: &mut usize,
|
|
) -> Result<String> {
|
|
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("working?".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<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 = state.fields().len();
|
|
if num_fields > 0 {
|
|
let current_field = state.current_field();
|
|
let new_field = (current_field + 1).min(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())
|
|
}
|
|
|
|
"prev_field" => {
|
|
let num_fields = state.fields().len();
|
|
if num_fields > 0 {
|
|
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_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 = current_field.saturating_sub(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).min(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_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())
|
|
}
|
|
|
|
// --- Autocomplete Actions ---
|
|
"suggestion_down" | "suggestion_up" | "select_suggestion" | "exit_suggestion_mode" => {
|
|
// Attempt to downcast to RegisterState to handle suggestion logic here
|
|
if let Some(register_state) = (state as &mut dyn Any).downcast_mut::<RegisterState>() {
|
|
// Only handle if it's the role field (index 4) and autocomplete is active
|
|
if register_state.current_field() == 4 && register_state.is_autocomplete_active() {
|
|
match action {
|
|
"suggestion_down" => {
|
|
if let Some(autocomplete_state) = register_state.autocomplete_state_mut() {
|
|
autocomplete_state.select_next();
|
|
Ok("Suggestion changed down".to_string())
|
|
} else {
|
|
Ok("No autocomplete state".to_string())
|
|
}
|
|
}
|
|
"suggestion_up" => {
|
|
if let Some(autocomplete_state) = register_state.autocomplete_state_mut() {
|
|
autocomplete_state.select_previous();
|
|
Ok("Suggestion changed up".to_string())
|
|
} else {
|
|
Ok("No autocomplete state".to_string())
|
|
}
|
|
}
|
|
"select_suggestion" => {
|
|
if let Some(message) = register_state.apply_autocomplete_selection() {
|
|
Ok(message)
|
|
} else {
|
|
Ok("No suggestion selected".to_string())
|
|
}
|
|
}
|
|
"exit_suggestion_mode" => {
|
|
register_state.deactivate_autocomplete();
|
|
Ok("Suggestions hidden".to_string())
|
|
}
|
|
_ => Ok("Suggestion action ignored: State mismatch.".to_string())
|
|
}
|
|
} else {
|
|
Ok("Suggestion action ignored: Not on role field or autocomplete not active.".to_string())
|
|
}
|
|
} else {
|
|
Ok(format!("Action '{}' not applicable for this state type.", action))
|
|
}
|
|
}
|
|
// --- End Autocomplete Actions ---
|
|
|
|
|
|
_ => 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<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
|
|
}
|
|
}
|