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
//! 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<S: CanvasState>(
action: CanvasAction,
state: &mut S,
@@ -20,6 +35,7 @@ pub async fn handle_edit_action<S: CanvasState>(
) -> Result<ActionResult> {
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<S: CanvasState>(
}
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<S: CanvasState>(
}
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<S: CanvasState>(
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<S: CanvasState>(
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<S: CanvasState>(
}
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<S: CanvasState>(
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<S: CanvasState>(
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<S: CanvasState>(
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<S: CanvasState>(
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<S: CanvasState>(
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<S: CanvasState>(
}
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
],
}
}

View File

@@ -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<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,
/// 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<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 {
// --- 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<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 {
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
}

View File

@@ -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<String>,
/// 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<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 {
/// 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<String, HandlerCapabilities> {
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<String>> {
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));
}