readonly and edit functionality to add table
This commit is contained in:
@@ -2,3 +2,4 @@
|
|||||||
|
|
||||||
pub mod form_e;
|
pub mod form_e;
|
||||||
pub mod auth_e;
|
pub mod auth_e;
|
||||||
|
pub mod add_table_e;
|
||||||
|
|||||||
343
client/src/functions/modes/edit/add_table_e.rs
Normal file
343
client/src/functions/modes/edit/add_table_e.rs
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
// 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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,101 +5,120 @@ use crate::state::{
|
|||||||
pages::add_table::{AddTableFocus, AddTableState},
|
pages::add_table::{AddTableFocus, AddTableState},
|
||||||
};
|
};
|
||||||
use crossterm::event::{KeyEvent};
|
use crossterm::event::{KeyEvent};
|
||||||
use ratatui::widgets::TableState; // Import TableState
|
use ratatui::widgets::TableState;
|
||||||
|
|
||||||
/// Handles navigation events specifically for the Add Table view.
|
/// Handles navigation events specifically for the Add Table view.
|
||||||
/// Returns true if the event was handled, false otherwise.
|
/// Returns true if the event was handled, false otherwise.
|
||||||
pub fn handle_add_table_navigation(
|
pub fn handle_add_table_navigation(
|
||||||
key: KeyEvent,
|
key: KeyEvent,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
_app_state: &AppState, // Keep for potential future use (e.g., checking permissions)
|
_app_state: &AppState,
|
||||||
add_table_state: &mut AddTableState,
|
add_table_state: &mut AddTableState,
|
||||||
command_message: &mut String,
|
command_message: &mut String,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let action = config.get_general_action(key.code, key.modifiers);
|
let action = config.get_general_action(key.code, key.modifiers);
|
||||||
let current_focus = add_table_state.current_focus;
|
let current_focus = add_table_state.current_focus;
|
||||||
let mut handled = true; // Assume handled unless logic determines otherwise
|
let mut handled = true; // Assume handled unless logic determines otherwise
|
||||||
|
let mut new_focus = current_focus; // Initialize new_focus
|
||||||
|
|
||||||
|
// Define focus groups for horizontal navigation
|
||||||
|
let is_left_pane_focus = matches!(current_focus,
|
||||||
|
AddTableFocus::ColumnsTable | AddTableFocus::IndexesTable | AddTableFocus::LinksTable
|
||||||
|
);
|
||||||
|
let is_right_pane_focus = matches!(current_focus,
|
||||||
|
AddTableFocus::InputTableName | AddTableFocus::InputColumnName | AddTableFocus::InputColumnType |
|
||||||
|
AddTableFocus::AddColumnButton | AddTableFocus::SaveButton | AddTableFocus::CancelButton
|
||||||
|
);
|
||||||
|
|
||||||
match action.as_deref() {
|
match action.as_deref() {
|
||||||
// --- Vertical Navigation (Up/Down) ---
|
// --- Vertical Navigation (Up/Down) ---
|
||||||
Some("move_up") => {
|
Some("move_up") => {
|
||||||
let mut new_focus = current_focus; // Start with current focus
|
|
||||||
match current_focus {
|
match current_focus {
|
||||||
AddTableFocus::InputTableName => new_focus = AddTableFocus::CancelButton, // Wrap top
|
AddTableFocus::InputTableName => new_focus = AddTableFocus::CancelButton, // Wrap top (right pane)
|
||||||
AddTableFocus::InputColumnName => new_focus = AddTableFocus::InputTableName,
|
AddTableFocus::InputColumnName => new_focus = AddTableFocus::InputTableName,
|
||||||
AddTableFocus::InputColumnType => new_focus = AddTableFocus::InputColumnName,
|
AddTableFocus::InputColumnType => new_focus = AddTableFocus::InputColumnName,
|
||||||
AddTableFocus::AddColumnButton => new_focus = AddTableFocus::InputColumnType,
|
AddTableFocus::AddColumnButton => new_focus = AddTableFocus::InputColumnType,
|
||||||
AddTableFocus::ColumnsTable => {
|
AddTableFocus::ColumnsTable => { // Left pane navigation
|
||||||
if !navigate_table_up(&mut add_table_state.column_table_state, add_table_state.columns.len()) {
|
if !navigate_table_up(&mut add_table_state.column_table_state, add_table_state.columns.len()) {
|
||||||
new_focus = AddTableFocus::AddColumnButton; // Move focus up if at table top
|
// If at top of columns, potentially wrap to bottom of left pane (LinksTable) or stay? Let's stay for now.
|
||||||
|
// Or maybe move to AddColumnButton? Let's try moving up from right pane instead.
|
||||||
|
new_focus = AddTableFocus::AddColumnButton; // Tentative: move focus up from right pane
|
||||||
}
|
}
|
||||||
// Keep focus on table while navigating within it
|
|
||||||
}
|
}
|
||||||
AddTableFocus::IndexesTable => {
|
AddTableFocus::IndexesTable => {
|
||||||
if !navigate_table_up(&mut add_table_state.index_table_state, add_table_state.indexes.len()) {
|
if !navigate_table_up(&mut add_table_state.index_table_state, add_table_state.indexes.len()) {
|
||||||
new_focus = AddTableFocus::ColumnsTable; // Move focus up
|
new_focus = AddTableFocus::ColumnsTable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AddTableFocus::LinksTable => {
|
AddTableFocus::LinksTable => {
|
||||||
if !navigate_table_up(&mut add_table_state.link_table_state, add_table_state.links.len()) {
|
if !navigate_table_up(&mut add_table_state.link_table_state, add_table_state.links.len()) {
|
||||||
new_focus = AddTableFocus::IndexesTable; // Move focus up
|
new_focus = AddTableFocus::IndexesTable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AddTableFocus::SaveButton => new_focus = AddTableFocus::LinksTable,
|
AddTableFocus::SaveButton => new_focus = AddTableFocus::LinksTable, // Move up to left pane bottom
|
||||||
AddTableFocus::CancelButton => new_focus = AddTableFocus::SaveButton,
|
AddTableFocus::CancelButton => new_focus = AddTableFocus::SaveButton,
|
||||||
}
|
}
|
||||||
add_table_state.current_focus = new_focus;
|
|
||||||
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
|
|
||||||
}
|
}
|
||||||
Some("move_down") => {
|
Some("move_down") => {
|
||||||
let mut new_focus = current_focus; // Start with current focus
|
|
||||||
match current_focus {
|
match current_focus {
|
||||||
AddTableFocus::InputTableName => new_focus = AddTableFocus::InputColumnName,
|
AddTableFocus::InputTableName => new_focus = AddTableFocus::InputColumnName,
|
||||||
AddTableFocus::InputColumnName => new_focus = AddTableFocus::InputColumnType,
|
AddTableFocus::InputColumnName => new_focus = AddTableFocus::InputColumnType,
|
||||||
AddTableFocus::InputColumnType => new_focus = AddTableFocus::AddColumnButton,
|
AddTableFocus::InputColumnType => new_focus = AddTableFocus::AddColumnButton,
|
||||||
AddTableFocus::AddColumnButton => new_focus = AddTableFocus::ColumnsTable,
|
AddTableFocus::AddColumnButton => new_focus = AddTableFocus::ColumnsTable, // Move down to left pane top
|
||||||
AddTableFocus::ColumnsTable => {
|
AddTableFocus::ColumnsTable => { // Left pane navigation
|
||||||
if !navigate_table_down(&mut add_table_state.column_table_state, add_table_state.columns.len()) {
|
if !navigate_table_down(&mut add_table_state.column_table_state, add_table_state.columns.len()) {
|
||||||
new_focus = AddTableFocus::IndexesTable; // Move focus down if at table bottom
|
new_focus = AddTableFocus::IndexesTable; // Move to next left pane item
|
||||||
}
|
}
|
||||||
// Keep focus on table while navigating within it
|
|
||||||
}
|
}
|
||||||
AddTableFocus::IndexesTable => {
|
AddTableFocus::IndexesTable => {
|
||||||
if !navigate_table_down(&mut add_table_state.index_table_state, add_table_state.indexes.len()) {
|
if !navigate_table_down(&mut add_table_state.index_table_state, add_table_state.indexes.len()) {
|
||||||
new_focus = AddTableFocus::LinksTable; // Move focus down
|
new_focus = AddTableFocus::LinksTable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AddTableFocus::LinksTable => {
|
AddTableFocus::LinksTable => {
|
||||||
if !navigate_table_down(&mut add_table_state.link_table_state, add_table_state.links.len()) {
|
if !navigate_table_down(&mut add_table_state.link_table_state, add_table_state.links.len()) {
|
||||||
new_focus = AddTableFocus::SaveButton; // Move focus down
|
new_focus = AddTableFocus::SaveButton; // Move down to right pane bottom
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AddTableFocus::SaveButton => new_focus = AddTableFocus::CancelButton,
|
AddTableFocus::SaveButton => new_focus = AddTableFocus::CancelButton,
|
||||||
AddTableFocus::CancelButton => new_focus = AddTableFocus::InputTableName, // Wrap bottom
|
AddTableFocus::CancelButton => new_focus = AddTableFocus::InputTableName, // Wrap bottom (right pane)
|
||||||
}
|
}
|
||||||
add_table_state.current_focus = new_focus;
|
|
||||||
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Horizontal Navigation (Left/Right) ---
|
// --- Horizontal Navigation (Left/Right) ---
|
||||||
Some("next_option") => { // 'l' or Right
|
Some("next_option") => { // 'l' or Right: Move from Left Pane to Right Pane
|
||||||
add_table_state.current_focus = match current_focus {
|
if is_left_pane_focus {
|
||||||
AddTableFocus::SaveButton => AddTableFocus::CancelButton,
|
new_focus = match current_focus {
|
||||||
_ => current_focus, // No change for others yet
|
// Map left pane items to corresponding right pane items (approximate vertical alignment)
|
||||||
};
|
AddTableFocus::ColumnsTable => AddTableFocus::InputTableName,
|
||||||
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
|
AddTableFocus::IndexesTable => AddTableFocus::InputColumnName, // Or AddColumnButton?
|
||||||
|
AddTableFocus::LinksTable => AddTableFocus::SaveButton,
|
||||||
|
_ => current_focus, // Should not happen based on is_left_pane_focus
|
||||||
|
};
|
||||||
|
} else if is_right_pane_focus {
|
||||||
|
// If already in right pane, maybe wrap Save -> Cancel or stay? Let's handle Save->Cancel only.
|
||||||
|
if current_focus == AddTableFocus::SaveButton {
|
||||||
|
new_focus = AddTableFocus::CancelButton;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some("previous_option") => { // 'h' or Left
|
Some("previous_option") => { // 'h' or Left: Move from Right Pane to Left Pane
|
||||||
add_table_state.current_focus = match current_focus {
|
if is_right_pane_focus {
|
||||||
AddTableFocus::CancelButton => AddTableFocus::SaveButton,
|
new_focus = match current_focus {
|
||||||
_ => current_focus, // No change for others yet
|
// Map right pane items back to left pane items (approximate vertical alignment)
|
||||||
};
|
AddTableFocus::InputTableName | AddTableFocus::InputColumnName | AddTableFocus::InputColumnType | AddTableFocus::AddColumnButton => AddTableFocus::ColumnsTable, // Go to top of left pane
|
||||||
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
|
AddTableFocus::SaveButton | AddTableFocus::CancelButton => AddTableFocus::LinksTable, // Go to bottom of left pane
|
||||||
|
_ => current_focus, // Should not happen
|
||||||
|
};
|
||||||
|
} else if is_left_pane_focus {
|
||||||
|
// If already in left pane, pressing 'h' could wrap to Cancel button?
|
||||||
|
new_focus = AddTableFocus::CancelButton; // Wrap left-to-right bottom
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Tab / Shift+Tab Navigation ---
|
// --- Tab / Shift+Tab Navigation (Keep as vertical cycle) ---
|
||||||
Some("next_field") => { // Tab
|
Some("next_field") => { // Tab
|
||||||
add_table_state.current_focus = match current_focus {
|
new_focus = match current_focus {
|
||||||
AddTableFocus::InputTableName => AddTableFocus::InputColumnName,
|
AddTableFocus::InputTableName => AddTableFocus::InputColumnName,
|
||||||
AddTableFocus::InputColumnName => AddTableFocus::InputColumnType,
|
AddTableFocus::InputColumnName => AddTableFocus::InputColumnType,
|
||||||
AddTableFocus::InputColumnType => AddTableFocus::AddColumnButton,
|
AddTableFocus::InputColumnType => AddTableFocus::AddColumnButton,
|
||||||
@@ -110,10 +129,9 @@ pub fn handle_add_table_navigation(
|
|||||||
AddTableFocus::SaveButton => AddTableFocus::CancelButton,
|
AddTableFocus::SaveButton => AddTableFocus::CancelButton,
|
||||||
AddTableFocus::CancelButton => AddTableFocus::InputTableName, // Wrap
|
AddTableFocus::CancelButton => AddTableFocus::InputTableName, // Wrap
|
||||||
};
|
};
|
||||||
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
|
|
||||||
}
|
}
|
||||||
Some("prev_field") => { // Shift+Tab
|
Some("prev_field") => { // Shift+Tab
|
||||||
add_table_state.current_focus = match current_focus {
|
new_focus = match current_focus {
|
||||||
AddTableFocus::InputTableName => AddTableFocus::CancelButton, // Wrap
|
AddTableFocus::InputTableName => AddTableFocus::CancelButton, // Wrap
|
||||||
AddTableFocus::InputColumnName => AddTableFocus::InputTableName,
|
AddTableFocus::InputColumnName => AddTableFocus::InputTableName,
|
||||||
AddTableFocus::InputColumnType => AddTableFocus::InputColumnName,
|
AddTableFocus::InputColumnType => AddTableFocus::InputColumnName,
|
||||||
@@ -124,7 +142,6 @@ pub fn handle_add_table_navigation(
|
|||||||
AddTableFocus::SaveButton => AddTableFocus::LinksTable,
|
AddTableFocus::SaveButton => AddTableFocus::LinksTable,
|
||||||
AddTableFocus::CancelButton => AddTableFocus::SaveButton,
|
AddTableFocus::CancelButton => AddTableFocus::SaveButton,
|
||||||
};
|
};
|
||||||
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Selection ---
|
// --- Selection ---
|
||||||
@@ -132,70 +149,54 @@ pub fn handle_add_table_navigation(
|
|||||||
match current_focus {
|
match current_focus {
|
||||||
AddTableFocus::AddColumnButton => {
|
AddTableFocus::AddColumnButton => {
|
||||||
*command_message = "Action: Add Column (Not Implemented)".to_string();
|
*command_message = "Action: Add Column (Not Implemented)".to_string();
|
||||||
// TODO: Implement logic to add column based on inputs
|
// TODO: Implement logic
|
||||||
// Clear input fields, add to columns list, mark unsaved changes
|
|
||||||
// add_table_state.add_column(); // Example method call
|
|
||||||
}
|
}
|
||||||
AddTableFocus::SaveButton => {
|
AddTableFocus::SaveButton => {
|
||||||
*command_message = "Action: Save Table (Not Implemented)".to_string();
|
*command_message = "Action: Save Table (Not Implemented)".to_string();
|
||||||
// TODO: Implement logic to save table (e.g., call API)
|
// TODO: Implement logic
|
||||||
// Mark changes as saved
|
|
||||||
// add_table_state.save_table(); // Example method call
|
|
||||||
}
|
}
|
||||||
AddTableFocus::CancelButton => {
|
AddTableFocus::CancelButton => {
|
||||||
*command_message = "Action: Cancel Add Table".to_string();
|
*command_message = "Action: Cancel Add Table".to_string();
|
||||||
// TODO: Implement logic to navigate back (e.g., update AppView history)
|
// TODO: Implement logic
|
||||||
// Maybe show a confirmation dialog if there are unsaved changes
|
|
||||||
// buffer_state.go_back(); // Example call
|
|
||||||
}
|
}
|
||||||
// Selecting input fields usually means entering Edit mode (handled elsewhere)
|
|
||||||
// Selecting tables might mean focusing on them for editing/deletion (TODO)
|
|
||||||
AddTableFocus::ColumnsTable => {
|
AddTableFocus::ColumnsTable => {
|
||||||
if let Some(index) = add_table_state.column_table_state.selected() {
|
if let Some(index) = add_table_state.column_table_state.selected() {
|
||||||
*command_message = format!("Selected column index {}", index);
|
*command_message = format!("Selected column index {}", index);
|
||||||
// TODO: Add logic for editing/deleting selected column
|
} else { *command_message = "No column selected".to_string(); }
|
||||||
} else {
|
|
||||||
*command_message = "No column selected".to_string();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
AddTableFocus::IndexesTable => {
|
AddTableFocus::IndexesTable => {
|
||||||
if let Some(index) = add_table_state.index_table_state.selected() {
|
if let Some(index) = add_table_state.index_table_state.selected() {
|
||||||
*command_message = format!("Selected index index {}", index);
|
*command_message = format!("Selected index index {}", index);
|
||||||
// TODO: Add logic for editing/deleting selected index
|
} else { *command_message = "No index selected".to_string(); }
|
||||||
} else {
|
|
||||||
*command_message = "No index selected".to_string();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
AddTableFocus::LinksTable => {
|
AddTableFocus::LinksTable => {
|
||||||
if let Some(index) = add_table_state.link_table_state.selected() {
|
if let Some(index) = add_table_state.link_table_state.selected() {
|
||||||
*command_message = format!("Selected link index {}", index);
|
*command_message = format!("Selected link index {}", index);
|
||||||
// TODO: Add logic for editing/deleting selected link
|
} else { *command_message = "No link selected".to_string(); }
|
||||||
} else {
|
|
||||||
*command_message = "No link selected".to_string();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => { // Input fields
|
||||||
// For InputTableName, InputColumnName, InputColumnType,
|
|
||||||
// the main event loop should handle 'select' by potentially
|
|
||||||
// switching to Edit mode if not already in it.
|
|
||||||
// We don't need specific logic here for that.
|
|
||||||
*command_message = format!("Select on {:?}", current_focus);
|
*command_message = format!("Select on {:?}", current_focus);
|
||||||
handled = false; // Let main loop handle edit mode toggle maybe
|
handled = false; // Let main loop handle edit mode toggle maybe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Keep handled = true for select actions unless specifically set to false
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Other General Keys (Ignore for add_table nav) ---
|
// --- Other General Keys ---
|
||||||
Some("toggle_sidebar") | Some("toggle_buffer_list") => {
|
Some("toggle_sidebar") | Some("toggle_buffer_list") => {
|
||||||
handled = false; // Let global handler manage these
|
handled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- No matching action ---
|
// --- No matching action ---
|
||||||
_ => handled = false, // Event not handled by add_table navigation
|
_ => handled = false,
|
||||||
}
|
}
|
||||||
|
|
||||||
// If focus changed TO a table, select the first row if nothing is selected
|
// Update focus state if it changed and was handled
|
||||||
if handled && current_focus != add_table_state.current_focus {
|
if handled && current_focus != new_focus {
|
||||||
|
add_table_state.current_focus = new_focus;
|
||||||
|
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
|
||||||
|
|
||||||
|
// Select first item when focusing a table
|
||||||
match add_table_state.current_focus {
|
match add_table_state.current_focus {
|
||||||
AddTableFocus::ColumnsTable if add_table_state.column_table_state.selected().is_none() && !add_table_state.columns.is_empty() => {
|
AddTableFocus::ColumnsTable if add_table_state.column_table_state.selected().is_none() && !add_table_state.columns.is_empty() => {
|
||||||
add_table_state.column_table_state.select(Some(0));
|
add_table_state.column_table_state.select(Some(0));
|
||||||
@@ -206,10 +207,14 @@ pub fn handle_add_table_navigation(
|
|||||||
AddTableFocus::LinksTable if add_table_state.link_table_state.selected().is_none() && !add_table_state.links.is_empty() => {
|
AddTableFocus::LinksTable if add_table_state.link_table_state.selected().is_none() && !add_table_state.links.is_empty() => {
|
||||||
add_table_state.link_table_state.select(Some(0));
|
add_table_state.link_table_state.select(Some(0));
|
||||||
}
|
}
|
||||||
_ => {} // No action needed for other focus states
|
_ => {}
|
||||||
}
|
}
|
||||||
|
} else if !handled {
|
||||||
|
// If not handled by this specific navigation, clear the message potentially set by get_general_action
|
||||||
|
// command_message.clear(); // Optional: depends if you want default messages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
handled
|
handled
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,20 +222,19 @@ pub fn handle_add_table_navigation(
|
|||||||
// Helper function for navigating up within a table state
|
// Helper function for navigating up within a table state
|
||||||
// Returns true if navigation happened within the table, false if it reached the top
|
// Returns true if navigation happened within the table, false if it reached the top
|
||||||
fn navigate_table_up(table_state: &mut TableState, item_count: usize) -> bool {
|
fn navigate_table_up(table_state: &mut TableState, item_count: usize) -> bool {
|
||||||
if item_count == 0 { return false; } // Cannot navigate empty table
|
if item_count == 0 { return false; }
|
||||||
let current_selection = table_state.selected();
|
let current_selection = table_state.selected();
|
||||||
match current_selection {
|
match current_selection {
|
||||||
Some(index) => {
|
Some(index) => {
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
table_state.select(Some(index - 1));
|
table_state.select(Some(index - 1));
|
||||||
true // Moved up within table
|
true
|
||||||
} else {
|
} else {
|
||||||
false // Was at the top
|
false // Was at the top
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// If nothing selected, moving up could select the last item
|
table_state.select(Some(item_count - 1)); // Select last item
|
||||||
table_state.select(Some(item_count - 1));
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,20 +243,19 @@ fn navigate_table_up(table_state: &mut TableState, item_count: usize) -> bool {
|
|||||||
// Helper function for navigating down within a table state
|
// Helper function for navigating down within a table state
|
||||||
// Returns true if navigation happened within the table, false if it reached the bottom
|
// Returns true if navigation happened within the table, false if it reached the bottom
|
||||||
fn navigate_table_down(table_state: &mut TableState, item_count: usize) -> bool {
|
fn navigate_table_down(table_state: &mut TableState, item_count: usize) -> bool {
|
||||||
if item_count == 0 { return false; } // Cannot navigate empty table
|
if item_count == 0 { return false; }
|
||||||
let current_selection = table_state.selected();
|
let current_selection = table_state.selected();
|
||||||
match current_selection {
|
match current_selection {
|
||||||
Some(index) => {
|
Some(index) => {
|
||||||
if index < item_count - 1 {
|
if index < item_count - 1 {
|
||||||
table_state.select(Some(index + 1));
|
table_state.select(Some(index + 1));
|
||||||
true // Moved down within table
|
true
|
||||||
} else {
|
} else {
|
||||||
false // Was at the bottom
|
false // Was at the bottom
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// If nothing selected, moving down could select the first item
|
table_state.select(Some(0)); // Select first item
|
||||||
table_state.select(Some(0));
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,3 +2,4 @@
|
|||||||
|
|
||||||
pub mod auth_ro;
|
pub mod auth_ro;
|
||||||
pub mod form_ro;
|
pub mod form_ro;
|
||||||
|
pub mod add_table_ro;
|
||||||
|
|||||||
229
client/src/functions/modes/read_only/add_table_ro.rs
Normal file
229
client/src/functions/modes/read_only/add_table_ro.rs
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
// src/functions/modes/read_only/add_table_ro.rs
|
||||||
|
use crate::config::binds::key_sequences::KeySequenceTracker;
|
||||||
|
use crate::state::pages::add_table::AddTableState;
|
||||||
|
use crate::state::pages::canvas_state::CanvasState; // Use trait for common actions
|
||||||
|
use crate::state::app::state::AppState;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
// Re-use word navigation helpers if they are public or move them to a common module
|
||||||
|
// For now, duplicating them here for simplicity. Consider refactoring later.
|
||||||
|
#[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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: find_prev_word_end might need adjustments based on desired behavior.
|
||||||
|
// This version finds the end of the word *before* the previous word start.
|
||||||
|
fn find_prev_word_end(text: &str, current_pos: usize) -> usize {
|
||||||
|
let prev_start = find_prev_word_start(text, current_pos);
|
||||||
|
if prev_start == 0 { return 0; }
|
||||||
|
// Find the end of the word that starts at prev_start - 1
|
||||||
|
find_word_end(text, prev_start.saturating_sub(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Executes read-only actions for the AddTable view canvas.
|
||||||
|
pub async fn execute_action(
|
||||||
|
action: &str,
|
||||||
|
app_state: &mut AppState, // Needed for focus_outside_canvas
|
||||||
|
state: &mut AddTableState,
|
||||||
|
ideal_cursor_column: &mut usize,
|
||||||
|
key_sequence_tracker: &mut KeySequenceTracker,
|
||||||
|
command_message: &mut String, // Keep for potential messages
|
||||||
|
) -> Result<String, Box<dyn Error>> {
|
||||||
|
// Use the CanvasState trait methods implemented for AddTableState
|
||||||
|
match action {
|
||||||
|
"move_up" => {
|
||||||
|
key_sequence_tracker.reset();
|
||||||
|
let num_fields = AddTableState::INPUT_FIELD_COUNT; // Use the constant
|
||||||
|
if num_fields == 0 { return Ok("No fields.".to_string()); }
|
||||||
|
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_cursor_pos = current_input.len(); // Allow cursor at end
|
||||||
|
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
|
||||||
|
state.set_current_cursor_pos(new_pos);
|
||||||
|
} else {
|
||||||
|
// Optionally move focus outside canvas when moving up from the first field
|
||||||
|
// app_state.ui.focus_outside_canvas = true;
|
||||||
|
// return Ok("Focus moved above canvas".to_string());
|
||||||
|
}
|
||||||
|
Ok("".to_string())
|
||||||
|
}
|
||||||
|
"move_down" => {
|
||||||
|
key_sequence_tracker.reset();
|
||||||
|
let num_fields = AddTableState::INPUT_FIELD_COUNT;
|
||||||
|
if num_fields == 0 { return Ok("No fields.".to_string()); }
|
||||||
|
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_cursor_pos = current_input.len(); // Allow cursor at end
|
||||||
|
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
|
||||||
|
state.set_current_cursor_pos(new_pos);
|
||||||
|
} else {
|
||||||
|
// Move focus outside canvas when moving down from the last field
|
||||||
|
app_state.ui.focus_outside_canvas = true;
|
||||||
|
// Set focus to the first element outside canvas (AddColumnButton)
|
||||||
|
state.current_focus = crate::state::pages::add_table::AddTableFocus::AddColumnButton;
|
||||||
|
key_sequence_tracker.reset();
|
||||||
|
return Ok("Focus moved below canvas".to_string());
|
||||||
|
}
|
||||||
|
Ok("".to_string())
|
||||||
|
}
|
||||||
|
"move_first_line" => {
|
||||||
|
key_sequence_tracker.reset();
|
||||||
|
if AddTableState::INPUT_FIELD_COUNT > 0 {
|
||||||
|
state.set_current_field(0);
|
||||||
|
let current_input = state.get_current_input();
|
||||||
|
let max_cursor_pos = current_input.len();
|
||||||
|
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
|
||||||
|
state.set_current_cursor_pos(new_pos);
|
||||||
|
*ideal_cursor_column = new_pos; // Update ideal column
|
||||||
|
}
|
||||||
|
Ok("".to_string())
|
||||||
|
}
|
||||||
|
"move_last_line" => {
|
||||||
|
key_sequence_tracker.reset();
|
||||||
|
let num_fields = AddTableState::INPUT_FIELD_COUNT;
|
||||||
|
if num_fields > 0 {
|
||||||
|
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 = current_input.len();
|
||||||
|
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
|
||||||
|
state.set_current_cursor_pos(new_pos);
|
||||||
|
*ideal_cursor_column = new_pos; // Update ideal column
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
// Allow moving cursor one position past the end
|
||||||
|
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_word_next" => {
|
||||||
|
let current_input = state.get_current_input();
|
||||||
|
let new_pos = find_next_word_start(current_input, state.current_cursor_pos());
|
||||||
|
let final_pos = new_pos.min(current_input.len()); // Allow cursor at end
|
||||||
|
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();
|
||||||
|
let current_pos = state.current_cursor_pos();
|
||||||
|
let new_pos = find_word_end(current_input, current_pos);
|
||||||
|
// If find_word_end returns current_pos, try starting search from next char
|
||||||
|
let final_pos = if new_pos == current_pos && current_pos < current_input.len() {
|
||||||
|
find_word_end(current_input, current_pos + 1)
|
||||||
|
} else {
|
||||||
|
new_pos
|
||||||
|
};
|
||||||
|
let max_valid_index = current_input.len(); // Allow cursor at end
|
||||||
|
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();
|
||||||
|
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();
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
"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(); // Allow cursor at end
|
||||||
|
state.set_current_cursor_pos(new_pos);
|
||||||
|
*ideal_cursor_column = new_pos;
|
||||||
|
Ok("".to_string())
|
||||||
|
}
|
||||||
|
// Actions handled by main event loop (mode changes)
|
||||||
|
"enter_edit_mode_before" | "enter_edit_mode_after" | "enter_command_mode" | "exit_highlight_mode" => {
|
||||||
|
key_sequence_tracker.reset();
|
||||||
|
Ok("Mode change handled by main loop".to_string())
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
key_sequence_tracker.reset();
|
||||||
|
command_message.clear(); // Clear message for unhandled actions
|
||||||
|
Ok(format!("Unknown read-only action: {}", action))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
// src/modes/canvas/edit.rs
|
// src/modes/canvas/edit.rs
|
||||||
use crate::config::binds::config::Config;
|
use crate::config::binds::config::Config;
|
||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
use crate::state::pages::{auth::{LoginState, RegisterState}, canvas_state::CanvasState};
|
use crate::state::pages::{
|
||||||
|
auth::{LoginState, RegisterState},
|
||||||
|
canvas_state::CanvasState,
|
||||||
|
};
|
||||||
use crate::state::pages::form::FormState;
|
use crate::state::pages::form::FormState;
|
||||||
|
use crate::state::pages::add_table::AddTableState; // Added
|
||||||
use crate::modes::handlers::event::EventOutcome;
|
use crate::modes::handlers::event::EventOutcome;
|
||||||
use crate::functions::modes::edit::{auth_e, form_e};
|
use crate::functions::modes::edit::{auth_e, form_e};
|
||||||
|
use crate::functions::modes::edit::add_table_e; // Added
|
||||||
use crate::state::app::state::AppState;
|
use crate::state::app::state::AppState;
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
// Removed duplicate/unused imports
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum EditEventOutcome {
|
pub enum EditEventOutcome {
|
||||||
@@ -21,210 +25,319 @@ pub async fn handle_edit_event(
|
|||||||
form_state: &mut FormState,
|
form_state: &mut FormState,
|
||||||
login_state: &mut LoginState,
|
login_state: &mut LoginState,
|
||||||
register_state: &mut RegisterState,
|
register_state: &mut RegisterState,
|
||||||
|
add_table_state: &mut AddTableState, // Added
|
||||||
ideal_cursor_column: &mut usize,
|
ideal_cursor_column: &mut usize,
|
||||||
// command_message: &mut String, // Removed as messages are returned
|
|
||||||
current_position: &mut u64,
|
current_position: &mut u64,
|
||||||
total_count: u64,
|
total_count: u64,
|
||||||
grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
app_state: &AppState,
|
app_state: &AppState,
|
||||||
) -> Result<EditEventOutcome, Box<dyn std::error::Error>> {
|
) -> Result<EditEventOutcome, Box<dyn std::error::Error>> {
|
||||||
|
// Global command mode check (should ideally be handled before calling this function)
|
||||||
// Global command mode check
|
|
||||||
if let Some("enter_command_mode") = config.get_action_for_key_in_mode(
|
if let Some("enter_command_mode") = config.get_action_for_key_in_mode(
|
||||||
&config.keybindings.global,
|
&config.keybindings.global,
|
||||||
key.code,
|
key.code,
|
||||||
key.modifiers
|
key.modifiers,
|
||||||
) {
|
) {
|
||||||
return Ok(EditEventOutcome::Message("Switching to Command Mode...".to_string()));
|
// This mode change should likely be handled in event.rs
|
||||||
|
// Returning a message here might prevent the mode switch.
|
||||||
|
// Consider if this check is necessary here.
|
||||||
|
return Ok(EditEventOutcome::Message(
|
||||||
|
"Command mode entry handled globally.".to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common actions (save/revert)
|
|
||||||
if let Some(action) = config.get_action_for_key_in_mode(
|
if let Some(action) = config.get_action_for_key_in_mode(
|
||||||
&config.keybindings.common,
|
&config.keybindings.common,
|
||||||
key.code,
|
key.code,
|
||||||
key.modifiers
|
key.modifiers,
|
||||||
) {
|
).as_deref() {
|
||||||
if matches!(action, "save" | "revert") {
|
if matches!(action, "save" | "revert") {
|
||||||
// Ensure all branches result in Result<String, Error> before the final Ok(...) wrap
|
|
||||||
let message_string: String = if app_state.ui.show_login {
|
let message_string: String = if app_state.ui.show_login {
|
||||||
auth_e::execute_common_action(
|
auth_e::execute_common_action(
|
||||||
action,
|
action,
|
||||||
login_state,
|
login_state,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
current_position,
|
current_position,
|
||||||
total_count
|
total_count,
|
||||||
).await? // Results in String on success
|
)
|
||||||
|
.await?
|
||||||
} else if app_state.ui.show_register {
|
} else if app_state.ui.show_register {
|
||||||
|
// Keeping this block as requested
|
||||||
auth_e::execute_common_action(
|
auth_e::execute_common_action(
|
||||||
action,
|
action,
|
||||||
register_state,
|
register_state,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
current_position,
|
current_position,
|
||||||
total_count
|
total_count,
|
||||||
).await? // Results in String on success
|
)
|
||||||
} else {
|
.await? // Results in String on success
|
||||||
|
} else if app_state.ui.show_add_table {
|
||||||
|
// Placeholder - common actions for AddTable might be different
|
||||||
|
format!(
|
||||||
|
"Action '{}' not fully implemented for Add Table view here.",
|
||||||
|
action
|
||||||
|
)
|
||||||
|
} else { // Assuming FormState otherwise
|
||||||
let outcome = form_e::execute_common_action(
|
let outcome = form_e::execute_common_action(
|
||||||
action,
|
action,
|
||||||
form_state,
|
form_state,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
current_position,
|
current_position,
|
||||||
total_count
|
total_count,
|
||||||
).await?; // This returns EventOutcome on success
|
)
|
||||||
|
.await?;
|
||||||
// Extract the message string from the EventOutcome
|
|
||||||
match outcome {
|
match outcome {
|
||||||
EventOutcome::Ok(msg) => msg,
|
EventOutcome::Ok(msg) => msg,
|
||||||
EventOutcome::DataSaved(_, msg) => msg,
|
EventOutcome::DataSaved(_, msg) => msg,
|
||||||
_ => format!("Unexpected outcome from common action: {:?}", outcome),
|
_ => format!(
|
||||||
|
"Unexpected outcome from common action: {:?}",
|
||||||
|
outcome
|
||||||
|
),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Wrap the resulting String message
|
|
||||||
return Ok(EditEventOutcome::Message(message_string));
|
return Ok(EditEventOutcome::Message(message_string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edit-specific actions
|
// Edit-specific actions
|
||||||
if let Some(action) = config.get_edit_action_for_key(key.code, key.modifiers) {
|
if let Some(action) =
|
||||||
if action == "exit" {
|
config.get_edit_action_for_key(key.code, key.modifiers)
|
||||||
|
.as_deref() {
|
||||||
|
if action == "exit_edit_mode" {
|
||||||
|
// Handle exiting suggestion mode in Register view first
|
||||||
if app_state.ui.show_register && register_state.in_suggestion_mode {
|
if app_state.ui.show_register && register_state.in_suggestion_mode {
|
||||||
// Call the action, get Result<String, Error>
|
|
||||||
let msg = auth_e::execute_edit_action(
|
let msg = auth_e::execute_edit_action(
|
||||||
"exit_suggestion_mode",
|
"exit_suggestion_mode", // Specific action for suggestion exit
|
||||||
|
key,
|
||||||
|
register_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
grpc_client, // Pass necessary args if needed by auth_e
|
||||||
|
current_position,
|
||||||
|
total_count,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
return Ok(EditEventOutcome::Message(msg));
|
||||||
|
} else {
|
||||||
|
// Signal exit from Edit mode
|
||||||
|
return Ok(EditEventOutcome::ExitEditMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling for role field suggestions (Register view only)
|
||||||
|
if app_state.ui.show_register && register_state.current_field() == 4 {
|
||||||
|
// Check if Tab was pressed to *enter* suggestion mode
|
||||||
|
if !register_state.in_suggestion_mode
|
||||||
|
&& key.code == KeyCode::Tab
|
||||||
|
&& key.modifiers == KeyModifiers::NONE
|
||||||
|
{
|
||||||
|
register_state.update_role_suggestions();
|
||||||
|
if !register_state.role_suggestions.is_empty() {
|
||||||
|
register_state.in_suggestion_mode = true;
|
||||||
|
register_state.selected_suggestion_index = Some(0); // Select first suggestion
|
||||||
|
return Ok(EditEventOutcome::Message(
|
||||||
|
"Suggestions shown".to_string(),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return Ok(EditEventOutcome::Message(
|
||||||
|
"No suggestions available".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Handle suggestion navigation/selection if already in suggestion mode
|
||||||
|
if register_state.in_suggestion_mode
|
||||||
|
&& matches!(
|
||||||
|
action,
|
||||||
|
"suggestion_down"
|
||||||
|
| "suggestion_up"
|
||||||
|
| "select_suggestion"
|
||||||
|
)
|
||||||
|
{
|
||||||
|
let msg = auth_e::execute_edit_action(
|
||||||
|
action, // Pass the specific suggestion action
|
||||||
key,
|
key,
|
||||||
register_state,
|
register_state,
|
||||||
ideal_cursor_column,
|
ideal_cursor_column,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
current_position,
|
current_position,
|
||||||
total_count,
|
total_count,
|
||||||
).await?; // Results in String on success
|
)
|
||||||
// Wrap the String message
|
.await?;
|
||||||
return Ok(EditEventOutcome::Message(msg));
|
return Ok(EditEventOutcome::Message(msg));
|
||||||
} else {
|
|
||||||
// Signal exit
|
|
||||||
return Ok(EditEventOutcome::ExitEditMode);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special handling for role field suggestions
|
// Execute other edit actions based on the current view
|
||||||
|
let msg = if app_state.ui.show_login {
|
||||||
|
auth_e::execute_edit_action(
|
||||||
|
action,
|
||||||
|
key,
|
||||||
|
login_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
grpc_client,
|
||||||
|
current_position,
|
||||||
|
total_count,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
} else if app_state.ui.show_add_table {
|
||||||
|
add_table_e::execute_edit_action(
|
||||||
|
action,
|
||||||
|
key,
|
||||||
|
add_table_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
// Pass other necessary params if add_table_e needs them
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
} else if app_state.ui.show_register {
|
||||||
|
auth_e::execute_edit_action(
|
||||||
|
action,
|
||||||
|
key,
|
||||||
|
register_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
grpc_client,
|
||||||
|
current_position,
|
||||||
|
total_count,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
} else {
|
||||||
|
// Assuming FormState otherwise
|
||||||
|
form_e::execute_edit_action(
|
||||||
|
action,
|
||||||
|
key,
|
||||||
|
form_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
grpc_client,
|
||||||
|
current_position,
|
||||||
|
total_count,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
};
|
||||||
|
return Ok(EditEventOutcome::Message(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Character insertion ---
|
||||||
|
if let KeyCode::Char(c) = key.code {
|
||||||
|
// Exit suggestion mode in Register view if a character is typed
|
||||||
|
if app_state.ui.show_register && register_state.in_suggestion_mode {
|
||||||
|
register_state.in_suggestion_mode = false;
|
||||||
|
register_state.show_role_suggestions = false;
|
||||||
|
register_state.selected_suggestion_index = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute insert_char action based on the current view
|
||||||
|
let msg = if app_state.ui.show_login {
|
||||||
|
auth_e::execute_edit_action(
|
||||||
|
"insert_char",
|
||||||
|
key, // Pass the key event containing the char
|
||||||
|
login_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
grpc_client,
|
||||||
|
current_position,
|
||||||
|
total_count,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
} else if app_state.ui.show_add_table {
|
||||||
|
add_table_e::execute_edit_action(
|
||||||
|
"insert_char",
|
||||||
|
key,
|
||||||
|
add_table_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
} else if app_state.ui.show_register {
|
||||||
|
auth_e::execute_edit_action(
|
||||||
|
"insert_char",
|
||||||
|
key,
|
||||||
|
register_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
grpc_client,
|
||||||
|
current_position,
|
||||||
|
total_count,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
} else {
|
||||||
|
// Assuming FormState otherwise
|
||||||
|
form_e::execute_edit_action(
|
||||||
|
"insert_char",
|
||||||
|
key,
|
||||||
|
form_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
grpc_client,
|
||||||
|
current_position,
|
||||||
|
total_count,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
};
|
||||||
|
// Update role suggestions after insertion if needed (Register view)
|
||||||
if app_state.ui.show_register && register_state.current_field() == 4 {
|
if app_state.ui.show_register && register_state.current_field() == 4 {
|
||||||
if !register_state.in_suggestion_mode && key.code == KeyCode::Tab && key.modifiers == KeyModifiers::NONE {
|
register_state.update_role_suggestions();
|
||||||
register_state.update_role_suggestions();
|
|
||||||
if !register_state.role_suggestions.is_empty() {
|
|
||||||
register_state.in_suggestion_mode = true;
|
|
||||||
register_state.selected_suggestion_index = Some(0);
|
|
||||||
return Ok(EditEventOutcome::Message("Suggestions shown".to_string()));
|
|
||||||
} else { // Added else here for clarity
|
|
||||||
return Ok(EditEventOutcome::Message("No suggestions available".to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute other edit actions
|
|
||||||
let msg = if app_state.ui.show_login {
|
|
||||||
auth_e::execute_edit_action(
|
|
||||||
action,
|
|
||||||
key,
|
|
||||||
login_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
grpc_client,
|
|
||||||
current_position,
|
|
||||||
total_count
|
|
||||||
).await? // Results in String
|
|
||||||
} else if app_state.ui.show_register {
|
|
||||||
auth_e::execute_edit_action(
|
|
||||||
action,
|
|
||||||
key,
|
|
||||||
register_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
grpc_client,
|
|
||||||
current_position,
|
|
||||||
total_count
|
|
||||||
).await? // Results in String
|
|
||||||
} else {
|
|
||||||
form_e::execute_edit_action(
|
|
||||||
action,
|
|
||||||
key,
|
|
||||||
form_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
grpc_client,
|
|
||||||
current_position,
|
|
||||||
total_count
|
|
||||||
).await? // Results in String
|
|
||||||
};
|
|
||||||
// Wrap the resulting String message
|
|
||||||
return Ok(EditEventOutcome::Message(msg));
|
return Ok(EditEventOutcome::Message(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Character insertion
|
// --- Handle Backspace/Delete ---
|
||||||
if let KeyCode::Char(_) = key.code {
|
|
||||||
if app_state.ui.show_register && register_state.in_suggestion_mode {
|
|
||||||
register_state.in_suggestion_mode = false;
|
|
||||||
register_state.show_role_suggestions = false;
|
|
||||||
register_state.selected_suggestion_index = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute insert_char action
|
|
||||||
let msg = if app_state.ui.show_login {
|
|
||||||
auth_e::execute_edit_action(
|
|
||||||
"insert_char",
|
|
||||||
key,
|
|
||||||
login_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
grpc_client,
|
|
||||||
current_position,
|
|
||||||
total_count
|
|
||||||
).await? // Results in String
|
|
||||||
} else if app_state.ui.show_register {
|
|
||||||
auth_e::execute_edit_action(
|
|
||||||
"insert_char",
|
|
||||||
key,
|
|
||||||
register_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
grpc_client,
|
|
||||||
current_position,
|
|
||||||
total_count
|
|
||||||
).await? // Results in String
|
|
||||||
} else {
|
|
||||||
form_e::execute_edit_action(
|
|
||||||
"insert_char",
|
|
||||||
key,
|
|
||||||
form_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
grpc_client,
|
|
||||||
current_position,
|
|
||||||
total_count
|
|
||||||
).await? // Results in String
|
|
||||||
};
|
|
||||||
// Wrap the resulting String message
|
|
||||||
return Ok(EditEventOutcome::Message(msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle Backspace/Delete
|
|
||||||
if matches!(key.code, KeyCode::Backspace | KeyCode::Delete) {
|
if matches!(key.code, KeyCode::Backspace | KeyCode::Delete) {
|
||||||
|
// Exit suggestion mode in Register view
|
||||||
if app_state.ui.show_register && register_state.in_suggestion_mode {
|
if app_state.ui.show_register && register_state.in_suggestion_mode {
|
||||||
register_state.in_suggestion_mode = false;
|
register_state.in_suggestion_mode = false;
|
||||||
register_state.show_role_suggestions = false;
|
register_state.show_role_suggestions = false;
|
||||||
register_state.selected_suggestion_index = None;
|
register_state.selected_suggestion_index = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let action_str = if key.code == KeyCode::Backspace { "backspace" } else { "delete_char" };
|
let action_str = if key.code == KeyCode::Backspace {
|
||||||
// Ensure both branches result in a String *before* wrapping
|
"delete_char_backward"
|
||||||
let result_msg: String = if app_state.ui.show_register {
|
} else {
|
||||||
auth_e::execute_edit_action(
|
"delete_char_forward"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Execute delete action based on the current view
|
||||||
|
let result_msg: String = if app_state.ui.show_login {
|
||||||
|
auth_e::execute_edit_action(
|
||||||
|
action_str,
|
||||||
|
key,
|
||||||
|
login_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
grpc_client,
|
||||||
|
current_position,
|
||||||
|
total_count,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
} else if app_state.ui.show_add_table {
|
||||||
|
add_table_e::execute_edit_action(
|
||||||
|
action_str,
|
||||||
|
key,
|
||||||
|
add_table_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
} else if app_state.ui.show_register {
|
||||||
|
auth_e::execute_edit_action(
|
||||||
action_str,
|
action_str,
|
||||||
key,
|
key,
|
||||||
register_state,
|
register_state,
|
||||||
ideal_cursor_column,
|
ideal_cursor_column,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
current_position,
|
current_position,
|
||||||
total_count
|
total_count,
|
||||||
).await? // Results in String
|
)
|
||||||
|
.await?
|
||||||
} else {
|
} else {
|
||||||
// Return String directly, not Ok(String)
|
// Assuming FormState otherwise
|
||||||
"Action not applicable here".to_string()
|
form_e::execute_edit_action(
|
||||||
}; // Semicolon here ends the if/else expression
|
action_str,
|
||||||
|
key,
|
||||||
|
form_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
grpc_client,
|
||||||
|
current_position,
|
||||||
|
total_count
|
||||||
|
).await?
|
||||||
|
};
|
||||||
|
// Update role suggestions after deletion if needed (Register view)
|
||||||
|
if app_state.ui.show_register && register_state.current_field() == 4 {
|
||||||
|
register_state.update_role_suggestions();
|
||||||
|
}
|
||||||
|
|
||||||
// Wrap the resulting String message
|
|
||||||
return Ok(EditEventOutcome::Message(result_msg));
|
return Ok(EditEventOutcome::Message(result_msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ use crate::services::grpc_client::GrpcClient;
|
|||||||
use crate::state::pages::{canvas_state::CanvasState, auth::RegisterState};
|
use crate::state::pages::{canvas_state::CanvasState, auth::RegisterState};
|
||||||
use crate::state::pages::auth::LoginState;
|
use crate::state::pages::auth::LoginState;
|
||||||
use crate::state::pages::form::FormState;
|
use crate::state::pages::form::FormState;
|
||||||
|
use crate::state::pages::add_table::AddTableState;
|
||||||
use crate::state::app::state::AppState;
|
use crate::state::app::state::AppState;
|
||||||
use crate::functions::modes::read_only::{auth_ro, form_ro};
|
use crate::functions::modes::read_only::{auth_ro, form_ro, add_table_ro};
|
||||||
use crossterm::event::KeyEvent;
|
use crossterm::event::KeyEvent;
|
||||||
|
|
||||||
pub async fn handle_read_only_event(
|
pub async fn handle_read_only_event(
|
||||||
@@ -17,6 +18,7 @@ pub async fn handle_read_only_event(
|
|||||||
form_state: &mut FormState,
|
form_state: &mut FormState,
|
||||||
login_state: &mut LoginState,
|
login_state: &mut LoginState,
|
||||||
register_state: &mut RegisterState,
|
register_state: &mut RegisterState,
|
||||||
|
add_table_state: &mut AddTableState,
|
||||||
key_sequence_tracker: &mut KeySequenceTracker,
|
key_sequence_tracker: &mut KeySequenceTracker,
|
||||||
current_position: &mut u64,
|
current_position: &mut u64,
|
||||||
total_count: u64,
|
total_count: u64,
|
||||||
@@ -32,34 +34,17 @@ pub async fn handle_read_only_event(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.is_enter_edit_mode_after(key.code, key.modifiers) {
|
if config.is_enter_edit_mode_after(key.code, key.modifiers) {
|
||||||
let (current_input, current_pos) = if app_state.ui.show_login { // Check Login first
|
// Determine target state to adjust cursor
|
||||||
(
|
let target_state: &mut dyn CanvasState = if app_state.ui.show_login { login_state }
|
||||||
login_state.get_current_input(),
|
else if app_state.ui.show_register { register_state }
|
||||||
login_state.current_cursor_pos(),
|
else if app_state.ui.show_add_table { add_table_state }
|
||||||
)
|
else { form_state };
|
||||||
} else if app_state.ui.show_register { // Then check Register
|
let current_input = target_state.get_current_input();
|
||||||
(
|
let current_pos = target_state.current_cursor_pos();
|
||||||
register_state.get_current_input(),
|
|
||||||
register_state.current_cursor_pos(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
form_state.get_current_input(),
|
|
||||||
form_state.current_cursor_pos(),
|
|
||||||
) // Default to Form
|
|
||||||
};
|
|
||||||
|
|
||||||
if !current_input.is_empty() && current_pos < current_input.len() {
|
if !current_input.is_empty() && current_pos < current_input.len() {
|
||||||
if app_state.ui.show_login {
|
target_state.set_current_cursor_pos(current_pos + 1);
|
||||||
login_state.set_current_cursor_pos(current_pos + 1);
|
*ideal_cursor_column = target_state.current_cursor_pos();
|
||||||
*ideal_cursor_column = login_state.current_cursor_pos();
|
|
||||||
} else if app_state.ui.show_register {
|
|
||||||
register_state.set_current_cursor_pos(current_pos + 1);
|
|
||||||
*ideal_cursor_column = register_state.current_cursor_pos();
|
|
||||||
} else { // Default to Form
|
|
||||||
form_state.set_current_cursor_pos(current_pos + 1);
|
|
||||||
*ideal_cursor_column = form_state.current_cursor_pos();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
*edit_mode_cooldown = true;
|
*edit_mode_cooldown = true;
|
||||||
*command_message = "Entering Edit mode (after cursor)".to_string();
|
*command_message = "Entering Edit mode (after cursor)".to_string();
|
||||||
@@ -83,7 +68,7 @@ pub async fn handle_read_only_event(
|
|||||||
key_sequence_tracker.add_key(key.code);
|
key_sequence_tracker.add_key(key.code);
|
||||||
let sequence = key_sequence_tracker.get_sequence();
|
let sequence = key_sequence_tracker.get_sequence();
|
||||||
|
|
||||||
if let Some(action) = config.matches_key_sequence_generalized(&sequence) {
|
if let Some(action) = config.matches_key_sequence_generalized(&sequence).as_deref() {
|
||||||
let result = if app_state.ui.show_form && CONTEXT_ACTIONS_FORM.contains(&action) {
|
let result = if app_state.ui.show_form && CONTEXT_ACTIONS_FORM.contains(&action) {
|
||||||
crate::tui::functions::form::handle_action(
|
crate::tui::functions::form::handle_action(
|
||||||
action,
|
action,
|
||||||
@@ -96,6 +81,15 @@ pub async fn handle_read_only_event(
|
|||||||
.await?
|
.await?
|
||||||
} else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { // Handle login context actions
|
} else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { // Handle login context actions
|
||||||
crate::tui::functions::login::handle_action(action).await?
|
crate::tui::functions::login::handle_action(action).await?
|
||||||
|
} else if app_state.ui.show_add_table {
|
||||||
|
add_table_ro::execute_action(
|
||||||
|
action,
|
||||||
|
app_state,
|
||||||
|
add_table_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
key_sequence_tracker,
|
||||||
|
command_message,
|
||||||
|
).await?
|
||||||
} else if app_state.ui.show_register{
|
} else if app_state.ui.show_register{
|
||||||
auth_ro::execute_action(
|
auth_ro::execute_action(
|
||||||
action,
|
action,
|
||||||
@@ -134,7 +128,7 @@ pub async fn handle_read_only_event(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if sequence.len() == 1 && !config.is_key_sequence_prefix(&sequence) {
|
if sequence.len() == 1 && !config.is_key_sequence_prefix(&sequence) {
|
||||||
if let Some(action) = config.get_read_only_action_for_key(key.code, key.modifiers) {
|
if let Some(action) = config.get_read_only_action_for_key(key.code, key.modifiers).as_deref() {
|
||||||
let result = if app_state.ui.show_form && CONTEXT_ACTIONS_FORM.contains(&action) {
|
let result = if app_state.ui.show_form && CONTEXT_ACTIONS_FORM.contains(&action) {
|
||||||
crate::tui::functions::form::handle_action(
|
crate::tui::functions::form::handle_action(
|
||||||
action,
|
action,
|
||||||
@@ -147,6 +141,15 @@ pub async fn handle_read_only_event(
|
|||||||
.await?
|
.await?
|
||||||
} else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { // Handle login context actions
|
} else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { // Handle login context actions
|
||||||
crate::tui::functions::login::handle_action(action).await?
|
crate::tui::functions::login::handle_action(action).await?
|
||||||
|
} else if app_state.ui.show_add_table {
|
||||||
|
add_table_ro::execute_action(
|
||||||
|
action,
|
||||||
|
app_state,
|
||||||
|
add_table_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
key_sequence_tracker,
|
||||||
|
command_message,
|
||||||
|
).await?
|
||||||
} else if app_state.ui.show_register /* && CONTEXT_ACTIONS_REGISTER.contains(&action) */ { // Handle register general actions
|
} else if app_state.ui.show_register /* && CONTEXT_ACTIONS_REGISTER.contains(&action) */ { // Handle register general actions
|
||||||
auth_ro::execute_action(
|
auth_ro::execute_action(
|
||||||
action,
|
action,
|
||||||
@@ -184,7 +187,7 @@ pub async fn handle_read_only_event(
|
|||||||
} else {
|
} else {
|
||||||
key_sequence_tracker.reset();
|
key_sequence_tracker.reset();
|
||||||
|
|
||||||
if let Some(action) = config.get_read_only_action_for_key(key.code, key.modifiers) {
|
if let Some(action) = config.get_read_only_action_for_key(key.code, key.modifiers).as_deref() {
|
||||||
let result = if app_state.ui.show_form && CONTEXT_ACTIONS_FORM.contains(&action) {
|
let result = if app_state.ui.show_form && CONTEXT_ACTIONS_FORM.contains(&action) {
|
||||||
crate::tui::functions::form::handle_action(
|
crate::tui::functions::form::handle_action(
|
||||||
action,
|
action,
|
||||||
@@ -195,8 +198,17 @@ pub async fn handle_read_only_event(
|
|||||||
ideal_cursor_column,
|
ideal_cursor_column,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
} else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { // Handle login context actions
|
} else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) {
|
||||||
crate::tui::functions::login::handle_action(action).await?
|
crate::tui::functions::login::handle_action(action).await?
|
||||||
|
} else if app_state.ui.show_add_table {
|
||||||
|
add_table_ro::execute_action(
|
||||||
|
action,
|
||||||
|
app_state,
|
||||||
|
add_table_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
key_sequence_tracker,
|
||||||
|
command_message,
|
||||||
|
).await?
|
||||||
} else if app_state.ui.show_register /* && CONTEXT_ACTIONS_REGISTER.contains(&action) */ { // Handle register general actions
|
} else if app_state.ui.show_register /* && CONTEXT_ACTIONS_REGISTER.contains(&action) */ { // Handle register general actions
|
||||||
auth_ro::execute_action(
|
auth_ro::execute_action(
|
||||||
action,
|
action,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ use crate::state::{
|
|||||||
auth::{AuthState, LoginState, RegisterState},
|
auth::{AuthState, LoginState, RegisterState},
|
||||||
admin::AdminState,
|
admin::AdminState,
|
||||||
canvas_state::CanvasState,
|
canvas_state::CanvasState,
|
||||||
|
add_table::AddTableState,
|
||||||
form::FormState,
|
form::FormState,
|
||||||
intro::IntroState,
|
intro::IntroState,
|
||||||
},
|
},
|
||||||
@@ -349,6 +350,7 @@ impl EventHandler {
|
|||||||
form_state,
|
form_state,
|
||||||
login_state,
|
login_state,
|
||||||
register_state,
|
register_state,
|
||||||
|
&mut admin_state.add_table_state,
|
||||||
&mut self.key_sequence_tracker,
|
&mut self.key_sequence_tracker,
|
||||||
current_position,
|
current_position,
|
||||||
total_count,
|
total_count,
|
||||||
@@ -383,7 +385,7 @@ impl EventHandler {
|
|||||||
|
|
||||||
let (_should_exit, message) = read_only::handle_read_only_event(
|
let (_should_exit, message) = read_only::handle_read_only_event(
|
||||||
app_state, key, config, form_state, login_state,
|
app_state, key, config, form_state, login_state,
|
||||||
register_state, &mut self.key_sequence_tracker,
|
register_state, &mut admin_state.add_table_state, &mut self.key_sequence_tracker,
|
||||||
current_position, total_count, grpc_client,
|
current_position, total_count, grpc_client,
|
||||||
&mut self.command_message, &mut self.edit_mode_cooldown,
|
&mut self.command_message, &mut self.edit_mode_cooldown,
|
||||||
&mut self.ideal_cursor_column,
|
&mut self.ideal_cursor_column,
|
||||||
@@ -431,6 +433,7 @@ impl EventHandler {
|
|||||||
form_state,
|
form_state,
|
||||||
login_state,
|
login_state,
|
||||||
register_state,
|
register_state,
|
||||||
|
&mut admin_state.add_table_state,
|
||||||
&mut self.ideal_cursor_column,
|
&mut self.ideal_cursor_column,
|
||||||
current_position,
|
current_position,
|
||||||
total_count,
|
total_count,
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
use crate::state::app::state::AppState;
|
use crate::state::app::state::AppState;
|
||||||
use crate::modes::handlers::event::EventHandler;
|
use crate::modes::handlers::event::EventHandler;
|
||||||
use crate::state::app::highlight::HighlightState;
|
use crate::state::app::highlight::HighlightState;
|
||||||
|
use crate::state::pages::add_table::AddTableFocus;
|
||||||
|
use crate::state::pages::admin::AdminState;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum AppMode {
|
pub enum AppMode {
|
||||||
@@ -29,17 +31,20 @@ impl ModeManager {
|
|||||||
return AppMode::General;
|
return AppMode::General;
|
||||||
}
|
}
|
||||||
|
|
||||||
if app_state.ui.show_login || app_state.ui.show_register {
|
let is_canvas_view = app_state.ui.show_login
|
||||||
if event_handler.is_edit_mode {
|
|| app_state.ui.show_register
|
||||||
AppMode::Edit
|
|| app_state.ui.show_form
|
||||||
|
|| app_state.ui.show_add_table;
|
||||||
|
|
||||||
|
if is_canvas_view {
|
||||||
|
if app_state.ui.focus_outside_canvas {
|
||||||
|
AppMode::General
|
||||||
} else {
|
} else {
|
||||||
AppMode::ReadOnly
|
if event_handler.is_edit_mode {
|
||||||
}
|
AppMode::Edit
|
||||||
} else if app_state.ui.show_form {
|
} else {
|
||||||
if event_handler.is_edit_mode {
|
AppMode::ReadOnly
|
||||||
AppMode::Edit
|
}
|
||||||
} else {
|
|
||||||
AppMode::ReadOnly
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
AppMode::General
|
AppMode::General
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::config::binds::key_sequences::KeySequenceTracker;
|
|||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
use crate::state::app::state::AppState;
|
use crate::state::app::state::AppState;
|
||||||
use crate::state::pages::auth::{LoginState, RegisterState};
|
use crate::state::pages::auth::{LoginState, RegisterState};
|
||||||
|
use crate::state::pages::add_table::AddTableState;
|
||||||
use crate::state::pages::form::FormState;
|
use crate::state::pages::form::FormState;
|
||||||
use crate::modes::handlers::event::EventOutcome;
|
use crate::modes::handlers::event::EventOutcome;
|
||||||
use crate::modes::read_only; // Import the ReadOnly handler
|
use crate::modes::read_only; // Import the ReadOnly handler
|
||||||
@@ -21,6 +22,7 @@ pub async fn handle_highlight_event(
|
|||||||
form_state: &mut FormState,
|
form_state: &mut FormState,
|
||||||
login_state: &mut LoginState,
|
login_state: &mut LoginState,
|
||||||
register_state: &mut RegisterState,
|
register_state: &mut RegisterState,
|
||||||
|
add_table_state: &mut AddTableState,
|
||||||
key_sequence_tracker: &mut KeySequenceTracker,
|
key_sequence_tracker: &mut KeySequenceTracker,
|
||||||
current_position: &mut u64,
|
current_position: &mut u64,
|
||||||
total_count: u64,
|
total_count: u64,
|
||||||
@@ -38,6 +40,7 @@ pub async fn handle_highlight_event(
|
|||||||
form_state,
|
form_state,
|
||||||
login_state,
|
login_state,
|
||||||
register_state,
|
register_state,
|
||||||
|
add_table_state,
|
||||||
key_sequence_tracker,
|
key_sequence_tracker,
|
||||||
current_position,
|
current_position,
|
||||||
total_count,
|
total_count,
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ impl Default for AddTableState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AddTableState {
|
impl AddTableState {
|
||||||
const INPUT_FIELD_COUNT: usize = 3;
|
pub const INPUT_FIELD_COUNT: usize = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement CanvasState for the input fields
|
// Implement CanvasState for the input fields
|
||||||
|
|||||||
Reference in New Issue
Block a user