working autocomplete, need more fixes soon

This commit is contained in:
filipriec
2025-05-26 11:54:28 +02:00
parent 116db3566f
commit 3463a52960
3 changed files with 346 additions and 25 deletions

View File

@@ -14,15 +14,14 @@ use ratatui::{
use crate::components::handlers::canvas::render_canvas;
use crate::components::common::{dialog, autocomplete}; // Added autocomplete
use crate::config::binds::config::EditorKeybindingMode;
use crate::modes::handlers::mode_manager::AppMode; // For checking AppMode::Edit
pub fn render_add_logic(
f: &mut Frame,
area: Rect,
theme: &Theme,
app_state: &AppState,
add_logic_state: &mut AddLogicState, // Changed to &mut
is_edit_mode: bool, // This is the general edit mode from EventHandler
add_logic_state: &mut AddLogicState,
is_edit_mode: bool,
highlight_state: &HighlightState,
) {
let main_block = Block::default()
@@ -47,13 +46,10 @@ pub fn render_add_logic(
let script_title_hint = match add_logic_state.editor_keybinding_mode {
EditorKeybindingMode::Vim => {
let vim_mode_status = crate::components::common::text_editor::TextEditor::get_vim_mode_status(&add_logic_state.vim_state);
// Vim mode status is relevant regardless of the general `is_edit_mode`
format!("Script {}", vim_mode_status)
}
EditorKeybindingMode::Emacs | EditorKeybindingMode::Default => {
// For default/emacs, the general `is_edit_mode` (passed to this function)
// indicates if the text area itself is in an "editing" state.
if is_edit_mode { // This `is_edit_mode` refers to the text area's active editing.
if is_edit_mode {
"Script (Editing)".to_string()
} else {
"Script".to_string()
@@ -70,7 +66,46 @@ pub fn render_add_logic(
.border_style(border_style),
);
f.render_widget(&*editor_ref, inner_area);
return;
// Drop the editor borrow before accessing autocomplete state
drop(editor_ref);
// === SCRIPT EDITOR AUTOCOMPLETE RENDERING ===
if add_logic_state.script_editor_autocomplete_active && !add_logic_state.script_editor_suggestions.is_empty() {
if let Some((trigger_line, trigger_col)) = add_logic_state.script_editor_trigger_position {
// For now, we'll position the autocomplete at a simple offset from the trigger
// Since we can't easily get viewport info, we'll position it relatively
// This is a simplified approach - in a real implementation you'd want proper viewport tracking
// Account for TextArea's block borders (1 for each side)
let block_offset_x = 1;
let block_offset_y = 1;
// Simple positioning: assume trigger is visible and use direct coordinates
// This works for small scripts but may need improvement for larger ones
let visible_line = trigger_line;
let visible_col = trigger_col + 1 + add_logic_state.script_editor_filter_text.len();
let input_rect = Rect {
x: (inner_area.x + block_offset_x + visible_col as u16).min(inner_area.right().saturating_sub(20)),
y: (inner_area.y + block_offset_y + visible_line as u16 + 1).min(inner_area.bottom().saturating_sub(5)),
width: 1, // Minimum width for positioning
height: 1,
};
// Render autocomplete dropdown
autocomplete::render_autocomplete_dropdown(
f,
input_rect,
f.area(), // Full frame area for clamping
theme,
&add_logic_state.script_editor_suggestions,
add_logic_state.script_editor_selected_suggestion_index,
);
}
}
return; // Exit early for fullscreen mode
}
// Regular layout with preview

View File

@@ -21,13 +21,217 @@ pub fn handle_add_logic_navigation(
add_logic_state: &mut AddLogicState,
is_edit_mode: &mut bool,
buffer_state: &mut BufferState,
grpc_client: GrpcClient,
save_logic_sender: SaveLogicResultSender,
_grpc_client: GrpcClient,
_save_logic_sender: SaveLogicResultSender,
command_message: &mut String,
) -> bool {
// === FULLSCREEN SCRIPT EDITING - COMPLETE ISOLATION ===
if add_logic_state.current_focus == AddLogicFocus::InsideScriptContent {
let mut editor_borrow = add_logic_state.script_content_editor.borrow_mut();
// === AUTOCOMPLETE HANDLING ===
if add_logic_state.script_editor_autocomplete_active {
match key_event.code {
KeyCode::Char(c) if c.is_alphanumeric() || c == '_' => {
// Update filter text first
add_logic_state.script_editor_filter_text.push(c);
add_logic_state.update_script_editor_suggestions();
// Then handle editor input
{
let mut editor_borrow = add_logic_state.script_content_editor.borrow_mut();
TextEditor::handle_input(
&mut editor_borrow,
key_event,
&add_logic_state.editor_keybinding_mode,
&mut add_logic_state.vim_state,
);
} // Drop editor borrow
*command_message = format!("Filtering: @{}", add_logic_state.script_editor_filter_text);
return true;
}
KeyCode::Backspace => {
if !add_logic_state.script_editor_filter_text.is_empty() {
// Remove last character from filter
add_logic_state.script_editor_filter_text.pop();
add_logic_state.update_script_editor_suggestions();
// Then handle editor input
{
let mut editor_borrow = add_logic_state.script_content_editor.borrow_mut();
TextEditor::handle_input(
&mut editor_borrow,
key_event,
&add_logic_state.editor_keybinding_mode,
&mut add_logic_state.vim_state,
);
} // Drop editor borrow
*command_message = if add_logic_state.script_editor_filter_text.is_empty() {
"Autocomplete: @".to_string()
} else {
format!("Filtering: @{}", add_logic_state.script_editor_filter_text)
};
} else {
// Check if we're deleting the @ trigger
let should_deactivate = if let Some((trigger_line, trigger_col)) = add_logic_state.script_editor_trigger_position {
let current_cursor = {
let editor_borrow = add_logic_state.script_content_editor.borrow();
editor_borrow.cursor()
};
current_cursor.0 == trigger_line && current_cursor.1 == trigger_col + 1
} else {
false
};
if should_deactivate {
add_logic_state.deactivate_script_editor_autocomplete();
*command_message = "Autocomplete cancelled".to_string();
}
// Handle editor input
{
let mut editor_borrow = add_logic_state.script_content_editor.borrow_mut();
TextEditor::handle_input(
&mut editor_borrow,
key_event,
&add_logic_state.editor_keybinding_mode,
&mut add_logic_state.vim_state,
);
} // Drop editor borrow
}
return true;
}
KeyCode::Tab | KeyCode::Down => {
// Navigate suggestions down
if !add_logic_state.script_editor_suggestions.is_empty() {
let current = add_logic_state.script_editor_selected_suggestion_index.unwrap_or(0);
let next = (current + 1) % add_logic_state.script_editor_suggestions.len();
add_logic_state.script_editor_selected_suggestion_index = Some(next);
*command_message = format!("Selected: {}", add_logic_state.script_editor_suggestions[next]);
}
return true; // Consume the key
}
KeyCode::Up => {
// Navigate suggestions up
if !add_logic_state.script_editor_suggestions.is_empty() {
let current = add_logic_state.script_editor_selected_suggestion_index.unwrap_or(0);
let prev = if current == 0 {
add_logic_state.script_editor_suggestions.len() - 1
} else {
current - 1
};
add_logic_state.script_editor_selected_suggestion_index = Some(prev);
*command_message = format!("Selected: {}", add_logic_state.script_editor_suggestions[prev]);
}
return true; // Consume the key
}
KeyCode::Enter => {
// Select current suggestion
if let Some(selected_idx) = add_logic_state.script_editor_selected_suggestion_index {
if let Some(suggestion) = add_logic_state.script_editor_suggestions.get(selected_idx).cloned() {
// Get trigger position and filter length
let trigger_pos = add_logic_state.script_editor_trigger_position;
let filter_len = add_logic_state.script_editor_filter_text.len();
// Deactivate autocomplete first
add_logic_state.deactivate_script_editor_autocomplete();
add_logic_state.has_unsaved_changes = true;
// Then replace text
if let Some(pos) = trigger_pos {
let mut editor_borrow = add_logic_state.script_content_editor.borrow_mut();
replace_autocomplete_text(
&mut editor_borrow,
pos,
filter_len,
&suggestion,
);
}
*command_message = format!("Inserted: {}", suggestion);
return true; // Consume the key
}
}
// If no suggestion selected, pass Enter to editor
add_logic_state.deactivate_script_editor_autocomplete();
{
let mut editor_borrow = add_logic_state.script_content_editor.borrow_mut();
TextEditor::handle_input(
&mut editor_borrow,
key_event,
&add_logic_state.editor_keybinding_mode,
&mut add_logic_state.vim_state,
);
}
return true;
}
KeyCode::Esc => {
// Cancel autocomplete first
add_logic_state.deactivate_script_editor_autocomplete();
*command_message = "Autocomplete cancelled".to_string();
// Then handle normal Esc behavior (vim mode, exit script, etc.)
// Fall through to normal Esc handling below
}
_ => {
// Other keys deactivate autocomplete and pass through
add_logic_state.deactivate_script_editor_autocomplete();
*command_message = "Autocomplete cancelled".to_string();
// Pass key to editor
{
let mut editor_borrow = add_logic_state.script_content_editor.borrow_mut();
TextEditor::handle_input(
&mut editor_borrow,
key_event,
&add_logic_state.editor_keybinding_mode,
&mut add_logic_state.vim_state,
);
}
return true;
}
}
}
// === AUTOCOMPLETE TRIGGER ===
if key_event.code == KeyCode::Char('@') && key_event.modifiers == KeyModifiers::NONE {
// Only trigger in insert mode for Vim, or always for other modes
let should_trigger = match add_logic_state.editor_keybinding_mode {
EditorKeybindingMode::Vim => *is_edit_mode, // Only in Vim insert mode
_ => true, // Always for non-Vim modes when editing
};
if should_trigger {
// Get cursor position before inserting @
let cursor_before = {
let editor_borrow = add_logic_state.script_content_editor.borrow();
editor_borrow.cursor()
};
// Handle editor input first
{
let mut editor_borrow = add_logic_state.script_content_editor.borrow_mut();
TextEditor::handle_input(
&mut editor_borrow,
key_event,
&add_logic_state.editor_keybinding_mode,
&mut add_logic_state.vim_state,
);
} // Drop editor borrow
// Activate autocomplete at the @ position
add_logic_state.script_editor_trigger_position = Some(cursor_before);
add_logic_state.script_editor_autocomplete_active = true;
add_logic_state.script_editor_filter_text.clear();
add_logic_state.update_script_editor_suggestions();
add_logic_state.has_unsaved_changes = true;
*command_message = "Autocomplete: @ (Tab/↑↓ to navigate, Enter to select, Esc to cancel)".to_string();
return true;
}
}
// Handle ONLY Escape to exit fullscreen mode
if key_event.code == KeyCode::Esc && key_event.modifiers == KeyModifiers::NONE {
@@ -35,12 +239,15 @@ pub fn handle_add_logic_navigation(
EditorKeybindingMode::Vim => {
if *is_edit_mode {
// First escape: try to go to Vim Normal mode
TextEditor::handle_input(
&mut editor_borrow,
key_event,
&add_logic_state.editor_keybinding_mode,
&mut add_logic_state.vim_state,
);
{
let mut editor_borrow = add_logic_state.script_content_editor.borrow_mut();
TextEditor::handle_input(
&mut editor_borrow,
key_event,
&add_logic_state.editor_keybinding_mode,
&mut add_logic_state.vim_state,
);
}
if TextEditor::is_vim_normal_mode(&add_logic_state.vim_state) {
*is_edit_mode = false;
*command_message = "VIM: Normal Mode. Esc again to exit script.".to_string();
@@ -70,14 +277,17 @@ pub fn handle_add_logic_navigation(
}
// ALL OTHER KEYS: Pass directly to textarea without any interference
let changed = TextEditor::handle_input(
&mut editor_borrow,
key_event,
&add_logic_state.editor_keybinding_mode,
&mut add_logic_state.vim_state,
);
if changed {
add_logic_state.has_unsaved_changes = true;
let changed = {
let mut editor_borrow = add_logic_state.script_content_editor.borrow_mut();
TextEditor::handle_input(
&mut editor_borrow,
key_event,
&add_logic_state.editor_keybinding_mode,
&mut add_logic_state.vim_state,
)
};
if changed {
add_logic_state.has_unsaved_changes = true;
}
// Update edit mode status for Vim
@@ -236,3 +446,25 @@ pub fn handle_add_logic_navigation(
handled
}
// Helper function for text replacement
fn replace_autocomplete_text(
editor: &mut tui_textarea::TextArea,
trigger_pos: (usize, usize),
filter_len: usize,
replacement: &str,
) {
use tui_textarea::CursorMove;
// Move cursor to the position right after the @ symbol (where filter text starts)
let filter_start_pos = (trigger_pos.0, trigger_pos.1 + 1);
editor.move_cursor(CursorMove::Jump(filter_start_pos.0 as u16, filter_start_pos.1 as u16));
// Delete only the filter text (not the @ symbol)
for _ in 0..filter_len {
editor.delete_next_char();
}
// Insert replacement text (this will be appended to the @ symbol)
editor.insert_str(replacement);
}

View File

@@ -42,6 +42,13 @@ pub struct AddLogicState {
pub show_target_column_suggestions: bool,
pub selected_target_column_suggestion_index: Option<usize>,
pub in_target_column_suggestion_mode: bool,
// Script Editor Autocomplete
pub script_editor_autocomplete_active: bool,
pub script_editor_suggestions: Vec<String>,
pub script_editor_selected_suggestion_index: Option<usize>,
pub script_editor_trigger_position: Option<(usize, usize)>, // (line, column)
pub script_editor_filter_text: String,
}
impl AddLogicState {
@@ -69,6 +76,13 @@ impl AddLogicState {
show_target_column_suggestions: false,
selected_target_column_suggestion_index: None,
in_target_column_suggestion_mode: false,
// Script Editor Autocomplete initialization
script_editor_autocomplete_active: false,
script_editor_suggestions: Vec::new(),
script_editor_selected_suggestion_index: None,
script_editor_trigger_position: None,
script_editor_filter_text: String::new(),
}
}
@@ -114,6 +128,46 @@ impl AddLogicState {
self.selected_target_column_suggestion_index = None;
}
}
/// Updates script editor suggestions based on current filter text
pub fn update_script_editor_suggestions(&mut self) {
let hardcoded_suggestions = vec![
"sql".to_string(),
"tablename".to_string(),
"table column".to_string()
];
if self.script_editor_filter_text.is_empty() {
self.script_editor_suggestions = hardcoded_suggestions;
} else {
let filter_lower = self.script_editor_filter_text.to_lowercase();
self.script_editor_suggestions = hardcoded_suggestions
.into_iter()
.filter(|suggestion| suggestion.to_lowercase().contains(&filter_lower))
.collect();
}
// Update selection index
if self.script_editor_suggestions.is_empty() {
self.script_editor_selected_suggestion_index = None;
self.script_editor_autocomplete_active = false;
} else if let Some(selected_idx) = self.script_editor_selected_suggestion_index {
if selected_idx >= self.script_editor_suggestions.len() {
self.script_editor_selected_suggestion_index = Some(0);
}
} else {
self.script_editor_selected_suggestion_index = Some(0);
}
}
/// Deactivates script editor autocomplete and clears related state
pub fn deactivate_script_editor_autocomplete(&mut self) {
self.script_editor_autocomplete_active = false;
self.script_editor_suggestions.clear();
self.script_editor_selected_suggestion_index = None;
self.script_editor_trigger_position = None;
self.script_editor_filter_text.clear();
}
}
impl Default for AddLogicState {