automcomplete running and working now
This commit is contained in:
@@ -12,16 +12,17 @@ use ratatui::{
|
|||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
use crate::components::handlers::canvas::render_canvas;
|
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::config::binds::config::EditorKeybindingMode;
|
||||||
|
use crate::modes::handlers::mode_manager::AppMode; // For checking AppMode::Edit
|
||||||
|
|
||||||
pub fn render_add_logic(
|
pub fn render_add_logic(
|
||||||
f: &mut Frame,
|
f: &mut Frame,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
app_state: &AppState,
|
app_state: &AppState,
|
||||||
add_logic_state: &mut AddLogicState,
|
add_logic_state: &mut AddLogicState, // Changed to &mut
|
||||||
is_edit_mode: bool, // Used for border/title hints in InsideScriptContent
|
is_edit_mode: bool, // This is the general edit mode from EventHandler
|
||||||
highlight_state: &HighlightState,
|
highlight_state: &HighlightState,
|
||||||
) {
|
) {
|
||||||
let main_block = Block::default()
|
let main_block = Block::default()
|
||||||
@@ -41,20 +42,18 @@ pub fn render_add_logic(
|
|||||||
let border_style = Style::default().fg(border_style_color);
|
let border_style = Style::default().fg(border_style_color);
|
||||||
|
|
||||||
editor_ref.set_cursor_line_style(Style::default());
|
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));
|
editor_ref.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
|
||||||
|
|
||||||
let script_title_hint = match add_logic_state.editor_keybinding_mode {
|
let script_title_hint = match add_logic_state.editor_keybinding_mode {
|
||||||
EditorKeybindingMode::Vim => {
|
EditorKeybindingMode::Vim => {
|
||||||
let vim_mode_status = crate::components::common::text_editor::TextEditor::get_vim_mode_status(&add_logic_state.vim_state);
|
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)
|
format!("Script {}", vim_mode_status)
|
||||||
} else {
|
|
||||||
format!("Script {}", vim_mode_status)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
EditorKeybindingMode::Emacs | EditorKeybindingMode::Default => {
|
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()
|
"Script (Editing)".to_string()
|
||||||
} else {
|
} else {
|
||||||
"Script".to_string()
|
"Script".to_string()
|
||||||
@@ -79,7 +78,7 @@ pub fn render_add_logic(
|
|||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([
|
.constraints([
|
||||||
Constraint::Length(3), // Top info
|
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::Min(5), // Script preview
|
||||||
Constraint::Length(3), // Buttons
|
Constraint::Length(3), // Buttons
|
||||||
])
|
])
|
||||||
@@ -123,10 +122,11 @@ pub fn render_add_logic(
|
|||||||
| AddLogicFocus::InputTargetColumn
|
| AddLogicFocus::InputTargetColumn
|
||||||
| AddLogicFocus::InputDescription
|
| AddLogicFocus::InputDescription
|
||||||
);
|
);
|
||||||
render_canvas(
|
// Call render_canvas and get the active_field_rect
|
||||||
|
let active_field_rect = render_canvas(
|
||||||
f,
|
f,
|
||||||
canvas_area,
|
canvas_area,
|
||||||
add_logic_state,
|
add_logic_state, // Pass the whole state as it impl CanvasState
|
||||||
&add_logic_state.fields(),
|
&add_logic_state.fields(),
|
||||||
&add_logic_state.current_field(),
|
&add_logic_state.current_field(),
|
||||||
&add_logic_state.inputs(),
|
&add_logic_state.inputs(),
|
||||||
@@ -135,6 +135,26 @@ pub fn render_add_logic(
|
|||||||
highlight_state,
|
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
|
// Script content preview
|
||||||
{
|
{
|
||||||
let mut editor_ref = add_logic_state.script_content_editor.borrow_mut();
|
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;
|
let is_script_preview_focused = add_logic_state.current_focus == AddLogicFocus::ScriptContentPreview;
|
||||||
|
|
||||||
if is_script_preview_focused {
|
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));
|
editor_ref.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
|
||||||
} else {
|
} else {
|
||||||
// When script PREVIEW is NOT focused, use an underscore cursor.
|
|
||||||
let underscore_cursor_style = Style::default()
|
let underscore_cursor_style = Style::default()
|
||||||
.add_modifier(Modifier::UNDERLINED)
|
.add_modifier(Modifier::UNDERLINED)
|
||||||
.fg(theme.secondary);
|
.fg(theme.secondary);
|
||||||
@@ -154,16 +172,12 @@ pub fn render_add_logic(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let border_style_color = if is_script_preview_focused {
|
let border_style_color = if is_script_preview_focused {
|
||||||
theme.highlight // Green highlight when focused and ready to select
|
theme.highlight
|
||||||
} else {
|
} else {
|
||||||
theme.secondary
|
theme.secondary
|
||||||
};
|
};
|
||||||
|
|
||||||
let title_text = if is_script_preview_focused {
|
let title_text = "Script Preview"; // Title doesn't need to change based on focus here
|
||||||
"Script Preview"
|
|
||||||
} else {
|
|
||||||
"Script Preview"
|
|
||||||
};
|
|
||||||
|
|
||||||
let title_style = if is_script_preview_focused {
|
let title_style = if is_script_preview_focused {
|
||||||
Style::default().fg(theme.highlight).add_modifier(Modifier::BOLD)
|
Style::default().fg(theme.highlight).add_modifier(Modifier::BOLD)
|
||||||
@@ -182,8 +196,8 @@ pub fn render_add_logic(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
let get_button_style = |button_focus: AddLogicFocus, current_focus| {
|
let get_button_style = |button_focus: AddLogicFocus, current_focus_state: AddLogicFocus| {
|
||||||
let is_focused = current_focus == button_focus;
|
let is_focused = current_focus_state == button_focus;
|
||||||
let base_style = Style::default().fg(if is_focused {
|
let base_style = Style::default().fg(if is_focused {
|
||||||
theme.highlight
|
theme.highlight
|
||||||
} else {
|
} 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 {
|
if is_focused {
|
||||||
Style::default().fg(theme.highlight)
|
Style::default().fg(current_theme.highlight)
|
||||||
} else {
|
} else {
|
||||||
Style::default().fg(theme.secondary)
|
Style::default().fg(current_theme.secondary)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,277 +1,135 @@
|
|||||||
// src/functions/modes/edit/add_logic_e.rs
|
// 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 crate::state::pages::canvas_state::CanvasState;
|
||||||
use crossterm::event::{KeyCode, KeyEvent};
|
|
||||||
use anyhow::Result;
|
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(
|
pub async fn execute_edit_action(
|
||||||
action: &str,
|
action: &str,
|
||||||
key: KeyEvent,
|
key: KeyEvent, // Keep key for insert_char
|
||||||
state: &mut AddLogicState, // Changed
|
state: &mut AddLogicState,
|
||||||
ideal_cursor_column: &mut usize,
|
ideal_cursor_column: &mut usize,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
|
let mut message = String::new();
|
||||||
|
|
||||||
match action {
|
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" => {
|
"next_field" => {
|
||||||
let num_fields = AddLogicState::INPUT_FIELD_COUNT; // Changed
|
let current_field = state.current_field();
|
||||||
if num_fields > 0 {
|
let next_field = (current_field + 1) % AddLogicState::INPUT_FIELD_COUNT;
|
||||||
let current_field = state.current_field();
|
state.set_current_field(next_field);
|
||||||
let last_field_index = num_fields - 1;
|
*ideal_cursor_column = state.current_cursor_pos();
|
||||||
if current_field < last_field_index { // Prevent cycling
|
message = format!("Focus on field {}", state.fields()[next_field]);
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
"prev_field" => {
|
"prev_field" => {
|
||||||
let num_fields = AddLogicState::INPUT_FIELD_COUNT; // Changed
|
let current_field = state.current_field();
|
||||||
if num_fields > 0 {
|
let prev_field = if current_field == 0 {
|
||||||
let current_field = state.current_field();
|
AddLogicState::INPUT_FIELD_COUNT - 1
|
||||||
if current_field > 0 { // Prevent cycling
|
} else {
|
||||||
state.set_current_field(current_field - 1);
|
current_field - 1
|
||||||
}
|
};
|
||||||
let current_input = state.get_current_input();
|
state.set_current_field(prev_field);
|
||||||
let max_pos = current_input.len();
|
*ideal_cursor_column = state.current_cursor_pos();
|
||||||
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
|
message = format!("Focus on field {}", state.fields()[prev_field]);
|
||||||
|
}
|
||||||
|
"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(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"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;
|
||||||
|
state.set_has_unsaved_changes(true);
|
||||||
|
if state.current_field() == 1 { state.update_target_column_suggestions(); }
|
||||||
}
|
}
|
||||||
Ok("".to_string())
|
|
||||||
}
|
}
|
||||||
"move_left" => {
|
"move_left" => {
|
||||||
let new_pos = state.current_cursor_pos().saturating_sub(1);
|
let current_pos = state.current_cursor_pos();
|
||||||
state.set_current_cursor_pos(new_pos);
|
if current_pos > 0 {
|
||||||
*ideal_cursor_column = new_pos;
|
let new_pos = current_pos - 1;
|
||||||
Ok("".to_string())
|
state.set_current_cursor_pos(new_pos);
|
||||||
|
*ideal_cursor_column = new_pos;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"move_right" => {
|
"move_right" => {
|
||||||
let current_input = state.get_current_input();
|
|
||||||
let current_pos = state.current_cursor_pos();
|
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;
|
let new_pos = current_pos + 1;
|
||||||
state.set_current_cursor_pos(new_pos);
|
state.set_current_cursor_pos(new_pos);
|
||||||
*ideal_cursor_column = new_pos;
|
*ideal_cursor_column = new_pos;
|
||||||
}
|
}
|
||||||
Ok("".to_string())
|
|
||||||
}
|
}
|
||||||
"move_up" => { // In edit mode, up/down usually means prev/next field
|
"insert_char" => {
|
||||||
let current_field = state.current_field();
|
if let KeyCode::Char(c) = key.code {
|
||||||
if current_field > 0 {
|
let current_pos = state.current_cursor_pos();
|
||||||
let new_field = current_field - 1;
|
state.get_current_input_mut().insert(current_pos, c);
|
||||||
state.set_current_field(new_field);
|
let new_pos = current_pos + 1;
|
||||||
let current_input = state.get_current_input();
|
state.set_current_cursor_pos(new_pos);
|
||||||
let max_pos = current_input.len();
|
*ideal_cursor_column = new_pos;
|
||||||
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
|
state.set_has_unsaved_changes(true);
|
||||||
}
|
if state.current_field() == 1 {
|
||||||
Ok("".to_string())
|
state.update_target_column_suggestions();
|
||||||
}
|
|
||||||
"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" => {
|
"suggestion_down" => {
|
||||||
state.set_current_cursor_pos(0);
|
if state.in_target_column_suggestion_mode && !state.target_column_suggestions.is_empty() {
|
||||||
*ideal_cursor_column = 0;
|
let current_selection = state.selected_target_column_suggestion_index.unwrap_or(0);
|
||||||
Ok("".to_string())
|
let next_selection = (current_selection + 1) % state.target_column_suggestions.len();
|
||||||
}
|
state.selected_target_column_suggestion_index = Some(next_selection);
|
||||||
"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" => {
|
"suggestion_up" => {
|
||||||
let num_fields = AddLogicState::INPUT_FIELD_COUNT; // Changed
|
if state.in_target_column_suggestion_mode && !state.target_column_suggestions.is_empty() {
|
||||||
if num_fields > 0 {
|
let current_selection = state.selected_target_column_suggestion_index.unwrap_or(0);
|
||||||
let new_field = num_fields - 1;
|
let prev_selection = if current_selection == 0 {
|
||||||
state.set_current_field(new_field);
|
state.target_column_suggestions.len() - 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())
|
|
||||||
}
|
|
||||||
"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() {
|
|
||||||
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)
|
|
||||||
} else {
|
} else {
|
||||||
new_pos
|
current_selection - 1
|
||||||
};
|
};
|
||||||
let max_valid_index = current_input.len(); // Allow cursor at end
|
state.selected_target_column_suggestion_index = Some(prev_selection);
|
||||||
let clamped_pos = final_pos.min(max_valid_index);
|
|
||||||
state.set_current_cursor_pos(clamped_pos);
|
|
||||||
*ideal_cursor_column = clamped_pos;
|
|
||||||
}
|
}
|
||||||
Ok("".to_string())
|
|
||||||
}
|
}
|
||||||
"move_word_prev" => {
|
"select_suggestion" => {
|
||||||
let current_input = state.get_current_input();
|
if state.in_target_column_suggestion_mode {
|
||||||
if !current_input.is_empty() {
|
let mut selected_suggestion_text: Option<String> = None;
|
||||||
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)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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},
|
auth::{LoginState, RegisterState},
|
||||||
canvas_state::CanvasState,
|
canvas_state::CanvasState,
|
||||||
};
|
};
|
||||||
use crate::state::pages::add_logic::AddLogicState;
|
use crate::state::pages::form::FormState; // <<< ADD THIS LINE
|
||||||
use crate::state::pages::form::FormState;
|
// AddLogicState is already imported
|
||||||
use crate::state::pages::add_table::AddTableState;
|
// AddTableState is already imported
|
||||||
use crate::state::pages::admin::AdminState;
|
use crate::state::pages::admin::AdminState;
|
||||||
use crate::modes::handlers::event::EventOutcome;
|
use crate::modes::handlers::event::EventOutcome;
|
||||||
use crate::functions::modes::edit::{add_logic_e, auth_e, form_e, add_table_e};
|
use crate::functions::modes::edit::{add_logic_e, auth_e, form_e, add_table_e};
|
||||||
use crate::state::app::state::AppState;
|
use crate::state::app::state::AppState;
|
||||||
use anyhow::Result;
|
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)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum EditEventOutcome {
|
pub enum EditEventOutcome {
|
||||||
Message(String), // Return a message, stay in Edit mode
|
Message(String),
|
||||||
ExitEditMode, // Signal to exit Edit mode
|
ExitEditMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_edit_event(
|
pub async fn handle_edit_event(
|
||||||
key: KeyEvent,
|
key: KeyEvent,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
form_state: &mut FormState,
|
form_state: &mut FormState, // Now FormState is in scope
|
||||||
login_state: &mut LoginState,
|
login_state: &mut LoginState,
|
||||||
register_state: &mut RegisterState,
|
register_state: &mut RegisterState,
|
||||||
admin_state: &mut AdminState,
|
admin_state: &mut AdminState,
|
||||||
@@ -34,17 +35,20 @@ pub async fn handle_edit_event(
|
|||||||
grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
app_state: &AppState,
|
app_state: &AppState,
|
||||||
) -> Result<EditEventOutcome> {
|
) -> 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(
|
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.code,
|
||||||
key.modifiers,
|
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(
|
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(
|
if let Some(action) = config.get_action_for_key_in_mode(
|
||||||
&config.keybindings.common,
|
&config.keybindings.common,
|
||||||
key.code,
|
key.code,
|
||||||
@@ -52,261 +56,197 @@ pub async fn handle_edit_event(
|
|||||||
).as_deref() {
|
).as_deref() {
|
||||||
if matches!(action, "save" | "revert") {
|
if matches!(action, "save" | "revert") {
|
||||||
let message_string: String = if app_state.ui.show_login {
|
let message_string: String = if app_state.ui.show_login {
|
||||||
auth_e::execute_common_action(
|
auth_e::execute_common_action(action, login_state, grpc_client, current_position, total_count).await?
|
||||||
action,
|
|
||||||
login_state,
|
|
||||||
grpc_client,
|
|
||||||
current_position,
|
|
||||||
total_count,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
} else if app_state.ui.show_register {
|
} else if app_state.ui.show_register {
|
||||||
auth_e::execute_common_action(
|
auth_e::execute_common_action(action, register_state, grpc_client, current_position, total_count).await?
|
||||||
action,
|
|
||||||
register_state,
|
|
||||||
grpc_client,
|
|
||||||
current_position,
|
|
||||||
total_count,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
} else if app_state.ui.show_add_table {
|
} else if app_state.ui.show_add_table {
|
||||||
format!(
|
// TODO: Implement common actions for AddTable if needed
|
||||||
"Action '{}' not fully implemented for Add Table view here.",
|
format!("Action '{}' not implemented for Add Table in edit mode.", action)
|
||||||
action
|
|
||||||
)
|
|
||||||
} else if app_state.ui.show_add_logic {
|
} else if app_state.ui.show_add_logic {
|
||||||
format!(
|
// TODO: Implement common actions for AddLogic if needed
|
||||||
"Action '{}' not fully implemented for Add Logic view here.",
|
format!("Action '{}' not implemented for Add Logic in edit mode.", action)
|
||||||
action
|
} else { // Assuming Form view
|
||||||
)
|
let outcome = form_e::execute_common_action(action, form_state, grpc_client, current_position, total_count).await?;
|
||||||
} else {
|
|
||||||
let outcome = form_e::execute_common_action(
|
|
||||||
action,
|
|
||||||
form_state,
|
|
||||||
grpc_client,
|
|
||||||
current_position,
|
|
||||||
total_count,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
match outcome {
|
match outcome {
|
||||||
EventOutcome::Ok(msg) => msg,
|
EventOutcome::Ok(msg) | EventOutcome::DataSaved(_, msg) => msg,
|
||||||
EventOutcome::DataSaved(_, msg) => msg,
|
_ => format!("Unexpected outcome from common action: {:?}", outcome),
|
||||||
_ => format!(
|
|
||||||
"Unexpected outcome from common action: {:?}",
|
|
||||||
outcome
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return Ok(EditEventOutcome::Message(message_string));
|
return Ok(EditEventOutcome::Message(message_string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edit-specific actions
|
// --- Edit-specific actions ---
|
||||||
if let Some(action) =
|
if let Some(action_str) = config.get_edit_action_for_key(key.code, key.modifiers).as_deref() {
|
||||||
config.get_edit_action_for_key(key.code, key.modifiers)
|
// --- Handle "enter_decider" (Enter key) ---
|
||||||
.as_deref() {
|
if action_str == "enter_decider" {
|
||||||
// Handle enter_decider first
|
|
||||||
if action == "enter_decider" {
|
|
||||||
let effective_action = if app_state.ui.show_register
|
let effective_action = if app_state.ui.show_register
|
||||||
&& register_state.in_suggestion_mode
|
&& 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"
|
"select_suggestion"
|
||||||
} else {
|
} else {
|
||||||
"next_field"
|
"next_field" // Default action for Enter
|
||||||
};
|
};
|
||||||
|
|
||||||
let msg = if app_state.ui.show_login {
|
let msg = if app_state.ui.show_login {
|
||||||
auth_e::execute_edit_action(
|
auth_e::execute_edit_action(effective_action, key, login_state, ideal_cursor_column).await?
|
||||||
effective_action,
|
|
||||||
key,
|
|
||||||
login_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
} else if app_state.ui.show_add_table {
|
} else if app_state.ui.show_add_table {
|
||||||
add_table_e::execute_edit_action(
|
add_table_e::execute_edit_action(effective_action, key, &mut admin_state.add_table_state, ideal_cursor_column).await?
|
||||||
effective_action,
|
|
||||||
key,
|
|
||||||
&mut admin_state.add_table_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
} else if app_state.ui.show_add_logic {
|
} else if app_state.ui.show_add_logic {
|
||||||
add_logic_e::execute_edit_action(
|
add_logic_e::execute_edit_action(effective_action, key, &mut admin_state.add_logic_state, ideal_cursor_column).await?
|
||||||
effective_action,
|
|
||||||
key,
|
|
||||||
&mut admin_state.add_logic_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
} else if app_state.ui.show_register {
|
} else if app_state.ui.show_register {
|
||||||
auth_e::execute_edit_action(
|
auth_e::execute_edit_action(effective_action, key, register_state, ideal_cursor_column).await?
|
||||||
effective_action,
|
} else { // Form view
|
||||||
key,
|
form_e::execute_edit_action(effective_action, key, form_state, ideal_cursor_column).await?
|
||||||
register_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
} else {
|
|
||||||
form_e::execute_edit_action(
|
|
||||||
effective_action,
|
|
||||||
key,
|
|
||||||
form_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
};
|
};
|
||||||
return Ok(EditEventOutcome::Message(msg));
|
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 {
|
if app_state.ui.show_register && register_state.in_suggestion_mode {
|
||||||
let msg = auth_e::execute_edit_action(
|
let msg = auth_e::execute_edit_action("exit_suggestion_mode", key, register_state, ideal_cursor_column).await?;
|
||||||
"exit_suggestion_mode",
|
|
||||||
key,
|
|
||||||
register_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
return Ok(EditEventOutcome::Message(msg));
|
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 {
|
} else {
|
||||||
return Ok(EditEventOutcome::ExitEditMode);
|
return Ok(EditEventOutcome::ExitEditMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special handling for role field suggestions (Register view only)
|
// --- Autocomplete for AddLogicState Target Column ---
|
||||||
if app_state.ui.show_register && register_state.current_field() == 4 {
|
if app_state.ui.show_add_logic && admin_state.add_logic_state.current_field() == 1 { // Target Column field
|
||||||
if !register_state.in_suggestion_mode
|
if action_str == "suggestion_down" { // "Tab" is mapped to suggestion_down
|
||||||
&& key.code == KeyCode::Tab
|
if !admin_state.add_logic_state.in_target_column_suggestion_mode {
|
||||||
&& key.modifiers == KeyModifiers::NONE
|
// Attempt to open suggestions
|
||||||
{
|
if let Some(profile_name) = admin_state.add_logic_state.profile_name.clone().into() {
|
||||||
register_state.update_role_suggestions();
|
if let Some(table_name) = admin_state.add_logic_state.selected_table_name.clone() {
|
||||||
if !register_state.role_suggestions.is_empty() {
|
debug!("Fetching table structure for autocomplete: Profile='{}', Table='{}'", profile_name, table_name);
|
||||||
register_state.in_suggestion_mode = true;
|
match grpc_client.get_table_structure(profile_name, table_name).await {
|
||||||
register_state.selected_suggestion_index = Some(0);
|
Ok(ts_response) => {
|
||||||
return Ok(EditEventOutcome::Message(
|
admin_state.add_logic_state.table_columns_for_suggestions =
|
||||||
"Suggestions shown".to_string(),
|
ts_response.columns.into_iter().map(|c| c.name).collect();
|
||||||
));
|
admin_state.add_logic_state.update_target_column_suggestions();
|
||||||
} else {
|
if !admin_state.add_logic_state.target_column_suggestions.is_empty() {
|
||||||
return Ok(EditEventOutcome::Message(
|
admin_state.add_logic_state.in_target_column_suggestion_mode = true;
|
||||||
"No suggestions available".to_string(),
|
// update_target_column_suggestions handles initial selection
|
||||||
));
|
return Ok(EditEventOutcome::Message("Column suggestions shown".to_string()));
|
||||||
|
} else {
|
||||||
|
return Ok(EditEventOutcome::Message("No column suggestions for current input".to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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" {
|
||||||
if register_state.in_suggestion_mode
|
let msg = add_logic_e::execute_edit_action(action_str, key, &mut admin_state.add_logic_state, ideal_cursor_column).await?;
|
||||||
&& matches!(
|
|
||||||
action,
|
|
||||||
"suggestion_down" | "suggestion_up"
|
|
||||||
)
|
|
||||||
{
|
|
||||||
let msg = auth_e::execute_edit_action(
|
|
||||||
action,
|
|
||||||
key,
|
|
||||||
register_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
return Ok(EditEventOutcome::Message(msg));
|
return Ok(EditEventOutcome::Message(msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute other edit actions based on the current view
|
// --- 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 {
|
||||||
|
// 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 {
|
let msg = if app_state.ui.show_login {
|
||||||
auth_e::execute_edit_action(
|
auth_e::execute_edit_action(action_str, key, login_state, ideal_cursor_column).await?
|
||||||
action,
|
|
||||||
key,
|
|
||||||
login_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
} else if app_state.ui.show_add_table {
|
} else if app_state.ui.show_add_table {
|
||||||
add_table_e::execute_edit_action(
|
add_table_e::execute_edit_action(action_str, key, &mut admin_state.add_table_state, ideal_cursor_column).await?
|
||||||
action,
|
|
||||||
key,
|
|
||||||
&mut admin_state.add_table_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
} else if app_state.ui.show_add_logic {
|
} else if app_state.ui.show_add_logic {
|
||||||
add_logic_e::execute_edit_action(
|
// If not a suggestion action handled above for AddLogic
|
||||||
action,
|
if !(admin_state.add_logic_state.in_target_column_suggestion_mode && matches!(action_str, "suggestion_down" | "suggestion_up")) {
|
||||||
key,
|
add_logic_e::execute_edit_action(action_str, key, &mut admin_state.add_logic_state, ideal_cursor_column).await?
|
||||||
&mut admin_state.add_logic_state,
|
} else { String::new() /* Already handled */ }
|
||||||
ideal_cursor_column,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
} else if app_state.ui.show_register {
|
} else if app_state.ui.show_register {
|
||||||
auth_e::execute_edit_action(
|
if !(register_state.in_suggestion_mode && matches!(action_str, "suggestion_down" | "suggestion_up")) {
|
||||||
action,
|
auth_e::execute_edit_action(action_str, key, register_state, ideal_cursor_column).await?
|
||||||
key,
|
} else { String::new() /* Already handled */ }
|
||||||
register_state,
|
} else { // Form view
|
||||||
ideal_cursor_column,
|
form_e::execute_edit_action(action_str, key, form_state, ideal_cursor_column).await?
|
||||||
)
|
|
||||||
.await?
|
|
||||||
} else {
|
|
||||||
form_e::execute_edit_action(
|
|
||||||
action,
|
|
||||||
key,
|
|
||||||
form_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
};
|
};
|
||||||
return Ok(EditEventOutcome::Message(msg));
|
return Ok(EditEventOutcome::Message(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Character insertion ---
|
// --- 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 {
|
if app_state.ui.show_register && register_state.in_suggestion_mode {
|
||||||
register_state.in_suggestion_mode = false;
|
register_state.in_suggestion_mode = false;
|
||||||
register_state.show_role_suggestions = false;
|
register_state.show_role_suggestions = false;
|
||||||
register_state.selected_suggestion_index = None;
|
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 {
|
let mut char_insert_msg = if app_state.ui.show_login {
|
||||||
auth_e::execute_edit_action(
|
auth_e::execute_edit_action("insert_char", key, login_state, ideal_cursor_column).await?
|
||||||
"insert_char",
|
|
||||||
key,
|
|
||||||
login_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
} else if app_state.ui.show_add_table {
|
} else if app_state.ui.show_add_table {
|
||||||
add_table_e::execute_edit_action(
|
add_table_e::execute_edit_action("insert_char", key, &mut admin_state.add_table_state, ideal_cursor_column).await?
|
||||||
"insert_char",
|
|
||||||
key,
|
|
||||||
&mut admin_state.add_table_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
} else if app_state.ui.show_add_logic {
|
} else if app_state.ui.show_add_logic {
|
||||||
add_logic_e::execute_edit_action(
|
add_logic_e::execute_edit_action("insert_char", key, &mut admin_state.add_logic_state, ideal_cursor_column).await?
|
||||||
"insert_char",
|
|
||||||
key,
|
|
||||||
&mut admin_state.add_logic_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
} else if app_state.ui.show_register {
|
} else if app_state.ui.show_register {
|
||||||
auth_e::execute_edit_action(
|
auth_e::execute_edit_action("insert_char", key, register_state, ideal_cursor_column).await?
|
||||||
"insert_char",
|
} else { // Form view
|
||||||
key,
|
form_e::execute_edit_action("insert_char", key, form_state, ideal_cursor_column).await?
|
||||||
register_state,
|
|
||||||
ideal_cursor_column,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
} else {
|
|
||||||
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 {
|
if app_state.ui.show_register && register_state.current_field() == 4 {
|
||||||
register_state.update_role_suggestions();
|
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,
|
InputLogicName,
|
||||||
InputTargetColumn,
|
InputTargetColumn,
|
||||||
InputDescription,
|
InputDescription,
|
||||||
ScriptContentPreview, // Like ColumnsTable - can be highlighted/selected
|
ScriptContentPreview,
|
||||||
InsideScriptContent, // Like InsideColumnsTable - full editing mode
|
InsideScriptContent,
|
||||||
SaveButton,
|
SaveButton,
|
||||||
CancelButton,
|
CancelButton,
|
||||||
}
|
}
|
||||||
@@ -35,6 +35,13 @@ pub struct AddLogicState {
|
|||||||
pub has_unsaved_changes: bool,
|
pub has_unsaved_changes: bool,
|
||||||
pub editor_keybinding_mode: EditorKeybindingMode,
|
pub editor_keybinding_mode: EditorKeybindingMode,
|
||||||
pub vim_state: VimState,
|
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 {
|
impl AddLogicState {
|
||||||
@@ -56,10 +63,57 @@ impl AddLogicState {
|
|||||||
has_unsaved_changes: false,
|
has_unsaved_changes: false,
|
||||||
editor_keybinding_mode: editor_config.keybinding_mode.clone(),
|
editor_keybinding_mode: editor_config.keybinding_mode.clone(),
|
||||||
vim_state: VimState::default(),
|
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;
|
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 {
|
impl Default for AddLogicState {
|
||||||
@@ -122,21 +176,21 @@ impl CanvasState for AddLogicState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_current_field(&mut self, index: usize) {
|
fn set_current_field(&mut self, index: usize) {
|
||||||
self.current_focus = match index {
|
let new_focus = match index {
|
||||||
0 => {
|
0 => AddLogicFocus::InputLogicName,
|
||||||
self.last_canvas_field = 0;
|
1 => AddLogicFocus::InputTargetColumn,
|
||||||
AddLogicFocus::InputLogicName
|
2 => AddLogicFocus::InputDescription,
|
||||||
},
|
_ => return, // Or handle error/default
|
||||||
1 => {
|
|
||||||
self.last_canvas_field = 1;
|
|
||||||
AddLogicFocus::InputTargetColumn
|
|
||||||
},
|
|
||||||
2 => {
|
|
||||||
self.last_canvas_field = 2;
|
|
||||||
AddLogicFocus::InputDescription
|
|
||||||
},
|
|
||||||
_ => self.current_focus,
|
|
||||||
};
|
};
|
||||||
|
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) {
|
fn set_current_cursor_pos(&mut self, pos: usize) {
|
||||||
@@ -161,10 +215,24 @@ impl CanvasState for AddLogicState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_suggestions(&self) -> Option<&[String]> {
|
fn get_suggestions(&self) -> Option<&[String]> {
|
||||||
None
|
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> {
|
fn get_selected_suggestion_index(&self) -> Option<usize> {
|
||||||
None
|
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