mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
..
This commit is contained in:
parent
4d01119981
commit
2bbe04fe5d
6 changed files with 132 additions and 16 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
|
@ -177,9 +177,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.21.3"
|
version = "0.21.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53"
|
checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bincode"
|
name = "bincode"
|
||||||
|
|
@ -1548,9 +1548,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.105"
|
version = "1.0.106"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
|
checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
|
@ -1880,9 +1880,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.7.7"
|
version = "0.7.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "de0a3ab2091e52d7299a39d098e200114a972df0a7724add02a273aa9aada592"
|
checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.0.0",
|
"indexmap 2.0.0",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ impl<'a> Widget for Folder<'a> {
|
||||||
|
|
||||||
if let Some(idx) = active
|
if let Some(idx) = active
|
||||||
.finder()
|
.finder()
|
||||||
.filter(|_| hovered && self.is_find)
|
.filter(|&f| hovered && self.is_find && f.has_matched())
|
||||||
.and_then(|finder| finder.matched_idx(f.url()))
|
.and_then(|finder| finder.matched_idx(f.url()))
|
||||||
{
|
{
|
||||||
let len = active.finder().unwrap().matched().len();
|
let len = active.finder().unwrap().matched().len();
|
||||||
|
|
|
||||||
|
|
@ -24,18 +24,54 @@ impl Finder {
|
||||||
.take(cursor)
|
.take(cursor)
|
||||||
.rev()
|
.rev()
|
||||||
.enumerate()
|
.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)
|
.map(|(i, _)| -(i as isize) - 1)
|
||||||
} else {
|
} else {
|
||||||
files
|
files
|
||||||
.iter()
|
.iter()
|
||||||
.skip(cursor + 1)
|
.skip(cursor + 1)
|
||||||
.enumerate()
|
.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)
|
.map(|(i, _)| i as isize + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn ring(&self, files: &Files, cursor: usize, prev: bool) -> Option<isize> {
|
||||||
|
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 {
|
pub(super) fn catchup(&mut self, files: &Files) -> bool {
|
||||||
if self.version == files.version() {
|
if self.version == files.version() {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -44,7 +80,7 @@ impl Finder {
|
||||||
|
|
||||||
let mut i = 0u8;
|
let mut i = 0u8;
|
||||||
for file in files.iter() {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,7 +97,7 @@ impl Finder {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_match(&self, name: &OsStr) -> bool {
|
fn matches(&self, name: &OsStr) -> bool {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
self.query.is_match(name.to_string_lossy().as_bytes())
|
self.query.is_match(name.to_string_lossy().as_bytes())
|
||||||
|
|
@ -78,12 +114,15 @@ impl Finder {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn matched(&self) -> &BTreeMap<Url, u8> { &self.matched }
|
pub fn matched(&self) -> &BTreeMap<Url, u8> { &self.matched }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn has_matched(&self) -> bool { !self.matched.is_empty() }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn matched_idx(&self, url: &Url) -> Option<u8> {
|
pub fn matched_idx(&self, url: &Url) -> Option<u8> {
|
||||||
if let Some((_, &idx)) = self.matched.iter().find(|(u, _)| *u == url) {
|
if let Some((_, &idx)) = self.matched.iter().find(|(u, _)| *u == url) {
|
||||||
return Some(idx);
|
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);
|
return Some(100);
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::{borrow::Cow, collections::{BTreeMap, BTreeSet}, ffi::{OsStr, OsString}
|
||||||
|
|
||||||
use anyhow::{bail, Error, Result};
|
use anyhow::{bail, Error, Result};
|
||||||
use config::{keymap::{Exec, KeymapLayer}, open::Opener};
|
use config::{keymap::{Exec, KeymapLayer}, open::Opener};
|
||||||
use shared::{Defer, Url};
|
use shared::{Debounce, Defer, InputError, Url};
|
||||||
use tokio::{pin, task::JoinHandle};
|
use tokio::{pin, task::JoinHandle};
|
||||||
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
||||||
|
|
||||||
|
|
@ -241,12 +241,25 @@ impl Tab {
|
||||||
|
|
||||||
pub fn find(&mut self, query: Option<&str>, prev: bool) -> bool {
|
pub fn find(&mut self, query: Option<&str>, prev: bool) -> bool {
|
||||||
if let Some(query) = query {
|
if let Some(query) = query {
|
||||||
self.finder = Finder::new(query).ok();
|
let Ok(finder) = Finder::new(query) else {
|
||||||
return self.find_arrow(prev);
|
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 {
|
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(
|
emit!(Call(
|
||||||
Exec::call("find", vec![s]).with_bool("previous", prev).vec(),
|
Exec::call("find", vec![s]).with_bool("previous", prev).vec(),
|
||||||
KeymapLayer::Manager
|
KeymapLayer::Manager
|
||||||
|
|
|
||||||
62
shared/src/debounce.rs
Normal file
62
shared/src/debounce.rs
Normal file
|
|
@ -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<S>
|
||||||
|
where
|
||||||
|
S: Stream,
|
||||||
|
{
|
||||||
|
stream: S,
|
||||||
|
interval: Duration,
|
||||||
|
|
||||||
|
sleep: Sleep,
|
||||||
|
last: Option<S::Item>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Debounce<S>
|
||||||
|
where
|
||||||
|
S: Stream + Unpin,
|
||||||
|
{
|
||||||
|
pub fn new(stream: S, interval: Duration) -> Debounce<S> {
|
||||||
|
Self { stream, interval, sleep: sleep(Duration::ZERO), last: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Stream for Debounce<S>
|
||||||
|
where
|
||||||
|
S: Stream + Unpin,
|
||||||
|
{
|
||||||
|
type Item = S::Item;
|
||||||
|
|
||||||
|
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#![allow(clippy::option_map_unit_fn)]
|
#![allow(clippy::option_map_unit_fn)]
|
||||||
|
|
||||||
mod chars;
|
mod chars;
|
||||||
|
mod debounce;
|
||||||
mod defer;
|
mod defer;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod fns;
|
mod fns;
|
||||||
|
|
@ -13,6 +14,7 @@ mod time;
|
||||||
mod url;
|
mod url;
|
||||||
|
|
||||||
pub use chars::*;
|
pub use chars::*;
|
||||||
|
pub use debounce::*;
|
||||||
pub use defer::*;
|
pub use defer::*;
|
||||||
pub use errors::*;
|
pub use errors::*;
|
||||||
pub use fns::*;
|
pub use fns::*;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue