From 9f5707ad7415149f79228b9672c7422dde18cb9f Mon Sep 17 00:00:00 2001 From: sxyazi Date: Thu, 13 Jul 2023 20:08:37 +0800 Subject: [PATCH] feat: support of navigating to file for cd event --- Cargo.lock | 10 +++++----- src/core/external/fzf.rs | 9 ++++++--- src/core/files/file.rs | 5 +++++ src/core/files/files.rs | 5 +++++ src/core/manager/folder.rs | 27 +++++++++++++++++++-------- src/core/manager/manager.rs | 24 ++++++++++-------------- src/core/manager/tab.rs | 32 ++++++++++++++++++++++++++------ src/core/manager/watcher.rs | 8 +++++--- src/ui/app.rs | 2 +- src/ui/logs.rs | 2 +- src/ui/manager/folder.rs | 14 +++++++------- 11 files changed, 90 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 473a3966..5caf273d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1414,7 +1414,7 @@ checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.2", + "regex-automata 0.3.3", "regex-syntax 0.7.4", ] @@ -1429,9 +1429,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" +checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" dependencies = [ "aho-corasick", "memchr", @@ -1990,9 +1990,9 @@ dependencies = [ [[package]] name = "trash" -version = "3.0.5" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85931d0ad541fa457c3f94c7cc5f14920f17a385832a7ef7621762c23cbf9729" +checksum = "af3663fb8f476d674b9c61d1d2796acec725bef6bec4b41402a904252a25971e" dependencies = [ "chrono", "libc", diff --git a/src/core/external/fzf.rs b/src/core/external/fzf.rs index f3bdca14..aec3659d 100644 --- a/src/core/external/fzf.rs +++ b/src/core/external/fzf.rs @@ -3,17 +3,20 @@ use std::{path::PathBuf, process::Stdio}; use anyhow::Result; use tokio::{process::Command, sync::oneshot::{self, Receiver}}; -pub struct FzfOpt {} +pub struct FzfOpt { + pub cwd: PathBuf, +} pub fn fzf(opt: FzfOpt) -> Result>> { - let child = Command::new("fzf").kill_on_drop(true).stdout(Stdio::piped()).spawn()?; + let child = + Command::new("fzf").current_dir(&opt.cwd).kill_on_drop(true).stdout(Stdio::piped()).spawn()?; let (tx, rx) = oneshot::channel(); tokio::spawn(async move { if let Ok(output) = child.wait_with_output().await { let selected = String::from_utf8_lossy(&output.stdout).trim().to_string(); if !selected.is_empty() { - tx.send(Ok(selected.into())).ok(); + tx.send(Ok(opt.cwd.join(selected))).ok(); return; } } diff --git a/src/core/files/file.rs b/src/core/files/file.rs index 425b7295..f610bee6 100644 --- a/src/core/files/file.rs +++ b/src/core/files/file.rs @@ -30,6 +30,11 @@ impl File { let is_hidden = path.file_name().map(|s| s.to_string_lossy().starts_with('.')).unwrap_or(false); File { path: path.to_path_buf(), meta, length, is_link, is_hidden, is_selected: false } } +} + +impl File { + #[inline] + pub fn path(&self) -> PathBuf { self.path.clone() } #[inline] pub fn set_path(mut self, path: &Path) -> Self { diff --git a/src/core/files/files.rs b/src/core/files/files.rs index 1a1ad487..57eef14e 100644 --- a/src/core/files/files.rs +++ b/src/core/files/files.rs @@ -64,6 +64,11 @@ impl Files { } } + #[inline] + pub fn duplicate(&self, idx: usize) -> Option { + self.items.get_index(idx).map(|(_, file)| file.clone()) + } + pub fn update_read(&mut self, mut items: IndexMap) -> bool { if !self.show_hidden { items.retain(|_, item| !item.is_hidden); diff --git a/src/core/manager/folder.rs b/src/core/manager/folder.rs index 397a321a..397fde3f 100644 --- a/src/core/manager/folder.rs +++ b/src/core/manager/folder.rs @@ -13,6 +13,7 @@ pub struct Folder { offset: usize, cursor: usize, + pub hovered: Option, pub in_search: bool, } @@ -34,8 +35,14 @@ impl Folder { } let len = self.files.len(); - self.cursor = self.cursor.min(len.saturating_sub(1)); self.offset = self.offset.min(len); + self.cursor = self.cursor.min(len.saturating_sub(1)); + + if let Some(h) = self.hovered.as_ref().map(|h| h.path()) { + self.hover(&h); + } + self.hovered = self.files.duplicate(self.cursor); + true } @@ -50,6 +57,7 @@ impl Folder { let old = self.cursor; self.cursor = (self.cursor + step).min(len - 1); + self.hovered = self.files.duplicate(self.cursor); let limit = Self::limit(); if self.cursor >= (self.offset + limit).min(len).saturating_sub(5) { @@ -62,6 +70,7 @@ impl Folder { pub fn prev(&mut self, step: usize) -> bool { let old = self.cursor; self.cursor = self.cursor.saturating_sub(step); + self.hovered = self.files.duplicate(self.cursor); if self.cursor < self.offset + 5 { self.offset = self.offset.saturating_sub(old - self.cursor); @@ -130,25 +139,27 @@ impl Folder { } pub fn hover(&mut self, path: &Path) -> bool { - if self.hovered().map(|h| h.path.as_path()) == Some(path) { + if matches!(self.hovered, Some(ref h) if h.path == path) { return false; } let new = self.position(path).unwrap_or(self.cursor); if new > self.cursor { self.next(new - self.cursor) } else { self.prev(self.cursor - new) } } + + pub fn hover_force(&mut self, file: File) -> bool { + if !self.hover(&file.path) && self.files.is_empty() { + self.hovered = Some(file); + return true; + } + false + } } impl Folder { - #[inline] - pub fn hovered(&self) -> Option<&File> { self.files.get_index(self.cursor).map(|(_, item)| item) } - #[inline] pub fn cursor(&self) -> usize { self.cursor } - #[inline] - pub fn rel_cursor(&self) -> usize { self.cursor - self.offset } - #[inline] pub fn position(&self, path: &Path) -> Option { self.files.iter().position(|(p, _)| p == path) diff --git a/src/core/manager/manager.rs b/src/core/manager/manager.rs index ddbaabe4..f345b504 100644 --- a/src/core/manager/manager.rs +++ b/src/core/manager/manager.rs @@ -37,9 +37,9 @@ impl Manager { if let Some(ref p) = tab.parent { to_watch.insert(p.cwd.clone()); } - if let Some(ref h) = tab.current.hovered() { + if let Some(ref h) = tab.current.hovered { if h.meta.is_dir() { - to_watch.insert(h.path.clone()); + to_watch.insert(h.path()); } } } @@ -62,7 +62,7 @@ impl Manager { self.active_mut().preview.go(&hovered.path, &mime); } else { tokio::spawn(async move { - if let Ok(mime) = Precache::mimetype(&vec![hovered.path.clone()]).await { + if let Ok(mime) = Precache::mimetype(&vec![hovered.path()]).await { if let Some(Some(mime)) = mime.first() { emit!(Mimetype(hovered.path, mime.clone())); } @@ -148,11 +148,7 @@ impl Manager { fn bulk_rename(&self) -> bool { false } pub fn selected(&self) -> Vec { - self - .current() - .selected() - .or_else(|| self.hovered().map(|h| vec![h.path.clone()])) - .unwrap_or_default() + self.current().selected().or_else(|| self.hovered().map(|h| vec![h.path()])).unwrap_or_default() } pub async fn mimetype(&mut self, files: &Vec) -> Vec> { @@ -171,7 +167,7 @@ impl Manager { pub fn update_read(&mut self, op: FilesOp) -> bool { let path = op.path(); let cwd = self.current().cwd.clone(); - let hovered = self.hovered().map(|h| h.path.clone()); + let hovered = self.hovered().map(|h| h.path()); let mut b = if cwd == path && !self.current().in_search { self.current_mut().update(op) @@ -191,7 +187,7 @@ impl Manager { b |= self.active_mut().parent.as_mut().map_or(false, |p| p.hover(&cwd)); b |= hovered.as_ref().map_or(false, |h| self.current_mut().hover(h)); - if hovered != self.hovered().map(|h| h.path.clone()) { + if hovered != self.hovered().map(|h| h.path()) { emit!(Hover); } b @@ -238,8 +234,8 @@ impl Manager { } pub fn update_preview(&mut self, path: PathBuf, data: PreviewData) -> bool { - let hovered = if let Some(h) = self.current().hovered() { - h.path.clone() + let hovered = if let Some(ref h) = self.current().hovered { + h.path() } else { return self.active_mut().preview.reset(); }; @@ -275,8 +271,8 @@ impl Manager { pub fn current_mut(&mut self) -> &mut Folder { &mut self.tabs.active_mut().current } #[inline] - pub fn parent(&self) -> &Option { &self.tabs.active().parent } + pub fn parent(&self) -> Option<&Folder> { self.tabs.active().parent.as_ref() } #[inline] - pub fn hovered(&self) -> Option<&File> { self.tabs.active().current.hovered() } + pub fn hovered(&self) -> Option<&File> { self.tabs.active().current.hovered.as_ref() } } diff --git a/src/core/manager/tab.rs b/src/core/manager/tab.rs index 4619e903..c5906586 100644 --- a/src/core/manager/tab.rs +++ b/src/core/manager/tab.rs @@ -1,11 +1,11 @@ use std::{collections::BTreeMap, mem, path::{Path, PathBuf}}; use anyhow::{Error, Result}; -use indexmap::IndexMap; use tokio::task::JoinHandle; +use tracing::info; use super::{Folder, Mode, Preview}; -use crate::{core::{external::{self, FzfOpt, ZoxideOpt}, files::{Files, FilesOp}, input::{Input, InputOpt}, Event, BLOCKER}, emit, misc::Defer}; +use crate::{core::{external::{self, FzfOpt, ZoxideOpt}, files::{File, Files, FilesOp}, input::{Input, InputOpt}, Event, BLOCKER}, emit, misc::Defer}; pub struct Tab { pub(super) current: Folder, @@ -73,8 +73,23 @@ impl Tab { true } - pub fn cd(&mut self, target: PathBuf) -> bool { + pub async fn cd(&mut self, mut target: PathBuf) -> bool { + let file = if let Ok(f) = File::from(&target).await { + f + } else { + return false; + }; + + let mut hovered = None; + if !file.meta.is_dir() { + hovered = Some(file); + target = target.parent().unwrap().to_path_buf(); + } + if self.current.cwd == target { + if hovered.map(|h| self.current.hover_force(h)).unwrap_or(false) { + emit!(Hover); + } return false; } @@ -92,12 +107,15 @@ impl Tab { self.parent = Some(self.history_new(parent)); } + if let Some(h) = hovered { + self.current.hover_force(h); + } emit!(Refresh); true } pub fn enter(&mut self) -> bool { - let hovered = if let Some(h) = self.current.hovered() { + let hovered = if let Some(ref h) = self.current.hovered { h.clone() } else { return false; @@ -125,7 +143,8 @@ impl Tab { pub fn leave(&mut self) -> bool { let current = self .current - .hovered() + .hovered + .as_ref() .and_then(|h| h.path.parent()) .and_then(|p| if p == self.current.cwd { None } else { Some(p) }) .or_else(|| self.current.cwd.parent()); @@ -214,7 +233,8 @@ impl Tab { let _defer = Defer::new(|| Event::Stop(false, None).emit()); emit!(Stop(true)).await; - let rx = if global { external::fzf(FzfOpt {}) } else { external::zoxide(ZoxideOpt { cwd }) }?; + let rx = + if global { external::fzf(FzfOpt { cwd }) } else { external::zoxide(ZoxideOpt { cwd }) }?; if let Ok(target) = rx.await? { emit!(Cd(target)); diff --git a/src/core/manager/watcher.rs b/src/core/manager/watcher.rs index e7fd131f..2e62be7d 100644 --- a/src/core/manager/watcher.rs +++ b/src/core/manager/watcher.rs @@ -105,13 +105,15 @@ impl Watcher { } } - pub(super) fn watch(&mut self, to_watch: BTreeSet) { + pub(super) fn watch(&mut self, mut to_watch: BTreeSet) { let keys = self.watched.read().keys().cloned().collect::>(); for p in keys.difference(&to_watch) { self.watcher.unwatch(p).ok(); } - for p in to_watch.difference(&keys) { - self.watcher.watch(&p, RecursiveMode::NonRecursive).ok(); + for p in to_watch.clone().difference(&keys) { + if self.watcher.watch(&p, RecursiveMode::NonRecursive).is_err() { + to_watch.remove(p); + } } let mut todo = Vec::new(); diff --git a/src/ui/app.rs b/src/ui/app.rs index ffcc83a0..49d7a12d 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -74,7 +74,7 @@ impl App { let manager = &mut self.cx.manager; match event { Event::Cd(path) => { - manager.active_mut().cd(path); + manager.active_mut().cd(path).await; } Event::Refresh => { manager.refresh(); diff --git a/src/ui/logs.rs b/src/ui/logs.rs index 50a55041..e611de25 100644 --- a/src/ui/logs.rs +++ b/src/ui/logs.rs @@ -14,7 +14,7 @@ impl Logs { let (handle, guard) = tracing_appender::non_blocking(appender); // let filter = EnvFilter::from_default_env(); - let subscriber = Registry::default().with(fmt::layer().compact().with_writer(handle)); + let subscriber = Registry::default().with(fmt::layer().pretty().with_writer(handle)); tracing::subscriber::set_global_default(subscriber) .context("setting default subscriber failed")?; diff --git a/src/ui/manager/folder.rs b/src/ui/manager/folder.rs index 22e84961..609d142a 100644 --- a/src/ui/manager/folder.rs +++ b/src/ui/manager/folder.rs @@ -32,35 +32,35 @@ impl<'a> Widget for Folder<'a> { .folder .paginate() .iter() - .enumerate() - .map(|(i, (_, v))| { + .map(|(k, v)| { let icon = THEME .icons .iter() - .find(|x| x.name.match_path(&v.path, Some(v.meta.is_dir()))) + .find(|x| x.name.match_path(k, Some(v.meta.is_dir()))) .map(|x| x.display.as_ref()) .unwrap_or(""); - let name = readable_path(&v.path, &self.folder.cwd); + let name = readable_path(k, &self.folder.cwd); let item = ListItem::new(if v.is_selected { format!("> {} {}", icon, name) } else { format!("{} {}", icon, name) }); + let hovered = matches!(self.folder.hovered, Some(ref h) if h.path == *k); let mut style = Style::default(); if self.is_selection { - if i == self.folder.rel_cursor() { + if hovered { style = style.fg(Color::Black).bg(Color::Red); } else if v.is_selected { style = style.fg(Color::Red); } } else if self.is_preview { - if i == self.folder.rel_cursor() { + if hovered { style = style.add_modifier(Modifier::UNDERLINED) } } else { - if i == self.folder.rel_cursor() { + if hovered { style = style.fg(Color::Black).bg(Color::Yellow); } else if v.is_selected { style = style.fg(Color::Red);