From 2b08e64db83ca6a63abab9a55d9034d559b76dcf Mon Sep 17 00:00:00 2001 From: Priec Date: Sat, 2 Aug 2025 00:19:45 +0200 Subject: [PATCH] fixed generics --- canvas/src/data_provider.rs | 23 ++++---- canvas/src/editor.rs | 108 ++++++++++++++++-------------------- 2 files changed, 59 insertions(+), 72 deletions(-) diff --git a/canvas/src/data_provider.rs b/canvas/src/data_provider.rs index 3b737f1..aabce75 100644 --- a/canvas/src/data_provider.rs +++ b/canvas/src/data_provider.rs @@ -8,23 +8,23 @@ use async_trait::async_trait; pub trait DataProvider { /// How many fields in the form fn field_count(&self) -> usize; - + /// Get field label/name fn field_name(&self, index: usize) -> &str; - - /// Get field value + + /// Get field value fn field_value(&self, index: usize) -> &str; - + /// Set field value (library calls this when text changes) fn set_field_value(&mut self, index: usize, value: String); - + /// Check if field supports autocomplete (optional) fn supports_autocomplete(&self, _field_index: usize) -> bool { false } - + /// Get display value (for password masking, etc.) - optional - fn display_value(&self, index: usize) -> Option<&str> { + fn display_value(&self, _index: usize) -> Option<&str> { None // Default: use actual value } } @@ -32,16 +32,13 @@ pub trait DataProvider { /// Optional: User implements this for autocomplete data #[async_trait] pub trait AutocompleteProvider { - type SuggestionData: Clone + Send + 'static; - /// Fetch autocomplete suggestions (user's business logic) - async fn fetch_suggestions(&mut self, field_index: usize, query: &str) - -> Result>>; + async fn fetch_suggestions(&mut self, field_index: usize, query: &str) + -> Result>; } #[derive(Debug, Clone)] -pub struct SuggestionItem { - pub data: T, +pub struct SuggestionItem { pub display_text: String, pub value_to_store: String, } diff --git a/canvas/src/editor.rs b/canvas/src/editor.rs index 275463c..4e5b941 100644 --- a/canvas/src/editor.rs +++ b/canvas/src/editor.rs @@ -10,12 +10,12 @@ use crate::canvas::modes::AppMode; pub struct FormEditor { // Library owns all UI state ui_state: EditorState, - + // User owns business data data_provider: D, - + // Autocomplete suggestions (library manages UI, user provides data) - pub(crate) suggestions: Vec>, + pub(crate) suggestions: Vec, } impl FormEditor { @@ -26,31 +26,31 @@ impl FormEditor { suggestions: Vec::new(), } } - + // =================================================================== // READ-ONLY ACCESS: User can fetch UI state // =================================================================== - + /// Get current field index (for user's compatibility) pub fn current_field(&self) -> usize { self.ui_state.current_field() } - + /// Get current cursor position (for user's compatibility) pub fn cursor_position(&self) -> usize { self.ui_state.cursor_position() } - + /// Get current mode (for user's mode-dependent logic) pub fn mode(&self) -> AppMode { self.ui_state.mode() } - + /// Check if autocomplete is active (for user's logic) pub fn is_autocomplete_active(&self) -> bool { self.ui_state.is_autocomplete_active() } - + /// Get current field text (convenience method) pub fn current_text(&self) -> &str { let field_index = self.ui_state.current_field; @@ -60,51 +60,51 @@ impl FormEditor { "" } } - + /// Get reference to UI state for rendering pub fn ui_state(&self) -> &EditorState { &self.ui_state } - + /// Get reference to data provider for rendering pub fn data_provider(&self) -> &D { &self.data_provider } - + /// Get autocomplete suggestions for rendering (read-only) - pub fn suggestions(&self) -> &[SuggestionItem] { + pub fn suggestions(&self) -> &[SuggestionItem] { &self.suggestions } - + // =================================================================== // SYNC OPERATIONS: No async needed for basic editing // =================================================================== - + /// Handle character insertion pub fn insert_char(&mut self, ch: char) -> Result<()> { if self.ui_state.current_mode != AppMode::Edit { return Ok(()); // Ignore in non-edit modes } - + let field_index = self.ui_state.current_field; let cursor_pos = self.ui_state.cursor_pos; - + // Get current text from user let mut current_text = self.data_provider.field_value(field_index).to_string(); - + // Insert character current_text.insert(cursor_pos, ch); - + // Update user's data self.data_provider.set_field_value(field_index, current_text); - + // Update library's UI state self.ui_state.cursor_pos += 1; self.ui_state.ideal_cursor_column = self.ui_state.cursor_pos; - + Ok(()) } - + /// Handle cursor movement pub fn move_left(&mut self) { if self.ui_state.cursor_pos > 0 { @@ -112,7 +112,7 @@ impl FormEditor { self.ui_state.ideal_cursor_column = self.ui_state.cursor_pos; } } - + pub fn move_right(&mut self) { let current_text = self.current_text(); let max_pos = if self.ui_state.current_mode == AppMode::Edit { @@ -120,111 +120,101 @@ impl FormEditor { } else { current_text.len().saturating_sub(1) // ReadOnly: stay in bounds }; - + if self.ui_state.cursor_pos < max_pos { self.ui_state.cursor_pos += 1; self.ui_state.ideal_cursor_column = self.ui_state.cursor_pos; } } - + /// Handle field navigation pub fn move_to_next_field(&mut self) { let field_count = self.data_provider.field_count(); let next_field = (self.ui_state.current_field + 1) % field_count; self.ui_state.move_to_field(next_field, field_count); - + // Clamp cursor to new field let current_text = self.current_text(); let max_pos = current_text.len(); self.ui_state.set_cursor( - self.ui_state.ideal_cursor_column, - max_pos, + self.ui_state.ideal_cursor_column, + max_pos, self.ui_state.current_mode == AppMode::Edit ); } - + /// Change mode (for vim compatibility) pub fn set_mode(&mut self, mode: AppMode) { self.ui_state.current_mode = mode; - + // Clear autocomplete when changing modes if mode != AppMode::Edit { self.ui_state.deactivate_autocomplete(); } } - + // =================================================================== // ASYNC OPERATIONS: Only autocomplete needs async // =================================================================== - + /// Trigger autocomplete (async because it fetches data) - pub async fn trigger_autocomplete(&mut self, provider: &mut A) -> Result<()> - where + pub async fn trigger_autocomplete(&mut self, provider: &mut A) -> Result<()> + where A: AutocompleteProvider, - A::SuggestionData: std::fmt::Debug, // Change from Display to Debug { let field_index = self.ui_state.current_field; - + if !self.data_provider.supports_autocomplete(field_index) { return Ok(()); } - + // Activate autocomplete UI self.ui_state.activate_autocomplete(field_index); - - // Fetch suggestions from user + + // Fetch suggestions from user (no conversion needed!) let query = self.current_text(); - let suggestions = provider.fetch_suggestions(field_index, query).await?; - - // Convert to library's format (could be avoided with better generics) - self.suggestions = suggestions.into_iter() - .map(|item| SuggestionItem { - data: format!("{:?}", item.data), // Use Debug formatting instead - display_text: item.display_text, - value_to_store: item.value_to_store, - }) - .collect(); - + self.suggestions = provider.fetch_suggestions(field_index, query).await?; + // Update UI state self.ui_state.autocomplete.is_loading = false; if !self.suggestions.is_empty() { self.ui_state.autocomplete.selected_index = Some(0); } - + Ok(()) } - + /// Navigate autocomplete suggestions pub fn autocomplete_next(&mut self) { if !self.ui_state.autocomplete.is_active || self.suggestions.is_empty() { return; } - + let current = self.ui_state.autocomplete.selected_index.unwrap_or(0); let next = (current + 1) % self.suggestions.len(); self.ui_state.autocomplete.selected_index = Some(next); } - + /// Apply selected autocomplete suggestion pub fn apply_autocomplete(&mut self) -> Option { if let Some(selected_index) = self.ui_state.autocomplete.selected_index { if let Some(suggestion) = self.suggestions.get(selected_index).cloned() { let field_index = self.ui_state.current_field; - + // Apply to user's data self.data_provider.set_field_value( - field_index, + field_index, suggestion.value_to_store.clone() ); - + // Update cursor position self.ui_state.cursor_pos = suggestion.value_to_store.len(); self.ui_state.ideal_cursor_column = self.ui_state.cursor_pos; - + // Close autocomplete self.ui_state.deactivate_autocomplete(); self.suggestions.clear(); - + return Some(suggestion.display_text); } }