autocomplete to suggestions

This commit is contained in:
Priec
2025-08-07 12:08:02 +02:00
parent 96cde3ca0d
commit dff320d534
20 changed files with 199 additions and 198 deletions

View File

@@ -1,12 +0,0 @@
// src/autocomplete/mod.rs
pub mod state;
#[cfg(feature = "gui")]
pub mod gui;
// Re-export the main autocomplete types
pub use state::{AutocompleteProvider, SuggestionItem};
// Re-export GUI functions if available
#[cfg(feature = "gui")]
pub use gui::render_autocomplete_dropdown;

View File

@@ -1,5 +0,0 @@
// src/autocomplete/state.rs
//! Autocomplete provider types
// Re-export the main types from data_provider
pub use crate::data_provider::{AutocompleteProvider, SuggestionItem};

View File

@@ -30,12 +30,12 @@ pub enum CanvasAction {
DeleteBackward,
DeleteForward,
// Autocomplete actions
TriggerAutocomplete,
SuggestionUp,
SuggestionDown,
SelectSuggestion,
ExitSuggestions,
// Suggestions actions
TriggerSuggestions,
SuggestionUp,
SuggestionDown,
SelectSuggestion,
ExitSuggestions,
// Custom actions
Custom(String),
@@ -101,7 +101,7 @@ impl CanvasAction {
Self::InsertChar(_c) => "insert character",
Self::DeleteBackward => "delete backward",
Self::DeleteForward => "delete forward",
Self::TriggerAutocomplete => "trigger autocomplete",
Self::TriggerSuggestions => "trigger suggestions",
Self::SuggestionUp => "suggestion up",
Self::SuggestionDown => "suggestion down",
Self::SelectSuggestion => "select suggestion",
@@ -139,10 +139,10 @@ impl CanvasAction {
]
}
/// Get all autocomplete-related actions
pub fn autocomplete_actions() -> Vec<CanvasAction> {
/// Get all suggestions-related actions
pub fn suggestions_actions() -> Vec<CanvasAction> {
vec![
Self::TriggerAutocomplete,
Self::TriggerSuggestions,
Self::SuggestionUp,
Self::SuggestionDown,
Self::SelectSuggestion,

View File

@@ -19,7 +19,7 @@ use crate::editor::FormEditor;
#[cfg(feature = "gui")]
use std::cmp::{max, min};
/// Render ONLY the canvas form fields - no autocomplete
/// Render ONLY the canvas form fields - no suggestions rendering here
/// Updated to work with FormEditor instead of CanvasState trait
#[cfg(feature = "gui")]
pub fn render_canvas<T: CanvasTheme, D: DataProvider>(

View File

@@ -14,8 +14,8 @@ pub struct EditorState {
// Mode state
pub(crate) current_mode: AppMode,
// Autocomplete state
pub(crate) autocomplete: AutocompleteUIState,
// Suggestions dropdown state
pub(crate) suggestions: SuggestionsUIState,
// Selection state (for vim visual mode)
pub(crate) selection: SelectionState,
@@ -26,7 +26,7 @@ pub struct EditorState {
}
#[derive(Debug, Clone)]
pub struct AutocompleteUIState {
pub struct SuggestionsUIState {
pub(crate) is_active: bool,
pub(crate) is_loading: bool,
pub(crate) selected_index: Option<usize>,
@@ -47,7 +47,7 @@ impl EditorState {
cursor_pos: 0,
ideal_cursor_column: 0,
current_mode: AppMode::Edit,
autocomplete: AutocompleteUIState {
suggestions: SuggestionsUIState {
is_active: false,
is_loading: false,
selected_index: None,
@@ -83,14 +83,14 @@ impl EditorState {
self.current_mode
}
/// Check if autocomplete is active (for user's business logic)
pub fn is_autocomplete_active(&self) -> bool {
self.autocomplete.is_active
/// Check if suggestions dropdown is active (for user's business logic)
pub fn is_suggestions_active(&self) -> bool {
self.suggestions.is_active
}
/// Check if autocomplete is loading (for user's business logic)
pub fn is_autocomplete_loading(&self) -> bool {
self.autocomplete.is_loading
/// Check if suggestions dropdown is loading (for user's business logic)
pub fn is_suggestions_loading(&self) -> bool {
self.suggestions.is_loading
}
/// Get selection state (for user's business logic)
@@ -128,18 +128,18 @@ impl EditorState {
self.ideal_cursor_column = self.cursor_pos;
}
pub(crate) fn activate_autocomplete(&mut self, field_index: usize) {
self.autocomplete.is_active = true;
self.autocomplete.is_loading = true;
self.autocomplete.active_field = Some(field_index);
self.autocomplete.selected_index = None;
pub(crate) fn activate_suggestions(&mut self, field_index: usize) {
self.suggestions.is_active = true;
self.suggestions.is_loading = true;
self.suggestions.active_field = Some(field_index);
self.suggestions.selected_index = None;
}
pub(crate) fn deactivate_autocomplete(&mut self) {
self.autocomplete.is_active = false;
self.autocomplete.is_loading = false;
self.autocomplete.active_field = None;
self.autocomplete.selected_index = None;
pub(crate) fn deactivate_suggestions(&mut self) {
self.suggestions.is_active = false;
self.suggestions.is_loading = false;
self.suggestions.active_field = None;
self.suggestions.selected_index = None;
}
}

View File

@@ -18,8 +18,8 @@ pub trait DataProvider {
/// 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 {
/// Check if field supports suggestions (optional)
fn supports_suggestions(&self, _field_index: usize) -> bool {
false
}
@@ -36,10 +36,10 @@ pub trait DataProvider {
}
}
/// Optional: User implements this for autocomplete data
/// Optional: User implements this for suggestions data
#[async_trait]
pub trait AutocompleteProvider {
/// Fetch autocomplete suggestions (user's business logic)
pub trait SuggestionsProvider {
/// Fetch suggestions (user's business logic)
async fn fetch_suggestions(&mut self, field_index: usize, query: &str)
-> Result<Vec<SuggestionItem>>;
}

View File

@@ -7,7 +7,7 @@ use crate::canvas::CursorManager;
use anyhow::Result;
use crate::canvas::state::EditorState;
use crate::data_provider::{DataProvider, AutocompleteProvider, SuggestionItem};
use crate::data_provider::{DataProvider, SuggestionsProvider, SuggestionItem};
use crate::canvas::modes::AppMode;
use crate::canvas::state::SelectionState;
@@ -70,9 +70,9 @@ impl<D: DataProvider> FormEditor<D> {
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()
/// Check if suggestions dropdown is active (for user's logic)
pub fn is_suggestions_active(&self) -> bool {
self.ui_state.is_suggestions_active()
}
/// Get current field text (convenience method)
@@ -580,50 +580,51 @@ impl<D: DataProvider> FormEditor<D> {
}
// ===================================================================
// ASYNC OPERATIONS: Only autocomplete needs async
// ASYNC OPERATIONS: Only suggestions need async
// ===================================================================
/// Trigger autocomplete (async because it fetches data)
pub async fn trigger_autocomplete<A>(&mut self, provider: &mut A) -> Result<()>
/// Trigger suggestions (async because it fetches data)
pub async fn trigger_suggestions<A>(&mut self, provider: &mut A) -> Result<()>
where
A: AutocompleteProvider,
A: SuggestionsProvider,
{
let field_index = self.ui_state.current_field;
if !self.data_provider.supports_autocomplete(field_index) {
if !self.data_provider.supports_suggestions(field_index) {
return Ok(());
}
// Activate autocomplete UI
self.ui_state.activate_autocomplete(field_index);
// Activate suggestions UI
self.ui_state.activate_suggestions(field_index);
// Fetch suggestions from user (no conversion needed!)
let query = self.current_text();
self.suggestions = provider.fetch_suggestions(field_index, query).await?;
// Update UI state
self.ui_state.autocomplete.is_loading = false;
self.ui_state.suggestions.is_loading = false;
if !self.suggestions.is_empty() {
self.ui_state.autocomplete.selected_index = Some(0);
self.ui_state.suggestions.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() {
/// Navigate suggestions
pub fn suggestions_next(&mut self) {
if !self.ui_state.suggestions.is_active || self.suggestions.is_empty() {
return;
}
let current = self.ui_state.autocomplete.selected_index.unwrap_or(0);
let current = self.ui_state.suggestions.selected_index.unwrap_or(0);
let next = (current + 1) % self.suggestions.len();
self.ui_state.autocomplete.selected_index = Some(next);
self.ui_state.suggestions.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 {
/// Apply selected suggestion
/// Apply selected suggestion
pub fn apply_suggestion(&mut self) -> Option<String> {
if let Some(selected_index) = self.ui_state.suggestions.selected_index {
if let Some(suggestion) = self.suggestions.get(selected_index).cloned() {
let field_index = self.ui_state.current_field;
@@ -637,8 +638,8 @@ impl<D: DataProvider> FormEditor<D> {
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();
// Close suggestions
self.ui_state.deactivate_suggestions();
self.suggestions.clear();
// Validate the new content if validation is enabled
@@ -951,8 +952,8 @@ impl<D: DataProvider> FormEditor<D> {
}
self.set_mode(AppMode::ReadOnly);
// Deactivate autocomplete when exiting edit mode
self.ui_state.deactivate_autocomplete();
// Deactivate suggestions when exiting edit mode
self.ui_state.deactivate_suggestions();
Ok(())
}

View File

@@ -4,9 +4,9 @@ pub mod canvas;
pub mod editor;
pub mod data_provider;
// Only include autocomplete module if feature is enabled
#[cfg(feature = "autocomplete")]
pub mod autocomplete;
// Only include suggestions module if feature is enabled
#[cfg(feature = "suggestions")]
pub mod suggestions;
// Only include validation module if feature is enabled
#[cfg(feature = "validation")]
@@ -21,7 +21,7 @@ pub use canvas::CursorManager;
// Main API exports
pub use editor::FormEditor;
pub use data_provider::{DataProvider, AutocompleteProvider, SuggestionItem};
pub use data_provider::{DataProvider, SuggestionsProvider, SuggestionItem};
// UI state (read-only access for users)
pub use canvas::state::EditorState;
@@ -51,5 +51,5 @@ pub use canvas::gui::render_canvas;
#[cfg(feature = "gui")]
pub use canvas::gui::render_canvas_default;
#[cfg(all(feature = "gui", feature = "autocomplete"))]
pub use autocomplete::gui::render_autocomplete_dropdown;
#[cfg(all(feature = "gui", feature = "suggestions"))]
pub use suggestions::gui::render_suggestions_dropdown;

View File

@@ -1,5 +1,5 @@
// src/autocomplete/gui.rs
//! Autocomplete GUI updated to work with FormEditor
// src/suggestions/gui.rs
//! Suggestions dropdown GUI (not inline autocomplete) updated to work with FormEditor
#[cfg(feature = "gui")]
use ratatui::{
@@ -17,9 +17,9 @@ use crate::editor::FormEditor;
#[cfg(feature = "gui")]
use unicode_width::UnicodeWidthStr;
/// Render autocomplete dropdown for FormEditor - call this AFTER rendering canvas
/// Render suggestions dropdown for FormEditor - call this AFTER rendering canvas
#[cfg(feature = "gui")]
pub fn render_autocomplete_dropdown<T: CanvasTheme, D: DataProvider>(
pub fn render_suggestions_dropdown<T: CanvasTheme, D: DataProvider>(
f: &mut Frame,
frame_area: Rect,
input_rect: Rect,
@@ -28,14 +28,14 @@ pub fn render_autocomplete_dropdown<T: CanvasTheme, D: DataProvider>(
) {
let ui_state = editor.ui_state();
if !ui_state.is_autocomplete_active() {
if !ui_state.is_suggestions_active() {
return;
}
if ui_state.autocomplete.is_loading {
if ui_state.suggestions.is_loading {
render_loading_indicator(f, frame_area, input_rect, theme);
} else if !editor.suggestions().is_empty() {
render_suggestions_dropdown(f, frame_area, input_rect, theme, editor.suggestions(), ui_state.autocomplete.selected_index);
render_suggestions_dropdown_list(f, frame_area, input_rect, theme, editor.suggestions(), ui_state.suggestions.selected_index);
}
}
@@ -71,7 +71,7 @@ fn render_loading_indicator<T: CanvasTheme>(
/// Show actual suggestions list
#[cfg(feature = "gui")]
fn render_suggestions_dropdown<T: CanvasTheme>(
fn render_suggestions_dropdown_list<T: CanvasTheme>(
f: &mut Frame,
frame_area: Rect,
input_rect: Rect,

View File

@@ -0,0 +1,12 @@
// src/suggestions/mod.rs
pub mod state;
#[cfg(feature = "gui")]
pub mod gui;
// Re-export the main suggestion types
pub use state::{SuggestionsProvider, SuggestionItem};
// Re-export GUI functions if available
#[cfg(feature = "gui")]
pub use gui::render_suggestions_dropdown;

View File

@@ -0,0 +1,5 @@
// src/suggestions/state.rs
//! Suggestions provider types (for dropdown suggestions, not real inline autocomplete)
// Re-export the main types from data_provider
pub use crate::data_provider::{SuggestionsProvider, SuggestionItem};