mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
..
This commit is contained in:
parent
4a72dea37e
commit
eac62492aa
10 changed files with 196 additions and 117 deletions
|
|
@ -1 +1 @@
|
|||
{"version":"0.2","words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags"],"language":"en","flagWords":[]}
|
||||
{"language":"en","flagWords":[],"version":"0.2","words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE"]}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,31 @@ use yazi_shared::{expand_path, Debounce, InputError, Url};
|
|||
|
||||
use crate::{emit, input::InputOpt, tab::Tab};
|
||||
|
||||
pub struct Opt {
|
||||
target: Url,
|
||||
interactive: bool,
|
||||
}
|
||||
|
||||
impl From<&Exec> for Opt {
|
||||
fn from(e: &Exec) -> Self {
|
||||
Self {
|
||||
target: e.args.first().map(Url::from).unwrap_or_default(),
|
||||
interactive: e.named.contains_key("interactive"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<Url> for Opt {
|
||||
fn from(target: Url) -> Self { Self { target, interactive: false } }
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
pub fn cd(&mut self, target: Url) -> bool {
|
||||
if self.current.cwd == target {
|
||||
pub fn cd(&mut self, opt: impl Into<Opt>) -> bool {
|
||||
let opt = opt.into() as Opt;
|
||||
if opt.interactive {
|
||||
return self.cd_interactive(opt);
|
||||
}
|
||||
|
||||
if self.current.cwd == opt.target {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -19,30 +41,34 @@ impl Tab {
|
|||
}
|
||||
|
||||
// Current
|
||||
let rep = self.history_new(&target);
|
||||
let rep = self.history_new(&opt.target);
|
||||
let rep = mem::replace(&mut self.current, rep);
|
||||
if rep.cwd.is_regular() {
|
||||
self.history.insert(rep.cwd.clone(), rep);
|
||||
}
|
||||
|
||||
// Parent
|
||||
if let Some(parent) = target.parent_url() {
|
||||
if let Some(parent) = opt.target.parent_url() {
|
||||
self.parent = Some(self.history_new(&parent));
|
||||
}
|
||||
|
||||
// Backstack
|
||||
if target.is_regular() {
|
||||
self.backstack.push(target.clone());
|
||||
if opt.target.is_regular() {
|
||||
self.backstack.push(opt.target.clone());
|
||||
}
|
||||
|
||||
emit!(Refresh);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn cd_interactive(&mut self, target: Url) -> bool {
|
||||
pub fn cd_interactive(&mut self, opt: impl Into<Opt>) -> bool {
|
||||
let opt = opt.into() as Opt;
|
||||
|
||||
tokio::spawn(async move {
|
||||
let rx = emit!(Input(
|
||||
InputOpt::top("Change directory:").with_value(target.to_string_lossy()).with_completion()
|
||||
InputOpt::top("Change directory:")
|
||||
.with_value(opt.target.to_string_lossy())
|
||||
.with_completion()
|
||||
));
|
||||
|
||||
let rx = Debounce::new(UnboundedReceiverStream::new(rx), Duration::from_millis(50));
|
||||
|
|
|
|||
|
|
@ -39,13 +39,13 @@ impl Tab {
|
|||
let _defer = Defer::new(|| Event::Stop(false, None).emit());
|
||||
emit!(Stop(true)).await;
|
||||
|
||||
let rx = if opt.type_ == OptType::Fzf {
|
||||
external::fzf(FzfOpt { cwd })
|
||||
let url = if opt.type_ == OptType::Fzf {
|
||||
external::fzf(FzfOpt { cwd }).await
|
||||
} else {
|
||||
external::zoxide(ZoxideOpt { cwd })
|
||||
external::zoxide(ZoxideOpt { cwd }).await
|
||||
}?;
|
||||
|
||||
let op = if global && !ends_with_slash(&url) { "reveal" } else { "cd" };
|
||||
let op = if opt.type_ == OptType::Fzf && !ends_with_slash(&url) { "reveal" } else { "cd" };
|
||||
emit!(Call(Exec::call(op, vec![url.to_string()]).vec(), KeymapLayer::Manager));
|
||||
Ok::<(), anyhow::Error>(())
|
||||
});
|
||||
|
|
|
|||
36
yazi-core/src/tasks/commands/arrow.rs
Normal file
36
yazi-core/src/tasks/commands/arrow.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use yazi_config::keymap::Exec;
|
||||
|
||||
use crate::tasks::Tasks;
|
||||
|
||||
pub struct Opt {
|
||||
step: isize,
|
||||
}
|
||||
|
||||
impl From<&Exec> for Opt {
|
||||
fn from(e: &Exec) -> Self {
|
||||
Self { step: e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Tasks {
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
fn next(&mut self) -> bool {
|
||||
let limit = Self::limit().min(self.len());
|
||||
|
||||
let old = self.cursor;
|
||||
self.cursor = limit.saturating_sub(1).min(self.cursor + 1);
|
||||
|
||||
old != self.cursor
|
||||
}
|
||||
|
||||
fn prev(&mut self) -> bool {
|
||||
let old = self.cursor;
|
||||
self.cursor = self.cursor.saturating_sub(1);
|
||||
old != self.cursor
|
||||
}
|
||||
|
||||
pub fn arrow(&mut self, opt: impl Into<Opt>) -> bool {
|
||||
let opt = opt.into() as Opt;
|
||||
if opt.step > 0 { self.next() } else { self.prev() }
|
||||
}
|
||||
}
|
||||
22
yazi-core/src/tasks/commands/cancel.rs
Normal file
22
yazi-core/src/tasks/commands/cancel.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use yazi_config::keymap::Exec;
|
||||
|
||||
use crate::tasks::Tasks;
|
||||
|
||||
pub struct Opt;
|
||||
|
||||
impl From<&Exec> for Opt {
|
||||
fn from(_: &Exec) -> Self { Self }
|
||||
}
|
||||
|
||||
impl Tasks {
|
||||
pub fn cancel(&mut self, _: impl Into<Opt>) -> bool {
|
||||
let id = self.scheduler.running.read().get_id(self.cursor);
|
||||
if id.map(|id| self.scheduler.cancel(id)) != Some(true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let len = self.scheduler.running.read().len();
|
||||
self.cursor = self.cursor.min(len.saturating_sub(1));
|
||||
true
|
||||
}
|
||||
}
|
||||
77
yazi-core/src/tasks/commands/inspect.rs
Normal file
77
yazi-core/src/tasks/commands/inspect.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
use std::io::{stdout, Write};
|
||||
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time};
|
||||
use yazi_config::keymap::Exec;
|
||||
use yazi_shared::{Defer, Term};
|
||||
|
||||
use crate::{emit, tasks::Tasks, Event, BLOCKER};
|
||||
|
||||
pub struct Opt;
|
||||
|
||||
impl From<&Exec> for Opt {
|
||||
fn from(_: &Exec) -> Self { Self }
|
||||
}
|
||||
|
||||
impl Tasks {
|
||||
pub fn inspect(&self, _: impl Into<Opt>) -> bool {
|
||||
let Some(id) = self.scheduler.running.read().get_id(self.cursor) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let scheduler = self.scheduler.clone();
|
||||
tokio::spawn(async move {
|
||||
let _guard = BLOCKER.acquire().await.unwrap();
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
|
||||
let buffered = {
|
||||
let mut running = scheduler.running.write();
|
||||
let Some(task) = running.get_mut(id) else { return };
|
||||
|
||||
task.logger = Some(tx);
|
||||
task.logs.clone()
|
||||
};
|
||||
|
||||
emit!(Stop(true)).await;
|
||||
let _defer = Defer::new(|| {
|
||||
disable_raw_mode().ok();
|
||||
Event::Stop(false, None).emit();
|
||||
});
|
||||
|
||||
Term::clear(&mut stdout()).ok();
|
||||
stdout().write_all(buffered.as_bytes()).ok();
|
||||
enable_raw_mode().ok();
|
||||
|
||||
let mut stdin = stdin();
|
||||
let mut quit = [0; 10];
|
||||
loop {
|
||||
select! {
|
||||
Some(line) = rx.recv() => {
|
||||
let mut stdout = stdout().lock();
|
||||
stdout.write_all(line.as_bytes()).ok();
|
||||
stdout.write_all(b"\r\n").ok();
|
||||
}
|
||||
_ = time::sleep(time::Duration::from_millis(100)) => {
|
||||
if scheduler.running.read().get(id).is_none() {
|
||||
stdout().write_all(b"Task finished, press `q` to quit\r\n").ok();
|
||||
break;
|
||||
}
|
||||
},
|
||||
Ok(_) = stdin.read(&mut quit) => {
|
||||
if quit[0] == b'q' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(task) = scheduler.running.write().get_mut(id) {
|
||||
task.logger = None;
|
||||
}
|
||||
while quit[0] != b'q' {
|
||||
stdin.read(&mut quit).await.ok();
|
||||
}
|
||||
});
|
||||
false
|
||||
}
|
||||
}
|
||||
3
yazi-core/src/tasks/commands/mod.rs
Normal file
3
yazi-core/src/tasks/commands/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
mod arrow;
|
||||
mod cancel;
|
||||
mod inspect;
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
mod commands;
|
||||
mod running;
|
||||
mod scheduler;
|
||||
mod task;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, io::{stdout, Write}, path::Path, sync::Arc};
|
||||
use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, path::Path, sync::Arc};
|
||||
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use serde::Serialize;
|
||||
use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time};
|
||||
use tracing::debug;
|
||||
use yazi_config::{manager::SortBy, open::Opener, OPEN};
|
||||
use yazi_shared::{Defer, MimeKind, Term, Url};
|
||||
use yazi_shared::{MimeKind, Term, Url};
|
||||
|
||||
use super::{running::Running, task::TaskSummary, Scheduler, TASKS_PADDING, TASKS_PERCENT};
|
||||
use crate::{emit, files::{File, Files}, input::InputOpt, Event, BLOCKER};
|
||||
use crate::{emit, files::{File, Files}, input::InputOpt};
|
||||
|
||||
pub struct Tasks {
|
||||
scheduler: Arc<Scheduler>,
|
||||
pub(super) scheduler: Arc<Scheduler>,
|
||||
|
||||
pub visible: bool,
|
||||
pub cursor: usize,
|
||||
|
|
@ -39,99 +37,11 @@ impl Tasks {
|
|||
true
|
||||
}
|
||||
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn next(&mut self) -> bool {
|
||||
let limit = Self::limit().min(self.len());
|
||||
|
||||
let old = self.cursor;
|
||||
self.cursor = limit.saturating_sub(1).min(self.cursor + 1);
|
||||
|
||||
old != self.cursor
|
||||
}
|
||||
|
||||
pub fn prev(&mut self) -> bool {
|
||||
let old = self.cursor;
|
||||
self.cursor = self.cursor.saturating_sub(1);
|
||||
old != self.cursor
|
||||
}
|
||||
|
||||
pub fn paginate(&self) -> Vec<TaskSummary> {
|
||||
let running = self.scheduler.running.read();
|
||||
running.values().take(Self::limit()).map(Into::into).collect()
|
||||
}
|
||||
|
||||
pub fn inspect(&self) -> bool {
|
||||
let Some(id) = self.scheduler.running.read().get_id(self.cursor) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let scheduler = self.scheduler.clone();
|
||||
tokio::spawn(async move {
|
||||
let _guard = BLOCKER.acquire().await.unwrap();
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
|
||||
let buffered = {
|
||||
let mut running = scheduler.running.write();
|
||||
let Some(task) = running.get_mut(id) else { return };
|
||||
|
||||
task.logger = Some(tx);
|
||||
task.logs.clone()
|
||||
};
|
||||
|
||||
emit!(Stop(true)).await;
|
||||
let _defer = Defer::new(|| {
|
||||
disable_raw_mode().ok();
|
||||
Event::Stop(false, None).emit();
|
||||
});
|
||||
|
||||
Term::clear(&mut stdout()).ok();
|
||||
stdout().write_all(buffered.as_bytes()).ok();
|
||||
enable_raw_mode().ok();
|
||||
|
||||
let mut stdin = stdin();
|
||||
let mut quit = [0; 10];
|
||||
loop {
|
||||
select! {
|
||||
Some(line) = rx.recv() => {
|
||||
let mut stdout = stdout().lock();
|
||||
stdout.write_all(line.as_bytes()).ok();
|
||||
stdout.write_all(b"\r\n").ok();
|
||||
}
|
||||
_ = time::sleep(time::Duration::from_millis(100)) => {
|
||||
if scheduler.running.read().get(id).is_none() {
|
||||
stdout().write_all(b"Task finished, press `q` to quit\r\n").ok();
|
||||
break;
|
||||
}
|
||||
},
|
||||
Ok(_) = stdin.read(&mut quit) => {
|
||||
if quit[0] == b'q' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(task) = scheduler.running.write().get_mut(id) {
|
||||
task.logger = None;
|
||||
}
|
||||
while quit[0] != b'q' {
|
||||
stdin.read(&mut quit).await.ok();
|
||||
}
|
||||
});
|
||||
false
|
||||
}
|
||||
|
||||
pub fn cancel(&mut self) -> bool {
|
||||
let id = self.scheduler.running.read().get_id(self.cursor);
|
||||
if id.map(|id| self.scheduler.cancel(id)) != Some(true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let len = self.scheduler.running.read().len();
|
||||
self.cursor = self.cursor.min(len.saturating_sub(1));
|
||||
true
|
||||
}
|
||||
|
||||
pub fn file_open(&self, targets: &[(impl AsRef<Path>, impl AsRef<str>)]) -> bool {
|
||||
let mut openers = BTreeMap::new();
|
||||
for (path, mime) in targets {
|
||||
|
|
|
|||
|
|
@ -100,7 +100,8 @@ impl<'a> Executor<'a> {
|
|||
on!(ACTIVE, enter);
|
||||
on!(ACTIVE, back);
|
||||
on!(ACTIVE, forward);
|
||||
// on!(A, cd);
|
||||
on!(ACTIVE, cd);
|
||||
on!(ACTIVE, reveal);
|
||||
|
||||
// Selection
|
||||
on!(ACTIVE, select);
|
||||
|
|
@ -145,17 +146,20 @@ impl<'a> Executor<'a> {
|
|||
}
|
||||
|
||||
fn tasks(&mut self, exec: &Exec) -> bool {
|
||||
match exec.cmd.as_str() {
|
||||
"close" => self.cx.tasks.toggle(),
|
||||
|
||||
"arrow" => {
|
||||
let step = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or(0);
|
||||
if step > 0 { self.cx.tasks.next() } else { self.cx.tasks.prev() }
|
||||
macro_rules! on {
|
||||
($name:ident) => {
|
||||
if exec.cmd == stringify!($name) {
|
||||
return self.cx.tasks.$name(exec);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
"inspect" => self.cx.tasks.inspect(),
|
||||
"cancel" => self.cx.tasks.cancel(),
|
||||
on!(arrow);
|
||||
on!(inspect);
|
||||
on!(cancel);
|
||||
|
||||
match exec.cmd.as_str() {
|
||||
"close" => self.cx.tasks.toggle(),
|
||||
"help" => self.cx.help.toggle(KeymapLayer::Tasks),
|
||||
_ => false,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue