fixed canvas library

This commit is contained in:
Priec
2025-07-31 20:44:23 +02:00
parent 5b64996462
commit 8788323c62
2 changed files with 261 additions and 190 deletions

View File

@@ -58,12 +58,12 @@ impl DemoFormState {
fn new() -> Self { fn new() -> Self {
Self { Self {
fields: vec![ fields: vec![
"John Doe".to_string(), // Name - has words to test with "John Doe".to_string(),
"john.doe@example.com".to_string(), // Email - has punctuation "john.doe@example.com".to_string(),
"+1 234 567 8900".to_string(), // Phone - has spaces and numbers "+1 234 567 8900".to_string(),
"123 Main Street Apt 4B".to_string(), // Address - multiple words "123 Main Street Apt 4B".to_string(),
"San Francisco".to_string(), // City - two words "San Francisco".to_string(),
"This is a test comment with multiple words".to_string(), // Comments - lots of words "This is a test comment with multiple words".to_string(),
], ],
field_names: vec![ field_names: vec![
"Name".to_string(), "Name".to_string(),
@@ -80,7 +80,7 @@ impl DemoFormState {
has_changes: false, has_changes: false,
ideal_cursor_column: 0, ideal_cursor_column: 0,
last_action: None, last_action: None,
debug_message: "Ready - Form loaded with sample data".to_string(), debug_message: "Ready".to_string(),
} }
} }
@@ -108,11 +108,6 @@ impl DemoFormState {
self.debug_message = "Entered VISUAL mode".to_string(); self.debug_message = "Entered VISUAL mode".to_string();
} }
} }
fn log_action(&mut self, action: &str) {
self.last_action = Some(action.to_string());
self.debug_message = format!("Action: {}", action);
}
} }
impl CanvasState for DemoFormState { impl CanvasState for DemoFormState {
@@ -126,7 +121,6 @@ impl CanvasState for DemoFormState {
fn set_current_field(&mut self, index: usize) { fn set_current_field(&mut self, index: usize) {
self.current_field = index.min(self.fields.len().saturating_sub(1)); self.current_field = index.min(self.fields.len().saturating_sub(1));
// Reset cursor to end of field when switching
self.cursor_pos = self.fields[self.current_field].len(); self.cursor_pos = self.fields[self.current_field].len();
} }
@@ -164,17 +158,6 @@ impl CanvasState for DemoFormState {
} }
fn handle_feature_action(&mut self, action: &CanvasAction, _context: &ActionContext) -> Option<String> { fn handle_feature_action(&mut self, action: &CanvasAction, _context: &ActionContext) -> Option<String> {
// FOCUS: Debug specifically for 'w' key (move_word_next)
if let CanvasAction::MoveWordNext = action {
let current_input = self.get_current_input();
let old_cursor = self.cursor_pos;
self.debug_message = format!("🔍 MoveWordNext: cursor {} -> text '{}' (len {})",
old_cursor, current_input, current_input.len());
// Return None to let the handler process it, but we'll see this debug message
return None;
}
match action { match action {
CanvasAction::Custom(cmd) => { CanvasAction::Custom(cmd) => {
match cmd.as_str() { match cmd.as_str() {
@@ -205,15 +188,7 @@ async fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut state: DemoFormStat
terminal.draw(|f| ui(f, &state, &theme))?; terminal.draw(|f| ui(f, &state, &theme))?;
if let Event::Key(key) = event::read()? { if let Event::Key(key) = event::read()? {
// BASIC DEBUG: Show EVERY key press for j, k, w // Handle quit
match key.code {
KeyCode::Char('j') | KeyCode::Char('k') | KeyCode::Char('w') => {
println!("🔥 KEY PRESSED: {:?} with modifiers {:?}", key.code, key.modifiers);
}
_ => {}
}
// Handle quit - multiple options
if (key.code == KeyCode::Char('q') && key.modifiers.contains(KeyModifiers::CONTROL)) || if (key.code == KeyCode::Char('q') && key.modifiers.contains(KeyModifiers::CONTROL)) ||
(key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL)) || (key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL)) ||
key.code == KeyCode::F(10) { key.code == KeyCode::F(10) {
@@ -223,51 +198,8 @@ async fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut state: DemoFormStat
let is_edit_mode = state.mode == AppMode::Edit; let is_edit_mode = state.mode == AppMode::Edit;
let mut handled = false; let mut handled = false;
// Debug: Show what key was pressed and check config lookup // First priority: Try to dispatch through config system
let key_debug = format!("{:?}", key.code);
let config_action = if is_edit_mode {
config.get_edit_action(key.code, key.modifiers)
} else {
config.get_read_only_action(key.code, key.modifiers)
};
// FOCUS: Special debug for j, k, w keys
match key.code {
KeyCode::Char('j') => {
println!("🔥 J KEY: Config action: {:?}", config_action);
state.debug_message = format!("🔍 'j' KEY: Mode={} | Config action: {:?}",
if is_edit_mode { "EDIT" } else { "READ-ONLY" }, config_action);
}
KeyCode::Char('k') => {
println!("🔥 K KEY: Config action: {:?}", config_action);
state.debug_message = format!("🔍 'k' KEY: Mode={} | Config action: {:?}",
if is_edit_mode { "EDIT" } else { "READ-ONLY" }, config_action);
}
KeyCode::Char('w') => {
println!("🔥 W KEY: Config action: {:?}", config_action);
state.debug_message = format!("🔍 'w' KEY: Mode={} | Config action: {:?} | Current pos: {} | Text: '{}'",
if is_edit_mode { "EDIT" } else { "READ-ONLY" },
config_action,
state.cursor_pos,
state.get_current_input());
}
_ => {
state.debug_message = format!("Key: {} | Mods: {:?} | Mode: {} | Config found: {:?}",
key_debug, key.modifiers,
if is_edit_mode { "EDIT" } else { "READ-ONLY" },
config_action);
}
}
// First priority: Try to dispatch through your config system
let mut ideal_cursor = state.ideal_cursor_column; let mut ideal_cursor = state.ideal_cursor_column;
let old_cursor_pos = state.cursor_pos; // Track cursor before action
// EXTRA DEBUG for w key
if key.code == KeyCode::Char('w') {
println!("🔥 W KEY: About to call ActionDispatcher::dispatch_key");
println!("🔥 W KEY: cursor before = {}, text = '{}'", old_cursor_pos, state.get_current_input());
}
if let Ok(Some(result)) = ActionDispatcher::dispatch_key( if let Ok(Some(result)) = ActionDispatcher::dispatch_key(
key.code, key.code,
@@ -275,21 +207,10 @@ async fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut state: DemoFormStat
&mut state, &mut state,
&mut ideal_cursor, &mut ideal_cursor,
is_edit_mode, is_edit_mode,
false, // no autocomplete suggestions false,
).await { ).await {
state.ideal_cursor_column = ideal_cursor; state.ideal_cursor_column = ideal_cursor;
state.debug_message = format!("Config handled: {:?}", key.code);
let new_cursor_pos = state.cursor_pos; // Track cursor after action
// FOCUS: Special debug for 'w' key
if key.code == KeyCode::Char('w') {
println!("SUCCESS W KEY PROCESSED: cursor {} -> {} | text: '{}'", old_cursor_pos, new_cursor_pos, state.get_current_input());
state.debug_message = format!("SUCCESS 'w' PROCESSED: cursor {} -> {} | text: '{}'",
old_cursor_pos, new_cursor_pos, state.get_current_input());
} else {
state.debug_message = format!("SUCCESS Config handled: {} -> {}", key_debug,
result.message().unwrap_or("success"));
}
// Mark as changed for text modification keys in edit mode // Mark as changed for text modification keys in edit mode
if is_edit_mode { if is_edit_mode {
@@ -301,34 +222,9 @@ async fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut state: DemoFormStat
} }
} }
handled = true; handled = true;
} else {
// Debug dispatch failures
if key.code == KeyCode::Char('w') {
println!("FAILED W KEY: ActionDispatcher::dispatch_key returned None or Error");
// Try calling dispatch_with_config directly to see the error
let action = CanvasAction::MoveWordNext;
println!("FAILED W KEY: Trying direct dispatch of MoveWordNext action");
match ActionDispatcher::dispatch_with_config(
action,
&mut state,
&mut ideal_cursor,
Some(&config),
).await {
Ok(result) => {
println!("FAILED W KEY: Direct dispatch SUCCESS: {:?}", result);
state.debug_message = "Direct dispatch worked!".to_string();
}
Err(e) => {
println!("FAILED W KEY: Direct dispatch ERROR: {:?}", e);
state.debug_message = format!("Direct dispatch error: {:?}", e);
}
}
}
} }
// Second priority: Handle character input in edit mode (if not handled by config) // Second priority: Handle character input in edit mode
if !handled && is_edit_mode { if !handled && is_edit_mode {
if let KeyCode::Char(c) = key.code { if let KeyCode::Char(c) = key.code {
if !key.modifiers.contains(KeyModifiers::CONTROL) && !key.modifiers.contains(KeyModifiers::ALT) { if !key.modifiers.contains(KeyModifiers::CONTROL) && !key.modifiers.contains(KeyModifiers::ALT) {
@@ -349,45 +245,33 @@ async fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut state: DemoFormStat
} }
} }
// Third priority: Fallback mode transitions (if not handled by config) // Third priority: Fallback mode transitions
if !handled { if !handled {
match (state.mode, key.code) { match (state.mode, key.code) {
// ReadOnly -> Edit mode fallbacks
(AppMode::ReadOnly, KeyCode::Char('i') | KeyCode::Char('a') | KeyCode::Insert) => { (AppMode::ReadOnly, KeyCode::Char('i') | KeyCode::Char('a') | KeyCode::Insert) => {
state.enter_edit_mode(); state.enter_edit_mode();
if key.code == KeyCode::Char('a') { if key.code == KeyCode::Char('a') {
state.cursor_pos = state.fields[state.current_field].len(); state.cursor_pos = state.fields[state.current_field].len();
} }
state.debug_message = format!("Fallback: entered edit mode via {:?}", key.code); state.debug_message = format!("Entered edit mode via {:?}", key.code);
handled = true; handled = true;
} }
// ReadOnly -> Visual mode fallback
(AppMode::ReadOnly, KeyCode::Char('v')) => { (AppMode::ReadOnly, KeyCode::Char('v')) => {
state.enter_highlight_mode(); state.enter_highlight_mode();
state.debug_message = "Fallback: entered visual mode via 'v'".to_string(); state.debug_message = "Entered visual mode".to_string();
handled = true; handled = true;
} }
// Any mode -> ReadOnly fallback
(_, KeyCode::Esc) => { (_, KeyCode::Esc) => {
state.enter_readonly_mode(); state.enter_readonly_mode();
state.debug_message = "Fallback: entered read-only mode via Esc".to_string(); state.debug_message = "Entered read-only mode".to_string();
handled = true; handled = true;
} }
_ => {} _ => {}
} }
} }
// If nothing handled the key, show more debug info
if !handled { if !handled {
let available_actions: Vec<String> = if is_edit_mode { state.debug_message = format!("Unhandled key: {:?}", key.code);
config.keybindings.edit.keys().cloned().collect()
} else {
config.keybindings.read_only.keys().cloned().collect()
};
state.debug_message = format!("❌ Unhandled: {} | Available actions: {}",
key_debug,
available_actions.join(", "));
} }
} }
} }
@@ -399,8 +283,8 @@ fn ui(f: &mut Frame, state: &DemoFormState, theme: &DemoTheme) {
let chunks = Layout::default() let chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints([ .constraints([
Constraint::Min(8), // Main form area Constraint::Min(8),
Constraint::Length(4), // Status area (increased for debug info) Constraint::Length(4),
]) ])
.split(f.area()); .split(f.area());
@@ -414,7 +298,7 @@ fn ui(f: &mut Frame, state: &DemoFormState, theme: &DemoTheme) {
&state.highlight_state, &state.highlight_state,
); );
// Render status bar with debug info // Render status bar
let mode_text = match state.mode { let mode_text = match state.mode {
AppMode::Edit => "EDIT", AppMode::Edit => "EDIT",
AppMode::ReadOnly => "NORMAL", AppMode::ReadOnly => "NORMAL",
@@ -448,30 +332,25 @@ fn ui(f: &mut Frame, state: &DemoFormState, theme: &DemoTheme) {
Line::from(Span::styled(state.debug_message.clone(), Style::default().fg(theme.warning()))), Line::from(Span::styled(state.debug_message.clone(), Style::default().fg(theme.warning()))),
Line::from(Span::styled(help_text, Style::default().fg(theme.secondary()))), Line::from(Span::styled(help_text, Style::default().fg(theme.secondary()))),
]) ])
.block(Block::default().borders(Borders::ALL).title("Status & Debug")); .block(Block::default().borders(Borders::ALL).title("Status"));
f.render_widget(status, chunks[1]); f.render_widget(status, chunks[1]);
} }
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load configuration
let config = CanvasConfig::load(); let config = CanvasConfig::load();
// Setup terminal
enable_raw_mode()?; enable_raw_mode()?;
let mut stdout = io::stdout(); let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stdout); let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?; let mut terminal = Terminal::new(backend)?;
// Create demo state
let state = DemoFormState::new(); let state = DemoFormState::new();
// Run app
let res = run_app(&mut terminal, state, config).await; let res = run_app(&mut terminal, state, config).await;
// Restore terminal
disable_raw_mode()?; disable_raw_mode()?;
execute!( execute!(
terminal.backend_mut(), terminal.backend_mut(),

View File

@@ -79,56 +79,156 @@ impl Default for CanvasConfig {
} }
impl CanvasKeybindings { impl CanvasKeybindings {
/// Generate complete vim defaults from introspection system
/// This ensures defaults are always in sync with actual handler capabilities
pub fn with_vim_defaults() -> Self { pub fn with_vim_defaults() -> Self {
// TODO: Could be generated from introspection too let registry = ActionRegistry::from_handlers();
Self::generate_from_registry(&registry)
}
/// Generate keybindings from action registry (used by both defaults and config generation)
/// This is the single source of truth for what keybindings should exist
fn generate_from_registry(registry: &ActionRegistry) -> Self {
let mut keybindings = Self::default(); let mut keybindings = Self::default();
// Read-only mode (vim-style navigation) // Generate keybindings for each mode discovered by introspection
for (mode_name, mode_registry) in &registry.modes {
let mode_bindings = match mode_name.as_str() {
"edit" => &mut keybindings.edit,
"read_only" => &mut keybindings.read_only,
"highlight" => &mut keybindings.global, // Highlight actions go in global
_ => {
// Handle any future modes discovered by introspection
eprintln!("Warning: Unknown mode '{}' discovered by introspection", mode_name);
continue;
}
};
// Add ALL required actions
for (action_name, action_spec) in &mode_registry.required {
if !action_spec.examples.is_empty() {
mode_bindings.insert(
action_name.clone(),
action_spec.examples.clone()
);
}
}
// Add ALL optional actions
for (action_name, action_spec) in &mode_registry.optional {
if !action_spec.examples.is_empty() {
mode_bindings.insert(
action_name.clone(),
action_spec.examples.clone()
);
}
}
}
keybindings
}
/// Generate a minimal fallback configuration if introspection fails
/// This should rarely be used, but provides safety net
fn minimal_fallback() -> Self {
let mut keybindings = Self::default();
// Absolute minimum required for basic functionality
keybindings.read_only.insert("move_left".to_string(), vec!["h".to_string()]); keybindings.read_only.insert("move_left".to_string(), vec!["h".to_string()]);
keybindings.read_only.insert("move_right".to_string(), vec!["l".to_string()]); keybindings.read_only.insert("move_right".to_string(), vec!["l".to_string()]);
keybindings.read_only.insert("move_up".to_string(), vec!["k".to_string()]); keybindings.read_only.insert("move_up".to_string(), vec!["k".to_string()]);
keybindings.read_only.insert("move_down".to_string(), vec!["j".to_string()]); keybindings.read_only.insert("move_down".to_string(), vec!["j".to_string()]);
// Edit mode
keybindings.edit.insert("delete_char_backward".to_string(), vec!["Backspace".to_string()]); keybindings.edit.insert("delete_char_backward".to_string(), vec!["Backspace".to_string()]);
keybindings.edit.insert("move_left".to_string(), vec!["Left".to_string()]); keybindings.edit.insert("move_left".to_string(), vec!["Left".to_string()]);
keybindings.edit.insert("move_right".to_string(), vec!["Right".to_string()]); keybindings.edit.insert("move_right".to_string(), vec!["Right".to_string()]);
keybindings.edit.insert("move_up".to_string(), vec!["Up".to_string()]); keybindings.edit.insert("move_up".to_string(), vec!["Up".to_string()]);
keybindings.edit.insert("move_down".to_string(), vec!["Down".to_string()]); keybindings.edit.insert("move_down".to_string(), vec!["Down".to_string()]);
keybindings.edit.insert("next_field".to_string(), vec!["Tab".to_string()]);
keybindings.edit.insert("prev_field".to_string(), vec!["Shift+Tab".to_string()]);
keybindings keybindings
} }
/// Validate that generated keybindings match the current introspection state
/// This helps catch when handlers change but defaults become stale
pub fn validate_against_introspection(&self) -> Result<(), Vec<String>> {
let registry = ActionRegistry::from_handlers();
let expected = Self::generate_from_registry(&registry);
let mut errors = Vec::new();
// Check each mode
for (mode_name, expected_bindings) in [
("edit", &expected.edit),
("read_only", &expected.read_only),
("global", &expected.global),
] {
let actual_bindings = match mode_name {
"edit" => &self.edit,
"read_only" => &self.read_only,
"global" => &self.global,
_ => continue,
};
// Check for missing actions
for action_name in expected_bindings.keys() {
if !actual_bindings.contains_key(action_name) {
errors.push(format!(
"Missing action '{}' in {} mode (expected by introspection)",
action_name, mode_name
));
}
}
// Check for unexpected actions
for action_name in actual_bindings.keys() {
if !expected_bindings.contains_key(action_name) {
errors.push(format!(
"Unexpected action '{}' in {} mode (not found in introspection)",
action_name, mode_name
));
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
} }
impl CanvasConfig { impl CanvasConfig {
/// NEW: Load and validate configuration using dynamic registry /// Enhanced load method with introspection validation
pub fn load() -> Self { pub fn load() -> Self {
match Self::load_and_validate() { match Self::load_and_validate() {
Ok(config) => config, Ok(config) => config,
Err(e) => { Err(e) => {
eprintln!("Failed to load config file: {}", e);
eprintln!("Using auto-generated defaults from introspection");
Self::default() Self::default()
} }
} }
} }
/// NEW: Load configuration with validation using dynamic registry /// Load and validate configuration with enhanced introspection checks
pub fn load_and_validate() -> Result<Self> { pub fn load_and_validate() -> Result<Self> {
// Try to load canvas_config.toml from current directory // Try to load canvas_config.toml from current directory
let config = if let Ok(config) = Self::from_file(std::path::Path::new("canvas_config.toml")) { let config = if let Ok(config) = Self::from_file(std::path::Path::new("canvas_config.toml")) {
config config
} else { } else {
// Use default if file doesn't exist // Use auto-generated defaults if file doesn't exist
eprintln!("Config file not found, using auto-generated defaults");
Self::default() Self::default()
}; };
// NEW: Use dynamic registry from actual handlers // Validate the configuration against current introspection state
let registry = ActionRegistry::from_handlers(); let registry = ActionRegistry::from_handlers();
// Validate the handlers match their claimed capabilities // Validate handlers are working correctly
if let Err(handler_errors) = registry.validate_against_implementation() { if let Err(handler_errors) = registry.validate_against_implementation() {
eprintln!("Handler validation warnings:");
for error in handler_errors { for error in handler_errors {
eprintln!(" - {}", error);
} }
} }
@@ -137,28 +237,122 @@ impl CanvasConfig {
let validation_result = validator.validate_keybindings(&config.keybindings); let validation_result = validator.validate_keybindings(&config.keybindings);
if !validation_result.is_valid { if !validation_result.is_valid {
eprintln!("Configuration validation failed:");
validator.print_validation_result(&validation_result); validator.print_validation_result(&validation_result);
} else if !validation_result.warnings.is_empty() { } else if !validation_result.warnings.is_empty() {
eprintln!("Configuration validation warnings:");
validator.print_validation_result(&validation_result); validator.print_validation_result(&validation_result);
} }
// Optional: Validate that our defaults match introspection
if let Err(sync_errors) = config.keybindings.validate_against_introspection() {
eprintln!("Default keybindings out of sync with introspection:");
for error in sync_errors {
eprintln!(" - {}", error);
}
}
Ok(config) Ok(config)
} }
/// NEW: Generate template from actual handler capabilities /// Generate a complete configuration template that matches current defaults
pub fn generate_template() -> String { /// This ensures the generated config file has the same content as defaults
pub fn generate_complete_template() -> String {
let registry = ActionRegistry::from_handlers(); let registry = ActionRegistry::from_handlers();
let defaults = CanvasKeybindings::generate_from_registry(&registry);
// Validate handlers first let mut template = String::new();
if let Err(errors) = registry.validate_against_implementation() { template.push_str("# Canvas Library Configuration\n");
for error in errors { template.push_str("# Auto-generated from handler introspection\n");
template.push_str("# This config contains ALL available actions\n\n");
// Generate sections for each mode
for (mode_name, bindings) in [
("read_only", &defaults.read_only),
("edit", &defaults.edit),
("global", &defaults.global),
] {
if bindings.is_empty() {
continue;
} }
template.push_str(&format!("[keybindings.{}]\n", mode_name));
// Get mode registry for categorization
if let Some(mode_registry) = registry.get_mode_registry(mode_name) {
// Required actions first
let mut found_required = false;
for (action_name, keybindings) in bindings {
if mode_registry.required.contains_key(action_name) {
if !found_required {
template.push_str("# Required\n");
found_required = true;
}
template.push_str(&format!("{} = {:?}\n", action_name, keybindings));
}
}
// Optional actions second
let mut found_optional = false;
for (action_name, keybindings) in bindings {
if mode_registry.optional.contains_key(action_name) {
if !found_optional {
template.push_str("# Optional\n");
found_optional = true;
}
template.push_str(&format!("{} = {:?}\n", action_name, keybindings));
}
}
} else {
// Fallback: just list all actions
for (action_name, keybindings) in bindings {
template.push_str(&format!("{} = {:?}\n", action_name, keybindings));
}
}
template.push('\n');
} }
registry.generate_config_template() template
} }
/// NEW: Generate clean template from actual handler capabilities /// Generate config that only contains actions different from defaults
/// Useful for minimal user configs
pub fn generate_minimal_template() -> String {
let defaults = CanvasKeybindings::with_vim_defaults();
let mut template = String::new();
template.push_str("# Minimal Canvas Configuration\n");
template.push_str("# Only uncomment and modify the keybindings you want to change\n");
template.push_str("# All other actions will use their default vim-style keybindings\n\n");
for (mode_name, bindings) in [
("read_only", &defaults.read_only),
("edit", &defaults.edit),
("global", &defaults.global),
] {
if bindings.is_empty() {
continue;
}
template.push_str(&format!("# [keybindings.{}]\n", mode_name));
for (action_name, keybindings) in bindings {
template.push_str(&format!("# {} = {:?}\n", action_name, keybindings));
}
template.push('\n');
}
template
}
/// Generate template from actual handler capabilities (legacy method for compatibility)
pub fn generate_template() -> String {
Self::generate_complete_template()
}
/// Generate clean template from actual handler capabilities (legacy method for compatibility)
pub fn generate_clean_template() -> String { pub fn generate_clean_template() -> String {
let registry = ActionRegistry::from_handlers(); let registry = ActionRegistry::from_handlers();
@@ -172,14 +366,14 @@ impl CanvasConfig {
registry.generate_clean_template() registry.generate_clean_template()
} }
/// NEW: Validate current configuration against actual implementation /// Validate current configuration against actual implementation
pub fn validate(&self) -> ValidationResult { pub fn validate(&self) -> ValidationResult {
let registry = ActionRegistry::from_handlers(); let registry = ActionRegistry::from_handlers();
let validator = ConfigValidator::new(registry); let validator = ConfigValidator::new(registry);
validator.validate_keybindings(&self.keybindings) validator.validate_keybindings(&self.keybindings)
} }
/// NEW: Print validation results for current config /// Print validation results for current config
pub fn print_validation(&self) { pub fn print_validation(&self) {
let registry = ActionRegistry::from_handlers(); let registry = ActionRegistry::from_handlers();
let validator = ConfigValidator::new(registry); let validator = ConfigValidator::new(registry);
@@ -200,22 +394,20 @@ impl CanvasConfig {
Self::from_toml(&contents) Self::from_toml(&contents)
} }
/// RESTORED: Check if autocomplete should auto-trigger (simple logic) /// Check if autocomplete should auto-trigger (simple logic)
pub fn should_auto_trigger_autocomplete(&self) -> bool { pub fn should_auto_trigger_autocomplete(&self) -> bool {
// If trigger_autocomplete keybinding exists anywhere, use manual mode only // If trigger_autocomplete keybinding exists anywhere, use manual mode only
// If no trigger_autocomplete keybinding, use auto-trigger mode // If no trigger_autocomplete keybinding, use auto-trigger mode
!self.has_trigger_autocomplete_keybinding() !self.has_trigger_autocomplete_keybinding()
} }
/// RESTORED: Check if user has configured manual trigger keybinding /// Check if user has configured manual trigger keybinding
pub fn has_trigger_autocomplete_keybinding(&self) -> bool { pub fn has_trigger_autocomplete_keybinding(&self) -> bool {
self.keybindings.edit.contains_key("trigger_autocomplete") || self.keybindings.edit.contains_key("trigger_autocomplete") ||
self.keybindings.read_only.contains_key("trigger_autocomplete") || self.keybindings.read_only.contains_key("trigger_autocomplete") ||
self.keybindings.global.contains_key("trigger_autocomplete") self.keybindings.global.contains_key("trigger_autocomplete")
} }
// ... keep all your existing key matching methods ...
/// Get action for key in read-only mode /// Get action for key in read-only mode
pub fn get_read_only_action(&self, key: KeyCode, modifiers: KeyModifiers) -> Option<&str> { pub fn get_read_only_action(&self, key: KeyCode, modifiers: KeyModifiers) -> Option<&str> {
self.get_action_in_mode(&self.keybindings.read_only, key, modifiers) self.get_action_in_mode(&self.keybindings.read_only, key, modifiers)