navigation in the menu but needs refactoring
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
// src/client/modes/general.rs
|
// src/client/modes/general.rs
|
||||||
pub mod navigation;
|
pub mod navigation;
|
||||||
pub mod dialog;
|
pub mod dialog;
|
||||||
|
pub mod command_navigation;
|
||||||
|
|||||||
204
client/src/modes/general/command_navigation.rs
Normal file
204
client/src/modes/general/command_navigation.rs
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum NavigationType {
|
||||||
|
FindFile,
|
||||||
|
// Future: CommandPalette, BufferList, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NavigationState {
|
||||||
|
pub active: bool,
|
||||||
|
pub input: String,
|
||||||
|
pub options: Vec<String>,
|
||||||
|
pub selected_index: Option<usize>,
|
||||||
|
pub filtered_options: Vec<(usize, String)>, // (original_index, filtered_string)
|
||||||
|
pub navigation_type: NavigationType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NavigationState {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
active: false,
|
||||||
|
input: String::new(),
|
||||||
|
options: Vec::new(),
|
||||||
|
selected_index: None,
|
||||||
|
filtered_options: Vec::new(),
|
||||||
|
navigation_type: NavigationType::FindFile,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn activate_find_file(&mut self, options: Vec<String>) {
|
||||||
|
self.active = true;
|
||||||
|
self.navigation_type = NavigationType::FindFile;
|
||||||
|
self.options = options;
|
||||||
|
self.input.clear();
|
||||||
|
self.update_filtered_options();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deactivate(&mut self) {
|
||||||
|
self.active = false;
|
||||||
|
self.input.clear();
|
||||||
|
self.options.clear();
|
||||||
|
self.filtered_options.clear();
|
||||||
|
self.selected_index = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_char(&mut self, c: char) {
|
||||||
|
self.input.push(c);
|
||||||
|
self.update_filtered_options();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_char(&mut self) {
|
||||||
|
self.input.pop();
|
||||||
|
self.update_filtered_options();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_up(&mut self) {
|
||||||
|
if self.filtered_options.is_empty() {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_down(&mut self) {
|
||||||
|
if self.filtered_options.is_empty() {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.selected_index = Some(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_selected_option(&self) -> Option<&str> {
|
||||||
|
self.selected_index
|
||||||
|
.and_then(|idx| self.filtered_options.get(idx))
|
||||||
|
.map(|(_, option)| option.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_filtered_options(&mut self) {
|
||||||
|
if self.input.is_empty() {
|
||||||
|
self.filtered_options = self.options
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, opt)| (i, opt.clone()))
|
||||||
|
.collect();
|
||||||
|
} else {
|
||||||
|
let input_lower = self.input.to_lowercase();
|
||||||
|
self.filtered_options = self.options
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, opt)| opt.to_lowercase().contains(&input_lower))
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle navigation events within General mode
|
||||||
|
pub async fn handle_command_navigation_event(
|
||||||
|
navigation_state: &mut NavigationState,
|
||||||
|
key: KeyEvent,
|
||||||
|
config: &Config,
|
||||||
|
) -> Result<EventOutcome> {
|
||||||
|
if !navigation_state.active {
|
||||||
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
|
}
|
||||||
|
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Esc => {
|
||||||
|
navigation_state.deactivate();
|
||||||
|
Ok(EventOutcome::Ok("Find File 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 {
|
||||||
|
NavigationType::FindFile => {
|
||||||
|
Ok(EventOutcome::Ok(format!("Selected file: {}", selected)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(EventOutcome::Ok("No file selected".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Up => {
|
||||||
|
navigation_state.move_up();
|
||||||
|
Ok(EventOutcome::Ok(String::new()))
|
||||||
|
}
|
||||||
|
KeyCode::Down => {
|
||||||
|
navigation_state.move_down();
|
||||||
|
Ok(EventOutcome::Ok(String::new()))
|
||||||
|
}
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
navigation_state.remove_char();
|
||||||
|
Ok(EventOutcome::Ok(String::new()))
|
||||||
|
}
|
||||||
|
KeyCode::Char(c) => {
|
||||||
|
navigation_state.add_char(c);
|
||||||
|
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) {
|
||||||
|
match action {
|
||||||
|
"move_up" => {
|
||||||
|
navigation_state.move_up();
|
||||||
|
Ok(EventOutcome::Ok(String::new()))
|
||||||
|
}
|
||||||
|
"move_down" => {
|
||||||
|
navigation_state.move_down();
|
||||||
|
Ok(EventOutcome::Ok(String::new()))
|
||||||
|
}
|
||||||
|
"select" => {
|
||||||
|
if let Some(selected) = navigation_state.get_selected_option() {
|
||||||
|
let selected = selected.to_string();
|
||||||
|
navigation_state.deactivate();
|
||||||
|
Ok(EventOutcome::Ok(format!("Selected file: {}", selected)))
|
||||||
|
} else {
|
||||||
|
Ok(EventOutcome::Ok("No file selected".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Ok(EventOutcome::Ok(String::new())),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(EventOutcome::Ok(String::new()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ use crate::state::pages::admin::AdminState;
|
|||||||
use crate::state::pages::canvas_state::CanvasState;
|
use crate::state::pages::canvas_state::CanvasState;
|
||||||
use crate::ui::handlers::context::UiContext;
|
use crate::ui::handlers::context::UiContext;
|
||||||
use crate::modes::handlers::event::EventOutcome;
|
use crate::modes::handlers::event::EventOutcome;
|
||||||
|
use crate::modes::general::command_navigation::{handle_command_navigation_event, NavigationState};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
pub async fn handle_navigation_event(
|
pub async fn handle_navigation_event(
|
||||||
@@ -25,7 +26,13 @@ pub async fn handle_navigation_event(
|
|||||||
command_mode: &mut bool,
|
command_mode: &mut bool,
|
||||||
command_input: &mut String,
|
command_input: &mut String,
|
||||||
command_message: &mut String,
|
command_message: &mut String,
|
||||||
|
navigation_state: &mut NavigationState,
|
||||||
) -> Result<EventOutcome> {
|
) -> Result<EventOutcome> {
|
||||||
|
// Handle command navigation first if active
|
||||||
|
if navigation_state.active {
|
||||||
|
return handle_command_navigation_event(navigation_state, key, config).await;
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
match action {
|
||||||
"move_up" => {
|
"move_up" => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// src/modes/handlers/event.rs
|
// src/modes/handlers/event.rs
|
||||||
use crossterm::event::Event;
|
use crossterm::event::{Event, KeyEvent};
|
||||||
use crossterm::cursor::SetCursorStyle;
|
use crossterm::cursor::SetCursorStyle;
|
||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
use crate::services::auth::AuthClient;
|
use crate::services::auth::AuthClient;
|
||||||
@@ -35,6 +35,7 @@ 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::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;
|
||||||
@@ -43,7 +44,7 @@ use crate::tui::functions::common::register::RegisterResult;
|
|||||||
use crate::functions::modes::navigation::add_table_nav::SaveTableResultSender;
|
use crate::functions::modes::navigation::add_table_nav::SaveTableResultSender;
|
||||||
use crate::functions::modes::navigation::add_logic_nav::SaveLogicResultSender;
|
use crate::functions::modes::navigation::add_logic_nav::SaveLogicResultSender;
|
||||||
use crate::functions::modes::navigation::add_logic_nav;
|
use crate::functions::modes::navigation::add_logic_nav;
|
||||||
use crossterm::event::{KeyCode, KeyModifiers};
|
use crossterm::event::KeyCode;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum EventOutcome {
|
pub enum EventOutcome {
|
||||||
@@ -53,6 +54,15 @@ pub enum EventOutcome {
|
|||||||
ButtonSelected { context: UiContext, index: usize },
|
ButtonSelected { context: UiContext, index: usize },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl EventOutcome {
|
||||||
|
pub fn get_message_if_ok(&self) -> String {
|
||||||
|
match self {
|
||||||
|
EventOutcome::Ok(msg) => msg.clone(),
|
||||||
|
_ => String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct EventHandler {
|
pub struct EventHandler {
|
||||||
pub command_mode: bool,
|
pub command_mode: bool,
|
||||||
pub command_input: String,
|
pub command_input: String,
|
||||||
@@ -67,10 +77,7 @@ pub struct EventHandler {
|
|||||||
pub register_result_sender: mpsc::Sender<RegisterResult>,
|
pub register_result_sender: mpsc::Sender<RegisterResult>,
|
||||||
pub save_table_result_sender: SaveTableResultSender,
|
pub save_table_result_sender: SaveTableResultSender,
|
||||||
pub save_logic_result_sender: SaveLogicResultSender,
|
pub save_logic_result_sender: SaveLogicResultSender,
|
||||||
pub find_file_palette_active: bool,
|
pub navigation_state: NavigationState,
|
||||||
pub find_file_options: Vec<String>,
|
|
||||||
pub find_file_selected_index: Option<usize>,
|
|
||||||
pub find_file_input: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventHandler {
|
impl EventHandler {
|
||||||
@@ -94,23 +101,18 @@ impl EventHandler {
|
|||||||
register_result_sender,
|
register_result_sender,
|
||||||
save_table_result_sender,
|
save_table_result_sender,
|
||||||
save_logic_result_sender,
|
save_logic_result_sender,
|
||||||
find_file_palette_active: false,
|
navigation_state: NavigationState::new(),
|
||||||
find_file_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(),
|
|
||||||
],
|
|
||||||
find_file_selected_index: None,
|
|
||||||
find_file_input: String::new(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_navigation_active(&self) -> bool {
|
||||||
|
self.navigation_state.active
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn activate_find_file(&mut self, options: Vec<String>) {
|
||||||
|
self.navigation_state.activate_find_file(options);
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn handle_event(
|
pub async fn handle_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -130,26 +132,28 @@ impl EventHandler {
|
|||||||
total_count: u64,
|
total_count: u64,
|
||||||
current_position: &mut u64,
|
current_position: &mut u64,
|
||||||
) -> Result<EventOutcome> {
|
) -> Result<EventOutcome> {
|
||||||
// Handle find file palette first
|
let mut current_mode = ModeManager::derive_mode(app_state, self, admin_state);
|
||||||
if self.find_file_palette_active {
|
|
||||||
if let Event::Key(key) = event {
|
// Handle active command navigation first
|
||||||
if key.code == KeyCode::Esc {
|
if current_mode == AppMode::General && self.navigation_state.active {
|
||||||
self.find_file_palette_active = false;
|
if let Event::Key(key_event) = event {
|
||||||
self.find_file_input.clear();
|
let outcome = handle_command_navigation_event(
|
||||||
self.find_file_selected_index = None;
|
&mut self.navigation_state,
|
||||||
self.command_message = "Find File palette closed".to_string();
|
key_event,
|
||||||
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
config,
|
||||||
|
).await?;
|
||||||
|
|
||||||
|
if !self.navigation_state.active {
|
||||||
|
self.command_message = outcome.get_message_if_ok();
|
||||||
|
current_mode = ModeManager::derive_mode(app_state, self, admin_state);
|
||||||
}
|
}
|
||||||
if let KeyCode::Char(c) = key.code {
|
app_state.update_mode(current_mode);
|
||||||
self.find_file_input.push(c);
|
return Ok(outcome);
|
||||||
} else if key.code == KeyCode::Backspace {
|
|
||||||
self.find_file_input.pop();
|
|
||||||
}
|
|
||||||
return Ok(EventOutcome::Ok("Palette event consumed".to_string()));
|
|
||||||
}
|
}
|
||||||
|
app_state.update_mode(current_mode);
|
||||||
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let current_mode = ModeManager::derive_mode(app_state, self, admin_state);
|
|
||||||
app_state.update_mode(current_mode);
|
app_state.update_mode(current_mode);
|
||||||
|
|
||||||
let current_view = {
|
let current_view = {
|
||||||
@@ -166,34 +170,29 @@ impl EventHandler {
|
|||||||
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 Some(dialog_result) = dialog::handle_dialog_event(
|
if let Event::Key(key_event) = event {
|
||||||
&event,
|
if let Some(dialog_result) = dialog::handle_dialog_event(
|
||||||
config,
|
&Event::Key(key_event),
|
||||||
app_state,
|
config, app_state, login_state, register_state, buffer_state, admin_state,
|
||||||
login_state,
|
).await {
|
||||||
register_state,
|
return dialog_result;
|
||||||
buffer_state,
|
}
|
||||||
admin_state,
|
} else if let Event::Resize(_,_) = event {
|
||||||
).await {
|
// Handle resize if needed
|
||||||
return dialog_result;
|
|
||||||
}
|
}
|
||||||
return Ok(EventOutcome::Ok(String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Event::Key(key) = event {
|
if let Event::Key(key_event) = event {
|
||||||
let key_code = key.code;
|
let key_code = key_event.code;
|
||||||
let modifiers = key.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!("Sidebar {}",
|
let message = format!("Sidebar {}", if app_state.ui.show_sidebar { "shown" } else { "hidden" });
|
||||||
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!("Buffer {}",
|
let message = format!("Buffer {}", if app_state.ui.show_buffer_list { "shown" } else { "hidden" });
|
||||||
if app_state.ui.show_buffer_list { "shown" } else { "hidden" }
|
|
||||||
);
|
|
||||||
return Ok(EventOutcome::Ok(message));
|
return Ok(EventOutcome::Ok(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,10 +223,9 @@ impl EventHandler {
|
|||||||
|
|
||||||
match current_mode {
|
match current_mode {
|
||||||
AppMode::General => {
|
AppMode::General => {
|
||||||
if app_state.ui.show_admin
|
if app_state.ui.show_admin && auth_state.role.as_deref() == Some("admin") {
|
||||||
&& auth_state.role.as_deref() == Some("admin") {
|
|
||||||
if admin_nav::handle_admin_navigation(
|
if admin_nav::handle_admin_navigation(
|
||||||
key,
|
key_event,
|
||||||
config,
|
config,
|
||||||
app_state,
|
app_state,
|
||||||
admin_state,
|
admin_state,
|
||||||
@@ -243,7 +241,7 @@ impl EventHandler {
|
|||||||
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,
|
key_event,
|
||||||
config,
|
config,
|
||||||
app_state,
|
app_state,
|
||||||
&mut admin_state.add_logic_state,
|
&mut admin_state.add_logic_state,
|
||||||
@@ -262,7 +260,7 @@ impl EventHandler {
|
|||||||
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,
|
key_event,
|
||||||
config,
|
config,
|
||||||
app_state,
|
app_state,
|
||||||
&mut admin_state.add_table_state,
|
&mut admin_state.add_table_state,
|
||||||
@@ -275,7 +273,7 @@ impl EventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let nav_outcome = navigation::handle_navigation_event(
|
let nav_outcome = navigation::handle_navigation_event(
|
||||||
key,
|
key_event,
|
||||||
config,
|
config,
|
||||||
form_state,
|
form_state,
|
||||||
app_state,
|
app_state,
|
||||||
@@ -286,7 +284,9 @@ impl EventHandler {
|
|||||||
&mut self.command_mode,
|
&mut self.command_mode,
|
||||||
&mut self.command_input,
|
&mut self.command_input,
|
||||||
&mut self.command_message,
|
&mut self.command_message,
|
||||||
|
&mut self.navigation_state,
|
||||||
).await;
|
).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 {
|
||||||
@@ -300,24 +300,22 @@ impl EventHandler {
|
|||||||
format!("Intro Option {} selected", index)
|
format!("Intro Option {} selected", index)
|
||||||
}
|
}
|
||||||
UiContext::Login => {
|
UiContext::Login => {
|
||||||
let login_action_message = match index {
|
match index {
|
||||||
0 => {
|
0 => {
|
||||||
login::initiate_login(login_state, app_state, self.auth_client.clone(), self.login_result_sender.clone())
|
login::initiate_login(login_state, app_state, self.auth_client.clone(), self.login_result_sender.clone())
|
||||||
},
|
},
|
||||||
1 => login::back_to_main(login_state, app_state, buffer_state).await,
|
1 => login::back_to_main(login_state, app_state, buffer_state).await,
|
||||||
_ => "Invalid Login Option".to_string(),
|
_ => "Invalid Login Option".to_string(),
|
||||||
};
|
}
|
||||||
login_action_message
|
|
||||||
}
|
}
|
||||||
UiContext::Register => {
|
UiContext::Register => {
|
||||||
let register_action_message = match index {
|
match index {
|
||||||
0 => {
|
0 => {
|
||||||
register::initiate_registration(register_state, app_state, self.auth_client.clone(), self.register_result_sender.clone())
|
register::initiate_registration(register_state, app_state, self.auth_client.clone(), self.register_result_sender.clone())
|
||||||
},
|
},
|
||||||
1 => register::back_to_login(register_state, app_state, buffer_state).await,
|
1 => register::back_to_login(register_state, app_state, buffer_state).await,
|
||||||
_ => "Invalid Login Option".to_string(),
|
_ => "Invalid Login Option".to_string(),
|
||||||
};
|
}
|
||||||
register_action_message
|
|
||||||
}
|
}
|
||||||
UiContext::Admin => {
|
UiContext::Admin => {
|
||||||
admin::handle_admin_selection(app_state, admin_state);
|
admin::handle_admin_selection(app_state, admin_state);
|
||||||
@@ -331,7 +329,7 @@ impl EventHandler {
|
|||||||
}
|
}
|
||||||
other => return other,
|
other => return other,
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
AppMode::ReadOnly => {
|
AppMode::ReadOnly => {
|
||||||
if config.get_read_only_action_for_key(key_code, modifiers) == Some("enter_highlight_mode_linewise")
|
if config.get_read_only_action_for_key(key_code, modifiers) == Some("enter_highlight_mode_linewise")
|
||||||
@@ -426,7 +424,7 @@ impl EventHandler {
|
|||||||
|
|
||||||
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,
|
key_event,
|
||||||
config,
|
config,
|
||||||
form_state,
|
form_state,
|
||||||
login_state,
|
login_state,
|
||||||
@@ -442,7 +440,7 @@ impl EventHandler {
|
|||||||
&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) == Some("exit_highlight_mode") {
|
if config.get_highlight_action_for_key(key_code, modifiers) == Some("exit_highlight_mode") {
|
||||||
@@ -461,7 +459,7 @@ impl EventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (_should_exit, message) = read_only::handle_read_only_event(
|
let (_should_exit, message) = read_only::handle_read_only_event(
|
||||||
app_state, key, config, form_state, login_state,
|
app_state, key_event, config, form_state, login_state,
|
||||||
register_state,
|
register_state,
|
||||||
&mut admin_state.add_table_state,
|
&mut admin_state.add_table_state,
|
||||||
&mut admin_state.add_logic_state,
|
&mut admin_state.add_logic_state,
|
||||||
@@ -500,7 +498,7 @@ impl EventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let edit_result = edit::handle_edit_event(
|
let edit_result = edit::handle_edit_event(
|
||||||
key,
|
key_event,
|
||||||
config,
|
config,
|
||||||
form_state,
|
form_state,
|
||||||
login_state,
|
login_state,
|
||||||
@@ -551,10 +549,9 @@ impl EventHandler {
|
|||||||
return Err(e.into());
|
return Err(e.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
AppMode::Command => {
|
AppMode::Command => {
|
||||||
// Handle immediate command mode actions
|
|
||||||
if config.is_exit_command_mode(key_code, modifiers) {
|
if config.is_exit_command_mode(key_code, modifiers) {
|
||||||
self.command_input.clear();
|
self.command_input.clear();
|
||||||
self.command_message.clear();
|
self.command_message.clear();
|
||||||
@@ -565,46 +562,54 @@ impl EventHandler {
|
|||||||
|
|
||||||
if config.is_command_execute(key_code, modifiers) {
|
if config.is_command_execute(key_code, modifiers) {
|
||||||
let outcome = command_mode::handle_command_event(
|
let outcome = command_mode::handle_command_event(
|
||||||
key, config, app_state, login_state, register_state, form_state,
|
key_event, config, app_state, login_state, register_state, form_state,
|
||||||
&mut self.command_input, &mut self.command_message,
|
&mut self.command_input, &mut self.command_message,
|
||||||
grpc_client, command_handler, terminal, current_position, total_count,
|
grpc_client, command_handler, terminal, current_position, total_count,
|
||||||
).await?;
|
).await?;
|
||||||
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);
|
||||||
|
app_state.update_mode(new_mode);
|
||||||
return Ok(outcome);
|
return Ok(outcome);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle backspace
|
|
||||||
if key_code == KeyCode::Backspace {
|
if key_code == KeyCode::Backspace {
|
||||||
self.command_input.pop();
|
self.command_input.pop();
|
||||||
self.key_sequence_tracker.reset();
|
self.key_sequence_tracker.reset();
|
||||||
return Ok(EventOutcome::Ok(String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle character input and sequences
|
|
||||||
if let KeyCode::Char(c) = key_code {
|
if let KeyCode::Char(c) = key_code {
|
||||||
if c == 'f' {
|
if c == '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 {
|
||||||
self.find_file_palette_active = true;
|
let options = vec![
|
||||||
self.find_file_input.clear();
|
"src/main.rs".to_string(),
|
||||||
self.find_file_selected_index = if self.find_file_options.is_empty() {
|
"src/lib.rs".to_string(),
|
||||||
None
|
"Cargo.toml".to_string(),
|
||||||
} else {
|
"README.md".to_string(),
|
||||||
Some(0)
|
"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;
|
self.command_mode = false;
|
||||||
self.command_input.clear();
|
self.command_input.clear();
|
||||||
self.command_message = "Find File:".to_string();
|
self.command_message = "Find File:".to_string();
|
||||||
self.key_sequence_tracker.reset();
|
self.key_sequence_tracker.reset();
|
||||||
|
app_state.update_mode(AppMode::General);
|
||||||
return Ok(EventOutcome::Ok("Find File palette activated".to_string()));
|
return Ok(EventOutcome::Ok("Find File 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');
|
||||||
self.command_input.push('f');
|
if sequence.len() > 1 && sequence[0] == KeyCode::Char('f') {
|
||||||
|
self.command_input.push('f');
|
||||||
|
}
|
||||||
self.command_message = "Find File not available in this view.".to_string();
|
self.command_message = "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()));
|
||||||
}
|
}
|
||||||
@@ -623,11 +628,12 @@ impl EventHandler {
|
|||||||
return Ok(EventOutcome::Ok(String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset tracker for other keys
|
|
||||||
self.key_sequence_tracker.reset();
|
self.key_sequence_tracker.reset();
|
||||||
return Ok(EventOutcome::Ok(String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if let Event::Resize(_,_) = event {
|
||||||
|
return Ok(EventOutcome::Ok("Resized".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.edit_mode_cooldown = false;
|
self.edit_mode_cooldown = false;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ impl ModeManager {
|
|||||||
event_handler: &EventHandler,
|
event_handler: &EventHandler,
|
||||||
admin_state: &AdminState,
|
admin_state: &AdminState,
|
||||||
) -> AppMode {
|
) -> AppMode {
|
||||||
if event_handler.find_file_palette_active {
|
if event_handler.navigation_state.active {
|
||||||
return AppMode::General;
|
return AppMode::General;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,14 +82,14 @@ impl ModeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mode transition rules
|
// Mode transition rules
|
||||||
pub fn can_enter_command_mode(current_mode: AppMode) -> bool {
|
pub fn can_enter_command_mode(current_mode: AppMode) -> bool {
|
||||||
!matches!(current_mode, AppMode::Edit) // Can't enter from Edit mode
|
!matches!(current_mode, AppMode::Edit)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn can_enter_edit_mode(current_mode: AppMode) -> bool {
|
pub fn can_enter_edit_mode(current_mode: AppMode) -> bool {
|
||||||
matches!(current_mode, AppMode::ReadOnly) // Only from ReadOnly
|
matches!(current_mode, AppMode::ReadOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn can_enter_read_only_mode(current_mode: AppMode) -> bool {
|
pub fn can_enter_read_only_mode(current_mode: AppMode) -> bool {
|
||||||
matches!(current_mode, AppMode::Edit | AppMode::Command | AppMode::Highlight)
|
matches!(current_mode, AppMode::Edit | AppMode::Command | AppMode::Highlight)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,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}, // Added Rect
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
style::Style, // Added Style for render_find_file_palette
|
style::Style,
|
||||||
widgets::{Block, List, ListItem, Paragraph}, // Added for render_find_file_palette
|
widgets::{Block, List, ListItem, Paragraph},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
use crate::state::pages::canvas_state::CanvasState;
|
use crate::state::pages::canvas_state::CanvasState;
|
||||||
@@ -29,6 +29,7 @@ use crate::state::app::buffer::BufferState;
|
|||||||
use crate::state::app::state::AppState; // AppState is needed for app_state.ui checks
|
use crate::state::app::state::AppState; // AppState is needed for app_state.ui checks
|
||||||
use crate::state::pages::admin::AdminState;
|
use crate::state::pages::admin::AdminState;
|
||||||
use crate::state::app::highlight::HighlightState;
|
use crate::state::app::highlight::HighlightState;
|
||||||
|
use crate::modes::general::command_navigation::NavigationState;
|
||||||
|
|
||||||
// ++ New function to render the Find File Palette ++
|
// ++ New function to render the Find File Palette ++
|
||||||
fn render_find_file_palette(
|
fn render_find_file_palette(
|
||||||
@@ -91,11 +92,8 @@ pub fn render_ui(
|
|||||||
event_handler_command_input: &str, // For normal command line
|
event_handler_command_input: &str, // For normal command line
|
||||||
event_handler_command_mode_active: bool, // Normal command line active?
|
event_handler_command_mode_active: bool, // Normal command line active?
|
||||||
event_handler_command_message: &str,
|
event_handler_command_message: &str,
|
||||||
// ++ Find File Palette specific state from EventHandler ++
|
// Navigation state replaces find file palette specific state
|
||||||
find_file_palette_active: bool,
|
navigation_state: &NavigationState,
|
||||||
find_file_options: &[String],
|
|
||||||
find_file_selected_index: Option<usize>,
|
|
||||||
find_file_palette_input: &str, // Input for the palette
|
|
||||||
// General app state
|
// General app state
|
||||||
total_count: u64,
|
total_count: u64,
|
||||||
current_position: u64,
|
current_position: u64,
|
||||||
@@ -108,10 +106,10 @@ pub fn render_ui(
|
|||||||
// Determine the height needed for the bottom bar (status + command/palette)
|
// Determine the height needed for the bottom bar (status + command/palette)
|
||||||
let mut bottom_area_constraints: Vec<Constraint> = vec![Constraint::Length(1)]; // Status line
|
let mut bottom_area_constraints: Vec<Constraint> = vec![Constraint::Length(1)]; // Status line
|
||||||
|
|
||||||
let command_palette_area_height = if find_file_palette_active {
|
let command_palette_area_height = if navigation_state.active {
|
||||||
// Height for palette: 1 for input + number of visible options
|
// Height for palette: 1 for input + number of visible options
|
||||||
let max_visible_options = 7; // Example, can be adjusted
|
let max_visible_options = 7; // Example, can be adjusted
|
||||||
1 + find_file_options.iter().take(max_visible_options).count().min(max_visible_options) as u16
|
1 + navigation_state.filtered_options.iter().take(max_visible_options).count().min(max_visible_options) as u16
|
||||||
} else if event_handler_command_mode_active {
|
} else if event_handler_command_mode_active {
|
||||||
1 // Height for normal command line
|
1 // Height for normal command line
|
||||||
} else {
|
} else {
|
||||||
@@ -228,14 +226,14 @@ pub fn render_ui(
|
|||||||
|
|
||||||
// Render Find File Palette OR Normal Command Line
|
// Render Find File Palette OR Normal Command Line
|
||||||
if let Some(area) = command_render_area {
|
if let Some(area) = command_render_area {
|
||||||
if find_file_palette_active {
|
if navigation_state.active {
|
||||||
render_find_file_palette(
|
render_find_file_palette(
|
||||||
f,
|
f,
|
||||||
area,
|
area,
|
||||||
theme,
|
theme,
|
||||||
find_file_palette_input, // Pass palette-specific input
|
&navigation_state.input,
|
||||||
find_file_options,
|
&navigation_state.filtered_options.iter().map(|(_, opt)| opt.clone()).collect::<Vec<_>>(),
|
||||||
find_file_selected_index,
|
navigation_state.selected_index,
|
||||||
);
|
);
|
||||||
} else if event_handler_command_mode_active {
|
} else if event_handler_command_mode_active {
|
||||||
// Normal command line
|
// Normal command line
|
||||||
@@ -243,10 +241,9 @@ pub fn render_ui(
|
|||||||
f,
|
f,
|
||||||
area,
|
area,
|
||||||
event_handler_command_input,
|
event_handler_command_input,
|
||||||
true, // It's active
|
true,
|
||||||
theme,
|
theme,
|
||||||
event_handler_command_message,
|
event_handler_command_message,
|
||||||
// No palette-specific args for normal command line
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,16 +209,12 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
&mut admin_state,
|
&mut admin_state,
|
||||||
&buffer_state,
|
&buffer_state,
|
||||||
&theme,
|
&theme,
|
||||||
event_handler.is_edit_mode, // is_event_handler_edit_mode
|
event_handler.is_edit_mode,
|
||||||
&event_handler.highlight_state,
|
&event_handler.highlight_state,
|
||||||
&event_handler.command_input, // event_handler_command_input
|
&event_handler.command_input,
|
||||||
event_handler.command_mode, // event_handler_command_mode_active
|
event_handler.command_mode,
|
||||||
&event_handler.command_message, // event_handler_command_message
|
&event_handler.command_message,
|
||||||
// Find File Palette specific state
|
&event_handler.navigation_state,
|
||||||
event_handler.find_file_palette_active,
|
|
||||||
&event_handler.find_file_options,
|
|
||||||
event_handler.find_file_selected_index,
|
|
||||||
&event_handler.find_file_input,
|
|
||||||
// General app state
|
// General app state
|
||||||
app_state.total_count,
|
app_state.total_count,
|
||||||
app_state.current_position,
|
app_state.current_position,
|
||||||
|
|||||||
Reference in New Issue
Block a user