Implement vim-style navigation and visual mode
All checks were successful
Build and Release / build-and-release (push) Successful in 51s
All checks were successful
Build and Release / build-and-release (push) Successful in 51s
- Add vim-style h key: closes folders and jumps to parent - Implement stop() method in player for proper playback stopping - Add space key to restart playback when stopped - Add playlist color coding: green (playing), blue (paused), yellow (stopped) - Fix n/p keys to preserve player state when switching tracks - Implement vim-style visual mode with v key for multi-file selection - Visual mode exits automatically on play/add actions - Remove unused play_previous method - Bump version to 0.1.7
This commit is contained in:
@@ -33,6 +33,8 @@ pub struct AppState {
|
||||
pub search_match_index: usize,
|
||||
pub tab_search_results: Vec<PathBuf>,
|
||||
pub tab_search_index: usize,
|
||||
pub visual_mode: bool,
|
||||
pub visual_anchor: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -71,6 +73,8 @@ impl AppState {
|
||||
search_match_index: 0,
|
||||
tab_search_results: Vec::new(),
|
||||
tab_search_index: 0,
|
||||
visual_mode: false,
|
||||
visual_anchor: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,12 +85,20 @@ impl AppState {
|
||||
if self.selected_index < self.scroll_offset {
|
||||
self.scroll_offset = self.selected_index;
|
||||
}
|
||||
// Update visual selection if in visual mode
|
||||
if self.visual_mode {
|
||||
self.update_visual_selection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_selection_down(&mut self) {
|
||||
if self.selected_index < self.flattened_items.len().saturating_sub(1) {
|
||||
self.selected_index += 1;
|
||||
// Update visual selection if in visual mode
|
||||
if self.visual_mode {
|
||||
self.update_visual_selection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,11 +132,39 @@ impl AppState {
|
||||
}
|
||||
|
||||
pub fn collapse_selected(&mut self) {
|
||||
if let Some(item) = self.get_selected_item() {
|
||||
let item = self.get_selected_item().cloned();
|
||||
if let Some(item) = item {
|
||||
if item.node.is_dir {
|
||||
let path = item.node.path.clone();
|
||||
self.expanded_dirs.remove(&path);
|
||||
self.rebuild_flattened_items();
|
||||
let was_expanded = self.expanded_dirs.contains(&path);
|
||||
|
||||
if was_expanded {
|
||||
// Close the expanded folder
|
||||
self.expanded_dirs.remove(&path);
|
||||
self.rebuild_flattened_items();
|
||||
} else {
|
||||
// Folder is collapsed, close parent instead and jump to it
|
||||
if let Some(parent) = path.parent() {
|
||||
let parent_buf = parent.to_path_buf();
|
||||
self.expanded_dirs.remove(&parent_buf);
|
||||
self.rebuild_flattened_items();
|
||||
// Jump to parent folder
|
||||
if let Some(parent_idx) = self.flattened_items.iter().position(|i| i.node.path == parent_buf) {
|
||||
self.selected_index = parent_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Close parent folder when on a file and jump to it
|
||||
if let Some(parent) = item.node.path.parent() {
|
||||
let parent_buf = parent.to_path_buf();
|
||||
self.expanded_dirs.remove(&parent_buf);
|
||||
self.rebuild_flattened_items();
|
||||
// Jump to parent folder
|
||||
if let Some(parent_idx) = self.flattened_items.iter().position(|i| i.node.path == parent_buf) {
|
||||
self.selected_index = parent_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,13 +180,37 @@ impl AppState {
|
||||
}
|
||||
|
||||
pub fn toggle_mark(&mut self) {
|
||||
if let Some(item) = self.get_selected_item() {
|
||||
if !item.node.is_dir {
|
||||
let path = item.node.path.clone();
|
||||
if self.marked_files.contains(&path) {
|
||||
self.marked_files.remove(&path);
|
||||
} else {
|
||||
self.marked_files.insert(path);
|
||||
if self.visual_mode {
|
||||
// Exit visual mode and mark all files in range
|
||||
self.update_visual_selection();
|
||||
self.visual_mode = false;
|
||||
} else {
|
||||
// Enter visual mode
|
||||
self.visual_mode = true;
|
||||
self.visual_anchor = self.selected_index;
|
||||
// Clear previous marks when entering visual mode
|
||||
self.marked_files.clear();
|
||||
// Mark current file
|
||||
if let Some(item) = self.get_selected_item() {
|
||||
if !item.node.is_dir {
|
||||
self.marked_files.insert(item.node.path.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_visual_selection(&mut self) {
|
||||
// Clear marks
|
||||
self.marked_files.clear();
|
||||
|
||||
// Mark all files between anchor and current position
|
||||
let start = self.visual_anchor.min(self.selected_index);
|
||||
let end = self.visual_anchor.max(self.selected_index);
|
||||
|
||||
for i in start..=end {
|
||||
if let Some(item) = self.flattened_items.get(i) {
|
||||
if !item.node.is_dir {
|
||||
self.marked_files.insert(item.node.path.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,14 +293,6 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_previous(&mut self) {
|
||||
if self.playlist_index > 0 {
|
||||
self.playlist_index -= 1;
|
||||
self.current_file = Some(self.playlist[self.playlist_index].clone());
|
||||
self.player_state = PlayerState::Playing;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_flattened_items(&mut self) {
|
||||
// Keep current expanded state after rescan
|
||||
self.rebuild_flattened_items();
|
||||
|
||||
Reference in New Issue
Block a user