diff --git a/client/src/components/common/find_file_palette.rs b/client/src/components/common/find_file_palette.rs index 451cdf6..077c74a 100644 --- a/client/src/components/common/find_file_palette.rs +++ b/client/src/components/common/find_file_palette.rs @@ -1,6 +1,7 @@ // src/components/common/find_file_palette.rs use crate::config::colors::themes::Theme; +use crate::modes::general::command_navigation::NavigationState; // Corrected path use ratatui::{ layout::{Constraint, Direction, Layout, Rect}, style::Style, @@ -9,21 +10,19 @@ use ratatui::{ }; use unicode_width::UnicodeWidthStr; -// Define a fixed height for the options list in the command palette const PALETTE_MAX_VISIBLE_OPTIONS: usize = 15; -// Use a regular space character for padding. const PADDING_CHAR: &str = " "; pub fn render_find_file_palette( f: &mut Frame, area: Rect, theme: &Theme, - palette_input: &str, // Specific input for the palette - options: &[String], // These are already filtered options - selected_index: Option, // Index within the filtered `options` + navigation_state: &NavigationState, ) { - let num_total_filtered = options.len(); - let current_selected_list_idx = selected_index; + let palette_display_input = navigation_state.get_display_input(); // Use the new method + + let num_total_filtered = navigation_state.filtered_options.len(); + let current_selected_list_idx = navigation_state.selected_index; let mut display_start_offset = 0; if num_total_filtered > PALETTE_MAX_VISIBLE_OPTIONS { @@ -39,34 +38,53 @@ pub fn render_find_file_palette( } display_start_offset = display_start_offset.max(0); - let display_end_offset = - (display_start_offset + PALETTE_MAX_VISIBLE_OPTIONS).min(num_total_filtered); + let display_end_offset = (display_start_offset + PALETTE_MAX_VISIBLE_OPTIONS) + .min(num_total_filtered); - let visible_options_slice = if num_total_filtered > 0 { - &options[display_start_offset..display_end_offset] + // navigation_state.filtered_options is Vec<(usize, String)> + // We only need the String part for display. + let visible_options_slice: Vec<&String> = if num_total_filtered > 0 { + navigation_state.filtered_options + [display_start_offset..display_end_offset] + .iter() + .map(|(_, opt_str)| opt_str) + .collect() } else { - &[] + Vec::new() }; let chunks = Layout::default() .direction(Direction::Vertical) .constraints([ Constraint::Length(1), // For palette input line - Constraint::Length(PALETTE_MAX_VISIBLE_OPTIONS as u16), // For options list (fixed height) + Constraint::Min(0), // For options list, take remaining space ]) .split(area); + + // Ensure list_area height does not exceed PALETTE_MAX_VISIBLE_OPTIONS + let list_area_height = std::cmp::min(chunks[1].height, PALETTE_MAX_VISIBLE_OPTIONS as u16); + let final_list_area = Rect::new(chunks[1].x, chunks[1].y, chunks[1].width, list_area_height); + let input_area = chunks[0]; - let list_area = chunks[1]; + // let list_area = chunks[1]; // Use final_list_area - // Draw the palette input line (with padding) - let base_prompt_text = format!("Find File: {}", palette_input); + let prompt_prefix = match navigation_state.navigation_type { + crate::modes::general::command_navigation::NavigationType::FindFile => "Find File: ", + crate::modes::general::command_navigation::NavigationType::TableTree => "Table Path: ", + }; + let base_prompt_text = format!("{}{}", prompt_prefix, palette_display_input); let prompt_text_width = UnicodeWidthStr::width(base_prompt_text.as_str()); let input_area_width = input_area.width as usize; - let input_padding_needed = input_area_width.saturating_sub(prompt_text_width); + let input_padding_needed = + input_area_width.saturating_sub(prompt_text_width); let padded_prompt_text = if input_padding_needed > 0 { - format!("{}{}", base_prompt_text, PADDING_CHAR.repeat(input_padding_needed)) + format!( + "{}{}", + base_prompt_text, + PADDING_CHAR.repeat(input_padding_needed) + ) } else { base_prompt_text }; @@ -75,12 +93,17 @@ pub fn render_find_file_palette( .style(Style::default().fg(theme.accent).bg(theme.bg)); f.render_widget(input_paragraph, input_area); - // --- Draw the list of options, ensuring all PALETTE_MAX_VISIBLE_OPTIONS rows are covered --- - let mut display_list_items: Vec = Vec::with_capacity(PALETTE_MAX_VISIBLE_OPTIONS); + let mut display_list_items: Vec = + Vec::with_capacity(PALETTE_MAX_VISIBLE_OPTIONS); - for (idx_in_slice, opt_str) in visible_options_slice.iter().enumerate() { - let original_list_idx = display_start_offset + idx_in_slice; - let is_selected = current_selected_list_idx == Some(original_list_idx); + for (idx_in_visible_slice, opt_str) in + visible_options_slice.iter().enumerate() + { + // The selected_index in navigation_state is relative to the full filtered_options list. + // We need to check if the current item (from the visible slice) corresponds to the selected_index. + let original_filtered_idx = display_start_offset + idx_in_visible_slice; + let is_selected = + current_selected_list_idx == Some(original_filtered_idx); let style = if is_selected { Style::default().fg(theme.bg).bg(theme.accent) @@ -89,7 +112,7 @@ pub fn render_find_file_palette( }; let opt_width = opt_str.width() as u16; - let list_item_width = list_area.width; + let list_item_width = final_list_area.width; let padding_amount = list_item_width.saturating_sub(opt_width); let padded_opt_str = format!( "{}{}", @@ -99,10 +122,12 @@ pub fn render_find_file_palette( display_list_items.push(ListItem::new(padded_opt_str).style(style)); } + // Fill remaining lines in the list area to maintain fixed height appearance let num_rendered_options = display_list_items.len(); - if num_rendered_options < PALETTE_MAX_VISIBLE_OPTIONS { - for _ in num_rendered_options..PALETTE_MAX_VISIBLE_OPTIONS { - let empty_padded_str = PADDING_CHAR.repeat(list_area.width as usize); + if num_rendered_options < PALETTE_MAX_VISIBLE_OPTIONS && (final_list_area.height as usize) > num_rendered_options { + for _ in num_rendered_options..(final_list_area.height as usize) { + let empty_padded_str = + PADDING_CHAR.repeat(final_list_area.width as usize); display_list_items.push( ListItem::new(empty_padded_str) .style(Style::default().fg(theme.bg).bg(theme.bg)), @@ -110,7 +135,8 @@ pub fn render_find_file_palette( } } + let options_list_widget = List::new(display_list_items) .block(Block::default().style(Style::default().bg(theme.bg))); - f.render_widget(options_list_widget, list_area); + f.render_widget(options_list_widget, final_list_area); } diff --git a/client/src/modes/general/command_navigation.rs b/client/src/modes/general/command_navigation.rs index c829b74..87f554d 100644 --- a/client/src/modes/general/command_navigation.rs +++ b/client/src/modes/general/command_navigation.rs @@ -1,22 +1,108 @@ // src/modes/general/command_navigation.rs -use crossterm::event::{KeyEvent, KeyCode}; use crate::config::binds::config::Config; use crate::modes::handlers::event::EventOutcome; use anyhow::Result; +use common::proto::multieko2::table_definition::ProfileTreeResponse; +use crossterm::event::{KeyCode, KeyEvent}; +use std::collections::{HashMap, HashSet}; // Added HashSet #[derive(Debug, Clone, PartialEq)] pub enum NavigationType { FindFile, - // Future: CommandPalette, BufferList, etc. + TableTree, // Represents navigating the table dependency graph +} + +// New structure for table dependencies +#[derive(Debug, Clone)] +pub struct TableDependencyGraph { + all_tables: HashSet, + dependents_map: HashMap>, // Key: table_name, Value: list of tables that depend on key + root_tables: Vec, // Tables that don't depend on others +} + +impl TableDependencyGraph { + pub fn from_profile_tree(profile_tree: &ProfileTreeResponse) -> Self { + let mut dependents_map: HashMap> = HashMap::new(); + let mut all_tables_set: HashSet = HashSet::new(); + let mut table_dependencies: HashMap> = + HashMap::new(); + + for profile in &profile_tree.profiles { + for table in &profile.tables { + all_tables_set.insert(table.name.clone()); + table_dependencies + .insert(table.name.clone(), table.depends_on.clone()); + + for dependency_name in &table.depends_on { + dependents_map + .entry(dependency_name.clone()) + .or_default() + .push(table.name.clone()); + } + } + } + + let root_tables: Vec = all_tables_set + .iter() + .filter(|name| { + table_dependencies + .get(*name) + .map_or(true, |deps| deps.is_empty()) + }) + .cloned() + .collect::>() + .into_iter() + .collect(); + + // Sort for consistent order + // dependents_map.values_mut().for_each(|deps| deps.sort()); + // root_tables.sort(); // This was causing an error if root_tables was not mutable. Let's fix it. + let mut sorted_root_tables = root_tables; + sorted_root_tables.sort(); + + for dependents_list in dependents_map.values_mut() { + dependents_list.sort(); + } + + + Self { + all_tables: all_tables_set, + dependents_map, + root_tables: sorted_root_tables, + } + } + + // Gets tables that depend on the last element of the path + pub fn get_dependent_children(&self, path: &str) -> Vec { + if path.is_empty() { + return self.root_tables.clone(); + } + + let path_segments: Vec<&str> = + path.split('/').filter(|s| !s.is_empty()).collect(); + if let Some(last_segment_name) = path_segments.last() { + // Ensure the last segment is a valid table name before querying dependents + if self.all_tables.contains(*last_segment_name) { + return self + .dependents_map + .get(*last_segment_name) + .cloned() + .unwrap_or_default(); + } + } + Vec::new() + } } pub struct NavigationState { pub active: bool, - pub input: String, - pub options: Vec, + pub input: String, // Current text in the input field (e.g., "my_tab" or "child_of_root") pub selected_index: Option, - pub filtered_options: Vec<(usize, String)>, // (original_index, filtered_string) + pub filtered_options: Vec<(usize, String)>, // (original_index_in_all_options, string_option) pub navigation_type: NavigationType, + pub current_path: String, // Path built so far, e.g., "root_table/parent_table" + pub graph: Option, // Changed from tree to graph + pub all_options: Vec, // Options currently available at this level of the path/input } impl NavigationState { @@ -24,37 +110,106 @@ impl NavigationState { Self { active: false, input: String::new(), - options: Vec::new(), selected_index: None, filtered_options: Vec::new(), navigation_type: NavigationType::FindFile, + current_path: String::new(), + graph: None, + all_options: Vec::new(), } } pub fn activate_find_file(&mut self, options: Vec) { self.active = true; self.navigation_type = NavigationType::FindFile; - self.options = options; + self.all_options = options; self.input.clear(); - self.update_filtered_options(); + self.current_path.clear(); + self.graph = None; + self.update_filtered_options(); // Initial filter with empty input + } + + pub fn activate_table_tree(&mut self, graph: TableDependencyGraph) { + self.active = true; + self.navigation_type = NavigationType::TableTree; + self.graph = Some(graph); + self.input.clear(); + self.current_path.clear(); + self.update_options_for_path(); // Initial options are root tables } pub fn deactivate(&mut self) { self.active = false; self.input.clear(); - self.options.clear(); + self.all_options.clear(); self.filtered_options.clear(); self.selected_index = None; + self.current_path.clear(); + self.graph = None; } pub fn add_char(&mut self, c: char) { - self.input.push(c); - self.update_filtered_options(); + match self.navigation_type { + NavigationType::FindFile => { + self.input.push(c); + self.update_filtered_options(); + } + NavigationType::TableTree => { + if c == '/' { + if !self.input.is_empty() { + // Append current input to path + if self.current_path.is_empty() { + self.current_path = self.input.clone(); + } else { + self.current_path.push('/'); + self.current_path.push_str(&self.input); + } + self.input.clear(); + self.update_options_for_path(); + } + // If input is empty and char is '/', do nothing or define behavior + } else { + self.input.push(c); + self.update_filtered_options(); // Filter current level options based on input + } + } + } } pub fn remove_char(&mut self) { - self.input.pop(); - self.update_filtered_options(); + match self.navigation_type { + NavigationType::FindFile => { + self.input.pop(); + self.update_filtered_options(); + } + NavigationType::TableTree => { + if self.input.is_empty() { + // If input is empty, try to go up in path + if !self.current_path.is_empty() { + if let Some(last_slash_idx) = + self.current_path.rfind('/') + { + // 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 { + // Path was a single segment + self.input = self.current_path.clone(); + self.current_path.clear(); + } + self.update_options_for_path(); + // After path change, current input might match some options, so filter + self.update_filtered_options(); + } + } else { + self.input.pop(); + self.update_filtered_options(); + } + } + } } pub fn move_up(&mut self) { @@ -62,19 +217,11 @@ impl NavigationState { self.selected_index = None; return; } - - match self.selected_index { - Some(current) => { - if current == 0 { - self.selected_index = Some(self.filtered_options.len() - 1); - } else { - self.selected_index = Some(current - 1); - } - } - None => { - self.selected_index = Some(self.filtered_options.len() - 1); - } - } + self.selected_index = match self.selected_index { + Some(0) => Some(self.filtered_options.len() - 1), + Some(current) => Some(current - 1), + None => Some(self.filtered_options.len() - 1), + }; } pub fn move_down(&mut self) { @@ -82,54 +229,101 @@ impl NavigationState { self.selected_index = None; return; } - - match self.selected_index { - Some(current) => { - if current >= self.filtered_options.len() - 1 { - self.selected_index = Some(0); - } else { - self.selected_index = Some(current + 1); - } + self.selected_index = match self.selected_index { + Some(current) if current >= self.filtered_options.len() - 1 => { + Some(0) } - None => { - self.selected_index = Some(0); + Some(current) => Some(current + 1), + None => Some(0), + }; + } + + pub fn get_selected_option_str(&self) -> Option<&str> { + self.selected_index + .and_then(|idx| self.filtered_options.get(idx)) + .map(|(_, option_str)| option_str.as_str()) + } + + // Returns the string to display in the input line of the palette + pub fn get_display_input(&self) -> String { + match self.navigation_type { + NavigationType::FindFile => self.input.clone(), + NavigationType::TableTree => { + if self.current_path.is_empty() { + self.input.clone() + } else { + format!("{}/{}", self.current_path, self.input) + } } } } - pub fn get_selected_option(&self) -> Option<&str> { - self.selected_index - .and_then(|idx| self.filtered_options.get(idx)) - .map(|(_, option)| option.as_str()) + // Gets the full path of the currently selected item for TableTree, or input for FindFile + pub fn get_selected_value(&self) -> Option { + match self.navigation_type { + NavigationType::FindFile => { + if self.input.is_empty() { None } else { Some(self.input.clone()) } + } + NavigationType::TableTree => { + self.get_selected_option_str().map(|selected_name| { + if self.current_path.is_empty() { + selected_name.to_string() + } else { + format!("{}/{}", self.current_path, selected_name) + } + }) + } + } } + + // Update self.all_options based on current_path (for TableTree) + fn update_options_for_path(&mut self) { + if let NavigationType::TableTree = self.navigation_type { + if let Some(graph) = &self.graph { + self.all_options = + graph.get_dependent_children(&self.current_path); + } else { + self.all_options.clear(); + } + } + // For FindFile, all_options is set once at activation. + self.update_filtered_options(); + } + + // Update self.filtered_options based on self.all_options and self.input fn update_filtered_options(&mut self) { - if self.input.is_empty() { - self.filtered_options = self.options + let filter_text = match self.navigation_type { + NavigationType::FindFile => &self.input, + NavigationType::TableTree => &self.input, // For TableTree, input is the current segment being typed + } + .to_lowercase(); + + if filter_text.is_empty() { + self.filtered_options = self + .all_options .iter() .enumerate() .map(|(i, opt)| (i, opt.clone())) .collect(); } else { - let input_lower = self.input.to_lowercase(); - self.filtered_options = self.options + self.filtered_options = self + .all_options .iter() .enumerate() - .filter(|(_, opt)| opt.to_lowercase().contains(&input_lower)) + .filter(|(_, opt)| opt.to_lowercase().contains(&filter_text)) .map(|(i, opt)| (i, opt.clone())) .collect(); } - // Reset selection to first item if current selection is invalid if self.filtered_options.is_empty() { self.selected_index = None; - } else if self.selected_index.map_or(true, |idx| idx >= self.filtered_options.len()) { - self.selected_index = Some(0); + } else { + self.selected_index = Some(0); // Default to selecting the first item } } } -/// Handle navigation events within General mode pub async fn handle_command_navigation_event( navigation_state: &mut NavigationState, key: KeyEvent, @@ -142,19 +336,33 @@ pub async fn handle_command_navigation_event( match key.code { KeyCode::Esc => { navigation_state.deactivate(); - Ok(EventOutcome::Ok("Find File cancelled".to_string())) + Ok(EventOutcome::Ok("Navigation cancelled".to_string())) } KeyCode::Enter => { - if let Some(selected) = navigation_state.get_selected_option() { - let selected = selected.to_string(); - navigation_state.deactivate(); - match navigation_state.navigation_type { + if let Some(selected_value) = navigation_state.get_selected_value() { + let message = match navigation_state.navigation_type { NavigationType::FindFile => { - Ok(EventOutcome::Ok(format!("Selected file: {}", selected))) + format!("Selected file: {}", selected_value) + } + NavigationType::TableTree => { + format!("Selected table: {}", selected_value) + } + }; + navigation_state.deactivate(); + Ok(EventOutcome::Ok(message)) + } else { + // If nothing is selected but enter is pressed, maybe try to navigate if input is a valid path part? + // For now, just indicate no selection or clear. + if navigation_state.navigation_type == NavigationType::TableTree && !navigation_state.input.is_empty() { + // Try to commit current input as a path segment + let current_input_clone = navigation_state.input.clone(); + navigation_state.add_char('/'); // This will commit the input if valid + // Check if path actually changed or options updated + if navigation_state.input.is_empty() && !navigation_state.all_options.is_empty() { + return Ok(EventOutcome::Ok(format!("Navigated to: {}/", current_input_clone))); } } - } else { - Ok(EventOutcome::Ok("No file selected".to_string())) + Ok(EventOutcome::Ok("No valid selection to confirm".to_string())) } } KeyCode::Up => { @@ -174,8 +382,9 @@ pub async fn handle_command_navigation_event( Ok(EventOutcome::Ok(String::new())) } _ => { - // Check for general keybindings that might apply to navigation - if let Some(action) = config.get_general_action(key.code, key.modifiers) { + if let Some(action) = + config.get_general_action(key.code, key.modifiers) + { match action { "move_up" => { navigation_state.move_up(); @@ -185,13 +394,16 @@ pub async fn handle_command_navigation_event( navigation_state.move_down(); Ok(EventOutcome::Ok(String::new())) } - "select" => { - if let Some(selected) = navigation_state.get_selected_option() { - let selected = selected.to_string(); + "select" => { // This is equivalent to Enter + if let Some(selected_value) = navigation_state.get_selected_value() { + let message = match navigation_state.navigation_type { + NavigationType::FindFile => format!("Selected file: {}", selected_value), + NavigationType::TableTree => format!("Selected table: {}", selected_value), + }; navigation_state.deactivate(); - Ok(EventOutcome::Ok(format!("Selected file: {}", selected))) + Ok(EventOutcome::Ok(message)) } else { - Ok(EventOutcome::Ok("No file selected".to_string())) + Ok(EventOutcome::Ok("No selection".to_string())) } } _ => Ok(EventOutcome::Ok(String::new())), diff --git a/client/src/modes/handlers/event.rs b/client/src/modes/handlers/event.rs index 8087225..e232ab5 100644 --- a/client/src/modes/handlers/event.rs +++ b/client/src/modes/handlers/event.rs @@ -35,7 +35,9 @@ use crate::modes::{ canvas::{edit, read_only, common_mode}, general::{navigation, dialog}, }; -use crate::modes::general::command_navigation::{NavigationState, handle_command_navigation_event}; +use crate::modes::general::command_navigation::{ + handle_command_navigation_event, NavigationState, TableDependencyGraph, +}; use crate::functions::modes::navigation::{admin_nav, add_table_nav}; use crate::config::binds::key_sequences::KeySequenceTracker; use tokio::sync::mpsc; @@ -580,30 +582,26 @@ impl EventHandler { } 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); let sequence = self.key_sequence_tracker.get_sequence(); if config.matches_key_sequence_generalized(&sequence) == Some("find_file_palette_toggle") { if app_state.ui.show_form || app_state.ui.show_intro { - let options = vec![ - "src/main.rs".to_string(), - "src/lib.rs".to_string(), - "Cargo.toml".to_string(), - "README.md".to_string(), - "config.toml".to_string(), - "src/ui/handlers/ui.rs".to_string(), - "src/modes/handlers/event.rs".to_string(), - "another_file.txt".to_string(), - "yet_another_one.md".to_string(), - ]; - self.activate_find_file(options); - self.command_mode = false; + // Build table graph from profile data + let graph = TableDependencyGraph::from_profile_tree(&app_state.profile_tree); + + // Activate navigation with graph + self.navigation_state.activate_table_tree(graph); + + self.command_mode = false; // Exit command mode self.command_input.clear(); - self.command_message = "Find File:".to_string(); + // Message is set by render_find_file_palette's prompt_prefix + self.command_message.clear(); // Clear old command message self.key_sequence_tracker.reset(); - app_state.update_mode(AppMode::General); - return Ok(EventOutcome::Ok("Find File palette activated".to_string())); + // ModeManager will derive AppMode::General due to navigation_state.active + // app_state.update_mode(AppMode::General); // This will be handled by ModeManager + return Ok(EventOutcome::Ok("Table tree palette activated".to_string())); } else { self.key_sequence_tracker.reset(); self.command_input.push('f'); diff --git a/client/src/ui/handlers/render.rs b/client/src/ui/handlers/render.rs index 4c265bc..c03e332 100644 --- a/client/src/ui/handlers/render.rs +++ b/client/src/ui/handlers/render.rs @@ -15,8 +15,9 @@ use crate::components::{ }; use crate::config::colors::themes::Theme; use ratatui::{ - layout::{Constraint, Direction, Layout, Rect}, - style::Style, + // layout::{Constraint, Direction, Layout, Rect}, // Rect might be unused if all areas are handled + layout::{Constraint, Direction, Layout}, // Style might be unused if all styling is in components + // style::Style, // Style might be unused Frame, }; use crate::state::pages::canvas_state::CanvasState; @@ -47,7 +48,7 @@ pub fn render_ui( event_handler_command_input: &str, event_handler_command_mode_active: bool, event_handler_command_message: &str, - navigation_state: &NavigationState, + navigation_state: &NavigationState, // This is the correct reference total_count: u64, current_position: u64, current_dir: &str, @@ -56,28 +57,31 @@ pub fn render_ui( ) { render_background(f, f.area(), theme); - const PALETTE_OPTIONS_HEIGHT_FOR_LAYOUT: u16 = 15; // Matches component's internal const + const PALETTE_OPTIONS_HEIGHT_FOR_LAYOUT: u16 = 15; - let mut bottom_area_constraints: Vec = vec![Constraint::Length(1)]; + let mut bottom_area_constraints: Vec = vec![Constraint::Length(1)]; // For status_line let command_palette_area_height = if navigation_state.active { 1 + PALETTE_OPTIONS_HEIGHT_FOR_LAYOUT // Input line + fixed height for options } else if event_handler_command_mode_active { - 1 + 1 // Normal command line } else { - 0 + 0 // Neither is active }; if command_palette_area_height > 0 { + // This constraint is for the command_render_area (palette or command line) bottom_area_constraints.push(Constraint::Length(command_palette_area_height)); } - let mut main_layout_constraints = vec![Constraint::Min(1)]; + let mut main_layout_constraints = vec![Constraint::Min(1)]; // Main content area if app_state.ui.show_buffer_list { - main_layout_constraints.insert(0, Constraint::Length(1)); + main_layout_constraints.insert(0, Constraint::Length(1)); // Buffer list at the top } + // bottom_area_constraints already contains status_line and potentially command_palette_area main_layout_constraints.extend(bottom_area_constraints); + let root_chunks = Layout::default() .direction(Direction::Vertical) .constraints(main_layout_constraints) @@ -85,8 +89,9 @@ pub fn render_ui( let mut chunk_idx = 0; let buffer_list_area = if app_state.ui.show_buffer_list { + let area = Some(root_chunks[chunk_idx]); chunk_idx += 1; - Some(root_chunks[0]) + area } else { None }; @@ -97,12 +102,19 @@ pub fn render_ui( let status_line_area = root_chunks[chunk_idx]; chunk_idx += 1; - let mut command_render_area = None; - if command_palette_area_height > 0 { + let command_render_area = if command_palette_area_height > 0 { + // Check if there's a chunk available for command_render_area if root_chunks.len() > chunk_idx { - command_render_area = Some(root_chunks[chunk_idx]); + Some(root_chunks[chunk_idx]) + } else { + // This case should ideally not happen if constraints are set up correctly + // but as a fallback, don't try to render if no area. + None } - } + } else { + None + }; + // --- Render main content views --- if app_state.ui.show_intro { @@ -110,7 +122,7 @@ pub fn render_ui( } else if app_state.ui.show_register { render_register( f, main_content_area, theme, register_state, app_state, - register_state.current_field() < 4, + register_state.current_field() < 4, // Assuming 4 fields before buttons highlight_state, ); } else if app_state.ui.show_add_table { @@ -127,7 +139,7 @@ pub fn render_ui( } else if app_state.ui.show_login { render_login( f, main_content_area, theme, login_state, app_state, - login_state.current_field() < 2, + login_state.current_field() < 2, // Assuming 2 fields before buttons highlight_state, ); } else if app_state.ui.show_admin { @@ -145,14 +157,15 @@ pub fn render_ui( ); } let available_width = form_actual_area.width; + // Center the form if space allows, otherwise use available width let form_render_area = if available_width >= 80 { Layout::default().direction(Direction::Horizontal) .constraints([Constraint::Min(0), Constraint::Length(80), Constraint::Min(0)]) - .split(main_content_area)[1] + .split(form_actual_area)[1] // Use form_actual_area here } else { Layout::default().direction(Direction::Horizontal) - .constraints([Constraint::Min(0), Constraint::Length(available_width.min(80)), Constraint::Min(0)]) - .split(form_actual_area)[1] + .constraints([Constraint::Min(0), Constraint::Length(available_width), Constraint::Min(0)]) + .split(form_actual_area)[1] // Use form_actual_area here }; let fields_vec: Vec<&str> = form_state.fields.iter().map(AsRef::as_ref).collect(); let values_vec: Vec<&String> = form_state.values.iter().collect(); @@ -162,32 +175,30 @@ pub fn render_ui( total_count, current_position, ); } + // --- End main content views --- if let Some(area) = buffer_list_area { - if app_state.ui.show_buffer_list { - render_buffer_list(f, area, theme, buffer_state, app_state); - } + // No need to check app_state.ui.show_buffer_list again, area is Some only if true + render_buffer_list(f, area, theme, buffer_state, app_state); } render_status_line(f, status_line_area, current_dir, theme, is_event_handler_edit_mode, current_fps); - if let Some(area) = command_render_area { + // Render command line or find_file_palette + if let Some(palette_or_command_area) = command_render_area { // Use the calculated area if navigation_state.active { - // Call the new component find_file_palette::render_find_file_palette( f, - area, + palette_or_command_area, // Use the correct area theme, - &navigation_state.input, - &navigation_state.filtered_options.iter().map(|(_, opt)| opt.clone()).collect::>(), - navigation_state.selected_index, + navigation_state, // Pass the navigation_state directly ); } else if event_handler_command_mode_active { render_command_line( f, - area, + palette_or_command_area, // Use the correct area event_handler_command_input, - true, + true, // Assuming it's always active when this branch is hit theme, event_handler_command_message, );