Add vim bindings and directory expand/collapse
- Replace arrow keys with j/k for navigation - Add h/l for collapse/expand directories - Remove emoji icons, use clean text markers - Show directories with [-]/[+] expand markers - Track expanded state per directory path - Add directory suffix (/) for clarity - Update help text with vim bindings
This commit is contained in:
parent
7ce264fd96
commit
8104f54887
10
src/main.rs
10
src/main.rs
@ -101,12 +101,18 @@ async fn handle_key_event(state: &mut AppState, key_code: KeyCode) -> Result<()>
|
||||
KeyCode::Char('q') => {
|
||||
state.should_quit = true;
|
||||
}
|
||||
KeyCode::Up => {
|
||||
KeyCode::Char('k') => {
|
||||
state.move_selection_up();
|
||||
}
|
||||
KeyCode::Down => {
|
||||
KeyCode::Char('j') => {
|
||||
state.move_selection_down();
|
||||
}
|
||||
KeyCode::Char('h') => {
|
||||
state.collapse_selected();
|
||||
}
|
||||
KeyCode::Char('l') => {
|
||||
state.expand_selected();
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
if let Some(item) = state.get_selected_item() {
|
||||
if !item.node.is_dir {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use crate::cache::{Cache, FileTreeNode};
|
||||
use crate::config::Config;
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@ -21,6 +22,7 @@ pub struct AppState {
|
||||
pub volume: i64,
|
||||
pub should_quit: bool,
|
||||
pub flattened_items: Vec<FlattenedItem>,
|
||||
pub expanded_dirs: HashSet<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -32,7 +34,11 @@ pub struct FlattenedItem {
|
||||
|
||||
impl AppState {
|
||||
pub fn new(cache: Cache, config: Config) -> Self {
|
||||
let flattened_items = flatten_tree(&cache.file_tree, 0);
|
||||
let mut expanded_dirs = HashSet::new();
|
||||
// Start with all directories expanded
|
||||
collect_all_dirs(&cache.file_tree, &mut expanded_dirs);
|
||||
|
||||
let flattened_items = flatten_tree(&cache.file_tree, 0, &expanded_dirs);
|
||||
|
||||
Self {
|
||||
cache,
|
||||
@ -46,6 +52,7 @@ impl AppState {
|
||||
volume: 100,
|
||||
should_quit: false,
|
||||
flattened_items,
|
||||
expanded_dirs,
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,28 +72,79 @@ impl AppState {
|
||||
self.flattened_items.get(self.selected_index)
|
||||
}
|
||||
|
||||
pub fn toggle_expand(&mut self) {
|
||||
if let Some(item) = self.get_selected_item() {
|
||||
if item.node.is_dir {
|
||||
let path = item.node.path.clone();
|
||||
if self.expanded_dirs.contains(&path) {
|
||||
self.expanded_dirs.remove(&path);
|
||||
} else {
|
||||
self.expanded_dirs.insert(path);
|
||||
}
|
||||
self.rebuild_flattened_items();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collapse_selected(&mut self) {
|
||||
if let Some(item) = self.get_selected_item() {
|
||||
if item.node.is_dir {
|
||||
let path = item.node.path.clone();
|
||||
self.expanded_dirs.remove(&path);
|
||||
self.rebuild_flattened_items();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expand_selected(&mut self) {
|
||||
if let Some(item) = self.get_selected_item() {
|
||||
if item.node.is_dir {
|
||||
let path = item.node.path.clone();
|
||||
self.expanded_dirs.insert(path);
|
||||
self.rebuild_flattened_items();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_flattened_items(&mut self) {
|
||||
self.flattened_items = flatten_tree(&self.cache.file_tree, 0);
|
||||
self.expanded_dirs.clear();
|
||||
collect_all_dirs(&self.cache.file_tree, &mut self.expanded_dirs);
|
||||
self.rebuild_flattened_items();
|
||||
}
|
||||
|
||||
fn rebuild_flattened_items(&mut self) {
|
||||
self.flattened_items = flatten_tree(&self.cache.file_tree, 0, &self.expanded_dirs);
|
||||
if self.selected_index >= self.flattened_items.len() {
|
||||
self.selected_index = self.flattened_items.len().saturating_sub(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flatten_tree(nodes: &[FileTreeNode], depth: usize) -> Vec<FlattenedItem> {
|
||||
fn flatten_tree(nodes: &[FileTreeNode], depth: usize, expanded_dirs: &HashSet<PathBuf>) -> Vec<FlattenedItem> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
for node in nodes {
|
||||
let is_expanded = expanded_dirs.contains(&node.path);
|
||||
|
||||
result.push(FlattenedItem {
|
||||
node: node.clone(),
|
||||
depth,
|
||||
is_expanded: true, // For now, all directories are expanded
|
||||
is_expanded,
|
||||
});
|
||||
|
||||
if node.is_dir && !node.children.is_empty() {
|
||||
result.extend(flatten_tree(&node.children, depth + 1));
|
||||
if node.is_dir && !node.children.is_empty() && is_expanded {
|
||||
result.extend(flatten_tree(&node.children, depth + 1, expanded_dirs));
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn collect_all_dirs(nodes: &[FileTreeNode], dirs: &mut HashSet<PathBuf>) {
|
||||
for node in nodes {
|
||||
if node.is_dir {
|
||||
dirs.insert(node.path.clone());
|
||||
collect_all_dirs(&node.children, dirs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,8 +24,13 @@ fn render_file_panel(frame: &mut Frame, state: &AppState, area: Rect) {
|
||||
.enumerate()
|
||||
.map(|(idx, item)| {
|
||||
let indent = " ".repeat(item.depth);
|
||||
let prefix = if item.node.is_dir { "📁 " } else if item.node.metadata.as_ref().map(|m| m.is_video).unwrap_or(false) { "🎥 " } else { "🎵 " };
|
||||
let text = format!("{}{}{}", indent, prefix, item.node.name);
|
||||
let expand_marker = if item.node.is_dir {
|
||||
if item.is_expanded { "[-] " } else { "[+] " }
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
let suffix = if item.node.is_dir { "/" } else { "" };
|
||||
let text = format!("{}{}{}{}", indent, expand_marker, item.node.name, suffix);
|
||||
|
||||
let style = if idx == state.selected_index {
|
||||
Style::default().fg(Color::Black).bg(Color::Cyan)
|
||||
@ -114,12 +119,16 @@ fn render_status_panel(frame: &mut Frame, state: &AppState, area: Rect) {
|
||||
let help_text = vec![
|
||||
Line::from(""),
|
||||
Line::from(vec![
|
||||
Span::styled("↑/↓", Style::default().fg(Color::Cyan)),
|
||||
Span::raw(" Navigate"),
|
||||
Span::styled("j/k", Style::default().fg(Color::Cyan)),
|
||||
Span::raw(" Navigate down/up"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled("h/l", Style::default().fg(Color::Cyan)),
|
||||
Span::raw(" Collapse/expand dir"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled("Enter", Style::default().fg(Color::Cyan)),
|
||||
Span::raw(" Play"),
|
||||
Span::raw(" Play file"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled("Space", Style::default().fg(Color::Cyan)),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user