fixed working canvas in client, need more fixes now

This commit is contained in:
Priec
2025-07-31 14:44:47 +02:00
parent ebe4adaa5d
commit 5d084bf822
4 changed files with 280 additions and 96 deletions

View File

@@ -31,12 +31,9 @@ pub async fn execute_canvas_action_with_autocomplete<S: CanvasState + Autocomple
// 3. AUTO-TRIGGER LOGIC: Check if we should activate/deactivate autocomplete // 3. AUTO-TRIGGER LOGIC: Check if we should activate/deactivate autocomplete
if let Some(cfg) = config { if let Some(cfg) = config {
println!("{:?}, {}", action, cfg.should_auto_trigger_autocomplete());
if cfg.should_auto_trigger_autocomplete() { if cfg.should_auto_trigger_autocomplete() {
println!("AUTO-TRIGGER");
match action { match action {
CanvasAction::InsertChar(_) => { CanvasAction::InsertChar(_) => {
println!("AUTO-T on Ins");
let current_field = state.current_field(); let current_field = state.current_field();
let current_input = state.get_current_input(); let current_input = state.get_current_input();
@@ -44,13 +41,11 @@ pub async fn execute_canvas_action_with_autocomplete<S: CanvasState + Autocomple
&& !state.is_autocomplete_active() && !state.is_autocomplete_active()
&& current_input.len() >= 1 && current_input.len() >= 1
{ {
println!("ACT AUTOC");
state.activate_autocomplete(); state.activate_autocomplete();
} }
} }
CanvasAction::NextField | CanvasAction::PrevField => { CanvasAction::NextField | CanvasAction::PrevField => {
println!("AUTO-T on nav");
let current_field = state.current_field(); let current_field = state.current_field();
if state.supports_autocomplete(current_field) && !state.is_autocomplete_active() { if state.supports_autocomplete(current_field) && !state.is_autocomplete_active() {

View File

@@ -108,8 +108,6 @@ impl CanvasConfig {
match Self::load_and_validate() { match Self::load_and_validate() {
Ok(config) => config, Ok(config) => config,
Err(e) => { Err(e) => {
eprintln!("⚠️ Failed to load canvas config: {}", e);
eprintln!(" Using default configuration");
Self::default() Self::default()
} }
} }
@@ -130,9 +128,7 @@ impl CanvasConfig {
// Validate the handlers match their claimed capabilities // Validate the handlers match their claimed capabilities
if let Err(handler_errors) = registry.validate_against_implementation() { if let Err(handler_errors) = registry.validate_against_implementation() {
eprintln!("⚠️ Handler validation failed:");
for error in handler_errors { for error in handler_errors {
eprintln!(" - {}", error);
} }
} }
@@ -141,15 +137,8 @@ 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!("❌ Canvas configuration validation failed:");
validator.print_validation_result(&validation_result); validator.print_validation_result(&validation_result);
eprintln!();
eprintln!("🔧 To generate a working config template:");
eprintln!(" CanvasConfig::generate_template()");
eprintln!();
eprintln!("📁 Expected config file location: canvas_config.toml");
} else if !validation_result.warnings.is_empty() { } else if !validation_result.warnings.is_empty() {
eprintln!("⚠️ Canvas configuration has warnings:");
validator.print_validation_result(&validation_result); validator.print_validation_result(&validation_result);
} }
@@ -162,9 +151,7 @@ impl CanvasConfig {
// Validate handlers first // Validate handlers first
if let Err(errors) = registry.validate_against_implementation() { if let Err(errors) = registry.validate_against_implementation() {
eprintln!("⚠️ Warning: Handler validation failed while generating template:");
for error in errors { for error in errors {
eprintln!(" - {}", error);
} }
} }
@@ -177,7 +164,6 @@ impl CanvasConfig {
// Validate handlers first // Validate handlers first
if let Err(errors) = registry.validate_against_implementation() { if let Err(errors) = registry.validate_against_implementation() {
eprintln!("⚠️ Warning: Handler validation failed while generating template:");
for error in errors { for error in errors {
eprintln!(" - {}", error); eprintln!(" - {}", error);
} }
@@ -263,26 +249,225 @@ impl CanvasConfig {
None None
} }
fn matches_keybinding(&self, _binding: &str, _key: KeyCode, _modifiers: KeyModifiers) -> bool { fn matches_keybinding(&self, binding: &str, key: KeyCode, modifiers: KeyModifiers) -> bool {
// Keep your existing implementation - this is just a placeholder // Special handling for shift+character combinations
true if binding.to_lowercase().starts_with("shift+") {
} let parts: Vec<&str> = binding.split('+').collect();
if parts.len() == 2 && parts[1].len() == 1 {
/// Debug method to print loaded keybindings with validation let expected_lowercase = parts[1].chars().next().unwrap().to_lowercase().next().unwrap();
pub fn debug_keybindings(&self) { let expected_uppercase = expected_lowercase.to_uppercase().next().unwrap();
println!("📋 Canvas keybindings loaded:"); if let KeyCode::Char(actual_char) = key {
println!(" Read-only: {} actions", self.keybindings.read_only.len()); if actual_char == expected_uppercase && modifiers.contains(KeyModifiers::SHIFT) {
println!(" Edit: {} actions", self.keybindings.edit.len()); return true;
}
// NEW: Show validation status against actual implementation }
let validation = self.validate(); }
if validation.is_valid {
println!(" ✅ Configuration matches actual implementation");
} else {
println!(" ❌ Configuration has {} errors vs implementation", validation.errors.len());
} }
if !validation.warnings.is_empty() {
println!(" ⚠️ Configuration has {} warnings", validation.warnings.len()); // Handle Shift+Tab -> BackTab
if binding.to_lowercase() == "shift+tab" && key == KeyCode::BackTab && modifiers.is_empty() {
return true;
} }
// Handle multi-character bindings (all standard keys without modifiers)
if binding.len() > 1 && !binding.contains('+') {
return match binding.to_lowercase().as_str() {
// Navigation keys
"left" => key == KeyCode::Left,
"right" => key == KeyCode::Right,
"up" => key == KeyCode::Up,
"down" => key == KeyCode::Down,
"home" => key == KeyCode::Home,
"end" => key == KeyCode::End,
"pageup" | "pgup" => key == KeyCode::PageUp,
"pagedown" | "pgdn" => key == KeyCode::PageDown,
// Editing keys
"insert" | "ins" => key == KeyCode::Insert,
"delete" | "del" => key == KeyCode::Delete,
"backspace" => key == KeyCode::Backspace,
// Tab keys
"tab" => key == KeyCode::Tab,
"backtab" => key == KeyCode::BackTab,
// Special keys
"enter" | "return" => key == KeyCode::Enter,
"escape" | "esc" => key == KeyCode::Esc,
"space" => key == KeyCode::Char(' '),
// Function keys F1-F24
"f1" => key == KeyCode::F(1),
"f2" => key == KeyCode::F(2),
"f3" => key == KeyCode::F(3),
"f4" => key == KeyCode::F(4),
"f5" => key == KeyCode::F(5),
"f6" => key == KeyCode::F(6),
"f7" => key == KeyCode::F(7),
"f8" => key == KeyCode::F(8),
"f9" => key == KeyCode::F(9),
"f10" => key == KeyCode::F(10),
"f11" => key == KeyCode::F(11),
"f12" => key == KeyCode::F(12),
"f13" => key == KeyCode::F(13),
"f14" => key == KeyCode::F(14),
"f15" => key == KeyCode::F(15),
"f16" => key == KeyCode::F(16),
"f17" => key == KeyCode::F(17),
"f18" => key == KeyCode::F(18),
"f19" => key == KeyCode::F(19),
"f20" => key == KeyCode::F(20),
"f21" => key == KeyCode::F(21),
"f22" => key == KeyCode::F(22),
"f23" => key == KeyCode::F(23),
"f24" => key == KeyCode::F(24),
// Lock keys (may not work reliably in all terminals)
"capslock" => key == KeyCode::CapsLock,
"scrolllock" => key == KeyCode::ScrollLock,
"numlock" => key == KeyCode::NumLock,
// System keys
"printscreen" => key == KeyCode::PrintScreen,
"pause" => key == KeyCode::Pause,
"menu" => key == KeyCode::Menu,
"keypadbegin" => key == KeyCode::KeypadBegin,
// Media keys (rarely supported but included for completeness)
"mediaplay" => key == KeyCode::Media(crossterm::event::MediaKeyCode::Play),
"mediapause" => key == KeyCode::Media(crossterm::event::MediaKeyCode::Pause),
"mediaplaypause" => key == KeyCode::Media(crossterm::event::MediaKeyCode::PlayPause),
"mediareverse" => key == KeyCode::Media(crossterm::event::MediaKeyCode::Reverse),
"mediastop" => key == KeyCode::Media(crossterm::event::MediaKeyCode::Stop),
"mediafastforward" => key == KeyCode::Media(crossterm::event::MediaKeyCode::FastForward),
"mediarewind" => key == KeyCode::Media(crossterm::event::MediaKeyCode::Rewind),
"mediatracknext" => key == KeyCode::Media(crossterm::event::MediaKeyCode::TrackNext),
"mediatrackprevious" => key == KeyCode::Media(crossterm::event::MediaKeyCode::TrackPrevious),
"mediarecord" => key == KeyCode::Media(crossterm::event::MediaKeyCode::Record),
"medialowervolume" => key == KeyCode::Media(crossterm::event::MediaKeyCode::LowerVolume),
"mediaraisevolume" => key == KeyCode::Media(crossterm::event::MediaKeyCode::RaiseVolume),
"mediamutevolume" => key == KeyCode::Media(crossterm::event::MediaKeyCode::MuteVolume),
// Modifier keys (these work better as part of combinations)
"leftshift" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::LeftShift),
"leftcontrol" | "leftctrl" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::LeftControl),
"leftalt" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::LeftAlt),
"leftsuper" | "leftwindows" | "leftcmd" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::LeftSuper),
"lefthyper" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::LeftHyper),
"leftmeta" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::LeftMeta),
"rightshift" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::RightShift),
"rightcontrol" | "rightctrl" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::RightControl),
"rightalt" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::RightAlt),
"rightsuper" | "rightwindows" | "rightcmd" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::RightSuper),
"righthyper" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::RightHyper),
"rightmeta" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::RightMeta),
"isolevel3shift" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::IsoLevel3Shift),
"isolevel5shift" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::IsoLevel5Shift),
// Multi-key sequences need special handling
"gg" => false, // This needs sequence handling
_ => {
// Handle single characters and punctuation
if binding.len() == 1 {
if let Some(c) = binding.chars().next() {
key == KeyCode::Char(c)
} else {
false
}
} else {
false
}
}
};
}
// Handle modifier combinations (like "Ctrl+F5", "Alt+Shift+A")
let parts: Vec<&str> = binding.split('+').collect();
let mut expected_modifiers = KeyModifiers::empty();
let mut expected_key = None;
for part in parts {
match part.to_lowercase().as_str() {
// Modifiers
"ctrl" | "control" => expected_modifiers |= KeyModifiers::CONTROL,
"shift" => expected_modifiers |= KeyModifiers::SHIFT,
"alt" => expected_modifiers |= KeyModifiers::ALT,
"super" | "windows" | "cmd" => expected_modifiers |= KeyModifiers::SUPER,
"hyper" => expected_modifiers |= KeyModifiers::HYPER,
"meta" => expected_modifiers |= KeyModifiers::META,
// Navigation keys
"left" => expected_key = Some(KeyCode::Left),
"right" => expected_key = Some(KeyCode::Right),
"up" => expected_key = Some(KeyCode::Up),
"down" => expected_key = Some(KeyCode::Down),
"home" => expected_key = Some(KeyCode::Home),
"end" => expected_key = Some(KeyCode::End),
"pageup" | "pgup" => expected_key = Some(KeyCode::PageUp),
"pagedown" | "pgdn" => expected_key = Some(KeyCode::PageDown),
// Editing keys
"insert" | "ins" => expected_key = Some(KeyCode::Insert),
"delete" | "del" => expected_key = Some(KeyCode::Delete),
"backspace" => expected_key = Some(KeyCode::Backspace),
// Tab keys
"tab" => expected_key = Some(KeyCode::Tab),
"backtab" => expected_key = Some(KeyCode::BackTab),
// Special keys
"enter" | "return" => expected_key = Some(KeyCode::Enter),
"escape" | "esc" => expected_key = Some(KeyCode::Esc),
"space" => expected_key = Some(KeyCode::Char(' ')),
// Function keys
"f1" => expected_key = Some(KeyCode::F(1)),
"f2" => expected_key = Some(KeyCode::F(2)),
"f3" => expected_key = Some(KeyCode::F(3)),
"f4" => expected_key = Some(KeyCode::F(4)),
"f5" => expected_key = Some(KeyCode::F(5)),
"f6" => expected_key = Some(KeyCode::F(6)),
"f7" => expected_key = Some(KeyCode::F(7)),
"f8" => expected_key = Some(KeyCode::F(8)),
"f9" => expected_key = Some(KeyCode::F(9)),
"f10" => expected_key = Some(KeyCode::F(10)),
"f11" => expected_key = Some(KeyCode::F(11)),
"f12" => expected_key = Some(KeyCode::F(12)),
"f13" => expected_key = Some(KeyCode::F(13)),
"f14" => expected_key = Some(KeyCode::F(14)),
"f15" => expected_key = Some(KeyCode::F(15)),
"f16" => expected_key = Some(KeyCode::F(16)),
"f17" => expected_key = Some(KeyCode::F(17)),
"f18" => expected_key = Some(KeyCode::F(18)),
"f19" => expected_key = Some(KeyCode::F(19)),
"f20" => expected_key = Some(KeyCode::F(20)),
"f21" => expected_key = Some(KeyCode::F(21)),
"f22" => expected_key = Some(KeyCode::F(22)),
"f23" => expected_key = Some(KeyCode::F(23)),
"f24" => expected_key = Some(KeyCode::F(24)),
// Lock keys
"capslock" => expected_key = Some(KeyCode::CapsLock),
"scrolllock" => expected_key = Some(KeyCode::ScrollLock),
"numlock" => expected_key = Some(KeyCode::NumLock),
// System keys
"printscreen" => expected_key = Some(KeyCode::PrintScreen),
"pause" => expected_key = Some(KeyCode::Pause),
"menu" => expected_key = Some(KeyCode::Menu),
"keypadbegin" => expected_key = Some(KeyCode::KeypadBegin),
// Single character (letters, numbers, punctuation)
part => {
if part.len() == 1 {
if let Some(c) = part.chars().next() {
expected_key = Some(KeyCode::Char(c));
}
}
}
}
}
modifiers == expected_modifiers && Some(key) == expected_key
} }
} }

