documented code now
This commit is contained in:
@@ -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
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user