fixing more code refactorization

This commit is contained in:
filipriec
2025-08-10 16:10:45 +02:00
parent f09e476bb6
commit b364a6606d
3 changed files with 320 additions and 223 deletions

View File

@@ -63,6 +63,10 @@ impl<D: DataProvider> AutoCursorFormEditor<D> {
} }
} }
fn close_suggestions(&mut self) {
self.editor.close_suggestions();
}
// === COMMAND BUFFER HANDLING === // === COMMAND BUFFER HANDLING ===
fn clear_command_buffer(&mut self) { fn clear_command_buffer(&mut self) {
@@ -223,10 +227,6 @@ impl<D: DataProvider> AutoCursorFormEditor<D> {
self.editor.open_suggestions(field_index); self.editor.open_suggestions(field_index);
} }
fn close_suggestions(&mut self) {
self.editor.close_suggestions();
}
// === MODE TRANSITIONS WITH AUTOMATIC CURSOR MANAGEMENT === // === MODE TRANSITIONS WITH AUTOMATIC CURSOR MANAGEMENT ===
fn enter_edit_mode(&mut self) { fn enter_edit_mode(&mut self) {
@@ -314,7 +314,8 @@ impl<D: DataProvider> AutoCursorFormEditor<D> {
} }
fn current_text(&self) -> &str { fn current_text(&self) -> &str {
self.editor.current_text() let field_index = self.editor.current_field();
self.editor.data_provider().field_value(field_index)
} }
fn data_provider(&self) -> &D { fn data_provider(&self) -> &D {
@@ -671,7 +672,6 @@ 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.close_suggestions(); // ⬅ close dropdown
editor.move_down(); editor.move_down();
let field_names = ["Fruit", "Job", "Language", "Country", "Color"]; let field_names = ["Fruit", "Job", "Language", "Country", "Color"];
let field_name = field_names.get(editor.current_field()).unwrap_or(&"Field"); let field_name = field_names.get(editor.current_field()).unwrap_or(&"Field");
@@ -680,7 +680,6 @@ async fn handle_key_press(
} }
(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.close_suggestions(); // ⬅ close dropdown
editor.move_up(); editor.move_up();
let field_names = ["Fruit", "Job", "Language", "Country", "Color"]; let field_names = ["Fruit", "Job", "Language", "Country", "Color"];
let field_name = field_names.get(editor.current_field()).unwrap_or(&"Field"); let field_name = field_names.get(editor.current_field()).unwrap_or(&"Field");
@@ -751,11 +750,9 @@ async fn handle_key_press(
editor.move_right(); editor.move_right();
} }
(AppMode::Edit, KeyCode::Up, _) => { (AppMode::Edit, KeyCode::Up, _) => {
editor.close_suggestions();
editor.move_up(); editor.move_up();
} }
(AppMode::Edit, KeyCode::Down, _) => { (AppMode::Edit, KeyCode::Down, _) => {
editor.close_suggestions();
editor.move_down(); editor.move_down();
} }
(AppMode::Edit, KeyCode::Home, _) => { (AppMode::Edit, KeyCode::Home, _) => {

View File

@@ -1,9 +1,10 @@
// examples/validation_5.rs // examples/validation_5.rs
//! Enhanced Feature 5: Comprehensive external validation (UI-only) demo with Feature 4 integration //! Enhanced Feature 5: Comprehensive external validation (UI-only) demo with automatic validation
//! //!
//! Demonstrates: //! Demonstrates:
//! - Multiple external validation types: PSC lookup, email domain check, username availability, //! - Multiple external validation types: PSC lookup, email domain check, username availability,
//! API key validation, credit card verification //! API key validation, credit card verification
//! - AUTOMATIC validation on field transitions (arrows, Tab, Esc)
//! - Async validation simulation with realistic delays //! - Async validation simulation with realistic delays
//! - Validation caching and debouncing //! - Validation caching and debouncing
//! - Progressive validation (local → remote) //! - Progressive validation (local → remote)
@@ -15,7 +16,8 @@
//! Controls: //! Controls:
//! - i/a: insert/append //! - i/a: insert/append
//! - Esc: exit edit mode (triggers validation on configured fields) //! - Esc: exit edit mode (triggers validation on configured fields)
//! - Tab/Shift+Tab: next/prev field (triggers validation) //! - Tab/Shift+Tab: next/prev field (triggers validation automatically)
//! - Arrow keys: move between fields (triggers validation automatically)
//! - v: manually trigger validation of current field //! - v: manually trigger validation of current field
//! - V: validate all fields //! - V: validate all fields
//! - c: clear external validation state for current field //! - c: clear external validation state for current field
@@ -37,7 +39,7 @@ compile_error!(
); );
use std::io; use std::io;
use std::sync::Arc; use std::sync::{Arc, Mutex};
use std::collections::HashMap; use std::collections::HashMap;
use std::time::{Instant, Duration}; use std::time::{Instant, Duration};
@@ -546,16 +548,15 @@ impl DataProvider for ValidationDemoData {
} }
} }
/// Enhanced editor with comprehensive external validation management /// Enhanced editor with automatic external validation management
struct ValidationDemoEditor<D: DataProvider> { struct ValidationDemoEditor<D: DataProvider> {
editor: FormEditor<D>, editor: FormEditor<D>,
services: ValidationServices, services: Arc<Mutex<ValidationServices>>,
validation_history: Vec<(usize, String, ValidationResult)>, validation_history: Vec<(usize, String, ValidationResult)>,
debug_message: String, debug_message: String,
show_history: bool, show_history: bool,
example_mode: usize, example_mode: usize,
validation_enabled: bool, validation_enabled: bool,
auto_validate: bool,
validation_stats: HashMap<usize, (u32, Duration)>, // field -> (count, total_time) validation_stats: HashMap<usize, (u32, Duration)>, // field -> (count, total_time)
} }
@@ -564,15 +565,70 @@ impl<D: DataProvider> ValidationDemoEditor<D> {
let mut editor = FormEditor::new(data_provider); let mut editor = FormEditor::new(data_provider);
editor.set_validation_enabled(true); editor.set_validation_enabled(true);
let services = Arc::new(Mutex::new(ValidationServices::new()));
let services_for_cb = Arc::clone(&services);
let services_for_history = Arc::clone(&services);
// Create a history tracker that we'll share between callback and editor
let validation_history: Arc<Mutex<Vec<(usize, String, ValidationResult)>>> = Arc::new(Mutex::new(Vec::new()));
let history_for_cb = Arc::clone(&validation_history);
// Library-level automatic external validation on field transitions
editor.set_external_validation_callback(move |field_idx, text| {
let mut svc = services_for_cb.lock().unwrap();
let validation_type = match field_idx {
0 => "PSC Lookup",
1 => "Email Domain Check",
2 => "Username Availability",
3 => "API Key Auth",
4 => "Credit Card Verify",
_ => "Unknown",
}.to_string();
let start_time = Instant::now();
let validation_result = match field_idx {
0 => svc.validate_psc(text),
1 => svc.validate_email(text),
2 => svc.validate_username(text),
3 => svc.validate_api_key(text),
4 => svc.validate_credit_card(text),
_ => ExternalValidationState::NotValidated,
};
// Record in shared history (if we can lock it)
if let Ok(mut history) = history_for_cb.try_lock() {
let duration = start_time.elapsed();
let result = ValidationResult {
state: validation_result.clone(),
started_at: start_time,
completed_at: Some(Instant::now()),
validation_type,
cached: false, // We could enhance this by checking if it was from cache
};
history.push((field_idx, text.to_string(), result));
// Limit history size
if history.len() > 50 {
history.remove(0);
}
}
validation_result
});
Self { Self {
editor, editor,
services: ValidationServices::new(), services,
validation_history: Vec::new(), validation_history: Vec::new(),
debug_message: "🧪 Enhanced External Validation Demo - Multiple validation types with rich scenarios!".to_string(), debug_message:
"🧪 Enhanced External Validation Demo - Automatic validation on field transitions!"
.to_string(),
show_history: false, show_history: false,
example_mode: 0, example_mode: 0,
validation_enabled: true, validation_enabled: true,
auto_validate: true,
validation_stats: HashMap::new(), validation_stats: HashMap::new(),
} }
} }
@@ -608,7 +664,7 @@ impl<D: DataProvider> ValidationDemoEditor<D> {
self.current_field() < 5 self.current_field() < 5
} }
/// Trigger external validation for specific field /// Trigger external validation for specific field (manual validation)
fn validate_field(&mut self, field_index: usize) { fn validate_field(&mut self, field_index: usize) {
if !self.validation_enabled || field_index >= 5 { if !self.validation_enabled || field_index >= 5 {
return; return;
@@ -634,14 +690,17 @@ impl<D: DataProvider> ValidationDemoEditor<D> {
let mut result = ValidationResult::new(validation_type.clone()); let mut result = ValidationResult::new(validation_type.clone());
// Perform validation (in real app, this would be async) // Perform validation using the shared services
let validation_result = match field_index { let validation_result = {
0 => self.services.validate_psc(&raw_value), let mut svc = self.services.lock().unwrap();
1 => self.services.validate_email(&raw_value), match field_index {
2 => self.services.validate_username(&raw_value), 0 => svc.validate_psc(&raw_value),
3 => self.services.validate_api_key(&raw_value), 1 => svc.validate_email(&raw_value),
4 => self.services.validate_credit_card(&raw_value), 2 => svc.validate_username(&raw_value),
3 => svc.validate_api_key(&raw_value),
4 => svc.validate_credit_card(&raw_value),
_ => ExternalValidationState::NotValidated, _ => ExternalValidationState::NotValidated,
}
}; };
result = result.complete(validation_result.clone()); result = result.complete(validation_result.clone());
@@ -665,7 +724,7 @@ impl<D: DataProvider> ValidationDemoEditor<D> {
let duration_ms = result.duration().as_millis(); let duration_ms = result.duration().as_millis();
let cached_text = if result.cached { " (cached)" } else { "" }; let cached_text = if result.cached { " (cached)" } else { "" };
self.debug_message = format!( self.debug_message = format!(
"🔍 {} validation completed in {}ms{}", "🔍 {} validation completed in {}ms{} (manual)",
validation_type, duration_ms, cached_text validation_type, duration_ms, cached_text
); );
} }
@@ -675,7 +734,7 @@ impl<D: DataProvider> ValidationDemoEditor<D> {
for i in 0..field_count { for i in 0..field_count {
self.validate_field(i); self.validate_field(i);
} }
self.debug_message = "🔍 All fields validated".to_string(); self.debug_message = "🔍 All fields validated manually".to_string();
} }
fn clear_validation_state(&mut self, field_index: Option<usize>) { fn clear_validation_state(&mut self, field_index: Option<usize>) {
@@ -690,7 +749,9 @@ impl<D: DataProvider> ValidationDemoEditor<D> {
} }
self.validation_history.clear(); self.validation_history.clear();
self.validation_stats.clear(); self.validation_stats.clear();
self.services.clear_cache(); if let Ok(mut svc) = self.services.lock() {
svc.clear_cache();
}
self.debug_message = "🧹 Cleared all validation states and cache".to_string(); self.debug_message = "🧹 Cleared all validation states and cache".to_string();
} }
} }
@@ -720,7 +781,7 @@ impl<D: DataProvider> ValidationDemoEditor<D> {
fn cycle_examples(&mut self) { fn cycle_examples(&mut self) {
let examples = [ let examples = [
// Valid examples // Valid examples
vec!["01001", "user@gmail.com", "alice_dev", "valid_api_key_123456789012345", "4000123456789012", "Valid data"], vec!["01001", "user@gmail.com", "alice_dev_new", "valid_api_key_123456789012345", "4000123456789012", "Valid data"],
// Invalid examples // Invalid examples
vec!["00000", "invalid-email", "admin", "short_key", "0000000000000000", "Invalid data"], vec!["00000", "invalid-email", "admin", "short_key", "0000000000000000", "Invalid data"],
// Warning examples // Warning examples
@@ -739,7 +800,7 @@ impl<D: DataProvider> ValidationDemoEditor<D> {
} }
let mode_names = ["Valid Examples", "Invalid Examples", "Warning Cases", "Mixed Scenarios"]; let mode_names = ["Valid Examples", "Invalid Examples", "Warning Cases", "Mixed Scenarios"];
self.debug_message = format!("📋 Loaded: {}", mode_names[self.example_mode]); self.debug_message = format!("📋 Loaded: {} (navigate to trigger validation)", mode_names[self.example_mode]);
} }
fn get_validation_summary(&self) -> String { fn get_validation_summary(&self) -> String {
@@ -758,7 +819,7 @@ impl<D: DataProvider> ValidationDemoEditor<D> {
self.editor.ui_state().validation_state().get_external_validation(field_index) self.editor.ui_state().validation_state().get_external_validation(field_index)
} }
// Editor pass-through methods // Editor pass-through methods - simplified since library handles automatic validation
fn enter_edit_mode(&mut self) { fn enter_edit_mode(&mut self) {
self.editor.enter_edit_mode(); self.editor.enter_edit_mode();
let rules = self.field_validation_rules(); let rules = self.field_validation_rules();
@@ -766,34 +827,36 @@ impl<D: DataProvider> ValidationDemoEditor<D> {
} }
fn exit_edit_mode(&mut self) { fn exit_edit_mode(&mut self) {
let current_field = self.current_field();
self.editor.exit_edit_mode(); self.editor.exit_edit_mode();
// Library automatically validates on exit, no manual call needed
// Auto-validate on blur if enabled self.debug_message = format!("🔒 NORMAL - Cursor: Steady Block █ - {} (auto-validated)", self.field_type());
if self.auto_validate && self.has_external_validation() {
self.validate_field(current_field);
}
self.debug_message = format!("🔒 NORMAL - Cursor: Steady Block █ - {}", self.field_type());
} }
fn next_field(&mut self) { fn next_field(&mut self) {
let current = self.current_field();
if let Ok(()) = self.editor.next_field() { if let Ok(()) = self.editor.next_field() {
if self.auto_validate && current < 5 { // Library triggers external validation automatically via transition_to_field()
self.validate_field(current); self.debug_message = "➡ Next field (auto-validation triggered by library)".to_string();
}
self.debug_message = "➡ Next field".to_string();
} }
} }
fn prev_field(&mut self) { fn prev_field(&mut self) {
let current = self.current_field();
if let Ok(()) = self.editor.prev_field() { if let Ok(()) = self.editor.prev_field() {
if self.auto_validate && current < 5 { // Library triggers external validation automatically via transition_to_field()
self.validate_field(current); self.debug_message = "⬅ Previous field (auto-validation triggered by library)".to_string();
} }
self.debug_message = "⬅ Previous field".to_string(); }
fn move_up(&mut self) {
if let Ok(()) = self.editor.move_up() {
// Library triggers external validation automatically via transition_to_field()
self.debug_message = "⬆ Move up (auto-validation triggered by library)".to_string();
}
}
fn move_down(&mut self) {
if let Ok(()) = self.editor.move_down() {
// Library triggers external validation automatically via transition_to_field()
self.debug_message = "⬇ Move down (auto-validation triggered by library)".to_string();
} }
} }
@@ -839,16 +902,25 @@ fn run_app<B: Backend>(
}, },
(_, KeyCode::Esc, _) => editor.exit_edit_mode(), (_, KeyCode::Esc, _) => editor.exit_edit_mode(),
// Movement - cursor within field // Movement - these now trigger automatic validation via the library!
(_, KeyCode::Left, _) | (AppMode::ReadOnly, KeyCode::Char('h'), _) => { let _ = editor.editor.move_left(); }, (_, KeyCode::Left, _) | (AppMode::ReadOnly, KeyCode::Char('h'), _) => {
(_, KeyCode::Right, _) | (AppMode::ReadOnly, KeyCode::Char('l'), _) => { let _ = editor.editor.move_right(); }, let _ = editor.editor.move_left();
(_, KeyCode::Up, _) | (AppMode::ReadOnly, KeyCode::Char('k'), _) => { let _ = editor.editor.move_up(); }, },
(_, KeyCode::Down, _) | (AppMode::ReadOnly, KeyCode::Char('j'), _) => { let _ = editor.editor.move_down(); }, (_, KeyCode::Right, _) | (AppMode::ReadOnly, KeyCode::Char('l'), _) => {
// Field switching let _ = editor.editor.move_right();
},
(_, KeyCode::Up, _) | (AppMode::ReadOnly, KeyCode::Char('k'), _) => {
editor.move_up(); // Use wrapper to get debug message
},
(_, KeyCode::Down, _) | (AppMode::ReadOnly, KeyCode::Char('j'), _) => {
editor.move_down(); // Use wrapper to get debug message
},
// Field switching - these trigger automatic validation via the library!
(_, KeyCode::Tab, _) => editor.next_field(), (_, KeyCode::Tab, _) => editor.next_field(),
(_, KeyCode::BackTab, _) => editor.prev_field(), (_, KeyCode::BackTab, _) => editor.prev_field(),
// Validation commands (ONLY in ReadOnly mode) // Manual validation commands (ONLY in ReadOnly mode)
(AppMode::ReadOnly, KeyCode::Char('v'), _) => { (AppMode::ReadOnly, KeyCode::Char('v'), _) => {
let field = editor.current_field(); let field = editor.current_field();
editor.validate_field(field); editor.validate_field(field);
@@ -868,8 +940,8 @@ fn run_app<B: Backend>(
// Editing // Editing
(AppMode::Edit, KeyCode::Left, _) => { let _ = editor.editor.move_left(); }, (AppMode::Edit, KeyCode::Left, _) => { let _ = editor.editor.move_left(); },
(AppMode::Edit, KeyCode::Right, _) => { let _ = editor.editor.move_right(); }, (AppMode::Edit, KeyCode::Right, _) => { let _ = editor.editor.move_right(); },
(AppMode::Edit, KeyCode::Up, _) => { let _ = editor.editor.move_up(); }, (AppMode::Edit, KeyCode::Up, _) => { editor.move_up(); },
(AppMode::Edit, KeyCode::Down, _) => { let _ = editor.editor.move_down(); }, (AppMode::Edit, KeyCode::Down, _) => { editor.move_down(); },
(AppMode::Edit, KeyCode::Char(c), m) if !m.contains(KeyModifiers::CONTROL) => { (AppMode::Edit, KeyCode::Char(c), m) if !m.contains(KeyModifiers::CONTROL) => {
let _ = editor.insert_char(c); let _ = editor.insert_char(c);
}, },
@@ -922,19 +994,18 @@ fn render_validation_panel(
let summary = editor.get_validation_summary(); let summary = editor.get_validation_summary();
let status_text = format!( let status_text = format!(
"-- {} -- {} | {} | Auto: {} | View: {}", "-- {} -- {} | {} | View: {}",
mode_text, mode_text,
editor.debug_message, editor.debug_message,
summary, summary,
if editor.auto_validate { "ON" } else { "OFF" },
if editor.show_history { "HISTORY" } else { "STATUS" } if editor.show_history { "HISTORY" } else { "STATUS" }
); );
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("🧪 External Validation Demo")); .block(Block::default().borders(Borders::ALL).title("🧪 Automatic External Validation Demo"));
f.render_widget(status, chunks[0]); f.render_widget(status, chunks[0]);
// Validation states for all fields - FIXED: render each field on its own line // Validation states for all fields - render each field on its own line
let mut field_lines: Vec<Line> = Vec::new(); let mut field_lines: Vec<Line> = Vec::new();
for i in 0..editor.data_provider().field_count() { for i in 0..editor.data_provider().field_count() {
let field_name = editor.data_provider().field_name(i); let field_name = editor.data_provider().field_name(i);
@@ -969,9 +1040,8 @@ fn render_validation_panel(
field_lines.push(field_line); field_lines.push(field_line);
} }
// Use Vec<Line> to avoid a single long line overflowing
let validation_states = Paragraph::new(field_lines) let validation_states = Paragraph::new(field_lines)
.block(Block::default().borders(Borders::ALL).title("🔍 Validation States")); .block(Block::default().borders(Borders::ALL).title("🔍 Validation States (Library Auto-triggered)"));
f.render_widget(validation_states, chunks[1]); f.render_widget(validation_states, chunks[1]);
// History or Help panel // History or Help panel
@@ -1014,36 +1084,36 @@ fn render_validation_panel(
.collect(); .collect();
let history = List::new(recent_history) let history = List::new(recent_history)
.block(Block::default().borders(Borders::ALL).title("📜 Validation History (recent 5)")); .block(Block::default().borders(Borders::ALL).title("📜 Auto-Validation History (recent 5)"));
f.render_widget(history, chunks[2]); f.render_widget(history, chunks[2]);
} else { } else {
let help_text = match editor.mode() { let help_text = match editor.mode() {
AppMode::ReadOnly => { AppMode::ReadOnly => {
"🎯 CURSOR-STYLE: Normal █ | Insert |\n\ "🎯 FULLY AUTOMATIC VALIDATION: Library handles all validation on field transitions!\n\
🧪 EXTERNAL VALIDATION DEMO - Multiple validation types with async simulation\n\ 🧪 EXTERNAL VALIDATION DEMO - No manual triggers needed, just navigate!\n\
\n\ \n\
Commands: v=validate current, V=validate all, c=clear current, C=clear all\n\ 🚀 AUTOMATIC: Arrow keys, Tab, and Esc trigger validation automatically\n\
e=cycle examples, r=toggle history, h=field help, F1=toggle validation\n\ Manual: v=validate current, V=validate all, c=clear current, C=clear all\n\
Movement: Tab/Shift+Tab=switch fields, i/a=insert/append, Esc=exit edit\n\ Controls: e=cycle examples, r=toggle history, h=field help, F1=toggle validation\n\
\n\ \n\
Try different values to see validation in action!" Just load examples and navigate - validation happens automatically!"
} }
AppMode::Edit => { AppMode::Edit => {
"🎯 INSERT MODE - Cursor: | (bar)\n\ "🎯 INSERT MODE - Cursor: | (bar)\n\
✏️ Type to see validation on field blur\n\ ✏️ Type to edit field content\n\
\n\ \n\
Current field validation will trigger when you:\n\ 🚀 AUTOMATIC: Library validates when you leave this field via:\n\
• Press Esc (exit edit mode)\n\ • Press Esc (exit edit mode)\n\
• Press Tab (move to next field)\n\ • Press Tab/Shift+Tab (move between fields)\n\
• Press 'v' manually\n\ • Press arrow keys (Up/Down move between fields)\n\
\n\ \n\
Esc=exit edit, arrows=navigate, Backspace/Del=delete" Esc=exit edit, arrows=navigate, Backspace/Del=delete"
} }
_ => "🧪 Enhanced External Validation Demo" _ => "🧪 Enhanced Fully Automatic External Validation Demo"
}; };
let help = Paragraph::new(help_text) let help = Paragraph::new(help_text)
.block(Block::default().borders(Borders::ALL).title("🚀 External Validation Features")) .block(Block::default().borders(Borders::ALL).title("🚀 Fully Automatic External Validation"))
.style(Style::default().fg(Color::Gray)) .style(Style::default().fg(Color::Gray))
.wrap(Wrap { trim: true }); .wrap(Wrap { trim: true });
f.render_widget(help, chunks[2]); f.render_widget(help, chunks[2]);
@@ -1051,16 +1121,19 @@ fn render_validation_panel(
} }
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("🧪 Enhanced External Validation Demo (Feature 5)"); println!("🧪 Enhanced Fully Automatic External Validation Demo (Feature 5)");
println!("✅ validation feature: ENABLED"); println!("✅ validation feature: ENABLED");
println!("✅ gui feature: ENABLED"); println!("✅ gui feature: ENABLED");
println!("✅ cursor-style feature: ENABLED"); println!("✅ cursor-style feature: ENABLED");
println!("🚀 NEW: Library handles all automatic validation!");
println!("🧪 Enhanced features:"); println!("🧪 Enhanced features:");
println!(" • 5 different external validation types with realistic scenarios"); println!(" • 5 different external validation types with realistic scenarios");
println!(" • LIBRARY-LEVEL automatic validation on all field transitions");
println!(" • Validation caching and performance metrics"); println!(" • Validation caching and performance metrics");
println!(" • Comprehensive validation history and error handling"); println!(" • Comprehensive validation history and error handling");
println!(" • Multiple example datasets for testing edge cases"); println!(" • Multiple example datasets for testing edge cases");
println!(" • Progressive validation patterns (local + remote simulation)"); println!(" • Progressive validation patterns (local + remote simulation)");
println!(" • NO manual validation calls needed - library handles everything!");
println!(); println!();
enable_raw_mode()?; enable_raw_mode()?;
@@ -1092,11 +1165,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("{:?}", err); println!("{:?}", err);
} }
println!("🧪 Enhanced external validation demo completed!"); println!("🧪 Enhanced fully automatic external validation demo completed!");
println!("🏆 You experienced comprehensive external validation with:"); println!("🏆 You experienced library-level automatic external validation with:");
println!(" • Multiple validation services (PSC, Email, Username, API Key, Credit Card)"); println!(" • Multiple validation services (PSC, Email, Username, API Key, Credit Card)");
println!(" • AUTOMATIC validation handled entirely by the library");
println!(" • Realistic async validation simulation with caching"); println!(" • Realistic async validation simulation with caching");
println!(" • Comprehensive error handling and user feedback"); println!(" • Comprehensive error handling and user feedback");
println!(" • Performance metrics and validation history tracking"); println!(" • Performance metrics and validation history tracking");
println!(" • Zero manual validation calls needed!");
Ok(()) Ok(())
} }

View File

@@ -23,7 +23,13 @@ pub struct FormEditor<D: DataProvider> {
pub(crate) suggestions: Vec<SuggestionItem>, pub(crate) suggestions: Vec<SuggestionItem>,
#[cfg(feature = "validation")] #[cfg(feature = "validation")]
external_validation_callback: Option<Box<dyn FnMut(usize, &str) + Send + Sync>>, external_validation_callback: Option<
Box<
dyn FnMut(usize, &str) -> crate::validation::ExternalValidationState
+ Send
+ Sync,
>,
>,
} }
impl<D: DataProvider> FormEditor<D> { impl<D: DataProvider> FormEditor<D> {
@@ -59,7 +65,7 @@ impl<D: DataProvider> FormEditor<D> {
} }
/// Get current field text (convenience method) /// Get current field text (convenience method)
fn current_text(&self) -> &str { pub fn current_text(&self) -> &str {
// Convenience wrapper, kept for compatibility with existing code // Convenience wrapper, kept for compatibility with existing code
let field_index = self.ui_state.current_field; let field_index = self.ui_state.current_field;
if field_index < self.data_provider.field_count() { if field_index < self.data_provider.field_count() {
@@ -208,7 +214,10 @@ impl<D: DataProvider> FormEditor<D> {
#[cfg(feature = "validation")] #[cfg(feature = "validation")]
pub fn set_external_validation_callback<F>(&mut self, callback: F) pub fn set_external_validation_callback<F>(&mut self, callback: F)
where where
F: FnMut(usize, &str) + Send + Sync + 'static, F: FnMut(usize, &str) -> crate::validation::ExternalValidationState
+ Send
+ Sync
+ 'static,
{ {
self.external_validation_callback = Some(Box::new(callback)); self.external_validation_callback = Some(Box::new(callback));
} }
@@ -373,9 +382,10 @@ impl<D: DataProvider> FormEditor<D> {
// Trigger external validation state // Trigger external validation state
self.set_external_validation(prev_field, crate::validation::ExternalValidationState::Validating); self.set_external_validation(prev_field, crate::validation::ExternalValidationState::Validating);
// Invoke external callback if registered // Invoke external callback if registered and set final state
if let Some(cb) = self.external_validation_callback.as_mut() { if let Some(cb) = self.external_validation_callback.as_mut() {
cb(prev_field, &text); let final_state = cb(prev_field, &text);
self.set_external_validation(prev_field, final_state);
} }
} }
} }
@@ -399,6 +409,9 @@ impl<D: DataProvider> FormEditor<D> {
self.ui_state.current_mode == AppMode::Edit, self.ui_state.current_mode == AppMode::Edit,
); );
// Automatically close suggestions on field switch
self.close_suggestions();
Ok(()) Ok(())
} }
@@ -785,8 +798,9 @@ impl<D: DataProvider> FormEditor<D> {
); );
// Update cursor position // Update cursor position
self.ui_state.cursor_pos = suggestion.value_to_store.len(); let char_len = suggestion.value_to_store.chars().count();
self.ui_state.ideal_cursor_column = self.ui_state.cursor_pos; self.ui_state.cursor_pos = char_len;
self.ui_state.ideal_cursor_column = char_len;
// Close suggestions // Close suggestions
self.ui_state.deactivate_suggestions(); self.ui_state.deactivate_suggestions();
@@ -811,16 +825,6 @@ impl<D: DataProvider> FormEditor<D> {
// MOVEMENT METHODS (keeping existing implementations) // MOVEMENT METHODS (keeping existing implementations)
// =================================================================== // ===================================================================
/// Move to previous field (vim k / up arrow)
pub fn move_up_only(&mut self) -> Result<()> {
self.move_up()
}
/// Move to next field (vim j / down arrow)
pub fn move_down_only(&mut self) -> Result<()> {
self.move_down()
}
/// Move to first line (vim gg) /// Move to first line (vim gg)
pub fn move_first_line(&mut self) -> Result<()> { pub fn move_first_line(&mut self) -> Result<()> {
self.transition_to_field(0) self.transition_to_field(0)
@@ -1165,6 +1169,27 @@ impl<D: DataProvider> FormEditor<D> {
} }
} }
// Trigger external validation on blur/exit edit mode
#[cfg(feature = "validation")]
{
let field_index = self.ui_state.current_field;
if let Some(cfg) = self.ui_state.validation.get_field_config(field_index) {
if cfg.external_validation_enabled {
let text = self.current_text().to_string();
if !text.is_empty() {
self.set_external_validation(
field_index,
crate::validation::ExternalValidationState::Validating,
);
if let Some(cb) = self.external_validation_callback.as_mut() {
let final_state = cb(field_index, &text);
self.set_external_validation(field_index, final_state);
}
}
}
}
}
self.set_mode(AppMode::ReadOnly); self.set_mode(AppMode::ReadOnly);
// Deactivate suggestions when exiting edit mode // Deactivate suggestions when exiting edit mode
self.ui_state.deactivate_suggestions(); self.ui_state.deactivate_suggestions();