diff --git a/canvas/src/canvas/actions/handlers/edit.rs b/canvas/src/canvas/actions/handlers/edit.rs index a26fe6f..70f2cb8 100644 --- a/canvas/src/canvas/actions/handlers/edit.rs +++ b/canvas/src/canvas/actions/handlers/edit.rs @@ -1,4 +1,8 @@ // src/canvas/actions/handlers/edit.rs +//! Edit mode action handler +//! +//! Handles user input when in edit mode, supporting text entry, deletion, +//! and cursor movement with edit-specific behavior (cursor can go past end of text). use crate::canvas::actions::types::{CanvasAction, ActionResult}; use crate::config::introspection::{ActionHandlerIntrospection, HandlerCapabilities, ActionSpec}; @@ -7,11 +11,22 @@ use crate::canvas::state::CanvasState; use crate::config::CanvasConfig; use anyhow::Result; -const FOR_EDIT_MODE: bool = true; // Edit mode flag - +/// Edit mode uses cursor-past-end behavior for text insertion +const FOR_EDIT_MODE: bool = true; + +/// Empty struct that implements edit mode capabilities pub struct EditHandler; /// Handle actions in edit mode with edit-specific cursor behavior +/// +/// Edit mode allows text modification and uses cursor positioning that can +/// go past the end of existing text to facilitate insertion. +/// +/// # Arguments +/// * `action` - The action to perform +/// * `state` - Mutable canvas state +/// * `ideal_cursor_column` - Desired column for vertical movement (maintained across line changes) +/// * `config` - Optional configuration for behavior customization pub async fn handle_edit_action( action: CanvasAction, state: &mut S, @@ -20,6 +35,7 @@ pub async fn handle_edit_action( ) -> Result { match action { CanvasAction::InsertChar(c) => { + // Insert character at cursor position and advance cursor let cursor_pos = state.current_cursor_pos(); let input = state.get_current_input_mut(); input.insert(cursor_pos, c); @@ -30,6 +46,7 @@ pub async fn handle_edit_action( } CanvasAction::DeleteBackward => { + // Delete character before cursor (Backspace behavior) let cursor_pos = state.current_cursor_pos(); if cursor_pos > 0 { let input = state.get_current_input_mut(); @@ -42,6 +59,7 @@ pub async fn handle_edit_action( } CanvasAction::DeleteForward => { + // Delete character at cursor position (Delete key behavior) let cursor_pos = state.current_cursor_pos(); let input = state.get_current_input_mut(); if cursor_pos < input.len() { @@ -51,6 +69,7 @@ pub async fn handle_edit_action( Ok(ActionResult::success()) } + // Cursor movement actions CanvasAction::MoveLeft => { let new_pos = move_left(state.current_cursor_pos()); state.set_current_cursor_pos(new_pos); @@ -67,8 +86,8 @@ pub async fn handle_edit_action( Ok(ActionResult::success()) } + // Field navigation (treating single-line fields as "lines") CanvasAction::MoveUp => { - // For single-line fields, move to previous field let current_field = state.current_field(); if current_field > 0 { state.set_current_field(current_field - 1); @@ -80,7 +99,6 @@ pub async fn handle_edit_action( } CanvasAction::MoveDown => { - // For single-line fields, move to next field let current_field = state.current_field(); let total_fields = state.fields().len(); if current_field < total_fields - 1 { @@ -92,6 +110,7 @@ pub async fn handle_edit_action( Ok(ActionResult::success()) } + // Line-based movement CanvasAction::MoveLineStart => { let new_pos = line_start_position(); state.set_current_cursor_pos(new_pos); @@ -107,6 +126,7 @@ pub async fn handle_edit_action( Ok(ActionResult::success()) } + // Document-level movement (first/last field) CanvasAction::MoveFirstLine => { state.set_current_field(0); let current_input = state.get_current_input(); @@ -126,6 +146,7 @@ pub async fn handle_edit_action( Ok(ActionResult::success()) } + // Word-based movement CanvasAction::MoveWordNext => { let current_input = state.get_current_input(); if !current_input.is_empty() { @@ -166,6 +187,7 @@ pub async fn handle_edit_action( Ok(ActionResult::success()) } + // Field navigation with wrapping behavior CanvasAction::NextField | CanvasAction::PrevField => { let current_field = state.current_field(); let total_fields = state.fields().len(); @@ -173,16 +195,16 @@ pub async fn handle_edit_action( let new_field = match action { CanvasAction::NextField => { if config.map_or(true, |c| c.behavior.wrap_around_fields) { - (current_field + 1) % total_fields + (current_field + 1) % total_fields // Wrap to first field } else { - (current_field + 1).min(total_fields - 1) + (current_field + 1).min(total_fields - 1) // Stop at last field } } CanvasAction::PrevField => { if config.map_or(true, |c| c.behavior.wrap_around_fields) { - if current_field == 0 { total_fields - 1 } else { current_field - 1 } + if current_field == 0 { total_fields - 1 } else { current_field - 1 } // Wrap to last field } else { - current_field.saturating_sub(1) + current_field.saturating_sub(1) // Stop at first field } } _ => unreachable!(), @@ -206,10 +228,12 @@ pub async fn handle_edit_action( } impl ActionHandlerIntrospection for EditHandler { + /// Report all actions this handler supports with examples and requirements + /// Used for automatic config generation and validation fn introspect() -> HandlerCapabilities { let mut actions = Vec::new(); - // REQUIRED ACTIONS - These must be configured for edit mode to work + // REQUIRED ACTIONS - These must be configured for edit mode to work properly actions.push(ActionSpec { name: "move_left".to_string(), description: "Move cursor one position to the left".to_string(), @@ -240,7 +264,7 @@ impl ActionHandlerIntrospection for EditHandler { actions.push(ActionSpec { name: "delete_char_backward".to_string(), - description: "Delete character before cursor".to_string(), + description: "Delete character before cursor (Backspace)".to_string(), examples: vec!["Backspace".to_string()], is_required: true, }); @@ -318,7 +342,7 @@ impl ActionHandlerIntrospection for EditHandler { actions.push(ActionSpec { name: "delete_char_forward".to_string(), - description: "Delete character after cursor".to_string(), + description: "Delete character after cursor (Delete key)".to_string(), examples: vec!["Delete".to_string()], is_required: false, }); @@ -327,7 +351,7 @@ impl ActionHandlerIntrospection for EditHandler { mode_name: "edit".to_string(), actions, auto_handled: vec![ - "insert_char".to_string(), // Any printable character + "insert_char".to_string(), // Any printable character is inserted automatically ], } } diff --git a/canvas/src/canvas/state.rs b/canvas/src/canvas/state.rs index 29915ba..f8a3e9b 100644 --- a/canvas/src/canvas/state.rs +++ b/canvas/src/canvas/state.rs @@ -1,48 +1,107 @@ // src/canvas/state.rs +//! Canvas state trait and related types +//! +//! This module defines the core trait that any form or input system must implement +//! to work with the canvas library. use crate::canvas::actions::CanvasAction; use crate::canvas::modes::AppMode; -/// Context passed to feature-specific action handlers +/// Context information passed to feature-specific action handlers #[derive(Debug)] pub struct ActionContext { - pub key_code: Option, // Kept for backwards compatibility + /// Original key code that triggered this action (for backwards compatibility) + pub key_code: Option, + /// Current ideal cursor column for vertical movement pub ideal_cursor_column: usize, + /// Current input text pub current_input: String, + /// Current field index 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. +/// Core trait that any form-like state must implement to work with canvas +/// +/// This trait enables the same mode behaviors (edit, read-only, highlight) to work +/// across any implementation - login forms, data entry forms, configuration screens, etc. +/// +/// # Required Implementation +/// +/// Your struct needs to track: +/// - Current field index and cursor position +/// - All input field values +/// - Current interaction mode +/// - Whether there are unsaved changes +/// +/// # Example Implementation +/// +/// ```rust +/// struct MyForm { +/// fields: Vec, +/// current_field: usize, +/// cursor_pos: usize, +/// mode: AppMode, +/// dirty: bool, +/// } +/// +/// impl CanvasState for MyForm { +/// fn current_field(&self) -> usize { self.current_field } +/// fn current_cursor_pos(&self) -> usize { self.cursor_pos } +/// // ... implement other required methods +/// } +/// ``` pub trait CanvasState { // --- Core Navigation --- + + /// Get current field index (0-based) fn current_field(&self) -> usize; + + /// Get current cursor position within the current field fn current_cursor_pos(&self) -> usize; + + /// Set current field index (should clamp to valid range) fn set_current_field(&mut self, index: usize); + + /// Set cursor position within current field (should clamp to valid range) fn set_current_cursor_pos(&mut self, pos: usize); // --- Mode Information --- + + /// Get current interaction mode (edit, read-only, highlight, etc.) fn current_mode(&self) -> AppMode; // --- Data Access --- + + /// Get immutable reference to current field's text fn get_current_input(&self) -> &str; + + /// Get mutable reference to current field's text fn get_current_input_mut(&mut self) -> &mut String; + + /// Get all input values as immutable references fn inputs(&self) -> Vec<&String>; + + /// Get all field names/labels fn fields(&self) -> Vec<&str>; // --- State Management --- + + /// Check if there are unsaved changes fn has_unsaved_changes(&self) -> bool; + + /// Mark whether there are unsaved changes fn set_has_unsaved_changes(&mut self, changed: bool); - // --- Feature-specific action handling --- + // --- Optional Overrides --- - /// Feature-specific action handling (Type-safe) + /// Handle application-specific actions not covered by standard handlers + /// Return Some(message) if the action was handled, None to use standard handling fn handle_feature_action(&mut self, _action: &CanvasAction, _context: &ActionContext) -> Option { - None // Default: no feature-specific handling + None // Default: no custom handling } - // --- Display Overrides (for links, computed values, etc.) --- + /// Get display value for a field (may differ from actual value) + /// Used for things like password masking or computed display values fn get_display_value_for_field(&self, index: usize) -> &str { self.inputs() .get(index) @@ -50,6 +109,8 @@ pub trait CanvasState { .unwrap_or("") } + /// Check if a field has a custom display value + /// Return true if get_display_value_for_field returns something different than the actual value fn has_display_override(&self, _index: usize) -> bool { false } diff --git a/canvas/src/config/introspection.rs b/canvas/src/config/introspection.rs index 052a2e6..3986b4e 100644 --- a/canvas/src/config/introspection.rs +++ b/canvas/src/config/introspection.rs @@ -1,43 +1,60 @@ // src/config/introspection.rs +//! Handler capability introspection system +//! +//! This module provides traits and utilities for handlers to report their capabilities, +//! enabling automatic configuration generation and validation. use std::collections::HashMap; +/// Specification for a single action that a handler can perform #[derive(Debug, Clone)] pub struct ActionSpec { + /// Action name (e.g., "move_left", "delete_char_backward") pub name: String, + /// Human-readable description of what this action does pub description: String, + /// Example keybindings for this action (e.g., ["Left", "h"]) pub examples: Vec, + /// Whether this action is required for the handler to function properly pub is_required: bool, } +/// Complete capability description for a single handler #[derive(Debug, Clone)] pub struct HandlerCapabilities { + /// Mode name this handler operates in (e.g., "edit", "read_only") pub mode_name: String, + /// All actions this handler can perform pub actions: Vec, - pub auto_handled: Vec, // Actions handled automatically (like insert_char) + /// Actions handled automatically without configuration (e.g., "insert_char") + pub auto_handled: Vec, } -/// Trait that each handler implements to report its capabilities +/// Trait that handlers implement to report their capabilities +/// +/// This enables the configuration system to automatically discover what actions +/// are available and validate user configurations against actual implementations. pub trait ActionHandlerIntrospection { - /// Return the capabilities of this handler + /// Return complete capability information for this handler fn introspect() -> HandlerCapabilities; - /// Validate that this handler actually supports the claimed actions + /// Validate that this handler actually supports its claimed actions + /// Override this to add custom validation logic fn validate_capabilities() -> Result<(), String> { - // Default implementation - handlers can override for custom validation - Ok(()) + Ok(()) // Default: assume handler is valid } } -/// System that discovers all handler capabilities +/// Discovers capabilities from all registered handlers pub struct HandlerDiscovery; impl HandlerDiscovery { - /// Discover all handler capabilities by calling their introspect methods + /// Discover capabilities from all known handlers + /// Add new handlers to this function as they are created pub fn discover_all() -> HashMap { let mut capabilities = HashMap::new(); - // Import and introspect each handler + // Register all known handlers here let edit_caps = crate::canvas::actions::handlers::edit::EditHandler::introspect(); capabilities.insert("edit".to_string(), edit_caps); @@ -50,10 +67,11 @@ impl HandlerDiscovery { capabilities } - /// Validate that all handlers actually support their claimed actions + /// Validate all handlers support their claimed capabilities pub fn validate_all_handlers() -> Result<(), Vec> { let mut errors = Vec::new(); + // Validate each handler if let Err(e) = crate::canvas::actions::handlers::edit::EditHandler::validate_capabilities() { errors.push(format!("Edit handler: {}", e)); }