autocomplete to suggestions
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
This guide covers the migration from the legacy canvas library structure to the new clean, modular architecture. The new design separates core canvas functionality from autocomplete features, providing better type safety and maintainability.
|
This guide covers the migration from the legacy canvas library structure to the new clean, modular architecture. The new design separates core canvas functionality from suggestions features, providing better type safety and maintainability.
|
||||||
|
|
||||||
## Key Changes
|
## Key Changes
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ This guide covers the migration from the legacy canvas library structure to the
|
|||||||
```
|
```
|
||||||
# Old Structure (LEGACY)
|
# Old Structure (LEGACY)
|
||||||
src/
|
src/
|
||||||
├── state.rs # Mixed canvas + autocomplete
|
├── state.rs # Mixed canvas + suggestions
|
||||||
├── actions/edit.rs # Mixed concerns
|
├── actions/edit.rs # Mixed concerns
|
||||||
├── gui/render.rs # Everything together
|
├── gui/render.rs # Everything together
|
||||||
└── suggestions.rs # Legacy file
|
└── suggestions.rs # Legacy file
|
||||||
@@ -21,9 +21,9 @@ src/
|
|||||||
│ ├── state.rs # CanvasState trait only
|
│ ├── state.rs # CanvasState trait only
|
||||||
│ ├── actions/edit.rs # Canvas actions only
|
│ ├── actions/edit.rs # Canvas actions only
|
||||||
│ └── gui.rs # Canvas rendering
|
│ └── gui.rs # Canvas rendering
|
||||||
├── autocomplete/ # Rich autocomplete features
|
├── suggestions/ # Suggestions dropdown features (not inline autocomplete)
|
||||||
│ ├── state.rs # AutocompleteCanvasState trait
|
│ ├── state.rs # Suggestion provider types
|
||||||
│ ├── types.rs # SuggestionItem, AutocompleteState
|
│ ├── gui.rs # Suggestions dropdown rendering
|
||||||
│ ├── actions.rs # Autocomplete actions
|
│ ├── actions.rs # Autocomplete actions
|
||||||
│ └── gui.rs # Autocomplete dropdown rendering
|
│ └── gui.rs # Autocomplete dropdown rendering
|
||||||
└── dispatcher.rs # Action routing
|
└── dispatcher.rs # Action routing
|
||||||
@@ -31,7 +31,7 @@ src/
|
|||||||
|
|
||||||
### 2. **Trait Separation**
|
### 2. **Trait Separation**
|
||||||
- **CanvasState**: Core form functionality (navigation, input, validation)
|
- **CanvasState**: Core form functionality (navigation, input, validation)
|
||||||
- **AutocompleteCanvasState**: Optional rich autocomplete features
|
- Suggestions module: Optional dropdown suggestions support
|
||||||
|
|
||||||
### 3. **Rich Suggestions**
|
### 3. **Rich Suggestions**
|
||||||
Replaced simple string suggestions with typed, rich suggestion objects.
|
Replaced simple string suggestions with typed, rich suggestion objects.
|
||||||
@@ -93,34 +93,29 @@ impl CanvasState for YourFormState {
|
|||||||
|
|
||||||
### Step 3: Implement Rich Autocomplete (Optional)
|
### Step 3: Implement Rich Autocomplete (Optional)
|
||||||
|
|
||||||
**If you want rich autocomplete features:**
|
**If you want suggestions dropdown features:**
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use canvas::autocomplete::{AutocompleteCanvasState, SuggestionItem, AutocompleteState};
|
use canvas::{SuggestionItem};
|
||||||
|
|
||||||
impl AutocompleteCanvasState for YourFormState {
|
impl YourFormState {
|
||||||
type SuggestionData = YourDataType; // e.g., Hit, CustomRecord, etc.
|
fn supports_suggestions(&self, field_index: usize) -> bool {
|
||||||
|
// Define which fields support suggestions
|
||||||
fn supports_autocomplete(&self, field_index: usize) -> bool {
|
|
||||||
// Define which fields support autocomplete
|
|
||||||
matches!(field_index, 2 | 3 | 5) // Example: only certain fields
|
matches!(field_index, 2 | 3 | 5) // Example: only certain fields
|
||||||
}
|
}
|
||||||
|
|
||||||
fn autocomplete_state(&self) -> Option<&AutocompleteState<Self::SuggestionData>> {
|
// Manage your own suggestion state or rely on FormEditor APIs
|
||||||
Some(&self.autocomplete)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn autocomplete_state_mut(&mut self) -> Option<&mut AutocompleteState<Self::SuggestionData>> {
|
// Manage your own suggestion state or rely on FormEditor APIs
|
||||||
Some(&mut self.autocomplete)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Add autocomplete field to your state:**
|
**Add suggestions storage to your state (optional, if you need to persist outside the editor):**
|
||||||
```rust
|
```rust
|
||||||
pub struct YourFormState {
|
pub struct YourFormState {
|
||||||
// ... existing fields
|
// ... existing fields
|
||||||
pub autocomplete: AutocompleteState<YourDataType>,
|
// Optional: your own suggestions cache if needed
|
||||||
|
// pub suggestion_cache: Vec<SuggestionItem>,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -149,9 +144,9 @@ form_state.set_autocomplete_suggestions(suggestions);
|
|||||||
|
|
||||||
**Old rendering:**
|
**Old rendering:**
|
||||||
```rust
|
```rust
|
||||||
// Manual autocomplete rendering
|
// Manual suggestions rendering
|
||||||
if form_state.autocomplete_active {
|
if editor.is_suggestions_active() {
|
||||||
render_autocomplete_dropdown(/* ... */);
|
suggestions::gui::render_suggestions_dropdown(/* ... */);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -162,13 +157,12 @@ use canvas::canvas::render_canvas;
|
|||||||
|
|
||||||
let active_field_rect = render_canvas(f, area, form_state, theme, edit_mode, highlight_state);
|
let active_field_rect = render_canvas(f, area, form_state, theme, edit_mode, highlight_state);
|
||||||
|
|
||||||
// Optional: Rich autocomplete (if implementing AutocompleteCanvasState)
|
// Suggestions dropdown (if active)
|
||||||
if form_state.is_autocomplete_active() {
|
if editor.is_suggestions_active() {
|
||||||
if let Some(autocomplete_state) = form_state.autocomplete_state() {
|
canvas::suggestions::render_suggestions_dropdown(
|
||||||
canvas::autocomplete::render_autocomplete_dropdown(
|
f, f.area(), active_field_rect.unwrap(), theme, &editor
|
||||||
f, f.area(), active_field_rect.unwrap(), theme, autocomplete_state
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -181,16 +175,16 @@ form_state.deactivate_suggestions();
|
|||||||
|
|
||||||
# NEW - Option A: Add your own method
|
# NEW - Option A: Add your own method
|
||||||
impl YourFormState {
|
impl YourFormState {
|
||||||
pub fn deactivate_autocomplete(&mut self) {
|
pub fn deactivate_suggestions(&mut self) {
|
||||||
self.autocomplete_active = false;
|
self.autocomplete_active = false;
|
||||||
self.autocomplete_suggestions.clear();
|
self.autocomplete_suggestions.clear();
|
||||||
self.selected_suggestion_index = None;
|
self.selected_suggestion_index = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
form_state.deactivate_autocomplete();
|
editor.ui_state_mut().deactivate_suggestions();
|
||||||
|
|
||||||
# NEW - Option B: Use rich autocomplete trait
|
# NEW - Option B: Suggestions via editor APIs
|
||||||
form_state.deactivate_autocomplete(); // If implementing AutocompleteCanvasState
|
editor.ui_state_mut().deactivate_suggestions();
|
||||||
```
|
```
|
||||||
|
|
||||||
## Benefits of New Architecture
|
## Benefits of New Architecture
|
||||||
@@ -217,8 +211,8 @@ let suggestions: Vec<SuggestionItem<UserRecord>> = vec![
|
|||||||
- **Display Overrides**: Show friendly text while storing normalized data
|
- **Display Overrides**: Show friendly text while storing normalized data
|
||||||
|
|
||||||
### 4. **Future-Proof**
|
### 4. **Future-Proof**
|
||||||
- Easy to add new autocomplete features
|
- Easy to add new suggestion features
|
||||||
- Canvas features don't interfere with autocomplete
|
- Canvas features don't interfere with suggestions
|
||||||
- Modular: Use only what you need
|
- Modular: Use only what you need
|
||||||
|
|
||||||
## Advanced Features
|
## Advanced Features
|
||||||
@@ -262,7 +256,7 @@ SuggestionItem::new(user, "John Doe (Manager)".to_string(), "123".to_string());
|
|||||||
## Breaking Changes Summary
|
## Breaking Changes Summary
|
||||||
|
|
||||||
1. **Import paths changed**: Add `canvas::` or `dispatcher::` prefixes
|
1. **Import paths changed**: Add `canvas::` or `dispatcher::` prefixes
|
||||||
2. **Legacy suggestion methods removed**: Replace with rich autocomplete or custom methods
|
2. **Legacy suggestion methods removed**: Replace with SuggestionItem-based dropdown or custom methods
|
||||||
3. **No more simple suggestions**: Use `SuggestionItem` for typed suggestions
|
3. **No more simple suggestions**: Use `SuggestionItem` for typed suggestions
|
||||||
4. **Trait split**: `AutocompleteCanvasState` is now separate and optional
|
4. **Trait split**: `AutocompleteCanvasState` is now separate and optional
|
||||||
|
|
||||||
@@ -283,11 +277,11 @@ SuggestionItem::new(user, "John Doe (Manager)".to_string(), "123".to_string());
|
|||||||
|
|
||||||
- [ ] Updated all import paths
|
- [ ] Updated all import paths
|
||||||
- [ ] Removed legacy methods from CanvasState implementation
|
- [ ] Removed legacy methods from CanvasState implementation
|
||||||
- [ ] Added custom autocomplete methods if needed
|
- [ ] Added custom suggestion methods if needed
|
||||||
- [ ] Updated suggestion usage to SuggestionItem
|
- [ ] Updated usage to SuggestionItem
|
||||||
- [ ] Updated rendering calls
|
- [ ] Updated rendering calls
|
||||||
- [ ] Tested form functionality
|
- [ ] Tested form functionality
|
||||||
- [ ] Tested autocomplete functionality (if using)
|
- [ ] Tested suggestions functionality (if using)
|
||||||
|
|
||||||
## Example: Complete Migration
|
## Example: Complete Migration
|
||||||
|
|
||||||
@@ -305,29 +299,25 @@ impl CanvasState for FormState {
|
|||||||
**After:**
|
**After:**
|
||||||
```rust
|
```rust
|
||||||
use canvas::canvas::{CanvasState, CanvasAction};
|
use canvas::canvas::{CanvasState, CanvasAction};
|
||||||
use canvas::autocomplete::{AutocompleteCanvasState, SuggestionItem};
|
use canvas::SuggestionItem;
|
||||||
|
|
||||||
impl CanvasState for FormState {
|
impl CanvasState for FormState {
|
||||||
// Only core canvas methods, no suggestion methods
|
// Only core canvas methods
|
||||||
fn current_field(&self) -> usize { /* ... */ }
|
fn current_field(&self) -> usize { /* ... */ }
|
||||||
fn get_current_input(&self) -> &str { /* ... */ }
|
fn get_current_input(&self) -> &str { /* ... */ }
|
||||||
// ... other core methods only
|
// ... other core methods only
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AutocompleteCanvasState for FormState {
|
// Use FormEditor + SuggestionsProvider for suggestions dropdown
|
||||||
type SuggestionData = Hit;
|
type SuggestionData = Hit;
|
||||||
|
|
||||||
fn supports_autocomplete(&self, field_index: usize) -> bool {
|
fn supports_suggestions(&self, field_index: usize) -> bool {
|
||||||
self.fields[field_index].is_link
|
self.fields[field_index].is_link
|
||||||
}
|
}
|
||||||
|
|
||||||
fn autocomplete_state(&self) -> Option<&AutocompleteState<Self::SuggestionData>> {
|
// Maintain suggestion state through FormEditor and DataProvider
|
||||||
Some(&self.autocomplete)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn autocomplete_state_mut(&mut self) -> Option<&mut AutocompleteState<Self::SuggestionData>> {
|
// Maintain suggestion state through FormEditor and DataProvider
|
||||||
Some(&mut self.autocomplete)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -30,15 +30,15 @@ tokio-test = "0.4.4"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
gui = ["ratatui"]
|
gui = ["ratatui", "crossterm"]
|
||||||
autocomplete = ["tokio"]
|
suggestions = ["tokio"]
|
||||||
cursor-style = ["crossterm"]
|
cursor-style = ["crossterm"]
|
||||||
validation = ["regex"]
|
validation = ["regex"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "autocomplete"
|
name = "suggestions"
|
||||||
required-features = ["autocomplete", "gui"]
|
required-features = ["suggestions", "gui", "cursor-style"]
|
||||||
path = "examples/autocomplete.rs"
|
path = "examples/suggestions.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "canvas_gui_demo"
|
name = "canvas_gui_demo"
|
||||||
@@ -48,3 +48,19 @@ path = "examples/canvas_gui_demo.rs"
|
|||||||
[[example]]
|
[[example]]
|
||||||
name = "validation_1"
|
name = "validation_1"
|
||||||
required-features = ["gui", "validation"]
|
required-features = ["gui", "validation"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "validation_2"
|
||||||
|
required-features = ["gui", "validation"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "validation_3"
|
||||||
|
required-features = ["gui", "validation"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "validation_4"
|
||||||
|
required-features = ["gui", "validation"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "validation_5"
|
||||||
|
required-features = ["gui", "validation"]
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ A reusable, type-safe canvas system for building form-based TUI applications wit
|
|||||||
- **Type-Safe Actions**: No more string-based action names - everything is compile-time checked
|
- **Type-Safe Actions**: No more string-based action names - everything is compile-time checked
|
||||||
- **Generic Design**: Implement `CanvasState` once, get navigation, editing, and suggestions for free
|
- **Generic Design**: Implement `CanvasState` once, get navigation, editing, and suggestions for free
|
||||||
- **Vim-Like Experience**: Modal editing with familiar keybindings
|
- **Vim-Like Experience**: Modal editing with familiar keybindings
|
||||||
- **Suggestion System**: Built-in autocomplete and suggestions support
|
- **Suggestion System**: Built-in suggestions dropdown support
|
||||||
- **Framework Agnostic**: Works with any TUI framework or raw terminal handling
|
- **Framework Agnostic**: Works with any TUI framework or raw terminal handling
|
||||||
- **Async Ready**: Full async/await support for modern Rust applications
|
- **Async Ready**: Full async/await support for modern Rust applications
|
||||||
- **Batch Operations**: Execute multiple actions atomically
|
- **Batch Operations**: Execute multiple actions atomically
|
||||||
@@ -144,7 +144,7 @@ pub enum CanvasAction {
|
|||||||
|
|
||||||
## 🔧 Advanced Features
|
## 🔧 Advanced Features
|
||||||
|
|
||||||
### Suggestions and Autocomplete
|
### Suggestions Dropdown (not inline autocomplete)
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
impl CanvasState for MyForm {
|
impl CanvasState for MyForm {
|
||||||
@@ -170,7 +170,7 @@ impl CanvasState for MyForm {
|
|||||||
CanvasAction::SelectSuggestion => {
|
CanvasAction::SelectSuggestion => {
|
||||||
if let Some(suggestion) = self.suggestions.get_selected() {
|
if let Some(suggestion) = self.suggestions.get_selected() {
|
||||||
*self.get_current_input_mut() = suggestion.clone();
|
*self.get_current_input_mut() = suggestion.clone();
|
||||||
self.deactivate_autocomplete();
|
self.deactivate_suggestions();
|
||||||
Some("Applied suggestion".to_string())
|
Some("Applied suggestion".to_string())
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ impl DataProvider for CursorDemoData {
|
|||||||
self.fields[index].1 = value;
|
self.fields[index].1 = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supports_autocomplete(&self, _field_index: usize) -> bool {
|
fn supports_suggestions(&self, _field_index: usize) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ impl DataProvider for FullDemoData {
|
|||||||
self.fields[index].1 = value;
|
self.fields[index].1 = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supports_autocomplete(&self, _field_index: usize) -> bool {
|
fn supports_suggestions(&self, _field_index: usize) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// examples/autocomplete.rs
|
// examples/suggestions.rs
|
||||||
// Run with: cargo run --example autocomplete --features "autocomplete,gui"
|
// Run with: cargo run --example suggestions --features "suggestions,gui"
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
@@ -22,8 +22,8 @@ use canvas::{
|
|||||||
modes::AppMode,
|
modes::AppMode,
|
||||||
theme::CanvasTheme,
|
theme::CanvasTheme,
|
||||||
},
|
},
|
||||||
autocomplete::gui::render_autocomplete_dropdown,
|
suggestions::gui::render_suggestions_dropdown,
|
||||||
FormEditor, DataProvider, AutocompleteProvider, SuggestionItem,
|
FormEditor, DataProvider, SuggestionsProvider, SuggestionItem,
|
||||||
};
|
};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@@ -108,7 +108,7 @@ impl DataProvider for ContactForm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supports_autocomplete(&self, field_index: usize) -> bool {
|
fn supports_suggestions(&self, field_index: usize) -> bool {
|
||||||
field_index == 1 // Only email field
|
field_index == 1 // Only email field
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,11 +120,9 @@ impl DataProvider for ContactForm {
|
|||||||
struct EmailAutocomplete;
|
struct EmailAutocomplete;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl AutocompleteProvider for EmailAutocomplete {
|
impl SuggestionsProvider for EmailAutocomplete {
|
||||||
type SuggestionData = EmailSuggestion;
|
|
||||||
|
|
||||||
async fn fetch_suggestions(&mut self, _field_index: usize, query: &str)
|
async fn fetch_suggestions(&mut self, _field_index: usize, query: &str)
|
||||||
-> Result<Vec<SuggestionItem<Self::SuggestionData>>>
|
-> Result<Vec<SuggestionItem>>
|
||||||
{
|
{
|
||||||
// Extract domain part from email
|
// Extract domain part from email
|
||||||
let (email_prefix, domain_part) = if let Some(at_pos) = query.find('@') {
|
let (email_prefix, domain_part) = if let Some(at_pos) = query.find('@') {
|
||||||
@@ -153,10 +151,6 @@ impl AutocompleteProvider for EmailAutocomplete {
|
|||||||
if domain.starts_with(&domain_part) || domain_part.is_empty() {
|
if domain.starts_with(&domain_part) || domain_part.is_empty() {
|
||||||
let full_email = format!("{}@{}", email_prefix, domain);
|
let full_email = format!("{}@{}", email_prefix, domain);
|
||||||
results.push(SuggestionItem {
|
results.push(SuggestionItem {
|
||||||
data: EmailSuggestion {
|
|
||||||
email: full_email.clone(),
|
|
||||||
provider: provider.to_string(),
|
|
||||||
},
|
|
||||||
display_text: format!("{} ({})", full_email, provider),
|
display_text: format!("{} ({})", full_email, provider),
|
||||||
value_to_store: full_email,
|
value_to_store: full_email,
|
||||||
});
|
});
|
||||||
@@ -175,7 +169,7 @@ impl AutocompleteProvider for EmailAutocomplete {
|
|||||||
|
|
||||||
struct AppState {
|
struct AppState {
|
||||||
editor: FormEditor<ContactForm>,
|
editor: FormEditor<ContactForm>,
|
||||||
autocomplete: EmailAutocomplete,
|
suggestions_provider: EmailAutocomplete,
|
||||||
debug_message: String,
|
debug_message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,8 +184,8 @@ impl AppState {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
editor,
|
editor,
|
||||||
autocomplete: EmailAutocomplete,
|
suggestions_provider: EmailAutocomplete,
|
||||||
debug_message: "Type in email field, Tab to trigger autocomplete, Enter to select, Esc to cancel".to_string(),
|
debug_message: "Type in email field, Tab to trigger suggestions, Enter to select, Esc to cancel".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,14 +201,14 @@ async fn handle_key_press(key: KeyCode, modifiers: KeyModifiers, state: &mut App
|
|||||||
|
|
||||||
// Handle input based on key
|
// Handle input based on key
|
||||||
let result = match key {
|
let result = match key {
|
||||||
// === AUTOCOMPLETE KEYS ===
|
// === SUGGESTIONS KEYS ===
|
||||||
KeyCode::Tab => {
|
KeyCode::Tab => {
|
||||||
if state.editor.is_autocomplete_active() {
|
if state.editor.is_suggestions_active() {
|
||||||
state.editor.autocomplete_next();
|
state.editor.suggestions_next();
|
||||||
Ok("Navigated to next suggestion".to_string())
|
Ok("Navigated to next suggestion".to_string())
|
||||||
} else if state.editor.data_provider().supports_autocomplete(state.editor.current_field()) {
|
} else if state.editor.data_provider().supports_suggestions(state.editor.current_field()) {
|
||||||
state.editor.trigger_autocomplete(&mut state.autocomplete).await
|
state.editor.trigger_suggestions(&mut state.suggestions_provider).await
|
||||||
.map(|_| "Triggered autocomplete".to_string())
|
.map(|_| "Triggered suggestions".to_string())
|
||||||
} else {
|
} else {
|
||||||
state.editor.move_to_next_field();
|
state.editor.move_to_next_field();
|
||||||
Ok("Moved to next field".to_string())
|
Ok("Moved to next field".to_string())
|
||||||
@@ -222,8 +216,8 @@ async fn handle_key_press(key: KeyCode, modifiers: KeyModifiers, state: &mut App
|
|||||||
}
|
}
|
||||||
|
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
if state.editor.is_autocomplete_active() {
|
if state.editor.is_suggestions_active() {
|
||||||
if let Some(applied) = state.editor.apply_autocomplete() {
|
if let Some(applied) = state.editor.apply_suggestion() {
|
||||||
Ok(format!("Applied: {}", applied))
|
Ok(format!("Applied: {}", applied))
|
||||||
} else {
|
} else {
|
||||||
Ok("No suggestion to apply".to_string())
|
Ok("No suggestion to apply".to_string())
|
||||||
@@ -235,9 +229,9 @@ async fn handle_key_press(key: KeyCode, modifiers: KeyModifiers, state: &mut App
|
|||||||
}
|
}
|
||||||
|
|
||||||
KeyCode::Esc => {
|
KeyCode::Esc => {
|
||||||
if state.editor.is_autocomplete_active() {
|
if state.editor.is_suggestions_active() {
|
||||||
// Autocomplete will be cleared automatically by mode change
|
// Suggestions will be cleared automatically by mode change
|
||||||
Ok("Cancelled autocomplete".to_string())
|
Ok("Cancelled suggestions".to_string())
|
||||||
} else {
|
} else {
|
||||||
// Toggle between edit and readonly mode
|
// Toggle between edit and readonly mode
|
||||||
let new_mode = match state.editor.mode() {
|
let new_mode = match state.editor.mode() {
|
||||||
@@ -324,9 +318,9 @@ fn ui(f: &mut Frame, state: &AppState, theme: &DemoTheme) {
|
|||||||
theme,
|
theme,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Render autocomplete dropdown if active
|
// Render suggestions dropdown if active
|
||||||
if let Some(input_rect) = active_field_rect {
|
if let Some(input_rect) = active_field_rect {
|
||||||
render_autocomplete_dropdown(
|
render_suggestions_dropdown(
|
||||||
f,
|
f,
|
||||||
chunks[0],
|
chunks[0],
|
||||||
input_rect,
|
input_rect,
|
||||||
@@ -336,8 +330,8 @@ fn ui(f: &mut Frame, state: &AppState, theme: &DemoTheme) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Status info
|
// Status info
|
||||||
let autocomplete_status = if state.editor.is_autocomplete_active() {
|
let autocomplete_status = if state.editor.is_suggestions_active() {
|
||||||
if state.editor.ui_state().is_autocomplete_loading() {
|
if state.editor.ui_state().is_suggestions_loading() {
|
||||||
"Loading suggestions..."
|
"Loading suggestions..."
|
||||||
} else if !state.editor.suggestions().is_empty() {
|
} else if !state.editor.suggestions().is_empty() {
|
||||||
"Use Tab to navigate, Enter to select, Esc to cancel"
|
"Use Tab to navigate, Enter to select, Esc to cancel"
|
||||||
@@ -345,7 +339,7 @@ fn ui(f: &mut Frame, state: &AppState, theme: &DemoTheme) {
|
|||||||
"No suggestions found"
|
"No suggestions found"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
"Tab to trigger autocomplete"
|
"Tab to trigger suggestions"
|
||||||
};
|
};
|
||||||
|
|
||||||
let status_lines = vec![
|
let status_lines = vec![
|
||||||
@@ -354,9 +348,9 @@ fn ui(f: &mut Frame, state: &AppState, theme: &DemoTheme) {
|
|||||||
state.editor.current_field() + 1,
|
state.editor.current_field() + 1,
|
||||||
state.editor.data_provider().field_count(),
|
state.editor.data_provider().field_count(),
|
||||||
state.editor.cursor_position()))),
|
state.editor.cursor_position()))),
|
||||||
Line::from(Span::raw(format!("Autocomplete: {}", autocomplete_status))),
|
Line::from(Span::raw(format!("Suggestions: {}", autocomplete_status))),
|
||||||
Line::from(Span::raw(state.debug_message.clone())),
|
Line::from(Span::raw(state.debug_message.clone())),
|
||||||
Line::from(Span::raw("F10: Quit | Tab: Trigger/Navigate autocomplete | Enter: Select | Esc: Cancel/Toggle mode")),
|
Line::from(Span::raw("F10: Quit | Tab: Trigger/Navigate suggestions | Enter: Select | Esc: Cancel/Toggle mode")),
|
||||||
];
|
];
|
||||||
|
|
||||||
let status = Paragraph::new(status_lines)
|
let status = Paragraph::new(status_lines)
|
||||||
@@ -447,7 +447,7 @@ impl DataProvider for ValidationDemoData {
|
|||||||
self.fields[index].1 = value;
|
self.fields[index].1 = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supports_autocomplete(&self, _field_index: usize) -> bool {
|
fn supports_suggestions(&self, _field_index: usize) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -848,21 +848,21 @@ fn run_app<B: Backend>(
|
|||||||
(_, KeyCode::Tab, _) => editor.next_field(),
|
(_, KeyCode::Tab, _) => editor.next_field(),
|
||||||
(_, KeyCode::BackTab, _) => editor.prev_field(),
|
(_, KeyCode::BackTab, _) => editor.prev_field(),
|
||||||
|
|
||||||
// Validation commands
|
// Validation commands (ONLY in ReadOnly mode)
|
||||||
(_, 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);
|
||||||
},
|
},
|
||||||
(_, KeyCode::Char('V'), _) => editor.validate_all_fields(),
|
(AppMode::ReadOnly, KeyCode::Char('V'), _) => editor.validate_all_fields(),
|
||||||
(_, KeyCode::Char('c'), _) => {
|
(AppMode::ReadOnly, KeyCode::Char('c'), _) => {
|
||||||
let field = editor.current_field();
|
let field = editor.current_field();
|
||||||
editor.clear_validation_state(Some(field));
|
editor.clear_validation_state(Some(field));
|
||||||
},
|
},
|
||||||
(_, KeyCode::Char('C'), _) => editor.clear_validation_state(None),
|
(AppMode::ReadOnly, KeyCode::Char('C'), _) => editor.clear_validation_state(None),
|
||||||
|
|
||||||
// UI toggles
|
// UI toggles (ONLY in ReadOnly mode for alpha keys to avoid blocking text input)
|
||||||
(_, KeyCode::Char('r'), _) => editor.toggle_history_view(),
|
(AppMode::ReadOnly, KeyCode::Char('r'), _) => editor.toggle_history_view(),
|
||||||
(_, KeyCode::Char('e'), _) => editor.cycle_examples(),
|
(AppMode::ReadOnly, KeyCode::Char('e'), _) => editor.cycle_examples(),
|
||||||
(_, KeyCode::F(1), _) => editor.toggle_validation(),
|
(_, KeyCode::F(1), _) => editor.toggle_validation(),
|
||||||
|
|
||||||
// Editing
|
// Editing
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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};
|
|
||||||
@@ -30,12 +30,12 @@ pub enum CanvasAction {
|
|||||||
DeleteBackward,
|
DeleteBackward,
|
||||||
DeleteForward,
|
DeleteForward,
|
||||||
|
|
||||||
// Autocomplete actions
|
// Suggestions actions
|
||||||
TriggerAutocomplete,
|
TriggerSuggestions,
|
||||||
SuggestionUp,
|
SuggestionUp,
|
||||||
SuggestionDown,
|
SuggestionDown,
|
||||||
SelectSuggestion,
|
SelectSuggestion,
|
||||||
ExitSuggestions,
|
ExitSuggestions,
|
||||||
|
|
||||||
// Custom actions
|
// Custom actions
|
||||||
Custom(String),
|
Custom(String),
|
||||||
@@ -101,7 +101,7 @@ impl CanvasAction {
|
|||||||
Self::InsertChar(_c) => "insert character",
|
Self::InsertChar(_c) => "insert character",
|
||||||
Self::DeleteBackward => "delete backward",
|
Self::DeleteBackward => "delete backward",
|
||||||
Self::DeleteForward => "delete forward",
|
Self::DeleteForward => "delete forward",
|
||||||
Self::TriggerAutocomplete => "trigger autocomplete",
|
Self::TriggerSuggestions => "trigger suggestions",
|
||||||
Self::SuggestionUp => "suggestion up",
|
Self::SuggestionUp => "suggestion up",
|
||||||
Self::SuggestionDown => "suggestion down",
|
Self::SuggestionDown => "suggestion down",
|
||||||
Self::SelectSuggestion => "select suggestion",
|
Self::SelectSuggestion => "select suggestion",
|
||||||
@@ -139,10 +139,10 @@ impl CanvasAction {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all autocomplete-related actions
|
/// Get all suggestions-related actions
|
||||||
pub fn autocomplete_actions() -> Vec<CanvasAction> {
|
pub fn suggestions_actions() -> Vec<CanvasAction> {
|
||||||
vec![
|
vec![
|
||||||
Self::TriggerAutocomplete,
|
Self::TriggerSuggestions,
|
||||||
Self::SuggestionUp,
|
Self::SuggestionUp,
|
||||||
Self::SuggestionDown,
|
Self::SuggestionDown,
|
||||||
Self::SelectSuggestion,
|
Self::SelectSuggestion,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use crate::editor::FormEditor;
|
|||||||
#[cfg(feature = "gui")]
|
#[cfg(feature = "gui")]
|
||||||
use std::cmp::{max, min};
|
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
|
/// Updated to work with FormEditor instead of CanvasState trait
|
||||||
#[cfg(feature = "gui")]
|
#[cfg(feature = "gui")]
|
||||||
pub fn render_canvas<T: CanvasTheme, D: DataProvider>(
|
pub fn render_canvas<T: CanvasTheme, D: DataProvider>(
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ pub struct EditorState {
|
|||||||
// Mode state
|
// Mode state
|
||||||
pub(crate) current_mode: AppMode,
|
pub(crate) current_mode: AppMode,
|
||||||
|
|
||||||
// Autocomplete state
|
// Suggestions dropdown state
|
||||||
pub(crate) autocomplete: AutocompleteUIState,
|
pub(crate) suggestions: SuggestionsUIState,
|
||||||
|
|
||||||
// Selection state (for vim visual mode)
|
// Selection state (for vim visual mode)
|
||||||
pub(crate) selection: SelectionState,
|
pub(crate) selection: SelectionState,
|
||||||
@@ -26,7 +26,7 @@ pub struct EditorState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AutocompleteUIState {
|
pub struct SuggestionsUIState {
|
||||||
pub(crate) is_active: bool,
|
pub(crate) is_active: bool,
|
||||||
pub(crate) is_loading: bool,
|
pub(crate) is_loading: bool,
|
||||||
pub(crate) selected_index: Option<usize>,
|
pub(crate) selected_index: Option<usize>,
|
||||||
@@ -47,7 +47,7 @@ impl EditorState {
|
|||||||
cursor_pos: 0,
|
cursor_pos: 0,
|
||||||
ideal_cursor_column: 0,
|
ideal_cursor_column: 0,
|
||||||
current_mode: AppMode::Edit,
|
current_mode: AppMode::Edit,
|
||||||
autocomplete: AutocompleteUIState {
|
suggestions: SuggestionsUIState {
|
||||||
is_active: false,
|
is_active: false,
|
||||||
is_loading: false,
|
is_loading: false,
|
||||||
selected_index: None,
|
selected_index: None,
|
||||||
@@ -83,14 +83,14 @@ impl EditorState {
|
|||||||
self.current_mode
|
self.current_mode
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if autocomplete is active (for user's business logic)
|
/// Check if suggestions dropdown is active (for user's business logic)
|
||||||
pub fn is_autocomplete_active(&self) -> bool {
|
pub fn is_suggestions_active(&self) -> bool {
|
||||||
self.autocomplete.is_active
|
self.suggestions.is_active
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if autocomplete is loading (for user's business logic)
|
/// Check if suggestions dropdown is loading (for user's business logic)
|
||||||
pub fn is_autocomplete_loading(&self) -> bool {
|
pub fn is_suggestions_loading(&self) -> bool {
|
||||||
self.autocomplete.is_loading
|
self.suggestions.is_loading
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get selection state (for user's business logic)
|
/// Get selection state (for user's business logic)
|
||||||
@@ -128,18 +128,18 @@ impl EditorState {
|
|||||||
self.ideal_cursor_column = self.cursor_pos;
|
self.ideal_cursor_column = self.cursor_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn activate_autocomplete(&mut self, field_index: usize) {
|
pub(crate) fn activate_suggestions(&mut self, field_index: usize) {
|
||||||
self.autocomplete.is_active = true;
|
self.suggestions.is_active = true;
|
||||||
self.autocomplete.is_loading = true;
|
self.suggestions.is_loading = true;
|
||||||
self.autocomplete.active_field = Some(field_index);
|
self.suggestions.active_field = Some(field_index);
|
||||||
self.autocomplete.selected_index = None;
|
self.suggestions.selected_index = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn deactivate_autocomplete(&mut self) {
|
pub(crate) fn deactivate_suggestions(&mut self) {
|
||||||
self.autocomplete.is_active = false;
|
self.suggestions.is_active = false;
|
||||||
self.autocomplete.is_loading = false;
|
self.suggestions.is_loading = false;
|
||||||
self.autocomplete.active_field = None;
|
self.suggestions.active_field = None;
|
||||||
self.autocomplete.selected_index = None;
|
self.suggestions.selected_index = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ pub trait DataProvider {
|
|||||||
/// Set field value (library calls this when text changes)
|
/// Set field value (library calls this when text changes)
|
||||||
fn set_field_value(&mut self, index: usize, value: String);
|
fn set_field_value(&mut self, index: usize, value: String);
|
||||||
|
|
||||||
/// Check if field supports autocomplete (optional)
|
/// Check if field supports suggestions (optional)
|
||||||
fn supports_autocomplete(&self, _field_index: usize) -> bool {
|
fn supports_suggestions(&self, _field_index: usize) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,10 +36,10 @@ pub trait DataProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optional: User implements this for autocomplete data
|
/// Optional: User implements this for suggestions data
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait AutocompleteProvider {
|
pub trait SuggestionsProvider {
|
||||||
/// Fetch autocomplete suggestions (user's business logic)
|
/// Fetch suggestions (user's business logic)
|
||||||
async fn fetch_suggestions(&mut self, field_index: usize, query: &str)
|
async fn fetch_suggestions(&mut self, field_index: usize, query: &str)
|
||||||
-> Result<Vec<SuggestionItem>>;
|
-> Result<Vec<SuggestionItem>>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use crate::canvas::CursorManager;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crate::canvas::state::EditorState;
|
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::modes::AppMode;
|
||||||
use crate::canvas::state::SelectionState;
|
use crate::canvas::state::SelectionState;
|
||||||
|
|
||||||
@@ -70,9 +70,9 @@ impl<D: DataProvider> FormEditor<D> {
|
|||||||
self.ui_state.mode()
|
self.ui_state.mode()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if autocomplete is active (for user's logic)
|
/// Check if suggestions dropdown is active (for user's logic)
|
||||||
pub fn is_autocomplete_active(&self) -> bool {
|
pub fn is_suggestions_active(&self) -> bool {
|
||||||
self.ui_state.is_autocomplete_active()
|
self.ui_state.is_suggestions_active()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get current field text (convenience method)
|
/// 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)
|
/// Trigger suggestions (async because it fetches data)
|
||||||
pub async fn trigger_autocomplete<A>(&mut self, provider: &mut A) -> Result<()>
|
pub async fn trigger_suggestions<A>(&mut self, provider: &mut A) -> Result<()>
|
||||||
where
|
where
|
||||||
A: AutocompleteProvider,
|
A: SuggestionsProvider,
|
||||||
{
|
{
|
||||||
let field_index = self.ui_state.current_field;
|
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(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activate autocomplete UI
|
// Activate suggestions UI
|
||||||
self.ui_state.activate_autocomplete(field_index);
|
self.ui_state.activate_suggestions(field_index);
|
||||||
|
|
||||||
// Fetch suggestions from user (no conversion needed!)
|
// Fetch suggestions from user (no conversion needed!)
|
||||||
let query = self.current_text();
|
let query = self.current_text();
|
||||||
self.suggestions = provider.fetch_suggestions(field_index, query).await?;
|
self.suggestions = provider.fetch_suggestions(field_index, query).await?;
|
||||||
|
|
||||||
// Update UI state
|
// Update UI state
|
||||||
self.ui_state.autocomplete.is_loading = false;
|
self.ui_state.suggestions.is_loading = false;
|
||||||
if !self.suggestions.is_empty() {
|
if !self.suggestions.is_empty() {
|
||||||
self.ui_state.autocomplete.selected_index = Some(0);
|
self.ui_state.suggestions.selected_index = Some(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Navigate autocomplete suggestions
|
/// Navigate suggestions
|
||||||
pub fn autocomplete_next(&mut self) {
|
pub fn suggestions_next(&mut self) {
|
||||||
if !self.ui_state.autocomplete.is_active || self.suggestions.is_empty() {
|
if !self.ui_state.suggestions.is_active || self.suggestions.is_empty() {
|
||||||
return;
|
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();
|
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
|
/// Apply selected suggestion
|
||||||
pub fn apply_autocomplete(&mut self) -> Option<String> {
|
/// Apply selected suggestion
|
||||||
if let Some(selected_index) = self.ui_state.autocomplete.selected_index {
|
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() {
|
if let Some(suggestion) = self.suggestions.get(selected_index).cloned() {
|
||||||
let field_index = self.ui_state.current_field;
|
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.cursor_pos = suggestion.value_to_store.len();
|
||||||
self.ui_state.ideal_cursor_column = self.ui_state.cursor_pos;
|
self.ui_state.ideal_cursor_column = self.ui_state.cursor_pos;
|
||||||
|
|
||||||
// Close autocomplete
|
// Close suggestions
|
||||||
self.ui_state.deactivate_autocomplete();
|
self.ui_state.deactivate_suggestions();
|
||||||
self.suggestions.clear();
|
self.suggestions.clear();
|
||||||
|
|
||||||
// Validate the new content if validation is enabled
|
// Validate the new content if validation is enabled
|
||||||
@@ -951,8 +952,8 @@ impl<D: DataProvider> FormEditor<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.set_mode(AppMode::ReadOnly);
|
self.set_mode(AppMode::ReadOnly);
|
||||||
// Deactivate autocomplete when exiting edit mode
|
// Deactivate suggestions when exiting edit mode
|
||||||
self.ui_state.deactivate_autocomplete();
|
self.ui_state.deactivate_suggestions();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ pub mod canvas;
|
|||||||
pub mod editor;
|
pub mod editor;
|
||||||
pub mod data_provider;
|
pub mod data_provider;
|
||||||
|
|
||||||
// Only include autocomplete module if feature is enabled
|
// Only include suggestions module if feature is enabled
|
||||||
#[cfg(feature = "autocomplete")]
|
#[cfg(feature = "suggestions")]
|
||||||
pub mod autocomplete;
|
pub mod suggestions;
|
||||||
|
|
||||||
// Only include validation module if feature is enabled
|
// Only include validation module if feature is enabled
|
||||||
#[cfg(feature = "validation")]
|
#[cfg(feature = "validation")]
|
||||||
@@ -21,7 +21,7 @@ pub use canvas::CursorManager;
|
|||||||
|
|
||||||
// Main API exports
|
// Main API exports
|
||||||
pub use editor::FormEditor;
|
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)
|
// UI state (read-only access for users)
|
||||||
pub use canvas::state::EditorState;
|
pub use canvas::state::EditorState;
|
||||||
@@ -51,5 +51,5 @@ pub use canvas::gui::render_canvas;
|
|||||||
#[cfg(feature = "gui")]
|
#[cfg(feature = "gui")]
|
||||||
pub use canvas::gui::render_canvas_default;
|
pub use canvas::gui::render_canvas_default;
|
||||||
|
|
||||||
#[cfg(all(feature = "gui", feature = "autocomplete"))]
|
#[cfg(all(feature = "gui", feature = "suggestions"))]
|
||||||
pub use autocomplete::gui::render_autocomplete_dropdown;
|
pub use suggestions::gui::render_suggestions_dropdown;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// src/autocomplete/gui.rs
|
// src/suggestions/gui.rs
|
||||||
//! Autocomplete GUI updated to work with FormEditor
|
//! Suggestions dropdown GUI (not inline autocomplete) updated to work with FormEditor
|
||||||
|
|
||||||
#[cfg(feature = "gui")]
|
#[cfg(feature = "gui")]
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
@@ -17,9 +17,9 @@ use crate::editor::FormEditor;
|
|||||||
#[cfg(feature = "gui")]
|
#[cfg(feature = "gui")]
|
||||||
use unicode_width::UnicodeWidthStr;
|
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")]
|
#[cfg(feature = "gui")]
|
||||||
pub fn render_autocomplete_dropdown<T: CanvasTheme, D: DataProvider>(
|
pub fn render_suggestions_dropdown<T: CanvasTheme, D: DataProvider>(
|
||||||
f: &mut Frame,
|
f: &mut Frame,
|
||||||
frame_area: Rect,
|
frame_area: Rect,
|
||||||
input_rect: Rect,
|
input_rect: Rect,
|
||||||
@@ -28,14 +28,14 @@ pub fn render_autocomplete_dropdown<T: CanvasTheme, D: DataProvider>(
|
|||||||
) {
|
) {
|
||||||
let ui_state = editor.ui_state();
|
let ui_state = editor.ui_state();
|
||||||
|
|
||||||
if !ui_state.is_autocomplete_active() {
|
if !ui_state.is_suggestions_active() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui_state.autocomplete.is_loading {
|
if ui_state.suggestions.is_loading {
|
||||||
render_loading_indicator(f, frame_area, input_rect, theme);
|
render_loading_indicator(f, frame_area, input_rect, theme);
|
||||||
} else if !editor.suggestions().is_empty() {
|
} 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
|
/// Show actual suggestions list
|
||||||
#[cfg(feature = "gui")]
|
#[cfg(feature = "gui")]
|
||||||
fn render_suggestions_dropdown<T: CanvasTheme>(
|
fn render_suggestions_dropdown_list<T: CanvasTheme>(
|
||||||
f: &mut Frame,
|
f: &mut Frame,
|
||||||
frame_area: Rect,
|
frame_area: Rect,
|
||||||
input_rect: Rect,
|
input_rect: Rect,
|
||||||
12
canvas/src/suggestions/mod.rs
Normal file
12
canvas/src/suggestions/mod.rs
Normal 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;
|
||||||
5
canvas/src/suggestions/state.rs
Normal file
5
canvas/src/suggestions/state.rs
Normal 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};
|
||||||
@@ -27,7 +27,7 @@ show_module() {
|
|||||||
|
|
||||||
# Main modules
|
# Main modules
|
||||||
show_module "canvas" "CANVAS SYSTEM"
|
show_module "canvas" "CANVAS SYSTEM"
|
||||||
show_module "autocomplete" "AUTOCOMPLETE SYSTEM"
|
show_module "suggestions" "SUGGESTIONS SYSTEM"
|
||||||
show_module "config" "CONFIGURATION SYSTEM"
|
show_module "config" "CONFIGURATION SYSTEM"
|
||||||
|
|
||||||
# Show lib.rs and other root files
|
# Show lib.rs and other root files
|
||||||
@@ -45,7 +45,7 @@ fi
|
|||||||
echo -e "\n\033[1;36m=========================================="
|
echo -e "\n\033[1;36m=========================================="
|
||||||
echo "To view specific module documentation:"
|
echo "To view specific module documentation:"
|
||||||
echo " ./view_canvas_docs.sh canvas"
|
echo " ./view_canvas_docs.sh canvas"
|
||||||
echo " ./view_canvas_docs.sh autocomplete"
|
echo " ./view_canvas_docs.sh suggestions"
|
||||||
echo " ./view_canvas_docs.sh config"
|
echo " ./view_canvas_docs.sh config"
|
||||||
echo "==========================================\033[0m"
|
echo "==========================================\033[0m"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user