fixed generics

This commit is contained in:
Priec
2025-08-02 00:19:45 +02:00
parent 643db8e586
commit 2b08e64db8
2 changed files with 59 additions and 72 deletions

View File

@@ -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<Vec<SuggestionItem<Self::SuggestionData>>>;
async fn fetch_suggestions(&mut self, field_index: usize, query: &str)
-> Result<Vec<SuggestionItem>>;
}
#[derive(Debug, Clone)]
pub struct SuggestionItem<T> {
pub data: T,
pub struct SuggestionItem {
pub display_text: String,
pub value_to_store: String,
}

View File

@@ -10,12 +10,12 @@ use crate::canvas::modes::AppMode;
pub struct FormEditor<D: DataProvider> {
// 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<SuggestionItem<String>>,
pub(crate) suggestions: Vec<SuggestionItem>,
}
impl<D: DataProvider> FormEditor<D> {
@@ -26,31 +26,31 @@ impl<D: DataProvider> FormEditor<D> {
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<D: DataProvider> FormEditor<D> {
""
}
}
/// 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<String>] {
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<D: DataProvider> FormEditor<D> {
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<D: DataProvider> FormEditor<D> {
} 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<A>(&mut self, provider: &mut A) -> Result<()>
where
pub async fn trigger_autocomplete<A>(&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<String> {
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);
}
}