suggestions2 only on tab trigger and not automatic
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
// examples/suggestions2.rs
|
// examples/suggestions2.rs
|
||||||
//! Production-ready non-blocking suggestions demonstration
|
//! Production-ready Tab-triggered suggestions demonstration
|
||||||
//!
|
//!
|
||||||
//! This example demonstrates:
|
//! This example demonstrates:
|
||||||
//! - Instant, responsive suggestions dropdown
|
//! - Tab-triggered suggestions dropdown
|
||||||
//! - Non-blocking architecture for real network/database calls
|
//! - Non-blocking architecture for real network/database calls
|
||||||
//! - Multiple suggestion field types
|
//! - Multiple suggestion field types
|
||||||
//! - Professional-grade user experience
|
//! - Professional-grade user experience
|
||||||
@@ -62,7 +62,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: "🚀 Production-Ready Suggestions Demo - Copy this architecture for your app!".to_string(),
|
debug_message: "🚀 Production-Ready Tab-Triggered Suggestions Demo - Copy this architecture for your app!".to_string(),
|
||||||
command_buffer: String::new(),
|
command_buffer: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,12 +232,12 @@ impl<D: DataProvider> AutoCursorFormEditor<D> {
|
|||||||
|
|
||||||
fn enter_edit_mode(&mut self) {
|
fn enter_edit_mode(&mut self) {
|
||||||
self.editor.enter_edit_mode(); // 🎯 Library automatically sets cursor to bar |
|
self.editor.enter_edit_mode(); // 🎯 Library automatically sets cursor to bar |
|
||||||
self.debug_message = "✏️ INSERT MODE - Cursor: Steady Bar |".to_string();
|
self.debug_message = "✏️ INSERT MODE - Cursor: Steady Bar | - Press Tab for suggestions".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enter_append_mode(&mut self) {
|
fn enter_append_mode(&mut self) {
|
||||||
self.editor.enter_append_mode(); // 🎯 Library automatically positions cursor and sets mode
|
self.editor.enter_append_mode(); // 🎯 Library automatically positions cursor and sets mode
|
||||||
self.debug_message = "✏️ INSERT (append) - Cursor: Steady Bar |".to_string();
|
self.debug_message = "✏️ INSERT (append) - Cursor: Steady Bar | - Press Tab for suggestions".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit_edit_mode(&mut self) {
|
fn exit_edit_mode(&mut self) {
|
||||||
@@ -257,7 +257,7 @@ impl<D: DataProvider> AutoCursorFormEditor<D> {
|
|||||||
// === PRODUCTION-READY NON-BLOCKING SUGGESTIONS ===
|
// === PRODUCTION-READY NON-BLOCKING SUGGESTIONS ===
|
||||||
|
|
||||||
/// Trigger suggestions with non-blocking approach (production pattern)
|
/// Trigger suggestions with non-blocking approach (production pattern)
|
||||||
///
|
///
|
||||||
/// This method demonstrates the proper way to integrate suggestions with
|
/// This method demonstrates the proper way to integrate suggestions with
|
||||||
/// real APIs, databases, or any async data source without blocking the UI.
|
/// real APIs, databases, or any async data source without blocking the UI.
|
||||||
async fn trigger_suggestions_async(
|
async fn trigger_suggestions_async(
|
||||||
@@ -289,17 +289,6 @@ impl<D: DataProvider> AutoCursorFormEditor<D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Auto-trigger suggestions for current field (production pattern)
|
|
||||||
///
|
|
||||||
/// Call this after character input, deletion, or field entry to automatically
|
|
||||||
/// show suggestions. Perfect for real-time search-as-you-type functionality.
|
|
||||||
async fn auto_trigger_suggestions(&mut self, provider: &mut ProductionSuggestionsProvider) {
|
|
||||||
let field_index = self.current_field();
|
|
||||||
if self.data_provider().supports_suggestions(field_index) {
|
|
||||||
self.trigger_suggestions_async(provider, field_index).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn suggestions_next(&mut self) {
|
fn suggestions_next(&mut self) {
|
||||||
self.editor.suggestions_next();
|
self.editor.suggestions_next();
|
||||||
}
|
}
|
||||||
@@ -437,15 +426,15 @@ impl DataProvider for ApplicationData {
|
|||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
/// Production-ready suggestions provider
|
/// Production-ready suggestions provider
|
||||||
///
|
///
|
||||||
/// Replace the data sources below with your actual:
|
/// Replace the data sources below with your actual:
|
||||||
/// - REST API calls (reqwest, hyper)
|
/// - REST API calls (reqwest, hyper)
|
||||||
/// - Database queries (sqlx, diesel)
|
/// - Database queries (sqlx, diesel)
|
||||||
/// - Search engines (elasticsearch, algolia)
|
/// - Search engines (elasticsearch, algolia)
|
||||||
/// - Cache lookups (redis, memcached)
|
/// - Cache lookups (redis, memcached)
|
||||||
/// - GraphQL queries
|
/// - GraphQL queries
|
||||||
/// - gRPC services
|
/// - gRPC services
|
||||||
///
|
///
|
||||||
/// The non-blocking architecture works with any async data source.
|
/// The non-blocking architecture works with any async data source.
|
||||||
struct ProductionSuggestionsProvider {
|
struct ProductionSuggestionsProvider {
|
||||||
// Add your API clients, database connections, cache clients here
|
// Add your API clients, database connections, cache clients here
|
||||||
@@ -473,7 +462,7 @@ impl ProductionSuggestionsProvider {
|
|||||||
// .send()
|
// .send()
|
||||||
// .await?;
|
// .await?;
|
||||||
// let fruits: Vec<Fruit> = response.json().await?;
|
// let fruits: Vec<Fruit> = response.json().await?;
|
||||||
|
|
||||||
let fruits = vec![
|
let fruits = vec![
|
||||||
("Apple", "🍎 Crisp and sweet"),
|
("Apple", "🍎 Crisp and sweet"),
|
||||||
("Banana", "🍌 Rich in potassium"),
|
("Banana", "🍌 Rich in potassium"),
|
||||||
@@ -500,7 +489,7 @@ impl ProductionSuggestionsProvider {
|
|||||||
// )
|
// )
|
||||||
// .fetch_all(&self.db_pool)
|
// .fetch_all(&self.db_pool)
|
||||||
// .await?;
|
// .await?;
|
||||||
|
|
||||||
let jobs = vec![
|
let jobs = vec![
|
||||||
("Software Engineer", "👨💻 Build applications"),
|
("Software Engineer", "👨💻 Build applications"),
|
||||||
("Product Manager", "📋 Manage product roadmap"),
|
("Product Manager", "📋 Manage product roadmap"),
|
||||||
@@ -522,7 +511,7 @@ impl ProductionSuggestionsProvider {
|
|||||||
// if let Some(cached_result) = cached {
|
// if let Some(cached_result) = cached {
|
||||||
// return Ok(serde_json::from_str(&cached_result)?);
|
// return Ok(serde_json::from_str(&cached_result)?);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
let languages = vec![
|
let languages = vec![
|
||||||
("Rust", "🦀 Systems programming"),
|
("Rust", "🦀 Systems programming"),
|
||||||
("Python", "🐍 Versatile and popular"),
|
("Python", "🐍 Versatile and popular"),
|
||||||
@@ -545,7 +534,7 @@ impl ProductionSuggestionsProvider {
|
|||||||
// .send()
|
// .send()
|
||||||
// .await?;
|
// .await?;
|
||||||
// let countries: Vec<Country> = response.json().await?;
|
// let countries: Vec<Country> = response.json().await?;
|
||||||
|
|
||||||
let countries = vec![
|
let countries = vec![
|
||||||
("United States", "🇺🇸 North America"),
|
("United States", "🇺🇸 North America"),
|
||||||
("Canada", "🇨🇦 Great neighbors"),
|
("Canada", "🇨🇦 Great neighbors"),
|
||||||
@@ -598,7 +587,7 @@ impl SuggestionsProvider for ProductionSuggestionsProvider {
|
|||||||
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>> {
|
||||||
match field_index {
|
match field_index {
|
||||||
0 => self.get_fruit_suggestions(query).await, // API call
|
0 => self.get_fruit_suggestions(query).await, // API call
|
||||||
1 => self.get_job_suggestions(query).await, // Database query
|
1 => self.get_job_suggestions(query).await, // Database query
|
||||||
2 => self.get_language_suggestions(query).await, // Cache + API
|
2 => self.get_language_suggestions(query).await, // Cache + API
|
||||||
3 => self.get_country_suggestions(query).await, // Geographic API
|
3 => self.get_country_suggestions(query).await, // Geographic API
|
||||||
4 => self.get_color_suggestions(query).await, // Local data
|
4 => self.get_color_suggestions(query).await, // Local data
|
||||||
@@ -607,7 +596,7 @@ impl SuggestionsProvider for ProductionSuggestionsProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Production-ready key handling with non-blocking suggestions
|
/// Production-ready key handling with Tab-triggered suggestions
|
||||||
async fn handle_key_press(
|
async fn handle_key_press(
|
||||||
key: KeyCode,
|
key: KeyCode,
|
||||||
modifiers: KeyModifiers,
|
modifiers: KeyModifiers,
|
||||||
@@ -625,7 +614,7 @@ async fn handle_key_press(
|
|||||||
}
|
}
|
||||||
|
|
||||||
match (mode, key, modifiers) {
|
match (mode, key, modifiers) {
|
||||||
// === NON-BLOCKING SUGGESTIONS HANDLING ===
|
// === TAB-TRIGGERED SUGGESTIONS HANDLING ===
|
||||||
(_, KeyCode::Tab, _) => {
|
(_, KeyCode::Tab, _) => {
|
||||||
if editor.is_suggestions_active() {
|
if editor.is_suggestions_active() {
|
||||||
// Cycle through suggestions
|
// Cycle through suggestions
|
||||||
@@ -677,22 +666,20 @@ async fn handle_key_press(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// === MODE TRANSITIONS WITH AUTO-SUGGESTIONS ===
|
// === MODE TRANSITIONS (NO AUTO-SUGGESTIONS) ===
|
||||||
(AppMode::ReadOnly, KeyCode::Char('i'), _) => {
|
(AppMode::ReadOnly, KeyCode::Char('i'), _) => {
|
||||||
editor.enter_edit_mode();
|
editor.enter_edit_mode();
|
||||||
editor.clear_command_buffer();
|
editor.clear_command_buffer();
|
||||||
// Auto-show suggestions on entering insert mode
|
|
||||||
editor.auto_trigger_suggestions(suggestions_provider).await;
|
|
||||||
}
|
}
|
||||||
(AppMode::ReadOnly, KeyCode::Char('a'), _) => {
|
(AppMode::ReadOnly, KeyCode::Char('a'), _) => {
|
||||||
editor.enter_append_mode();
|
editor.enter_append_mode();
|
||||||
editor.set_debug_message("✏️ INSERT (append) - Cursor: Steady Bar |".to_string());
|
editor.set_debug_message("✏️ INSERT (append) - Cursor: Steady Bar | - Press Tab for suggestions".to_string());
|
||||||
editor.clear_command_buffer();
|
editor.clear_command_buffer();
|
||||||
}
|
}
|
||||||
(AppMode::ReadOnly, KeyCode::Char('A'), _) => {
|
(AppMode::ReadOnly, KeyCode::Char('A'), _) => {
|
||||||
editor.move_line_end();
|
editor.move_line_end();
|
||||||
editor.enter_edit_mode();
|
editor.enter_edit_mode();
|
||||||
editor.set_debug_message("✏️ INSERT (end of line) - Cursor: Steady Bar |".to_string());
|
editor.set_debug_message("✏️ INSERT (end of line) - Cursor: Steady Bar | - Press Tab for suggestions".to_string());
|
||||||
editor.clear_command_buffer();
|
editor.clear_command_buffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -821,16 +808,22 @@ async fn handle_key_press(
|
|||||||
editor.move_line_end();
|
editor.move_line_end();
|
||||||
}
|
}
|
||||||
|
|
||||||
// === DELETE OPERATIONS WITH AUTO-SUGGESTIONS ===
|
// === DELETE OPERATIONS (AUTO-FETCH WHEN SUGGESTIONS ACTIVE) ===
|
||||||
(AppMode::Edit, KeyCode::Backspace, _) => {
|
(AppMode::Edit, KeyCode::Backspace, _) => {
|
||||||
editor.delete_backward()?;
|
editor.delete_backward()?;
|
||||||
// Auto-trigger suggestions after deletion
|
// Auto-fetch only if suggestions are already active (triggered by Tab)
|
||||||
editor.auto_trigger_suggestions(suggestions_provider).await;
|
if editor.is_suggestions_active() {
|
||||||
|
let field_index = editor.current_field();
|
||||||
|
editor.trigger_suggestions_async(suggestions_provider, field_index).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(AppMode::Edit, KeyCode::Delete, _) => {
|
(AppMode::Edit, KeyCode::Delete, _) => {
|
||||||
editor.delete_forward()?;
|
editor.delete_forward()?;
|
||||||
// Auto-trigger suggestions after deletion
|
// Auto-fetch only if suggestions are already active (triggered by Tab)
|
||||||
editor.auto_trigger_suggestions(suggestions_provider).await;
|
if editor.is_suggestions_active() {
|
||||||
|
let field_index = editor.current_field();
|
||||||
|
editor.trigger_suggestions_async(suggestions_provider, field_index).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete operations in normal mode (vim x)
|
// Delete operations in normal mode (vim x)
|
||||||
@@ -843,11 +836,14 @@ async fn handle_key_press(
|
|||||||
editor.set_debug_message("X: deleted character backward".to_string());
|
editor.set_debug_message("X: deleted character backward".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
// === CHARACTER INPUT WITH REAL-TIME SUGGESTIONS ===
|
// === CHARACTER INPUT (AUTO-FETCH WHEN SUGGESTIONS ACTIVE) ===
|
||||||
(AppMode::Edit, KeyCode::Char(c), m) if !m.contains(KeyModifiers::CONTROL) => {
|
(AppMode::Edit, KeyCode::Char(c), m) if !m.contains(KeyModifiers::CONTROL) => {
|
||||||
editor.insert_char(c)?;
|
editor.insert_char(c)?;
|
||||||
// Auto-trigger suggestions after typing - this is the magic!
|
// Auto-fetch only if suggestions are already active (triggered by Tab)
|
||||||
editor.auto_trigger_suggestions(suggestions_provider).await;
|
if editor.is_suggestions_active() {
|
||||||
|
let field_index = editor.current_field();
|
||||||
|
editor.trigger_suggestions_async(suggestions_provider, field_index).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// === DEBUG/INFO COMMANDS ===
|
// === DEBUG/INFO COMMANDS ===
|
||||||
@@ -980,7 +976,7 @@ fn render_status_and_help(
|
|||||||
);
|
);
|
||||||
|
|
||||||
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("🚀 Production-Ready Non-Blocking Suggestions"));
|
.block(Block::default().borders(Borders::ALL).title("🚀 Production-Ready Smart Suggestions (Tab to activate → type to filter)"));
|
||||||
|
|
||||||
f.render_widget(status, chunks[0]);
|
f.render_widget(status, chunks[0]);
|
||||||
|
|
||||||
@@ -992,19 +988,19 @@ fn render_status_and_help(
|
|||||||
Actions: i/a/A=insert, v/V=visual, x/X=delete, ?=info, Enter=next field\n\
|
Actions: i/a/A=insert, v/V=visual, x/X=delete, ?=info, Enter=next field\n\
|
||||||
Integration: Replace data sources with your APIs, databases, caches\n\
|
Integration: Replace data sources with your APIs, databases, caches\n\
|
||||||
Architecture: Non-blocking • Instant UI • Stale protection • Professional UX\n\
|
Architecture: Non-blocking • Instant UI • Stale protection • Professional UX\n\
|
||||||
Tab=suggestions, Enter=select • Ready for: REST, GraphQL, SQL, Redis, etc."
|
🔑 Tab=activate suggestions → type to filter • Enter=select • Ready for: REST, GraphQL, SQL, Redis"
|
||||||
}
|
}
|
||||||
AppMode::Edit => {
|
AppMode::Edit => {
|
||||||
"🚀 INSERT MODE - Type for instant suggestions!\n\
|
"🚀 INSERT MODE - Press Tab to activate suggestions, then type to filter!\n\
|
||||||
Real-time search-as-you-type with non-blocking architecture\n\
|
Tab=activate suggestions • Type/Backspace=filter while active • Enter=select\n\
|
||||||
Perfect for: User search, autocomplete, typeahead, smart suggestions\n\
|
Perfect for: Autocomplete, search dropdowns, data entry assistance\n\
|
||||||
Navigation: arrows=move, Ctrl+arrows=words, Home/End=line edges\n\
|
Navigation: arrows=move, Ctrl+arrows=words, Home/End=line edges\n\
|
||||||
Copy this pattern for production: API calls, database queries, cache lookups"
|
Copy this pattern for production: API calls, database queries, cache lookups"
|
||||||
}
|
}
|
||||||
AppMode::Highlight => {
|
AppMode::Highlight => {
|
||||||
"🚀 VISUAL MODE - Selection with suggestions support\n\
|
"🚀 VISUAL MODE - Selection with suggestions support\n\
|
||||||
Selection: hjkl/arrows=extend, w/b/e=word selection, Esc=normal\n\
|
Selection: hjkl/arrows=extend, w/b/e=word selection, Esc=normal\n\
|
||||||
Professional editor experience with modern autocomplete!"
|
Professional editor experience with Tab-triggered autocomplete!"
|
||||||
}
|
}
|
||||||
_ => "🚀 Copy this suggestions architecture for your production app!"
|
_ => "🚀 Copy this suggestions architecture for your production app!"
|
||||||
};
|
};
|
||||||
@@ -1019,23 +1015,24 @@ 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 production-ready information
|
// Print production-ready information
|
||||||
println!("🚀 Production-Ready Non-Blocking Suggestions Demo");
|
println!("🚀 Production-Ready Tab-Triggered Suggestions Demo");
|
||||||
println!("✅ Instant, responsive UI - no blocking on network/database calls");
|
println!("✅ Press Tab to activate suggestions, then type to filter in real-time");
|
||||||
println!("✅ Professional autocomplete architecture");
|
println!("✅ Professional autocomplete architecture");
|
||||||
println!("✅ Copy this pattern for your production application!");
|
println!("✅ Copy this pattern for your production application!");
|
||||||
println!();
|
println!();
|
||||||
println!("🏗️ Integration Ready For:");
|
println!("🏗️ Integration Ready For:");
|
||||||
println!(" 📡 REST APIs (reqwest, hyper)");
|
println!(" 📡 REST APIs (reqwest, hyper)");
|
||||||
println!(" 🗄️ Databases (sqlx, diesel, mongodb)");
|
println!(" 🗄️ Databases (sqlx, diesel, mongodb)");
|
||||||
println!(" 🔍 Search Engines (elasticsearch, algolia, typesense)");
|
println!(" 🔍 Search Engines (elasticsearch, algolia, typesense)");
|
||||||
println!(" 💾 Caches (redis, memcached)");
|
println!(" 💾 Caches (redis, memcached)");
|
||||||
println!(" 🌐 GraphQL APIs");
|
println!(" 🌐 GraphQL APIs");
|
||||||
println!(" 🔗 gRPC Services");
|
println!(" 🔗 gRPC Services");
|
||||||
println!();
|
println!();
|
||||||
println!("⚡ Key Features:");
|
println!("⚡ Key Features:");
|
||||||
println!(" • Dropdown appears instantly (never waits for network)");
|
println!(" • Press Tab to activate suggestions dropdown");
|
||||||
|
println!(" • Real-time filtering while suggestions are active");
|
||||||
println!(" • Built-in stale result protection");
|
println!(" • Built-in stale result protection");
|
||||||
println!(" • Search-as-you-type with real-time filtering");
|
println!(" • Tab cycles through suggestions");
|
||||||
println!(" • Professional-grade user experience");
|
println!(" • Professional-grade user experience");
|
||||||
println!(" • Easy to integrate with any async data source");
|
println!(" • Easy to integrate with any async data source");
|
||||||
println!();
|
println!();
|
||||||
|
|||||||
Reference in New Issue
Block a user