View File

@@ -54,10 +54,10 @@ move_word_next = ["w"]
next_field = ["Tab"] next_field = ["Tab"]
move_word_prev = ["b"] move_word_prev = ["b"]
move_word_end = ["e"] move_word_end = ["e"]
move_last_line = ["G"] move_last_line = ["shift+g"]
move_word_end_prev = ["ge"] move_word_end_prev = ["ge"]
move_line_start = ["0"] move_line_start = ["0"]
move_first_line = ["gg"] move_first_line = ["g+g"]
prev_field = ["Shift+Tab"] prev_field = ["Shift+Tab"]
[keybindings.highlight] [keybindings.highlight]

View File

@@ -12,7 +12,7 @@ use canvas::canvas::CanvasState;
use canvas::{canvas::CanvasAction, dispatcher::ActionDispatcher, canvas::ActionResult}; use canvas::{canvas::CanvasAction, dispatcher::ActionDispatcher, canvas::ActionResult};
use anyhow::Result; use anyhow::Result;
use common::proto::komp_ac::search::search_response::Hit; use common::proto::komp_ac::search::search_response::Hit;
use crossterm::event::{KeyCode, KeyEvent}; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tracing::info; use tracing::info;
@@ -143,23 +143,46 @@ async fn execute_canvas_action(
} }
} }
/// NEW: Unified canvas action handler for any CanvasState (LoginState, RegisterState, etc.) /// FIXED: Unified canvas action handler with proper priority order for edit mode
/// This replaces the old auth_e::execute_edit_action calls with the new canvas library
/// NEW: Unified canvas action handler for any CanvasState with character fallback
/// Complete canvas action handler with fallbacks for common keys
/// Debug version to see what's happening
async fn handle_canvas_state_edit<S: CanvasState>( async fn handle_canvas_state_edit<S: CanvasState>(
key: KeyEvent, key: KeyEvent,
config: &Config, config: &Config,
state: &mut S, state: &mut S,
ideal_cursor_column: &mut usize, ideal_cursor_column: &mut usize,
) -> Result<String> { ) -> Result<String> {
println!("DEBUG: Key pressed: {:?}", key); // DEBUG // println!("DEBUG: Key pressed: {:?}", key); // DEBUG
// Try direct key mapping first (same pattern as FormState) // PRIORITY 1: Character insertion in edit mode comes FIRST
if let KeyCode::Char(c) = key.code {
// Only insert if no modifiers or just shift (for uppercase)
if key.modifiers.is_empty() || key.modifiers == KeyModifiers::SHIFT {
// println!("DEBUG: Using character insertion priority for: {}", c); // DEBUG
let canvas_action = CanvasAction::InsertChar(c);
match ActionDispatcher::dispatch(canvas_action, state, ideal_cursor_column).await {
Ok(ActionResult::Success(msg)) => {
return Ok(msg.unwrap_or_default());
}
Ok(ActionResult::HandledByFeature(msg)) => {
return Ok(msg);
}
Ok(ActionResult::Error(msg)) => {
return Ok(format!("Error: {}", msg));
}
Ok(ActionResult::RequiresContext(msg)) => {
return Ok(format!("Context needed: {}", msg));
}
Err(e) => {
// println!("DEBUG: Character insertion failed: {:?}, trying config", e);
// Fall through to try config mappings
}
}
}
}
// PRIORITY 2: Check canvas config for special keys/combinations
let canvas_config = canvas::config::CanvasConfig::load(); let canvas_config = canvas::config::CanvasConfig::load();
if let Some(action_name) = canvas_config.get_edit_action(key.code, key.modifiers) { if let Some(action_name) = canvas_config.get_edit_action(key.code, key.modifiers) {
println!("DEBUG: Canvas config mapped to: {}", action_name); // DEBUG // println!("DEBUG: Canvas config mapped to: {}", action_name); // DEBUG
let canvas_action = CanvasAction::from_string(action_name); let canvas_action = CanvasAction::from_string(action_name);
match ActionDispatcher::dispatch(canvas_action, state, ideal_cursor_column).await { match ActionDispatcher::dispatch(canvas_action, state, ideal_cursor_column).await {
@@ -176,62 +199,43 @@ async fn handle_canvas_state_edit<S: CanvasState>(
return Ok(format!("Context needed: {}", msg)); return Ok(format!("Context needed: {}", msg));
} }
Err(_) => { Err(_) => {
println!("DEBUG: Canvas action failed, trying client config"); // DEBUG // println!("DEBUG: Canvas action failed, trying client config"); // DEBUG
} }
} }
} else { } else {
println!("DEBUG: No canvas config mapping found"); // DEBUG // println!("DEBUG: No canvas config mapping found"); // DEBUG
} }
// Try config-mapped action (same pattern as FormState) // PRIORITY 3: Check client config ONLY for non-character keys or modified keys
if let Some(action_str) = config.get_edit_action_for_key(key.code, key.modifiers) { if !matches!(key.code, KeyCode::Char(_)) || !key.modifiers.is_empty() {
println!("DEBUG: Client config mapped to: {}", action_str); // DEBUG if let Some(action_str) = config.get_edit_action_for_key(key.code, key.modifiers) {
let canvas_action = CanvasAction::from_string(&action_str); // println!("DEBUG: Client config mapped to: {} (for non-char key)", action_str); // DEBUG
match ActionDispatcher::dispatch(canvas_action, state, ideal_cursor_column).await { let canvas_action = CanvasAction::from_string(&action_str);
Ok(ActionResult::Success(msg)) => { match ActionDispatcher::dispatch(canvas_action, state, ideal_cursor_column).await {
return Ok(msg.unwrap_or_default()); Ok(ActionResult::Success(msg)) => {
} return Ok(msg.unwrap_or_default());
Ok(ActionResult::HandledByFeature(msg)) => { }
return Ok(msg); Ok(ActionResult::HandledByFeature(msg)) => {
} return Ok(msg);
Ok(ActionResult::Error(msg)) => { }
return Ok(format!("Error: {}", msg)); Ok(ActionResult::Error(msg)) => {
} return Ok(format!("Error: {}", msg));
Ok(ActionResult::RequiresContext(msg)) => { }
return Ok(format!("Context needed: {}", msg)); Ok(ActionResult::RequiresContext(msg)) => {
} return Ok(format!("Context needed: {}", msg));
Err(e) => { }
return Ok(format!("Action failed: {}", e)); Err(e) => {
return Ok(format!("Action failed: {}", e));
}
} }
} else {
// println!("DEBUG: No client config mapping found for non-char key"); // DEBUG
} }
} else { } else {
println!("DEBUG: No client config mapping found"); // DEBUG // println!("DEBUG: Skipping client config for character key in edit mode"); // DEBUG
} }
// Character insertion fallback // println!("DEBUG: No action taken for key: {:?}", key); // DEBUG
if let KeyCode::Char(c) = key.code {
println!("DEBUG: Using character fallback for: {}", c); // DEBUG
let canvas_action = CanvasAction::InsertChar(c);
match ActionDispatcher::dispatch(canvas_action, state, ideal_cursor_column).await {
Ok(ActionResult::Success(msg)) => {
return Ok(msg.unwrap_or_default());
}
Ok(ActionResult::HandledByFeature(msg)) => {
return Ok(msg);
}
Ok(ActionResult::Error(msg)) => {
return Ok(format!("Error: {}", msg));
}
Ok(ActionResult::RequiresContext(msg)) => {
return Ok(format!("Context needed: {}", msg));
}
Err(e) => {
return Ok(format!("Character insertion failed: {}", e));
}
}
}
println!("DEBUG: No action taken for key: {:?}", key); // DEBUG
Ok(String::new()) Ok(String::new())
} }