documented code now

This commit is contained in:
Priec
2025-07-31 17:29:03 +02:00
parent 59a29aa54b
commit 3f4380ff48
3 changed files with 134 additions and 31 deletions

View File

@@ -1,4 +1,8 @@
// src/canvas/actions/handlers/edit.rs // 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::canvas::actions::types::{CanvasAction, ActionResult};
use crate::config::introspection::{ActionHandlerIntrospection, HandlerCapabilities, ActionSpec}; use crate::config::introspection::{ActionHandlerIntrospection, HandlerCapabilities, ActionSpec};
@@ -7,11 +11,22 @@ use crate::canvas::state::CanvasState;
use crate::config::CanvasConfig; use crate::config::CanvasConfig;
use anyhow::Result; 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; pub struct EditHandler;
/// Handle actions in edit mode with edit-specific cursor behavior /// 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<S: CanvasState>( pub async fn handle_edit_action<S: CanvasState>(
action: CanvasAction, action: CanvasAction,
state: &mut S, state: &mut S,
@@ -20,6 +35,7 @@ pub async fn handle_edit_action<S: CanvasState>(
) -> Result<ActionResult> { ) -> Result<ActionResult> {
match action { match action {
CanvasAction::InsertChar(c) => { CanvasAction::InsertChar(c) => {
// Insert character at cursor position and advance cursor
let cursor_pos = state.current_cursor_pos(); let cursor_pos = state.current_cursor_pos();
let input = state.get_current_input_mut(); let input = state.get_current_input_mut();
input.insert(cursor_pos, c); input.insert(cursor_pos, c);
@@ -30,6 +46,7 @@ pub async fn handle_edit_action<S: CanvasState>(
} }
CanvasAction::DeleteBackward => { CanvasAction::DeleteBackward => {
// Delete character before cursor (Backspace behavior)
let cursor_pos = state.current_cursor_pos(); let cursor_pos = state.current_cursor_pos();
if cursor_pos > 0 { if cursor_pos > 0 {
let input = state.get_current_input_mut(); let input = state.get_current_input_mut();
@@ -42,6 +59,7 @@ pub async fn handle_edit_action<S: CanvasState>(
} }
CanvasAction::DeleteForward => { CanvasAction::DeleteForward => {
// Delete character at cursor position (Delete key behavior)
let cursor_pos = state.current_cursor_pos(); let cursor_pos = state.current_cursor_pos();
let input = state.get_current_input_mut(); let input = state.get_current_input_mut();
if cursor_pos < input.len() { if cursor_pos < input.len() {
@@ -51,6 +69,7 @@ pub async fn handle_edit_action<S: CanvasState>(
Ok(ActionResult::success()) Ok(ActionResult::success())
} }
// Cursor movement actions
CanvasAction::MoveLeft => { CanvasAction::MoveLeft => {
let new_pos = move_left(state.current_cursor_pos()); let new_pos = move_left(state.current_cursor_pos());
state.set_current_cursor_pos(new_pos); state.set_current_cursor_pos(new_pos);
@@ -67,8 +86,8 @@ pub async fn handle_edit_action<S: CanvasState>(
Ok(ActionResult::success()) Ok(ActionResult::success())
} }
// Field navigation (treating single-line fields as "lines")
CanvasAction::MoveUp => { CanvasAction::MoveUp => {
// For single-line fields, move to previous field
let current_field = state.current_field(); let current_field = state.current_field();
if current_field > 0 { if current_field > 0 {
state.set_current_field(current_field - 1); state.set_current_field(current_field - 1);
@@ -80,7 +99,6 @@ pub async fn handle_edit_action<S: CanvasState>(
} }
CanvasAction::MoveDown => { CanvasAction::MoveDown => {
// For single-line fields, move to next field
let current_field = state.current_field(); let current_field = state.current_field();
let total_fields = state.fields().len(); let total_fields = state.fields().len();
if current_field < total_fields - 1 { if current_field < total_fields - 1 {
@@ -92,6 +110,7 @@ pub async fn handle_edit_action<S: CanvasState>(
Ok(ActionResult::success()) Ok(ActionResult::success())
} }
// Line-based movement
CanvasAction::MoveLineStart => { CanvasAction::MoveLineStart => {
let new_pos = line_start_position(); let new_pos = line_start_position();
state.set_current_cursor_pos(new_pos); state.set_current_cursor_pos(new_pos);
@@ -107,6 +126,7 @@ pub async fn handle_edit_action<S: CanvasState>(
Ok(ActionResult::success()) Ok(ActionResult::success())
} }
// Document-level movement (first/last field)
CanvasAction::MoveFirstLine => { CanvasAction::MoveFirstLine => {
state.set_current_field(0); state.set_current_field(0);
let current_input = state.get_current_input(); let current_input = state.get_current_input();
@@ -126,6 +146,7 @@ pub async fn handle_edit_action<S: CanvasState>(
Ok(ActionResult::success()) Ok(ActionResult::success())
} }
// Word-based movement
CanvasAction::MoveWordNext => { CanvasAction::MoveWordNext => {
let current_input = state.get_current_input(); let current_input = state.get_current_input();
if !current_input.is_empty() { if !current_input.is_empty() {
@@ -166,6 +187,7 @@ pub async fn handle_edit_action<S: CanvasState>(
Ok(ActionResult::success()) Ok(ActionResult::success())
} }
// Field navigation with wrapping behavior
CanvasAction::NextField | CanvasAction::PrevField => { CanvasAction::NextField | CanvasAction::PrevField => {
let current_field = state.current_field(); let current_field = state.current_field();
let total_fields = state.fields().len(); let total_fields = state.fields().len();
@@ -173,16 +195,16 @@ pub async fn handle_edit_action<S: CanvasState>(
let new_field = match action { let new_field = match action {
CanvasAction::NextField => { CanvasAction::NextField => {
if config.map_or(true, |c| c.behavior.wrap_around_fields) { 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 { } else {
(current_field + 1).min(total_fields - 1) (current_field + 1).min(total_fields - 1) // Stop at last field
} }
} }
CanvasAction::PrevField => { CanvasAction::PrevField => {
if config.map_or(true, |c| c.behavior.wrap_around_fields) { 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 { } else {
current_field.saturating_sub(1) current_field.saturating_sub(1) // Stop at first field
} }
} }
_ => unreachable!(), _ => unreachable!(),
@@ -206,10 +228,12 @@ pub async fn handle_edit_action<S: CanvasState>(
} }
impl ActionHandlerIntrospection for EditHandler { impl ActionHandlerIntrospection for EditHandler {
/// Report all actions this handler supports with examples and requirements
/// Used for automatic config generation and validation
fn introspect() -> HandlerCapabilities { fn introspect() -> HandlerCapabilities {
let mut actions = Vec::new(); 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 { actions.push(ActionSpec {
name: "move_left".to_string(), name: "move_left".to_string(),
description: "Move cursor one position to the left".to_string(), description: "Move cursor one position to the left".to_string(),
@@ -240,7 +264,7 @@ impl ActionHandlerIntrospection for EditHandler {
actions.push(ActionSpec { actions.push(ActionSpec {
name: "delete_char_backward".to_string(), 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()], examples: vec!["Backspace".to_string()],
is_required: true, is_required: true,
}); });
@@ -318,7 +342,7 @@ impl ActionHandlerIntrospection for EditHandler {
actions.push(ActionSpec { actions.push(ActionSpec {
name: "delete_char_forward".to_string(), 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()], examples: vec!["Delete".to_string()],
is_required: false, is_required: false,
}); });
@@ -327,7 +351,7 @@ impl ActionHandlerIntrospection for EditHandler {
mode_name: "edit".to_string(), mode_name: "edit".to_string(),
actions, actions,
auto_handled: vec![ auto_handled: vec![
"insert_char".to_string(), // Any printable character "insert_char".to_string(), // Any printable character is inserted automatically
], ],
} }
} }

View File

@@ -1,48 +1,107 @@
// src/canvas/state.rs // 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::actions::CanvasAction;
use crate::canvas::modes::AppMode; use crate::canvas::modes::AppMode;
/// Context passed to feature-specific action handlers /// Context information passed to feature-specific action handlers
#[derive(Debug)] #[derive(Debug)]
pub struct ActionContext { pub struct ActionContext {
pub key_code: Option<crossterm::event::KeyCode>, // Kept for backwards compatibility /// Original key code that triggered this action (for backwards compatibility)
pub key_code: Option<crossterm::event::KeyCode>,
/// Current ideal cursor column for vertical movement
pub ideal_cursor_column: usize, pub ideal_cursor_column: usize,
/// Current input text
pub current_input: String, pub current_input: String,
/// Current field index
pub current_field: usize, pub current_field: usize,
} }
/// Core trait that any form-like state must implement to work with the canvas system. /// Core trait that any form-like state must implement to work with canvas
/// This enables the same mode behaviors (edit, read-only, highlight) to work across ///
/// any implementation - login forms, data entry forms, configuration screens, etc. /// 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<String>,
/// 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 { pub trait CanvasState {
// --- Core Navigation --- // --- Core Navigation ---
/// Get current field index (0-based)
fn current_field(&self) -> usize; fn current_field(&self) -> usize;
/// Get current cursor position within the current field
fn current_cursor_pos(&self) -> usize; fn current_cursor_pos(&self) -> usize;
/// Set current field index (should clamp to valid range)
fn set_current_field(&mut self, index: usize); 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); fn set_current_cursor_pos(&mut self, pos: usize);
// --- Mode Information --- // --- Mode Information ---
/// Get current interaction mode (edit, read-only, highlight, etc.)
fn current_mode(&self) -> AppMode; fn current_mode(&self) -> AppMode;
// --- Data Access --- // --- Data Access ---
/// Get immutable reference to current field's text
fn get_current_input(&self) -> &str; fn get_current_input(&self) -> &str;
/// Get mutable reference to current field's text
fn get_current_input_mut(&mut self) -> &mut String; fn get_current_input_mut(&mut self) -> &mut String;
/// Get all input values as immutable references
fn inputs(&self) -> Vec<&String>; fn inputs(&self) -> Vec<&String>;
/// Get all field names/labels
fn fields(&self) -> Vec<&str>; fn fields(&self) -> Vec<&str>;
// --- State Management --- // --- State Management ---
/// Check if there are unsaved changes
fn has_unsaved_changes(&self) -> bool; fn has_unsaved_changes(&self) -> bool;
/// Mark whether there are unsaved changes
fn set_has_unsaved_changes(&mut self, changed: bool); 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<String> { fn handle_feature_action(&mut self, _action: &CanvasAction, _context: &ActionContext) -> Option<String> {
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 { fn get_display_value_for_field(&self, index: usize) -> &str {
self.inputs() self.inputs()
.get(index) .get(index)
@@ -50,6 +109,8 @@ pub trait CanvasState {
.unwrap_or("") .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 { fn has_display_override(&self, _index: usize) -> bool {
false false
} }

View File

@@ -1,43 +1,60 @@
// src/config/introspection.rs // 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; use std::collections::HashMap;
/// Specification for a single action that a handler can perform
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ActionSpec { pub struct ActionSpec {
/// Action name (e.g., "move_left", "delete_char_backward")
pub name: String, pub name: String,
/// Human-readable description of what this action does
pub description: String, pub description: String,
/// Example keybindings for this action (e.g., ["Left", "h"])
pub examples: Vec<String>, pub examples: Vec<String>,
/// Whether this action is required for the handler to function properly
pub is_required: bool, pub is_required: bool,
} }
/// Complete capability description for a single handler
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct HandlerCapabilities { pub struct HandlerCapabilities {
/// Mode name this handler operates in (e.g., "edit", "read_only")
pub mode_name: String, pub mode_name: String,
/// All actions this handler can perform
pub actions: Vec<ActionSpec>, pub actions: Vec<ActionSpec>,
pub auto_handled: Vec<String>, // Actions handled automatically (like insert_char) /// Actions handled automatically without configuration (e.g., "insert_char")
pub auto_handled: Vec<String>,
} }
/// 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 { pub trait ActionHandlerIntrospection {
/// Return the capabilities of this handler /// Return complete capability information for this handler
fn introspect() -> HandlerCapabilities; 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> { fn validate_capabilities() -> Result<(), String> {
// Default implementation - handlers can override for custom validation Ok(()) // Default: assume handler is valid
Ok(())
} }
} }
/// System that discovers all handler capabilities /// Discovers capabilities from all registered handlers
pub struct HandlerDiscovery; pub struct HandlerDiscovery;
impl 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<String, HandlerCapabilities> { pub fn discover_all() -> HashMap<String, HandlerCapabilities> {
let mut capabilities = HashMap::new(); 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(); let edit_caps = crate::canvas::actions::handlers::edit::EditHandler::introspect();
capabilities.insert("edit".to_string(), edit_caps); capabilities.insert("edit".to_string(), edit_caps);
@@ -50,10 +67,11 @@ impl HandlerDiscovery {
capabilities capabilities
} }
/// Validate that all handlers actually support their claimed actions /// Validate all handlers support their claimed capabilities
pub fn validate_all_handlers() -> Result<(), Vec<String>> { pub fn validate_all_handlers() -> Result<(), Vec<String>> {
let mut errors = Vec::new(); let mut errors = Vec::new();
// Validate each handler
if let Err(e) = crate::canvas::actions::handlers::edit::EditHandler::validate_capabilities() { if let Err(e) = crate::canvas::actions::handlers::edit::EditHandler::validate_capabilities() {
errors.push(format!("Edit handler: {}", e)); errors.push(format!("Edit handler: {}", e));
} }