working find palette now properly well

This commit is contained in:
filipriec
2025-06-07 09:16:43 +02:00
parent 02c62213c3
commit f5edf52571
2 changed files with 134 additions and 446 deletions

View File

@@ -82,6 +82,8 @@ impl TableDependencyGraph {
} }
} }
// ... (NavigationState struct and its new(), activate_*, deactivate(), add_char(), remove_char(), move_*, autocomplete_selected(), get_display_input() methods are unchanged) ...
pub struct NavigationState { pub struct NavigationState {
pub active: bool, pub active: bool,
pub input: String, pub input: String,
@@ -114,7 +116,7 @@ impl NavigationState {
self.input.clear(); self.input.clear();
self.current_path.clear(); self.current_path.clear();
self.graph = None; self.graph = None;
self.update_filtered_options(); // Initial filter with empty input self.update_filtered_options();
} }
pub fn activate_table_tree(&mut self, graph: TableDependencyGraph) { pub fn activate_table_tree(&mut self, graph: TableDependencyGraph) {
@@ -123,7 +125,7 @@ impl NavigationState {
self.graph = Some(graph); self.graph = Some(graph);
self.input.clear(); self.input.clear();
self.current_path.clear(); self.current_path.clear();
self.update_options_for_path(); // Initial options are root tables self.update_options_for_path();
} }
pub fn deactivate(&mut self) { pub fn deactivate(&mut self) {
@@ -145,7 +147,6 @@ impl NavigationState {
NavigationType::TableTree => { NavigationType::TableTree => {
if c == '/' { if c == '/' {
if !self.input.is_empty() { if !self.input.is_empty() {
// Append current input to path
if self.current_path.is_empty() { if self.current_path.is_empty() {
self.current_path = self.input.clone(); self.current_path = self.input.clone();
} else { } else {
@@ -155,10 +156,9 @@ impl NavigationState {
self.input.clear(); self.input.clear();
self.update_options_for_path(); self.update_options_for_path();
} }
// If input is empty and char is '/', do nothing or define behavior
} else { } else {
self.input.push(c); self.input.push(c);
self.update_filtered_options(); // Filter current level options based on input self.update_filtered_options();
} }
} }
} }
@@ -172,24 +172,15 @@ impl NavigationState {
} }
NavigationType::TableTree => { NavigationType::TableTree => {
if self.input.is_empty() { if self.input.is_empty() {
// If input is empty, try to go up in path
if !self.current_path.is_empty() { if !self.current_path.is_empty() {
if let Some(last_slash_idx) = if let Some(last_slash_idx) = self.current_path.rfind('/') {
self.current_path.rfind('/') self.input = self.current_path[last_slash_idx + 1..].to_string();
{ self.current_path = self.current_path[..last_slash_idx].to_string();
// Set input to the segment being removed from path
self.input = self.current_path
[last_slash_idx + 1..]
.to_string();
self.current_path =
self.current_path[..last_slash_idx].to_string();
} else { } else {
// Path was a single segment
self.input = self.current_path.clone(); self.input = self.current_path.clone();
self.current_path.clear(); self.current_path.clear();
} }
self.update_options_for_path(); self.update_options_for_path();
// After path change, current input might match some options, so filter
self.update_filtered_options(); self.update_filtered_options();
} }
} else { } else {
@@ -218,9 +209,7 @@ impl NavigationState {
return; return;
} }
self.selected_index = match self.selected_index { self.selected_index = match self.selected_index {
Some(current) if current >= self.filtered_options.len() - 1 => { Some(current) if current >= self.filtered_options.len() - 1 => Some(0),
Some(0)
}
Some(current) => Some(current + 1), Some(current) => Some(current + 1),
None => Some(0), None => Some(0),
}; };
@@ -234,18 +223,11 @@ impl NavigationState {
pub fn autocomplete_selected(&mut self) { pub fn autocomplete_selected(&mut self) {
if let Some(selected_option_str) = self.get_selected_option_str() { if let Some(selected_option_str) = self.get_selected_option_str() {
// The current `self.input` is the text being typed for the current segment/filter.
// We replace it with the full string of the selected option.
self.input = selected_option_str.to_string(); self.input = selected_option_str.to_string();
// After updating the input, we need to re-filter the options.
// This will typically result in the filtered_options containing only the
// autocompleted item (or items that start with it, if any).
self.update_filtered_options(); self.update_filtered_options();
} }
} }
// Returns the string to display in the input line of the palette
pub fn get_display_input(&self) -> String { pub fn get_display_input(&self) -> String {
match self.navigation_type { match self.navigation_type {
NavigationType::FindFile => self.input.clone(), NavigationType::FindFile => self.input.clone(),
@@ -259,11 +241,12 @@ impl NavigationState {
} }
} }
// Gets the full path of the currently selected item for TableTree, or input for FindFile // --- START FIX ---
pub fn get_selected_value(&self) -> Option<String> { pub fn get_selected_value(&self) -> Option<String> {
match self.navigation_type { match self.navigation_type {
NavigationType::FindFile => { NavigationType::FindFile => {
if self.input.is_empty() { None } else { Some(self.input.clone()) } // Return the highlighted option, not the raw input buffer.
self.get_selected_option_str().map(|s| s.to_string())
} }
NavigationType::TableTree => { NavigationType::TableTree => {
self.get_selected_option_str().map(|selected_name| { self.get_selected_option_str().map(|selected_name| {
@@ -276,26 +259,23 @@ impl NavigationState {
} }
} }
} }
// --- END FIX ---
// Update self.all_options based on current_path (for TableTree)
fn update_options_for_path(&mut self) { fn update_options_for_path(&mut self) {
if let NavigationType::TableTree = self.navigation_type { if let NavigationType::TableTree = self.navigation_type {
if let Some(graph) = &self.graph { if let Some(graph) = &self.graph {
self.all_options = self.all_options = graph.get_dependent_children(&self.current_path);
graph.get_dependent_children(&self.current_path);
} else { } else {
self.all_options.clear(); self.all_options.clear();
} }
} }
// For FindFile, all_options is set once at activation.
self.update_filtered_options(); self.update_filtered_options();
} }
// Update self.filtered_options based on self.all_options and self.input
fn update_filtered_options(&mut self) { fn update_filtered_options(&mut self) {
let filter_text = match self.navigation_type { let filter_text = match self.navigation_type {
NavigationType::FindFile => &self.input, NavigationType::FindFile => &self.input,
NavigationType::TableTree => &self.input, // For TableTree, input is the current segment being typed NavigationType::TableTree => &self.input,
} }
.to_lowercase(); .to_lowercase();
@@ -319,11 +299,12 @@ impl NavigationState {
if self.filtered_options.is_empty() { if self.filtered_options.is_empty() {
self.selected_index = None; self.selected_index = None;
} else { } else {
self.selected_index = Some(0); // Default to selecting the first item self.selected_index = Some(0);
} }
} }
} }
pub async fn handle_command_navigation_event( pub async fn handle_command_navigation_event(
navigation_state: &mut NavigationState, navigation_state: &mut NavigationState,
key: KeyEvent, key: KeyEvent,
@@ -338,55 +319,15 @@ pub async fn handle_command_navigation_event(
navigation_state.deactivate(); navigation_state.deactivate();
Ok(EventOutcome::Ok("Navigation cancelled".to_string())) Ok(EventOutcome::Ok("Navigation cancelled".to_string()))
} }
KeyCode::Enter => {
if let Some(selected_value) = navigation_state.get_selected_value() {
let outcome = match navigation_state.navigation_type {
NavigationType::FindFile => {
EventOutcome::Ok(format!("Selected file: {}", selected_value))
}
NavigationType::TableTree => {
EventOutcome::TableSelected { path: selected_value }
}
};
navigation_state.deactivate();
Ok(outcome)
} else {
// Enhanced Enter behavior for TableTree: if input is a valid partial path, try to navigate
if navigation_state.navigation_type == NavigationType::TableTree && !navigation_state.input.is_empty() {
// Check if current input is a prefix of any option or a full option name
if let Some(selected_opt_str) = navigation_state.get_selected_option_str() {
if navigation_state.input == selected_opt_str {
// Input exactly matches the selected option, try to navigate
let input_before_slash = navigation_state.input.clone();
navigation_state.add_char('/');
if navigation_state.input.is_empty() {
return Ok(EventOutcome::Ok(format!("Navigated to: {}/", input_before_slash)));
} else {
return Ok(EventOutcome::Ok(format!("Selected leaf: {}", input_before_slash)));
}
}
}
}
Ok(EventOutcome::Ok("No valid selection to confirm or navigate".to_string()))
}
}
KeyCode::Tab => { KeyCode::Tab => {
if let Some(selected_opt_str) = navigation_state.get_selected_option_str() { if let Some(selected_opt_str) = navigation_state.get_selected_option_str() {
// Scenario 1: Input already exactly matches the selected option
if navigation_state.input == selected_opt_str { if navigation_state.input == selected_opt_str {
// Only attempt to navigate deeper for TableTree mode
if navigation_state.navigation_type == NavigationType::TableTree { if navigation_state.navigation_type == NavigationType::TableTree {
let path_before_nav = navigation_state.current_path.clone(); let path_before_nav = navigation_state.current_path.clone();
let input_before_nav = navigation_state.input.clone(); let input_before_nav = navigation_state.input.clone();
navigation_state.add_char('/'); navigation_state.add_char('/');
if !(navigation_state.input.is_empty() &&
if navigation_state.input.is_empty() && (navigation_state.current_path != path_before_nav || !navigation_state.all_options.is_empty())) {
(navigation_state.current_path != path_before_nav || !navigation_state.all_options.is_empty()) {
// Navigation successful
} else {
// Revert if navigation didn't happen
if !navigation_state.input.is_empty() && navigation_state.input != input_before_nav { if !navigation_state.input.is_empty() && navigation_state.input != input_before_nav {
navigation_state.input = input_before_nav; navigation_state.input = input_before_nav;
if navigation_state.current_path != path_before_nav { if navigation_state.current_path != path_before_nav {
@@ -397,20 +338,11 @@ pub async fn handle_command_navigation_event(
} }
} }
} else { } else {
// Scenario 2: Input is a partial match - autocomplete
navigation_state.autocomplete_selected(); navigation_state.autocomplete_selected();
} }
} }
Ok(EventOutcome::Ok(String::new())) Ok(EventOutcome::Ok(String::new()))
} }
KeyCode::Up => {
navigation_state.move_up();
Ok(EventOutcome::Ok(String::new()))
}
KeyCode::Down => {
navigation_state.move_down();
Ok(EventOutcome::Ok(String::new()))
}
KeyCode::Backspace => { KeyCode::Backspace => {
navigation_state.remove_char(); navigation_state.remove_char();
Ok(EventOutcome::Ok(String::new())) Ok(EventOutcome::Ok(String::new()))
@@ -432,12 +364,24 @@ pub async fn handle_command_navigation_event(
} }
"select" => { "select" => {
if let Some(selected_value) = navigation_state.get_selected_value() { if let Some(selected_value) = navigation_state.get_selected_value() {
let message = match navigation_state.navigation_type { let outcome = match navigation_state.navigation_type {
NavigationType::FindFile => format!("Selected file: {}", selected_value), // --- START FIX ---
NavigationType::TableTree => format!("Selected table: {}", selected_value), NavigationType::FindFile => {
// The purpose of this palette is to select a table.
// Emit a TableSelected event instead of a generic Ok message.
EventOutcome::TableSelected {
path: selected_value,
}
}
// --- END FIX ---
NavigationType::TableTree => {
EventOutcome::TableSelected {
path: selected_value,
}
}
}; };
navigation_state.deactivate(); navigation_state.deactivate();
Ok(EventOutcome::Ok(message)) Ok(outcome)
} else { } else {
Ok(EventOutcome::Ok("No selection".to_string())) Ok(EventOutcome::Ok("No selection".to_string()))
} }

View File

@@ -7,7 +7,7 @@ use crate::functions::modes::navigation::add_logic_nav::SaveLogicResultSender;
use crate::functions::modes::navigation::add_table_nav::SaveTableResultSender; use crate::functions::modes::navigation::add_table_nav::SaveTableResultSender;
use crate::functions::modes::navigation::{add_table_nav, admin_nav}; use crate::functions::modes::navigation::{add_table_nav, admin_nav};
use crate::modes::general::command_navigation::{ use crate::modes::general::command_navigation::{
handle_command_navigation_event, NavigationState, TableDependencyGraph, handle_command_navigation_event, NavigationState,
}; };
use crate::modes::{ use crate::modes::{
canvas::{common_mode, edit, read_only}, canvas::{common_mode, edit, read_only},
@@ -133,7 +133,6 @@ impl EventHandler {
) -> Result<EventOutcome> { ) -> Result<EventOutcome> {
let mut current_mode = ModeManager::derive_mode(app_state, self, admin_state); let mut current_mode = ModeManager::derive_mode(app_state, self, admin_state);
// Handle active command navigation first
if current_mode == AppMode::General && self.navigation_state.active { if current_mode == AppMode::General && self.navigation_state.active {
if let Event::Key(key_event) = event { if let Event::Key(key_event) = event {
let outcome = let outcome =
@@ -155,43 +154,26 @@ impl EventHandler {
let current_view = { let current_view = {
let ui = &app_state.ui; let ui = &app_state.ui;
if ui.show_intro { if ui.show_intro { AppView::Intro }
AppView::Intro else if ui.show_login { AppView::Login }
} else if ui.show_login { else if ui.show_register { AppView::Register }
AppView::Login else if ui.show_admin { AppView::Admin }
} else if ui.show_register { else if ui.show_add_logic { AppView::AddLogic }
AppView::Register else if ui.show_add_table { AppView::AddTable }
} else if ui.show_admin { else if ui.show_form { AppView::Form }
AppView::Admin else { AppView::Scratch }
} else if ui.show_add_logic {
AppView::AddLogic
} else if ui.show_add_table {
AppView::AddTable
} else if ui.show_form {
AppView::Form
} else {
AppView::Scratch
}
}; };
buffer_state.update_history(current_view); buffer_state.update_history(current_view);
if app_state.ui.dialog.dialog_show { if app_state.ui.dialog.dialog_show {
if let Event::Key(key_event) = event { if let Event::Key(key_event) = event {
if let Some(dialog_result) = dialog::handle_dialog_event( if let Some(dialog_result) = dialog::handle_dialog_event(
&Event::Key(key_event), &Event::Key(key_event), config, app_state, login_state,
config, register_state, buffer_state, admin_state,
app_state, ).await {
login_state,
register_state,
buffer_state,
admin_state,
)
.await
{
return dialog_result; return dialog_result;
} }
} else if let Event::Resize(_, _) = event { } else if let Event::Resize(_, _) = event {
// Handle resize if needed
} }
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
@@ -201,34 +183,16 @@ impl EventHandler {
let modifiers = key_event.modifiers; let modifiers = key_event.modifiers;
if UiStateHandler::toggle_sidebar(&mut app_state.ui, config, key_code, modifiers) { if UiStateHandler::toggle_sidebar(&mut app_state.ui, config, key_code, modifiers) {
let message = format!( let message = format!("Sidebar {}", if app_state.ui.show_sidebar { "shown" } else { "hidden" });
"Sidebar {}",
if app_state.ui.show_sidebar {
"shown"
} else {
"hidden"
}
);
return Ok(EventOutcome::Ok(message)); return Ok(EventOutcome::Ok(message));
} }
if UiStateHandler::toggle_buffer_list(&mut app_state.ui, config, key_code, modifiers) { if UiStateHandler::toggle_buffer_list(&mut app_state.ui, config, key_code, modifiers) {
let message = format!( let message = format!("Buffer {}", if app_state.ui.show_buffer_list { "shown" } else { "hidden" });
"Buffer {}",
if app_state.ui.show_buffer_list {
"shown"
} else {
"hidden"
}
);
return Ok(EventOutcome::Ok(message)); return Ok(EventOutcome::Ok(message));
} }
if !matches!(current_mode, AppMode::Edit | AppMode::Command) { if !matches!(current_mode, AppMode::Edit | AppMode::Command) {
if let Some(action) = config.get_action_for_key_in_mode( if let Some(action) = config.get_action_for_key_in_mode(&config.keybindings.global, key_code, modifiers) {
&config.keybindings.global,
key_code,
modifiers,
) {
match action { match action {
"next_buffer" => { "next_buffer" => {
if buffer::switch_buffer(buffer_state, true) { if buffer::switch_buffer(buffer_state, true) {
@@ -237,15 +201,12 @@ impl EventHandler {
} }
"previous_buffer" => { "previous_buffer" => {
if buffer::switch_buffer(buffer_state, false) { if buffer::switch_buffer(buffer_state, false) {
return Ok(EventOutcome::Ok( return Ok(EventOutcome::Ok("Switched to previous buffer".to_string()));
"Switched to previous buffer".to_string(),
));
} }
} }
"close_buffer" => { "close_buffer" => {
let current_table_name = Some("2025_customer"); let current_table_name = app_state.current_view_table_name.as_deref();
let message = let message = buffer_state.close_buffer_with_intro_fallback(current_table_name);
buffer_state.close_buffer_with_intro_fallback(current_table_name);
return Ok(EventOutcome::Ok(message)); return Ok(EventOutcome::Ok(message));
} }
_ => {} _ => {}
@@ -256,14 +217,7 @@ impl EventHandler {
match current_mode { match current_mode {
AppMode::General => { AppMode::General => {
if app_state.ui.show_admin && auth_state.role.as_deref() == Some("admin") { if app_state.ui.show_admin && auth_state.role.as_deref() == Some("admin") {
if admin_nav::handle_admin_navigation( if admin_nav::handle_admin_navigation(key_event, config, app_state, admin_state, buffer_state, &mut self.command_message) {
key_event,
config,
app_state,
admin_state,
buffer_state,
&mut self.command_message,
) {
return Ok(EventOutcome::Ok(self.command_message.clone())); return Ok(EventOutcome::Ok(self.command_message.clone()));
} }
} }
@@ -271,17 +225,9 @@ impl EventHandler {
if app_state.ui.show_add_logic { if app_state.ui.show_add_logic {
let client_clone = grpc_client.clone(); let client_clone = grpc_client.clone();
let sender_clone = self.save_logic_result_sender.clone(); let sender_clone = self.save_logic_result_sender.clone();
if add_logic_nav::handle_add_logic_navigation( if add_logic_nav::handle_add_logic_navigation(
key_event, key_event, config, app_state, &mut admin_state.add_logic_state,
config, &mut self.is_edit_mode, buffer_state, client_clone, sender_clone, &mut self.command_message,
app_state,
&mut admin_state.add_logic_state,
&mut self.is_edit_mode,
buffer_state,
client_clone,
sender_clone,
&mut self.command_message,
) { ) {
return Ok(EventOutcome::Ok(self.command_message.clone())); return Ok(EventOutcome::Ok(self.command_message.clone()));
} }
@@ -290,85 +236,45 @@ impl EventHandler {
if app_state.ui.show_add_table { if app_state.ui.show_add_table {
let client_clone = grpc_client.clone(); let client_clone = grpc_client.clone();
let sender_clone = self.save_table_result_sender.clone(); let sender_clone = self.save_table_result_sender.clone();
if add_table_nav::handle_add_table_navigation( if add_table_nav::handle_add_table_navigation(
key_event, key_event, config, app_state, &mut admin_state.add_table_state,
config, client_clone, sender_clone, &mut self.command_message,
app_state,
&mut admin_state.add_table_state,
client_clone,
sender_clone,
&mut self.command_message,
) { ) {
return Ok(EventOutcome::Ok(self.command_message.clone())); return Ok(EventOutcome::Ok(self.command_message.clone()));
} }
} }
let nav_outcome = navigation::handle_navigation_event( let nav_outcome = navigation::handle_navigation_event(
key_event, key_event, config, form_state, app_state, login_state, register_state,
config, intro_state, admin_state, &mut self.command_mode, &mut self.command_input,
form_state, &mut self.command_message, &mut self.navigation_state,
app_state, ).await;
login_state,
register_state,
intro_state,
admin_state,
&mut self.command_mode,
&mut self.command_input,
&mut self.command_message,
&mut self.navigation_state,
)
.await;
match nav_outcome { match nav_outcome {
Ok(EventOutcome::ButtonSelected { context, index }) => { Ok(EventOutcome::ButtonSelected { context, index }) => {
let message = match context { let message = match context {
UiContext::Intro => { UiContext::Intro => {
intro::handle_intro_selection(app_state, buffer_state, index); intro::handle_intro_selection(app_state, buffer_state, index);
if app_state.ui.show_admin { if app_state.ui.show_admin && !app_state.profile_tree.profiles.is_empty() {
if !app_state.profile_tree.profiles.is_empty() {
admin_state.profile_list_state.select(Some(0)); admin_state.profile_list_state.select(Some(0));
} }
}
format!("Intro Option {} selected", index) format!("Intro Option {} selected", index)
} }
UiContext::Login => match index { UiContext::Login => match index {
0 => login::initiate_login( 0 => login::initiate_login(login_state, app_state, self.auth_client.clone(), self.login_result_sender.clone()),
login_state, 1 => login::back_to_main(login_state, app_state, buffer_state).await,
app_state,
self.auth_client.clone(),
self.login_result_sender.clone(),
),
1 => {
login::back_to_main(login_state, app_state, buffer_state)
.await
}
_ => "Invalid Login Option".to_string(), _ => "Invalid Login Option".to_string(),
}, },
UiContext::Register => match index { UiContext::Register => match index {
0 => register::initiate_registration( 0 => register::initiate_registration(register_state, app_state, self.auth_client.clone(), self.register_result_sender.clone()),
register_state, 1 => register::back_to_login(register_state, app_state, buffer_state).await,
app_state,
self.auth_client.clone(),
self.register_result_sender.clone(),
),
1 => {
register::back_to_login(
register_state,
app_state,
buffer_state,
)
.await
}
_ => "Invalid Login Option".to_string(), _ => "Invalid Login Option".to_string(),
}, },
UiContext::Admin => { UiContext::Admin => {
admin::handle_admin_selection(app_state, admin_state); admin::handle_admin_selection(app_state, admin_state);
format!("Admin Option {} selected", index) format!("Admin Option {} selected", index)
} }
UiContext::Dialog => { UiContext::Dialog => "Internal error: Unexpected dialog state".to_string(),
"Internal error: Unexpected dialog state".to_string()
}
}; };
return Ok(EventOutcome::Ok(message)); return Ok(EventOutcome::Ok(message));
} }
@@ -377,74 +283,27 @@ impl EventHandler {
} }
AppMode::ReadOnly => { AppMode::ReadOnly => {
if config.get_read_only_action_for_key(key_code, modifiers) if config.get_read_only_action_for_key(key_code, modifiers) == Some("enter_highlight_mode_linewise") && ModeManager::can_enter_highlight_mode(current_mode) {
== Some("enter_highlight_mode_linewise") let current_field_index = if app_state.ui.show_login { login_state.current_field() } else if app_state.ui.show_register { register_state.current_field() } else { form_state.current_field() };
&& ModeManager::can_enter_highlight_mode(current_mode) self.highlight_state = HighlightState::Linewise { anchor_line: current_field_index };
{
let current_field_index = if app_state.ui.show_login {
login_state.current_field()
} else if app_state.ui.show_register {
register_state.current_field()
} else {
form_state.current_field()
};
self.highlight_state = HighlightState::Linewise {
anchor_line: current_field_index,
};
self.command_message = "-- LINE HIGHLIGHT --".to_string(); self.command_message = "-- LINE HIGHLIGHT --".to_string();
return Ok(EventOutcome::Ok(self.command_message.clone())); return Ok(EventOutcome::Ok(self.command_message.clone()));
} else if config.get_read_only_action_for_key(key_code, modifiers) } else if config.get_read_only_action_for_key(key_code, modifiers) == Some("enter_highlight_mode") && ModeManager::can_enter_highlight_mode(current_mode) {
== Some("enter_highlight_mode") let current_field_index = if app_state.ui.show_login { login_state.current_field() } else if app_state.ui.show_register { register_state.current_field() } else { form_state.current_field() };
&& ModeManager::can_enter_highlight_mode(current_mode) let current_cursor_pos = if app_state.ui.show_login { login_state.current_cursor_pos() } else if app_state.ui.show_register { register_state.current_cursor_pos() } else { form_state.current_cursor_pos() };
{
let current_field_index = if app_state.ui.show_login {
login_state.current_field()
} else if app_state.ui.show_register {
register_state.current_field()
} else {
form_state.current_field()
};
let current_cursor_pos = if app_state.ui.show_login {
login_state.current_cursor_pos()
} else if app_state.ui.show_register {
register_state.current_cursor_pos()
} else {
form_state.current_cursor_pos()
};
let anchor = (current_field_index, current_cursor_pos); let anchor = (current_field_index, current_cursor_pos);
self.highlight_state = HighlightState::Characterwise { anchor }; self.highlight_state = HighlightState::Characterwise { anchor };
self.command_message = "-- HIGHLIGHT --".to_string(); self.command_message = "-- HIGHLIGHT --".to_string();
return Ok(EventOutcome::Ok(self.command_message.clone())); return Ok(EventOutcome::Ok(self.command_message.clone()));
} else if config } else if config.get_read_only_action_for_key(key_code, modifiers).as_deref() == Some("enter_edit_mode_before") && ModeManager::can_enter_edit_mode(current_mode) {
.get_read_only_action_for_key(key_code, modifiers)
.as_deref()
== Some("enter_edit_mode_before")
&& ModeManager::can_enter_edit_mode(current_mode)
{
self.is_edit_mode = true; self.is_edit_mode = true;
self.edit_mode_cooldown = true; self.edit_mode_cooldown = true;
self.command_message = "Edit mode".to_string(); self.command_message = "Edit mode".to_string();
terminal.set_cursor_style(SetCursorStyle::BlinkingBar)?; terminal.set_cursor_style(SetCursorStyle::BlinkingBar)?;
return Ok(EventOutcome::Ok(self.command_message.clone())); return Ok(EventOutcome::Ok(self.command_message.clone()));
} else if config } else if config.get_read_only_action_for_key(key_code, modifiers).as_deref() == Some("enter_edit_mode_after") && ModeManager::can_enter_edit_mode(current_mode) {
.get_read_only_action_for_key(key_code, modifiers) let current_input = if app_state.ui.show_login || app_state.ui.show_register { login_state.get_current_input() } else { form_state.get_current_input() };
.as_deref() let current_cursor_pos = if app_state.ui.show_login || app_state.ui.show_register { login_state.current_cursor_pos() } else { form_state.current_cursor_pos() };
== Some("enter_edit_mode_after")
&& ModeManager::can_enter_edit_mode(current_mode)
{
let current_input = if app_state.ui.show_login || app_state.ui.show_register
{
login_state.get_current_input()
} else {
form_state.get_current_input()
};
let current_cursor_pos =
if app_state.ui.show_login || app_state.ui.show_register {
login_state.current_cursor_pos()
} else {
form_state.current_cursor_pos()
};
if !current_input.is_empty() && current_cursor_pos < current_input.len() { if !current_input.is_empty() && current_cursor_pos < current_input.len() {
if app_state.ui.show_login || app_state.ui.show_register { if app_state.ui.show_login || app_state.ui.show_register {
login_state.set_current_cursor_pos(current_cursor_pos + 1); login_state.set_current_cursor_pos(current_cursor_pos + 1);
@@ -460,10 +319,7 @@ impl EventHandler {
self.command_message = "Edit mode (after cursor)".to_string(); self.command_message = "Edit mode (after cursor)".to_string();
terminal.set_cursor_style(SetCursorStyle::BlinkingBar)?; terminal.set_cursor_style(SetCursorStyle::BlinkingBar)?;
return Ok(EventOutcome::Ok(self.command_message.clone())); return Ok(EventOutcome::Ok(self.command_message.clone()));
} else if config.get_read_only_action_for_key(key_code, modifiers) } else if config.get_read_only_action_for_key(key_code, modifiers) == Some("enter_command_mode") && ModeManager::can_enter_command_mode(current_mode) {
== Some("enter_command_mode")
&& ModeManager::can_enter_command_mode(current_mode)
{
self.command_mode = true; self.command_mode = true;
self.command_input.clear(); self.command_input.clear();
self.command_message.clear(); self.command_message.clear();
@@ -474,90 +330,50 @@ impl EventHandler {
match action { match action {
"save" | "force_quit" | "save_and_quit" | "revert" => { "save" | "force_quit" | "save_and_quit" | "revert" => {
return common_mode::handle_core_action( return common_mode::handle_core_action(
action, action, form_state, auth_state, login_state, register_state,
form_state, grpc_client, &mut self.auth_client, terminal, app_state,
auth_state, ).await;
login_state,
register_state,
grpc_client,
&mut self.auth_client,
terminal,
app_state,
)
.await;
} }
_ => {} _ => {}
} }
} }
// Extracting values to avoid borrow conflicts
let mut current_position = form_state.current_position; let mut current_position = form_state.current_position;
let total_count = form_state.total_count; let total_count = form_state.total_count;
let (_should_exit, message) = read_only::handle_read_only_event( let (_should_exit, message) = read_only::handle_read_only_event(
app_state, app_state, key_event, config, form_state, login_state, register_state,
key_event, &mut admin_state.add_table_state, &mut admin_state.add_logic_state,
config, &mut self.key_sequence_tracker, &mut current_position, total_count,
form_state, grpc_client, &mut self.command_message, &mut self.edit_mode_cooldown,
login_state,
register_state,
&mut admin_state.add_table_state,
&mut admin_state.add_logic_state,
&mut self.key_sequence_tracker,
&mut current_position,
total_count,
grpc_client,
&mut self.command_message,
&mut self.edit_mode_cooldown,
&mut self.ideal_cursor_column, &mut self.ideal_cursor_column,
) ).await?;
.await?;
return Ok(EventOutcome::Ok(message)); return Ok(EventOutcome::Ok(message));
} }
AppMode::Highlight => { AppMode::Highlight => {
if config.get_highlight_action_for_key(key_code, modifiers) if config.get_highlight_action_for_key(key_code, modifiers) == Some("exit_highlight_mode") {
== Some("exit_highlight_mode")
{
self.highlight_state = HighlightState::Off; self.highlight_state = HighlightState::Off;
self.command_message = "Exited highlight mode".to_string(); self.command_message = "Exited highlight mode".to_string();
terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?;
return Ok(EventOutcome::Ok(self.command_message.clone())); return Ok(EventOutcome::Ok(self.command_message.clone()));
} else if config.get_highlight_action_for_key(key_code, modifiers) } else if config.get_highlight_action_for_key(key_code, modifiers) == Some("enter_highlight_mode_linewise") {
== Some("enter_highlight_mode_linewise")
{
if let HighlightState::Characterwise { anchor } = self.highlight_state { if let HighlightState::Characterwise { anchor } = self.highlight_state {
self.highlight_state = HighlightState::Linewise { self.highlight_state = HighlightState::Linewise { anchor_line: anchor.0 };
anchor_line: anchor.0,
};
self.command_message = "-- LINE HIGHLIGHT --".to_string(); self.command_message = "-- LINE HIGHLIGHT --".to_string();
return Ok(EventOutcome::Ok(self.command_message.clone())); return Ok(EventOutcome::Ok(self.command_message.clone()));
} }
return Ok(EventOutcome::Ok("".to_string())); return Ok(EventOutcome::Ok("".to_string()));
} }
// Extracting values to avoid borrow conflicts
let mut current_position = form_state.current_position; let mut current_position = form_state.current_position;
let total_count = form_state.total_count; let total_count = form_state.total_count;
let (_should_exit, message) = read_only::handle_read_only_event( let (_should_exit, message) = read_only::handle_read_only_event(
app_state, app_state, key_event, config, form_state, login_state, register_state,
key_event, &mut admin_state.add_table_state, &mut admin_state.add_logic_state,
config, &mut self.key_sequence_tracker, &mut current_position, total_count,
form_state, grpc_client, &mut self.command_message, &mut self.edit_mode_cooldown,
login_state,
register_state,
&mut admin_state.add_table_state,
&mut admin_state.add_logic_state,
&mut self.key_sequence_tracker,
&mut current_position,
total_count,
grpc_client,
&mut self.command_message,
&mut self.edit_mode_cooldown,
&mut self.ideal_cursor_column, &mut self.ideal_cursor_column,
) ).await?;
.await?;
return Ok(EventOutcome::Ok(message)); return Ok(EventOutcome::Ok(message));
} }
@@ -566,99 +382,45 @@ impl EventHandler {
match action { match action {
"save" | "force_quit" | "save_and_quit" | "revert" => { "save" | "force_quit" | "save_and_quit" | "revert" => {
return common_mode::handle_core_action( return common_mode::handle_core_action(
action, action, form_state, auth_state, login_state, register_state,
form_state, grpc_client, &mut self.auth_client, terminal, app_state,
auth_state, ).await;
login_state,
register_state,
grpc_client,
&mut self.auth_client,
terminal,
app_state,
)
.await;
} }
_ => {} _ => {}
} }
} }
// Extracting values to avoid borrow conflicts
let mut current_position = form_state.current_position; let mut current_position = form_state.current_position;
let total_count = form_state.total_count; let total_count = form_state.total_count;
let edit_result = edit::handle_edit_event( let edit_result = edit::handle_edit_event(
key_event, key_event, config, form_state, login_state, register_state, admin_state,
config, &mut self.ideal_cursor_column, &mut current_position, total_count,
form_state, grpc_client, app_state,
login_state, ).await;
register_state,
admin_state,
&mut self.ideal_cursor_column,
&mut current_position,
total_count,
grpc_client,
app_state,
)
.await;
match edit_result { match edit_result {
Ok(edit::EditEventOutcome::ExitEditMode) => { Ok(edit::EditEventOutcome::ExitEditMode) => {
self.is_edit_mode = false; self.is_edit_mode = false;
self.edit_mode_cooldown = true; self.edit_mode_cooldown = true;
let has_changes = if app_state.ui.show_login { let has_changes = if app_state.ui.show_login { login_state.has_unsaved_changes() } else if app_state.ui.show_register { register_state.has_unsaved_changes() } else { form_state.has_unsaved_changes() };
login_state.has_unsaved_changes() self.command_message = if has_changes { "Exited edit mode (unsaved changes remain)".to_string() } else { "Read-only mode".to_string() };
} else if app_state.ui.show_register {
register_state.has_unsaved_changes()
} else {
form_state.has_unsaved_changes()
};
self.command_message = if has_changes {
"Exited edit mode (unsaved changes remain)".to_string()
} else {
"Read-only mode".to_string()
};
terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?;
let current_input = if app_state.ui.show_login { let current_input = if app_state.ui.show_login { login_state.get_current_input() } else if app_state.ui.show_register { register_state.get_current_input() } else { form_state.get_current_input() };
login_state.get_current_input() let current_cursor_pos = if app_state.ui.show_login { login_state.current_cursor_pos() } else if app_state.ui.show_register { register_state.current_cursor_pos() } else { form_state.current_cursor_pos() };
} else if app_state.ui.show_register { if !current_input.is_empty() && current_cursor_pos >= current_input.len() {
register_state.get_current_input()
} else {
form_state.get_current_input()
};
let current_cursor_pos = if app_state.ui.show_login {
login_state.current_cursor_pos()
} else if app_state.ui.show_register {
register_state.current_cursor_pos()
} else {
form_state.current_cursor_pos()
};
if !current_input.is_empty()
&& current_cursor_pos >= current_input.len()
{
let new_pos = current_input.len() - 1; let new_pos = current_input.len() - 1;
let target_state: &mut dyn CanvasState = if app_state.ui.show_login let target_state: &mut dyn CanvasState = if app_state.ui.show_login { login_state } else if app_state.ui.show_register { register_state } else { form_state };
{
login_state
} else if app_state.ui.show_register {
register_state
} else {
form_state
};
target_state.set_current_cursor_pos(new_pos); target_state.set_current_cursor_pos(new_pos);
self.ideal_cursor_column = new_pos; self.ideal_cursor_column = new_pos;
} }
return Ok(EventOutcome::Ok(self.command_message.clone())); return Ok(EventOutcome::Ok(self.command_message.clone()));
} }
Ok(edit::EditEventOutcome::Message(msg)) => { Ok(edit::EditEventOutcome::Message(msg)) => {
if !msg.is_empty() { if !msg.is_empty() { self.command_message = msg; }
self.command_message = msg;
}
self.key_sequence_tracker.reset(); self.key_sequence_tracker.reset();
return Ok(EventOutcome::Ok(self.command_message.clone())); return Ok(EventOutcome::Ok(self.command_message.clone()));
} }
Err(e) => { Err(e) => { return Err(e.into()); }
return Err(e.into());
}
} }
} }
@@ -672,31 +434,14 @@ impl EventHandler {
} }
if config.is_command_execute(key_code, modifiers) { if config.is_command_execute(key_code, modifiers) {
// Extracting values to avoid borrow conflicts
let mut current_position = form_state.current_position; let mut current_position = form_state.current_position;
let total_count = form_state.total_count; let total_count = form_state.total_count;
let outcome = command_mode::handle_command_event( let outcome = command_mode::handle_command_event(
key_event, key_event, config, app_state, login_state, register_state, form_state,
config, &mut self.command_input, &mut self.command_message, grpc_client,
app_state, command_handler, terminal, &mut current_position, total_count,
login_state, ).await?;
register_state,
form_state,
&mut self.command_input,
&mut self.command_message,
grpc_client,
command_handler,
terminal,
&mut current_position,
total_count,
)
.await?;
// Update form_state with potentially changed position
form_state.current_position = current_position; form_state.current_position = current_position;
self.command_mode = false; self.command_mode = false;
self.key_sequence_tracker.reset(); self.key_sequence_tracker.reset();
let new_mode = ModeManager::derive_mode(app_state, self, admin_state); let new_mode = ModeManager::derive_mode(app_state, self, admin_state);
@@ -712,40 +457,39 @@ impl EventHandler {
if let KeyCode::Char(c) = key_code { if let KeyCode::Char(c) = key_code {
if c == 'f' { if c == 'f' {
// Assuming 'f' is part of the sequence, e.g. ":f" or " f"
self.key_sequence_tracker.add_key(key_code); self.key_sequence_tracker.add_key(key_code);
let sequence = self.key_sequence_tracker.get_sequence(); let sequence = self.key_sequence_tracker.get_sequence();
if config.matches_key_sequence_generalized(&sequence) if config.matches_key_sequence_generalized(&sequence) == Some("find_file_palette_toggle") {
== Some("find_file_palette_toggle")
{
if app_state.ui.show_form || app_state.ui.show_intro { if app_state.ui.show_form || app_state.ui.show_intro {
// Build table graph from profile data // --- START FIX ---
let graph = TableDependencyGraph::from_profile_tree( let mut all_table_paths: Vec<String> = app_state
&app_state.profile_tree, .profile_tree
); .profiles
.iter()
.flat_map(|profile| {
profile.tables.iter().map(move |table| {
format!("{}/{}", profile.name, table.name)
})
})
.collect();
all_table_paths.sort();
// Activate navigation with graph self.navigation_state.activate_find_file(all_table_paths);
self.navigation_state.activate_table_tree(graph); // --- END FIX ---
self.command_mode = false; // Exit command mode self.command_mode = false;
self.command_input.clear(); self.command_input.clear();
// Message is set by render_find_file_palette's prompt_prefix self.command_message.clear();
self.command_message.clear(); // Clear old command message
self.key_sequence_tracker.reset(); self.key_sequence_tracker.reset();
// ModeManager will derive AppMode::General due to navigation_state.active return Ok(EventOutcome::Ok("Table selection palette activated".to_string()));
// app_state.update_mode(AppMode::General); // This will be handled by ModeManager
return Ok(EventOutcome::Ok(
"Table tree palette activated".to_string(),
));
} else { } else {
self.key_sequence_tracker.reset(); self.key_sequence_tracker.reset();
self.command_input.push('f'); self.command_input.push('f');
if sequence.len() > 1 && sequence[0] == KeyCode::Char('f') { if sequence.len() > 1 && sequence[0] == KeyCode::Char('f') {
self.command_input.push('f'); self.command_input.push('f');
} }
self.command_message = self.command_message = "Find File not available in this view.".to_string();
"Find File not available in this view.".to_string();
return Ok(EventOutcome::Ok(self.command_message.clone())); return Ok(EventOutcome::Ok(self.command_message.clone()));
} }
} }