mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
Done!
This commit is contained in:
parent
146606e034
commit
774f382e2d
13 changed files with 98 additions and 47 deletions
|
|
@ -10,7 +10,7 @@ impl From<&Exec> for Opt {
|
||||||
|
|
||||||
impl Completion {
|
impl Completion {
|
||||||
fn next(&mut self, step: usize) -> bool {
|
fn next(&mut self, step: usize) -> bool {
|
||||||
let len = self.items.len();
|
let len = self.cands.len();
|
||||||
if len == 0 {
|
if len == 0 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,7 @@ impl Completion {
|
||||||
let submit = opt.into().0;
|
let submit = opt.into().0;
|
||||||
if submit {
|
if submit {
|
||||||
emit!(Call(
|
emit!(Call(
|
||||||
Exec::call("complete", vec![self.items[self.cursor].to_owned()])
|
Exec::call("complete", vec![self.selected().into()]).with("ticket", self.ticket).vec(),
|
||||||
.with_bool("apply", true)
|
|
||||||
.with("ticket", self.ticket)
|
|
||||||
.vec(),
|
|
||||||
KeymapLayer::Input
|
KeymapLayer::Input
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,22 @@
|
||||||
|
use std::ops::ControlFlow;
|
||||||
|
|
||||||
use yazi_config::keymap::Exec;
|
use yazi_config::keymap::Exec;
|
||||||
|
|
||||||
use crate::completion::Completion;
|
use crate::completion::Completion;
|
||||||
|
|
||||||
pub struct Opt<'a> {
|
pub struct Opt<'a> {
|
||||||
cands: &'a Vec<String>,
|
cache: &'a Vec<String>,
|
||||||
|
cache_name: &'a str,
|
||||||
|
word: &'a str,
|
||||||
ticket: usize,
|
ticket: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a Exec> for Opt<'a> {
|
impl<'a> From<&'a Exec> for Opt<'a> {
|
||||||
fn from(e: &'a Exec) -> Self {
|
fn from(e: &'a Exec) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cands: &e.args,
|
cache: &e.args,
|
||||||
|
cache_name: e.named.get("cache-name").map(|n| n.as_str()).unwrap_or_default(),
|
||||||
|
word: e.named.get("word").map(|w| w.as_str()).unwrap_or_default(),
|
||||||
ticket: e.named.get("ticket").and_then(|v| v.parse().ok()).unwrap_or(0),
|
ticket: e.named.get("ticket").and_then(|v| v.parse().ok()).unwrap_or(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -23,10 +29,33 @@ impl Completion {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !opt.cache.is_empty() {
|
||||||
|
self.caches.insert(opt.cache_name.to_owned(), opt.cache.clone());
|
||||||
|
}
|
||||||
|
let Some(cache) = self.caches.get(opt.cache_name) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let flow = cache.iter().try_fold(Vec::with_capacity(30), |mut v, s| {
|
||||||
|
if s.contains(opt.word) && s != opt.word {
|
||||||
|
v.push(s.to_owned());
|
||||||
|
if v.len() >= 30 {
|
||||||
|
return ControlFlow::Break(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ControlFlow::Continue(v)
|
||||||
|
});
|
||||||
|
|
||||||
self.close(false);
|
self.close(false);
|
||||||
self.items = opt.cands.clone();
|
self.cands = match flow {
|
||||||
|
ControlFlow::Continue(v) => v,
|
||||||
|
ControlFlow::Break(v) => v,
|
||||||
|
};
|
||||||
self.ticket = opt.ticket;
|
self.ticket = opt.ticket;
|
||||||
|
|
||||||
|
if !self.cands.is_empty() {
|
||||||
self.visible = true;
|
self.visible = true;
|
||||||
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,15 @@ use yazi_config::keymap::{Exec, KeymapLayer};
|
||||||
use crate::{completion::Completion, emit};
|
use crate::{completion::Completion, emit};
|
||||||
|
|
||||||
pub struct Opt<'a> {
|
pub struct Opt<'a> {
|
||||||
word: &'a str,
|
before: &'a str,
|
||||||
ticket: usize,
|
ticket: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a Exec> for Opt<'a> {
|
impl<'a> From<&'a Exec> for Opt<'a> {
|
||||||
fn from(e: &'a Exec) -> Self {
|
fn from(e: &'a Exec) -> Self {
|
||||||
Self {
|
Self {
|
||||||
word: e.args.first().map(|w| w.as_str()).unwrap_or_default(),
|
before: e.named.get("before").map(|s| s.as_str()).unwrap_or_default(),
|
||||||
ticket: e.named.get("ticket").and_then(|v| v.parse().ok()).unwrap_or(0),
|
ticket: e.named.get("ticket").and_then(|s| s.parse().ok()).unwrap_or(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -27,28 +27,43 @@ impl Completion {
|
||||||
self.close(false);
|
self.close(false);
|
||||||
self.ticket = opt.ticket;
|
self.ticket = opt.ticket;
|
||||||
|
|
||||||
let word = opt.word.to_owned();
|
let (parent, child) = opt.before.rsplit_once('/').unwrap_or((".", opt.before));
|
||||||
|
if self.caches.contains_key(parent) {
|
||||||
|
return self.show(
|
||||||
|
&Exec::call("show", vec![])
|
||||||
|
.with("cache-name", parent)
|
||||||
|
.with("word", child)
|
||||||
|
.with("ticket", opt.ticket),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let ticket = self.ticket;
|
let ticket = self.ticket;
|
||||||
|
let (parent, child) = (parent.to_owned(), child.to_owned());
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let (parent, child) = word.rsplit_once('/').unwrap_or((".", word.as_str()));
|
let mut dir = fs::read_dir(&parent).await?;
|
||||||
|
let mut cache = Vec::new();
|
||||||
let mut dir = fs::read_dir(parent).await?;
|
|
||||||
let mut cands = Vec::new();
|
|
||||||
while let Ok(Some(f)) = dir.next_entry().await {
|
while let Ok(Some(f)) = dir.next_entry().await {
|
||||||
let name = f.file_name().to_string_lossy().into_owned();
|
let Ok(meta) = f.metadata().await else {
|
||||||
if !name.starts_with(child) {
|
|
||||||
continue;
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let sep = if !meta.is_dir() {
|
||||||
|
""
|
||||||
|
} else if cfg!(windows) {
|
||||||
|
"\\"
|
||||||
|
} else {
|
||||||
|
"/"
|
||||||
|
};
|
||||||
|
cache.push(format!("{}{sep}", f.file_name().to_string_lossy()));
|
||||||
}
|
}
|
||||||
|
|
||||||
cands.push(name);
|
if !cache.is_empty() {
|
||||||
if cands.len() >= 20 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cands.is_empty() {
|
|
||||||
emit!(Call(
|
emit!(Call(
|
||||||
Exec::call("show", cands).with("ticket", ticket).vec(),
|
Exec::call("show", cache)
|
||||||
|
.with("cache-name", parent)
|
||||||
|
.with("word", child)
|
||||||
|
.with("ticket", ticket)
|
||||||
|
.vec(),
|
||||||
KeymapLayer::Completion
|
KeymapLayer::Completion
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,26 @@
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Completion {
|
pub struct Completion {
|
||||||
pub(super) items: Vec<String>,
|
pub(super) caches: BTreeMap<String, Vec<String>>,
|
||||||
|
pub(super) cands: Vec<String>,
|
||||||
pub(super) offset: usize,
|
pub(super) offset: usize,
|
||||||
pub cursor: usize,
|
pub cursor: usize,
|
||||||
|
|
||||||
pub ticket: usize,
|
pub(super) ticket: usize,
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Completion {
|
impl Completion {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn window(&self) -> &[String] {
|
pub fn window(&self) -> &[String] {
|
||||||
let end = (self.offset + self.limit()).min(self.items.len());
|
let end = (self.offset + self.limit()).min(self.cands.len());
|
||||||
&self.items[self.offset..end]
|
&self.cands[self.offset..end]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn limit(&self) -> usize { self.items.len().min(5) }
|
pub fn limit(&self) -> usize { self.cands.len().min(5) }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn selected(&self) -> &String { &self.cands[self.cursor] }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ impl Input {
|
||||||
snap.value = new;
|
snap.value = new;
|
||||||
|
|
||||||
self.move_(delta);
|
self.move_(delta);
|
||||||
self.flush_value(true);
|
self.flush_value();
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -213,7 +213,7 @@ impl Input {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.move_(s.chars().count() as isize);
|
self.move_(s.chars().count() as isize);
|
||||||
self.flush_value(false);
|
self.flush_value();
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -226,7 +226,7 @@ impl Input {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.move_(-1);
|
self.move_(-1);
|
||||||
self.flush_value(false);
|
self.flush_value();
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -320,13 +320,13 @@ impl Input {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if !matches!(old.op, InputOp::None | InputOp::Select(_)) {
|
if !matches!(old.op, InputOp::None | InputOp::Select(_)) {
|
||||||
self.snaps.tag().then(|| self.flush_value(false));
|
self.snaps.tag().then(|| self.flush_value());
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn flush_value(&mut self, no_complete: bool) {
|
pub(super) fn flush_value(&mut self) {
|
||||||
self.ticket = self.ticket.wrapping_add(1);
|
self.ticket = self.ticket.wrapping_add(1);
|
||||||
|
|
||||||
if self.realtime {
|
if self.realtime {
|
||||||
|
|
@ -334,7 +334,7 @@ impl Input {
|
||||||
self.callback.as_ref().unwrap().send(Err(InputError::Typed(value))).ok();
|
self.callback.as_ref().unwrap().send(Err(InputError::Typed(value))).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.completion && !no_complete {
|
if self.completion {
|
||||||
let before = self.partition()[0].to_owned();
|
let before = self.partition()[0].to_owned();
|
||||||
self.callback.as_ref().unwrap().send(Err(InputError::Completed(before, self.ticket))).ok();
|
self.callback.as_ref().unwrap().send(Err(InputError::Completed(before, self.ticket))).ok();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,5 @@ pub use input::*;
|
||||||
pub use mode::*;
|
pub use mode::*;
|
||||||
use op::*;
|
use op::*;
|
||||||
pub use option::*;
|
pub use option::*;
|
||||||
pub use shell::*;
|
|
||||||
use snap::*;
|
use snap::*;
|
||||||
use snaps::*;
|
use snaps::*;
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ impl Tab {
|
||||||
}
|
}
|
||||||
Err(InputError::Completed(before, ticket)) => {
|
Err(InputError::Completed(before, ticket)) => {
|
||||||
emit!(Call(
|
emit!(Call(
|
||||||
Exec::call("complete", vec![before]).with("ticket", ticket).vec(),
|
Exec::call("complete", vec![]).with("before", before).with("ticket", ticket).vec(),
|
||||||
KeymapLayer::Input
|
KeymapLayer::Input
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,13 @@ impl<'a> Widget for Completion<'a> {
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, x)| {
|
.map(|(i, x)| {
|
||||||
let mut item = ListItem::new(format!(" {} {}", THEME.completion.icon_file, x));
|
let icon = if x.ends_with('/') || x.ends_with('\\') {
|
||||||
|
&THEME.completion.icon_folder
|
||||||
|
} else {
|
||||||
|
&THEME.completion.icon_file
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut item = ListItem::new(format!(" {} {}", icon, x));
|
||||||
if i == self.cx.completion.cursor {
|
if i == self.cx.completion.cursor {
|
||||||
item = item.style(THEME.completion.active.into());
|
item = item.style(THEME.completion.active.into());
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -268,10 +268,10 @@ impl<'a> Executor<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
"complete" => {
|
"complete" => {
|
||||||
return if exec.named.contains_key("apply") {
|
return if exec.args.is_empty() {
|
||||||
self.cx.input.complete(exec)
|
|
||||||
} else {
|
|
||||||
self.cx.completion.trigger(exec)
|
self.cx.completion.trigger(exec)
|
||||||
|
} else {
|
||||||
|
self.cx.input.complete(exec)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ mod tasks;
|
||||||
|
|
||||||
pub use active::*;
|
pub use active::*;
|
||||||
pub use bindings::*;
|
pub use bindings::*;
|
||||||
|
#[allow(unused_imports)]
|
||||||
pub use files::*;
|
pub use files::*;
|
||||||
pub use shared::*;
|
pub use shared::*;
|
||||||
pub use tabs::*;
|
pub use tabs::*;
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,4 @@
|
||||||
mod cursor;
|
mod cursor;
|
||||||
mod term;
|
mod term;
|
||||||
|
|
||||||
pub use cursor::*;
|
|
||||||
pub use term::*;
|
pub use term::*;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue