From 5c87168fb9935de64e6c8d6378518782626e41d1 Mon Sep 17 00:00:00 2001 From: filipriec Date: Fri, 23 May 2025 21:59:17 +0200 Subject: [PATCH] we compiled multi canvas, needs so much work to do now --- client/src/components/admin/add_logic.rs | 75 ++-- client/src/components/handlers.rs | 2 + .../src/components/handlers/multi_canvas.rs | 2 +- .../src/functions/modes/edit/add_logic_e.rs | 421 ++++++++++-------- .../modes/navigation/add_logic_nav.rs | 275 +++++++----- client/src/state/pages/add_logic.rs | 60 ++- 6 files changed, 503 insertions(+), 332 deletions(-) diff --git a/client/src/components/admin/add_logic.rs b/client/src/components/admin/add_logic.rs index d9cb6731..075cfd95 100644 --- a/client/src/components/admin/add_logic.rs +++ b/client/src/components/admin/add_logic.rs @@ -3,16 +3,21 @@ use crate::config::colors::themes::Theme; use crate::state::app::highlight::HighlightState; use crate::state::app::state::AppState; use crate::state::pages::add_logic::{AddLogicFocus, AddLogicState}; -use crate::state::pages::canvas_state::CanvasState; +use crate::state::pages::canvas_state::CanvasState; // For the top 3 fields use ratatui::{ layout::{Alignment, Constraint, Direction, Layout, Rect}, style::{Modifier, Style}, - text::{Line, Span, Text}, + text::{Line, Span}, widgets::{Block, BorderType, Borders, Paragraph}, Frame, }; -use crate::components::handlers::canvas::render_canvas; +use crate::components::handlers::canvas::render_canvas as render_single_line_canvas; // Alias for clarity use crate::components::common::dialog; +// Use your existing multiline renderer +use crate::components::handlers::multi_canvas::render_multiline_editor; +// We also need the trait for the renderer's signature +use crate::state::pages::multi_canvas_state::MultilineEditorState; + pub fn render_add_logic( f: &mut Frame, @@ -33,7 +38,6 @@ pub fn render_add_logic( let inner_area = main_block.inner(area); f.render_widget(main_block, area); - // Calculate areas dynamically like add_table let main_chunks = Layout::default() .direction(Direction::Vertical) .constraints([ @@ -49,19 +53,21 @@ pub fn render_add_logic( let script_content_area = main_chunks[2]; let buttons_area = main_chunks[3]; - // Top Info Rendering (like add_table) + // Top Info Rendering let profile_text = Paragraph::new(vec![ Line::from(Span::styled( format!("Profile: {}", add_logic_state.profile_name), - theme.fg, + Style::default().fg(theme.fg), )), Line::from(Span::styled( - format!("Table: {}", - add_logic_state.selected_table_id + format!( + "Table: {}", + add_logic_state + .selected_table_id .map(|id| format!("ID {}", id)) .unwrap_or_else(|| "Global".to_string()) ), - theme.fg, + Style::default().fg(theme.fg), )), ]) .block( @@ -71,47 +77,43 @@ pub fn render_add_logic( ); f.render_widget(profile_text, top_info_area); - // Canvas rendering for input fields (like add_table) - let focus_on_canvas_inputs = matches!( + // Single-line canvas rendering for top input fields + let focus_on_single_line_canvas_inputs = matches!( add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription ); - render_canvas( + render_single_line_canvas( f, canvas_area, - add_logic_state, + add_logic_state, // Implements CanvasState for these fields &add_logic_state.fields(), &add_logic_state.current_field(), &add_logic_state.inputs(), theme, - is_edit_mode && focus_on_canvas_inputs, + is_edit_mode && focus_on_single_line_canvas_inputs, highlight_state, ); - // Script Content Area - let script_block_border_style = if add_logic_state.current_focus == AddLogicFocus::InputScriptContent { - Style::default().fg(theme.highlight) - } else { - Style::default().fg(theme.secondary) - }; + // --- Script Content Area using your MultilineEditor --- + let is_script_editor_focused = + add_logic_state.current_focus == AddLogicFocus::InputScriptContent; - let script_block = Block::default() - .title(" Steel Script Content ") - .borders(Borders::ALL) - .border_type(BorderType::Rounded) - .border_style(script_block_border_style); + // Pass the script_editor field which implements MultilineEditorState + // The `render_multiline_editor` from canvas_multi.rs takes `&impl MultilineEditorState` + render_multiline_editor( + f, + script_content_area, + &add_logic_state.script_editor, // Pass your BasicMultilineEditor instance + theme, + is_script_editor_focused && is_edit_mode, // Editor is active for input + highlight_state, // Pass the global highlight state + ); - let script_text = Text::from(add_logic_state.script_content_input.as_str()); - let script_paragraph = Paragraph::new(script_text) - .block(script_block) - .scroll(add_logic_state.script_content_scroll) - .style(Style::default().fg(theme.fg)); - f.render_widget(script_paragraph, script_content_area); - // Button Style Helpers (same as add_table) + // Button Style Helpers let get_button_style = |button_focus: AddLogicFocus, current_focus| { let is_focused = current_focus == button_focus; let base_style = Style::default().fg(if is_focused { @@ -134,12 +136,12 @@ pub fn render_add_logic( } }; - // Bottom Buttons (same style as add_table) + // Bottom Buttons let button_chunks = Layout::default() .direction(Direction::Horizontal) .constraints([ - Constraint::Percentage(50), // Save Button - Constraint::Percentage(50), // Cancel Button + Constraint::Percentage(50), + Constraint::Percentage(50), ]) .split(buttons_area); @@ -177,7 +179,7 @@ pub fn render_add_logic( ); f.render_widget(cancel_button, button_chunks[1]); - // Dialog rendering (same as add_table) + // Dialog rendering if app_state.ui.dialog.dialog_show { dialog::render_dialog( f, @@ -191,4 +193,3 @@ pub fn render_add_logic( ); } } - diff --git a/client/src/components/handlers.rs b/client/src/components/handlers.rs index ba43b77f..f41d23c7 100644 --- a/client/src/components/handlers.rs +++ b/client/src/components/handlers.rs @@ -2,7 +2,9 @@ pub mod canvas; pub mod sidebar; pub mod buffer_list; +pub mod multi_canvas; pub use canvas::*; pub use sidebar::*; pub use buffer_list::*; +pub use multi_canvas::*; diff --git a/client/src/components/handlers/multi_canvas.rs b/client/src/components/handlers/multi_canvas.rs index 1d48adf0..37c3343f 100644 --- a/client/src/components/handlers/multi_canvas.rs +++ b/client/src/components/handlers/multi_canvas.rs @@ -9,7 +9,7 @@ use ratatui::{ }; use crate::config::colors::themes::Theme; // Import the new trait -use crate::state::pages::multiline_editor_state::MultilineEditorState; +use crate::state::pages::multi_canvas_state::MultilineEditorState; use crate::state::app::highlight::HighlightState; // Assuming this is your global highlight state use std::cmp::{min, max}; diff --git a/client/src/functions/modes/edit/add_logic_e.rs b/client/src/functions/modes/edit/add_logic_e.rs index f16a6baa..ce43fa1e 100644 --- a/client/src/functions/modes/edit/add_logic_e.rs +++ b/client/src/functions/modes/edit/add_logic_e.rs @@ -1,8 +1,11 @@ // src/functions/modes/edit/add_logic_e.rs -use crate::state::pages::add_logic::AddLogicState; // Changed +use crate::state::pages::add_logic::{AddLogicFocus, AddLogicState}; // Added AddLogicFocus use crate::state::pages::canvas_state::CanvasState; use crossterm::event::{KeyCode, KeyEvent}; use anyhow::Result; +// Import the trait your BasicMultilineEditor implements +use crate::state::pages::multi_canvas_state::MultilineEditorState; + // Word navigation helpers (get_char_type, find_next_word_start, etc.) // can be kept as they are generic. @@ -69,209 +72,279 @@ fn find_prev_word_end(text: &str, current_pos: usize) -> usize { if pos > 0 { pos - 1 } else { 0 } } -/// Executes edit actions for the AddLogic view canvas. + pub async fn execute_edit_action( - action: &str, - key: KeyEvent, - state: &mut AddLogicState, // Changed - ideal_cursor_column: &mut usize, + action: &str, // This 'action' string comes from keybindings for edit mode + key: KeyEvent, // The raw key event, useful for Char input + state: &mut AddLogicState, + ideal_cursor_column: &mut usize, // Used for single-line canvas fields ) -> Result { - match action { - "insert_char" => { - if let KeyCode::Char(c) = key.code { + // Check if the current focus is on the script content editor + if state.current_focus == AddLogicFocus::InputScriptContent { + // Delegate to script editor's methods based on key or mapped action + match key.code { + KeyCode::Char(c) => { + // If action is "insert_char" or if no specific action mapped, treat as char input + if action == "insert_char" || action.is_empty() { // Assuming unmapped keys might default to char insert + state.script_editor.insert_char_at_cursor(c); + state.script_editor.set_has_unsaved_changes(true); + return Ok("".to_string()); + } + } + KeyCode::Enter => { + state.script_editor.insert_newline_at_cursor(); + state.script_editor.set_has_unsaved_changes(true); + return Ok("".to_string()); + } + KeyCode::Backspace => { + state.script_editor.delete_char_before_cursor(); + state.script_editor.set_has_unsaved_changes(true); + return Ok("".to_string()); + } + KeyCode::Delete => { + state.script_editor.delete_char_at_cursor(); + state.script_editor.set_has_unsaved_changes(true); + return Ok("".to_string()); + } + KeyCode::Left => { + state.script_editor.move_cursor_left(); + return Ok("".to_string()); + } + KeyCode::Right => { + state.script_editor.move_cursor_right(); + return Ok("".to_string()); + } + KeyCode::Up => { + state.script_editor.move_cursor_up(); + return Ok("".to_string()); + } + KeyCode::Down => { + state.script_editor.move_cursor_down(); + return Ok("".to_string()); + } + // Home, End, PageUp, PageDown could be added here for the script_editor + // KeyCode::Home => { state.script_editor.move_cursor_to_line_start(); Ok("".to_string()) } + // KeyCode::End => { state.script_editor.move_cursor_to_line_end(); Ok("".to_string()) } + _ => {} // Let it fall through to action string matching if needed + } + // Handle actions specific to multiline editor that might be bound to other keys + match action { + "move_left" => { state.script_editor.move_cursor_left(); Ok("".to_string()) } + "move_right" => { state.script_editor.move_cursor_right(); Ok("".to_string()) } + "move_up" => { state.script_editor.move_cursor_up(); Ok("".to_string()) } + "move_down" => { state.script_editor.move_cursor_down(); Ok("".to_string()) } + // "next_field" and "prev_field" in script editor context might mean nothing, + // or could exit the editor focus. This is handled by the navigation logic. + // "exit_edit_mode" is also typically handled by the caller (mode_manager). + "exit_edit_mode" | "save" | "revert" | "next_field" | "prev_field" => { + Ok(format!("Action '{}' not directly handled by script editor, caller should manage.", action)) + } + _ => Ok(format!("Unknown/unhandled edit action for script: {}", action)), + } + } else { + // Original logic for single-line canvas fields + 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(); // This uses CanvasState + let mut chars: Vec = 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); // For single-line canvas + *ideal_cursor_column = state.current_cursor_pos(); + } + } else { + return Ok("Error: insert_char called without a char key.".to_string()); + } + Ok("".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 = 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 = field_value.chars().collect(); - if cursor_pos <= chars.len() { - chars.insert(cursor_pos, c); + if cursor_pos < chars.len() { + chars.remove(cursor_pos); *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(); + *ideal_cursor_column = cursor_pos; } - } else { - return Ok("Error: insert_char called without a char key.".to_string()); + Ok("".to_string()) } - Ok("".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 = 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; + "next_field" => { // For single-line canvas + let num_fields = AddLogicState::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 { + state.set_current_field(current_field + 1); + } + // else { // Optionally, if at last field, Tab could move to InputScriptContent + // state.current_focus = AddLogicFocus::InputScriptContent; + // } + 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()) } - 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 = 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 = AddLogicState::INPUT_FIELD_COUNT; // Changed - if num_fields > 0 { - let current_field = state.current_field(); - let last_field_index = num_fields - 1; - if current_field < last_field_index { // Prevent cycling - state.set_current_field(current_field + 1); + "prev_field" => { // For single-line canvas + let num_fields = AddLogicState::INPUT_FIELD_COUNT; + if num_fields > 0 { + let current_field = state.current_field(); + if current_field > 0 { + state.set_current_field(current_field - 1); + } + // else { // Optionally, if at first field, Shift-Tab could move to CancelButton or similar + // state.current_focus = AddLogicFocus::CancelButton; // Example + // } + let current_input = state.get_current_input(); + let max_pos = current_input.len(); + state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos)); } - 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()) } - Ok("".to_string()) - } - "prev_field" => { - let num_fields = AddLogicState::INPUT_FIELD_COUNT; // Changed - if num_fields > 0 { - let current_field = state.current_field(); - if current_field > 0 { // Prevent cycling - state.set_current_field(current_field - 1); - } - 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; + "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()) } - Ok("".to_string()) - } - "move_up" => { // In edit mode, up/down usually means prev/next field - let current_field = state.current_field(); - if current_field > 0 { - let new_field = current_field - 1; - state.set_current_field(new_field); + "move_right" => { let current_input = state.get_current_input(); - let max_pos = current_input.len(); - state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos)); + 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()) } - Ok("".to_string()) - } - "move_down" => { // In edit mode, up/down usually means prev/next field - let num_fields = AddLogicState::INPUT_FIELD_COUNT; // Changed - if num_fields > 0 { + "move_up" => { // In edit mode for single-line canvas, up/down usually means prev/next field 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; + 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()) } - 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 AddLogicState::INPUT_FIELD_COUNT > 0 { // Changed - state.set_current_field(0); + "move_down" => { // In edit mode for single-line canvas, up/down usually means prev/next field + let num_fields = AddLogicState::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 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 = AddLogicState::INPUT_FIELD_COUNT; // Changed - 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 && current_pos < current_input.len() { // Ensure not to go past end - 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(); - if !current_input.is_empty() { - let new_pos = find_prev_word_start(current_input, state.current_cursor_pos()); + let new_pos = current_input.len(); state.set_current_cursor_pos(new_pos); *ideal_cursor_column = new_pos; + Ok("".to_string()) } - 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; + "move_first_line" => { // For single-line canvas, effectively "first field" + if AddLogicState::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()) } - Ok("".to_string()) + "move_last_line" => { // For single-line canvas, effectively "last field" + let num_fields = AddLogicState::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 && current_pos < current_input.len() { + find_word_end(current_input, current_pos + 1) + } else { + new_pos + }; + let max_valid_index = current_input.len(); + 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()) + } + "exit_edit_mode" | "save" | "revert" => { + Ok("Action handled by main loop".to_string()) + } + _ => Ok(format!("Unknown or unhandled edit action for single-line: {}", action)), } - "exit_edit_mode" | "save" | "revert" => { - Ok("Action handled by main loop".to_string()) - } - _ => Ok(format!("Unknown or unhandled edit action: {}", action)), } } - diff --git a/client/src/functions/modes/navigation/add_logic_nav.rs b/client/src/functions/modes/navigation/add_logic_nav.rs index c0584d09..a1d960c7 100644 --- a/client/src/functions/modes/navigation/add_logic_nav.rs +++ b/client/src/functions/modes/navigation/add_logic_nav.rs @@ -7,11 +7,13 @@ use crate::state::{ app::buffer::BufferState, }; use crate::state::pages::canvas_state::CanvasState; -use crossterm::event::{KeyEvent}; +use crossterm::event::{KeyEvent, KeyCode}; // Added KeyCode here use crate::services::GrpcClient; use tokio::sync::mpsc; use anyhow::Result; use common::proto::multieko2::table_script::{PostTableScriptRequest}; +// Import the trait your BasicMultilineEditor implements +use crate::state::pages::multi_canvas_state::MultilineEditorState; pub type SaveLogicResultSender = mpsc::Sender>; @@ -29,57 +31,81 @@ pub fn handle_add_logic_navigation( let action = config.get_general_action(key.code, key.modifiers).map(String::from); let mut handled = false; - // Check if focus is on canvas input fields - let focus_on_canvas_inputs = matches!( + // Check if focus is on canvas input fields (single-line ones) + let _focus_on_canvas_inputs = matches!( // Renamed as it's not directly used below for multiline add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription ); // Handle script content editing separately (multiline) if *is_edit_mode && add_logic_state.current_focus == AddLogicFocus::InputScriptContent { + // Use methods from MultilineEditorState trait match key.code { - crossterm::event::KeyCode::Char(c) => { - add_logic_state.script_content_input.push(c); - add_logic_state.has_unsaved_changes = true; + KeyCode::Char(c) => { + add_logic_state.script_editor.insert_char_at_cursor(c); + add_logic_state.script_editor.set_has_unsaved_changes(true); // Mark editor as changed + // add_logic_state.has_unsaved_changes = true; // Optionally mark the whole page state handled = true; } - crossterm::event::KeyCode::Enter => { - add_logic_state.script_content_input.push('\n'); - add_logic_state.has_unsaved_changes = true; - add_logic_state.script_content_scroll.0 = add_logic_state.script_content_scroll.0.saturating_add(1); + KeyCode::Enter => { + add_logic_state.script_editor.insert_newline_at_cursor(); + add_logic_state.script_editor.set_has_unsaved_changes(true); + // Scrolling is handled internally by the editor's state or renderer, + // or by dedicated scroll commands if you add them. + // We don't directly manipulate scroll here anymore. handled = true; } - crossterm::event::KeyCode::Backspace => { - if !add_logic_state.script_content_input.is_empty() { - add_logic_state.script_content_input.pop(); - add_logic_state.has_unsaved_changes = true; - handled = true; - } + KeyCode::Backspace => { + add_logic_state.script_editor.delete_char_before_cursor(); + add_logic_state.script_editor.set_has_unsaved_changes(true); + handled = true; } - _ => {} + // Add other key handling for the multiline editor as needed: + // KeyCode::Delete => { + // add_logic_state.script_editor.delete_char_at_cursor(); + // add_logic_state.script_editor.set_has_unsaved_changes(true); + // handled = true; + // } + // KeyCode::Left => { + // add_logic_state.script_editor.move_cursor_left(); + // handled = true; + // } + // KeyCode::Right => { + // add_logic_state.script_editor.move_cursor_right(); + // handled = true; + // } + KeyCode::Up => { // In edit mode, up in multiline editor moves cursor up a line + add_logic_state.script_editor.move_cursor_up(); + handled = true; + } + KeyCode::Down => { // In edit mode, down in multiline editor moves cursor down a line + add_logic_state.script_editor.move_cursor_down(); + handled = true; + } + // Potentially PageUp, PageDown, Home, End etc. + _ => {} // Let other handlers or default behavior take over } } if !handled { match action.as_deref() { Some("exit_view") | Some("cancel_action") => { - buffer_state.update_history(AppView::Admin); // Fixed: was AdminPanel + buffer_state.update_history(AppView::Admin); app_state.ui.focus_outside_canvas = true; *command_message = "Exited Add Logic".to_string(); handled = true; } - Some("next_field") => { + Some("next_field") => { // Tab let previous_focus = add_logic_state.current_focus; add_logic_state.current_focus = match add_logic_state.current_focus { AddLogicFocus::InputLogicName => AddLogicFocus::InputTargetColumn, AddLogicFocus::InputTargetColumn => AddLogicFocus::InputDescription, AddLogicFocus::InputDescription => AddLogicFocus::InputScriptContent, - AddLogicFocus::InputScriptContent => AddLogicFocus::SaveButton, + AddLogicFocus::InputScriptContent => AddLogicFocus::SaveButton, // Move from script editor to Save AddLogicFocus::SaveButton => AddLogicFocus::CancelButton, AddLogicFocus::CancelButton => AddLogicFocus::InputLogicName, }; - // Update canvas field index only when moving between canvas inputs if matches!(previous_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn) { if matches!(add_logic_state.current_focus, AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) { let new_field = match add_logic_state.current_focus { @@ -91,10 +117,9 @@ pub fn handle_add_logic_navigation( } } - // Update focus outside canvas flag app_state.ui.focus_outside_canvas = !matches!( add_logic_state.current_focus, - AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription + AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent ); *command_message = format!("Focus: {:?}", add_logic_state.current_focus); @@ -103,108 +128,125 @@ pub fn handle_add_logic_navigation( AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent); handled = true; } - Some("prev_field") => { - let previous_focus = add_logic_state.current_focus; + Some("prev_field") => { // Shift-Tab + let _previous_focus = add_logic_state.current_focus; // Keep for potential future use add_logic_state.current_focus = match add_logic_state.current_focus { AddLogicFocus::InputLogicName => AddLogicFocus::CancelButton, AddLogicFocus::InputTargetColumn => AddLogicFocus::InputLogicName, AddLogicFocus::InputDescription => AddLogicFocus::InputTargetColumn, - AddLogicFocus::InputScriptContent => AddLogicFocus::InputDescription, + AddLogicFocus::InputScriptContent => AddLogicFocus::InputDescription, // Move from script editor to Description AddLogicFocus::SaveButton => AddLogicFocus::InputScriptContent, AddLogicFocus::CancelButton => AddLogicFocus::SaveButton, }; - // Update canvas field index only when moving between canvas inputs - if matches!(previous_focus, AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) { - if matches!(add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn) { + // This logic for setting single-line canvas field might need adjustment + // if previous_focus was InputScriptContent + if matches!(add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) { + let new_field_index = match add_logic_state.current_focus { + AddLogicFocus::InputLogicName => 0, + AddLogicFocus::InputTargetColumn => 1, + AddLogicFocus::InputDescription => 2, + _ => add_logic_state.current_field(), // Should not happen + }; + add_logic_state.set_current_field(new_field_index); + } + + + app_state.ui.focus_outside_canvas = !matches!( + add_logic_state.current_focus, + AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent + ); + + *command_message = format!("Focus: {:?}", add_logic_state.current_focus); + *is_edit_mode = matches!(add_logic_state.current_focus, + AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | + AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent); + handled = true; + } + // "next_option" and "previous_option" (likely right/left arrow in non-edit mode) + // These typically navigate between focusable elements like buttons or cycle through fields. + // If current focus is InputScriptContent and not in edit mode, these might scroll the content. + Some("next_option") => { + if !*is_edit_mode && add_logic_state.current_focus == AddLogicFocus::InputScriptContent { + // TODO: Implement horizontal scroll for BasicMultilineEditor if needed + // add_logic_state.script_editor.scroll_right(); + *command_message = "Horizontal scroll right (not implemented for script editor)".to_string(); + handled = true; + } else { + // Default horizontal navigation logic (copied from next_field for now, adjust as needed) + add_logic_state.current_focus = match add_logic_state.current_focus { + AddLogicFocus::InputLogicName => AddLogicFocus::InputTargetColumn, + AddLogicFocus::InputTargetColumn => AddLogicFocus::InputDescription, + AddLogicFocus::InputDescription => AddLogicFocus::InputScriptContent, + AddLogicFocus::InputScriptContent => AddLogicFocus::SaveButton, + AddLogicFocus::SaveButton => AddLogicFocus::CancelButton, + AddLogicFocus::CancelButton => AddLogicFocus::InputLogicName, + }; + if matches!(add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) { let new_field = match add_logic_state.current_focus { AddLogicFocus::InputLogicName => 0, AddLogicFocus::InputTargetColumn => 1, - _ => 0, + AddLogicFocus::InputDescription => 2, + _ => add_logic_state.current_field(), }; add_logic_state.set_current_field(new_field); } + app_state.ui.focus_outside_canvas = !matches!( + add_logic_state.current_focus, + AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent + ); + *command_message = format!("Focus: {:?}", add_logic_state.current_focus); + *is_edit_mode = matches!(add_logic_state.current_focus, + AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | + AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent); + handled = true; } - - // Update focus outside canvas flag - app_state.ui.focus_outside_canvas = !matches!( - add_logic_state.current_focus, - AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription - ); - - *command_message = format!("Focus: {:?}", add_logic_state.current_focus); - *is_edit_mode = matches!(add_logic_state.current_focus, - AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | - AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent); - handled = true; } - Some("next_option") => { // Horizontal next - let previous_focus = add_logic_state.current_focus; - add_logic_state.current_focus = match add_logic_state.current_focus { - AddLogicFocus::InputLogicName => AddLogicFocus::InputTargetColumn, - AddLogicFocus::InputTargetColumn => AddLogicFocus::InputDescription, - AddLogicFocus::InputDescription => AddLogicFocus::InputScriptContent, - AddLogicFocus::InputScriptContent => AddLogicFocus::SaveButton, - AddLogicFocus::SaveButton => AddLogicFocus::CancelButton, - AddLogicFocus::CancelButton => AddLogicFocus::InputLogicName, // Cycle back - }; - // Update canvas field index if moving within canvas inputs - if matches!(add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) { - let new_field = match add_logic_state.current_focus { - AddLogicFocus::InputLogicName => 0, - AddLogicFocus::InputTargetColumn => 1, - AddLogicFocus::InputDescription => 2, - _ => add_logic_state.current_field(), // Should not happen + Some("previous_option") => { + if !*is_edit_mode && add_logic_state.current_focus == AddLogicFocus::InputScriptContent { + // TODO: Implement horizontal scroll for BasicMultilineEditor if needed + // add_logic_state.script_editor.scroll_left(); + *command_message = "Horizontal scroll left (not implemented for script editor)".to_string(); + handled = true; + } else { + // Default horizontal navigation logic (copied from prev_field for now, adjust as needed) + add_logic_state.current_focus = match add_logic_state.current_focus { + AddLogicFocus::InputLogicName => AddLogicFocus::CancelButton, + AddLogicFocus::InputTargetColumn => AddLogicFocus::InputLogicName, + AddLogicFocus::InputDescription => AddLogicFocus::InputTargetColumn, + AddLogicFocus::InputScriptContent => AddLogicFocus::InputDescription, + AddLogicFocus::SaveButton => AddLogicFocus::InputScriptContent, + AddLogicFocus::CancelButton => AddLogicFocus::SaveButton, }; - add_logic_state.set_current_field(new_field); + if matches!(add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) { + let new_field = match add_logic_state.current_focus { + AddLogicFocus::InputLogicName => 0, + AddLogicFocus::InputTargetColumn => 1, + AddLogicFocus::InputDescription => 2, + _ => add_logic_state.current_field(), + }; + add_logic_state.set_current_field(new_field); + } + app_state.ui.focus_outside_canvas = !matches!( + add_logic_state.current_focus, + AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent + ); + *command_message = format!("Focus: {:?}", add_logic_state.current_focus); + *is_edit_mode = matches!(add_logic_state.current_focus, + AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | + AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent); + handled = true; } - app_state.ui.focus_outside_canvas = !matches!( - add_logic_state.current_focus, - AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription - ); - *command_message = format!("Focus: {:?}", add_logic_state.current_focus); - *is_edit_mode = matches!(add_logic_state.current_focus, - AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | - AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent); - handled = true; } - Some("previous_option") => { // Horizontal previous - let previous_focus = add_logic_state.current_focus; - add_logic_state.current_focus = match add_logic_state.current_focus { - AddLogicFocus::InputLogicName => AddLogicFocus::CancelButton, // Cycle back - AddLogicFocus::InputTargetColumn => AddLogicFocus::InputLogicName, - AddLogicFocus::InputDescription => AddLogicFocus::InputTargetColumn, - AddLogicFocus::InputScriptContent => AddLogicFocus::InputDescription, - AddLogicFocus::SaveButton => AddLogicFocus::InputScriptContent, - AddLogicFocus::CancelButton => AddLogicFocus::SaveButton, - }; - // Update canvas field index if moving within canvas inputs - if matches!(add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription) { - let new_field = match add_logic_state.current_focus { - AddLogicFocus::InputLogicName => 0, - AddLogicFocus::InputTargetColumn => 1, - AddLogicFocus::InputDescription => 2, - _ => add_logic_state.current_field(), // Should not happen - }; - add_logic_state.set_current_field(new_field); - } - app_state.ui.focus_outside_canvas = !matches!( - add_logic_state.current_focus, - AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription - ); - *command_message = format!("Focus: {:?}", add_logic_state.current_focus); - *is_edit_mode = matches!(add_logic_state.current_focus, - AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | - AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent); - handled = true; - } - Some("select") => { + Some("select") => { // Enter key in navigation mode match add_logic_state.current_focus { AddLogicFocus::SaveButton => { if let Some(table_def_id) = add_logic_state.selected_table_id { + // Use the new get_script_content method + let script_content = add_logic_state.get_script_content(); if add_logic_state.target_column_input.trim().is_empty() { *command_message = "Cannot save: Target Column cannot be empty.".to_string(); - } else if add_logic_state.script_content_input.trim().is_empty() { + } else if script_content.trim().is_empty() { // Check content from editor *command_message = "Cannot save: Script Content cannot be empty.".to_string(); } else { *command_message = "Saving logic script...".to_string(); @@ -213,7 +255,7 @@ pub fn handle_add_logic_navigation( let request = PostTableScriptRequest { table_definition_id: table_def_id, target_column: add_logic_state.target_column_input.trim().to_string(), - script: add_logic_state.script_content_input.trim().to_string(), + script: script_content.trim().to_string(), // Use content from editor description: add_logic_state.description_input.trim().to_string(), }; @@ -233,16 +275,19 @@ pub fn handle_add_logic_navigation( handled = true; } AddLogicFocus::CancelButton => { - buffer_state.update_history(AppView::Admin); // Fixed: was AdminPanel + buffer_state.update_history(AppView::Admin); app_state.ui.focus_outside_canvas = true; *command_message = "Cancelled Add Logic".to_string(); handled = true; } + // If Enter is pressed on an input field (including script content), switch to edit mode AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent => { if !*is_edit_mode { *is_edit_mode = true; *command_message = "Edit mode: ON".to_string(); + // Ensure focus_outside_canvas is false when entering edit mode for any input + app_state.ui.focus_outside_canvas = false; } handled = true; } @@ -251,17 +296,34 @@ pub fn handle_add_logic_navigation( Some("toggle_edit_mode") => { *is_edit_mode = !*is_edit_mode; *command_message = format!("Edit mode: {}", if *is_edit_mode { "ON" } else { "OFF" }); + // When toggling edit mode, adjust focus_outside_canvas + if *is_edit_mode { + // If entering edit mode, and focus is on an input, ensure canvas has focus + if matches!(add_logic_state.current_focus, + AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | + AddLogicFocus::InputDescription | AddLogicFocus::InputScriptContent) { + app_state.ui.focus_outside_canvas = false; + } + } else { + // If exiting edit mode, and focus was on an input, it might now be considered "outside" + // This depends on your desired navigation flow after exiting edit mode. + // For now, let's assume it stays on the element but is no longer editable. + // If you want focus to jump to a button, handle that explicitly. + } handled = true; } - // Handle script content scrolling when not in edit mode + // Handle script content scrolling when not in edit mode using arrow keys _ if !*is_edit_mode && add_logic_state.current_focus == AddLogicFocus::InputScriptContent => { + // This specific block for action.as_deref() might be redundant if + // KeyCode::Up/Down are handled directly above for edit mode. + // However, if "move_up"/"move_down" are distinct actions in your config for non-edit mode scrolling: match action.as_deref() { Some("move_up") => { - add_logic_state.script_content_scroll.0 = add_logic_state.script_content_scroll.0.saturating_sub(1); + add_logic_state.script_editor.move_cursor_up(); // Or a dedicated scroll_up method handled = true; } Some("move_down") => { - add_logic_state.script_content_scroll.0 = add_logic_state.script_content_scroll.0.saturating_add(1); + add_logic_state.script_editor.move_cursor_down(); // Or a dedicated scroll_down method handled = true; } _ => {} @@ -272,4 +334,3 @@ pub fn handle_add_logic_navigation( } handled } - diff --git a/client/src/state/pages/add_logic.rs b/client/src/state/pages/add_logic.rs index 2d2d88c6..d18d8aa3 100644 --- a/client/src/state/pages/add_logic.rs +++ b/client/src/state/pages/add_logic.rs @@ -1,12 +1,17 @@ // src/state/pages/add_logic.rs use crate::state::pages::canvas_state::CanvasState; +// Use your existing BasicMultilineEditor +use crate::state::pages::multi_canvas_state::BasicMultilineEditor; +// You might also need the trait if you pass it around as dyn MultilineEditorState +use crate::state::pages::multi_canvas_state::MultilineEditorState; + #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum AddLogicFocus { #[default] InputLogicName, InputTargetColumn, - InputScriptContent, + InputScriptContent, // This will now refer to the BasicMultilineEditor InputDescription, SaveButton, CancelButton, @@ -19,14 +24,16 @@ pub struct AddLogicState { pub selected_table_name: Option, pub logic_name_input: String, pub target_column_input: String, - pub script_content_input: String, + // pub script_content_input: String, // Remove this pub description_input: String, pub current_focus: AddLogicFocus, pub logic_name_cursor_pos: usize, pub target_column_cursor_pos: usize, - pub script_content_scroll: (u16, u16), // (vertical, horizontal) + // pub script_content_scroll: (u16, u16), // Remove this pub description_cursor_pos: usize, - pub has_unsaved_changes: bool, + pub has_unsaved_changes: bool, // For the single-line canvas fields + // Add this field using your existing struct + pub script_editor: BasicMultilineEditor, } impl Default for AddLogicState { @@ -37,30 +44,49 @@ impl Default for AddLogicState { selected_table_name: None, logic_name_input: String::new(), target_column_input: String::new(), - script_content_input: String::new(), description_input: String::new(), current_focus: AddLogicFocus::InputLogicName, logic_name_cursor_pos: 0, target_column_cursor_pos: 0, - script_content_scroll: (0, 0), description_cursor_pos: 0, has_unsaved_changes: false, + // Initialize your BasicMultilineEditor + script_editor: BasicMultilineEditor::new( + None, // No initial content + Some("Steel Script Content".to_string()), // Optional label + ), } } } impl AddLogicState { - // Number of canvas-editable fields + // Number of canvas-editable fields (for the top single-line inputs) pub const INPUT_FIELD_COUNT: usize = 3; // Logic Name, Target Column, Description + + // Method to get script content from the editor + pub fn get_script_content(&self) -> String { + // Assuming BasicMultilineEditor will have a method to get all lines as a single string + // If not, we'll need to add it to BasicMultilineEditor or implement it here. + // For now, let's assume it's: + self.script_editor.lines().join("\n") + } + + // Method to set script content in the editor (e.g., when loading to edit) + pub fn set_script_content(&mut self, content: &str) { + let lines: Vec = content.lines().map(String::from).collect(); + self.script_editor.set_lines(lines); + // self.script_editor.set_has_unsaved_changes(false); // If this is initial population + } } +// The CanvasState implementation remains for the single-line inputs. impl CanvasState for AddLogicState { fn current_field(&self) -> usize { match self.current_focus { AddLogicFocus::InputLogicName => 0, AddLogicFocus::InputTargetColumn => 1, AddLogicFocus::InputDescription => 2, - _ => 0, // Default or non-input focus + _ => 0, } } @@ -74,6 +100,10 @@ impl CanvasState for AddLogicState { } fn has_unsaved_changes(&self) -> bool { + // This refers to the single-line fields. + // The BasicMultilineEditor has its own `has_unsaved_changes`. + // You might want a combined status: + // self.has_unsaved_changes || self.script_editor.has_unsaved_changes() self.has_unsaved_changes } @@ -95,11 +125,12 @@ impl CanvasState for AddLogicState { } fn get_current_input_mut(&mut self) -> &mut String { + self.has_unsaved_changes = true; match self.current_focus { AddLogicFocus::InputLogicName => &mut self.logic_name_input, AddLogicFocus::InputTargetColumn => &mut self.target_column_input, AddLogicFocus::InputDescription => &mut self.description_input, - _ => &mut self.logic_name_input, // Placeholder, should not be hit if focus is correct + _ => &mut self.logic_name_input, } } @@ -112,20 +143,23 @@ impl CanvasState for AddLogicState { 0 => AddLogicFocus::InputLogicName, 1 => AddLogicFocus::InputTargetColumn, 2 => AddLogicFocus::InputDescription, - _ => self.current_focus, // Stay if out of bounds + _ => self.current_focus, }; } fn set_current_cursor_pos(&mut self, pos: usize) { match self.current_focus { AddLogicFocus::InputLogicName => { - self.logic_name_cursor_pos = pos.min(self.logic_name_input.len()); + self.logic_name_cursor_pos = + pos.min(self.logic_name_input.chars().count()); } AddLogicFocus::InputTargetColumn => { - self.target_column_cursor_pos = pos.min(self.target_column_input.len()); + self.target_column_cursor_pos = + pos.min(self.target_column_input.chars().count()); } AddLogicFocus::InputDescription => { - self.description_cursor_pos = pos.min(self.description_input.len()); + self.description_cursor_pos = + pos.min(self.description_input.chars().count()); } _ => {} }