library is now conflicting with client and its breaking it, but lets change the client
This commit is contained in:
93
canvas/src/autocomplete/actions.rs
Normal file
93
canvas/src/autocomplete/actions.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
// src/autocomplete/actions.rs
|
||||
|
||||
use crate::canvas::state::{CanvasState, ActionContext};
|
||||
use crate::autocomplete::state::AutocompleteCanvasState;
|
||||
use crate::canvas::actions::types::{CanvasAction, ActionResult};
|
||||
use crate::canvas::actions::edit::handle_generic_canvas_action; // Import the core function
|
||||
use anyhow::Result;
|
||||
|
||||
/// Version for states that implement rich autocomplete
|
||||
pub async fn execute_canvas_action_with_autocomplete<S: CanvasState + AutocompleteCanvasState>(
|
||||
action: CanvasAction,
|
||||
state: &mut S,
|
||||
ideal_cursor_column: &mut usize,
|
||||
) -> Result<ActionResult> {
|
||||
// 1. Try feature-specific handler first
|
||||
let context = ActionContext {
|
||||
key_code: None,
|
||||
ideal_cursor_column: *ideal_cursor_column,
|
||||
current_input: state.get_current_input().to_string(),
|
||||
current_field: state.current_field(),
|
||||
};
|
||||
|
||||
if let Some(result) = state.handle_feature_action(&action, &context) {
|
||||
return Ok(ActionResult::HandledByFeature(result));
|
||||
}
|
||||
|
||||
// 2. Handle rich autocomplete actions
|
||||
if let Some(result) = handle_rich_autocomplete_action(&action, state)? {
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
// 3. Handle generic canvas actions
|
||||
handle_generic_canvas_action(action, state, ideal_cursor_column).await
|
||||
}
|
||||
|
||||
/// Handle rich autocomplete actions for AutocompleteCanvasState
|
||||
fn handle_rich_autocomplete_action<S: CanvasState + AutocompleteCanvasState>(
|
||||
action: &CanvasAction,
|
||||
state: &mut S,
|
||||
) -> Result<Option<ActionResult>> {
|
||||
match action {
|
||||
CanvasAction::TriggerAutocomplete => {
|
||||
if state.supports_autocomplete(state.current_field()) {
|
||||
state.activate_autocomplete();
|
||||
Ok(Some(ActionResult::success_with_message("Autocomplete activated - fetching suggestions...")))
|
||||
} else {
|
||||
Ok(Some(ActionResult::error("Autocomplete not supported for this field")))
|
||||
}
|
||||
}
|
||||
|
||||
CanvasAction::SuggestionDown => {
|
||||
if state.is_autocomplete_ready() {
|
||||
if let Some(autocomplete_state) = state.autocomplete_state_mut() {
|
||||
autocomplete_state.select_next();
|
||||
return Ok(Some(ActionResult::success()));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
CanvasAction::SuggestionUp => {
|
||||
if state.is_autocomplete_ready() {
|
||||
if let Some(autocomplete_state) = state.autocomplete_state_mut() {
|
||||
autocomplete_state.select_previous();
|
||||
return Ok(Some(ActionResult::success()));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
CanvasAction::SelectSuggestion => {
|
||||
if state.is_autocomplete_ready() {
|
||||
if let Some(message) = state.apply_autocomplete_selection() {
|
||||
return Ok(Some(ActionResult::success_with_message(message)));
|
||||
} else {
|
||||
return Ok(Some(ActionResult::error("No suggestion selected")));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
CanvasAction::ExitSuggestions => {
|
||||
if state.is_autocomplete_active() {
|
||||
state.deactivate_autocomplete();
|
||||
Ok(Some(ActionResult::success_with_message("Autocomplete cancelled")))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ use ratatui::{
|
||||
Frame,
|
||||
};
|
||||
|
||||
use crate::autocomplete::AutocompleteState;
|
||||
use crate::autocomplete::types::AutocompleteState;
|
||||
|
||||
#[cfg(feature = "gui")]
|
||||
use crate::canvas::theme::CanvasTheme;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// src/autocomplete/mod.rs
|
||||
pub mod types;
|
||||
pub mod gui;
|
||||
pub mod state; // Add this line
|
||||
pub mod state;
|
||||
pub mod actions;
|
||||
|
||||
// Re-export autocomplete types
|
||||
pub use types::{SuggestionItem, AutocompleteState};
|
||||
pub use state::AutocompleteCanvasState; // Add this line
|
||||
pub use state::AutocompleteCanvasState;
|
||||
pub use actions::execute_canvas_action_with_autocomplete;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// canvas/src/actions/edit.rs
|
||||
|
||||
use crate::canvas::state::{CanvasState, ActionContext};
|
||||
use crate::autocomplete::state::AutocompleteCanvasState;
|
||||
use crate::canvas::actions::types::{CanvasAction, ActionResult};
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use anyhow::Result;
|
||||
|
||||
/// Execute a typed canvas action on any CanvasState implementation
|
||||
@@ -12,7 +10,6 @@ pub async fn execute_canvas_action<S: CanvasState>(
|
||||
state: &mut S,
|
||||
ideal_cursor_column: &mut usize,
|
||||
) -> Result<ActionResult> {
|
||||
// 1. Try feature-specific handler first
|
||||
let context = ActionContext {
|
||||
key_code: None,
|
||||
ideal_cursor_column: *ideal_cursor_column,
|
||||
@@ -24,195 +21,11 @@ pub async fn execute_canvas_action<S: CanvasState>(
|
||||
return Ok(ActionResult::HandledByFeature(result));
|
||||
}
|
||||
|
||||
// 2. Handle autocomplete actions (falls back to legacy methods)
|
||||
if let Some(result) = handle_autocomplete_action(&action, state)? {
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
// 3. Handle generic canvas actions
|
||||
handle_generic_canvas_action(action, state, ideal_cursor_column).await
|
||||
}
|
||||
|
||||
/// Version for states that implement rich autocomplete
|
||||
pub async fn execute_canvas_action_with_autocomplete<S: CanvasState + AutocompleteCanvasState>(
|
||||
action: CanvasAction,
|
||||
state: &mut S,
|
||||
ideal_cursor_column: &mut usize,
|
||||
) -> Result<ActionResult> {
|
||||
// 1. Try feature-specific handler first
|
||||
let context = ActionContext {
|
||||
key_code: None,
|
||||
ideal_cursor_column: *ideal_cursor_column,
|
||||
current_input: state.get_current_input().to_string(),
|
||||
current_field: state.current_field(),
|
||||
};
|
||||
|
||||
if let Some(result) = state.handle_feature_action(&action, &context) {
|
||||
return Ok(ActionResult::HandledByFeature(result));
|
||||
}
|
||||
|
||||
// 2. Handle rich autocomplete actions
|
||||
if let Some(result) = handle_rich_autocomplete_action(&action, state)? {
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
// 3. Handle generic canvas actions
|
||||
handle_generic_canvas_action(action, state, ideal_cursor_column).await
|
||||
}
|
||||
|
||||
/// Legacy function for string-based actions (backwards compatibility)
|
||||
pub async fn execute_edit_action<S: CanvasState>(
|
||||
action: &str,
|
||||
key: KeyEvent,
|
||||
state: &mut S,
|
||||
ideal_cursor_column: &mut usize,
|
||||
) -> Result<String> {
|
||||
let typed_action = match action {
|
||||
"insert_char" => {
|
||||
if let KeyCode::Char(c) = key.code {
|
||||
CanvasAction::InsertChar(c)
|
||||
} else {
|
||||
return Ok("Error: insert_char called without a char key.".to_string());
|
||||
}
|
||||
}
|
||||
_ => CanvasAction::from_string(action),
|
||||
};
|
||||
|
||||
let result = execute_canvas_action(typed_action, state, ideal_cursor_column).await?;
|
||||
|
||||
// Convert ActionResult back to string for backwards compatibility
|
||||
Ok(result.message().unwrap_or("").to_string())
|
||||
}
|
||||
|
||||
/// Handle autocomplete actions for basic CanvasState (uses legacy methods)
|
||||
fn handle_autocomplete_action<S: CanvasState>(
|
||||
action: &CanvasAction,
|
||||
state: &mut S,
|
||||
) -> Result<Option<ActionResult>> {
|
||||
match action {
|
||||
CanvasAction::TriggerAutocomplete => {
|
||||
// For basic CanvasState, just return an error or no-op
|
||||
Ok(Some(ActionResult::error("Autocomplete not supported - implement AutocompleteCanvasState for rich autocomplete")))
|
||||
}
|
||||
|
||||
CanvasAction::SuggestionDown => {
|
||||
// Try legacy suggestions
|
||||
if let Some(suggestions) = state.get_suggestions() {
|
||||
if !suggestions.is_empty() {
|
||||
let current = state.get_selected_suggestion_index().unwrap_or(0);
|
||||
let next = (current + 1) % suggestions.len();
|
||||
state.set_selected_suggestion_index(Some(next));
|
||||
return Ok(Some(ActionResult::success()));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
CanvasAction::SuggestionUp => {
|
||||
// Try legacy suggestions
|
||||
if let Some(suggestions) = state.get_suggestions() {
|
||||
if !suggestions.is_empty() {
|
||||
let current = state.get_selected_suggestion_index().unwrap_or(0);
|
||||
let prev = if current == 0 { suggestions.len() - 1 } else { current - 1 };
|
||||
state.set_selected_suggestion_index(Some(prev));
|
||||
return Ok(Some(ActionResult::success()));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
CanvasAction::SelectSuggestion => {
|
||||
// Try legacy suggestions
|
||||
if let Some(suggestions) = state.get_suggestions() {
|
||||
if let Some(index) = state.get_selected_suggestion_index() {
|
||||
if let Some(selected) = suggestions.get(index) {
|
||||
// Clone the string first to avoid borrowing issues
|
||||
let selected_text = selected.clone();
|
||||
|
||||
// Now we can mutate state without holding any references
|
||||
*state.get_current_input_mut() = selected_text.clone();
|
||||
state.set_current_cursor_pos(selected_text.len());
|
||||
state.set_has_unsaved_changes(true);
|
||||
state.deactivate_suggestions();
|
||||
return Ok(Some(ActionResult::success_with_message(
|
||||
format!("Selected: {}", selected_text)
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
CanvasAction::ExitSuggestions => {
|
||||
state.deactivate_suggestions();
|
||||
Ok(Some(ActionResult::success_with_message("Suggestions cancelled")))
|
||||
}
|
||||
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle rich autocomplete actions for AutocompleteCanvasState
|
||||
fn handle_rich_autocomplete_action<S: CanvasState + AutocompleteCanvasState>(
|
||||
action: &CanvasAction,
|
||||
state: &mut S,
|
||||
) -> Result<Option<ActionResult>> {
|
||||
match action {
|
||||
CanvasAction::TriggerAutocomplete => {
|
||||
if state.supports_autocomplete(state.current_field()) {
|
||||
state.activate_autocomplete();
|
||||
Ok(Some(ActionResult::success_with_message("Autocomplete activated - fetching suggestions...")))
|
||||
} else {
|
||||
Ok(Some(ActionResult::error("Autocomplete not supported for this field")))
|
||||
}
|
||||
}
|
||||
|
||||
CanvasAction::SuggestionDown => {
|
||||
if state.is_autocomplete_ready() {
|
||||
if let Some(autocomplete_state) = state.autocomplete_state_mut() {
|
||||
autocomplete_state.select_next();
|
||||
return Ok(Some(ActionResult::success()));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
CanvasAction::SuggestionUp => {
|
||||
if state.is_autocomplete_ready() {
|
||||
if let Some(autocomplete_state) = state.autocomplete_state_mut() {
|
||||
autocomplete_state.select_previous();
|
||||
return Ok(Some(ActionResult::success()));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
CanvasAction::SelectSuggestion => {
|
||||
if state.is_autocomplete_ready() {
|
||||
if let Some(message) = state.apply_autocomplete_selection() {
|
||||
return Ok(Some(ActionResult::success_with_message(message)));
|
||||
} else {
|
||||
return Ok(Some(ActionResult::error("No suggestion selected")));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
CanvasAction::ExitSuggestions => {
|
||||
if state.is_autocomplete_active() {
|
||||
state.deactivate_autocomplete();
|
||||
Ok(Some(ActionResult::success_with_message("Autocomplete cancelled")))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle core canvas actions with full type safety
|
||||
async fn handle_generic_canvas_action<S: CanvasState>(
|
||||
pub async fn handle_generic_canvas_action<S: CanvasState>(
|
||||
action: CanvasAction,
|
||||
state: &mut S,
|
||||
ideal_cursor_column: &mut usize,
|
||||
@@ -432,9 +245,10 @@ async fn handle_generic_canvas_action<S: CanvasState>(
|
||||
Ok(ActionResult::error(format!("Unknown or unhandled custom action: {}", action_str)))
|
||||
}
|
||||
|
||||
// Autocomplete actions are handled by the autocomplete module
|
||||
CanvasAction::TriggerAutocomplete | CanvasAction::SuggestionUp | CanvasAction::SuggestionDown |
|
||||
CanvasAction::SelectSuggestion | CanvasAction::ExitSuggestions => {
|
||||
Ok(ActionResult::error("Autocomplete action not handled properly"))
|
||||
Ok(ActionResult::error("Autocomplete actions should be handled by autocomplete module"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// canvas/src/actions/mod.rs
|
||||
|
||||
// canvas/src/canvas/actions/mod.rs
|
||||
pub mod types;
|
||||
pub mod edit;
|
||||
|
||||
// Re-export the main types for convenience
|
||||
pub use types::{CanvasAction, ActionResult};
|
||||
pub use edit::{execute_canvas_action, execute_edit_action};
|
||||
pub use edit::execute_canvas_action; // Remove execute_edit_action
|
||||
|
||||
@@ -3,12 +3,15 @@ pub mod actions;
|
||||
pub mod modes;
|
||||
pub mod gui;
|
||||
pub mod theme;
|
||||
pub mod state; // Add this line
|
||||
pub mod state;
|
||||
|
||||
// Re-export commonly used canvas types
|
||||
pub use actions::{CanvasAction, ActionResult};
|
||||
pub use modes::{AppMode, ModeManager, HighlightState};
|
||||
pub use state::{CanvasState, ActionContext}; // Add this line
|
||||
pub use state::{CanvasState, ActionContext};
|
||||
|
||||
#[cfg(feature = "gui")]
|
||||
pub use theme::CanvasTheme;
|
||||
|
||||
#[cfg(feature = "gui")]
|
||||
pub use gui::render_canvas;
|
||||
|
||||
@@ -31,33 +31,6 @@ pub trait CanvasState {
|
||||
fn has_unsaved_changes(&self) -> bool;
|
||||
fn set_has_unsaved_changes(&mut self, changed: bool);
|
||||
|
||||
// --- LEGACY AUTOCOMPLETE SUPPORT (for backwards compatibility) ---
|
||||
|
||||
/// Legacy suggestion support (deprecated - use AutocompleteCanvasState for rich features)
|
||||
fn get_suggestions(&self) -> Option<&[String]> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Legacy selected suggestion index (deprecated)
|
||||
fn get_selected_suggestion_index(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Legacy suggestion index setter (deprecated)
|
||||
fn set_selected_suggestion_index(&mut self, _index: Option<usize>) {
|
||||
// Default: no-op
|
||||
}
|
||||
|
||||
/// Legacy activate suggestions (deprecated)
|
||||
fn activate_suggestions(&mut self, _suggestions: Vec<String>) {
|
||||
// Default: no-op
|
||||
}
|
||||
|
||||
/// Legacy deactivate suggestions (deprecated)
|
||||
fn deactivate_suggestions(&mut self) {
|
||||
// Default: no-op
|
||||
}
|
||||
|
||||
// --- Feature-specific action handling ---
|
||||
|
||||
/// Feature-specific action handling (NEW: Type-safe)
|
||||
@@ -65,25 +38,7 @@ pub trait CanvasState {
|
||||
None // Default: no feature-specific handling
|
||||
}
|
||||
|
||||
/// Legacy string-based action handling (for backwards compatibility)
|
||||
fn handle_feature_action_legacy(&mut self, action: &str, context: &ActionContext) -> Option<String> {
|
||||
// Convert string to typed action and delegate
|
||||
let typed_action = match action {
|
||||
"insert_char" => {
|
||||
// This is tricky - we need the char from the KeyCode in context
|
||||
if let Some(crossterm::event::KeyCode::Char(c)) = context.key_code {
|
||||
CanvasAction::InsertChar(c)
|
||||
} else {
|
||||
CanvasAction::Custom(action.to_string())
|
||||
}
|
||||
}
|
||||
_ => CanvasAction::from_string(action),
|
||||
};
|
||||
self.handle_feature_action(&typed_action, context)
|
||||
}
|
||||
|
||||
// --- Display Overrides (for links, computed values, etc.) ---
|
||||
|
||||
fn get_display_value_for_field(&self, index: usize) -> &str {
|
||||
self.inputs()
|
||||
.get(index)
|
||||
|
||||
@@ -3,17 +3,3 @@ pub mod canvas;
|
||||
pub mod autocomplete;
|
||||
pub mod config;
|
||||
pub mod dispatcher;
|
||||
pub mod suggestions; // Keep for backwards compatibility
|
||||
|
||||
// Re-export from modules
|
||||
pub use canvas::{CanvasAction, ActionResult, AppMode, ModeManager, HighlightState};
|
||||
|
||||
#[cfg(feature = "gui")]
|
||||
pub use canvas::CanvasTheme;
|
||||
|
||||
pub use autocomplete::{SuggestionItem, AutocompleteState};
|
||||
pub use dispatcher::ActionDispatcher;
|
||||
pub use canvas::state::{CanvasState, ActionContext}; // Fixed path
|
||||
|
||||
// Backwards compatibility
|
||||
pub use suggestions::SuggestionState;
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
// canvas/src/suggestions.rs
|
||||
|
||||
/// Generic suggestion system that can be implemented by any CanvasState
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SuggestionState {
|
||||
pub suggestions: Vec<String>,
|
||||
pub selected_index: Option<usize>,
|
||||
pub is_active: bool,
|
||||
pub trigger_chars: Vec<char>, // Characters that trigger suggestions
|
||||
}
|
||||
|
||||
impl Default for SuggestionState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
suggestions: Vec::new(),
|
||||
selected_index: None,
|
||||
is_active: false,
|
||||
trigger_chars: vec![], // No auto-trigger by default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SuggestionState {
|
||||
pub fn new(trigger_chars: Vec<char>) -> Self {
|
||||
Self {
|
||||
trigger_chars,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn activate_with_suggestions(&mut self, suggestions: Vec<String>) {
|
||||
self.suggestions = suggestions;
|
||||
self.is_active = !self.suggestions.is_empty();
|
||||
self.selected_index = if self.is_active { Some(0) } else { None };
|
||||
}
|
||||
|
||||
pub fn deactivate(&mut self) {
|
||||
self.suggestions.clear();
|
||||
self.selected_index = None;
|
||||
self.is_active = false;
|
||||
}
|
||||
|
||||
pub fn select_next(&mut self) {
|
||||
if !self.suggestions.is_empty() {
|
||||
let current = self.selected_index.unwrap_or(0);
|
||||
self.selected_index = Some((current + 1) % self.suggestions.len());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_previous(&mut self) {
|
||||
if !self.suggestions.is_empty() {
|
||||
let current = self.selected_index.unwrap_or(0);
|
||||
self.selected_index = Some(
|
||||
if current == 0 { self.suggestions.len() - 1 } else { current - 1 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_selected(&self) -> Option<&String> {
|
||||
self.selected_index
|
||||
.and_then(|idx| self.suggestions.get(idx))
|
||||
}
|
||||
|
||||
pub fn should_trigger(&self, c: char) -> bool {
|
||||
self.trigger_chars.contains(&c)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/components/form/form.rs
|
||||
use crate::components::common::autocomplete;
|
||||
use crate::config::colors::themes::Theme;
|
||||
use canvas::{CanvasState, render_canvas, HighlightState}; // CHANGED: Import HighlightState from canvas
|
||||
use canvas::canvas::{CanvasState, render_canvas, HighlightState};
|
||||
use crate::state::pages::form::FormState;
|
||||
use ratatui::{
|
||||
layout::{Alignment, Constraint, Direction, Layout, Margin, Rect},
|
||||
|
||||
Reference in New Issue
Block a user