diff --git a/canvas/src/gui/autocomplete.rs b/canvas/src/autocomplete/gui.rs similarity index 98% rename from canvas/src/gui/autocomplete.rs rename to canvas/src/autocomplete/gui.rs index 30706b5..5fba042 100644 --- a/canvas/src/gui/autocomplete.rs +++ b/canvas/src/autocomplete/gui.rs @@ -1,4 +1,4 @@ -// canvas/src/gui/autocomplete.rs +// canvas/src/autocomplete/gui.rs #[cfg(feature = "gui")] use ratatui::{ @@ -9,7 +9,9 @@ use ratatui::{ }; use crate::autocomplete::AutocompleteState; -use super::theme::CanvasTheme; + +#[cfg(feature = "gui")] +use crate::canvas::theme::CanvasTheme; #[cfg(feature = "gui")] use unicode_width::UnicodeWidthStr; diff --git a/canvas/src/autocomplete/mod.rs b/canvas/src/autocomplete/mod.rs new file mode 100644 index 0000000..c058ee4 --- /dev/null +++ b/canvas/src/autocomplete/mod.rs @@ -0,0 +1,8 @@ +// src/autocomplete/mod.rs +pub mod types; +pub mod gui; +pub mod state; // Add this line + +// Re-export autocomplete types +pub use types::{SuggestionItem, AutocompleteState}; +pub use state::AutocompleteCanvasState; // Add this line diff --git a/canvas/src/state.rs b/canvas/src/autocomplete/state.rs similarity index 50% rename from canvas/src/state.rs rename to canvas/src/autocomplete/state.rs index 3cdea73..90f31e3 100644 --- a/canvas/src/state.rs +++ b/canvas/src/autocomplete/state.rs @@ -1,100 +1,6 @@ // canvas/src/state.rs -use crate::actions::CanvasAction; - -/// Context passed to feature-specific action handlers -#[derive(Debug)] -pub struct ActionContext { - pub key_code: Option, // Kept for backwards compatibility - pub ideal_cursor_column: usize, - pub current_input: String, - pub current_field: usize, -} - -/// Core trait that any form-like state must implement to work with the canvas system. -/// This enables the same mode behaviors (edit, read-only, highlight) to work across -/// any implementation - login forms, data entry forms, configuration screens, etc. -pub trait CanvasState { - // --- Core Navigation --- - fn current_field(&self) -> usize; - fn current_cursor_pos(&self) -> usize; - fn set_current_field(&mut self, index: usize); - fn set_current_cursor_pos(&mut self, pos: usize); - - // --- Data Access --- - fn get_current_input(&self) -> &str; - fn get_current_input_mut(&mut self) -> &mut String; - fn inputs(&self) -> Vec<&String>; - fn fields(&self) -> Vec<&str>; - - // --- State Management --- - fn has_unsaved_changes(&self) -> bool; - fn set_has_unsaved_changes(&mut self, changed: bool); - - // --- LEGACY AUTOCOMPLETE SUPPORT (for backwards compatibility) --- - - /// Legacy suggestion support (deprecated - use AutocompleteCanvasState for rich features) - fn get_suggestions(&self) -> Option<&[String]> { - None - } - - /// Legacy selected suggestion index (deprecated) - fn get_selected_suggestion_index(&self) -> Option { - None - } - - /// Legacy suggestion index setter (deprecated) - fn set_selected_suggestion_index(&mut self, _index: Option) { - // Default: no-op - } - - /// Legacy activate suggestions (deprecated) - fn activate_suggestions(&mut self, _suggestions: Vec) { - // Default: no-op - } - - /// Legacy deactivate suggestions (deprecated) - fn deactivate_suggestions(&mut self) { - // Default: no-op - } - - // --- Feature-specific action handling --- - - /// Feature-specific action handling (NEW: Type-safe) - fn handle_feature_action(&mut self, _action: &CanvasAction, _context: &ActionContext) -> Option { - None // Default: no feature-specific handling - } - - /// Legacy string-based action handling (for backwards compatibility) - fn handle_feature_action_legacy(&mut self, action: &str, context: &ActionContext) -> Option { - // Convert string to typed action and delegate - let typed_action = match action { - "insert_char" => { - // This is tricky - we need the char from the KeyCode in context - if let Some(crossterm::event::KeyCode::Char(c)) = context.key_code { - CanvasAction::InsertChar(c) - } else { - CanvasAction::Custom(action.to_string()) - } - } - _ => CanvasAction::from_string(action), - }; - self.handle_feature_action(&typed_action, context) - } - - // --- Display Overrides (for links, computed values, etc.) --- - - fn get_display_value_for_field(&self, index: usize) -> &str { - self.inputs() - .get(index) - .map(|s| s.as_str()) - .unwrap_or("") - } - - fn has_display_override(&self, _index: usize) -> bool { - false - } -} +use crate::canvas::state::CanvasState; /// OPTIONAL extension trait for states that want rich autocomplete functionality. /// Only implement this if you need the new autocomplete features. diff --git a/canvas/src/autocomplete.rs b/canvas/src/autocomplete/types.rs similarity index 100% rename from canvas/src/autocomplete.rs rename to canvas/src/autocomplete/types.rs diff --git a/canvas/src/actions/edit.rs b/canvas/src/canvas/actions/edit.rs similarity index 99% rename from canvas/src/actions/edit.rs rename to canvas/src/canvas/actions/edit.rs index 6ddfe41..a750b86 100644 --- a/canvas/src/actions/edit.rs +++ b/canvas/src/canvas/actions/edit.rs @@ -1,7 +1,8 @@ // canvas/src/actions/edit.rs -use crate::state::{CanvasState, AutocompleteCanvasState, ActionContext}; -use crate::actions::types::{CanvasAction, ActionResult}; +use crate::canvas::state::{CanvasState, ActionContext}; +use crate::autocomplete::state::AutocompleteCanvasState; +use crate::canvas::actions::types::{CanvasAction, ActionResult}; use crossterm::event::{KeyCode, KeyEvent}; use anyhow::Result; diff --git a/canvas/src/actions/mod.rs b/canvas/src/canvas/actions/mod.rs similarity index 100% rename from canvas/src/actions/mod.rs rename to canvas/src/canvas/actions/mod.rs diff --git a/canvas/src/actions/types.rs b/canvas/src/canvas/actions/types.rs similarity index 100% rename from canvas/src/actions/types.rs rename to canvas/src/canvas/actions/types.rs diff --git a/canvas/src/gui/canvas.rs b/canvas/src/canvas/gui.rs similarity index 98% rename from canvas/src/gui/canvas.rs rename to canvas/src/canvas/gui.rs index 36d060e..4e06985 100644 --- a/canvas/src/gui/canvas.rs +++ b/canvas/src/canvas/gui.rs @@ -1,4 +1,4 @@ -// canvas/src/gui/canvas.rs +// canvas/src/canvas/gui.rs #[cfg(feature = "gui")] use ratatui::{ @@ -9,9 +9,11 @@ use ratatui::{ Frame, }; -use crate::state::CanvasState; -use crate::modes::HighlightState; -use super::theme::CanvasTheme; +use crate::canvas::state::CanvasState; +use crate::canvas::modes::HighlightState; + +#[cfg(feature = "gui")] +use crate::canvas::theme::CanvasTheme; #[cfg(feature = "gui")] use std::cmp::{max, min}; diff --git a/canvas/src/canvas/mod.rs b/canvas/src/canvas/mod.rs new file mode 100644 index 0000000..4729640 --- /dev/null +++ b/canvas/src/canvas/mod.rs @@ -0,0 +1,14 @@ +// src/canvas/mod.rs +pub mod actions; +pub mod modes; +pub mod gui; +pub mod theme; +pub mod state; // Add this line + +// Re-export commonly used canvas types +pub use actions::{CanvasAction, ActionResult}; +pub use modes::{AppMode, ModeManager, HighlightState}; +pub use state::{CanvasState, ActionContext}; // Add this line + +#[cfg(feature = "gui")] +pub use theme::CanvasTheme; diff --git a/canvas/src/modes/highlight.rs b/canvas/src/canvas/modes/highlight.rs similarity index 100% rename from canvas/src/modes/highlight.rs rename to canvas/src/canvas/modes/highlight.rs diff --git a/canvas/src/modes/manager.rs b/canvas/src/canvas/modes/manager.rs similarity index 100% rename from canvas/src/modes/manager.rs rename to canvas/src/canvas/modes/manager.rs diff --git a/canvas/src/modes/mod.rs b/canvas/src/canvas/modes/mod.rs similarity index 100% rename from canvas/src/modes/mod.rs rename to canvas/src/canvas/modes/mod.rs diff --git a/canvas/src/canvas/state.rs b/canvas/src/canvas/state.rs new file mode 100644 index 0000000..4998372 --- /dev/null +++ b/canvas/src/canvas/state.rs @@ -0,0 +1,97 @@ +// canvas/src/state.rs + +use crate::canvas::actions::CanvasAction; + +/// Context passed to feature-specific action handlers +#[derive(Debug)] +pub struct ActionContext { + pub key_code: Option, // Kept for backwards compatibility + pub ideal_cursor_column: usize, + pub current_input: String, + pub current_field: usize, +} + +/// Core trait that any form-like state must implement to work with the canvas system. +/// This enables the same mode behaviors (edit, read-only, highlight) to work across +/// any implementation - login forms, data entry forms, configuration screens, etc. +pub trait CanvasState { + // --- Core Navigation --- + fn current_field(&self) -> usize; + fn current_cursor_pos(&self) -> usize; + fn set_current_field(&mut self, index: usize); + fn set_current_cursor_pos(&mut self, pos: usize); + + // --- Data Access --- + fn get_current_input(&self) -> &str; + fn get_current_input_mut(&mut self) -> &mut String; + fn inputs(&self) -> Vec<&String>; + fn fields(&self) -> Vec<&str>; + + // --- State Management --- + fn has_unsaved_changes(&self) -> bool; + fn set_has_unsaved_changes(&mut self, changed: bool); + + // --- LEGACY AUTOCOMPLETE SUPPORT (for backwards compatibility) --- + + /// Legacy suggestion support (deprecated - use AutocompleteCanvasState for rich features) + fn get_suggestions(&self) -> Option<&[String]> { + None + } + + /// Legacy selected suggestion index (deprecated) + fn get_selected_suggestion_index(&self) -> Option { + None + } + + /// Legacy suggestion index setter (deprecated) + fn set_selected_suggestion_index(&mut self, _index: Option) { + // Default: no-op + } + + /// Legacy activate suggestions (deprecated) + fn activate_suggestions(&mut self, _suggestions: Vec) { + // Default: no-op + } + + /// Legacy deactivate suggestions (deprecated) + fn deactivate_suggestions(&mut self) { + // Default: no-op + } + + // --- Feature-specific action handling --- + + /// Feature-specific action handling (NEW: Type-safe) + fn handle_feature_action(&mut self, _action: &CanvasAction, _context: &ActionContext) -> Option { + None // Default: no feature-specific handling + } + + /// Legacy string-based action handling (for backwards compatibility) + fn handle_feature_action_legacy(&mut self, action: &str, context: &ActionContext) -> Option { + // Convert string to typed action and delegate + let typed_action = match action { + "insert_char" => { + // This is tricky - we need the char from the KeyCode in context + if let Some(crossterm::event::KeyCode::Char(c)) = context.key_code { + CanvasAction::InsertChar(c) + } else { + CanvasAction::Custom(action.to_string()) + } + } + _ => CanvasAction::from_string(action), + }; + self.handle_feature_action(&typed_action, context) + } + + // --- Display Overrides (for links, computed values, etc.) --- + + fn get_display_value_for_field(&self, index: usize) -> &str { + self.inputs() + .get(index) + .map(|s| s.as_str()) + .unwrap_or("") + } + + fn has_display_override(&self, _index: usize) -> bool { + false + } +} diff --git a/canvas/src/gui/theme.rs b/canvas/src/canvas/theme.rs similarity index 100% rename from canvas/src/gui/theme.rs rename to canvas/src/canvas/theme.rs diff --git a/canvas/src/config.rs b/canvas/src/config.rs index 25588b5..f5e027c 100644 --- a/canvas/src/config.rs +++ b/canvas/src/config.rs @@ -476,5 +476,5 @@ impl CanvasConfig { } // Re-export for convenience -pub use crate::actions::CanvasAction; +pub use crate::canvas::actions::CanvasAction; pub use crate::dispatcher::ActionDispatcher; diff --git a/canvas/src/dispatcher.rs b/canvas/src/dispatcher.rs index 1ac1d6e..064e9b1 100644 --- a/canvas/src/dispatcher.rs +++ b/canvas/src/dispatcher.rs @@ -1,7 +1,7 @@ // canvas/src/dispatcher.rs -use crate::state::CanvasState; -use crate::actions::{CanvasAction, ActionResult, execute_canvas_action}; +use crate::canvas::state::CanvasState; +use crate::canvas::actions::{CanvasAction, ActionResult, execute_canvas_action}; /// High-level action dispatcher that coordinates between different action types pub struct ActionDispatcher; diff --git a/canvas/src/gui/mod.rs b/canvas/src/gui/mod.rs deleted file mode 100644 index f5c833d..0000000 --- a/canvas/src/gui/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -// canvas/src/gui/mod.rs - -#[cfg(feature = "gui")] -pub mod canvas; - -#[cfg(feature = "gui")] -pub mod autocomplete; - -#[cfg(feature = "gui")] -pub mod theme; - -// Export the separate rendering functions -#[cfg(feature = "gui")] -pub use canvas::render_canvas; - -#[cfg(feature = "gui")] -pub use autocomplete::render_autocomplete_dropdown; - -#[cfg(feature = "gui")] -pub use theme::CanvasTheme; diff --git a/canvas/src/lib.rs b/canvas/src/lib.rs index 596082d..544d37c 100644 --- a/canvas/src/lib.rs +++ b/canvas/src/lib.rs @@ -1,25 +1,19 @@ -// canvas/src/lib.rs - -pub mod actions; +// src/lib.rs +pub mod canvas; +pub mod autocomplete; pub mod config; pub mod dispatcher; -pub mod state; pub mod suggestions; // Keep for backwards compatibility -pub mod autocomplete; // NEW: Core autocomplete functionality -pub mod modes; + +// Re-export from modules +pub use canvas::{CanvasAction, ActionResult, AppMode, ModeManager, HighlightState}; #[cfg(feature = "gui")] -pub mod gui; +pub use canvas::CanvasTheme; -// Re-export commonly used types -pub use actions::{CanvasAction, ActionResult}; +pub use autocomplete::{SuggestionItem, AutocompleteState}; pub use dispatcher::ActionDispatcher; -pub use state::{CanvasState, ActionContext}; -pub use autocomplete::{SuggestionItem, AutocompleteState}; // NEW -pub use modes::{AppMode, ModeManager, HighlightState}; +pub use canvas::state::{CanvasState, ActionContext}; // Fixed path -#[cfg(feature = "gui")] -pub use gui::{render_canvas, CanvasTheme}; - -// Keep backwards compatibility exports +// Backwards compatibility pub use suggestions::SuggestionState;