better suggestions2 example, not there yet

This commit is contained in:
Priec
2025-08-07 18:51:45 +02:00
parent a8de16f66d
commit 0d0e54032c

View File

@@ -1,5 +1,5 @@
// examples/suggestions2.rs // examples/suggestions2.rs
//! Demonstrates automatic cursor management + INTELLIGENT SUGGESTIONS //! Demonstrates automatic cursor management + MULTIPLE SUGGESTION FIELDS
//! //!
//! This example REQUIRES the `cursor-style` feature to compile. //! This example REQUIRES the `cursor-style` feature to compile.
//! //!
@@ -14,7 +14,6 @@ compile_error!(
); );
use std::io; use std::io;
use std::collections::HashMap;
use crossterm::{ use crossterm::{
event::{ event::{
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyModifiers, self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyModifiers,
@@ -59,7 +58,7 @@ impl<D: DataProvider> AutoCursorFormEditor<D> {
Self { Self {
editor: FormEditor::new(data_provider), editor: FormEditor::new(data_provider),
has_unsaved_changes: false, has_unsaved_changes: false,
debug_message: "🎯 Automatic Cursor + Suggestions Demo - cursor-style feature enabled!".to_string(), debug_message: "🎯 Multi-Field Suggestions Demo - 5 fields with different suggestions!".to_string(),
command_buffer: String::new(), command_buffer: String::new(),
} }
} }
@@ -334,29 +333,29 @@ impl<D: DataProvider> AutoCursorFormEditor<D> {
} }
} }
// Demo form data with interesting text for cursor demonstration + SUGGESTIONS // ===================================================================
struct CursorDemoData { // MULTI-FIELD DEMO DATA - 5 different types of suggestion fields
// ===================================================================
struct MultiFieldDemoData {
fields: Vec<(String, String)>, fields: Vec<(String, String)>,
} }
impl CursorDemoData { impl MultiFieldDemoData {
fn new() -> Self { fn new() -> Self {
Self { Self {
fields: vec![ fields: vec![
("👤 Name".to_string(), "John-Paul McDonald".to_string()), ("🍎 Favorite Fruit".to_string(), "".to_string()), // Field 0: Fruits
("📧 Email".to_string(), "user@exa".to_string()), // Partial for suggestions demo ("💼 Job Role".to_string(), "".to_string()), // Field 1: Jobs
("📱 Phone".to_string(), "+1 (555) 123-4567".to_string()), ("💻 Programming Language".to_string(), "".to_string()), // Field 2: Languages
("🏢 Company".to_string(), "tech".to_string()), // Partial for suggestions demo ("🌍 Country".to_string(), "".to_string()), // Field 3: Countries
("🏠 Address".to_string(), "123 Main St, Apt 4B".to_string()), ("🎨 Favorite Color".to_string(), "".to_string()), // Field 4: Colors
("💻 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 { impl DataProvider for MultiFieldDemoData {
fn field_count(&self) -> usize { fn field_count(&self) -> usize {
self.fields.len() self.fields.len()
} }
@@ -374,8 +373,8 @@ impl DataProvider for CursorDemoData {
} }
fn supports_suggestions(&self, field_index: usize) -> bool { fn supports_suggestions(&self, field_index: usize) -> bool {
// Enable suggestions for email, company, and skills fields // All 5 fields support suggestions - perfect for testing!
matches!(field_index, 1 | 3 | 5) field_index < 5
} }
fn display_value(&self, _index: usize) -> Option<&str> { fn display_value(&self, _index: usize) -> Option<&str> {
@@ -384,358 +383,145 @@ impl DataProvider for CursorDemoData {
} }
// =================================================================== // ===================================================================
// INTELLIGENT SUGGESTIONS PROVIDER - More logical behavior // COMPREHENSIVE SUGGESTIONS PROVIDER - 5 different suggestion types!
// =================================================================== // ===================================================================
struct PowerfulSuggestionsProvider { struct ComprehensiveSuggestionsProvider;
// Cache for performance (realistic behavior)
email_cache: HashMap<String, Vec<SuggestionItem>>,
company_cache: HashMap<String, Vec<SuggestionItem>>,
}
impl PowerfulSuggestionsProvider { impl ComprehensiveSuggestionsProvider {
fn new() -> Self { fn new() -> Self {
Self { Self
email_cache: HashMap::new(), }
company_cache: HashMap::new(),
} /// Get fruit suggestions (field 0)
fn get_fruit_suggestions(&self, query: &str) -> Vec<SuggestionItem> {
let fruits = vec![
("Apple", "🍎 Crisp and sweet"),
("Banana", "🍌 Rich in potassium"),
("Cherry", "🍒 Small and tart"),
("Date", "📅 Sweet and chewy"),
("Elderberry", "🫐 Dark purple berry"),
("Fig", "🍇 Sweet Mediterranean fruit"),
("Grape", "🍇 Perfect for wine"),
("Honeydew", "🍈 Sweet melon"),
];
self.filter_suggestions(fruits, query, "fruit")
}
/// Get job role suggestions (field 1)
fn get_job_suggestions(&self, query: &str) -> Vec<SuggestionItem> {
let jobs = vec![
("Software Engineer", "👨‍💻 Build applications"),
("Product Manager", "📋 Manage product roadmap"),
("Data Scientist", "📊 Analyze data patterns"),
("UX Designer", "🎨 Design user experiences"),
("DevOps Engineer", "⚙️ Manage infrastructure"),
("Marketing Manager", "📢 Drive growth"),
("Sales Representative", "💰 Generate revenue"),
("Accountant", "💼 Manage finances"),
];
self.filter_suggestions(jobs, query, "role")
}
/// Get programming language suggestions (field 2)
fn get_language_suggestions(&self, query: &str) -> Vec<SuggestionItem> {
let languages = vec![
("Rust", "🦀 Systems programming"),
("Python", "🐍 Versatile and popular"),
("JavaScript", "⚡ Web development"),
("TypeScript", "🔷 Typed JavaScript"),
("Go", "🏃 Fast and simple"),
("Java", "☕ Enterprise favorite"),
("C++", "⚡ High performance"),
("Swift", "🍎 iOS development"),
];
self.filter_suggestions(languages, query, "language")
}
/// Get country suggestions (field 3)
fn get_country_suggestions(&self, query: &str) -> Vec<SuggestionItem> {
let countries = vec![
("United States", "🇺🇸 North America"),
("Canada", "🇨🇦 Great neighbors"),
("United Kingdom", "🇬🇧 Tea and crumpets"),
("Germany", "🇩🇪 Engineering excellence"),
("France", "🇫🇷 Art and cuisine"),
("Japan", "🇯🇵 Technology hub"),
("Australia", "🇦🇺 Down under"),
("Brazil", "🇧🇷 Carnival country"),
];
self.filter_suggestions(countries, query, "country")
}
/// Get color suggestions (field 4)
fn get_color_suggestions(&self, query: &str) -> Vec<SuggestionItem> {
let colors = vec![
("Red", "🔴 Bold and energetic"),
("Blue", "🔵 Calm and trustworthy"),
("Green", "🟢 Natural and fresh"),
("Yellow", "🟡 Bright and cheerful"),
("Purple", "🟣 Royal and mysterious"),
("Orange", "🟠 Warm and vibrant"),
("Pink", "🩷 Soft and gentle"),
("Black", "⚫ Classic and elegant"),
];
self.filter_suggestions(colors, query, "color")
}
/// Generic filtering helper
fn filter_suggestions(&self, items: Vec<(&str, &str)>, query: &str, category: &str) -> Vec<SuggestionItem> {
let query_lower = query.to_lowercase();
items.iter()
.filter(|(item, _)| {
query.is_empty() || item.to_lowercase().starts_with(&query_lower)
})
.map(|(item, description)| SuggestionItem {
display_text: format!("{} - {}", item, description),
value_to_store: item.to_string(),
})
.collect()
} }
} }
#[async_trait] #[async_trait]
impl SuggestionsProvider for PowerfulSuggestionsProvider { impl SuggestionsProvider for ComprehensiveSuggestionsProvider {
async fn fetch_suggestions(&mut self, field_index: usize, query: &str) -> Result<Vec<SuggestionItem>> { async fn fetch_suggestions(&mut self, field_index: usize, query: &str) -> Result<Vec<SuggestionItem>> {
// Minimum query length check (realistic behavior) // Simulate different network delays for different fields (realistic!)
if query.trim().is_empty() {
return Ok(Vec::new());
}
// Simulate realistic network delay based on field type
let delay_ms = match field_index { let delay_ms = match field_index {
1 => 150, // Email: fast local lookup 0 => 100, // Fruits: local data
3 => 300, // Company: API call 1 => 200, // Jobs: medium API call
5 => 100, // Skills: local data 2 => 150, // Languages: cached data
_ => 50, 3 => 300, // Countries: slow geographic API
4 => 80, // Colors: instant local
_ => 100,
}; };
tokio::time::sleep(std::time::Duration::from_millis(delay_ms)).await; tokio::time::sleep(std::time::Duration::from_millis(delay_ms)).await;
// Route to appropriate suggestion logic let suggestions = match field_index {
match field_index { 0 => self.get_fruit_suggestions(query),
1 => self.get_email_suggestions(query).await, 1 => self.get_job_suggestions(query),
3 => self.get_company_suggestions(query).await, 2 => self.get_language_suggestions(query),
5 => self.get_skills_suggestions(query).await, 3 => self.get_country_suggestions(query),
_ => Ok(Vec::new()), 4 => self.get_color_suggestions(query),
} _ => Vec::new(),
}
}
impl PowerfulSuggestionsProvider {
/// Smart email suggestions with domain completion and validation
async fn get_email_suggestions(&mut self, query: &str) -> Result<Vec<SuggestionItem>> {
// 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<Vec<SuggestionItem>> {
// 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<SuggestionItem> = 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<Vec<SuggestionItem>> {
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) Ok(suggestions)
} }
} }
/// Simple fuzzy matching: check if all characters of pattern appear in text in order /// Multi-field suggestions demonstration + automatic cursor management
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( async fn handle_key_press(
key: KeyCode, key: KeyCode,
modifiers: KeyModifiers, modifiers: KeyModifiers,
editor: &mut AutoCursorFormEditor<CursorDemoData>, editor: &mut AutoCursorFormEditor<MultiFieldDemoData>,
suggestions_provider: &mut PowerfulSuggestionsProvider, suggestions_provider: &mut ComprehensiveSuggestionsProvider,
) -> anyhow::Result<bool> { ) -> anyhow::Result<bool> {
let mode = editor.mode(); let mode = editor.mode();
@@ -748,20 +534,23 @@ async fn handle_key_press(
} }
match (mode, key, modifiers) { match (mode, key, modifiers) {
// === SUGGESTIONS HANDLING (NEW!) === // === SUGGESTIONS HANDLING ===
// Tab: Trigger or navigate suggestions // Tab: Trigger or navigate suggestions
(_, KeyCode::Tab, _) => { (_, KeyCode::Tab, _) => {
if editor.is_suggestions_active() { if editor.is_suggestions_active() {
editor.suggestions_next(); editor.suggestions_next();
editor.set_debug_message("📍 Next suggestion".to_string()); editor.set_debug_message("📍 Next suggestion".to_string());
} else if editor.data_provider().supports_suggestions(editor.current_field()) { } else if editor.data_provider().supports_suggestions(editor.current_field()) {
let field_names = ["Fruit", "Job", "Language", "Country", "Color"];
let field_name = field_names.get(editor.current_field()).unwrap_or(&"Unknown");
match editor.trigger_suggestions(suggestions_provider).await { match editor.trigger_suggestions(suggestions_provider).await {
Ok(_) => { Ok(_) => {
if editor.suggestions().is_empty() { if editor.suggestions().is_empty() {
editor.set_debug_message("🔍 No suggestions found".to_string()); editor.set_debug_message(format!("🔍 No {} suggestions found", field_name.to_lowercase()));
} else { } else {
editor.set_debug_message(format!("{} suggestions found", editor.suggestions().len())); editor.set_debug_message(format!("{} {} suggestions found!", editor.suggestions().len(), field_name.to_lowercase()));
} }
} }
Err(e) => { Err(e) => {
@@ -778,13 +567,15 @@ async fn handle_key_press(
(_, KeyCode::Enter, _) => { (_, KeyCode::Enter, _) => {
if editor.is_suggestions_active() { if editor.is_suggestions_active() {
if let Some(applied) = editor.apply_suggestion() { if let Some(applied) = editor.apply_suggestion() {
editor.set_debug_message(format!("Applied: {}", applied)); editor.set_debug_message(format!("Selected: {}", applied));
} else { } else {
editor.set_debug_message("❌ No suggestion selected".to_string()); editor.set_debug_message("❌ No suggestion selected".to_string());
} }
} else { } else {
editor.next_field(); editor.next_field();
editor.set_debug_message("Enter: next field".to_string()); let field_names = ["Fruit", "Job", "Language", "Country", "Color"];
let field_name = field_names.get(editor.current_field()).unwrap_or(&"Field");
editor.set_debug_message(format!("Enter: moved to {} field", field_name));
} }
} }
@@ -804,12 +595,6 @@ async fn handle_key_press(
editor.set_debug_message("✏️ INSERT (end of line) - Cursor: Steady Bar |".to_string()); editor.set_debug_message("✏️ INSERT (end of line) - Cursor: Steady Bar |".to_string());
editor.clear_command_buffer(); 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 // From Normal Mode: Enter visual modes
(AppMode::ReadOnly, KeyCode::Char('v'), _) => { (AppMode::ReadOnly, KeyCode::Char('v'), _) => {
@@ -821,43 +606,6 @@ async fn handle_key_press(
editor.clear_command_buffer(); 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) // Escape: Exit any mode back to normal (and cancel suggestions)
(_, KeyCode::Esc, _) => { (_, KeyCode::Esc, _) => {
match mode { match mode {
@@ -900,13 +648,17 @@ async fn handle_key_press(
(AppMode::ReadOnly | AppMode::Highlight, KeyCode::Char('j'), _) (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Char('j'), _)
| (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Down, _) => { | (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Down, _) => {
editor.move_down(); editor.move_down();
editor.set_debug_message("↓ next field".to_string()); let field_names = ["Fruit", "Job", "Language", "Country", "Color"];
let field_name = field_names.get(editor.current_field()).unwrap_or(&"Field");
editor.set_debug_message(format!("↓ moved to {} field", field_name));
editor.clear_command_buffer(); editor.clear_command_buffer();
} }
(AppMode::ReadOnly | AppMode::Highlight, KeyCode::Char('k'), _) (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Char('k'), _)
| (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Up, _) => { | (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Up, _) => {
editor.move_up(); editor.move_up();
editor.set_debug_message("↑ previous field".to_string()); let field_names = ["Fruit", "Job", "Language", "Country", "Color"];
let field_name = field_names.get(editor.current_field()).unwrap_or(&"Field");
editor.set_debug_message(format!("↑ moved to {} field", field_name));
editor.clear_command_buffer(); editor.clear_command_buffer();
} }
@@ -939,11 +691,11 @@ async fn handle_key_press(
editor.set_debug_message("$: line end".to_string()); editor.set_debug_message("$: line end".to_string());
} }
// Field/document movement // Document movement
(AppMode::ReadOnly | AppMode::Highlight, KeyCode::Char('g'), _) => { (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Char('g'), _) => {
if editor.get_command_buffer() == "g" { if editor.get_command_buffer() == "g" {
editor.move_first_line(); editor.move_first_line();
editor.set_debug_message("gg: first field".to_string()); editor.set_debug_message("gg: first field (Fruit)".to_string());
editor.clear_command_buffer(); editor.clear_command_buffer();
} else { } else {
editor.clear_command_buffer(); editor.clear_command_buffer();
@@ -953,7 +705,7 @@ async fn handle_key_press(
} }
(AppMode::ReadOnly | AppMode::Highlight, KeyCode::Char('G'), _) => { (AppMode::ReadOnly | AppMode::Highlight, KeyCode::Char('G'), _) => {
editor.move_last_line(); editor.move_last_line();
editor.set_debug_message("G: last field".to_string()); editor.set_debug_message("G: last field (Color)".to_string());
editor.clear_command_buffer(); editor.clear_command_buffer();
} }
@@ -1010,8 +762,11 @@ async fn handle_key_press(
// === DEBUG/INFO COMMANDS === // === DEBUG/INFO COMMANDS ===
(AppMode::ReadOnly, KeyCode::Char('?'), _) => { (AppMode::ReadOnly, KeyCode::Char('?'), _) => {
let field_names = ["Fruit🍎", "Job💼", "Language💻", "Country🌍", "Color🎨"];
let current_field_name = field_names.get(editor.current_field()).unwrap_or(&"Unknown");
editor.set_debug_message(format!( editor.set_debug_message(format!(
"Field {}/{}, Pos {}, Mode: {:?} - Smart suggestions: Email domains, Company scoring, Skills multi-tag", "Field: {} ({}/{}), Pos: {}, Mode: {:?}",
current_field_name,
editor.current_field() + 1, editor.current_field() + 1,
editor.data_provider().field_count(), editor.data_provider().field_count(),
editor.cursor_position(), editor.cursor_position(),
@@ -1024,9 +779,11 @@ async fn handle_key_press(
editor.clear_command_buffer(); editor.clear_command_buffer();
editor.set_debug_message("Invalid command sequence".to_string()); editor.set_debug_message("Invalid command sequence".to_string());
} else { } else {
let field_names = ["Fruit", "Job", "Language", "Country", "Color"];
let current_field = field_names.get(editor.current_field()).unwrap_or(&"Field");
editor.set_debug_message(format!( editor.set_debug_message(format!(
"Unhandled: {:?} + {:?} in {:?} mode", "{} field - Try: i=insert, Tab=suggestions, j/k=move. Key: {:?}",
key, modifiers, mode current_field, key
)); ));
} }
} }
@@ -1037,9 +794,9 @@ async fn handle_key_press(
async fn run_app<B: Backend>( async fn run_app<B: Backend>(
terminal: &mut Terminal<B>, terminal: &mut Terminal<B>,
mut editor: AutoCursorFormEditor<CursorDemoData>, mut editor: AutoCursorFormEditor<MultiFieldDemoData>,
) -> io::Result<()> { ) -> io::Result<()> {
let mut suggestions_provider = PowerfulSuggestionsProvider::new(); let mut suggestions_provider = ComprehensiveSuggestionsProvider::new();
loop { loop {
terminal.draw(|f| ui(f, &editor))?; terminal.draw(|f| ui(f, &editor))?;
@@ -1061,14 +818,14 @@ async fn run_app<B: Backend>(
Ok(()) Ok(())
} }
fn ui(f: &mut Frame, editor: &AutoCursorFormEditor<CursorDemoData>) { fn ui(f: &mut Frame, editor: &AutoCursorFormEditor<MultiFieldDemoData>) {
let chunks = Layout::default() let chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints([Constraint::Min(8), Constraint::Length(10)]) .constraints([Constraint::Min(8), Constraint::Length(12)])
.split(f.area()); .split(f.area());
let active_field_rect = render_enhanced_canvas(f, chunks[0], editor); let active_field_rect = render_enhanced_canvas(f, chunks[0], editor);
// Render suggestions dropdown if active // Render suggestions dropdown if active
if let Some(input_rect) = active_field_rect { if let Some(input_rect) = active_field_rect {
render_suggestions_dropdown( render_suggestions_dropdown(
@@ -1079,14 +836,14 @@ fn ui(f: &mut Frame, editor: &AutoCursorFormEditor<CursorDemoData>) {
&editor.editor, &editor.editor,
); );
} }
render_status_and_help(f, chunks[1], editor); render_status_and_help(f, chunks[1], editor);
} }
fn render_enhanced_canvas( fn render_enhanced_canvas(
f: &mut Frame, f: &mut Frame,
area: ratatui::layout::Rect, area: ratatui::layout::Rect,
editor: &AutoCursorFormEditor<CursorDemoData>, editor: &AutoCursorFormEditor<MultiFieldDemoData>,
) -> Option<ratatui::layout::Rect> { ) -> Option<ratatui::layout::Rect> {
render_canvas_default(f, area, &editor.editor) render_canvas_default(f, area, &editor.editor)
} }
@@ -1094,86 +851,77 @@ fn render_enhanced_canvas(
fn render_status_and_help( fn render_status_and_help(
f: &mut Frame, f: &mut Frame,
area: ratatui::layout::Rect, area: ratatui::layout::Rect,
editor: &AutoCursorFormEditor<CursorDemoData>, editor: &AutoCursorFormEditor<MultiFieldDemoData>,
) { ) {
let chunks = Layout::default() let chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints([Constraint::Length(3), Constraint::Length(7)]) .constraints([Constraint::Length(3), Constraint::Length(9)])
.split(area); .split(area);
// Status bar with cursor information + suggestions // Status bar with current field and cursor information
let field_names = ["Fruit🍎", "Job💼", "Language💻", "Country🌍", "Color🎨"];
let current_field_name = field_names.get(editor.current_field()).unwrap_or(&"Unknown");
let mode_text = match editor.mode() { let mode_text = match editor.mode() {
AppMode::Edit => "INSERT | (bar cursor)", AppMode::Edit => "INSERT | (bar cursor)",
AppMode::ReadOnly => "NORMAL █ (block cursor)", AppMode::ReadOnly => "NORMAL █ (block cursor)",
AppMode::Highlight => { AppMode::Highlight => "VISUAL █ (blinking block)",
// 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)", _ => "NORMAL █ (block cursor)",
}; };
let suggestions_info = if editor.is_suggestions_active() { let suggestions_info = if editor.is_suggestions_active() {
if editor.editor.ui_state().is_suggestions_loading() { if editor.editor.ui_state().is_suggestions_loading() {
" | ⏳ Loading suggestions..." " | ⏳ Loading suggestions...".to_string()
} else if !editor.suggestions().is_empty() { } else if !editor.suggestions().is_empty() {
" | ✨ Suggestions available" format!(" | ✨ {} suggestions", editor.suggestions().len())
} else { } else {
" | 🔍 No suggestions" " | 🔍 No matches".to_string()
} }
} else { } else {
"" "".to_string()
}; };
let status_text = if editor.has_pending_command() { let status_text = format!(
format!("-- {} -- {} [{}]{}", mode_text, editor.debug_message(), editor.get_command_buffer(), suggestions_info) "-- {} -- {} | Field: {}{}",
} else if editor.has_unsaved_changes() { mode_text,
format!("-- {} -- [Modified] {}{}", mode_text, editor.debug_message(), suggestions_info) editor.debug_message(),
} else { current_field_name,
format!("-- {} -- {}{}", mode_text, editor.debug_message(), suggestions_info) suggestions_info
}; );
let status = Paragraph::new(Line::from(Span::raw(status_text))) let status = Paragraph::new(Line::from(Span::raw(status_text)))
.block(Block::default().borders(Borders::ALL).title("🎯 Smart Cursor + Intelligent Suggestions")); .block(Block::default().borders(Borders::ALL).title("🎯 Multi-Field Suggestions Demo"));
f.render_widget(status, chunks[0]); f.render_widget(status, chunks[0]);
// Enhanced help text with suggestions info // Comprehensive help text
let help_text = match editor.mode() { let help_text = match editor.mode() {
AppMode::ReadOnly => { AppMode::ReadOnly => {
if editor.has_pending_command() { "🎯 MULTI-FIELD SUGGESTIONS DEMO: Normal █ | Insert | | Visual █\n\
match editor.get_command_buffer() { Movement: j/k or ↑↓=fields, h/l or ←→=chars, gg/G=first/last, w/b/e=words\n\
"g" => "Press 'g' again for first field, or any other key to cancel", Actions: i/a/A=insert, v/V=visual, x/X=delete, ?=info, Enter=next field\n\
_ => "Pending command... (Esc to cancel)" 🍎 Fruits: Apple, Banana, Cherry... | 💼 Jobs: Engineer, Manager, Designer...\n\
} 💻 Languages: Rust, Python, JS... | 🌍 Countries: USA, Canada, UK...\n\
} else { 🎨 Colors: Red, Blue, Green... | Tab=suggestions, Enter=select\n\
"🎯 SMART SUGGESTIONS DEMO: Normal █ | Insert | | Visual blinking█\n\ Edge cases to test: empty→suggestions, partial matches, field navigation!"
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 => { AppMode::Edit => {
"🎯 INSERT MODE - Cursor: | (bar)\n\ "🎯 INSERT MODE - Cursor: | (bar)\n\
arrows=move, Ctrl+arrows=words, Backspace/Del=delete\n\ Type to filter suggestions! Tab=show/cycle, Enter=select, Esc=normal\n\
Tab=smart suggestions: domains, companies, skills with fuzzy matching\n\ Test cases: 'r'→Red/Rust, 's'→Software Engineer/Swift, 'c'→Canada/Cherry...\n\
Enter=apply suggestion, Esc=normal mode" Navigation: arrows=move, Ctrl+arrows=words, Home/End=line edges\n\
Try different fields for different suggestion behaviors and timing!"
} }
AppMode::Highlight => { AppMode::Highlight => {
"🎯 VISUAL MODE - Cursor: █ (blinking block)\n\ "🎯 VISUAL MODE - Cursor: █ (blinking block)\n\
hjkl/arrows=extend selection, w/b/e=word selection\n\ Selection: hjkl/arrows=extend, w/b/e=word selection, Esc=normal\n\
Esc=normal" Test multi-character selections across different suggestion field types!"
} }
_ => "🎯 Watch the cursor change automatically! Tab for intelligent suggestions on certain fields!" _ => "🎯 Multi-field suggestions! 5 fields × 8 suggestions each = lots of testing!"
}; };
let help = Paragraph::new(help_text) let help = Paragraph::new(help_text)
.block(Block::default().borders(Borders::ALL).title("🚀 Smart Cursor + Intelligent Suggestions")) .block(Block::default().borders(Borders::ALL).title("🚀 Comprehensive Testing Guide"))
.style(Style::default().fg(Color::Gray)); .style(Style::default().fg(Color::Gray));
f.render_widget(help, chunks[1]); f.render_widget(help, chunks[1]);
@@ -1181,19 +929,27 @@ fn render_status_and_help(
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Print feature status // Print comprehensive demo information
println!("🎯 Smart Cursor + Intelligent Suggestions Demo"); println!("🎯 Multi-Field Suggestions Demo - Perfect for Testing Edge Cases!");
println!("✅ cursor-style feature: ENABLED"); println!("✅ cursor-style feature: ENABLED");
println!("✅ suggestions feature: ENABLED"); println!("✅ suggestions feature: ENABLED");
println!("🚀 Automatic cursor management: ACTIVE"); println!("🚀 Automatic cursor management: ACTIVE");
println!("Intelligent suggestions: ACTIVE"); println!("5 different suggestion types: ACTIVE");
println!("📖 Watch your terminal cursor change based on mode!");
println!(); println!();
println!("🎯 Try these logical suggestions:"); println!("📋 Test These 5 Fields:");
println!(" 📧 Email field: 'user@exa' + Tab → smart domain completion"); println!(" 🍎 Fruits: Apple, Banana, Cherry, Date, Elderberry, Fig, Grape, Honeydew");
println!(" 🏢 Company field: 'tech' + Tab → scored company matches"); println!(" 💼 Jobs: Software Engineer, Product Manager, Data Scientist, UX Designer...");
println!(" 💻 Skills field: 'rust,py' + Tab → intelligent multi-tag skills"); println!(" 💻 Languages: Rust, Python, JavaScript, TypeScript, Go, Java, C++, Swift");
println!(" 🧠 Features: caching, fuzzy matching, scoring, validation"); println!(" 🌍 Countries: USA, Canada, UK, Germany, France, Japan, Australia, Brazil");
println!(" 🎨 Colors: Red, Blue, Green, Yellow, Purple, Orange, Pink, Black");
println!();
println!("🧪 Edge Cases to Test:");
println!(" • Navigation between suggestion/non-suggestion fields");
println!(" • Empty field → Tab → see all suggestions");
println!(" • Partial typing → Tab → filtered suggestions");
println!(" • Different loading times per field (100-300ms)");
println!(" • Field switching while suggestions active");
println!(" • Visual mode selections across suggestion fields");
println!(); println!();
enable_raw_mode()?; enable_raw_mode()?;
@@ -1202,7 +958,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let backend = CrosstermBackend::new(stdout); let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?; let mut terminal = Terminal::new(backend)?;
let data = CursorDemoData::new(); let data = MultiFieldDemoData::new();
let mut editor = AutoCursorFormEditor::new(data); let mut editor = AutoCursorFormEditor::new(data);
// Initialize with normal mode - library automatically sets block cursor // Initialize with normal mode - library automatically sets block cursor
@@ -1229,6 +985,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("{:?}", err); println!("{:?}", err);
} }
println!("🎯 Cursor automatically reset to default! Smart suggestions cache cleared!"); println!("🎯 Multi-field testing complete! Great for finding edge cases!");
Ok(()) Ok(())
} }