navigation in the menu but needs refactoring
This commit is contained in:
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::ui::handlers::context::UiContext;
|
||||
use crate::modes::handlers::event::EventOutcome;
|
||||
use crate::modes::general::command_navigation::{handle_command_navigation_event, NavigationState};
|
||||
use anyhow::Result;
|
||||
|
||||
pub async fn handle_navigation_event(
|
||||
@@ -25,7 +26,13 @@ pub async fn handle_navigation_event(
|
||||
command_mode: &mut bool,
|
||||
command_input: &mut String,
|
||||
command_message: &mut String,
|
||||
navigation_state: &mut NavigationState,
|
||||
) -> 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) {
|
||||
match action {
|
||||
"move_up" => {
|
||||
|
||||
Reference in New Issue
Block a user