// examples/suggestions2.rs //! Demonstrates automatic cursor management + INTELLIGENT SUGGESTIONS //! //! This example REQUIRES the `cursor-style` feature to compile. //! //! Run with: //! cargo run --example suggestions2 --features "gui,cursor-style,suggestions" // REQUIRE cursor-style feature - example won't compile without it #[cfg(not(feature = "cursor-style"))] compile_error!( "This example requires the 'cursor-style' feature. \ Run with: cargo run --example suggestions2 --features \"gui,cursor-style,suggestions\"" ); use std::io; use std::collections::HashMap; use crossterm::{ event::{ self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyModifiers, }, execute, terminal::{ disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, }, }; use ratatui::{ backend::{Backend, CrosstermBackend}, layout::{Constraint, Direction, Layout}, style::{Color, Style}, text::{Line, Span}, widgets::{Block, Borders, Paragraph}, Frame, Terminal, }; use canvas::{ canvas::{ gui::render_canvas_default, modes::{AppMode, ModeManager, HighlightState}, CursorManager, // This import only exists when cursor-style feature is enabled }, suggestions::gui::render_suggestions_dropdown, DataProvider, FormEditor, SuggestionsProvider, SuggestionItem, }; use async_trait::async_trait; use anyhow::Result; // Enhanced FormEditor that demonstrates automatic cursor management + SUGGESTIONS struct AutoCursorFormEditor { editor: FormEditor, has_unsaved_changes: bool, debug_message: String, command_buffer: String, // For multi-key vim commands like "gg" } impl AutoCursorFormEditor { fn new(data_provider: D) -> Self { Self { editor: FormEditor::new(data_provider), has_unsaved_changes: false, debug_message: "🎯 Automatic Cursor + Suggestions Demo - cursor-style feature enabled!".to_string(), command_buffer: String::new(), } } // === COMMAND BUFFER HANDLING === fn clear_command_buffer(&mut self) { self.command_buffer.clear(); } fn add_to_command_buffer(&mut self, ch: char) { self.command_buffer.push(ch); } fn get_command_buffer(&self) -> &str { &self.command_buffer } fn has_pending_command(&self) -> bool { !self.command_buffer.is_empty() } // === VISUAL/HIGHLIGHT MODE SUPPORT === fn enter_visual_mode(&mut self) { // Use the library method instead of manual state setting self.editor.enter_highlight_mode(); self.debug_message = "πŸ”₯ VISUAL MODE - Cursor: Blinking Block β–ˆ".to_string(); } fn enter_visual_line_mode(&mut self) { // Use the library method instead of manual state setting self.editor.enter_highlight_line_mode(); self.debug_message = "πŸ”₯ VISUAL LINE MODE - Cursor: Blinking Block β–ˆ".to_string(); } fn exit_visual_mode(&mut self) { // Use the library method self.editor.exit_highlight_mode(); self.debug_message = "πŸ”’ NORMAL MODE - Cursor: Steady Block β–ˆ".to_string(); } fn update_visual_selection(&mut self) { if self.editor.is_highlight_mode() { use canvas::canvas::state::SelectionState; match self.editor.selection_state() { SelectionState::Characterwise { anchor } => { self.debug_message = format!( "🎯 Visual selection: anchor=({},{}) current=({},{}) - Cursor: Blinking Block β–ˆ", anchor.0, anchor.1, self.editor.current_field(), self.editor.cursor_position() ); } SelectionState::Linewise { anchor_field } => { self.debug_message = format!( "🎯 Visual LINE selection: anchor={} current={} - Cursor: Blinking Block β–ˆ", anchor_field, self.editor.current_field() ); } _ => {} } } } // === ENHANCED MOVEMENT WITH VISUAL UPDATES === fn move_left(&mut self) { self.editor.move_left(); self.update_visual_selection(); } fn move_right(&mut self) { self.editor.move_right(); self.update_visual_selection(); } fn move_up(&mut self) { let _ = self.editor.move_up(); self.update_visual_selection(); } fn move_down(&mut self) { let _ = self.editor.move_down(); self.update_visual_selection(); } fn move_word_next(&mut self) { self.editor.move_word_next(); self.update_visual_selection(); } fn move_word_prev(&mut self) { self.editor.move_word_prev(); self.update_visual_selection(); } fn move_word_end(&mut self) { self.editor.move_word_end(); self.update_visual_selection(); } fn move_word_end_prev(&mut self) { self.editor.move_word_end_prev(); self.update_visual_selection(); } fn move_line_start(&mut self) { self.editor.move_line_start(); self.update_visual_selection(); } fn move_line_end(&mut self) { self.editor.move_line_end(); self.update_visual_selection(); } fn move_first_line(&mut self) { self.editor.move_first_line(); self.update_visual_selection(); } fn move_last_line(&mut self) { self.editor.move_last_line(); self.update_visual_selection(); } fn prev_field(&mut self) { let _ = self.editor.prev_field(); self.update_visual_selection(); } fn next_field(&mut self) { let _ = self.editor.next_field(); self.update_visual_selection(); } // === DELETE OPERATIONS === fn delete_backward(&mut self) -> anyhow::Result<()> { let result = self.editor.delete_backward(); if result.is_ok() { self.has_unsaved_changes = true; self.debug_message = "⌫ Deleted character backward".to_string(); } Ok(result?) } fn delete_forward(&mut self) -> anyhow::Result<()> { let result = self.editor.delete_forward(); if result.is_ok() { self.has_unsaved_changes = true; self.debug_message = "⌦ Deleted character forward".to_string(); } Ok(result?) } // === MODE TRANSITIONS WITH AUTOMATIC CURSOR MANAGEMENT === fn enter_edit_mode(&mut self) { self.editor.enter_edit_mode(); // 🎯 Library automatically sets cursor to bar | self.debug_message = "✏️ INSERT MODE - Cursor: Steady Bar |".to_string(); } fn enter_append_mode(&mut self) { self.editor.enter_append_mode(); // 🎯 Library automatically positions cursor and sets mode self.debug_message = "✏️ INSERT (append) - Cursor: Steady Bar |".to_string(); } fn exit_edit_mode(&mut self) { let _ = self.editor.exit_edit_mode(); // 🎯 Library automatically sets cursor to block β–ˆ self.exit_visual_mode(); self.debug_message = "πŸ”’ NORMAL MODE - Cursor: Steady Block β–ˆ".to_string(); } fn insert_char(&mut self, ch: char) -> anyhow::Result<()> { let result = self.editor.insert_char(ch); if result.is_ok() { self.has_unsaved_changes = true; } Ok(result?) } // === SUGGESTIONS SUPPORT === async fn trigger_suggestions(&mut self, provider: &mut A) -> anyhow::Result<()> where A: SuggestionsProvider, { self.editor.trigger_suggestions(provider).await } fn suggestions_next(&mut self) { self.editor.suggestions_next(); } fn apply_suggestion(&mut self) -> Option { self.editor.apply_suggestion() } fn is_suggestions_active(&self) -> bool { self.editor.is_suggestions_active() } fn suggestions(&self) -> &[SuggestionItem] { self.editor.suggestions() } // === MANUAL CURSOR OVERRIDE DEMONSTRATION === /// Demonstrate manual cursor control (for advanced users) fn demo_manual_cursor_control(&mut self) -> std::io::Result<()> { // Users can still manually control cursor if needed CursorManager::update_for_mode(AppMode::Command)?; self.debug_message = "πŸ”§ Manual override: Command cursor _".to_string(); Ok(()) } fn restore_automatic_cursor(&mut self) -> std::io::Result<()> { // Restore automatic cursor based on current mode CursorManager::update_for_mode(self.editor.mode())?; self.debug_message = "🎯 Restored automatic cursor management".to_string(); Ok(()) } // === DELEGATE TO ORIGINAL EDITOR === fn current_field(&self) -> usize { self.editor.current_field() } fn cursor_position(&self) -> usize { self.editor.cursor_position() } fn mode(&self) -> AppMode { self.editor.mode() } fn current_text(&self) -> &str { self.editor.current_text() } fn data_provider(&self) -> &D { self.editor.data_provider() } fn ui_state(&self) -> &canvas::EditorState { self.editor.ui_state() } fn set_mode(&mut self, mode: AppMode) { self.editor.set_mode(mode); // 🎯 Library automatically updates cursor if mode != AppMode::Highlight { self.exit_visual_mode(); } } // === STATUS AND DEBUG === fn set_debug_message(&mut self, msg: String) { self.debug_message = msg; } fn debug_message(&self) -> &str { &self.debug_message } fn has_unsaved_changes(&self) -> bool { self.has_unsaved_changes } } // Demo form data with interesting text for cursor demonstration + SUGGESTIONS struct CursorDemoData { fields: Vec<(String, String)>, } impl CursorDemoData { fn new() -> Self { Self { fields: vec![ ("πŸ‘€ Name".to_string(), "John-Paul McDonald".to_string()), ("πŸ“§ Email".to_string(), "user@exa".to_string()), // Partial for suggestions demo ("πŸ“± Phone".to_string(), "+1 (555) 123-4567".to_string()), ("🏒 Company".to_string(), "tech".to_string()), // Partial for suggestions demo ("🏠 Address".to_string(), "123 Main St, Apt 4B".to_string()), ("πŸ’» Skills".to_string(), "rust,py".to_string()), // Multi-tag demo ("πŸ“ Notes".to_string(), "Watch the cursor change! Normal=β–ˆ Insert=| Visual=blinkingβ–ˆ".to_string()), ("🎯 Cursor + Suggestions".to_string(), "Try: Email (user@exa), Company (tech), Skills (rust,py) + Tab for smart suggestions!".to_string()), ], } } } impl DataProvider for CursorDemoData { fn field_count(&self) -> usize { self.fields.len() } fn field_name(&self, index: usize) -> &str { &self.fields[index].0 } fn field_value(&self, index: usize) -> &str { &self.fields[index].1 } fn set_field_value(&mut self, index: usize, value: String) { self.fields[index].1 = value; } fn supports_suggestions(&self, field_index: usize) -> bool { // Enable suggestions for email, company, and skills fields matches!(field_index, 1 | 3 | 5) } fn display_value(&self, _index: usize) -> Option<&str> { None } } // =================================================================== // INTELLIGENT SUGGESTIONS PROVIDER - More logical behavior // =================================================================== struct PowerfulSuggestionsProvider { // Cache for performance (realistic behavior) email_cache: HashMap>, company_cache: HashMap>, } impl PowerfulSuggestionsProvider { fn new() -> Self { Self { email_cache: HashMap::new(), company_cache: HashMap::new(), } } } #[async_trait] impl SuggestionsProvider for PowerfulSuggestionsProvider { async fn fetch_suggestions(&mut self, field_index: usize, query: &str) -> Result> { // Minimum query length check (realistic behavior) if query.trim().is_empty() { return Ok(Vec::new()); } // Simulate realistic network delay based on field type let delay_ms = match field_index { 1 => 150, // Email: fast local lookup 3 => 300, // Company: API call 5 => 100, // Skills: local data _ => 50, }; tokio::time::sleep(std::time::Duration::from_millis(delay_ms)).await; // Route to appropriate suggestion logic match field_index { 1 => self.get_email_suggestions(query).await, 3 => self.get_company_suggestions(query).await, 5 => self.get_skills_suggestions(query).await, _ => Ok(Vec::new()), } } } impl PowerfulSuggestionsProvider { /// Smart email suggestions with domain completion and validation async fn get_email_suggestions(&mut self, query: &str) -> Result> { // Check cache first (realistic performance optimization) if let Some(cached) = self.email_cache.get(query) { return Ok(cached.clone()); } let suggestions = if let Some(at_pos) = query.find('@') { // Domain completion mode: "user@exa" β†’ complete domains let username = &query[..at_pos]; let domain_part = &query[at_pos + 1..]; // Validate username (basic email validation) if username.is_empty() || username.len() > 64 { return Ok(Vec::new()); } let domains = vec![ ("example.com", "Example Domain - For testing"), ("gmail.com", "Gmail - Google's email service"), ("outlook.com", "Outlook - Microsoft email"), ("yahoo.com", "Yahoo Mail - Classic email"), ("hotmail.com", "Hotmail - Microsoft legacy"), ("company.com", "Company Email - Corporate"), ("university.edu", "University - Educational"), ("protonmail.com", "ProtonMail - Secure email"), ("icloud.com", "iCloud - Apple's email service"), ("fastmail.com", "FastMail - Premium email"), ]; // Smart filtering: exact prefix match first, then contains let mut exact_matches = Vec::new(); let mut fuzzy_matches = Vec::new(); for (domain, desc) in domains { if domain.starts_with(domain_part) { exact_matches.push((domain, desc)); } else if domain.contains(domain_part) && !domain_part.is_empty() { fuzzy_matches.push((domain, desc)); } } // Combine results: exact matches first, then fuzzy let mut all_matches = exact_matches; all_matches.extend(fuzzy_matches); all_matches.into_iter() .take(8) // Limit results (realistic UX) .map(|(domain, desc)| { let full_email = format!("{}@{}", username, domain); SuggestionItem { display_text: format!("{} - {}", full_email, desc), value_to_store: full_email, } }) .collect() } else { // Username mode: "user" β†’ suggest common email formats if query.len() >= 2 && query.len() <= 32 { vec![ SuggestionItem { display_text: format!("{}@gmail.com - Most popular email", query), value_to_store: format!("{}@gmail.com", query), }, SuggestionItem { display_text: format!("{}@outlook.com - Microsoft email", query), value_to_store: format!("{}@outlook.com", query), }, SuggestionItem { display_text: format!("{}@company.com - Work email", query), value_to_store: format!("{}@company.com", query), }, ] } else { Vec::new() } }; // Cache the result (realistic performance) self.email_cache.insert(query.to_string(), suggestions.clone()); Ok(suggestions) } /// Intelligent company suggestions with fuzzy matching and scoring async fn get_company_suggestions(&mut self, query: &str) -> Result> { // Check cache first if let Some(cached) = self.company_cache.get(query) { return Ok(cached.clone()); } let companies = vec![ ("Google", "Technology", "Search, Cloud, AI", 10), ("Microsoft", "Technology", "Software, Cloud, Gaming", 10), ("Apple", "Technology", "Consumer Electronics", 10), ("Amazon", "E-commerce", "Cloud, Retail, Logistics", 10), ("Meta", "Social Media", "Facebook, Instagram, WhatsApp", 9), ("Tesla", "Automotive", "Electric Vehicles, Energy", 8), ("Netflix", "Entertainment", "Streaming, Content", 8), ("SpaceX", "Aerospace", "Space Exploration", 7), ("Uber", "Transportation", "Ride-sharing, Delivery", 7), ("Airbnb", "Hospitality", "Home Sharing", 7), ("Stripe", "Fintech", "Payment Processing", 8), ("Shopify", "E-commerce", "Online Store Platform", 7), ("Slack", "Productivity", "Team Communication", 6), ("Zoom", "Communication", "Video Conferencing", 6), ("Figma", "Design", "Collaborative Design Tools", 5), ("Tech Startup", "Technology", "Early Stage Company", 3), ("Tech Corporation", "Technology", "Large Enterprise", 4), ("Technology Solutions", "Technology", "Custom Software", 3), ]; let query_lower = query.to_lowercase(); // Smart scoring algorithm let mut scored_companies: Vec<_> = companies.iter() .filter_map(|(name, category, desc, popularity)| { let name_lower = name.to_lowercase(); let category_lower = category.to_lowercase(); let desc_lower = desc.to_lowercase(); let mut score = 0; // Exact name prefix (highest score) if name_lower.starts_with(&query_lower) { score += 100; } // Name contains query else if name_lower.contains(&query_lower) { score += 50; } // Category match else if category_lower.contains(&query_lower) { score += 30; } // Description match else if desc_lower.contains(&query_lower) { score += 20; } // Fuzzy match (simple version) else if fuzzy_match_simple(&name_lower, &query_lower) { score += 10; } if score > 0 { score += popularity; // Add popularity bonus Some((name, category, desc, score)) } else { None } }) .collect(); // Sort by score (highest first) scored_companies.sort_by(|a, b| b.3.cmp(&a.3)); let suggestions: Vec = scored_companies.into_iter() .take(10) // Limit results .map(|(name, category, desc, _score)| SuggestionItem { display_text: format!("{} - {} β€’ {}", name, category, desc), value_to_store: name.to_string(), }) .collect(); // Cache the result self.company_cache.insert(query.to_string(), suggestions.clone()); Ok(suggestions) } /// Smart multi-tag skills suggestions with context awareness async fn get_skills_suggestions(&mut self, query: &str) -> Result> { let skills_db = vec![ // Programming Languages ("Rust", "Language", "Systems programming"), ("Python", "Language", "Versatile, data science"), ("JavaScript", "Language", "Web development"), ("TypeScript", "Language", "Typed JavaScript"), ("Go", "Language", "Cloud, microservices"), ("Java", "Language", "Enterprise, Android"), ("C++", "Language", "Performance critical"), ("C#", "Language", "Microsoft ecosystem"), ("Swift", "Language", "iOS development"), ("Kotlin", "Language", "Android, JVM"), // Frontend Frameworks ("React", "Frontend", "Popular UI library"), ("Vue", "Frontend", "Progressive framework"), ("Angular", "Frontend", "Full framework"), ("Svelte", "Frontend", "Compile-time framework"), // Backend Technologies ("Node.js", "Backend", "JavaScript runtime"), ("Express", "Backend", "Web framework"), ("Django", "Backend", "Python web framework"), ("Flask", "Backend", "Micro web framework"), ("Rails", "Backend", "Ruby web framework"), // Databases ("PostgreSQL", "Database", "Advanced SQL database"), ("MongoDB", "Database", "Document database"), ("Redis", "Database", "In-memory cache"), ("MySQL", "Database", "Popular SQL database"), // DevOps & Cloud ("Docker", "DevOps", "Containerization"), ("Kubernetes", "DevOps", "Container orchestration"), ("AWS", "Cloud", "Amazon Web Services"), ("GCP", "Cloud", "Google Cloud Platform"), ("Azure", "Cloud", "Microsoft cloud"), // Tools & Methodologies ("Git", "Tool", "Version control"), ("Linux", "Tool", "Operating system"), ("DevOps", "Methodology", "Development operations"), ("CI/CD", "Methodology", "Continuous integration"), ("TDD", "Methodology", "Test-driven development"), ("Agile", "Methodology", "Project management"), ("Scrum", "Methodology", "Agile framework"), // Emerging Tech ("Machine Learning", "AI", "Predictive models"), ("Data Science", "Analytics", "Data analysis"), ("Blockchain", "Distributed", "Decentralized systems"), ("WebAssembly", "Performance", "High-performance web"), ]; // Parse existing tags to avoid duplicates let current_tags: Vec<&str> = query.split(',').map(|s| s.trim()).collect(); let last_tag = current_tags.last().unwrap_or(&"").to_lowercase(); // Don't suggest if last tag is empty if last_tag.is_empty() { return Ok(Vec::new()); } // Find matching skills let mut matching_skills: Vec<_> = skills_db.iter() .filter(|(skill, _, _)| { let skill_lower = skill.to_lowercase(); // Check if skill matches and isn't already selected skill_lower.contains(&last_tag) && !current_tags[..current_tags.len()-1].iter().any(|&existing| existing.eq_ignore_ascii_case(skill) ) }) .collect(); // Sort by relevance (exact prefix first) matching_skills.sort_by(|a, b| { let a_exact = a.0.to_lowercase().starts_with(&last_tag); let b_exact = b.0.to_lowercase().starts_with(&last_tag); match (a_exact, b_exact) { (true, false) => std::cmp::Ordering::Less, (false, true) => std::cmp::Ordering::Greater, _ => a.0.cmp(b.0), } }); let suggestions = matching_skills.into_iter() .take(8) // Limit results .map(|(skill, category, desc)| { let mut new_tags = current_tags[..current_tags.len()-1].to_vec(); new_tags.push(skill); let new_value = new_tags.join(", "); SuggestionItem { display_text: format!("+ {} ({} β€’ {})", skill, category, desc), value_to_store: new_value, } }) .collect(); Ok(suggestions) } } /// Simple fuzzy matching: check if all characters of pattern appear in text in order fn fuzzy_match_simple(text: &str, pattern: &str) -> bool { if pattern.is_empty() { return true; } let mut pattern_chars = pattern.chars(); let mut current_char = pattern_chars.next(); for text_char in text.chars() { if let Some(pattern_char) = current_char { if text_char == pattern_char { current_char = pattern_chars.next(); if current_char.is_none() { return true; // All pattern characters matched } } } } false } /// Automatic cursor management demonstration + SUGGESTIONS /// Features the CursorManager directly to show it's working async fn handle_key_press( key: KeyCode, modifiers: KeyModifiers, editor: &mut AutoCursorFormEditor, suggestions_provider: &mut PowerfulSuggestionsProvider, ) -> anyhow::Result { let mode = editor.mode(); // Quit handling if (key == KeyCode::Char('q') && modifiers.contains(KeyModifiers::CONTROL)) || (key == KeyCode::Char('c') && modifiers.contains(KeyModifiers::CONTROL)) || key == KeyCode::F(10) { return Ok(false); } match (mode, key, modifiers) { // === SUGGESTIONS HANDLING (NEW!) === // Tab: Trigger or navigate suggestions (_, KeyCode::Tab, _) => { if editor.is_suggestions_active() { editor.suggestions_next(); editor.set_debug_message("πŸ“ Next suggestion".to_string()); } else if editor.data_provider().supports_suggestions(editor.current_field()) { match editor.trigger_suggestions(suggestions_provider).await { Ok(_) => { if editor.suggestions().is_empty() { editor.set_debug_message("πŸ” No suggestions found".to_string()); } else { editor.set_debug_message(format!("✨ {} suggestions found", editor.suggestions().len())); } } Err(e) => { editor.set_debug_message(format!("❌ Suggestion error: {}", e)); } } } else { editor.next_field(); editor.set_debug_message("Tab: next field".to_string()); } } // Enter: Apply suggestion or move to next field (_, KeyCode::Enter, _) => { if editor.is_suggestions_active() { if let Some(applied) = editor.apply_suggestion() { editor.set_debug_message(format!("βœ… Applied: {}", applied)); } else { editor.set_debug_message("❌ No suggestion selected".to_string()); } } else { editor.next_field(); editor.set_debug_message("Enter: next field".to_string()); } } // === MODE TRANSITIONS WITH AUTOMATIC CURSOR MANAGEMENT === (AppMode::ReadOnly, KeyCode::Char('i'), _) => { editor.enter_edit_mode(); // 🎯 Automatic: cursor becomes bar | editor.clear_command_buffer(); } (AppMode::ReadOnly, KeyCode::Char('a'), _) => { editor.enter_append_mode(); editor.set_debug_message("✏️ INSERT (append) - Cursor: Steady Bar |".to_string()); editor.clear_command_buffer(); } (AppMode::ReadOnly, KeyCode::Char('A'), _) => { editor.move_line_end(); editor.enter_edit_mode(); // 🎯 Automatic: cursor becomes bar | editor.set_debug_message("✏️ INSERT (end of line) - Cursor: Steady Bar |".to_string()); editor.clear_command_buffer(); } (AppMode::ReadOnly, KeyCode::Char('o'), _) => { editor.move_line_end(); editor.enter_edit_mode(); // 🎯 Automatic: cursor becomes bar | editor.set_debug_message("✏️ INSERT (open line) - Cursor: Steady Bar |".to_string()); editor.clear_command_buffer(); } // From Normal Mode: Enter visual modes (AppMode::ReadOnly, KeyCode::Char('v'), _) => { editor.enter_visual_mode(); editor.clear_command_buffer(); } (AppMode::ReadOnly, KeyCode::Char('V'), _) => { editor.enter_visual_line_mode(); editor.clear_command_buffer(); } // From Visual Mode: Switch between visual modes or exit (AppMode::Highlight, KeyCode::Char('v'), _) => { use canvas::canvas::state::SelectionState; match editor.editor.selection_state() { SelectionState::Characterwise { .. } => { // Already in characterwise mode, exit visual mode (vim behavior) editor.exit_visual_mode(); editor.set_debug_message("πŸ”’ Exited visual mode".to_string()); } _ => { // Switch from linewise to characterwise mode editor.editor.enter_highlight_mode(); editor.update_visual_selection(); editor.set_debug_message("πŸ”₯ Switched to VISUAL mode".to_string()); } } editor.clear_command_buffer(); } (AppMode::Highlight, KeyCode::Char('V'), _) => { use canvas::canvas::state::SelectionState; match editor.editor.selection_state() { SelectionState::Linewise { .. } => { // Already in linewise mode, exit visual mode (vim behavior) editor.exit_visual_mode(); editor.set_debug_message("πŸ”’ Exited visual mode".to_string()); } _ => { // Switch from characterwise to linewise mode editor.editor.enter_highlight_line_mode(); editor.update_visual_selection(); editor.set_debug_message("πŸ”₯ Switched to VISUAL LINE mode".to_string()); } } editor.clear_command_buffer(); } // Escape: Exit any mode back to normal (and cancel suggestions) (_, KeyCode::Esc, _) => { match mode { AppMode::Edit => { editor.exit_edit_mode(); // Exit insert mode (suggestions auto-cancelled) } AppMode::Highlight => { editor.exit_visual_mode(); // Exit visual mode } _ => { // Already in normal mode, just clear command buffer editor.clear_command_buffer(); } } } // === CURSOR MANAGEMENT DEMONSTRATION === (AppMode::ReadOnly, KeyCode::F(1), _) => { editor.demo_manual_cursor_control()?; } (AppMode::ReadOnly, KeyCode::F(2), _) => { editor.restore_automatic_cursor()?; } // === MOVEMENT: VIM-STYLE NAVIGATION === // Basic movement (hjkl and arrows) (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Char('h'), _) | (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Left, _) => { editor.move_left(); editor.set_debug_message("← left".to_string()); editor.clear_command_buffer(); } (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Char('l'), _) | (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Right, _) => { editor.move_right(); editor.set_debug_message("β†’ right".to_string()); editor.clear_command_buffer(); } (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Char('j'), _) | (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Down, _) => { editor.move_down(); editor.set_debug_message("↓ next field".to_string()); editor.clear_command_buffer(); } (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Char('k'), _) | (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Up, _) => { editor.move_up(); editor.set_debug_message("↑ previous field".to_string()); editor.clear_command_buffer(); } // Word movement (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Char('w'), _) => { editor.move_word_next(); editor.set_debug_message("w: next word start".to_string()); editor.clear_command_buffer(); } (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Char('b'), _) => { editor.move_word_prev(); editor.set_debug_message("b: previous word start".to_string()); editor.clear_command_buffer(); } (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Char('e'), _) => { editor.move_word_end(); editor.set_debug_message("e: word end".to_string()); editor.clear_command_buffer(); } // Line movement (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Char('0'), _) | (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Home, _) => { editor.move_line_start(); editor.set_debug_message("0: line start".to_string()); } (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Char('$'), _) | (AppMode::ReadOnly | AppMode::Highlight, KeyCode::End, _) => { editor.move_line_end(); editor.set_debug_message("$: line end".to_string()); } // Field/document movement (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Char('g'), _) => { if editor.get_command_buffer() == "g" { editor.move_first_line(); editor.set_debug_message("gg: first field".to_string()); editor.clear_command_buffer(); } else { editor.clear_command_buffer(); editor.add_to_command_buffer('g'); editor.set_debug_message("g".to_string()); } } (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Char('G'), _) => { editor.move_last_line(); editor.set_debug_message("G: last field".to_string()); editor.clear_command_buffer(); } // === EDIT MODE MOVEMENT === (AppMode::Edit, KeyCode::Left, m) if m.contains(KeyModifiers::CONTROL) => { editor.move_word_prev(); editor.set_debug_message("Ctrl+← word back".to_string()); } (AppMode::Edit, KeyCode::Right, m) if m.contains(KeyModifiers::CONTROL) => { editor.move_word_next(); editor.set_debug_message("Ctrl+β†’ word forward".to_string()); } (AppMode::Edit, KeyCode::Left, _) => { editor.move_left(); } (AppMode::Edit, KeyCode::Right, _) => { editor.move_right(); } (AppMode::Edit, KeyCode::Up, _) => { editor.move_up(); } (AppMode::Edit, KeyCode::Down, _) => { editor.move_down(); } (AppMode::Edit, KeyCode::Home, _) => { editor.move_line_start(); } (AppMode::Edit, KeyCode::End, _) => { editor.move_line_end(); } // === DELETE OPERATIONS === (AppMode::Edit, KeyCode::Backspace, _) => { editor.delete_backward()?; } (AppMode::Edit, KeyCode::Delete, _) => { editor.delete_forward()?; } // Delete operations in normal mode (vim x) (AppMode::ReadOnly, KeyCode::Char('x'), _) => { editor.delete_forward()?; editor.set_debug_message("x: deleted character".to_string()); } (AppMode::ReadOnly, KeyCode::Char('X'), _) => { editor.delete_backward()?; editor.set_debug_message("X: deleted character backward".to_string()); } // === CHARACTER INPUT === (AppMode::Edit, KeyCode::Char(c), m) if !m.contains(KeyModifiers::CONTROL) => { editor.insert_char(c)?; } // === DEBUG/INFO COMMANDS === (AppMode::ReadOnly, KeyCode::Char('?'), _) => { editor.set_debug_message(format!( "Field {}/{}, Pos {}, Mode: {:?} - Smart suggestions: Email domains, Company scoring, Skills multi-tag", editor.current_field() + 1, editor.data_provider().field_count(), editor.cursor_position(), editor.mode() )); } _ => { if editor.has_pending_command() { editor.clear_command_buffer(); editor.set_debug_message("Invalid command sequence".to_string()); } else { editor.set_debug_message(format!( "Unhandled: {:?} + {:?} in {:?} mode", key, modifiers, mode )); } } } Ok(true) } async fn run_app( terminal: &mut Terminal, mut editor: AutoCursorFormEditor, ) -> io::Result<()> { let mut suggestions_provider = PowerfulSuggestionsProvider::new(); loop { terminal.draw(|f| ui(f, &editor))?; if let Event::Key(key) = event::read()? { match handle_key_press(key.code, key.modifiers, &mut editor, &mut suggestions_provider).await { Ok(should_continue) => { if !should_continue { break; } } Err(e) => { editor.set_debug_message(format!("Error: {}", e)); } } } } Ok(()) } fn ui(f: &mut Frame, editor: &AutoCursorFormEditor) { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Min(8), Constraint::Length(10)]) .split(f.area()); let active_field_rect = render_enhanced_canvas(f, chunks[0], editor); // Render suggestions dropdown if active if let Some(input_rect) = active_field_rect { render_suggestions_dropdown( f, chunks[0], input_rect, &canvas::canvas::theme::DefaultCanvasTheme::default(), &editor.editor, ); } render_status_and_help(f, chunks[1], editor); } fn render_enhanced_canvas( f: &mut Frame, area: ratatui::layout::Rect, editor: &AutoCursorFormEditor, ) -> Option { render_canvas_default(f, area, &editor.editor) } fn render_status_and_help( f: &mut Frame, area: ratatui::layout::Rect, editor: &AutoCursorFormEditor, ) { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Length(3), Constraint::Length(7)]) .split(area); // Status bar with cursor information + suggestions let mode_text = match editor.mode() { AppMode::Edit => "INSERT | (bar cursor)", AppMode::ReadOnly => "NORMAL β–ˆ (block cursor)", AppMode::Highlight => { // Use library selection state instead of editor.highlight_state() use canvas::canvas::state::SelectionState; match editor.editor.selection_state() { SelectionState::Characterwise { .. } => "VISUAL β–ˆ (blinking block)", SelectionState::Linewise { .. } => "VISUAL LINE β–ˆ (blinking block)", _ => "VISUAL β–ˆ (blinking block)", } }, _ => "NORMAL β–ˆ (block cursor)", }; let suggestions_info = if editor.is_suggestions_active() { if editor.editor.ui_state().is_suggestions_loading() { " | ⏳ Loading suggestions..." } else if !editor.suggestions().is_empty() { " | ✨ Suggestions available" } else { " | πŸ” No suggestions" } } else { "" }; let status_text = if editor.has_pending_command() { format!("-- {} -- {} [{}]{}", mode_text, editor.debug_message(), editor.get_command_buffer(), suggestions_info) } else if editor.has_unsaved_changes() { format!("-- {} -- [Modified] {}{}", mode_text, editor.debug_message(), suggestions_info) } else { format!("-- {} -- {}{}", mode_text, editor.debug_message(), suggestions_info) }; let status = Paragraph::new(Line::from(Span::raw(status_text))) .block(Block::default().borders(Borders::ALL).title("🎯 Smart Cursor + Intelligent Suggestions")); f.render_widget(status, chunks[0]); // Enhanced help text with suggestions info let help_text = match editor.mode() { AppMode::ReadOnly => { if editor.has_pending_command() { match editor.get_command_buffer() { "g" => "Press 'g' again for first field, or any other key to cancel", _ => "Pending command... (Esc to cancel)" } } else { "🎯 SMART SUGGESTIONS DEMO: Normal β–ˆ | Insert | | Visual blinkingβ–ˆ\n\ Normal: hjkl/arrows=move, w/b/e=words, 0/$=line, gg/G=first/last\n\ i/a/A=insert, v/V=visual, x/X=delete, ?=info\n\ Tab=smart suggestions: Email domains, Company scoring, Multi-tag skills\n\ Features: caching, fuzzy matching, validation, scoring | F1/F2=cursor demo" } } AppMode::Edit => { "🎯 INSERT MODE - Cursor: | (bar)\n\ arrows=move, Ctrl+arrows=words, Backspace/Del=delete\n\ Tab=smart suggestions: domains, companies, skills with fuzzy matching\n\ Enter=apply suggestion, Esc=normal mode" } AppMode::Highlight => { "🎯 VISUAL MODE - Cursor: β–ˆ (blinking block)\n\ hjkl/arrows=extend selection, w/b/e=word selection\n\ Esc=normal" } _ => "🎯 Watch the cursor change automatically! Tab for intelligent suggestions on certain fields!" }; let help = Paragraph::new(help_text) .block(Block::default().borders(Borders::ALL).title("πŸš€ Smart Cursor + Intelligent Suggestions")) .style(Style::default().fg(Color::Gray)); f.render_widget(help, chunks[1]); } #[tokio::main] async fn main() -> Result<(), Box> { // Print feature status println!("🎯 Smart Cursor + Intelligent Suggestions Demo"); println!("βœ… cursor-style feature: ENABLED"); println!("βœ… suggestions feature: ENABLED"); println!("πŸš€ Automatic cursor management: ACTIVE"); println!("✨ Intelligent suggestions: ACTIVE"); println!("πŸ“– Watch your terminal cursor change based on mode!"); println!(); println!("🎯 Try these logical suggestions:"); println!(" πŸ“§ Email field: 'user@exa' + Tab β†’ smart domain completion"); println!(" 🏒 Company field: 'tech' + Tab β†’ scored company matches"); println!(" πŸ’» Skills field: 'rust,py' + Tab β†’ intelligent multi-tag skills"); println!(" 🧠 Features: caching, fuzzy matching, scoring, validation"); println!(); enable_raw_mode()?; let mut stdout = io::stdout(); execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; let data = CursorDemoData::new(); let mut editor = AutoCursorFormEditor::new(data); // Initialize with normal mode - library automatically sets block cursor editor.set_mode(AppMode::ReadOnly); // Demonstrate that CursorManager is available and working CursorManager::update_for_mode(AppMode::ReadOnly)?; let res = run_app(&mut terminal, editor).await; // Library automatically resets cursor on FormEditor::drop() // But we can also manually reset if needed CursorManager::reset()?; disable_raw_mode()?; execute!( terminal.backend_mut(), LeaveAlternateScreen, DisableMouseCapture )?; terminal.show_cursor()?; if let Err(err) = res { println!("{:?}", err); } println!("🎯 Cursor automatically reset to default! Smart suggestions cache cleared!"); Ok(()) }