automcomplete running and working now
This commit is contained in:
@@ -12,16 +12,17 @@ use ratatui::{
|
||||
Frame,
|
||||
};
|
||||
use crate::components::handlers::canvas::render_canvas;
|
||||
use crate::components::common::dialog;
|
||||
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,
|
||||
is_edit_mode: bool, // Used for border/title hints in InsideScriptContent
|
||||
add_logic_state: &mut AddLogicState, // Changed to &mut
|
||||
is_edit_mode: bool, // This is the general edit mode from EventHandler
|
||||
highlight_state: &HighlightState,
|
||||
) {
|
||||
let main_block = Block::default()
|
||||
@@ -41,20 +42,18 @@ pub fn render_add_logic(
|
||||
let border_style = Style::default().fg(border_style_color);
|
||||
|
||||
editor_ref.set_cursor_line_style(Style::default());
|
||||
// Explicitly set to tui-textarea's default "active" editing cursor style
|
||||
editor_ref.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
|
||||
|
||||
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);
|
||||
if is_edit_mode { // is_edit_mode here refers to Vim's Insert mode
|
||||
// Vim mode status is relevant regardless of the general `is_edit_mode`
|
||||
format!("Script {}", vim_mode_status)
|
||||
} else {
|
||||
format!("Script {}", vim_mode_status)
|
||||
}
|
||||
}
|
||||
EditorKeybindingMode::Emacs | EditorKeybindingMode::Default => {
|
||||
if is_edit_mode {
|
||||
// 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.
|
||||
"Script (Editing)".to_string()
|
||||
} else {
|
||||
"Script".to_string()
|
||||
@@ -79,7 +78,7 @@ pub fn render_add_logic(
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
Constraint::Length(3), // Top info
|
||||
Constraint::Length(9), // Canvas
|
||||
Constraint::Length(9), // Canvas for 3 inputs (each 1 line + 1 padding = 2 lines * 3 + 2 border = 8, +1 for good measure)
|
||||
Constraint::Min(5), // Script preview
|
||||
Constraint::Length(3), // Buttons
|
||||
])
|
||||
@@ -123,10 +122,11 @@ pub fn render_add_logic(
|
||||
| AddLogicFocus::InputTargetColumn
|
||||
| AddLogicFocus::InputDescription
|
||||
);
|
||||
render_canvas(
|
||||
// Call render_canvas and get the active_field_rect
|
||||
let active_field_rect = render_canvas(
|
||||
f,
|
||||
canvas_area,
|
||||
add_logic_state,
|
||||
add_logic_state, // Pass the whole state as it impl CanvasState
|
||||
&add_logic_state.fields(),
|
||||
&add_logic_state.current_field(),
|
||||
&add_logic_state.inputs(),
|
||||
@@ -135,6 +135,26 @@ pub fn render_add_logic(
|
||||
highlight_state,
|
||||
);
|
||||
|
||||
// --- Render Autocomplete for Target Column ---
|
||||
// `is_edit_mode` here refers to the general edit mode of the EventHandler
|
||||
if is_edit_mode && add_logic_state.current_field() == 1 { // Target Column field
|
||||
if let Some(suggestions) = add_logic_state.get_suggestions() { // Uses CanvasState impl
|
||||
let selected = add_logic_state.get_selected_suggestion_index();
|
||||
if !suggestions.is_empty() { // Only render if there are suggestions to show
|
||||
if let Some(input_rect) = active_field_rect {
|
||||
autocomplete::render_autocomplete_dropdown(
|
||||
f,
|
||||
input_rect,
|
||||
f.area(), // Full frame area for clamping
|
||||
theme,
|
||||
suggestions,
|
||||
selected,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Script content preview
|
||||
{
|
||||
let mut editor_ref = add_logic_state.script_content_editor.borrow_mut();
|
||||
@@ -143,10 +163,8 @@ pub fn render_add_logic(
|
||||
let is_script_preview_focused = add_logic_state.current_focus == AddLogicFocus::ScriptContentPreview;
|
||||
|
||||
if is_script_preview_focused {
|
||||
// When script PREVIEW is focused, use tui-textarea's default "active" cursor (block-like).
|
||||
editor_ref.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
|
||||
} else {
|
||||
// When script PREVIEW is NOT focused, use an underscore cursor.
|
||||
let underscore_cursor_style = Style::default()
|
||||
.add_modifier(Modifier::UNDERLINED)
|
||||
.fg(theme.secondary);
|
||||
@@ -154,16 +172,12 @@ pub fn render_add_logic(
|
||||
}
|
||||
|
||||
let border_style_color = if is_script_preview_focused {
|
||||
theme.highlight // Green highlight when focused and ready to select
|
||||
theme.highlight
|
||||
} else {
|
||||
theme.secondary
|
||||
};
|
||||
|
||||
let title_text = if is_script_preview_focused {
|
||||
"Script Preview"
|
||||
} else {
|
||||
"Script Preview"
|
||||
};
|
||||
let title_text = "Script Preview"; // Title doesn't need to change based on focus here
|
||||
|
||||
let title_style = if is_script_preview_focused {
|
||||
Style::default().fg(theme.highlight).add_modifier(Modifier::BOLD)
|
||||
@@ -182,8 +196,8 @@ pub fn render_add_logic(
|
||||
}
|
||||
|
||||
// Buttons
|
||||
let get_button_style = |button_focus: AddLogicFocus, current_focus| {
|
||||
let is_focused = current_focus == button_focus;
|
||||
let get_button_style = |button_focus: AddLogicFocus, current_focus_state: AddLogicFocus| {
|
||||
let is_focused = current_focus_state == button_focus;
|
||||
let base_style = Style::default().fg(if is_focused {
|
||||
theme.highlight
|
||||
} else {
|
||||
@@ -196,11 +210,11 @@ pub fn render_add_logic(
|
||||
}
|
||||
};
|
||||
|
||||
let get_button_border_style = |is_focused: bool, theme: &Theme| {
|
||||
let get_button_border_style = |is_focused: bool, current_theme: &Theme| {
|
||||
if is_focused {
|
||||
Style::default().fg(theme.highlight)
|
||||
Style::default().fg(current_theme.highlight)
|
||||
} else {
|
||||
Style::default().fg(theme.secondary)
|
||||
Style::default().fg(current_theme.secondary)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,277 +1,135 @@
|
||||
// src/functions/modes/edit/add_logic_e.rs
|
||||
use crate::state::pages::add_logic::AddLogicState; // Changed
|
||||
use crate::state::pages::add_logic::AddLogicState;
|
||||
use crate::state::pages::canvas_state::CanvasState;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use anyhow::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
|
||||
// Word navigation helpers (get_char_type, find_next_word_start, etc.)
|
||||
// can be kept as they are generic.
|
||||
#[derive(PartialEq)]
|
||||
enum CharType {
|
||||
Whitespace,
|
||||
Alphanumeric,
|
||||
Punctuation,
|
||||
}
|
||||
|
||||
fn get_char_type(c: char) -> CharType {
|
||||
if c.is_whitespace() { CharType::Whitespace }
|
||||
else if c.is_alphanumeric() { CharType::Alphanumeric }
|
||||
else { CharType::Punctuation }
|
||||
}
|
||||
|
||||
fn find_next_word_start(text: &str, current_pos: usize) -> usize {
|
||||
let chars: Vec<char> = text.chars().collect();
|
||||
let len = chars.len();
|
||||
if len == 0 || current_pos >= len { return len; }
|
||||
let mut pos = current_pos;
|
||||
let initial_type = get_char_type(chars[pos]);
|
||||
while pos < len && get_char_type(chars[pos]) == initial_type { pos += 1; }
|
||||
while pos < len && get_char_type(chars[pos]) == CharType::Whitespace { pos += 1; }
|
||||
pos
|
||||
}
|
||||
|
||||
fn find_word_end(text: &str, current_pos: usize) -> usize {
|
||||
let chars: Vec<char> = text.chars().collect();
|
||||
let len = chars.len();
|
||||
if len == 0 { return 0; }
|
||||
let mut pos = current_pos.min(len - 1);
|
||||
if get_char_type(chars[pos]) == CharType::Whitespace {
|
||||
pos = find_next_word_start(text, pos);
|
||||
}
|
||||
if pos >= len { return len.saturating_sub(1); }
|
||||
let word_type = get_char_type(chars[pos]);
|
||||
while pos < len && get_char_type(chars[pos]) == word_type { pos += 1; }
|
||||
pos.saturating_sub(1).min(len.saturating_sub(1))
|
||||
}
|
||||
|
||||
fn find_prev_word_start(text: &str, current_pos: usize) -> usize {
|
||||
let chars: Vec<char> = text.chars().collect();
|
||||
if chars.is_empty() || current_pos == 0 { return 0; }
|
||||
let mut pos = current_pos.saturating_sub(1);
|
||||
while pos > 0 && get_char_type(chars[pos]) == CharType::Whitespace { pos -= 1; }
|
||||
if pos == 0 && get_char_type(chars[pos]) == CharType::Whitespace { return 0; }
|
||||
let word_type = get_char_type(chars[pos]);
|
||||
while pos > 0 && get_char_type(chars[pos - 1]) == word_type { pos -= 1; }
|
||||
pos
|
||||
}
|
||||
|
||||
fn find_prev_word_end(text: &str, current_pos: usize) -> usize {
|
||||
let chars: Vec<char> = text.chars().collect();
|
||||
let len = chars.len();
|
||||
if len == 0 || current_pos == 0 { return 0; }
|
||||
let mut pos = current_pos.saturating_sub(1);
|
||||
while pos > 0 && get_char_type(chars[pos]) == CharType::Whitespace { pos -= 1; }
|
||||
if pos == 0 && get_char_type(chars[pos]) == CharType::Whitespace { return 0; }
|
||||
if pos == 0 && get_char_type(chars[pos]) != CharType::Whitespace { return 0; }
|
||||
let word_type = get_char_type(chars[pos]);
|
||||
while pos > 0 && get_char_type(chars[pos - 1]) == word_type { pos -= 1; }
|
||||
while pos > 0 && get_char_type(chars[pos - 1]) == CharType::Whitespace { pos -= 1; }
|
||||
if pos > 0 { pos - 1 } else { 0 }
|
||||
}
|
||||
|
||||
/// Executes edit actions for the AddLogic view canvas.
|
||||
pub async fn execute_edit_action(
|
||||
action: &str,
|
||||
key: KeyEvent,
|
||||
state: &mut AddLogicState, // Changed
|
||||
key: KeyEvent, // Keep key for insert_char
|
||||
state: &mut AddLogicState,
|
||||
ideal_cursor_column: &mut usize,
|
||||
) -> Result<String> {
|
||||
let mut message = String::new();
|
||||
|
||||
match action {
|
||||
"insert_char" => {
|
||||
if let KeyCode::Char(c) = key.code {
|
||||
let cursor_pos = state.current_cursor_pos();
|
||||
let field_value = state.get_current_input_mut();
|
||||
let mut chars: Vec<char> = field_value.chars().collect();
|
||||
if cursor_pos <= chars.len() {
|
||||
chars.insert(cursor_pos, c);
|
||||
*field_value = chars.into_iter().collect();
|
||||
state.set_current_cursor_pos(cursor_pos + 1);
|
||||
state.set_has_unsaved_changes(true);
|
||||
*ideal_cursor_column = state.current_cursor_pos();
|
||||
}
|
||||
} else {
|
||||
return Ok("Error: insert_char called without a char key.".to_string());
|
||||
}
|
||||
Ok("".to_string())
|
||||
}
|
||||
"delete_char_backward" => {
|
||||
if state.current_cursor_pos() > 0 {
|
||||
let cursor_pos = state.current_cursor_pos();
|
||||
let field_value = state.get_current_input_mut();
|
||||
let mut chars: Vec<char> = field_value.chars().collect();
|
||||
if cursor_pos <= chars.len() {
|
||||
chars.remove(cursor_pos - 1);
|
||||
*field_value = chars.into_iter().collect();
|
||||
let new_pos = cursor_pos - 1;
|
||||
state.set_current_cursor_pos(new_pos);
|
||||
state.set_has_unsaved_changes(true);
|
||||
*ideal_cursor_column = new_pos;
|
||||
}
|
||||
}
|
||||
Ok("".to_string())
|
||||
}
|
||||
"delete_char_forward" => {
|
||||
let cursor_pos = state.current_cursor_pos();
|
||||
let field_value = state.get_current_input_mut();
|
||||
let mut chars: Vec<char> = field_value.chars().collect();
|
||||
if cursor_pos < chars.len() {
|
||||
chars.remove(cursor_pos);
|
||||
*field_value = chars.into_iter().collect();
|
||||
state.set_has_unsaved_changes(true);
|
||||
*ideal_cursor_column = cursor_pos;
|
||||
}
|
||||
Ok("".to_string())
|
||||
}
|
||||
"next_field" => {
|
||||
let num_fields = AddLogicState::INPUT_FIELD_COUNT; // Changed
|
||||
if num_fields > 0 {
|
||||
let current_field = state.current_field();
|
||||
let last_field_index = num_fields - 1;
|
||||
if current_field < last_field_index { // Prevent cycling
|
||||
state.set_current_field(current_field + 1);
|
||||
}
|
||||
let current_input = state.get_current_input();
|
||||
let max_pos = current_input.len();
|
||||
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
|
||||
}
|
||||
Ok("".to_string())
|
||||
let next_field = (current_field + 1) % AddLogicState::INPUT_FIELD_COUNT;
|
||||
state.set_current_field(next_field);
|
||||
*ideal_cursor_column = state.current_cursor_pos();
|
||||
message = format!("Focus on field {}", state.fields()[next_field]);
|
||||
}
|
||||
"prev_field" => {
|
||||
let num_fields = AddLogicState::INPUT_FIELD_COUNT; // Changed
|
||||
if num_fields > 0 {
|
||||
let current_field = state.current_field();
|
||||
if current_field > 0 { // Prevent cycling
|
||||
state.set_current_field(current_field - 1);
|
||||
let prev_field = if current_field == 0 {
|
||||
AddLogicState::INPUT_FIELD_COUNT - 1
|
||||
} else {
|
||||
current_field - 1
|
||||
};
|
||||
state.set_current_field(prev_field);
|
||||
*ideal_cursor_column = state.current_cursor_pos();
|
||||
message = format!("Focus on field {}", state.fields()[prev_field]);
|
||||
}
|
||||
let current_input = state.get_current_input();
|
||||
let max_pos = current_input.len();
|
||||
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
|
||||
"delete_char_forward" => {
|
||||
let current_pos = state.current_cursor_pos();
|
||||
let current_input_mut = state.get_current_input_mut();
|
||||
if current_pos < current_input_mut.len() {
|
||||
current_input_mut.remove(current_pos);
|
||||
state.set_has_unsaved_changes(true);
|
||||
if state.current_field() == 1 { state.update_target_column_suggestions(); }
|
||||
}
|
||||
Ok("".to_string())
|
||||
}
|
||||
"move_left" => {
|
||||
let new_pos = state.current_cursor_pos().saturating_sub(1);
|
||||
"delete_char_backward" => {
|
||||
let current_pos = state.current_cursor_pos();
|
||||
if current_pos > 0 {
|
||||
let new_pos = current_pos - 1;
|
||||
state.get_current_input_mut().remove(new_pos);
|
||||
state.set_current_cursor_pos(new_pos);
|
||||
*ideal_cursor_column = new_pos;
|
||||
Ok("".to_string())
|
||||
state.set_has_unsaved_changes(true);
|
||||
if state.current_field() == 1 { state.update_target_column_suggestions(); }
|
||||
}
|
||||
}
|
||||
"move_left" => {
|
||||
let current_pos = state.current_cursor_pos();
|
||||
if current_pos > 0 {
|
||||
let new_pos = current_pos - 1;
|
||||
state.set_current_cursor_pos(new_pos);
|
||||
*ideal_cursor_column = new_pos;
|
||||
}
|
||||
}
|
||||
"move_right" => {
|
||||
let current_input = state.get_current_input();
|
||||
let current_pos = state.current_cursor_pos();
|
||||
if current_pos < current_input.len() {
|
||||
let input_len = state.get_current_input().len();
|
||||
if current_pos < input_len {
|
||||
let new_pos = current_pos + 1;
|
||||
state.set_current_cursor_pos(new_pos);
|
||||
*ideal_cursor_column = new_pos;
|
||||
}
|
||||
Ok("".to_string())
|
||||
}
|
||||
"move_up" => { // In edit mode, up/down usually means prev/next field
|
||||
let current_field = state.current_field();
|
||||
if current_field > 0 {
|
||||
let new_field = current_field - 1;
|
||||
state.set_current_field(new_field);
|
||||
let current_input = state.get_current_input();
|
||||
let max_pos = current_input.len();
|
||||
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
|
||||
}
|
||||
Ok("".to_string())
|
||||
}
|
||||
"move_down" => { // In edit mode, up/down usually means prev/next field
|
||||
let num_fields = AddLogicState::INPUT_FIELD_COUNT; // Changed
|
||||
if num_fields > 0 {
|
||||
let current_field = state.current_field();
|
||||
let last_field_index = num_fields - 1;
|
||||
if current_field < last_field_index {
|
||||
let new_field = current_field + 1;
|
||||
state.set_current_field(new_field);
|
||||
let current_input = state.get_current_input();
|
||||
let max_pos = current_input.len();
|
||||
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
|
||||
}
|
||||
}
|
||||
Ok("".to_string())
|
||||
}
|
||||
"move_line_start" => {
|
||||
state.set_current_cursor_pos(0);
|
||||
*ideal_cursor_column = 0;
|
||||
Ok("".to_string())
|
||||
}
|
||||
"move_line_end" => {
|
||||
let current_input = state.get_current_input();
|
||||
let new_pos = current_input.len();
|
||||
state.set_current_cursor_pos(new_pos);
|
||||
*ideal_cursor_column = new_pos;
|
||||
Ok("".to_string())
|
||||
}
|
||||
"move_first_line" => {
|
||||
if AddLogicState::INPUT_FIELD_COUNT > 0 { // Changed
|
||||
state.set_current_field(0);
|
||||
let current_input = state.get_current_input();
|
||||
let max_pos = current_input.len();
|
||||
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
|
||||
}
|
||||
Ok("".to_string())
|
||||
}
|
||||
"move_last_line" => {
|
||||
let num_fields = AddLogicState::INPUT_FIELD_COUNT; // Changed
|
||||
if num_fields > 0 {
|
||||
let new_field = num_fields - 1;
|
||||
state.set_current_field(new_field);
|
||||
let current_input = state.get_current_input();
|
||||
let max_pos = current_input.len();
|
||||
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
|
||||
}
|
||||
Ok("".to_string())
|
||||
}
|
||||
"move_word_next" => {
|
||||
let current_input = state.get_current_input();
|
||||
if !current_input.is_empty() {
|
||||
let new_pos = find_next_word_start(current_input, state.current_cursor_pos());
|
||||
let final_pos = new_pos.min(current_input.len());
|
||||
state.set_current_cursor_pos(final_pos);
|
||||
*ideal_cursor_column = final_pos;
|
||||
}
|
||||
Ok("".to_string())
|
||||
}
|
||||
"move_word_end" => {
|
||||
let current_input = state.get_current_input();
|
||||
if !current_input.is_empty() {
|
||||
"insert_char" => {
|
||||
if let KeyCode::Char(c) = key.code {
|
||||
let current_pos = state.current_cursor_pos();
|
||||
let new_pos = find_word_end(current_input, current_pos);
|
||||
let final_pos = if new_pos == current_pos && current_pos < current_input.len() { // Ensure not to go past end
|
||||
find_word_end(current_input, current_pos + 1)
|
||||
state.get_current_input_mut().insert(current_pos, c);
|
||||
let new_pos = current_pos + 1;
|
||||
state.set_current_cursor_pos(new_pos);
|
||||
*ideal_cursor_column = new_pos;
|
||||
state.set_has_unsaved_changes(true);
|
||||
if state.current_field() == 1 {
|
||||
state.update_target_column_suggestions();
|
||||
}
|
||||
}
|
||||
}
|
||||
"suggestion_down" => {
|
||||
if state.in_target_column_suggestion_mode && !state.target_column_suggestions.is_empty() {
|
||||
let current_selection = state.selected_target_column_suggestion_index.unwrap_or(0);
|
||||
let next_selection = (current_selection + 1) % state.target_column_suggestions.len();
|
||||
state.selected_target_column_suggestion_index = Some(next_selection);
|
||||
}
|
||||
}
|
||||
"suggestion_up" => {
|
||||
if state.in_target_column_suggestion_mode && !state.target_column_suggestions.is_empty() {
|
||||
let current_selection = state.selected_target_column_suggestion_index.unwrap_or(0);
|
||||
let prev_selection = if current_selection == 0 {
|
||||
state.target_column_suggestions.len() - 1
|
||||
} else {
|
||||
new_pos
|
||||
current_selection - 1
|
||||
};
|
||||
let max_valid_index = current_input.len(); // Allow cursor at end
|
||||
let clamped_pos = final_pos.min(max_valid_index);
|
||||
state.set_current_cursor_pos(clamped_pos);
|
||||
*ideal_cursor_column = clamped_pos;
|
||||
state.selected_target_column_suggestion_index = Some(prev_selection);
|
||||
}
|
||||
Ok("".to_string())
|
||||
}
|
||||
"move_word_prev" => {
|
||||
let current_input = state.get_current_input();
|
||||
if !current_input.is_empty() {
|
||||
let new_pos = find_prev_word_start(current_input, state.current_cursor_pos());
|
||||
state.set_current_cursor_pos(new_pos);
|
||||
*ideal_cursor_column = new_pos;
|
||||
}
|
||||
Ok("".to_string())
|
||||
}
|
||||
"move_word_end_prev" => {
|
||||
let current_input = state.get_current_input();
|
||||
if !current_input.is_empty() {
|
||||
let new_pos = find_prev_word_end(current_input, state.current_cursor_pos());
|
||||
state.set_current_cursor_pos(new_pos);
|
||||
*ideal_cursor_column = new_pos;
|
||||
}
|
||||
Ok("".to_string())
|
||||
}
|
||||
"exit_edit_mode" | "save" | "revert" => {
|
||||
Ok("Action handled by main loop".to_string())
|
||||
}
|
||||
_ => Ok(format!("Unknown or unhandled edit action: {}", action)),
|
||||
}
|
||||
}
|
||||
"select_suggestion" => {
|
||||
if state.in_target_column_suggestion_mode {
|
||||
let mut selected_suggestion_text: Option<String> = None;
|
||||
|
||||
if let Some(selected_idx) = state.selected_target_column_suggestion_index {
|
||||
if let Some(suggestion) = state.target_column_suggestions.get(selected_idx) {
|
||||
selected_suggestion_text = Some(suggestion.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(suggestion_text) = selected_suggestion_text {
|
||||
state.target_column_input = suggestion_text.clone();
|
||||
state.target_column_cursor_pos = state.target_column_input.len();
|
||||
*ideal_cursor_column = state.target_column_cursor_pos;
|
||||
state.set_has_unsaved_changes(true);
|
||||
message = format!("Selected column: '{}'", suggestion_text);
|
||||
}
|
||||
|
||||
state.in_target_column_suggestion_mode = false;
|
||||
state.show_target_column_suggestions = false;
|
||||
state.selected_target_column_suggestion_index = None;
|
||||
state.update_target_column_suggestions();
|
||||
} else {
|
||||
let current_field = state.current_field();
|
||||
let next_field = (current_field + 1) % AddLogicState::INPUT_FIELD_COUNT;
|
||||
state.set_current_field(next_field);
|
||||
*ideal_cursor_column = state.current_cursor_pos();
|
||||
message = format!("Focus on field {}", state.fields()[next_field]);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
@@ -5,26 +5,27 @@ use crate::state::pages::{
|
||||
auth::{LoginState, RegisterState},
|
||||
canvas_state::CanvasState,
|
||||
};
|
||||
use crate::state::pages::add_logic::AddLogicState;
|
||||
use crate::state::pages::form::FormState;
|
||||
use crate::state::pages::add_table::AddTableState;
|
||||
use crate::state::pages::form::FormState; // <<< ADD THIS LINE
|
||||
// AddLogicState is already imported
|
||||
// AddTableState is already imported
|
||||
use crate::state::pages::admin::AdminState;
|
||||
use crate::modes::handlers::event::EventOutcome;
|
||||
use crate::functions::modes::edit::{add_logic_e, auth_e, form_e, add_table_e};
|
||||
use crate::state::app::state::AppState;
|
||||
use anyhow::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use crossterm::event::KeyEvent; // Removed KeyCode, KeyModifiers as they were unused
|
||||
use tracing::debug;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EditEventOutcome {
|
||||
Message(String), // Return a message, stay in Edit mode
|
||||
ExitEditMode, // Signal to exit Edit mode
|
||||
Message(String),
|
||||
ExitEditMode,
|
||||
}
|
||||
|
||||
pub async fn handle_edit_event(
|
||||
key: KeyEvent,
|
||||
config: &Config,
|
||||
form_state: &mut FormState,
|
||||
form_state: &mut FormState, // Now FormState is in scope
|
||||
login_state: &mut LoginState,
|
||||
register_state: &mut RegisterState,
|
||||
admin_state: &mut AdminState,
|
||||
@@ -34,17 +35,20 @@ pub async fn handle_edit_event(
|
||||
grpc_client: &mut GrpcClient,
|
||||
app_state: &AppState,
|
||||
) -> Result<EditEventOutcome> {
|
||||
// Global command mode check (should ideally be handled before calling this function)
|
||||
// --- Global command mode check ---
|
||||
if let Some("enter_command_mode") = config.get_action_for_key_in_mode(
|
||||
&config.keybindings.global,
|
||||
&config.keybindings.global, // Assuming command mode can be entered globally
|
||||
key.code,
|
||||
key.modifiers,
|
||||
) {
|
||||
// This check might be redundant if EventHandler already prevents entering Edit mode
|
||||
// when command_mode is true. However, it's a safeguard.
|
||||
return Ok(EditEventOutcome::Message(
|
||||
"Command mode entry handled globally.".to_string(),
|
||||
"Cannot enter command mode from edit mode here.".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// --- Common actions (save, revert) ---
|
||||
if let Some(action) = config.get_action_for_key_in_mode(
|
||||
&config.keybindings.common,
|
||||
key.code,
|
||||
@@ -52,261 +56,197 @@ pub async fn handle_edit_event(
|
||||
).as_deref() {
|
||||
if matches!(action, "save" | "revert") {
|
||||
let message_string: String = if app_state.ui.show_login {
|
||||
auth_e::execute_common_action(
|
||||
action,
|
||||
login_state,
|
||||
grpc_client,
|
||||
current_position,
|
||||
total_count,
|
||||
)
|
||||
.await?
|
||||
auth_e::execute_common_action(action, login_state, grpc_client, current_position, total_count).await?
|
||||
} else if app_state.ui.show_register {
|
||||
auth_e::execute_common_action(
|
||||
action,
|
||||
register_state,
|
||||
grpc_client,
|
||||
current_position,
|
||||
total_count,
|
||||
)
|
||||
.await?
|
||||
auth_e::execute_common_action(action, register_state, grpc_client, current_position, total_count).await?
|
||||
} else if app_state.ui.show_add_table {
|
||||
format!(
|
||||
"Action '{}' not fully implemented for Add Table view here.",
|
||||
action
|
||||
)
|
||||
// TODO: Implement common actions for AddTable if needed
|
||||
format!("Action '{}' not implemented for Add Table in edit mode.", action)
|
||||
} else if app_state.ui.show_add_logic {
|
||||
format!(
|
||||
"Action '{}' not fully implemented for Add Logic view here.",
|
||||
action
|
||||
)
|
||||
} else {
|
||||
let outcome = form_e::execute_common_action(
|
||||
action,
|
||||
form_state,
|
||||
grpc_client,
|
||||
current_position,
|
||||
total_count,
|
||||
)
|
||||
.await?;
|
||||
// TODO: Implement common actions for AddLogic if needed
|
||||
format!("Action '{}' not implemented for Add Logic in edit mode.", action)
|
||||
} else { // Assuming Form view
|
||||
let outcome = form_e::execute_common_action(action, form_state, grpc_client, current_position, total_count).await?;
|
||||
match outcome {
|
||||
EventOutcome::Ok(msg) => msg,
|
||||
EventOutcome::DataSaved(_, msg) => msg,
|
||||
_ => format!(
|
||||
"Unexpected outcome from common action: {:?}",
|
||||
outcome
|
||||
),
|
||||
EventOutcome::Ok(msg) | EventOutcome::DataSaved(_, msg) => msg,
|
||||
_ => format!("Unexpected outcome from common action: {:?}", outcome),
|
||||
}
|
||||
};
|
||||
return Ok(EditEventOutcome::Message(message_string));
|
||||
}
|
||||
}
|
||||
|
||||
// Edit-specific actions
|
||||
if let Some(action) =
|
||||
config.get_edit_action_for_key(key.code, key.modifiers)
|
||||
.as_deref() {
|
||||
// Handle enter_decider first
|
||||
if action == "enter_decider" {
|
||||
// --- Edit-specific actions ---
|
||||
if let Some(action_str) = config.get_edit_action_for_key(key.code, key.modifiers).as_deref() {
|
||||
// --- Handle "enter_decider" (Enter key) ---
|
||||
if action_str == "enter_decider" {
|
||||
let effective_action = if app_state.ui.show_register
|
||||
&& register_state.in_suggestion_mode
|
||||
&& register_state.current_field() == 4 {
|
||||
&& register_state.current_field() == 4 { // Role field
|
||||
"select_suggestion"
|
||||
} else if app_state.ui.show_add_logic
|
||||
&& admin_state.add_logic_state.in_target_column_suggestion_mode
|
||||
&& admin_state.add_logic_state.current_field() == 1 { // Target Column field
|
||||
"select_suggestion"
|
||||
} else {
|
||||
"next_field"
|
||||
"next_field" // Default action for Enter
|
||||
};
|
||||
|
||||
let msg = if app_state.ui.show_login {
|
||||
auth_e::execute_edit_action(
|
||||
effective_action,
|
||||
key,
|
||||
login_state,
|
||||
ideal_cursor_column,
|
||||
)
|
||||
.await?
|
||||
auth_e::execute_edit_action(effective_action, key, login_state, ideal_cursor_column).await?
|
||||
} else if app_state.ui.show_add_table {
|
||||
add_table_e::execute_edit_action(
|
||||
effective_action,
|
||||
key,
|
||||
&mut admin_state.add_table_state,
|
||||
ideal_cursor_column,
|
||||
)
|
||||
.await?
|
||||
add_table_e::execute_edit_action(effective_action, key, &mut admin_state.add_table_state, ideal_cursor_column).await?
|
||||
} else if app_state.ui.show_add_logic {
|
||||
add_logic_e::execute_edit_action(
|
||||
effective_action,
|
||||
key,
|
||||
&mut admin_state.add_logic_state,
|
||||
ideal_cursor_column,
|
||||
)
|
||||
.await?
|
||||
add_logic_e::execute_edit_action(effective_action, key, &mut admin_state.add_logic_state, ideal_cursor_column).await?
|
||||
} else if app_state.ui.show_register {
|
||||
auth_e::execute_edit_action(
|
||||
effective_action,
|
||||
key,
|
||||
register_state,
|
||||
ideal_cursor_column,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
form_e::execute_edit_action(
|
||||
effective_action,
|
||||
key,
|
||||
form_state,
|
||||
ideal_cursor_column,
|
||||
)
|
||||
.await?
|
||||
auth_e::execute_edit_action(effective_action, key, register_state, ideal_cursor_column).await?
|
||||
} else { // Form view
|
||||
form_e::execute_edit_action(effective_action, key, form_state, ideal_cursor_column).await?
|
||||
};
|
||||
return Ok(EditEventOutcome::Message(msg));
|
||||
}
|
||||
|
||||
if action == "exit" {
|
||||
// --- Handle "exit" (Escape key) ---
|
||||
if action_str == "exit" {
|
||||
if app_state.ui.show_register && register_state.in_suggestion_mode {
|
||||
let msg = auth_e::execute_edit_action(
|
||||
"exit_suggestion_mode",
|
||||
key,
|
||||
register_state,
|
||||
ideal_cursor_column,
|
||||
)
|
||||
.await?;
|
||||
let msg = auth_e::execute_edit_action("exit_suggestion_mode", key, register_state, ideal_cursor_column).await?;
|
||||
return Ok(EditEventOutcome::Message(msg));
|
||||
} else if app_state.ui.show_add_logic && admin_state.add_logic_state.in_target_column_suggestion_mode {
|
||||
admin_state.add_logic_state.in_target_column_suggestion_mode = false;
|
||||
admin_state.add_logic_state.show_target_column_suggestions = false;
|
||||
admin_state.add_logic_state.selected_target_column_suggestion_index = None;
|
||||
return Ok(EditEventOutcome::Message("Exited column suggestions".to_string()));
|
||||
} else {
|
||||
return Ok(EditEventOutcome::ExitEditMode);
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling for role field suggestions (Register view only)
|
||||
if app_state.ui.show_register && register_state.current_field() == 4 {
|
||||
if !register_state.in_suggestion_mode
|
||||
&& key.code == KeyCode::Tab
|
||||
&& key.modifiers == KeyModifiers::NONE
|
||||
{
|
||||
register_state.update_role_suggestions();
|
||||
if !register_state.role_suggestions.is_empty() {
|
||||
register_state.in_suggestion_mode = true;
|
||||
register_state.selected_suggestion_index = Some(0);
|
||||
return Ok(EditEventOutcome::Message(
|
||||
"Suggestions shown".to_string(),
|
||||
));
|
||||
// --- Autocomplete for AddLogicState Target Column ---
|
||||
if app_state.ui.show_add_logic && admin_state.add_logic_state.current_field() == 1 { // Target Column field
|
||||
if action_str == "suggestion_down" { // "Tab" is mapped to suggestion_down
|
||||
if !admin_state.add_logic_state.in_target_column_suggestion_mode {
|
||||
// Attempt to open suggestions
|
||||
if let Some(profile_name) = admin_state.add_logic_state.profile_name.clone().into() {
|
||||
if let Some(table_name) = admin_state.add_logic_state.selected_table_name.clone() {
|
||||
debug!("Fetching table structure for autocomplete: Profile='{}', Table='{}'", profile_name, table_name);
|
||||
match grpc_client.get_table_structure(profile_name, table_name).await {
|
||||
Ok(ts_response) => {
|
||||
admin_state.add_logic_state.table_columns_for_suggestions =
|
||||
ts_response.columns.into_iter().map(|c| c.name).collect();
|
||||
admin_state.add_logic_state.update_target_column_suggestions();
|
||||
if !admin_state.add_logic_state.target_column_suggestions.is_empty() {
|
||||
admin_state.add_logic_state.in_target_column_suggestion_mode = true;
|
||||
// update_target_column_suggestions handles initial selection
|
||||
return Ok(EditEventOutcome::Message("Column suggestions shown".to_string()));
|
||||
} else {
|
||||
return Ok(EditEventOutcome::Message(
|
||||
"No suggestions available".to_string(),
|
||||
));
|
||||
return Ok(EditEventOutcome::Message("No column suggestions for current input".to_string()));
|
||||
}
|
||||
}
|
||||
if register_state.in_suggestion_mode
|
||||
&& matches!(
|
||||
action,
|
||||
"suggestion_down" | "suggestion_up"
|
||||
)
|
||||
{
|
||||
let msg = auth_e::execute_edit_action(
|
||||
action,
|
||||
key,
|
||||
register_state,
|
||||
ideal_cursor_column,
|
||||
)
|
||||
.await?;
|
||||
Err(e) => {
|
||||
debug!("Error fetching table structure: {}", e);
|
||||
admin_state.add_logic_state.table_columns_for_suggestions.clear(); // Clear old data on error
|
||||
admin_state.add_logic_state.update_target_column_suggestions();
|
||||
return Ok(EditEventOutcome::Message(format!("Error fetching columns: {}", e)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Ok(EditEventOutcome::Message("No table selected for column suggestions".to_string()));
|
||||
}
|
||||
} else { // Should not happen if AddLogic is properly initialized
|
||||
return Ok(EditEventOutcome::Message("Profile name missing for column suggestions".to_string()));
|
||||
}
|
||||
} else { // Already in suggestion mode, navigate down
|
||||
let msg = add_logic_e::execute_edit_action(action_str, key, &mut admin_state.add_logic_state, ideal_cursor_column).await?;
|
||||
return Ok(EditEventOutcome::Message(msg));
|
||||
}
|
||||
} else if admin_state.add_logic_state.in_target_column_suggestion_mode && action_str == "suggestion_up" {
|
||||
let msg = add_logic_e::execute_edit_action(action_str, key, &mut admin_state.add_logic_state, ideal_cursor_column).await?;
|
||||
return Ok(EditEventOutcome::Message(msg));
|
||||
}
|
||||
}
|
||||
|
||||
// Execute other edit actions based on the current view
|
||||
let msg = if app_state.ui.show_login {
|
||||
auth_e::execute_edit_action(
|
||||
action,
|
||||
key,
|
||||
login_state,
|
||||
ideal_cursor_column,
|
||||
)
|
||||
.await?
|
||||
} else if app_state.ui.show_add_table {
|
||||
add_table_e::execute_edit_action(
|
||||
action,
|
||||
key,
|
||||
&mut admin_state.add_table_state,
|
||||
ideal_cursor_column,
|
||||
)
|
||||
.await?
|
||||
} else if app_state.ui.show_add_logic {
|
||||
add_logic_e::execute_edit_action(
|
||||
action,
|
||||
key,
|
||||
&mut admin_state.add_logic_state,
|
||||
ideal_cursor_column,
|
||||
)
|
||||
.await?
|
||||
} else if app_state.ui.show_register {
|
||||
auth_e::execute_edit_action(
|
||||
action,
|
||||
key,
|
||||
register_state,
|
||||
ideal_cursor_column,
|
||||
)
|
||||
.await?
|
||||
// --- Autocomplete for RegisterState Role Field ---
|
||||
if app_state.ui.show_register && register_state.current_field() == 4 { // Role field
|
||||
if !register_state.in_suggestion_mode && action_str == "suggestion_down" { // Tab
|
||||
register_state.update_role_suggestions();
|
||||
if !register_state.role_suggestions.is_empty() {
|
||||
register_state.in_suggestion_mode = true;
|
||||
// update_role_suggestions should handle initial selection
|
||||
return Ok(EditEventOutcome::Message("Role suggestions shown".to_string()));
|
||||
} else {
|
||||
form_e::execute_edit_action(
|
||||
action,
|
||||
key,
|
||||
form_state,
|
||||
ideal_cursor_column,
|
||||
)
|
||||
.await?
|
||||
// If Tab doesn't open suggestions, it might fall through to "next_field"
|
||||
// or you might want specific behavior. For now, let it fall through.
|
||||
}
|
||||
}
|
||||
if register_state.in_suggestion_mode && matches!(action_str, "suggestion_down" | "suggestion_up") {
|
||||
let msg = auth_e::execute_edit_action(action_str, key, register_state, ideal_cursor_column).await?;
|
||||
return Ok(EditEventOutcome::Message(msg));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Dispatch other edit actions ---
|
||||
let msg = if app_state.ui.show_login {
|
||||
auth_e::execute_edit_action(action_str, key, login_state, ideal_cursor_column).await?
|
||||
} else if app_state.ui.show_add_table {
|
||||
add_table_e::execute_edit_action(action_str, key, &mut admin_state.add_table_state, ideal_cursor_column).await?
|
||||
} else if app_state.ui.show_add_logic {
|
||||
// If not a suggestion action handled above for AddLogic
|
||||
if !(admin_state.add_logic_state.in_target_column_suggestion_mode && matches!(action_str, "suggestion_down" | "suggestion_up")) {
|
||||
add_logic_e::execute_edit_action(action_str, key, &mut admin_state.add_logic_state, ideal_cursor_column).await?
|
||||
} else { String::new() /* Already handled */ }
|
||||
} else if app_state.ui.show_register {
|
||||
if !(register_state.in_suggestion_mode && matches!(action_str, "suggestion_down" | "suggestion_up")) {
|
||||
auth_e::execute_edit_action(action_str, key, register_state, ideal_cursor_column).await?
|
||||
} else { String::new() /* Already handled */ }
|
||||
} else { // Form view
|
||||
form_e::execute_edit_action(action_str, key, form_state, ideal_cursor_column).await?
|
||||
};
|
||||
return Ok(EditEventOutcome::Message(msg));
|
||||
}
|
||||
|
||||
// --- Character insertion ---
|
||||
// If character insertion happens while in suggestion mode, exit suggestion mode first.
|
||||
let mut exited_suggestion_mode_for_typing = false;
|
||||
if app_state.ui.show_register && register_state.in_suggestion_mode {
|
||||
register_state.in_suggestion_mode = false;
|
||||
register_state.show_role_suggestions = false;
|
||||
register_state.selected_suggestion_index = None;
|
||||
exited_suggestion_mode_for_typing = true;
|
||||
}
|
||||
if app_state.ui.show_add_logic && admin_state.add_logic_state.in_target_column_suggestion_mode {
|
||||
admin_state.add_logic_state.in_target_column_suggestion_mode = false;
|
||||
admin_state.add_logic_state.show_target_column_suggestions = false;
|
||||
admin_state.add_logic_state.selected_target_column_suggestion_index = None;
|
||||
exited_suggestion_mode_for_typing = true;
|
||||
}
|
||||
|
||||
let msg = if app_state.ui.show_login {
|
||||
auth_e::execute_edit_action(
|
||||
"insert_char",
|
||||
key,
|
||||
login_state,
|
||||
ideal_cursor_column,
|
||||
)
|
||||
.await?
|
||||
let mut char_insert_msg = if app_state.ui.show_login {
|
||||
auth_e::execute_edit_action("insert_char", key, login_state, ideal_cursor_column).await?
|
||||
} else if app_state.ui.show_add_table {
|
||||
add_table_e::execute_edit_action(
|
||||
"insert_char",
|
||||
key,
|
||||
&mut admin_state.add_table_state,
|
||||
ideal_cursor_column,
|
||||
)
|
||||
.await?
|
||||
add_table_e::execute_edit_action("insert_char", key, &mut admin_state.add_table_state, ideal_cursor_column).await?
|
||||
} else if app_state.ui.show_add_logic {
|
||||
add_logic_e::execute_edit_action(
|
||||
"insert_char",
|
||||
key,
|
||||
&mut admin_state.add_logic_state,
|
||||
ideal_cursor_column,
|
||||
)
|
||||
.await?
|
||||
add_logic_e::execute_edit_action("insert_char", key, &mut admin_state.add_logic_state, ideal_cursor_column).await?
|
||||
} else if app_state.ui.show_register {
|
||||
auth_e::execute_edit_action(
|
||||
"insert_char",
|
||||
key,
|
||||
register_state,
|
||||
ideal_cursor_column,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
form_e::execute_edit_action(
|
||||
"insert_char",
|
||||
key,
|
||||
form_state,
|
||||
ideal_cursor_column,
|
||||
)
|
||||
.await?
|
||||
auth_e::execute_edit_action("insert_char", key, register_state, ideal_cursor_column).await?
|
||||
} else { // Form view
|
||||
form_e::execute_edit_action("insert_char", key, form_state, ideal_cursor_column).await?
|
||||
};
|
||||
|
||||
// After character insertion, update suggestions if applicable
|
||||
if app_state.ui.show_register && register_state.current_field() == 4 {
|
||||
register_state.update_role_suggestions();
|
||||
// If we just exited suggestion mode by typing, don't immediately show them again unless Tab is pressed.
|
||||
// However, update_role_suggestions will set show_role_suggestions if matches are found.
|
||||
// This is fine, as the render logic checks in_suggestion_mode.
|
||||
}
|
||||
if app_state.ui.show_add_logic && admin_state.add_logic_state.current_field() == 1 {
|
||||
admin_state.add_logic_state.update_target_column_suggestions();
|
||||
}
|
||||
|
||||
return Ok(EditEventOutcome::Message(msg));
|
||||
if exited_suggestion_mode_for_typing && char_insert_msg.is_empty() {
|
||||
char_insert_msg = "Suggestions hidden".to_string();
|
||||
}
|
||||
|
||||
|
||||
Ok(EditEventOutcome::Message(char_insert_msg))
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ pub enum AddLogicFocus {
|
||||
InputLogicName,
|
||||
InputTargetColumn,
|
||||
InputDescription,
|
||||
ScriptContentPreview, // Like ColumnsTable - can be highlighted/selected
|
||||
InsideScriptContent, // Like InsideColumnsTable - full editing mode
|
||||
ScriptContentPreview,
|
||||
InsideScriptContent,
|
||||
SaveButton,
|
||||
CancelButton,
|
||||
}
|
||||
@@ -35,6 +35,13 @@ pub struct AddLogicState {
|
||||
pub has_unsaved_changes: bool,
|
||||
pub editor_keybinding_mode: EditorKeybindingMode,
|
||||
pub vim_state: VimState,
|
||||
|
||||
// New fields for Target Column Autocomplete
|
||||
pub table_columns_for_suggestions: Vec<String>, // All columns for the table
|
||||
pub target_column_suggestions: Vec<String>, // Filtered suggestions
|
||||
pub show_target_column_suggestions: bool,
|
||||
pub selected_target_column_suggestion_index: Option<usize>,
|
||||
pub in_target_column_suggestion_mode: bool,
|
||||
}
|
||||
|
||||
impl AddLogicState {
|
||||
@@ -56,10 +63,57 @@ impl AddLogicState {
|
||||
has_unsaved_changes: false,
|
||||
editor_keybinding_mode: editor_config.keybinding_mode.clone(),
|
||||
vim_state: VimState::default(),
|
||||
|
||||
table_columns_for_suggestions: Vec::new(),
|
||||
target_column_suggestions: Vec::new(),
|
||||
show_target_column_suggestions: false,
|
||||
selected_target_column_suggestion_index: None,
|
||||
in_target_column_suggestion_mode: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub const INPUT_FIELD_COUNT: usize = 3;
|
||||
|
||||
/// Updates the target_column_suggestions based on current input.
|
||||
pub fn update_target_column_suggestions(&mut self) {
|
||||
let current_input = self.target_column_input.to_lowercase();
|
||||
if self.table_columns_for_suggestions.is_empty() {
|
||||
self.target_column_suggestions.clear();
|
||||
self.show_target_column_suggestions = false;
|
||||
self.selected_target_column_suggestion_index = None;
|
||||
return;
|
||||
}
|
||||
|
||||
if current_input.is_empty() {
|
||||
self.target_column_suggestions = self.table_columns_for_suggestions.clone();
|
||||
} else {
|
||||
self.target_column_suggestions = self
|
||||
.table_columns_for_suggestions
|
||||
.iter()
|
||||
.filter(|name| name.to_lowercase().contains(¤t_input))
|
||||
.cloned()
|
||||
.collect();
|
||||
}
|
||||
|
||||
self.show_target_column_suggestions = !self.target_column_suggestions.is_empty();
|
||||
if self.show_target_column_suggestions {
|
||||
// If suggestions are shown, ensure a selection (usually the first)
|
||||
// or maintain current if it's still valid.
|
||||
if let Some(selected_idx) = self.selected_target_column_suggestion_index {
|
||||
if selected_idx >= self.target_column_suggestions.len() {
|
||||
self.selected_target_column_suggestion_index = Some(0);
|
||||
}
|
||||
// If the previously selected item is no longer in the filtered list, reset.
|
||||
// This is a bit more complex to check perfectly without iterating again.
|
||||
// For now, just ensuring it's within bounds is a good start.
|
||||
// A more robust way would be to check if the string at selected_idx still matches.
|
||||
} else {
|
||||
self.selected_target_column_suggestion_index = Some(0);
|
||||
}
|
||||
} else {
|
||||
self.selected_target_column_suggestion_index = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AddLogicState {
|
||||
@@ -122,21 +176,21 @@ impl CanvasState for AddLogicState {
|
||||
}
|
||||
|
||||
fn set_current_field(&mut self, index: usize) {
|
||||
self.current_focus = match index {
|
||||
0 => {
|
||||
self.last_canvas_field = 0;
|
||||
AddLogicFocus::InputLogicName
|
||||
},
|
||||
1 => {
|
||||
self.last_canvas_field = 1;
|
||||
AddLogicFocus::InputTargetColumn
|
||||
},
|
||||
2 => {
|
||||
self.last_canvas_field = 2;
|
||||
AddLogicFocus::InputDescription
|
||||
},
|
||||
_ => self.current_focus,
|
||||
let new_focus = match index {
|
||||
0 => AddLogicFocus::InputLogicName,
|
||||
1 => AddLogicFocus::InputTargetColumn,
|
||||
2 => AddLogicFocus::InputDescription,
|
||||
_ => return, // Or handle error/default
|
||||
};
|
||||
if self.current_focus != new_focus {
|
||||
// If changing field, exit suggestion mode for target column
|
||||
if self.current_focus == AddLogicFocus::InputTargetColumn {
|
||||
self.in_target_column_suggestion_mode = false;
|
||||
self.show_target_column_suggestions = false;
|
||||
}
|
||||
self.current_focus = new_focus;
|
||||
self.last_canvas_field = index;
|
||||
}
|
||||
}
|
||||
|
||||
fn set_current_cursor_pos(&mut self, pos: usize) {
|
||||
@@ -161,10 +215,24 @@ impl CanvasState for AddLogicState {
|
||||
}
|
||||
|
||||
fn get_suggestions(&self) -> Option<&[String]> {
|
||||
if self.current_field() == 1 // Target Column field index
|
||||
&& self.in_target_column_suggestion_mode
|
||||
&& self.show_target_column_suggestions
|
||||
{
|
||||
Some(&self.target_column_suggestions)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_selected_suggestion_index(&self) -> Option<usize> {
|
||||
if self.current_field() == 1 // Target Column field index
|
||||
&& self.in_target_column_suggestion_mode
|
||||
&& self.show_target_column_suggestions
|
||||
{
|
||||
self.selected_target_column_suggestion_index
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user