Files
komp_ac/client/src/modes/general/command_navigation.rs
2025-05-29 16:11:41 +02:00

205 lines
6.4 KiB
Rust

// 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()))
}
}
}
}