From 2bbe04fe5d4864a97eb3d8d4454d0b282aaf8304 Mon Sep 17 00:00:00 2001 From: sxyazi Date: Mon, 11 Sep 2023 00:33:32 +0800 Subject: [PATCH] .. --- Cargo.lock | 12 ++++---- app/src/manager/folder.rs | 2 +- core/src/manager/finder.rs | 49 +++++++++++++++++++++++++++--- core/src/manager/tab.rs | 21 ++++++++++--- shared/src/debounce.rs | 62 ++++++++++++++++++++++++++++++++++++++ shared/src/lib.rs | 2 ++ 6 files changed, 132 insertions(+), 16 deletions(-) create mode 100644 shared/src/debounce.rs diff --git a/Cargo.lock b/Cargo.lock index 79fe8430..30878463 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -177,9 +177,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.3" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "bincode" @@ -1548,9 +1548,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2" dependencies = [ "itoa", "ryu", @@ -1880,9 +1880,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0a3ab2091e52d7299a39d098e200114a972df0a7724add02a273aa9aada592" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" dependencies = [ "indexmap 2.0.0", "serde", diff --git a/app/src/manager/folder.rs b/app/src/manager/folder.rs index c335fca4..68bafe86 100644 --- a/app/src/manager/folder.rs +++ b/app/src/manager/folder.rs @@ -112,7 +112,7 @@ impl<'a> Widget for Folder<'a> { if let Some(idx) = active .finder() - .filter(|_| hovered && self.is_find) + .filter(|&f| hovered && self.is_find && f.has_matched()) .and_then(|finder| finder.matched_idx(f.url())) { let len = active.finder().unwrap().matched().len(); diff --git a/core/src/manager/finder.rs b/core/src/manager/finder.rs index 8a08b48b..01a6b85a 100644 --- a/core/src/manager/finder.rs +++ b/core/src/manager/finder.rs @@ -24,18 +24,54 @@ impl Finder { .take(cursor) .rev() .enumerate() - .find(|(_, f)| f.name().map_or(false, |n| self.is_match(n))) + .find(|(_, f)| f.name().map_or(false, |n| self.matches(n))) .map(|(i, _)| -(i as isize) - 1) } else { files .iter() .skip(cursor + 1) .enumerate() - .find(|(_, f)| f.name().map_or(false, |n| self.is_match(n))) + .find(|(_, f)| f.name().map_or(false, |n| self.matches(n))) .map(|(i, _)| i as isize + 1) } } + pub(super) fn ring(&self, files: &Files, cursor: usize, prev: bool) -> Option { + if prev { + files + .iter() + .take(cursor + 1) + .rev() + .enumerate() + .find(|(_, f)| f.name().map_or(false, |n| self.matches(n))) + .map(|(i, _)| -(i as isize)) + .or_else(|| { + files + .iter() + .skip(cursor + 1) + .enumerate() + .find(|(_, f)| f.name().map_or(false, |n| self.matches(n))) + .map(|(i, _)| i as isize + 1) + }) + } else { + files + .iter() + .skip(cursor) + .enumerate() + .find(|(_, f)| f.name().map_or(false, |n| self.matches(n))) + .map(|(i, _)| i as isize) + .or_else(|| { + files + .iter() + .take(cursor) + .rev() + .enumerate() + .find(|(_, f)| f.name().map_or(false, |n| self.matches(n))) + .map(|(i, _)| -(i as isize) - 1) + }) + } + } + pub(super) fn catchup(&mut self, files: &Files) -> bool { if self.version == files.version() { return false; @@ -44,7 +80,7 @@ impl Finder { let mut i = 0u8; for file in files.iter() { - if file.name().map(|n| self.is_match(n)) != Some(true) { + if file.name().map(|n| self.matches(n)) != Some(true) { continue; } @@ -61,7 +97,7 @@ impl Finder { } #[inline] - fn is_match(&self, name: &OsStr) -> bool { + fn matches(&self, name: &OsStr) -> bool { #[cfg(target_os = "windows")] { self.query.is_match(name.to_string_lossy().as_bytes()) @@ -78,12 +114,15 @@ impl Finder { #[inline] pub fn matched(&self) -> &BTreeMap { &self.matched } + #[inline] + pub fn has_matched(&self) -> bool { !self.matched.is_empty() } + #[inline] pub fn matched_idx(&self, url: &Url) -> Option { if let Some((_, &idx)) = self.matched.iter().find(|(u, _)| *u == url) { return Some(idx); } - if url.file_name().map(|n| self.is_match(n)) == Some(true) { + if url.file_name().map(|n| self.matches(n)) == Some(true) { return Some(100); } None diff --git a/core/src/manager/tab.rs b/core/src/manager/tab.rs index b2c5982d..a41ab3c5 100644 --- a/core/src/manager/tab.rs +++ b/core/src/manager/tab.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, collections::{BTreeMap, BTreeSet}, ffi::{OsStr, OsString} use anyhow::{bail, Error, Result}; use config::{keymap::{Exec, KeymapLayer}, open::Opener}; -use shared::{Defer, Url}; +use shared::{Debounce, Defer, InputError, Url}; use tokio::{pin, task::JoinHandle}; use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; @@ -241,12 +241,25 @@ impl Tab { pub fn find(&mut self, query: Option<&str>, prev: bool) -> bool { if let Some(query) = query { - self.finder = Finder::new(query).ok(); - return self.find_arrow(prev); + let Ok(finder) = Finder::new(query) else { + return false; + }; + + if let Some(step) = finder.ring(&self.current.files, self.current.cursor(), prev) { + self.arrow(step); + } + + self.finder = Some(finder); + return true; } tokio::spawn(async move { - if let Some(Ok(s)) = emit!(Input(InputOpt::top("Find:"))).recv().await { + let rx = emit!(Input(InputOpt::top("Find:").with_realtime())); + + let rx = Debounce::new(UnboundedReceiverStream::new(rx), Duration::from_millis(50)); + pin!(rx); + + while let Some(Ok(s)) | Some(Err(InputError::Typed(s))) = rx.next().await { emit!(Call( Exec::call("find", vec![s]).with_bool("previous", prev).vec(), KeymapLayer::Manager diff --git a/shared/src/debounce.rs b/shared/src/debounce.rs new file mode 100644 index 00000000..dce640a2 --- /dev/null +++ b/shared/src/debounce.rs @@ -0,0 +1,62 @@ +use std::{pin::Pin, task::{Context, Poll}, time::Duration}; + +use futures::{FutureExt, Stream, StreamExt}; +use tokio::time::{sleep, Instant, Sleep}; + +pub struct Debounce +where + S: Stream, +{ + stream: S, + interval: Duration, + + sleep: Sleep, + last: Option, +} + +impl Debounce +where + S: Stream + Unpin, +{ + pub fn new(stream: S, interval: Duration) -> Debounce { + Self { stream, interval, sleep: sleep(Duration::ZERO), last: None } + } +} + +impl Stream for Debounce +where + S: Stream + Unpin, +{ + type Item = S::Item; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let (mut stream, interval, mut sleep, last) = unsafe { + let me = self.get_unchecked_mut(); + (Pin::new(&mut me.stream), me.interval, Pin::new_unchecked(&mut me.sleep), &mut me.last) + }; + + if sleep.poll_unpin(cx).is_ready() { + if let Some(last) = last.take() { + return Poll::Ready(Some(last)); + } + } + + while let Poll::Ready(next) = stream.poll_next_unpin(cx) { + match next { + Some(next) => { + *last = Some(next); + } + None if last.is_none() => { + return Poll::Ready(None); + } + None => { + sleep.reset(Instant::now()); + return Poll::Pending; + } + } + } + + sleep.reset(Instant::now() + interval); + Poll::Pending + } +} diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 3b12b26f..189b2c4f 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -1,6 +1,7 @@ #![allow(clippy::option_map_unit_fn)] mod chars; +mod debounce; mod defer; mod errors; mod fns; @@ -13,6 +14,7 @@ mod time; mod url; pub use chars::*; +pub use debounce::*; pub use defer::*; pub use errors::*; pub use fns::*;