Files
komp_ac/client/src/functions/modes/navigation/add_logic_nav.rs
2025-08-27 12:14:09 +02:00

534 lines
26 KiB
Rust

// src/functions/modes/navigation/add_logic_nav.rs
use crate::config::binds::config::{Config, EditorKeybindingMode};
use crate::state::{
app::state::AppState,
pages::add_logic::{AddLogicFocus, AddLogicState},
};
use crate::buffer::{AppView, BufferState};
use crossterm::event::{KeyEvent, KeyCode, KeyModifiers};
use crate::services::GrpcClient;
use tokio::sync::mpsc;
use anyhow::Result;
use crate::components::common::text_editor::TextEditor;
use crate::services::ui_service::UiService;
use tui_textarea::CursorMove;
use crate::pages::admin::AdminState;
use crate::pages::routing::{Router, Page};
pub type SaveLogicResultSender = mpsc::Sender<Result<String>>;
pub fn handle_add_logic_navigation(
key_event: KeyEvent,
config: &Config,
app_state: &mut AppState,
buffer_state: &mut BufferState,
grpc_client: GrpcClient,
save_logic_sender: SaveLogicResultSender,
command_message: &mut String,
router: &mut Router,
) -> bool {
if let Page::AddLogic(add_logic_state) = &mut router.current {
// === FULLSCREEN SCRIPT EDITING ===
if add_logic_state.current_focus == AddLogicFocus::InsideScriptContent {
// === AUTOCOMPLETE HANDLING ===
if add_logic_state.script_editor_autocomplete_active {
match key_event.code {
KeyCode::Char(c) if c.is_alphanumeric() || c == '_' => {
add_logic_state.script_editor_filter_text.push(c);
add_logic_state.update_script_editor_suggestions();
{
let mut editor_borrow =
add_logic_state.script_content_editor.borrow_mut();
TextEditor::handle_input(
&mut editor_borrow,
key_event,
&add_logic_state.editor_keybinding_mode,
&mut add_logic_state.vim_state,
);
}
*command_message =
format!("Filtering: @{}", add_logic_state.script_editor_filter_text);
return true;
}
KeyCode::Backspace => {
if !add_logic_state.script_editor_filter_text.is_empty() {
add_logic_state.script_editor_filter_text.pop();
add_logic_state.update_script_editor_suggestions();
{
let mut editor_borrow =
add_logic_state.script_content_editor.borrow_mut();
TextEditor::handle_input(
&mut editor_borrow,
key_event,
&add_logic_state.editor_keybinding_mode,
&mut add_logic_state.vim_state,
);
}
*command_message =
if add_logic_state.script_editor_filter_text.is_empty() {
"Autocomplete: @".to_string()
} else {
format!(
"Filtering: @{}",
add_logic_state.script_editor_filter_text
)
};
} else {
let should_deactivate =
if let Some((trigger_line, trigger_col)) =
add_logic_state.script_editor_trigger_position
{
let current_cursor = {
let editor_borrow =
add_logic_state.script_content_editor.borrow();
editor_borrow.cursor()
};
current_cursor.0 == trigger_line
&& current_cursor.1 == trigger_col + 1
} else {
false
};
if should_deactivate {
add_logic_state.deactivate_script_editor_autocomplete();
*command_message = "Autocomplete cancelled".to_string();
}
{
let mut editor_borrow =
add_logic_state.script_content_editor.borrow_mut();
TextEditor::handle_input(
&mut editor_borrow,
key_event,
&add_logic_state.editor_keybinding_mode,
&mut add_logic_state.vim_state,
);
}
}
return true;
}
KeyCode::Tab | KeyCode::Down => {
if !add_logic_state.script_editor_suggestions.is_empty() {
let current = add_logic_state
.script_editor_selected_suggestion_index
.unwrap_or(0);
let next =
(current + 1) % add_logic_state.script_editor_suggestions.len();
add_logic_state.script_editor_selected_suggestion_index = Some(next);
*command_message = format!(
"Selected: {}",
add_logic_state.script_editor_suggestions[next]
);
}
return true;
}
KeyCode::Up => {
if !add_logic_state.script_editor_suggestions.is_empty() {
let current = add_logic_state
.script_editor_selected_suggestion_index
.unwrap_or(0);
let prev = if current == 0 {
add_logic_state.script_editor_suggestions.len() - 1
} else {
current - 1
};
add_logic_state.script_editor_selected_suggestion_index = Some(prev);
*command_message = format!(
"Selected: {}",
add_logic_state.script_editor_suggestions[prev]
);
}
return true;
}
KeyCode::Enter => {
if let Some(selected_idx) =
add_logic_state.script_editor_selected_suggestion_index
{
if let Some(suggestion) = add_logic_state
.script_editor_suggestions
.get(selected_idx)
.cloned()
{
let trigger_pos =
add_logic_state.script_editor_trigger_position;
let filter_len =
add_logic_state.script_editor_filter_text.len();
add_logic_state.deactivate_script_editor_autocomplete();
add_logic_state.has_unsaved_changes = true;
if let Some(pos) = trigger_pos {
let mut editor_borrow =
add_logic_state.script_content_editor.borrow_mut();
if suggestion == "sql" {
replace_autocomplete_text(
&mut editor_borrow,
pos,
filter_len,
"sql",
);
editor_borrow.insert_str("('')");
editor_borrow.move_cursor(CursorMove::Back);
editor_borrow.move_cursor(CursorMove::Back);
*command_message = "Inserted: @sql('')".to_string();
} else {
let is_table_selection =
add_logic_state.is_table_name_suggestion(&suggestion);
replace_autocomplete_text(
&mut editor_borrow,
pos,
filter_len,
&suggestion,
);
if is_table_selection {
editor_borrow.insert_str(".");
let new_cursor = editor_borrow.cursor();
drop(editor_borrow);
add_logic_state.script_editor_trigger_position =
Some(new_cursor);
add_logic_state.script_editor_autocomplete_active = true;
add_logic_state.script_editor_filter_text.clear();
add_logic_state
.trigger_column_autocomplete_for_table(
suggestion.clone(),
);
let profile_name =
add_logic_state.profile_name.clone();
let table_name_for_fetch = suggestion.clone();
let mut client_clone = grpc_client.clone();
tokio::spawn(async move {
if let Err(e) = UiService::fetch_columns_for_table(
&mut client_clone,
&profile_name,
&table_name_for_fetch,
)
.await
{
tracing::error!(
"Failed to fetch columns for {}.{}: {}",
profile_name,
table_name_for_fetch,
e
);
}
});
*command_message = format!(
"Selected table '{}', fetching columns...",
suggestion
);
} else {
*command_message =
format!("Inserted: {}", suggestion);
}
}
}
return true;
}
}
add_logic_state.deactivate_script_editor_autocomplete();
{
let mut editor_borrow =
add_logic_state.script_content_editor.borrow_mut();
TextEditor::handle_input(
&mut editor_borrow,
key_event,
&add_logic_state.editor_keybinding_mode,
&mut add_logic_state.vim_state,
);
}
return true;
}
KeyCode::Esc => {
add_logic_state.deactivate_script_editor_autocomplete();
*command_message = "Autocomplete cancelled".to_string();
}
_ => {
add_logic_state.deactivate_script_editor_autocomplete();
*command_message = "Autocomplete cancelled".to_string();
{
let mut editor_borrow =
add_logic_state.script_content_editor.borrow_mut();
TextEditor::handle_input(
&mut editor_borrow,
key_event,
&add_logic_state.editor_keybinding_mode,
&mut add_logic_state.vim_state,
);
}
return true;
}
}
}
// Trigger autocomplete with '@'
if key_event.code == KeyCode::Char('@') && key_event.modifiers == KeyModifiers::NONE {
let should_trigger = match add_logic_state.editor_keybinding_mode {
EditorKeybindingMode::Vim => {
TextEditor::is_vim_insert_mode(&add_logic_state.vim_state)
}
_ => true,
};
if should_trigger {
let cursor_before = {
let editor_borrow = add_logic_state.script_content_editor.borrow();
editor_borrow.cursor()
};
{
let mut editor_borrow =
add_logic_state.script_content_editor.borrow_mut();
TextEditor::handle_input(
&mut editor_borrow,
key_event,
&add_logic_state.editor_keybinding_mode,
&mut add_logic_state.vim_state,
);
}
add_logic_state.script_editor_trigger_position = Some(cursor_before);
add_logic_state.script_editor_autocomplete_active = true;
add_logic_state.script_editor_filter_text.clear();
add_logic_state.update_script_editor_suggestions();
add_logic_state.has_unsaved_changes = true;
*command_message = "Autocomplete: @ (Tab/↑↓ to navigate, Enter to select, Esc to cancel)".to_string();
return true;
}
}
// Esc handling
if key_event.code == KeyCode::Esc && key_event.modifiers == KeyModifiers::NONE {
match add_logic_state.editor_keybinding_mode {
EditorKeybindingMode::Vim => {
let was_insert =
TextEditor::is_vim_insert_mode(&add_logic_state.vim_state);
{
let mut editor_borrow =
add_logic_state.script_content_editor.borrow_mut();
TextEditor::handle_input(
&mut editor_borrow,
key_event,
&add_logic_state.editor_keybinding_mode,
&mut add_logic_state.vim_state,
);
}
if was_insert {
*command_message =
"VIM: Normal Mode. Esc again to exit script.".to_string();
} else {
add_logic_state.current_focus =
AddLogicFocus::ScriptContentPreview;
app_state.ui.focus_outside_canvas = true;
*command_message = "Exited script editing.".to_string();
}
}
_ => {
add_logic_state.current_focus = AddLogicFocus::ScriptContentPreview;
app_state.ui.focus_outside_canvas = true;
*command_message = "Exited script editing.".to_string();
}
}
return true;
}
// Normal text input
let changed = {
let mut editor_borrow = add_logic_state.script_content_editor.borrow_mut();
TextEditor::handle_input(
&mut editor_borrow,
key_event,
&add_logic_state.editor_keybinding_mode,
&mut add_logic_state.vim_state,
)
};
if changed {
add_logic_state.has_unsaved_changes = true;
}
return true;
}
// === NON-FULLSCREEN NAVIGATION ===
let action = config.get_general_action(key_event.code, key_event.modifiers);
let current_focus = add_logic_state.current_focus;
let mut handled = true;
let mut new_focus = current_focus;
match action.as_deref() {
Some("exit_table_scroll") => {
handled = false;
}
Some("move_up") => {
match current_focus {
AddLogicFocus::InputLogicName => {}
AddLogicFocus::InputTargetColumn => new_focus = AddLogicFocus::InputLogicName,
AddLogicFocus::InputDescription => {
new_focus = AddLogicFocus::InputTargetColumn
}
AddLogicFocus::ScriptContentPreview => {
new_focus = AddLogicFocus::InputDescription
}
AddLogicFocus::SaveButton => new_focus = AddLogicFocus::ScriptContentPreview,
AddLogicFocus::CancelButton => new_focus = AddLogicFocus::SaveButton,
_ => handled = false,
}
}
Some("move_down") => {
match current_focus {
AddLogicFocus::InputLogicName => {
new_focus = AddLogicFocus::InputTargetColumn
}
AddLogicFocus::InputTargetColumn => {
new_focus = AddLogicFocus::InputDescription
}
AddLogicFocus::InputDescription => {
add_logic_state.last_canvas_field = 2;
new_focus = AddLogicFocus::ScriptContentPreview;
}
AddLogicFocus::ScriptContentPreview => {
new_focus = AddLogicFocus::SaveButton
}
AddLogicFocus::SaveButton => new_focus = AddLogicFocus::CancelButton,
AddLogicFocus::CancelButton => {}
_ => handled = false,
}
}
Some("next_option") => {
match current_focus {
AddLogicFocus::InputLogicName
| AddLogicFocus::InputTargetColumn
| AddLogicFocus::InputDescription => {
new_focus = AddLogicFocus::ScriptContentPreview
}
AddLogicFocus::ScriptContentPreview => {
new_focus = AddLogicFocus::SaveButton
}
AddLogicFocus::SaveButton => new_focus = AddLogicFocus::CancelButton,
AddLogicFocus::CancelButton => {}
_ => handled = false,
}
}
Some("previous_option") => {
match current_focus {
AddLogicFocus::InputLogicName
| AddLogicFocus::InputTargetColumn
| AddLogicFocus::InputDescription => {}
AddLogicFocus::ScriptContentPreview => {
new_focus = AddLogicFocus::InputDescription
}
AddLogicFocus::SaveButton => {
new_focus = AddLogicFocus::ScriptContentPreview
}
AddLogicFocus::CancelButton => new_focus = AddLogicFocus::SaveButton,
_ => handled = false,
}
}
Some("next_field") => {
new_focus = match current_focus {
AddLogicFocus::InputLogicName => AddLogicFocus::InputTargetColumn,
AddLogicFocus::InputTargetColumn => AddLogicFocus::InputDescription,
AddLogicFocus::InputDescription => AddLogicFocus::ScriptContentPreview,
AddLogicFocus::ScriptContentPreview => AddLogicFocus::SaveButton,
AddLogicFocus::SaveButton => AddLogicFocus::CancelButton,
AddLogicFocus::CancelButton => AddLogicFocus::InputLogicName,
_ => current_focus,
};
}
Some("prev_field") => {
new_focus = match current_focus {
AddLogicFocus::InputLogicName => AddLogicFocus::CancelButton,
AddLogicFocus::InputTargetColumn => AddLogicFocus::InputLogicName,
AddLogicFocus::InputDescription => AddLogicFocus::InputTargetColumn,
AddLogicFocus::ScriptContentPreview => AddLogicFocus::InputDescription,
AddLogicFocus::SaveButton => AddLogicFocus::ScriptContentPreview,
AddLogicFocus::CancelButton => AddLogicFocus::SaveButton,
_ => current_focus,
};
}
Some("select") => {
match current_focus {
AddLogicFocus::ScriptContentPreview => {
new_focus = AddLogicFocus::InsideScriptContent;
app_state.ui.focus_outside_canvas = false;
let mode_hint = match add_logic_state.editor_keybinding_mode {
EditorKeybindingMode::Vim => {
"VIM mode - 'i'/'a'/'o' to edit"
}
_ => "Enter/Ctrl+E to edit",
};
*command_message = format!(
"Fullscreen script editing. {} or Esc to exit.",
mode_hint
);
handled = true;
}
AddLogicFocus::SaveButton => {
*command_message = "Save logic action".to_string();
handled = true;
}
AddLogicFocus::CancelButton => {
buffer_state.update_history(AppView::Admin);
*command_message = "Cancelled Add Logic".to_string();
handled = true;
}
AddLogicFocus::InputLogicName
| AddLogicFocus::InputTargetColumn
| AddLogicFocus::InputDescription => {
// Focus canvas inputs; let canvas keymap handle editing
app_state.ui.focus_outside_canvas = false;
handled = false; // forward to canvas
}
_ => handled = false,
}
}
Some("toggle_edit_mode") => {
match current_focus {
AddLogicFocus::InputLogicName
| AddLogicFocus::InputTargetColumn
| AddLogicFocus::InputDescription => {
app_state.ui.focus_outside_canvas = false;
*command_message =
"Focus moved to input. Use i/a (Vim) or type to edit.".to_string();
handled = true;
}
_ => {
*command_message = "Cannot toggle edit mode here.".to_string();
}
}
}
_ => handled = false,
}
if handled && current_focus != new_focus {
add_logic_state.current_focus = new_focus;
let new_is_canvas_input_focus = matches!(
new_focus,
AddLogicFocus::InputLogicName
| AddLogicFocus::InputTargetColumn
| AddLogicFocus::InputDescription
);
if new_is_canvas_input_focus {
app_state.ui.focus_outside_canvas = false;
} else {
app_state.ui.focus_outside_canvas = true;
}
}
handled
} else {
false
}
}
fn replace_autocomplete_text(
editor: &mut tui_textarea::TextArea,
trigger_pos: (usize, usize),
filter_len: usize,
replacement: &str,
) {
let filter_start_pos = (trigger_pos.0, trigger_pos.1 + 1);
editor.move_cursor(CursorMove::Jump(filter_start_pos.0 as u16, filter_start_pos.1 as u16));
for _ in 0..filter_len {
editor.delete_next_char();
}
editor.insert_str(replacement);
}