it works amazingly well now, we can select the table name via command line
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
// src/components/common/find_file_palette.rs
|
// src/components/common/find_file_palette.rs
|
||||||
|
|
||||||
use crate::config::colors::themes::Theme;
|
use crate::config::colors::themes::Theme;
|
||||||
|
use crate::modes::general::command_navigation::NavigationState; // Corrected path
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Constraint, Direction, Layout, Rect},
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
style::Style,
|
style::Style,
|
||||||
@@ -9,21 +10,19 @@ use ratatui::{
|
|||||||
};
|
};
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
// Define a fixed height for the options list in the command palette
|
|
||||||
const PALETTE_MAX_VISIBLE_OPTIONS: usize = 15;
|
const PALETTE_MAX_VISIBLE_OPTIONS: usize = 15;
|
||||||
// Use a regular space character for padding.
|
|
||||||
const PADDING_CHAR: &str = " ";
|
const PADDING_CHAR: &str = " ";
|
||||||
|
|
||||||
pub fn render_find_file_palette(
|
pub fn render_find_file_palette(
|
||||||
f: &mut Frame,
|
f: &mut Frame,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
palette_input: &str, // Specific input for the palette
|
navigation_state: &NavigationState,
|
||||||
options: &[String], // These are already filtered options
|
|
||||||
selected_index: Option<usize>, // Index within the filtered `options`
|
|
||||||
) {
|
) {
|
||||||
let num_total_filtered = options.len();
|
let palette_display_input = navigation_state.get_display_input(); // Use the new method
|
||||||
let current_selected_list_idx = selected_index;
|
|
||||||
|
let num_total_filtered = navigation_state.filtered_options.len();
|
||||||
|
let current_selected_list_idx = navigation_state.selected_index;
|
||||||
|
|
||||||
let mut display_start_offset = 0;
|
let mut display_start_offset = 0;
|
||||||
if num_total_filtered > PALETTE_MAX_VISIBLE_OPTIONS {
|
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);
|
display_start_offset = display_start_offset.max(0);
|
||||||
|
|
||||||
let display_end_offset =
|
let display_end_offset = (display_start_offset + PALETTE_MAX_VISIBLE_OPTIONS)
|
||||||
(display_start_offset + PALETTE_MAX_VISIBLE_OPTIONS).min(num_total_filtered);
|
.min(num_total_filtered);
|
||||||
|
|
||||||
let visible_options_slice = if num_total_filtered > 0 {
|
// navigation_state.filtered_options is Vec<(usize, String)>
|
||||||
&options[display_start_offset..display_end_offset]
|
// 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 {
|
} else {
|
||||||
&[]
|
Vec::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([
|
.constraints([
|
||||||
Constraint::Length(1), // For palette input line
|
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);
|
.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 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 prompt_prefix = match navigation_state.navigation_type {
|
||||||
let base_prompt_text = format!("Find File: {}", palette_input);
|
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 prompt_text_width = UnicodeWidthStr::width(base_prompt_text.as_str());
|
||||||
let input_area_width = input_area.width as usize;
|
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 {
|
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 {
|
} else {
|
||||||
base_prompt_text
|
base_prompt_text
|
||||||
};
|
};
|
||||||
@@ -75,12 +93,17 @@ pub fn render_find_file_palette(
|
|||||||
.style(Style::default().fg(theme.accent).bg(theme.bg));
|
.style(Style::default().fg(theme.accent).bg(theme.bg));
|
||||||
f.render_widget(input_paragraph, input_area);
|
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<ListItem> =
|
||||||
let mut display_list_items: Vec<ListItem> = Vec::with_capacity(PALETTE_MAX_VISIBLE_OPTIONS);
|
Vec::with_capacity(PALETTE_MAX_VISIBLE_OPTIONS);
|
||||||
|
|
||||||
for (idx_in_slice, opt_str) in visible_options_slice.iter().enumerate() {
|
for (idx_in_visible_slice, opt_str) in
|
||||||
let original_list_idx = display_start_offset + idx_in_slice;
|
visible_options_slice.iter().enumerate()
|
||||||
let is_selected = current_selected_list_idx == Some(original_list_idx);
|
{
|
||||||
|
// 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 {
|
let style = if is_selected {
|
||||||
Style::default().fg(theme.bg).bg(theme.accent)
|
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 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 padding_amount = list_item_width.saturating_sub(opt_width);
|
||||||
let padded_opt_str = format!(
|
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));
|
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();
|
let num_rendered_options = display_list_items.len();
|
||||||
if num_rendered_options < PALETTE_MAX_VISIBLE_OPTIONS {
|
if num_rendered_options < PALETTE_MAX_VISIBLE_OPTIONS && (final_list_area.height as usize) > num_rendered_options {
|
||||||
for _ in num_rendered_options..PALETTE_MAX_VISIBLE_OPTIONS {
|
for _ in num_rendered_options..(final_list_area.height as usize) {
|
||||||
let empty_padded_str = PADDING_CHAR.repeat(list_area.width as usize);
|
let empty_padded_str =
|
||||||
|
PADDING_CHAR.repeat(final_list_area.width as usize);
|
||||||
display_list_items.push(
|
display_list_items.push(
|
||||||
ListItem::new(empty_padded_str)
|
ListItem::new(empty_padded_str)
|
||||||
.style(Style::default().fg(theme.bg).bg(theme.bg)),
|
.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)
|
let options_list_widget = List::new(display_list_items)
|
||||||
.block(Block::default().style(Style::default().bg(theme.bg)));
|
.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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,108 @@
|
|||||||
// src/modes/general/command_navigation.rs
|
// src/modes/general/command_navigation.rs
|
||||||
use crossterm::event::{KeyEvent, KeyCode};
|
|
||||||
use crate::config::binds::config::Config;
|
use crate::config::binds::config::Config;
|
||||||
use crate::modes::handlers::event::EventOutcome;
|
use crate::modes::handlers::event::EventOutcome;
|
||||||
use anyhow::Result;
|
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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum NavigationType {
|
pub enum NavigationType {
|
||||||
FindFile,
|
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<String>,
|
||||||
|
dependents_map: HashMap<String, Vec<String>>, // Key: table_name, Value: list of tables that depend on key
|
||||||
|
root_tables: Vec<String>, // Tables that don't depend on others
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableDependencyGraph {
|
||||||
|
pub fn from_profile_tree(profile_tree: &ProfileTreeResponse) -> Self {
|
||||||
|
let mut dependents_map: HashMap<String, Vec<String>> = HashMap::new();
|
||||||
|
let mut all_tables_set: HashSet<String> = HashSet::new();
|
||||||
|
let mut table_dependencies: HashMap<String, Vec<String>> =
|
||||||
|
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<String> = all_tables_set
|
||||||
|
.iter()
|
||||||
|
.filter(|name| {
|
||||||
|
table_dependencies
|
||||||
|
.get(*name)
|
||||||
|
.map_or(true, |deps| deps.is_empty())
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.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<String> {
|
||||||
|
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 struct NavigationState {
|
||||||
pub active: bool,
|
pub active: bool,
|
||||||
pub input: String,
|
pub input: String, // Current text in the input field (e.g., "my_tab" or "child_of_root")
|
||||||
pub options: Vec<String>,
|
|
||||||
pub selected_index: Option<usize>,
|
pub selected_index: Option<usize>,
|
||||||
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 navigation_type: NavigationType,
|
||||||
|
pub current_path: String, // Path built so far, e.g., "root_table/parent_table"
|
||||||
|
pub graph: Option<TableDependencyGraph>, // Changed from tree to graph
|
||||||
|
pub all_options: Vec<String>, // Options currently available at this level of the path/input
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NavigationState {
|
impl NavigationState {
|
||||||
@@ -24,37 +110,106 @@ impl NavigationState {
|
|||||||
Self {
|
Self {
|
||||||
active: false,
|
active: false,
|
||||||
input: String::new(),
|
input: String::new(),
|
||||||
options: Vec::new(),
|
|
||||||
selected_index: None,
|
selected_index: None,
|
||||||
filtered_options: Vec::new(),
|
filtered_options: Vec::new(),
|
||||||
navigation_type: NavigationType::FindFile,
|
navigation_type: NavigationType::FindFile,
|
||||||
|
current_path: String::new(),
|
||||||
|
graph: None,
|
||||||
|
all_options: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn activate_find_file(&mut self, options: Vec<String>) {
|
pub fn activate_find_file(&mut self, options: Vec<String>) {
|
||||||
self.active = true;
|
self.active = true;
|
||||||
self.navigation_type = NavigationType::FindFile;
|
self.navigation_type = NavigationType::FindFile;
|
||||||
self.options = options;
|
self.all_options = options;
|
||||||
self.input.clear();
|
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) {
|
pub fn deactivate(&mut self) {
|
||||||
self.active = false;
|
self.active = false;
|
||||||
self.input.clear();
|
self.input.clear();
|
||||||
self.options.clear();
|
self.all_options.clear();
|
||||||
self.filtered_options.clear();
|
self.filtered_options.clear();
|
||||||
self.selected_index = None;
|
self.selected_index = None;
|
||||||
|
self.current_path.clear();
|
||||||
|
self.graph = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_char(&mut self, c: char) {
|
pub fn add_char(&mut self, c: char) {
|
||||||
self.input.push(c);
|
match self.navigation_type {
|
||||||
self.update_filtered_options();
|
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) {
|
pub fn remove_char(&mut self) {
|
||||||
self.input.pop();
|
match self.navigation_type {
|
||||||
self.update_filtered_options();
|
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) {
|
pub fn move_up(&mut self) {
|
||||||
@@ -62,19 +217,11 @@ impl NavigationState {
|
|||||||
self.selected_index = None;
|
self.selected_index = None;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
self.selected_index = match self.selected_index {
|
||||||
match self.selected_index {
|
Some(0) => Some(self.filtered_options.len() - 1),
|
||||||
Some(current) => {
|
Some(current) => Some(current - 1),
|
||||||
if current == 0 {
|
None => Some(self.filtered_options.len() - 1),
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_down(&mut self) {
|
pub fn move_down(&mut self) {
|
||||||
@@ -82,54 +229,101 @@ impl NavigationState {
|
|||||||
self.selected_index = None;
|
self.selected_index = None;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
self.selected_index = match self.selected_index {
|
||||||
match self.selected_index {
|
Some(current) if current >= self.filtered_options.len() - 1 => {
|
||||||
Some(current) => {
|
Some(0)
|
||||||
if current >= self.filtered_options.len() - 1 {
|
|
||||||
self.selected_index = Some(0);
|
|
||||||
} else {
|
|
||||||
self.selected_index = Some(current + 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => {
|
Some(current) => Some(current + 1),
|
||||||
self.selected_index = Some(0);
|
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> {
|
// Gets the full path of the currently selected item for TableTree, or input for FindFile
|
||||||
self.selected_index
|
pub fn get_selected_value(&self) -> Option<String> {
|
||||||
.and_then(|idx| self.filtered_options.get(idx))
|
match self.navigation_type {
|
||||||
.map(|(_, option)| option.as_str())
|
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) {
|
fn update_filtered_options(&mut self) {
|
||||||
if self.input.is_empty() {
|
let filter_text = match self.navigation_type {
|
||||||
self.filtered_options = self.options
|
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()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, opt)| (i, opt.clone()))
|
.map(|(i, opt)| (i, opt.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
} else {
|
} else {
|
||||||
let input_lower = self.input.to_lowercase();
|
self.filtered_options = self
|
||||||
self.filtered_options = self.options
|
.all_options
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(_, opt)| opt.to_lowercase().contains(&input_lower))
|
.filter(|(_, opt)| opt.to_lowercase().contains(&filter_text))
|
||||||
.map(|(i, opt)| (i, opt.clone()))
|
.map(|(i, opt)| (i, opt.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset selection to first item if current selection is invalid
|
|
||||||
if self.filtered_options.is_empty() {
|
if self.filtered_options.is_empty() {
|
||||||
self.selected_index = None;
|
self.selected_index = None;
|
||||||
} else if self.selected_index.map_or(true, |idx| idx >= self.filtered_options.len()) {
|
} else {
|
||||||
self.selected_index = Some(0);
|
self.selected_index = Some(0); // Default to selecting the first item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle navigation events within General mode
|
|
||||||
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,
|
||||||
@@ -142,19 +336,33 @@ pub async fn handle_command_navigation_event(
|
|||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Esc => {
|
KeyCode::Esc => {
|
||||||
navigation_state.deactivate();
|
navigation_state.deactivate();
|
||||||
Ok(EventOutcome::Ok("Find File cancelled".to_string()))
|
Ok(EventOutcome::Ok("Navigation cancelled".to_string()))
|
||||||
}
|
}
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
if let Some(selected) = navigation_state.get_selected_option() {
|
if let Some(selected_value) = navigation_state.get_selected_value() {
|
||||||
let selected = selected.to_string();
|
let message = match navigation_state.navigation_type {
|
||||||
navigation_state.deactivate();
|
|
||||||
match navigation_state.navigation_type {
|
|
||||||
NavigationType::FindFile => {
|
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 valid selection to confirm".to_string()))
|
||||||
Ok(EventOutcome::Ok("No file selected".to_string()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Up => {
|
KeyCode::Up => {
|
||||||
@@ -174,8 +382,9 @@ pub async fn handle_command_navigation_event(
|
|||||||
Ok(EventOutcome::Ok(String::new()))
|
Ok(EventOutcome::Ok(String::new()))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Check for general keybindings that might apply to navigation
|
if let Some(action) =
|
||||||
if let Some(action) = config.get_general_action(key.code, key.modifiers) {
|
config.get_general_action(key.code, key.modifiers)
|
||||||
|
{
|
||||||
match action {
|
match action {
|
||||||
"move_up" => {
|
"move_up" => {
|
||||||
navigation_state.move_up();
|
navigation_state.move_up();
|
||||||
@@ -185,13 +394,16 @@ pub async fn handle_command_navigation_event(
|
|||||||
navigation_state.move_down();
|
navigation_state.move_down();
|
||||||
Ok(EventOutcome::Ok(String::new()))
|
Ok(EventOutcome::Ok(String::new()))
|
||||||
}
|
}
|
||||||
"select" => {
|
"select" => { // This is equivalent to Enter
|
||||||
if let Some(selected) = navigation_state.get_selected_option() {
|
if let Some(selected_value) = navigation_state.get_selected_value() {
|
||||||
let selected = selected.to_string();
|
let message = match navigation_state.navigation_type {
|
||||||
|
NavigationType::FindFile => format!("Selected file: {}", selected_value),
|
||||||
|
NavigationType::TableTree => format!("Selected table: {}", selected_value),
|
||||||
|
};
|
||||||
navigation_state.deactivate();
|
navigation_state.deactivate();
|
||||||
Ok(EventOutcome::Ok(format!("Selected file: {}", selected)))
|
Ok(EventOutcome::Ok(message))
|
||||||
} else {
|
} else {
|
||||||
Ok(EventOutcome::Ok("No file selected".to_string()))
|
Ok(EventOutcome::Ok("No selection".to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Ok(EventOutcome::Ok(String::new())),
|
_ => Ok(EventOutcome::Ok(String::new())),
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ use crate::modes::{
|
|||||||
canvas::{edit, read_only, common_mode},
|
canvas::{edit, read_only, common_mode},
|
||||||
general::{navigation, dialog},
|
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::functions::modes::navigation::{admin_nav, add_table_nav};
|
||||||
use crate::config::binds::key_sequences::KeySequenceTracker;
|
use crate::config::binds::key_sequences::KeySequenceTracker;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
@@ -580,30 +582,26 @@ 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) == Some("find_file_palette_toggle") {
|
if config.matches_key_sequence_generalized(&sequence) == 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 {
|
||||||
let options = vec![
|
// Build table graph from profile data
|
||||||
"src/main.rs".to_string(),
|
let graph = TableDependencyGraph::from_profile_tree(&app_state.profile_tree);
|
||||||
"src/lib.rs".to_string(),
|
|
||||||
"Cargo.toml".to_string(),
|
// Activate navigation with graph
|
||||||
"README.md".to_string(),
|
self.navigation_state.activate_table_tree(graph);
|
||||||
"config.toml".to_string(),
|
|
||||||
"src/ui/handlers/ui.rs".to_string(),
|
self.command_mode = false; // Exit command mode
|
||||||
"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;
|
|
||||||
self.command_input.clear();
|
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();
|
self.key_sequence_tracker.reset();
|
||||||
app_state.update_mode(AppMode::General);
|
// ModeManager will derive AppMode::General due to navigation_state.active
|
||||||
return Ok(EventOutcome::Ok("Find File 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');
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ use crate::components::{
|
|||||||
};
|
};
|
||||||
use crate::config::colors::themes::Theme;
|
use crate::config::colors::themes::Theme;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Constraint, Direction, Layout, Rect},
|
// layout::{Constraint, Direction, Layout, Rect}, // Rect might be unused if all areas are handled
|
||||||
style::Style,
|
layout::{Constraint, Direction, Layout}, // Style might be unused if all styling is in components
|
||||||
|
// style::Style, // Style might be unused
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
use crate::state::pages::canvas_state::CanvasState;
|
use crate::state::pages::canvas_state::CanvasState;
|
||||||
@@ -47,7 +48,7 @@ pub fn render_ui(
|
|||||||
event_handler_command_input: &str,
|
event_handler_command_input: &str,
|
||||||
event_handler_command_mode_active: bool,
|
event_handler_command_mode_active: bool,
|
||||||
event_handler_command_message: &str,
|
event_handler_command_message: &str,
|
||||||
navigation_state: &NavigationState,
|
navigation_state: &NavigationState, // This is the correct reference
|
||||||
total_count: u64,
|
total_count: u64,
|
||||||
current_position: u64,
|
current_position: u64,
|
||||||
current_dir: &str,
|
current_dir: &str,
|
||||||
@@ -56,28 +57,31 @@ pub fn render_ui(
|
|||||||
) {
|
) {
|
||||||
render_background(f, f.area(), theme);
|
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<Constraint> = vec![Constraint::Length(1)];
|
let mut bottom_area_constraints: Vec<Constraint> = vec![Constraint::Length(1)]; // For status_line
|
||||||
|
|
||||||
let command_palette_area_height = if navigation_state.active {
|
let command_palette_area_height = if navigation_state.active {
|
||||||
1 + PALETTE_OPTIONS_HEIGHT_FOR_LAYOUT // Input line + fixed height for options
|
1 + PALETTE_OPTIONS_HEIGHT_FOR_LAYOUT // Input line + fixed height for options
|
||||||
} else if event_handler_command_mode_active {
|
} else if event_handler_command_mode_active {
|
||||||
1
|
1 // Normal command line
|
||||||
} else {
|
} else {
|
||||||
0
|
0 // Neither is active
|
||||||
};
|
};
|
||||||
|
|
||||||
if command_palette_area_height > 0 {
|
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));
|
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 {
|
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);
|
main_layout_constraints.extend(bottom_area_constraints);
|
||||||
|
|
||||||
|
|
||||||
let root_chunks = Layout::default()
|
let root_chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(main_layout_constraints)
|
.constraints(main_layout_constraints)
|
||||||
@@ -85,8 +89,9 @@ pub fn render_ui(
|
|||||||
|
|
||||||
let mut chunk_idx = 0;
|
let mut chunk_idx = 0;
|
||||||
let buffer_list_area = if app_state.ui.show_buffer_list {
|
let buffer_list_area = if app_state.ui.show_buffer_list {
|
||||||
|
let area = Some(root_chunks[chunk_idx]);
|
||||||
chunk_idx += 1;
|
chunk_idx += 1;
|
||||||
Some(root_chunks[0])
|
area
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -97,12 +102,19 @@ pub fn render_ui(
|
|||||||
let status_line_area = root_chunks[chunk_idx];
|
let status_line_area = root_chunks[chunk_idx];
|
||||||
chunk_idx += 1;
|
chunk_idx += 1;
|
||||||
|
|
||||||
let mut command_render_area = None;
|
let command_render_area = if command_palette_area_height > 0 {
|
||||||
if command_palette_area_height > 0 {
|
// Check if there's a chunk available for command_render_area
|
||||||
if root_chunks.len() > chunk_idx {
|
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 ---
|
// --- Render main content views ---
|
||||||
if app_state.ui.show_intro {
|
if app_state.ui.show_intro {
|
||||||
@@ -110,7 +122,7 @@ pub fn render_ui(
|
|||||||
} else if app_state.ui.show_register {
|
} else if app_state.ui.show_register {
|
||||||
render_register(
|
render_register(
|
||||||
f, main_content_area, theme, register_state, app_state,
|
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,
|
highlight_state,
|
||||||
);
|
);
|
||||||
} else if app_state.ui.show_add_table {
|
} else if app_state.ui.show_add_table {
|
||||||
@@ -127,7 +139,7 @@ pub fn render_ui(
|
|||||||
} else if app_state.ui.show_login {
|
} else if app_state.ui.show_login {
|
||||||
render_login(
|
render_login(
|
||||||
f, main_content_area, theme, login_state, app_state,
|
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,
|
highlight_state,
|
||||||
);
|
);
|
||||||
} else if app_state.ui.show_admin {
|
} else if app_state.ui.show_admin {
|
||||||
@@ -145,14 +157,15 @@ pub fn render_ui(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
let available_width = form_actual_area.width;
|
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 {
|
let form_render_area = if available_width >= 80 {
|
||||||
Layout::default().direction(Direction::Horizontal)
|
Layout::default().direction(Direction::Horizontal)
|
||||||
.constraints([Constraint::Min(0), Constraint::Length(80), Constraint::Min(0)])
|
.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 {
|
} else {
|
||||||
Layout::default().direction(Direction::Horizontal)
|
Layout::default().direction(Direction::Horizontal)
|
||||||
.constraints([Constraint::Min(0), Constraint::Length(available_width.min(80)), Constraint::Min(0)])
|
.constraints([Constraint::Min(0), Constraint::Length(available_width), Constraint::Min(0)])
|
||||||
.split(form_actual_area)[1]
|
.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 fields_vec: Vec<&str> = form_state.fields.iter().map(AsRef::as_ref).collect();
|
||||||
let values_vec: Vec<&String> = form_state.values.iter().collect();
|
let values_vec: Vec<&String> = form_state.values.iter().collect();
|
||||||
@@ -162,32 +175,30 @@ pub fn render_ui(
|
|||||||
total_count, current_position,
|
total_count, current_position,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// --- End main content views ---
|
||||||
|
|
||||||
if let Some(area) = buffer_list_area {
|
if let Some(area) = buffer_list_area {
|
||||||
if app_state.ui.show_buffer_list {
|
// 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_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);
|
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 {
|
if navigation_state.active {
|
||||||
// Call the new component
|
|
||||||
find_file_palette::render_find_file_palette(
|
find_file_palette::render_find_file_palette(
|
||||||
f,
|
f,
|
||||||
area,
|
palette_or_command_area, // Use the correct area
|
||||||
theme,
|
theme,
|
||||||
&navigation_state.input,
|
navigation_state, // Pass the navigation_state directly
|
||||||
&navigation_state.filtered_options.iter().map(|(_, opt)| opt.clone()).collect::<Vec<_>>(),
|
|
||||||
navigation_state.selected_index,
|
|
||||||
);
|
);
|
||||||
} else if event_handler_command_mode_active {
|
} else if event_handler_command_mode_active {
|
||||||
render_command_line(
|
render_command_line(
|
||||||
f,
|
f,
|
||||||
area,
|
palette_or_command_area, // Use the correct area
|
||||||
event_handler_command_input,
|
event_handler_command_input,
|
||||||
true,
|
true, // Assuming it's always active when this branch is hit
|
||||||
theme,
|
theme,
|
||||||
event_handler_command_message,
|
event_handler_command_message,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user