add_logic is now using canvas library

This commit is contained in:
Priec
2025-07-30 18:02:59 +02:00
parent 4e0338276f
commit e4982f871f
5 changed files with 194 additions and 93 deletions

View File

@@ -3,7 +3,7 @@ use crate::config::colors::themes::Theme;
use crate::state::app::highlight::HighlightState; use crate::state::app::highlight::HighlightState;
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::state::pages::add_logic::{AddLogicFocus, AddLogicState}; use crate::state::pages::add_logic::{AddLogicFocus, AddLogicState};
use crate::state::pages::canvas_state::CanvasState; use canvas::canvas::{render_canvas, CanvasState, HighlightState as CanvasHighlightState}; // Use canvas library
use ratatui::{ use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Rect}, layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Modifier, Style}, style::{Modifier, Style},
@@ -11,10 +11,18 @@ use ratatui::{
widgets::{Block, BorderType, Borders, Paragraph}, widgets::{Block, BorderType, Borders, Paragraph},
Frame, Frame,
}; };
use crate::components::handlers::canvas::render_canvas;
use crate::components::common::{dialog, autocomplete}; // Added autocomplete use crate::components::common::{dialog, autocomplete}; // Added autocomplete
use crate::config::binds::config::EditorKeybindingMode; use crate::config::binds::config::EditorKeybindingMode;
// Helper function to convert between HighlightState types
fn convert_highlight_state(local: &HighlightState) -> CanvasHighlightState {
match local {
HighlightState::Off => CanvasHighlightState::Off,
HighlightState::Characterwise { anchor } => CanvasHighlightState::Characterwise { anchor: *anchor },
HighlightState::Linewise { anchor_line } => CanvasHighlightState::Linewise { anchor_line: *anchor_line },
}
}
pub fn render_add_logic( pub fn render_add_logic(
f: &mut Frame, f: &mut Frame,
area: Rect, area: Rect,
@@ -77,18 +85,18 @@ pub fn render_add_logic(
let editor_borrow = add_logic_state.script_content_editor.borrow(); let editor_borrow = add_logic_state.script_content_editor.borrow();
editor_borrow.cursor() // Returns (row, col) as (usize, usize) editor_borrow.cursor() // Returns (row, col) as (usize, usize)
}; };
let (cursor_line, cursor_col) = current_cursor; let (cursor_line, cursor_col) = current_cursor;
// Account for TextArea's block borders (1 for each side) // Account for TextArea's block borders (1 for each side)
let block_offset_x = 1; let block_offset_x = 1;
let block_offset_y = 1; let block_offset_y = 1;
// Position autocomplete at current cursor position // Position autocomplete at current cursor position
// Add 1 to column to position dropdown right after the cursor // Add 1 to column to position dropdown right after the cursor
let autocomplete_x = cursor_col + 1; let autocomplete_x = cursor_col + 1;
let autocomplete_y = cursor_line; let autocomplete_y = cursor_line;
let input_rect = Rect { let input_rect = Rect {
x: (inner_area.x + block_offset_x + autocomplete_x as u16).min(inner_area.right().saturating_sub(20)), x: (inner_area.x + block_offset_x + autocomplete_x as u16).min(inner_area.right().saturating_sub(20)),
y: (inner_area.y + block_offset_y + autocomplete_y as u16).min(inner_area.bottom().saturating_sub(5)), y: (inner_area.y + block_offset_y + autocomplete_y as u16).min(inner_area.bottom().saturating_sub(5)),
@@ -152,40 +160,37 @@ pub fn render_add_logic(
); );
f.render_widget(profile_text, top_info_area); f.render_widget(profile_text, top_info_area);
// Canvas // Canvas - USING CANVAS LIBRARY
let focus_on_canvas_inputs = matches!( let focus_on_canvas_inputs = matches!(
add_logic_state.current_focus, add_logic_state.current_focus,
AddLogicFocus::InputLogicName AddLogicFocus::InputLogicName
| AddLogicFocus::InputTargetColumn | AddLogicFocus::InputTargetColumn
| AddLogicFocus::InputDescription | AddLogicFocus::InputDescription
); );
// Call render_canvas and get the active_field_rect
let canvas_highlight_state = convert_highlight_state(highlight_state);
let active_field_rect = render_canvas( let active_field_rect = render_canvas(
f, f,
canvas_area, canvas_area,
add_logic_state, // Pass the whole state as it impl CanvasState add_logic_state, // AddLogicState implements CanvasState
&add_logic_state.fields(), theme, // Theme implements CanvasTheme
&add_logic_state.current_field(), is_edit_mode && focus_on_canvas_inputs,
&add_logic_state.inputs(), &canvas_highlight_state,
theme,
is_edit_mode && focus_on_canvas_inputs, // is_edit_mode for canvas fields
highlight_state,
); );
// --- Render Autocomplete for Target Column --- // --- Render Autocomplete for Target Column ---
// `is_edit_mode` here refers to the general edit mode of the EventHandler // `is_edit_mode` here refers to the general edit mode of the EventHandler
if is_edit_mode && add_logic_state.current_field() == 1 { // Target Column field if is_edit_mode && add_logic_state.current_field() == 1 { // Target Column field
if let Some(suggestions) = add_logic_state.get_suggestions() { // Uses CanvasState impl if add_logic_state.in_target_column_suggestion_mode && add_logic_state.show_target_column_suggestions {
let selected = add_logic_state.get_selected_suggestion_index(); if !add_logic_state.target_column_suggestions.is_empty() {
if !suggestions.is_empty() { // Only render if there are suggestions to show
if let Some(input_rect) = active_field_rect { if let Some(input_rect) = active_field_rect {
autocomplete::render_autocomplete_dropdown( autocomplete::render_autocomplete_dropdown(
f, f,
input_rect, input_rect,
f.area(), // Full frame area for clamping f.area(), // Full frame area for clamping
theme, theme,
suggestions, &add_logic_state.target_column_suggestions,
selected, add_logic_state.selected_target_column_suggestion_index,
); );
} }
} }

View File

@@ -1,6 +1,6 @@
// src/functions/modes/edit/add_logic_e.rs // src/functions/modes/edit/add_logic_e.rs
use crate::state::pages::add_logic::AddLogicState; use crate::state::pages::add_logic::AddLogicState;
use crate::state::pages::canvas_state::CanvasState; use canvas::canvas::CanvasState;
use anyhow::Result; use anyhow::Result;
use crossterm::event::{KeyCode, KeyEvent}; use crossterm::event::{KeyCode, KeyEvent};

View File

@@ -1,8 +1,8 @@
// src/functions/modes/read_only/add_logic_ro.rs // src/functions/modes/read_only/add_logic_ro.rs
use crate::config::binds::key_sequences::KeySequenceTracker; use crate::config::binds::key_sequences::KeySequenceTracker;
use crate::state::pages::add_logic::AddLogicState; // Changed use crate::state::pages::add_logic::AddLogicState; // Changed
use crate::state::pages::canvas_state::CanvasState;
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use canvas::canvas::CanvasState;
use anyhow::Result; use anyhow::Result;
// Word navigation helpers (get_char_type, find_next_word_start, etc.) // Word navigation helpers (get_char_type, find_next_word_start, etc.)

View File

@@ -357,10 +357,10 @@ pub async fn handle_edit_event(
) )
.await? .await?
} else if app_state.ui.show_add_logic { } else if app_state.ui.show_add_logic {
// FIX: Pass &mut event_handler.ideal_cursor_column // NEW: Use unified canvas handler instead of add_logic_e::execute_edit_action
add_logic_e::execute_edit_action( handle_canvas_state_edit(
action_str,
key, key,
config,
&mut admin_state.add_logic_state, &mut admin_state.add_logic_state,
&mut event_handler.ideal_cursor_column, &mut event_handler.ideal_cursor_column,
) )
@@ -408,10 +408,10 @@ pub async fn handle_edit_event(
) )
.await? .await?
} else if app_state.ui.show_add_logic { } else if app_state.ui.show_add_logic {
// FIX: Pass &mut event_handler.ideal_cursor_column // NEW: Use unified canvas handler instead of add_logic_e::execute_edit_action
add_logic_e::execute_edit_action( handle_canvas_state_edit(
"insert_char",
key, key,
config,
&mut admin_state.add_logic_state, &mut admin_state.add_logic_state,
&mut event_handler.ideal_cursor_column, &mut event_handler.ideal_cursor_column,
) )

View File

@@ -1,6 +1,6 @@
// src/state/pages/add_logic.rs // src/state/pages/add_logic.rs
use crate::config::binds::config::{EditorConfig, EditorKeybindingMode}; use crate::config::binds::config::{EditorConfig, EditorKeybindingMode};
use crate::state::pages::canvas_state::CanvasState; use canvas::canvas::{CanvasState, ActionContext, CanvasAction}; // External library
use crate::components::common::text_editor::{TextEditor, VimState}; use crate::components::common::text_editor::{TextEditor, VimState};
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
@@ -50,7 +50,7 @@ pub struct AddLogicState {
pub script_editor_trigger_position: Option<(usize, usize)>, // (line, column) pub script_editor_trigger_position: Option<(usize, usize)>, // (line, column)
pub all_table_names: Vec<String>, pub all_table_names: Vec<String>,
pub script_editor_filter_text: String, pub script_editor_filter_text: String,
// New fields for same-profile table names and column autocomplete // New fields for same-profile table names and column autocomplete
pub same_profile_table_names: Vec<String>, // Tables from same profile only pub same_profile_table_names: Vec<String>, // Tables from same profile only
pub script_editor_awaiting_column_autocomplete: Option<String>, // Table name waiting for column fetch pub script_editor_awaiting_column_autocomplete: Option<String>, // Table name waiting for column fetch
@@ -88,7 +88,7 @@ impl AddLogicState {
script_editor_trigger_position: None, script_editor_trigger_position: None,
all_table_names: Vec::new(), all_table_names: Vec::new(),
script_editor_filter_text: String::new(), script_editor_filter_text: String::new(),
same_profile_table_names: Vec::new(), same_profile_table_names: Vec::new(),
script_editor_awaiting_column_autocomplete: None, script_editor_awaiting_column_autocomplete: None,
} }
@@ -181,7 +181,7 @@ impl AddLogicState {
} }
self.same_profile_table_names.contains(&suggestion.to_string()) self.same_profile_table_names.contains(&suggestion.to_string())
} }
/// Sets table columns for autocomplete suggestions /// Sets table columns for autocomplete suggestions
pub fn set_table_columns(&mut self, columns: Vec<String>) { pub fn set_table_columns(&mut self, columns: Vec<String>) {
self.table_columns_for_suggestions = columns.clone(); self.table_columns_for_suggestions = columns.clone();
@@ -225,6 +225,46 @@ impl AddLogicState {
self.script_editor_trigger_position = None; self.script_editor_trigger_position = None;
self.script_editor_filter_text.clear(); self.script_editor_filter_text.clear();
} }
/// Helper method to validate and save logic
pub fn save_logic(&mut self) -> Option<String> {
if self.logic_name_input.trim().is_empty() {
return Some("Logic name is required".to_string());
}
if self.target_column_input.trim().is_empty() {
return Some("Target column is required".to_string());
}
let script_content = {
let editor_borrow = self.script_content_editor.borrow();
editor_borrow.lines().join("\n")
};
if script_content.trim().is_empty() {
return Some("Script content is required".to_string());
}
// Here you would typically save to database/storage
// For now, just clear the form and mark as saved
self.has_unsaved_changes = false;
Some(format!("Logic '{}' saved successfully", self.logic_name_input.trim()))
}
/// Helper method to clear the form
pub fn clear_form(&mut self) -> Option<String> {
let profile = self.profile_name.clone();
let table_id = self.selected_table_id;
let table_name = self.selected_table_name.clone();
let editor_config = EditorConfig::default(); // You might want to preserve the actual config
*self = Self::new(&editor_config);
self.profile_name = profile;
self.selected_table_id = table_id;
self.selected_table_name = table_name;
Some("Form cleared".to_string())
}
} }
impl Default for AddLogicState { impl Default for AddLogicState {
@@ -233,59 +273,18 @@ impl Default for AddLogicState {
} }
} }
// Implement external library's CanvasState for AddLogicState
impl CanvasState for AddLogicState { impl CanvasState for AddLogicState {
fn current_field(&self) -> usize { fn current_field(&self) -> usize {
match self.current_focus { match self.current_focus {
AddLogicFocus::InputLogicName => 0, AddLogicFocus::InputLogicName => 0,
AddLogicFocus::InputTargetColumn => 1, AddLogicFocus::InputTargetColumn => 1,
AddLogicFocus::InputDescription => 2, AddLogicFocus::InputDescription => 2,
// If focus is elsewhere, return the last canvas field used
_ => self.last_canvas_field, _ => self.last_canvas_field,
} }
} }
fn current_cursor_pos(&self) -> usize {
match self.current_focus {
AddLogicFocus::InputLogicName => self.logic_name_cursor_pos,
AddLogicFocus::InputTargetColumn => self.target_column_cursor_pos,
AddLogicFocus::InputDescription => self.description_cursor_pos,
_ => 0,
}
}
fn has_unsaved_changes(&self) -> bool {
self.has_unsaved_changes
}
fn inputs(&self) -> Vec<&String> {
vec![
&self.logic_name_input,
&self.target_column_input,
&self.description_input,
]
}
fn get_current_input(&self) -> &str {
match self.current_focus {
AddLogicFocus::InputLogicName => &self.logic_name_input,
AddLogicFocus::InputTargetColumn => &self.target_column_input,
AddLogicFocus::InputDescription => &self.description_input,
_ => "",
}
}
fn get_current_input_mut(&mut self) -> &mut String {
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,
}
}
fn fields(&self) -> Vec<&str> {
vec!["Logic Name", "Target Column", "Description"]
}
fn set_current_field(&mut self, index: usize) { fn set_current_field(&mut self, index: usize) {
let new_focus = match index { let new_focus = match index {
0 => AddLogicFocus::InputLogicName, 0 => AddLogicFocus::InputLogicName,
@@ -303,6 +302,15 @@ impl CanvasState for AddLogicState {
} }
} }
fn current_cursor_pos(&self) -> usize {
match self.current_focus {
AddLogicFocus::InputLogicName => self.logic_name_cursor_pos,
AddLogicFocus::InputTargetColumn => self.target_column_cursor_pos,
AddLogicFocus::InputDescription => self.description_cursor_pos,
_ => 0,
}
}
fn set_current_cursor_pos(&mut self, pos: usize) { fn set_current_cursor_pos(&mut self, pos: usize) {
match self.current_focus { match self.current_focus {
AddLogicFocus::InputLogicName => { AddLogicFocus::InputLogicName => {
@@ -318,29 +326,117 @@ impl CanvasState for AddLogicState {
} }
} }
fn get_current_input(&self) -> &str {
match self.current_focus {
AddLogicFocus::InputLogicName => &self.logic_name_input,
AddLogicFocus::InputTargetColumn => &self.target_column_input,
AddLogicFocus::InputDescription => &self.description_input,
_ => "", // Should not happen if called correctly
}
}
fn get_current_input_mut(&mut self) -> &mut String {
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, // Fallback
}
}
fn inputs(&self) -> Vec<&String> {
vec![
&self.logic_name_input,
&self.target_column_input,
&self.description_input,
]
}
fn fields(&self) -> Vec<&str> {
vec!["Logic Name", "Target Column", "Description"]
}
fn has_unsaved_changes(&self) -> bool {
self.has_unsaved_changes
}
fn set_has_unsaved_changes(&mut self, changed: bool) { fn set_has_unsaved_changes(&mut self, changed: bool) {
self.has_unsaved_changes = changed; self.has_unsaved_changes = changed;
} }
fn get_suggestions(&self) -> Option<&[String]> { fn handle_feature_action(&mut self, action: &CanvasAction, _context: &ActionContext) -> Option<String> {
if self.current_field() == 1 match action {
&& self.in_target_column_suggestion_mode // Handle saving logic script
&& self.show_target_column_suggestions CanvasAction::Custom(action_str) if action_str == "save_logic" => {
{ self.save_logic()
Some(&self.target_column_suggestions) }
} else {
None
}
}
fn get_selected_suggestion_index(&self) -> Option<usize> { // Handle clearing the form
if self.current_field() == 1 CanvasAction::Custom(action_str) if action_str == "clear_form" => {
&& self.in_target_column_suggestion_mode self.clear_form()
&& self.show_target_column_suggestions }
{
self.selected_target_column_suggestion_index // Handle target column autocomplete activation
} else { CanvasAction::Custom(action_str) if action_str == "activate_autocomplete" => {
None if self.current_field() == 1 { // Target Column field
self.in_target_column_suggestion_mode = true;
self.update_target_column_suggestions();
Some("Autocomplete activated".to_string())
} else {
None
}
}
// Handle target column suggestion selection
CanvasAction::Custom(action_str) if action_str == "select_suggestion" => {
if self.current_field() == 1 && self.in_target_column_suggestion_mode {
if let Some(selected_idx) = self.selected_target_column_suggestion_index {
if let Some(suggestion) = self.target_column_suggestions.get(selected_idx) {
self.target_column_input = suggestion.clone();
self.target_column_cursor_pos = suggestion.len();
self.in_target_column_suggestion_mode = false;
self.show_target_column_suggestions = false;
self.has_unsaved_changes = true;
return Some(format!("Selected: {}", suggestion));
}
}
}
None
}
// Custom validation when moving between fields
CanvasAction::NextField => {
match self.current_field() {
0 => { // Logic Name field
if self.logic_name_input.trim().is_empty() {
Some("Logic name cannot be empty".to_string())
} else {
None // Let canvas library handle the normal field movement
}
}
1 => { // Target Column field
// Update suggestions when entering target column field
self.update_target_column_suggestions();
None
}
_ => None,
}
}
// Handle character insertion with validation
CanvasAction::InsertChar(c) => {
if self.current_field() == 1 { // Target Column field
// Update suggestions after character insertion
// Note: Canvas library will handle the actual insertion
// This is just for triggering suggestion updates
None // Let canvas handle insertion, then we'll update suggestions
} else {
None // Let canvas handle normally
}
}
// Let canvas library handle everything else
_ => None,
} }
} }
} }