fixed canvas library
This commit is contained in:
@@ -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,74 +198,20 @@ 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,
|
||||||
key.modifiers,
|
key.modifiers,
|
||||||
&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 {
|
||||||
match key.code {
|
match key.code {
|
||||||
@@ -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",
|
||||||
@@ -429,15 +313,15 @@ fn ui(f: &mut Frame, state: &DemoFormState, theme: &DemoTheme) {
|
|||||||
format!("-- {} --", mode_text)
|
format!("-- {} --", mode_text)
|
||||||
};
|
};
|
||||||
|
|
||||||
let position_text = format!("Field: {}/{} | Cursor: {} | Column: {}",
|
let position_text = format!("Field: {}/{} | Cursor: {} | Column: {}",
|
||||||
state.current_field + 1,
|
state.current_field + 1,
|
||||||
state.fields.len(),
|
state.fields.len(),
|
||||||
state.cursor_pos,
|
state.cursor_pos,
|
||||||
state.ideal_cursor_column);
|
state.ideal_cursor_column);
|
||||||
|
|
||||||
let help_text = match state.mode {
|
let help_text = match state.mode {
|
||||||
AppMode::ReadOnly => "hjkl/arrows: Move | Tab/Shift+Tab: Fields | w/b/e: Words | 0/$: Line | gg/G: File | i/a: Edit | v: Visual | F10: Quit",
|
AppMode::ReadOnly => "hjkl/arrows: Move | Tab/Shift+Tab: Fields | w/b/e: Words | 0/$: Line | gg/G: File | i/a: Edit | v: Visual | F10: Quit",
|
||||||
AppMode::Edit => "Type to edit | hjkl/arrows: Move | Tab/Enter: Next field | Backspace/Delete: Delete | Home/End: Line | Esc: Normal | F10: Quit",
|
AppMode::Edit => "Type to edit | hjkl/arrows: Move | Tab/Enter: Next field | Backspace/Delete: Delete | Home/End: Line | Esc: Normal | F10: Quit",
|
||||||
AppMode::Highlight => "hjkl/arrows: Select | w/b/e: Words | 0/$: Line | Esc: Normal | F10: Quit",
|
AppMode::Highlight => "hjkl/arrows: Select | w/b/e: Words | 0/$: Line | Esc: Normal | F10: Quit",
|
||||||
_ => "Esc: Normal | F10: Quit",
|
_ => "Esc: Normal | F10: Quit",
|
||||||
};
|
};
|
||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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(®istry)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 ®istry.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(®istry);
|
||||||
|
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,31 +237,125 @@ 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(®istry);
|
||||||
|
|
||||||
// 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();
|
||||||
|
|
||||||
// Validate handlers first
|
// Validate handlers first
|
||||||
if let Err(errors) = registry.validate_against_implementation() {
|
if let Err(errors) = registry.validate_against_implementation() {
|
||||||
for error in errors {
|
for error in errors {
|
||||||
@@ -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)
|
||||||
@@ -281,21 +473,21 @@ impl CanvasConfig {
|
|||||||
"end" => key == KeyCode::End,
|
"end" => key == KeyCode::End,
|
||||||
"pageup" | "pgup" => key == KeyCode::PageUp,
|
"pageup" | "pgup" => key == KeyCode::PageUp,
|
||||||
"pagedown" | "pgdn" => key == KeyCode::PageDown,
|
"pagedown" | "pgdn" => key == KeyCode::PageDown,
|
||||||
|
|
||||||
// Editing keys
|
// Editing keys
|
||||||
"insert" | "ins" => key == KeyCode::Insert,
|
"insert" | "ins" => key == KeyCode::Insert,
|
||||||
"delete" | "del" => key == KeyCode::Delete,
|
"delete" | "del" => key == KeyCode::Delete,
|
||||||
"backspace" => key == KeyCode::Backspace,
|
"backspace" => key == KeyCode::Backspace,
|
||||||
|
|
||||||
// Tab keys
|
// Tab keys
|
||||||
"tab" => key == KeyCode::Tab,
|
"tab" => key == KeyCode::Tab,
|
||||||
"backtab" => key == KeyCode::BackTab,
|
"backtab" => key == KeyCode::BackTab,
|
||||||
|
|
||||||
// Special keys
|
// Special keys
|
||||||
"enter" | "return" => key == KeyCode::Enter,
|
"enter" | "return" => key == KeyCode::Enter,
|
||||||
"escape" | "esc" => key == KeyCode::Esc,
|
"escape" | "esc" => key == KeyCode::Esc,
|
||||||
"space" => key == KeyCode::Char(' '),
|
"space" => key == KeyCode::Char(' '),
|
||||||
|
|
||||||
// Function keys F1-F24
|
// Function keys F1-F24
|
||||||
"f1" => key == KeyCode::F(1),
|
"f1" => key == KeyCode::F(1),
|
||||||
"f2" => key == KeyCode::F(2),
|
"f2" => key == KeyCode::F(2),
|
||||||
@@ -321,18 +513,18 @@ impl CanvasConfig {
|
|||||||
"f22" => key == KeyCode::F(22),
|
"f22" => key == KeyCode::F(22),
|
||||||
"f23" => key == KeyCode::F(23),
|
"f23" => key == KeyCode::F(23),
|
||||||
"f24" => key == KeyCode::F(24),
|
"f24" => key == KeyCode::F(24),
|
||||||
|
|
||||||
// Lock keys (may not work reliably in all terminals)
|
// Lock keys (may not work reliably in all terminals)
|
||||||
"capslock" => key == KeyCode::CapsLock,
|
"capslock" => key == KeyCode::CapsLock,
|
||||||
"scrolllock" => key == KeyCode::ScrollLock,
|
"scrolllock" => key == KeyCode::ScrollLock,
|
||||||
"numlock" => key == KeyCode::NumLock,
|
"numlock" => key == KeyCode::NumLock,
|
||||||
|
|
||||||
// System keys
|
// System keys
|
||||||
"printscreen" => key == KeyCode::PrintScreen,
|
"printscreen" => key == KeyCode::PrintScreen,
|
||||||
"pause" => key == KeyCode::Pause,
|
"pause" => key == KeyCode::Pause,
|
||||||
"menu" => key == KeyCode::Menu,
|
"menu" => key == KeyCode::Menu,
|
||||||
"keypadbegin" => key == KeyCode::KeypadBegin,
|
"keypadbegin" => key == KeyCode::KeypadBegin,
|
||||||
|
|
||||||
// Media keys (rarely supported but included for completeness)
|
// Media keys (rarely supported but included for completeness)
|
||||||
"mediaplay" => key == KeyCode::Media(crossterm::event::MediaKeyCode::Play),
|
"mediaplay" => key == KeyCode::Media(crossterm::event::MediaKeyCode::Play),
|
||||||
"mediapause" => key == KeyCode::Media(crossterm::event::MediaKeyCode::Pause),
|
"mediapause" => key == KeyCode::Media(crossterm::event::MediaKeyCode::Pause),
|
||||||
@@ -347,7 +539,7 @@ impl CanvasConfig {
|
|||||||
"medialowervolume" => key == KeyCode::Media(crossterm::event::MediaKeyCode::LowerVolume),
|
"medialowervolume" => key == KeyCode::Media(crossterm::event::MediaKeyCode::LowerVolume),
|
||||||
"mediaraisevolume" => key == KeyCode::Media(crossterm::event::MediaKeyCode::RaiseVolume),
|
"mediaraisevolume" => key == KeyCode::Media(crossterm::event::MediaKeyCode::RaiseVolume),
|
||||||
"mediamutevolume" => key == KeyCode::Media(crossterm::event::MediaKeyCode::MuteVolume),
|
"mediamutevolume" => key == KeyCode::Media(crossterm::event::MediaKeyCode::MuteVolume),
|
||||||
|
|
||||||
// Modifier keys (these work better as part of combinations)
|
// Modifier keys (these work better as part of combinations)
|
||||||
"leftshift" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::LeftShift),
|
"leftshift" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::LeftShift),
|
||||||
"leftcontrol" | "leftctrl" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::LeftControl),
|
"leftcontrol" | "leftctrl" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::LeftControl),
|
||||||
@@ -363,7 +555,7 @@ impl CanvasConfig {
|
|||||||
"rightmeta" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::RightMeta),
|
"rightmeta" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::RightMeta),
|
||||||
"isolevel3shift" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::IsoLevel3Shift),
|
"isolevel3shift" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::IsoLevel3Shift),
|
||||||
"isolevel5shift" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::IsoLevel5Shift),
|
"isolevel5shift" => key == KeyCode::Modifier(crossterm::event::ModifierKeyCode::IsoLevel5Shift),
|
||||||
|
|
||||||
// Multi-key sequences need special handling
|
// Multi-key sequences need special handling
|
||||||
"gg" => false, // This needs sequence handling
|
"gg" => false, // This needs sequence handling
|
||||||
_ => {
|
_ => {
|
||||||
@@ -395,7 +587,7 @@ impl CanvasConfig {
|
|||||||
"super" | "windows" | "cmd" => expected_modifiers |= KeyModifiers::SUPER,
|
"super" | "windows" | "cmd" => expected_modifiers |= KeyModifiers::SUPER,
|
||||||
"hyper" => expected_modifiers |= KeyModifiers::HYPER,
|
"hyper" => expected_modifiers |= KeyModifiers::HYPER,
|
||||||
"meta" => expected_modifiers |= KeyModifiers::META,
|
"meta" => expected_modifiers |= KeyModifiers::META,
|
||||||
|
|
||||||
// Navigation keys
|
// Navigation keys
|
||||||
"left" => expected_key = Some(KeyCode::Left),
|
"left" => expected_key = Some(KeyCode::Left),
|
||||||
"right" => expected_key = Some(KeyCode::Right),
|
"right" => expected_key = Some(KeyCode::Right),
|
||||||
@@ -405,21 +597,21 @@ impl CanvasConfig {
|
|||||||
"end" => expected_key = Some(KeyCode::End),
|
"end" => expected_key = Some(KeyCode::End),
|
||||||
"pageup" | "pgup" => expected_key = Some(KeyCode::PageUp),
|
"pageup" | "pgup" => expected_key = Some(KeyCode::PageUp),
|
||||||
"pagedown" | "pgdn" => expected_key = Some(KeyCode::PageDown),
|
"pagedown" | "pgdn" => expected_key = Some(KeyCode::PageDown),
|
||||||
|
|
||||||
// Editing keys
|
// Editing keys
|
||||||
"insert" | "ins" => expected_key = Some(KeyCode::Insert),
|
"insert" | "ins" => expected_key = Some(KeyCode::Insert),
|
||||||
"delete" | "del" => expected_key = Some(KeyCode::Delete),
|
"delete" | "del" => expected_key = Some(KeyCode::Delete),
|
||||||
"backspace" => expected_key = Some(KeyCode::Backspace),
|
"backspace" => expected_key = Some(KeyCode::Backspace),
|
||||||
|
|
||||||
// Tab keys
|
// Tab keys
|
||||||
"tab" => expected_key = Some(KeyCode::Tab),
|
"tab" => expected_key = Some(KeyCode::Tab),
|
||||||
"backtab" => expected_key = Some(KeyCode::BackTab),
|
"backtab" => expected_key = Some(KeyCode::BackTab),
|
||||||
|
|
||||||
// Special keys
|
// Special keys
|
||||||
"enter" | "return" => expected_key = Some(KeyCode::Enter),
|
"enter" | "return" => expected_key = Some(KeyCode::Enter),
|
||||||
"escape" | "esc" => expected_key = Some(KeyCode::Esc),
|
"escape" | "esc" => expected_key = Some(KeyCode::Esc),
|
||||||
"space" => expected_key = Some(KeyCode::Char(' ')),
|
"space" => expected_key = Some(KeyCode::Char(' ')),
|
||||||
|
|
||||||
// Function keys
|
// Function keys
|
||||||
"f1" => expected_key = Some(KeyCode::F(1)),
|
"f1" => expected_key = Some(KeyCode::F(1)),
|
||||||
"f2" => expected_key = Some(KeyCode::F(2)),
|
"f2" => expected_key = Some(KeyCode::F(2)),
|
||||||
@@ -445,18 +637,18 @@ impl CanvasConfig {
|
|||||||
"f22" => expected_key = Some(KeyCode::F(22)),
|
"f22" => expected_key = Some(KeyCode::F(22)),
|
||||||
"f23" => expected_key = Some(KeyCode::F(23)),
|
"f23" => expected_key = Some(KeyCode::F(23)),
|
||||||
"f24" => expected_key = Some(KeyCode::F(24)),
|
"f24" => expected_key = Some(KeyCode::F(24)),
|
||||||
|
|
||||||
// Lock keys
|
// Lock keys
|
||||||
"capslock" => expected_key = Some(KeyCode::CapsLock),
|
"capslock" => expected_key = Some(KeyCode::CapsLock),
|
||||||
"scrolllock" => expected_key = Some(KeyCode::ScrollLock),
|
"scrolllock" => expected_key = Some(KeyCode::ScrollLock),
|
||||||
"numlock" => expected_key = Some(KeyCode::NumLock),
|
"numlock" => expected_key = Some(KeyCode::NumLock),
|
||||||
|
|
||||||
// System keys
|
// System keys
|
||||||
"printscreen" => expected_key = Some(KeyCode::PrintScreen),
|
"printscreen" => expected_key = Some(KeyCode::PrintScreen),
|
||||||
"pause" => expected_key = Some(KeyCode::Pause),
|
"pause" => expected_key = Some(KeyCode::Pause),
|
||||||
"menu" => expected_key = Some(KeyCode::Menu),
|
"menu" => expected_key = Some(KeyCode::Menu),
|
||||||
"keypadbegin" => expected_key = Some(KeyCode::KeypadBegin),
|
"keypadbegin" => expected_key = Some(KeyCode::KeypadBegin),
|
||||||
|
|
||||||
// Single character (letters, numbers, punctuation)
|
// Single character (letters, numbers, punctuation)
|
||||||
part => {
|
part => {
|
||||||
if part.len() == 1 {
|
if part.len() == 1 {
|
||||||
|
|||||||
Reference in New Issue
Block a user