mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 14:51:03 +00:00
refactor: switch to actor model (#2986)
This commit is contained in:
parent
6366e46c23
commit
60a2382087
337 changed files with 4166 additions and 3053 deletions
77
Cargo.lock
generated
77
Cargo.lock
generated
|
|
@ -340,9 +340,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
|||
|
||||
[[package]]
|
||||
name = "castaway"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5"
|
||||
checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
|
@ -528,9 +528,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
|||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
|
@ -2207,9 +2207,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
|||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.51"
|
||||
version = "0.8.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a457e416a0f90d246a4c3288bd7a25b2304ca727f253f95be383dd17af56be8f"
|
||||
checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
|
|
@ -2739,9 +2739,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0207d6ed1852c2a124c1fbec61621acb8330d2bf969a5d0643131e9affd985a5"
|
||||
checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
|
|
@ -2785,18 +2785,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5c1c469eda89749d2230d8156a5969a69ffe0d6d01200581cdc6110674d293e"
|
||||
checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b679217f2848de74cabd3e8fc5e6d66f40b7da40f8e1954d92054d9010690fd5"
|
||||
checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
|
|
@ -3440,9 +3440,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
|||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.11"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
|
||||
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
@ -3471,6 +3471,33 @@ dependencies = [
|
|||
"tap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yazi-actor"
|
||||
version = "25.6.11"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"crossterm 0.29.0",
|
||||
"futures",
|
||||
"libc",
|
||||
"paste",
|
||||
"scopeguard",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"yazi-boot",
|
||||
"yazi-config",
|
||||
"yazi-core",
|
||||
"yazi-dds",
|
||||
"yazi-fs",
|
||||
"yazi-macro",
|
||||
"yazi-parser",
|
||||
"yazi-plugin",
|
||||
"yazi-proxy",
|
||||
"yazi-shared",
|
||||
"yazi-term",
|
||||
"yazi-widgets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yazi-adapter"
|
||||
version = "25.6.11"
|
||||
|
|
@ -3544,7 +3571,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"toml 0.9.1",
|
||||
"toml 0.9.2",
|
||||
"twox-hash",
|
||||
"vergen-gitcl",
|
||||
"yazi-boot",
|
||||
|
|
@ -3575,7 +3602,7 @@ dependencies = [
|
|||
"ratatui",
|
||||
"regex",
|
||||
"serde",
|
||||
"toml 0.9.1",
|
||||
"toml 0.9.2",
|
||||
"tracing",
|
||||
"yazi-codegen",
|
||||
"yazi-fs",
|
||||
|
|
@ -3596,16 +3623,12 @@ dependencies = [
|
|||
"notify",
|
||||
"parking_lot",
|
||||
"ratatui",
|
||||
"scopeguard",
|
||||
"serde",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"unicode-width 0.2.0",
|
||||
"yazi-adapter",
|
||||
"yazi-boot",
|
||||
"yazi-codegen",
|
||||
"yazi-config",
|
||||
"yazi-dds",
|
||||
"yazi-fs",
|
||||
|
|
@ -3615,7 +3638,6 @@ dependencies = [
|
|||
"yazi-proxy",
|
||||
"yazi-scheduler",
|
||||
"yazi-shared",
|
||||
"yazi-term",
|
||||
"yazi-widgets",
|
||||
]
|
||||
|
||||
|
|
@ -3678,15 +3700,16 @@ dependencies = [
|
|||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-subscriber",
|
||||
"yazi-actor",
|
||||
"yazi-adapter",
|
||||
"yazi-binding",
|
||||
"yazi-boot",
|
||||
"yazi-codegen",
|
||||
"yazi-config",
|
||||
"yazi-core",
|
||||
"yazi-dds",
|
||||
"yazi-fs",
|
||||
"yazi-macro",
|
||||
"yazi-parser",
|
||||
"yazi-plugin",
|
||||
"yazi-proxy",
|
||||
"yazi-shared",
|
||||
|
|
@ -3734,7 +3757,9 @@ version = "25.6.11"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.1",
|
||||
"crossterm 0.29.0",
|
||||
"mlua",
|
||||
"serde",
|
||||
"tokio",
|
||||
"yazi-binding",
|
||||
"yazi-boot",
|
||||
|
|
@ -3744,7 +3769,6 @@ dependencies = [
|
|||
"yazi-macro",
|
||||
"yazi-proxy",
|
||||
"yazi-shared",
|
||||
"yazi-widgets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3752,7 +3776,6 @@ name = "yazi-plugin"
|
|||
version = "25.6.11"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"crossterm 0.29.0",
|
||||
"futures",
|
||||
"globset",
|
||||
|
|
@ -3782,7 +3805,6 @@ dependencies = [
|
|||
"yazi-prebuilt",
|
||||
"yazi-proxy",
|
||||
"yazi-shared",
|
||||
"yazi-term",
|
||||
"yazi-widgets",
|
||||
]
|
||||
|
||||
|
|
@ -3824,6 +3846,7 @@ dependencies = [
|
|||
"yazi-dds",
|
||||
"yazi-fs",
|
||||
"yazi-macro",
|
||||
"yazi-parser",
|
||||
"yazi-plugin",
|
||||
"yazi-proxy",
|
||||
"yazi-shared",
|
||||
|
|
@ -3866,6 +3889,7 @@ dependencies = [
|
|||
name = "yazi-widgets"
|
||||
version = "25.6.11"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"clipboard-win",
|
||||
"crossterm 0.29.0",
|
||||
|
|
@ -3875,10 +3899,9 @@ dependencies = [
|
|||
"tokio",
|
||||
"unicode-width 0.2.0",
|
||||
"yazi-binding",
|
||||
"yazi-codegen",
|
||||
"yazi-config",
|
||||
"yazi-macro",
|
||||
"yazi-proxy",
|
||||
"yazi-parser",
|
||||
"yazi-shared",
|
||||
"yazi-term",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ syntect = { version = "5.2.0", default-features = false, features =
|
|||
tokio = { version = "1.46.1", features = [ "full" ] }
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-util = "0.7.15"
|
||||
toml = { version = "0.9.1" }
|
||||
toml = { version = "0.9.2" }
|
||||
tracing = { version = "0.1.41", features = [ "max_level_debug", "release_max_level_debug" ] }
|
||||
twox-hash = { version = "2.1.1", default-features = false, features = [ "std", "random", "xxhash3_128" ] }
|
||||
unicode-width = { version = "0.2.0", default-features = false }
|
||||
|
|
|
|||
39
yazi-actor/Cargo.toml
Normal file
39
yazi-actor/Cargo.toml
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
[package]
|
||||
name = "yazi-actor"
|
||||
version = "25.6.11"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
authors = [ "sxyazi <sxyazi@gmail.com>" ]
|
||||
description = "Yazi actor model"
|
||||
homepage = "https://yazi-rs.github.io"
|
||||
repository = "https://github.com/sxyazi/yazi"
|
||||
|
||||
[dependencies]
|
||||
yazi-boot = { path = "../yazi-boot", version = "25.6.11" }
|
||||
yazi-config = { path = "../yazi-config", version = "25.6.11" }
|
||||
yazi-core = { path = "../yazi-core", version = "25.6.11" }
|
||||
yazi-dds = { path = "../yazi-dds", version = "25.6.11" }
|
||||
yazi-fs = { path = "../yazi-fs", version = "25.6.11" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "25.6.11" }
|
||||
yazi-parser = { path = "../yazi-parser", version = "25.6.11" }
|
||||
yazi-plugin = { path = "../yazi-plugin", version = "25.6.11" }
|
||||
yazi-proxy = { path = "../yazi-proxy", version = "25.6.11" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "25.6.11" }
|
||||
yazi-term = { path = "../yazi-term", version = "25.6.11" }
|
||||
yazi-widgets = { path = "../yazi-widgets", version = "25.6.11" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
scopeguard = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
libc = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
12
yazi-actor/src/actor.rs
Normal file
12
yazi-actor/src/actor.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
use anyhow::Result;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::Ctx;
|
||||
|
||||
pub trait Actor {
|
||||
type Options;
|
||||
|
||||
const NAME: &'static str;
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data>;
|
||||
}
|
||||
19
yazi-actor/src/cmp/arrow.rs
Normal file
19
yazi-actor/src/cmp/arrow.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::ArrowOpt;
|
||||
use yazi_shared::event::Data;
|
||||
use yazi_widgets::Scrollable;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Arrow;
|
||||
|
||||
impl Actor for Arrow {
|
||||
type Options = ArrowOpt;
|
||||
|
||||
const NAME: &'static str = "arrow";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
succ!(render!(cx.cmp.scroll(opt.step)));
|
||||
}
|
||||
}
|
||||
26
yazi-actor/src/cmp/close.rs
Normal file
26
yazi-actor/src/cmp/close.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use std::mem;
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::{cmp::CloseOpt, input::CompleteOpt};
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Close;
|
||||
|
||||
impl Actor for Close {
|
||||
type Options = CloseOpt;
|
||||
|
||||
const NAME: &'static str = "close";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let cmp = &mut cx.core.cmp;
|
||||
if let Some(item) = cmp.selected().filter(|_| opt.submit).cloned() {
|
||||
return act!(complete, cx.core.input, CompleteOpt { item, _ticket: cmp.ticket });
|
||||
}
|
||||
|
||||
cmp.caches.clear();
|
||||
succ!(render!(mem::replace(&mut cmp.visible, false)));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +1,49 @@
|
|||
use std::{mem, ops::ControlFlow};
|
||||
|
||||
use yazi_macro::render;
|
||||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::cmp::ShowOpt;
|
||||
use yazi_proxy::options::CmpItem;
|
||||
use yazi_shared::{osstr_contains, osstr_starts_with};
|
||||
use yazi_shared::{event::Data, osstr_contains, osstr_starts_with};
|
||||
|
||||
use crate::cmp::Cmp;
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
const LIMIT: usize = 30;
|
||||
|
||||
impl Cmp {
|
||||
#[yazi_codegen::command]
|
||||
pub fn show(&mut self, opt: ShowOpt) {
|
||||
if self.ticket != opt.ticket {
|
||||
return;
|
||||
pub struct Show;
|
||||
|
||||
impl Actor for Show {
|
||||
type Options = ShowOpt;
|
||||
|
||||
const NAME: &'static str = "show";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let cmp = &mut cx.cmp;
|
||||
if cmp.ticket != opt.ticket {
|
||||
succ!();
|
||||
}
|
||||
|
||||
if !opt.cache.is_empty() {
|
||||
self.caches.insert(opt.cache_name.clone(), opt.cache);
|
||||
cmp.caches.insert(opt.cache_name.clone(), opt.cache);
|
||||
}
|
||||
let Some(cache) = self.caches.get(&opt.cache_name) else {
|
||||
return;
|
||||
let Some(cache) = cmp.caches.get(&opt.cache_name) else {
|
||||
succ!();
|
||||
};
|
||||
|
||||
self.ticket = opt.ticket;
|
||||
self.cands = Self::match_candidates(&opt.word, cache);
|
||||
if self.cands.is_empty() {
|
||||
return render!(mem::replace(&mut self.visible, false));
|
||||
cmp.ticket = opt.ticket;
|
||||
cmp.cands = Self::match_candidates(&opt.word, cache);
|
||||
if cmp.cands.is_empty() {
|
||||
succ!(render!(mem::replace(&mut cmp.visible, false)));
|
||||
}
|
||||
|
||||
self.offset = 0;
|
||||
self.cursor = 0;
|
||||
self.visible = true;
|
||||
render!();
|
||||
cmp.offset = 0;
|
||||
cmp.cursor = 0;
|
||||
cmp.visible = true;
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
||||
impl Show {
|
||||
fn match_candidates(word: &str, cache: &[CmpItem]) -> Vec<CmpItem> {
|
||||
let smart = !word.bytes().any(|c| c.is_ascii_uppercase());
|
||||
|
||||
|
|
@ -1,38 +1,41 @@
|
|||
use std::{ffi::OsString, mem, path::{MAIN_SEPARATOR_STR, Path, PathBuf}};
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::fs;
|
||||
use yazi_fs::{CWD, expand_path};
|
||||
use yazi_macro::{emit, render};
|
||||
use yazi_parser::cmp::TriggerOpt;
|
||||
use yazi_macro::{act, emit, render, succ};
|
||||
use yazi_parser::cmp::{ShowOpt, TriggerOpt};
|
||||
use yazi_proxy::options::CmpItem;
|
||||
use yazi_shared::{event::Cmd, natsort};
|
||||
use yazi_shared::{event::{Cmd, Data}, natsort};
|
||||
|
||||
use crate::cmp::Cmp;
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
impl Cmp {
|
||||
#[yazi_codegen::command]
|
||||
pub fn trigger(&mut self, opt: TriggerOpt) {
|
||||
pub struct Trigger;
|
||||
|
||||
impl Actor for Trigger {
|
||||
type Options = TriggerOpt;
|
||||
|
||||
const NAME: &'static str = "trigger";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let cmp = &mut cx.cmp;
|
||||
if let Some(t) = opt.ticket {
|
||||
if t < self.ticket {
|
||||
return;
|
||||
if t < cmp.ticket {
|
||||
succ!();
|
||||
}
|
||||
self.ticket = t;
|
||||
cmp.ticket = t;
|
||||
}
|
||||
|
||||
let Some((parent, word)) = Self::split_path(&opt.word) else {
|
||||
return self.close(false);
|
||||
return act!(cmp:close, cx, false);
|
||||
};
|
||||
|
||||
if self.caches.contains_key(&parent) {
|
||||
return self.show(
|
||||
Cmd::default()
|
||||
.with_any("cache-name", parent)
|
||||
.with("word", word)
|
||||
.with("ticket", self.ticket),
|
||||
);
|
||||
if cmp.caches.contains_key(&parent) {
|
||||
let ticket = cmp.ticket;
|
||||
return act!(cmp:show, cx, ShowOpt { cache_name: parent, word: word.into(), ticket, ..Default::default() });
|
||||
}
|
||||
|
||||
let ticket = self.ticket;
|
||||
let ticket = cmp.ticket;
|
||||
tokio::spawn(async move {
|
||||
let mut dir = fs::read_dir(&parent).await?;
|
||||
let mut cache = vec![];
|
||||
|
|
@ -65,9 +68,11 @@ impl Cmp {
|
|||
Ok::<_, anyhow::Error>(())
|
||||
});
|
||||
|
||||
render!(mem::replace(&mut self.visible, false));
|
||||
succ!(render!(mem::replace(&mut cmp.visible, false)));
|
||||
}
|
||||
}
|
||||
|
||||
impl Trigger {
|
||||
fn split_path(s: &str) -> Option<(PathBuf, String)> {
|
||||
if s == "~" {
|
||||
return None; // We don't autocomplete a `~`, but `~/`
|
||||
|
|
@ -93,7 +98,7 @@ mod tests {
|
|||
use super::*;
|
||||
|
||||
fn compare(s: &str, parent: &str, child: &str) {
|
||||
let (p, c) = Cmp::split_path(s).unwrap();
|
||||
let (p, c) = Trigger::split_path(s).unwrap();
|
||||
let p = p.strip_prefix(yazi_fs::CWD.load().as_ref()).unwrap_or(&p);
|
||||
assert_eq!((p, c.as_str()), (Path::new(parent), child));
|
||||
}
|
||||
26
yazi-actor/src/confirm/arrow.rs
Normal file
26
yazi-actor/src/confirm/arrow.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::ArrowOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Arrow;
|
||||
|
||||
impl Actor for Arrow {
|
||||
type Options = ArrowOpt;
|
||||
|
||||
const NAME: &'static str = "arrow";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let confirm = &mut cx.core.confirm;
|
||||
|
||||
let area = cx.core.mgr.area(confirm.position);
|
||||
let len = confirm.list.line_count(area.width);
|
||||
|
||||
let old = confirm.offset;
|
||||
confirm.offset = opt.step.add(confirm.offset, len, area.height as _);
|
||||
|
||||
succ!(render!(old != confirm.offset));
|
||||
}
|
||||
}
|
||||
23
yazi-actor/src/confirm/close.rs
Normal file
23
yazi-actor/src/confirm/close.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::confirm::CloseOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Close;
|
||||
|
||||
impl Actor for Close {
|
||||
type Options = CloseOpt;
|
||||
|
||||
const NAME: &'static str = "close";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
if let Some(cb) = cx.confirm.callback.take() {
|
||||
_ = cb.send(opt.submit);
|
||||
}
|
||||
|
||||
cx.confirm.visible = false;
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
31
yazi-actor/src/confirm/show.rs
Normal file
31
yazi-actor/src/confirm/show.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::confirm::ShowOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Show;
|
||||
|
||||
impl Actor for Show {
|
||||
type Options = ShowOpt;
|
||||
|
||||
const NAME: &'static str = "show";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
act!(confirm:close, cx)?;
|
||||
|
||||
let confirm = &mut cx.confirm;
|
||||
confirm.title = opt.cfg.title;
|
||||
confirm.body = opt.cfg.body;
|
||||
confirm.list = opt.cfg.list;
|
||||
|
||||
confirm.position = opt.cfg.position;
|
||||
confirm.offset = 0;
|
||||
|
||||
confirm.callback = Some(opt.tx);
|
||||
confirm.visible = true;
|
||||
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
83
yazi-actor/src/context.rs
Normal file
83
yazi-actor/src/context.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use yazi_core::{Core, mgr::Tabs, tab::{Folder, Tab}, tasks::Tasks};
|
||||
use yazi_fs::File;
|
||||
use yazi_shared::{Id, url::Url};
|
||||
|
||||
pub struct Ctx<'a> {
|
||||
pub core: &'a mut Core,
|
||||
pub tab: usize,
|
||||
}
|
||||
|
||||
impl Deref for Ctx<'_> {
|
||||
type Target = Core;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target { self.core }
|
||||
}
|
||||
|
||||
impl DerefMut for Ctx<'_> {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { self.core }
|
||||
}
|
||||
|
||||
impl<'a> Ctx<'a> {
|
||||
#[inline]
|
||||
pub fn new(core: &'a mut Core, tab: Option<Id>) -> Result<Self> {
|
||||
let tab = if let Some(id) = tab {
|
||||
core
|
||||
.mgr
|
||||
.tabs
|
||||
.iter()
|
||||
.position(|t| t.id == id)
|
||||
.ok_or_else(|| anyhow!("Tab with id {id} not found"))?
|
||||
} else {
|
||||
core.mgr.tabs.cursor
|
||||
};
|
||||
|
||||
Ok(Self { core, tab })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn active(core: &'a mut Core) -> Self {
|
||||
let tab = core.mgr.tabs.cursor;
|
||||
Self { core, tab }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tabs(&self) -> &Tabs { &self.mgr.tabs }
|
||||
|
||||
#[inline]
|
||||
pub fn tabs_mut(&mut self) -> &mut Tabs { &mut self.mgr.tabs }
|
||||
|
||||
#[inline]
|
||||
pub fn tab(&self) -> &Tab { &self.tabs()[self.tab] }
|
||||
|
||||
#[inline]
|
||||
pub fn tab_mut(&mut self) -> &mut Tab { &mut self.core.mgr.tabs[self.tab] }
|
||||
|
||||
#[inline]
|
||||
pub fn cwd(&self) -> &Url { self.tab().cwd() }
|
||||
|
||||
#[inline]
|
||||
pub fn parent(&self) -> Option<&Folder> { self.tab().parent.as_ref() }
|
||||
|
||||
#[inline]
|
||||
pub fn parent_mut(&mut self) -> Option<&mut Folder> { self.tab_mut().parent.as_mut() }
|
||||
|
||||
#[inline]
|
||||
pub fn current(&self) -> &Folder { &self.tab().current }
|
||||
|
||||
#[inline]
|
||||
pub fn current_mut(&mut self) -> &mut Folder { &mut self.tab_mut().current }
|
||||
|
||||
#[inline]
|
||||
pub fn hovered(&self) -> Option<&File> { self.tab().hovered() }
|
||||
|
||||
#[inline]
|
||||
pub fn hovered_folder(&self) -> Option<&Folder> { self.tab().hovered_folder() }
|
||||
|
||||
#[inline]
|
||||
pub fn tasks(&self) -> &Tasks { &self.tasks }
|
||||
}
|
||||
19
yazi-actor/src/help/arrow.rs
Normal file
19
yazi-actor/src/help/arrow.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::ArrowOpt;
|
||||
use yazi_shared::event::Data;
|
||||
use yazi_widgets::Scrollable;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Arrow;
|
||||
|
||||
impl Actor for Arrow {
|
||||
type Options = ArrowOpt;
|
||||
|
||||
const NAME: &'static str = "arrow";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
succ!(render!(cx.help.scroll(opt.step)));
|
||||
}
|
||||
}
|
||||
27
yazi-actor/src/help/escape.rs
Normal file
27
yazi-actor/src/help/escape.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Escape;
|
||||
|
||||
impl Actor for Escape {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "escape";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
if cx.help.keyword().is_none() {
|
||||
return act!(help:toggle, cx, cx.help.layer);
|
||||
}
|
||||
|
||||
let help = &mut cx.help;
|
||||
help.keyword = String::new();
|
||||
help.in_filter = None;
|
||||
help.filter_apply();
|
||||
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
22
yazi-actor/src/help/filter.rs
Normal file
22
yazi-actor/src/help/filter.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Filter;
|
||||
|
||||
impl Actor for Filter {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "filter";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
let help = &mut cx.help;
|
||||
|
||||
help.in_filter = Some(Default::default());
|
||||
help.filter_apply();
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
1
yazi-actor/src/help/mod.rs
Normal file
1
yazi-actor/src/help/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
yazi_macro::mod_flat!(arrow escape filter toggle);
|
||||
29
yazi-actor/src/help/toggle.rs
Normal file
29
yazi-actor/src/help/toggle.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::help::ToggleOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Toggle;
|
||||
|
||||
impl Actor for Toggle {
|
||||
type Options = ToggleOpt;
|
||||
|
||||
const NAME: &'static str = "toggle";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let help = &mut cx.help;
|
||||
|
||||
help.visible = !help.visible;
|
||||
help.layer = opt.layer;
|
||||
|
||||
help.keyword = String::new();
|
||||
help.in_filter = None;
|
||||
help.filter_apply();
|
||||
|
||||
help.offset = 0;
|
||||
help.cursor = 0;
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
28
yazi-actor/src/input/close.rs
Normal file
28
yazi-actor/src/input/close.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::input::CloseOpt;
|
||||
use yazi_shared::{errors::InputError, event::Data};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Close;
|
||||
|
||||
impl Actor for Close {
|
||||
type Options = CloseOpt;
|
||||
|
||||
const NAME: &'static str = "close";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let input = &mut cx.input;
|
||||
input.visible = false;
|
||||
input.ticket.next();
|
||||
|
||||
if let Some(tx) = input.tx.take() {
|
||||
let value = input.snap().value.clone();
|
||||
_ = tx.send(if opt.submit { Ok(value) } else { Err(InputError::Canceled(value)) });
|
||||
}
|
||||
|
||||
act!(cmp:close, cx)?;
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
30
yazi-actor/src/input/escape.rs
Normal file
30
yazi-actor/src/input/escape.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_shared::event::Data;
|
||||
use yazi_widgets::input::InputOp;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Escape;
|
||||
|
||||
impl Actor for Escape {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "escape";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
use yazi_widgets::input::InputMode as M;
|
||||
let input = &mut cx.input;
|
||||
|
||||
let mode = input.snap().mode;
|
||||
match mode {
|
||||
M::Normal if input.snap_mut().op == InputOp::None => act!(input:close, cx),
|
||||
M::Insert => act!(cmp:close, cx),
|
||||
M::Normal | M::Replace => Ok(().into()),
|
||||
}?;
|
||||
|
||||
act!(escape, cx.input)?;
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
55
yazi-actor/src/input/show.rs
Normal file
55
yazi-actor/src/input/show.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
use std::ops::DerefMut;
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_config::YAZI;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::input::ShowOpt;
|
||||
use yazi_shared::{errors::InputError, event::Data};
|
||||
use yazi_widgets::input::InputCallback;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Show;
|
||||
|
||||
impl Actor for Show {
|
||||
type Options = ShowOpt;
|
||||
|
||||
const NAME: &'static str = "show";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
act!(input:close, cx)?;
|
||||
|
||||
let input = &mut cx.input;
|
||||
input.visible = true;
|
||||
input.title = opt.cfg.title;
|
||||
input.position = opt.cfg.position;
|
||||
|
||||
// Typing
|
||||
input.tx = Some(opt.tx.clone());
|
||||
let ticket = input.ticket.clone();
|
||||
|
||||
// Reset input
|
||||
let cb: InputCallback = Box::new(move |before, after| {
|
||||
if opt.cfg.realtime {
|
||||
opt.tx.send(Err(InputError::Typed(format!("{before}{after}")))).ok();
|
||||
} else if opt.cfg.completion {
|
||||
opt.tx.send(Err(InputError::Completed(before.to_owned(), ticket.current()))).ok();
|
||||
}
|
||||
});
|
||||
*input.deref_mut() = yazi_widgets::input::Input::new(
|
||||
opt.cfg.value,
|
||||
opt.cfg.position.offset.width.saturating_sub(YAZI.input.border()) as usize,
|
||||
opt.cfg.obscure,
|
||||
cb,
|
||||
);
|
||||
|
||||
// Set cursor after reset
|
||||
// TODO: remove this
|
||||
if let Some(cursor) = opt.cfg.cursor {
|
||||
input.snap_mut().cursor = cursor;
|
||||
act!(r#move, input)?;
|
||||
}
|
||||
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
5
yazi-actor/src/lib.rs
Normal file
5
yazi-actor/src/lib.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
extern crate self as yazi_actor;
|
||||
|
||||
yazi_macro::mod_pub!(cmp confirm help input mgr pick spot tasks which);
|
||||
|
||||
yazi_macro::mod_flat!(actor context);
|
||||
33
yazi-actor/src/mgr/arrow.rs
Normal file
33
yazi-actor/src/mgr/arrow.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::ArrowOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Arrow;
|
||||
|
||||
impl Actor for Arrow {
|
||||
type Options = ArrowOpt;
|
||||
|
||||
const NAME: &'static str = "arrow";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let tab = cx.tab_mut();
|
||||
if !tab.current.arrow(opt.step) {
|
||||
succ!();
|
||||
}
|
||||
|
||||
// Visual selection
|
||||
if let Some((start, items)) = tab.mode.visual_mut() {
|
||||
let end = tab.current.cursor;
|
||||
*items = (start.min(end)..=end.max(start)).collect();
|
||||
}
|
||||
|
||||
act!(mgr:hover, cx, None)?;
|
||||
act!(mgr:peek, cx)?;
|
||||
act!(mgr:watch, cx)?;
|
||||
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
21
yazi-actor/src/mgr/back.rs
Normal file
21
yazi-actor/src/mgr/back.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::{VoidOpt, tab::CdSource};
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Back;
|
||||
|
||||
impl Actor for Back {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "back";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
if let Some(u) = cx.tab_mut().backstack.shift_backward().cloned() {
|
||||
return act!(mgr:cd, cx, (u, CdSource::Back));
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
@ -7,25 +7,32 @@ use tokio::{fs::{self, OpenOptions}, io::AsyncWriteExt};
|
|||
use yazi_config::YAZI;
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_fs::{File, FilesOp, max_common_root, maybe_exists, paths_to_same_file};
|
||||
use yazi_macro::err;
|
||||
use yazi_macro::{err, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_proxy::{AppProxy, HIDER, TasksProxy, WATCHER};
|
||||
use yazi_shared::{terminal_clear, url::Url};
|
||||
use yazi_shared::{event::Data, terminal_clear, url::Url};
|
||||
use yazi_term::tty::TTY;
|
||||
|
||||
use crate::mgr::Mgr;
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
impl Mgr {
|
||||
pub(super) fn bulk_rename(&self) {
|
||||
pub struct BulkRename;
|
||||
|
||||
impl Actor for BulkRename {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "bulk_rename";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
let Some(opener) = YAZI.opener.block(YAZI.open.all("bulk-rename.txt", "text/plain")) else {
|
||||
return AppProxy::notify_warn("Bulk rename", "No text opener found");
|
||||
succ!(AppProxy::notify_warn("Bulk rename", "No text opener found"));
|
||||
};
|
||||
|
||||
let old: Vec<_> = self.selected_or_hovered().collect();
|
||||
let old: Vec<_> = cx.tab().selected_or_hovered().collect();
|
||||
|
||||
let root = max_common_root(&old);
|
||||
let old: Vec<_> = old.into_iter().map(|p| p.strip_prefix(&root).unwrap().to_owned()).collect();
|
||||
|
||||
let cwd = self.cwd().clone();
|
||||
let cwd = cx.cwd().clone();
|
||||
tokio::spawn(async move {
|
||||
let tmp = YAZI.preview.tmpfile("bulk");
|
||||
let s = old.iter().map(|o| o.as_os_str()).collect::<Vec<_>>().join(OsStr::new("\n"));
|
||||
|
|
@ -50,11 +57,14 @@ impl Mgr {
|
|||
|
||||
let new: Vec<_> =
|
||||
fs::read_to_string(&tmp).await?.lines().take(old.len()).map(PathBuf::from).collect();
|
||||
Self::bulk_rename_do(root, old, new).await
|
||||
Self::r#do(root, old, new).await
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
||||
async fn bulk_rename_do(root: PathBuf, old: Vec<PathBuf>, new: Vec<PathBuf>) -> Result<()> {
|
||||
impl BulkRename {
|
||||
async fn r#do(root: PathBuf, old: Vec<PathBuf>, new: Vec<PathBuf>) -> Result<()> {
|
||||
terminal_clear(TTY.writer())?;
|
||||
if old.len() != new.len() {
|
||||
#[rustfmt::skip]
|
||||
|
|
@ -176,7 +186,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_sort() {
|
||||
fn cmp(input: &[(&str, &str)], expected: &[(&str, &str)]) {
|
||||
let sorted = Mgr::prioritized_paths(
|
||||
let sorted = BulkRename::prioritized_paths(
|
||||
input.iter().map(|&(o, _)| o.into()).collect(),
|
||||
input.iter().map(|&(_, n)| n.into()).collect(),
|
||||
);
|
||||
|
|
@ -1,67 +1,72 @@
|
|||
use std::{mem, time::Duration};
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::pin;
|
||||
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
|
||||
use yazi_config::popup::InputCfg;
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_fs::{File, FilesOp, expand_path};
|
||||
use yazi_macro::{err, render};
|
||||
use yazi_macro::{act, err, render, succ};
|
||||
use yazi_parser::tab::CdOpt;
|
||||
use yazi_proxy::{CmpProxy, InputProxy, MgrProxy, TabProxy};
|
||||
use yazi_shared::{Debounce, errors::InputError, url::Url};
|
||||
use yazi_proxy::{CmpProxy, InputProxy, MgrProxy};
|
||||
use yazi_shared::{Debounce, errors::InputError, event::Data, url::Url};
|
||||
|
||||
use crate::tab::Tab;
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
impl Tab {
|
||||
#[yazi_codegen::command]
|
||||
pub fn cd(&mut self, opt: CdOpt) {
|
||||
if !self.try_escape_visual() {
|
||||
return;
|
||||
}
|
||||
pub struct Cd;
|
||||
|
||||
impl Actor for Cd {
|
||||
type Options = CdOpt;
|
||||
|
||||
const NAME: &'static str = "cd";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
act!(mgr:escape_visual, cx)?;
|
||||
if opt.interactive {
|
||||
return self.cd_interactive();
|
||||
return Self::cd_interactive();
|
||||
}
|
||||
|
||||
if opt.target == *self.cwd() {
|
||||
return;
|
||||
let tab = cx.tab_mut();
|
||||
if opt.target == *tab.cwd() {
|
||||
succ!();
|
||||
}
|
||||
|
||||
// Take parent to history
|
||||
if let Some(rep) = self.parent.take() {
|
||||
self.history.insert(rep.url.to_owned(), rep);
|
||||
if let Some(rep) = tab.parent.take() {
|
||||
tab.history.insert(rep.url.to_owned(), rep);
|
||||
}
|
||||
|
||||
// Backstack
|
||||
if opt.source.big_jump() {
|
||||
if self.current.url.is_regular() {
|
||||
self.backstack.push(&self.current.url);
|
||||
if tab.current.url.is_regular() {
|
||||
tab.backstack.push(&tab.current.url);
|
||||
}
|
||||
if opt.target.is_regular() {
|
||||
self.backstack.push(&opt.target);
|
||||
tab.backstack.push(&opt.target);
|
||||
}
|
||||
}
|
||||
|
||||
// Current
|
||||
let rep = self.history.remove_or(&opt.target);
|
||||
let rep = mem::replace(&mut self.current, rep);
|
||||
let rep = tab.history.remove_or(&opt.target);
|
||||
let rep = mem::replace(&mut tab.current, rep);
|
||||
if rep.url.is_regular() {
|
||||
self.history.insert(rep.url.to_owned(), rep);
|
||||
tab.history.insert(rep.url.to_owned(), rep);
|
||||
}
|
||||
|
||||
// Parent
|
||||
if let Some(parent) = opt.target.parent_url() {
|
||||
self.parent = Some(self.history.remove_or(&parent));
|
||||
tab.parent = Some(tab.history.remove_or(&parent));
|
||||
}
|
||||
|
||||
err!(Pubsub::pub_after_cd(self.id, self.cwd()));
|
||||
self.hover(None);
|
||||
|
||||
MgrProxy::refresh();
|
||||
render!();
|
||||
err!(Pubsub::pub_after_cd(tab.id, tab.cwd()));
|
||||
act!(mgr:hover, cx)?;
|
||||
act!(mgr:refresh, cx)?;
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
||||
fn cd_interactive(&mut self) {
|
||||
impl Cd {
|
||||
fn cd_interactive() -> Result<Data> {
|
||||
let input = InputProxy::show(InputCfg::cd());
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
|
@ -75,13 +80,13 @@ impl Tab {
|
|||
|
||||
let Ok(file) = File::new(url.clone()).await else { return };
|
||||
if file.is_dir() {
|
||||
return TabProxy::cd(&url);
|
||||
return MgrProxy::cd(&url);
|
||||
}
|
||||
|
||||
if let Some(p) = url.parent_url() {
|
||||
FilesOp::Upserting(p, [(url.urn_owned(), file)].into()).emit();
|
||||
}
|
||||
TabProxy::reveal(&url);
|
||||
MgrProxy::reveal(&url);
|
||||
}
|
||||
Err(InputError::Completed(before, ticket)) => {
|
||||
CmpProxy::trigger(&before, ticket);
|
||||
|
|
@ -90,5 +95,6 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
22
yazi-actor/src/mgr/close.rs
Normal file
22
yazi-actor/src/mgr/close.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::act;
|
||||
use yazi_parser::mgr::CloseOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Close;
|
||||
|
||||
impl Actor for Close {
|
||||
type Options = CloseOpt;
|
||||
|
||||
const NAME: &'static str = "close";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
if cx.tabs().len() > 1 {
|
||||
act!(mgr:tab_close, cx, cx.tabs().cursor)
|
||||
} else {
|
||||
act!(mgr:quit, cx, opt.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,28 @@
|
|||
use std::{ffi::OsString, path::Path};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::tab::CopyOpt;
|
||||
use yazi_shared::event::Data;
|
||||
use yazi_widgets::CLIPBOARD;
|
||||
|
||||
use crate::tab::Tab;
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
impl Tab {
|
||||
#[yazi_codegen::command]
|
||||
pub fn copy(&mut self, opt: CopyOpt) {
|
||||
if !self.try_escape_visual() {
|
||||
return;
|
||||
}
|
||||
pub struct Copy;
|
||||
|
||||
impl Actor for Copy {
|
||||
type Options = CopyOpt;
|
||||
|
||||
const NAME: &'static str = "copy";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
act!(mgr:escape_visual, cx)?;
|
||||
|
||||
let mut s = OsString::new();
|
||||
let mut it = if opt.hovered {
|
||||
Box::new(self.hovered().map(|h| &h.url).into_iter())
|
||||
Box::new(cx.hovered().map(|h| &h.url).into_iter())
|
||||
} else {
|
||||
self.selected_or_hovered()
|
||||
cx.tab().selected_or_hovered()
|
||||
}
|
||||
.peekable();
|
||||
|
||||
|
|
@ -26,7 +32,7 @@ impl Tab {
|
|||
"dirname" => opt.separator.transform(u.parent().unwrap_or(Path::new(""))),
|
||||
"filename" => opt.separator.transform(u.name()),
|
||||
"name_without_ext" => opt.separator.transform(u.file_stem().unwrap_or_default()),
|
||||
_ => return,
|
||||
_ => bail!("Unknown copy type: {}", opt.r#type),
|
||||
});
|
||||
if it.peek().is_some() {
|
||||
s.push("\n");
|
||||
|
|
@ -35,9 +41,10 @@ impl Tab {
|
|||
|
||||
// Copy the CWD path regardless even if the directory is empty
|
||||
if s.is_empty() && opt.r#type == "dirname" {
|
||||
s.push(self.cwd());
|
||||
s.push(cx.cwd());
|
||||
}
|
||||
|
||||
futures::executor::block_on(CLIPBOARD.set(s));
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
@ -2,16 +2,22 @@ use anyhow::Result;
|
|||
use tokio::fs;
|
||||
use yazi_config::popup::{ConfirmCfg, InputCfg};
|
||||
use yazi_fs::{File, FilesOp, maybe_exists, ok_or_not_found, realname};
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::mgr::CreateOpt;
|
||||
use yazi_proxy::{ConfirmProxy, InputProxy, TabProxy, WATCHER};
|
||||
use yazi_shared::url::{Url, UrnBuf};
|
||||
use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy, WATCHER};
|
||||
use yazi_shared::{event::Data, url::{Url, UrnBuf}};
|
||||
|
||||
use crate::mgr::Mgr;
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
impl Mgr {
|
||||
#[yazi_codegen::command]
|
||||
pub fn create(&self, opt: CreateOpt) {
|
||||
let cwd = self.cwd().to_owned();
|
||||
pub struct Create;
|
||||
|
||||
impl Actor for Create {
|
||||
type Options = CreateOpt;
|
||||
|
||||
const NAME: &'static str = "create";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let cwd = cx.cwd().to_owned();
|
||||
let mut input = InputProxy::show(InputCfg::create(opt.dir));
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
|
@ -28,11 +34,14 @@ impl Mgr {
|
|||
return;
|
||||
}
|
||||
|
||||
_ = Self::create_do(new, opt.dir || name.ends_with('/') || name.ends_with('\\')).await;
|
||||
_ = Self::r#do(new, opt.dir || name.ends_with('/') || name.ends_with('\\')).await;
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_do(new: Url, dir: bool) -> Result<()> {
|
||||
impl Create {
|
||||
async fn r#do(new: Url, dir: bool) -> Result<()> {
|
||||
let Some(parent) = new.parent_url() else { return Ok(()) };
|
||||
let _permit = WATCHER.acquire().await.unwrap();
|
||||
|
||||
|
|
@ -50,7 +59,7 @@ impl Mgr {
|
|||
|
||||
if let Ok(f) = File::new(new.clone()).await {
|
||||
FilesOp::Upserting(parent, [(f.urn_owned(), f)].into()).emit();
|
||||
TabProxy::reveal(&new)
|
||||
MgrProxy::reveal(&new)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
20
yazi-actor/src/mgr/enter.rs
Normal file
20
yazi-actor/src/mgr/enter.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::{VoidOpt, tab::CdSource};
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Enter;
|
||||
|
||||
impl Actor for Enter {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "enter";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
let Some(h) = cx.hovered().filter(|h| h.is_dir()) else { succ!() };
|
||||
|
||||
act!(mgr:cd, cx, (h.url.to_regular(), CdSource::Enter))
|
||||
}
|
||||
}
|
||||
146
yazi-actor/src/mgr/escape.rs
Normal file
146
yazi-actor/src/mgr/escape.rs
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
use anyhow::{Result, bail};
|
||||
use yazi_macro::{act, render, render_and, succ};
|
||||
use yazi_parser::{VoidOpt, tab::EscapeOpt};
|
||||
use yazi_proxy::AppProxy;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Escape;
|
||||
|
||||
impl Actor for Escape {
|
||||
type Options = EscapeOpt;
|
||||
|
||||
const NAME: &'static str = "escape";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
if opt.is_empty() {
|
||||
_ = act!(mgr:escape_find, cx)? != false
|
||||
|| act!(mgr:escape_visual, cx)? != false
|
||||
|| act!(mgr:escape_filter, cx)? != false
|
||||
|| act!(mgr:escape_select, cx)? != false
|
||||
|| act!(mgr:escape_search, cx)? != false;
|
||||
succ!();
|
||||
}
|
||||
|
||||
if opt.contains(EscapeOpt::FIND) {
|
||||
act!(mgr:escape_find, cx)?;
|
||||
}
|
||||
if opt.contains(EscapeOpt::VISUAL) {
|
||||
act!(mgr:escape_visual, cx)?;
|
||||
}
|
||||
if opt.contains(EscapeOpt::FILTER) {
|
||||
act!(mgr:escape_filter, cx)?;
|
||||
}
|
||||
if opt.contains(EscapeOpt::SELECT) {
|
||||
act!(mgr:escape_select, cx)?;
|
||||
}
|
||||
if opt.contains(EscapeOpt::SEARCH) {
|
||||
act!(mgr:escape_search, cx)?;
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Find
|
||||
pub struct EscapeFind;
|
||||
|
||||
impl Actor for EscapeFind {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "escape_find";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
succ!(render_and!(cx.tab_mut().finder.take().is_some()))
|
||||
}
|
||||
}
|
||||
|
||||
// --- Visual
|
||||
pub struct EscapeVisual;
|
||||
|
||||
impl Actor for EscapeVisual {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "escape_visual";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
let tab = cx.tab_mut();
|
||||
|
||||
let select = tab.mode.is_select();
|
||||
let Some((_, indices)) = tab.mode.take_visual() else { succ!(false) };
|
||||
|
||||
render!();
|
||||
let urls: Vec<_> =
|
||||
indices.into_iter().filter_map(|i| tab.current.files.get(i)).map(|f| &f.url).collect();
|
||||
|
||||
if !select {
|
||||
tab.selected.remove_many(&urls);
|
||||
} else if tab.selected.add_many(&urls) != urls.len() {
|
||||
AppProxy::notify_warn(
|
||||
"Escape visual mode",
|
||||
"Some files cannot be selected, due to path nesting conflict.",
|
||||
);
|
||||
bail!("Some files cannot be selected, due to path nesting conflict.");
|
||||
}
|
||||
|
||||
succ!(true)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Filter
|
||||
pub struct EscapeFilter;
|
||||
|
||||
impl Actor for EscapeFilter {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "escape_filter";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
if cx.current_mut().files.filter().is_none() {
|
||||
succ!(false);
|
||||
}
|
||||
|
||||
act!(mgr:filter_do, cx)?;
|
||||
render!();
|
||||
succ!(true);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Select
|
||||
pub struct EscapeSelect;
|
||||
|
||||
impl Actor for EscapeSelect {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "escape_select";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
let tab = cx.tab_mut();
|
||||
if tab.selected.is_empty() {
|
||||
succ!(false);
|
||||
}
|
||||
|
||||
tab.selected.clear();
|
||||
if tab.hovered().is_some_and(|h| h.is_dir()) {
|
||||
act!(mgr:peek, cx, true)?;
|
||||
}
|
||||
|
||||
render!();
|
||||
succ!(true);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Search
|
||||
pub struct EscapeSearch;
|
||||
|
||||
impl Actor for EscapeSearch {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "escape_search";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
let b = cx.cwd().is_search();
|
||||
act!(mgr:search_stop, cx)?;
|
||||
succ!(render_and!(b));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,25 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::pin;
|
||||
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
|
||||
use yazi_config::popup::InputCfg;
|
||||
use yazi_fs::FilterCase;
|
||||
use yazi_macro::emit;
|
||||
use yazi_macro::{emit, succ};
|
||||
use yazi_parser::tab::FilterOpt;
|
||||
use yazi_proxy::InputProxy;
|
||||
use yazi_shared::{Debounce, errors::InputError, event::Cmd};
|
||||
use yazi_shared::{Debounce, errors::InputError, event::{Cmd, Data}};
|
||||
|
||||
use crate::tab::Tab;
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
impl Tab {
|
||||
#[yazi_codegen::command]
|
||||
pub fn filter(&mut self, opt: FilterOpt) {
|
||||
pub struct Filter;
|
||||
|
||||
impl Actor for Filter {
|
||||
type Options = FilterOpt;
|
||||
|
||||
const NAME: &'static str = "filter";
|
||||
|
||||
fn act(_: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let input = InputProxy::show(InputCfg::filter());
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
|
@ -32,5 +38,6 @@ impl Tab {
|
|||
));
|
||||
}
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
36
yazi-actor/src/mgr/filter_do.rs
Normal file
36
yazi-actor/src/mgr/filter_do.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use anyhow::Result;
|
||||
use yazi_fs::Filter;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::tab::FilterOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct FilterDo;
|
||||
|
||||
impl Actor for FilterDo {
|
||||
type Options = FilterOpt;
|
||||
|
||||
const NAME: &'static str = "filter_do";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let filter = if opt.query.is_empty() { None } else { Some(Filter::new(&opt.query, opt.case)?) };
|
||||
|
||||
let hovered = cx.hovered().map(|f| f.urn_owned());
|
||||
if cx.current_mut().files.set_filter(filter) {
|
||||
cx.current_mut().repos(hovered.as_ref());
|
||||
}
|
||||
|
||||
if cx.hovered().map(|f| f.urn()) != hovered.as_ref().map(|u| u.as_urn()) {
|
||||
act!(mgr:hover, cx)?;
|
||||
act!(mgr:peek, cx)?;
|
||||
act!(mgr:watch, cx)?;
|
||||
}
|
||||
|
||||
if opt.done {
|
||||
act!(mgr:update_paged, cx)?;
|
||||
}
|
||||
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,25 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::pin;
|
||||
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
|
||||
use yazi_config::popup::InputCfg;
|
||||
use yazi_fs::FilterCase;
|
||||
use yazi_macro::emit;
|
||||
use yazi_macro::{emit, succ};
|
||||
use yazi_parser::tab::FindOpt;
|
||||
use yazi_proxy::InputProxy;
|
||||
use yazi_shared::{Debounce, errors::InputError, event::Cmd};
|
||||
use yazi_shared::{Debounce, errors::InputError, event::{Cmd, Data}};
|
||||
|
||||
use crate::tab::Tab;
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
impl Tab {
|
||||
#[yazi_codegen::command]
|
||||
pub fn find(&mut self, opt: FindOpt) {
|
||||
pub struct Find;
|
||||
|
||||
impl Actor for Find {
|
||||
type Options = FindOpt;
|
||||
|
||||
const NAME: &'static str = "find";
|
||||
|
||||
fn act(_: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let input = InputProxy::show(InputCfg::find(opt.prev));
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
|
@ -29,5 +35,6 @@ impl Tab {
|
|||
));
|
||||
}
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
31
yazi-actor/src/mgr/find_arrow.rs
Normal file
31
yazi-actor/src/mgr/find_arrow.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::tab::FindArrowOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct FindArrow;
|
||||
|
||||
impl Actor for FindArrow {
|
||||
type Options = FindArrowOpt;
|
||||
|
||||
const NAME: &'static str = "find_arrow";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let tab = cx.tab_mut();
|
||||
let Some(finder) = &mut tab.finder else { succ!() };
|
||||
|
||||
render!(finder.catchup(&tab.current));
|
||||
let offset = if opt.prev {
|
||||
finder.prev(&tab.current.files, tab.current.cursor, false)
|
||||
} else {
|
||||
finder.next(&tab.current.files, tab.current.cursor, false)
|
||||
};
|
||||
|
||||
if let Some(offset) = offset {
|
||||
act!(mgr:arrow, cx, offset)?;
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
39
yazi-actor/src/mgr/find_do.rs
Normal file
39
yazi-actor/src/mgr/find_do.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
use anyhow::Result;
|
||||
use yazi_core::tab::Finder;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::tab::FindDoOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct FindDo;
|
||||
|
||||
impl Actor for FindDo {
|
||||
type Options = FindDoOpt;
|
||||
|
||||
const NAME: &'static str = "find_do";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
if opt.query.is_empty() {
|
||||
return act!(mgr:escape_find, cx);
|
||||
}
|
||||
|
||||
let finder = Finder::new(&opt.query, opt.case)?;
|
||||
if matches!(&cx.tab().finder, Some(f) if f.filter == finder.filter) {
|
||||
succ!();
|
||||
}
|
||||
|
||||
let step = if opt.prev {
|
||||
finder.prev(&cx.current().files, cx.current().cursor, true)
|
||||
} else {
|
||||
finder.next(&cx.current().files, cx.current().cursor, true)
|
||||
};
|
||||
|
||||
if let Some(step) = step {
|
||||
act!(mgr:arrow, cx, step)?;
|
||||
}
|
||||
|
||||
cx.tab_mut().finder = Some(finder);
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
28
yazi-actor/src/mgr/follow.rs
Normal file
28
yazi-actor/src/mgr/follow.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
use anyhow::Result;
|
||||
use yazi_fs::clean_url;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Follow;
|
||||
|
||||
impl Actor for Follow {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "follow";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
let Some(file) = cx.hovered() else { succ!() };
|
||||
let Some(link_to) = &file.link_to else { succ!() };
|
||||
|
||||
if link_to.is_absolute() {
|
||||
act!(mgr:reveal, cx, link_to.to_owned())
|
||||
} else if let Some(p) = file.url.parent_url() {
|
||||
act!(mgr:reveal, cx, clean_url(&p.join(link_to)))
|
||||
} else {
|
||||
succ!()
|
||||
}
|
||||
}
|
||||
}
|
||||
21
yazi-actor/src/mgr/forward.rs
Normal file
21
yazi-actor/src/mgr/forward.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::{VoidOpt, tab::CdSource};
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Forward;
|
||||
|
||||
impl Actor for Forward {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "forward";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
match cx.tab_mut().backstack.shift_forward().cloned() {
|
||||
Some(u) => act!(mgr:cd, cx, (u, CdSource::Forward)),
|
||||
None => succ!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
25
yazi-actor/src/mgr/hardlink.rs
Normal file
25
yazi-actor/src/mgr/hardlink.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::mgr::HardlinkOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Hardlink;
|
||||
|
||||
impl Actor for Hardlink {
|
||||
type Options = HardlinkOpt;
|
||||
|
||||
const NAME: &'static str = "hardlink";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let mgr = &mut cx.core.mgr;
|
||||
let tab = &mgr.tabs[cx.tab];
|
||||
|
||||
if !mgr.yanked.cut {
|
||||
cx.core.tasks.file_hardlink(&mgr.yanked, tab.cwd(), opt.force, opt.follow);
|
||||
}
|
||||
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
33
yazi-actor/src/mgr/hidden.rs
Normal file
33
yazi-actor/src/mgr/hidden.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::mgr::HiddenOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Hidden;
|
||||
|
||||
impl Actor for Hidden {
|
||||
type Options = HiddenOpt;
|
||||
|
||||
const NAME: &'static str = "hidden";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let tab = cx.tab_mut();
|
||||
tab.pref.show_hidden = opt.state.unwrap_or(!tab.pref.show_hidden);
|
||||
|
||||
let hovered = tab.hovered().map(|f| f.url_owned());
|
||||
tab.apply_files_attrs();
|
||||
|
||||
if hovered.as_ref() != tab.hovered().map(|f| &f.url) {
|
||||
act!(mgr:hover, cx, hovered)?;
|
||||
act!(mgr:peek, cx)?;
|
||||
act!(mgr:watch, cx)?;
|
||||
} else if tab.hovered().is_some_and(|f| f.is_dir()) {
|
||||
act!(mgr:peek, cx, true)?;
|
||||
}
|
||||
|
||||
act!(mgr:update_paged, cx)?;
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
52
yazi-actor/src/mgr/hover.rs
Normal file
52
yazi-actor/src/mgr/hover.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
use anyhow::Result;
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_macro::{act, err, render, succ};
|
||||
use yazi_parser::tab::{HoverDoOpt, HoverOpt};
|
||||
use yazi_shared::{event::Data, url::Urn};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Hover;
|
||||
|
||||
impl Actor for Hover {
|
||||
type Options = HoverOpt;
|
||||
|
||||
const NAME: &'static str = "hover";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
if let Some(u) = opt.url {
|
||||
act!(mgr:hover_do, cx, u)?;
|
||||
} else {
|
||||
cx.current_mut().arrow(0);
|
||||
}
|
||||
|
||||
// Publish through DDS
|
||||
let tab = cx.tab();
|
||||
err!(Pubsub::pub_after_hover(tab.id, tab.hovered().map(|h| &h.url)));
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Do
|
||||
pub struct HoverDo;
|
||||
|
||||
impl Actor for HoverDo {
|
||||
type Options = HoverDoOpt;
|
||||
|
||||
const NAME: &'static str = "hover_do";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
// Hover on the file
|
||||
if let Ok(p) = opt.url.strip_prefix(cx.cwd()) {
|
||||
render!(cx.current_mut().hover(Urn::new(p)));
|
||||
}
|
||||
|
||||
// Turn on tracing
|
||||
if cx.hovered().is_some_and(|h| h.url == opt.url) {
|
||||
// `hover(Some)` occurs after user actions, such as create, rename, reveal, etc.
|
||||
// At this point, it's intuitive to track the location of the file regardless.
|
||||
cx.current_mut().trace = Some(opt.url.urn_owned());
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
24
yazi-actor/src/mgr/leave.rs
Normal file
24
yazi-actor/src/mgr/leave.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::{VoidOpt, tab::CdSource};
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Leave;
|
||||
|
||||
impl Actor for Leave {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "leave";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
let url = cx
|
||||
.hovered()
|
||||
.and_then(|h| h.url.parent_url())
|
||||
.filter(|u| u != cx.cwd())
|
||||
.or_else(|| cx.cwd().parent_url());
|
||||
|
||||
if let Some(u) = url { act!(mgr:cd, cx, (u.into_regular(), CdSource::Leave)) } else { succ!() }
|
||||
}
|
||||
}
|
||||
25
yazi-actor/src/mgr/linemode.rs
Normal file
25
yazi-actor/src/mgr/linemode.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::mgr::LinemodeOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Linemode;
|
||||
|
||||
impl Actor for Linemode {
|
||||
type Options = LinemodeOpt;
|
||||
|
||||
const NAME: &'static str = "linemode";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let tab = cx.tab_mut();
|
||||
|
||||
if opt.new != tab.pref.linemode {
|
||||
tab.pref.linemode = opt.new.into_owned();
|
||||
render!();
|
||||
}
|
||||
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
25
yazi-actor/src/mgr/link.rs
Normal file
25
yazi-actor/src/mgr/link.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::mgr::LinkOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Link;
|
||||
|
||||
impl Actor for Link {
|
||||
type Options = LinkOpt;
|
||||
|
||||
const NAME: &'static str = "link";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let mgr = &mut cx.core.mgr;
|
||||
let tab = &mgr.tabs[cx.tab];
|
||||
|
||||
if !mgr.yanked.cut {
|
||||
cx.core.tasks.file_link(&mgr.yanked, tab.cwd(), opt.relative, opt.force);
|
||||
}
|
||||
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,25 @@
|
|||
yazi_macro::mod_flat!(
|
||||
arrow
|
||||
back
|
||||
bulk_rename
|
||||
cd
|
||||
close
|
||||
copy
|
||||
create
|
||||
enter
|
||||
escape
|
||||
filter
|
||||
filter_do
|
||||
find
|
||||
find_arrow
|
||||
find_do
|
||||
follow
|
||||
forward
|
||||
hardlink
|
||||
hidden
|
||||
hover
|
||||
leave
|
||||
linemode
|
||||
link
|
||||
open
|
||||
paste
|
||||
|
|
@ -11,19 +28,28 @@ yazi_macro::mod_flat!(
|
|||
refresh
|
||||
remove
|
||||
rename
|
||||
reveal
|
||||
search
|
||||
seek
|
||||
shell
|
||||
sort
|
||||
spot
|
||||
suspend
|
||||
tab_close
|
||||
tab_create
|
||||
tab_swap
|
||||
tab_switch
|
||||
toggle
|
||||
toggle_all
|
||||
unyank
|
||||
update_files
|
||||
update_mimes
|
||||
update_paged
|
||||
update_peeked
|
||||
update_spotted
|
||||
update_tasks
|
||||
update_yanked
|
||||
visual_mode
|
||||
watch
|
||||
yank
|
||||
);
|
||||
|
|
@ -1,29 +1,34 @@
|
|||
use std::{borrow::Cow, iter};
|
||||
|
||||
use anyhow::Result;
|
||||
use tracing::error;
|
||||
use yazi_config::{YAZI, popup::PickCfg};
|
||||
use yazi_core::tab::Folder;
|
||||
use yazi_fs::File;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::mgr::OpenOpt;
|
||||
use yazi_plugin::isolate;
|
||||
use yazi_proxy::{MgrProxy, PickProxy, TasksProxy, options::OpenDoOpt};
|
||||
use yazi_shared::{MIME_DIR, event::CmdCow, url::Url};
|
||||
use yazi_shared::{MIME_DIR, event::{CmdCow, Data}, url::Url};
|
||||
|
||||
use crate::{mgr::Mgr, tab::Folder, tasks::Tasks};
|
||||
use crate::{Actor, Ctx, mgr::Quit};
|
||||
|
||||
impl Mgr {
|
||||
#[yazi_codegen::command]
|
||||
pub fn open(&mut self, opt: OpenOpt, tasks: &Tasks) {
|
||||
if !self.active_mut().try_escape_visual() {
|
||||
return;
|
||||
}
|
||||
let Some(hovered) = self.hovered().map(|h| h.url_owned()) else {
|
||||
return;
|
||||
};
|
||||
pub struct Open;
|
||||
|
||||
impl Actor for Open {
|
||||
type Options = OpenOpt;
|
||||
|
||||
const NAME: &'static str = "open";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
act!(mgr:escape_visual, cx)?;
|
||||
|
||||
let Some(hovered) = cx.hovered().map(|h| h.url_owned()) else { succ!() };
|
||||
|
||||
let mut selected =
|
||||
if opt.hovered { Box::new(iter::once(&hovered)) } else { self.selected_or_hovered() };
|
||||
if Self::quit_with_selected(opt, &mut selected) {
|
||||
return;
|
||||
if opt.hovered { Box::new(iter::once(&hovered)) } else { cx.tab().selected_or_hovered() };
|
||||
if Quit::quit_with_selected(opt, &mut selected) {
|
||||
succ!();
|
||||
}
|
||||
|
||||
let mut todo = vec![];
|
||||
|
|
@ -31,9 +36,9 @@ impl Mgr {
|
|||
.cloned()
|
||||
.enumerate()
|
||||
.map(|(i, u)| {
|
||||
if self.mimetype.contains(&u) {
|
||||
if cx.mgr.mimetype.contains(&u) {
|
||||
(u, "")
|
||||
} else if self.guess_folder(&u) {
|
||||
} else if Self::guess_folder(cx, &u) {
|
||||
(u, MIME_DIR)
|
||||
} else {
|
||||
todo.push(i);
|
||||
|
|
@ -42,10 +47,9 @@ impl Mgr {
|
|||
})
|
||||
.collect();
|
||||
|
||||
let cwd = self.cwd().clone();
|
||||
let cwd = cx.cwd().clone();
|
||||
if todo.is_empty() {
|
||||
return self
|
||||
.open_do(OpenDoOpt { cwd, hovered, targets, interactive: opt.interactive }, tasks);
|
||||
return act!(mgr:open_do, cx, OpenDoOpt { cwd, hovered, targets, interactive: opt.interactive });
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
|
@ -64,25 +68,34 @@ impl Mgr {
|
|||
|
||||
MgrProxy::open_do(OpenDoOpt { cwd, hovered, targets, interactive: opt.interactive });
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
||||
#[yazi_codegen::command]
|
||||
pub fn open_do(&mut self, opt: OpenDoOpt, tasks: &Tasks) {
|
||||
// --- Do
|
||||
pub struct OpenDo;
|
||||
|
||||
impl Actor for OpenDo {
|
||||
type Options = OpenDoOpt;
|
||||
|
||||
const NAME: &'static str = "open_do";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let mut targets = opt.targets;
|
||||
targets.iter_mut().filter(|(_, m)| m.is_empty()).for_each(|(u, m)| {
|
||||
*m = self.mimetype.by_url(u).unwrap_or_default();
|
||||
*m = cx.mgr.mimetype.by_url(u).unwrap_or_default();
|
||||
});
|
||||
|
||||
targets.retain(|(_, m)| !m.is_empty());
|
||||
if targets.is_empty() {
|
||||
return;
|
||||
succ!();
|
||||
} else if !opt.interactive {
|
||||
return tasks.process_from_files(opt.cwd, opt.hovered, targets);
|
||||
succ!(cx.tasks.process_from_files(opt.cwd, opt.hovered, targets));
|
||||
}
|
||||
|
||||
let openers: Vec<_> = YAZI.opener.all(YAZI.open.common(&targets).into_iter());
|
||||
if openers.is_empty() {
|
||||
return;
|
||||
succ!();
|
||||
}
|
||||
|
||||
let pick = PickProxy::show(PickCfg::open(openers.iter().map(|o| o.desc()).collect()));
|
||||
|
|
@ -92,9 +105,12 @@ impl Mgr {
|
|||
TasksProxy::open_with(Cow::Borrowed(openers[choice]), opt.cwd, urls);
|
||||
}
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
||||
fn guess_folder(&self, url: &Url) -> bool {
|
||||
impl Open {
|
||||
fn guess_folder(cx: &Ctx, url: &Url) -> bool {
|
||||
let Some(p) = url.parent_url() else {
|
||||
return true;
|
||||
};
|
||||
|
|
@ -105,9 +121,9 @@ impl Mgr {
|
|||
})
|
||||
};
|
||||
|
||||
find(Some(self.current()))
|
||||
|| find(self.parent())
|
||||
|| find(self.hovered_folder())
|
||||
|| find(self.active().history.get(&p))
|
||||
find(Some(cx.current()))
|
||||
|| find(cx.parent())
|
||||
|| find(cx.hovered_folder())
|
||||
|| find(cx.tab().history.get(&p))
|
||||
}
|
||||
}
|
||||
29
yazi-actor/src/mgr/paste.rs
Normal file
29
yazi-actor/src/mgr/paste.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::mgr::PasteOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Paste;
|
||||
|
||||
impl Actor for Paste {
|
||||
type Options = PasteOpt;
|
||||
|
||||
const NAME: &'static str = "paste";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let mgr = &mut cx.core.mgr;
|
||||
let tab = &mgr.tabs[cx.tab];
|
||||
let (src, dest) = (mgr.yanked.iter().collect::<Vec<_>>(), tab.cwd());
|
||||
|
||||
if mgr.yanked.cut {
|
||||
cx.core.tasks.file_cut(&src, dest, opt.force);
|
||||
|
||||
mgr.tabs.iter_mut().for_each(|t| _ = t.selected.remove_many(&src));
|
||||
act!(mgr:unyank, cx)
|
||||
} else {
|
||||
succ!(cx.core.tasks.file_copy(&src, dest, opt.force, opt.follow));
|
||||
}
|
||||
}
|
||||
}
|
||||
54
yazi-actor/src/mgr/peek.rs
Normal file
54
yazi-actor/src/mgr/peek.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::mgr::PeekOpt;
|
||||
use yazi_proxy::HIDER;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Peek;
|
||||
|
||||
impl Actor for Peek {
|
||||
type Options = PeekOpt;
|
||||
|
||||
const NAME: &'static str = "peek";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let Some(hovered) = cx.hovered().cloned() else {
|
||||
succ!(cx.tab_mut().preview.reset());
|
||||
};
|
||||
if HIDER.try_acquire().is_err() {
|
||||
succ!(cx.tab_mut().preview.reset_image());
|
||||
}
|
||||
|
||||
let mime = cx.mgr.mimetype.by_file_owned(&hovered).unwrap_or_default();
|
||||
let folder = cx.tab().hovered_folder().map(|f| (f.offset, f.cha));
|
||||
|
||||
if !cx.tab().preview.same_url(&hovered.url) {
|
||||
cx.tab_mut().preview.skip = folder.map(|f| f.0).unwrap_or_default();
|
||||
}
|
||||
if !cx.tab().preview.same_file(&hovered, &mime) {
|
||||
cx.tab_mut().preview.reset();
|
||||
}
|
||||
|
||||
if matches!(opt.only_if, Some(u) if u != hovered.url) {
|
||||
succ!();
|
||||
}
|
||||
|
||||
if let Some(skip) = opt.skip {
|
||||
let preview = &mut cx.tab_mut().preview;
|
||||
if opt.upper_bound {
|
||||
preview.skip = preview.skip.min(skip);
|
||||
} else {
|
||||
preview.skip = skip;
|
||||
}
|
||||
}
|
||||
|
||||
if hovered.is_dir() {
|
||||
cx.tab_mut().preview.go_folder(hovered, folder.map(|(_, cha)| cha), opt.force);
|
||||
} else {
|
||||
cx.tab_mut().preview.go(hovered, mime, opt.force);
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +1,34 @@
|
|||
use std::{ffi::OsString, time::Duration};
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::{select, time};
|
||||
use yazi_boot::ARGS;
|
||||
use yazi_config::popup::ConfirmCfg;
|
||||
use yazi_macro::emit;
|
||||
use yazi_macro::{emit, succ};
|
||||
use yazi_parser::mgr::{OpenOpt, QuitOpt};
|
||||
use yazi_proxy::ConfirmProxy;
|
||||
use yazi_shared::{event::EventQuit, url::Url};
|
||||
use yazi_shared::{event::{Data, EventQuit}, url::Url};
|
||||
|
||||
use crate::{mgr::Mgr, tasks::Tasks};
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
impl Mgr {
|
||||
#[yazi_codegen::command]
|
||||
pub fn quit(&self, opt: QuitOpt, tasks: &Tasks) {
|
||||
pub struct Quit;
|
||||
|
||||
impl Actor for Quit {
|
||||
type Options = QuitOpt;
|
||||
|
||||
const NAME: &'static str = "quit";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let event = opt.into();
|
||||
|
||||
let ongoing = tasks.ongoing().clone();
|
||||
let ongoing = cx.tasks().ongoing().clone();
|
||||
let (left, left_names) = {
|
||||
let ongoing = ongoing.lock();
|
||||
(ongoing.len(), ongoing.values().take(11).map(|t| t.name.clone()).collect())
|
||||
};
|
||||
|
||||
if left == 0 {
|
||||
emit!(Quit(event));
|
||||
return;
|
||||
succ!(emit!(Quit(event)));
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
|
@ -52,8 +57,11 @@ impl Mgr {
|
|||
emit!(Quit(event));
|
||||
}
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
||||
impl Quit {
|
||||
pub(super) fn quit_with_selected<'a, I>(opt: OpenOpt, selected: I) -> bool
|
||||
where
|
||||
I: Iterator<Item = &'a Url>,
|
||||
39
yazi-actor/src/mgr/refresh.rs
Normal file
39
yazi-actor/src/mgr/refresh.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
use anyhow::Result;
|
||||
use crossterm::{execute, terminal::SetTitle};
|
||||
use yazi_config::YAZI;
|
||||
use yazi_fs::CWD;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_shared::event::Data;
|
||||
use yazi_term::tty::TTY;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Refresh;
|
||||
|
||||
impl Actor for Refresh {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "refresh";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
if let (_, Some(s)) = (CWD.set(cx.cwd()), YAZI.mgr.title()) {
|
||||
execute!(TTY.writer(), SetTitle(s)).ok();
|
||||
}
|
||||
|
||||
cx.tab_mut().apply_files_attrs();
|
||||
|
||||
if let Some(p) = cx.parent() {
|
||||
cx.mgr.watcher.trigger_dirs(&[cx.current(), p]);
|
||||
} else {
|
||||
cx.mgr.watcher.trigger_dirs(&[cx.current()]);
|
||||
}
|
||||
|
||||
act!(mgr:peek, cx, false)?;
|
||||
act!(mgr:watch, cx)?;
|
||||
act!(mgr:update_paged, cx)?;
|
||||
|
||||
cx.tasks().prework_sorted(&cx.current().files);
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
70
yazi-actor/src/mgr/remove.rs
Normal file
70
yazi-actor/src/mgr/remove.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
use anyhow::Result;
|
||||
use yazi_config::popup::ConfirmCfg;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::mgr::RemoveOpt;
|
||||
use yazi_proxy::{ConfirmProxy, MgrProxy};
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Remove;
|
||||
|
||||
impl Actor for Remove {
|
||||
type Options = RemoveOpt;
|
||||
|
||||
const NAME: &'static str = "remove";
|
||||
|
||||
fn act(cx: &mut Ctx, mut opt: Self::Options) -> Result<Data> {
|
||||
act!(mgr:escape_visual, cx)?;
|
||||
|
||||
opt.targets = if opt.hovered {
|
||||
cx.hovered().map_or(vec![], |h| vec![h.url.clone()])
|
||||
} else {
|
||||
cx.tab().selected_or_hovered().cloned().collect()
|
||||
};
|
||||
|
||||
if opt.targets.is_empty() {
|
||||
succ!();
|
||||
} else if opt.force {
|
||||
return act!(mgr:remove_do, cx, opt);
|
||||
}
|
||||
|
||||
let confirm = ConfirmProxy::show(if opt.permanently {
|
||||
ConfirmCfg::delete(&opt.targets)
|
||||
} else {
|
||||
ConfirmCfg::trash(&opt.targets)
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
if confirm.await {
|
||||
MgrProxy::remove_do(opt.targets, opt.permanently);
|
||||
}
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Do
|
||||
pub struct RemoveDo;
|
||||
|
||||
impl Actor for RemoveDo {
|
||||
type Options = RemoveOpt;
|
||||
|
||||
const NAME: &'static str = "remove_do";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let mgr = &mut cx.mgr;
|
||||
|
||||
mgr.tabs.iter_mut().for_each(|t| {
|
||||
t.selected.remove_many(&opt.targets);
|
||||
});
|
||||
|
||||
for u in &opt.targets {
|
||||
mgr.yanked.remove(u);
|
||||
}
|
||||
|
||||
mgr.yanked.catchup_revision(false);
|
||||
cx.tasks.file_remove(opt.targets, opt.permanently);
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
@ -3,23 +3,29 @@ use tokio::fs;
|
|||
use yazi_config::popup::{ConfirmCfg, InputCfg};
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_fs::{File, FilesOp, maybe_exists, ok_or_not_found, paths_to_same_file, realname};
|
||||
use yazi_macro::err;
|
||||
use yazi_macro::{act, err, succ};
|
||||
use yazi_parser::mgr::RenameOpt;
|
||||
use yazi_proxy::{ConfirmProxy, InputProxy, TabProxy, WATCHER};
|
||||
use yazi_shared::{Id, url::{Url, UrnBuf}};
|
||||
use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy, WATCHER};
|
||||
use yazi_shared::{Id, event::Data, url::{Url, UrnBuf}};
|
||||
|
||||
use crate::mgr::Mgr;
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
impl Mgr {
|
||||
#[yazi_codegen::command]
|
||||
pub fn rename(&mut self, opt: RenameOpt) {
|
||||
if !self.active_mut().try_escape_visual() {
|
||||
return;
|
||||
} else if !opt.hovered && !self.active().selected.is_empty() {
|
||||
return self.bulk_rename();
|
||||
pub struct Rename;
|
||||
|
||||
impl Actor for Rename {
|
||||
type Options = RenameOpt;
|
||||
|
||||
const NAME: &'static str = "rename";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
act!(mgr:escape_visual, cx)?;
|
||||
|
||||
if !opt.hovered && !cx.tab_mut().selected.is_empty() {
|
||||
return act!(mgr:bulk_rename, cx);
|
||||
}
|
||||
|
||||
let Some(hovered) = self.hovered() else { return };
|
||||
let Some(hovered) = cx.hovered() else { succ!() };
|
||||
|
||||
let name = Self::empty_url_part(&hovered.url, &opt.empty);
|
||||
let cursor = match opt.cursor.as_ref() {
|
||||
"start" => Some(0),
|
||||
|
|
@ -33,8 +39,7 @@ impl Mgr {
|
|||
_ => None,
|
||||
};
|
||||
|
||||
let old = hovered.url_owned();
|
||||
let tab = self.tabs.active().id;
|
||||
let (tab, old) = (cx.tab().id, hovered.url_owned());
|
||||
let mut input = InputProxy::show(InputCfg::rename().with_value(name).with_cursor(cursor));
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
|
@ -45,14 +50,17 @@ impl Mgr {
|
|||
|
||||
let new = Url::from(old.parent().unwrap().join(name));
|
||||
if opt.force || !maybe_exists(&new).await || paths_to_same_file(&old, &new).await {
|
||||
Self::rename_do(tab, old, new).await.ok();
|
||||
Self::r#do(tab, old, new).await.ok();
|
||||
} else if ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await {
|
||||
Self::rename_do(tab, old, new).await.ok();
|
||||
Self::r#do(tab, old, new).await.ok();
|
||||
}
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
||||
async fn rename_do(tab: Id, old: Url, new: Url) -> Result<()> {
|
||||
impl Rename {
|
||||
async fn r#do(tab: Id, old: Url, new: Url) -> Result<()> {
|
||||
let Some((p_old, n_old)) = old.pair() else { return Ok(()) };
|
||||
let Some((p_new, n_new)) = new.pair() else { return Ok(()) };
|
||||
let _permit = WATCHER.acquire().await.unwrap();
|
||||
|
|
@ -73,7 +81,7 @@ impl Mgr {
|
|||
FilesOp::Upserting(p_new, [(n_new, file)].into()).emit();
|
||||
}
|
||||
|
||||
TabProxy::reveal(&new);
|
||||
MgrProxy::reveal(&new);
|
||||
err!(Pubsub::pub_after_rename(tab, &old, &new));
|
||||
Ok(())
|
||||
}
|
||||
40
yazi-actor/src/mgr/reveal.rs
Normal file
40
yazi-actor/src/mgr/reveal.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
use anyhow::Result;
|
||||
use yazi_fs::{File, FilesOp};
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::tab::RevealOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Reveal;
|
||||
|
||||
impl Actor for Reveal {
|
||||
type Options = RevealOpt;
|
||||
|
||||
const NAME: &'static str = "reveal";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let Some((parent, child)) = opt.target.pair() else { succ!() };
|
||||
|
||||
// Cd to the parent directory
|
||||
act!(mgr:cd, cx, (parent.clone(), opt.source))?;
|
||||
|
||||
// Try to hover on the child file
|
||||
let tab = cx.tab_mut();
|
||||
tab.current.hover(child.as_urn());
|
||||
|
||||
// If the child is not hovered, which means it doesn't exist,
|
||||
// create a dummy file
|
||||
if !opt.no_dummy && tab.hovered().is_none_or(|f| &child != f.urn()) {
|
||||
let op = FilesOp::Creating(parent, vec![File::from_dummy(opt.target.clone(), None)]);
|
||||
tab.current.update_pub(tab.id, op);
|
||||
}
|
||||
|
||||
// Now, we can safely hover on the target
|
||||
act!(mgr:hover, cx, Some(opt.target))?;
|
||||
|
||||
act!(mgr:peek, cx)?;
|
||||
act!(mgr:watch, cx)?;
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +1,60 @@
|
|||
use std::{borrow::Cow, mem, time::Duration};
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::pin;
|
||||
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
|
||||
use tracing::error;
|
||||
use yazi_config::popup::InputCfg;
|
||||
use yazi_fs::{FilesOp, cha::Cha};
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_plugin::external;
|
||||
use yazi_proxy::{AppProxy, InputProxy, MgrProxy, TabProxy, options::{SearchOpt, SearchOptVia}};
|
||||
use yazi_proxy::{InputProxy, MgrProxy, options::{SearchOpt, SearchOptVia}};
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::tab::Tab;
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
impl Tab {
|
||||
pub fn search(&mut self, opt: impl TryInto<SearchOpt>) {
|
||||
let Ok(mut opt): Result<SearchOpt, _> = opt.try_into() else {
|
||||
return AppProxy::notify_error("Invalid `search` option", "Failed to parse search option");
|
||||
};
|
||||
pub struct Search;
|
||||
|
||||
if let Some(handle) = self.search.take() {
|
||||
impl Actor for Search {
|
||||
type Options = SearchOpt;
|
||||
|
||||
const NAME: &'static str = "search";
|
||||
|
||||
fn act(cx: &mut Ctx, mut opt: Self::Options) -> Result<Data> {
|
||||
if let Some(handle) = cx.tab_mut().search.take() {
|
||||
handle.abort();
|
||||
}
|
||||
|
||||
let mut input =
|
||||
InputProxy::show(InputCfg::search(opt.via.into_str()).with_value(&*opt.subject));
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Some(Ok(subject)) = input.recv().await {
|
||||
opt.subject = Cow::Owned(subject);
|
||||
TabProxy::search_do(opt);
|
||||
MgrProxy::search_do(opt);
|
||||
}
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn search_do(&mut self, opt: impl TryInto<SearchOpt>) {
|
||||
let Ok(opt): Result<SearchOpt, _> = opt.try_into() else {
|
||||
return error!("Failed to parse search option for `search_do`");
|
||||
};
|
||||
// --- Do
|
||||
pub struct SearchDo;
|
||||
|
||||
if let Some(handle) = self.search.take() {
|
||||
impl Actor for SearchDo {
|
||||
type Options = SearchOpt;
|
||||
|
||||
const NAME: &'static str = "search_do";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let tab = cx.tab_mut();
|
||||
if let Some(handle) = tab.search.take() {
|
||||
handle.abort();
|
||||
}
|
||||
|
||||
let cwd = self.cwd().to_search(opt.subject.as_ref());
|
||||
let hidden = self.pref.show_hidden;
|
||||
let cwd = tab.cwd().to_search(opt.subject.as_ref());
|
||||
let hidden = tab.pref.show_hidden;
|
||||
|
||||
self.search = Some(tokio::spawn(async move {
|
||||
tab.search = Some(tokio::spawn(async move {
|
||||
let rx = match opt.via {
|
||||
SearchOptVia::Rg => external::rg(external::RgOpt {
|
||||
cwd: cwd.clone(),
|
||||
|
|
@ -67,7 +79,7 @@ impl Tab {
|
|||
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(5000, Duration::from_millis(500));
|
||||
pin!(rx);
|
||||
|
||||
let ((), ticket) = (TabProxy::cd(&cwd), FilesOp::prepare(&cwd));
|
||||
let ((), ticket) = (MgrProxy::cd(&cwd), FilesOp::prepare(&cwd));
|
||||
while let Some(chunk) = rx.next().await {
|
||||
FilesOp::Part(cwd.clone(), chunk, ticket).emit();
|
||||
}
|
||||
|
|
@ -75,16 +87,30 @@ impl Tab {
|
|||
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
pub(super) fn search_stop(&mut self) {
|
||||
if let Some(handle) = self.search.take() {
|
||||
handle.abort();
|
||||
}
|
||||
if self.cwd().is_search() {
|
||||
let rep = self.history.remove_or(&self.cwd().to_regular());
|
||||
drop(mem::replace(&mut self.current, rep));
|
||||
MgrProxy::refresh();
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Stop
|
||||
pub struct SearchStop;
|
||||
|
||||
impl Actor for SearchStop {
|
||||
type Options = ();
|
||||
|
||||
const NAME: &'static str = "search_stop";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
let tab = cx.tab_mut();
|
||||
if let Some(handle) = tab.search.take() {
|
||||
handle.abort();
|
||||
}
|
||||
|
||||
if tab.cwd().is_search() {
|
||||
let rep = tab.history.remove_or(&tab.cwd().to_regular());
|
||||
drop(mem::replace(&mut tab.current, rep));
|
||||
act!(mgr:refresh, cx)?;
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
33
yazi-actor/src/mgr/seek.rs
Normal file
33
yazi-actor/src/mgr/seek.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use anyhow::Result;
|
||||
use yazi_config::YAZI;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::mgr::SeekOpt;
|
||||
use yazi_plugin::isolate;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Seek;
|
||||
|
||||
impl Actor for Seek {
|
||||
type Options = SeekOpt;
|
||||
|
||||
const NAME: &'static str = "seek";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let Some(hovered) = cx.hovered() else {
|
||||
succ!(cx.tab_mut().preview.reset());
|
||||
};
|
||||
|
||||
let Some(mime) = cx.mgr.mimetype.by_file(hovered) else {
|
||||
succ!(cx.tab_mut().preview.reset());
|
||||
};
|
||||
|
||||
let Some(previewer) = YAZI.plugin.previewer(&hovered.url, mime) else {
|
||||
succ!(cx.tab_mut().preview.reset());
|
||||
};
|
||||
|
||||
isolate::seek_sync(&previewer.run, hovered.clone(), opt.units);
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +1,26 @@
|
|||
use std::{borrow::Cow, fmt::Display};
|
||||
use std::borrow::Cow;
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_config::{opener::OpenerRule, popup::InputCfg};
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::tab::ShellOpt;
|
||||
use yazi_proxy::{AppProxy, InputProxy, TasksProxy};
|
||||
use yazi_proxy::{InputProxy, TasksProxy};
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::tab::Tab;
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
impl Tab {
|
||||
pub fn shell(&mut self, opt: impl TryInto<ShellOpt, Error = impl Display>) {
|
||||
if !self.try_escape_visual() {
|
||||
return;
|
||||
}
|
||||
pub struct Shell;
|
||||
|
||||
let mut opt = match opt.try_into() {
|
||||
Ok(o) => o as ShellOpt,
|
||||
Err(e) => return AppProxy::notify_warn("`shell` command", e),
|
||||
};
|
||||
impl Actor for Shell {
|
||||
type Options = ShellOpt;
|
||||
|
||||
let cwd = opt.cwd.take().unwrap_or_else(|| self.cwd().clone());
|
||||
let selected = self.hovered_and_selected().cloned().collect();
|
||||
const NAME: &'static str = "shell";
|
||||
|
||||
fn act(cx: &mut Ctx, mut opt: Self::Options) -> Result<Data> {
|
||||
act!(mgr:escape_visual, cx)?;
|
||||
|
||||
let cwd = opt.cwd.take().unwrap_or_else(|| cx.cwd().clone());
|
||||
let selected = cx.tab().hovered_and_selected().cloned().collect();
|
||||
|
||||
let input = opt.interactive.then(|| {
|
||||
InputProxy::show(InputCfg::shell(opt.block).with_value(&*opt.run).with_cursor(opt.cursor))
|
||||
|
|
@ -48,5 +50,7 @@ impl Tab {
|
|||
selected,
|
||||
);
|
||||
});
|
||||
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
40
yazi-actor/src/mgr/sort.rs
Normal file
40
yazi-actor/src/mgr/sort.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_fs::SortBy;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_shared::event::{CmdCow, Data};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Sort;
|
||||
|
||||
impl Actor for Sort {
|
||||
type Options = CmdCow;
|
||||
|
||||
const NAME: &'static str = "sort";
|
||||
|
||||
fn act(cx: &mut Ctx, c: Self::Options) -> Result<Data> {
|
||||
let mut new = cx.tab().pref.clone();
|
||||
new.sort_by = c.first_str().and_then(|s| SortBy::from_str(s).ok()).unwrap_or(new.sort_by);
|
||||
new.sort_reverse = c.maybe_bool("reverse").unwrap_or(new.sort_reverse);
|
||||
new.sort_dir_first = c.maybe_bool("dir-first").unwrap_or(new.sort_dir_first);
|
||||
new.sort_sensitive = c.maybe_bool("sensitive").unwrap_or(new.sort_sensitive);
|
||||
new.sort_translit = c.maybe_bool("translit").unwrap_or(new.sort_translit);
|
||||
|
||||
if new == cx.tab().pref {
|
||||
succ!();
|
||||
}
|
||||
|
||||
cx.tab_mut().pref = new;
|
||||
cx.tab_mut().apply_files_attrs();
|
||||
act!(mgr:hover, cx)?;
|
||||
|
||||
cx.tasks.prework_sorted(&cx.mgr.tabs[cx.tab].current.files);
|
||||
act!(mgr:peek, cx)?;
|
||||
act!(mgr:watch, cx)?;
|
||||
act!(mgr:update_paged, cx)?;
|
||||
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
32
yazi-actor/src/mgr/spot.rs
Normal file
32
yazi-actor/src/mgr/spot.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::mgr::SpotOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Spot;
|
||||
|
||||
impl Actor for Spot {
|
||||
type Options = SpotOpt;
|
||||
|
||||
const NAME: &'static str = "spot";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let Some(hovered) = cx.hovered().cloned() else { succ!() };
|
||||
|
||||
let mime = cx.mgr.mimetype.by_file_owned(&hovered).unwrap_or_default();
|
||||
// if !self.active().spot.same_file(&hovered, &mime) {
|
||||
// self.active_mut().spot.reset();
|
||||
// }
|
||||
|
||||
if let Some(skip) = opt.skip {
|
||||
cx.tab_mut().spot.skip = skip;
|
||||
} else if !cx.tab().spot.same_url(&hovered.url) {
|
||||
cx.tab_mut().spot.skip = 0;
|
||||
}
|
||||
|
||||
cx.tab_mut().spot.go(hovered, mime);
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
22
yazi-actor/src/mgr/suspend.rs
Normal file
22
yazi-actor/src/mgr/suspend.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Suspend;
|
||||
|
||||
impl Actor for Suspend {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "suspend";
|
||||
|
||||
fn act(_: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
#[cfg(unix)]
|
||||
unsafe {
|
||||
libc::raise(libc::SIGTSTP);
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
36
yazi-actor/src/mgr/tab_close.rs
Normal file
36
yazi-actor/src/mgr/tab_close.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::mgr::TabCloseOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct TabClose;
|
||||
|
||||
impl Actor for TabClose {
|
||||
type Options = TabCloseOpt;
|
||||
|
||||
const NAME: &'static str = "tab_close";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let len = cx.tabs().len();
|
||||
if len < 2 || opt.idx >= len {
|
||||
succ!();
|
||||
}
|
||||
|
||||
let tabs = cx.tabs_mut();
|
||||
tabs.remove(opt.idx).shutdown();
|
||||
|
||||
if opt.idx > tabs.cursor {
|
||||
tabs.set_idx(tabs.cursor);
|
||||
} else {
|
||||
tabs.set_idx(usize::min(tabs.cursor + 1, tabs.len() - 1));
|
||||
}
|
||||
|
||||
let cx = &mut Ctx::active(cx.core);
|
||||
act!(mgr:refresh, cx)?;
|
||||
act!(mgr:peek, cx, true)?;
|
||||
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
55
yazi-actor/src/mgr/tab_create.rs
Normal file
55
yazi-actor/src/mgr/tab_create.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
use anyhow::Result;
|
||||
use yazi_core::tab::Tab;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::{mgr::TabCreateOpt, tab::CdSource};
|
||||
use yazi_proxy::AppProxy;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
const MAX_TABS: usize = 9;
|
||||
|
||||
pub struct TabCreate;
|
||||
|
||||
impl Actor for TabCreate {
|
||||
type Options = TabCreateOpt;
|
||||
|
||||
const NAME: &'static str = "tab_create";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
if cx.tabs().len() >= MAX_TABS {
|
||||
succ!(AppProxy::notify_warn(
|
||||
"Too many tabs",
|
||||
"You can only open up to 9 tabs at the same time."
|
||||
));
|
||||
}
|
||||
|
||||
let mut tab = Tab::default();
|
||||
let (cd, url) = if let Some(wd) = opt.wd {
|
||||
(true, wd)
|
||||
} else if let Some(h) = cx.hovered() {
|
||||
tab.pref = cx.tab().pref.clone();
|
||||
tab.apply_files_attrs();
|
||||
(false, h.url.to_regular())
|
||||
} else {
|
||||
tab.pref = cx.tab().pref.clone();
|
||||
tab.apply_files_attrs();
|
||||
(true, cx.cwd().to_regular())
|
||||
};
|
||||
|
||||
let tabs = &mut cx.mgr.tabs;
|
||||
tabs.items.insert(tabs.cursor + 1, tab);
|
||||
tabs.set_idx(tabs.cursor + 1);
|
||||
|
||||
let cx = &mut Ctx::active(cx.core);
|
||||
if cd {
|
||||
act!(mgr:cd, cx, (url, CdSource::Tab))?;
|
||||
} else {
|
||||
act!(mgr:reveal, cx, (url, CdSource::Tab))?;
|
||||
}
|
||||
|
||||
act!(mgr:refresh, cx)?;
|
||||
act!(mgr:peek, cx, true)?;
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
32
yazi-actor/src/mgr/tab_swap.rs
Normal file
32
yazi-actor/src/mgr/tab_swap.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::ArrowOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct TabSwap;
|
||||
|
||||
impl Actor for TabSwap {
|
||||
type Options = ArrowOpt;
|
||||
|
||||
const NAME: &'static str = "tab_swap";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let tabs = cx.tabs_mut();
|
||||
|
||||
let new = opt.step.add(tabs.cursor, tabs.len(), 0);
|
||||
if new == tabs.cursor {
|
||||
succ!();
|
||||
}
|
||||
|
||||
tabs.items.swap(tabs.cursor, new);
|
||||
tabs.set_idx(new);
|
||||
|
||||
let cx = &mut Ctx::active(cx.core);
|
||||
act!(mgr:refresh, cx)?;
|
||||
act!(mgr:peek, cx, true)?;
|
||||
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
34
yazi-actor/src/mgr/tab_switch.rs
Normal file
34
yazi-actor/src/mgr/tab_switch.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::mgr::TabSwitchOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct TabSwitch;
|
||||
|
||||
impl Actor for TabSwitch {
|
||||
type Options = TabSwitchOpt;
|
||||
|
||||
const NAME: &'static str = "tab_switch";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let tabs = cx.tabs_mut();
|
||||
let idx = if opt.relative {
|
||||
opt.step.saturating_add_unsigned(tabs.cursor).rem_euclid(tabs.len() as _) as _
|
||||
} else {
|
||||
opt.step as usize
|
||||
};
|
||||
|
||||
if idx == tabs.cursor || idx >= tabs.len() {
|
||||
succ!();
|
||||
}
|
||||
|
||||
tabs.set_idx(idx);
|
||||
let cx = &mut Ctx::active(cx.core);
|
||||
|
||||
act!(mgr:refresh, cx)?;
|
||||
act!(mgr:peek, cx, true)?;
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
34
yazi-actor/src/mgr/toggle.rs
Normal file
34
yazi-actor/src/mgr/toggle.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render_and, succ};
|
||||
use yazi_parser::tab::ToggleOpt;
|
||||
use yazi_proxy::AppProxy;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Toggle;
|
||||
|
||||
impl Actor for Toggle {
|
||||
type Options = ToggleOpt;
|
||||
|
||||
const NAME: &'static str = "toggle";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let tab = cx.tab_mut();
|
||||
let Some(url) = opt.url.as_ref().or(tab.current.hovered().map(|h| &h.url)) else { succ!() };
|
||||
|
||||
let b = match opt.state {
|
||||
Some(true) => render_and!(tab.selected.add(url)),
|
||||
Some(false) => render_and!(tab.selected.remove(url)) | true,
|
||||
None => render_and!(tab.selected.remove(url) || tab.selected.add(url)),
|
||||
};
|
||||
|
||||
if !b {
|
||||
AppProxy::notify_warn(
|
||||
"Toggle",
|
||||
"This file cannot be selected, due to path nesting conflict.",
|
||||
);
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
49
yazi-actor/src/mgr/toggle_all.rs
Normal file
49
yazi-actor/src/mgr/toggle_all.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::tab::ToggleAllOpt;
|
||||
use yazi_proxy::AppProxy;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct ToggleAll;
|
||||
|
||||
impl Actor for ToggleAll {
|
||||
type Options = ToggleAllOpt;
|
||||
|
||||
const NAME: &'static str = "toggle_all";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
use yazi_shared::Either::*;
|
||||
let tab = cx.tab_mut();
|
||||
|
||||
let it = tab.current.files.iter().map(|f| &f.url);
|
||||
let either = match opt.state {
|
||||
Some(true) if opt.urls.is_empty() => Left((vec![], it.collect())),
|
||||
Some(true) => Right((vec![], opt.urls)),
|
||||
Some(false) if opt.urls.is_empty() => Left((it.collect(), vec![])),
|
||||
Some(false) => Right((opt.urls, vec![])),
|
||||
None if opt.urls.is_empty() => Left(it.partition(|&u| tab.selected.contains_key(u))),
|
||||
None => Right(opt.urls.into_iter().partition(|u| tab.selected.contains_key(u))),
|
||||
};
|
||||
|
||||
let warn = match either {
|
||||
Left((removal, addition)) => {
|
||||
render!(tab.selected.remove_many(&removal) > 0);
|
||||
render!(tab.selected.add_many(&addition), > 0) != addition.len()
|
||||
}
|
||||
Right((removal, addition)) => {
|
||||
render!(tab.selected.remove_many(&removal) > 0);
|
||||
render!(tab.selected.add_many(&addition), > 0) != addition.len()
|
||||
}
|
||||
};
|
||||
|
||||
if warn {
|
||||
AppProxy::notify_warn(
|
||||
"Toggle all",
|
||||
"Some files cannot be selected, due to path nesting conflict.",
|
||||
);
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
26
yazi-actor/src/mgr/unyank.rs
Normal file
26
yazi-actor/src/mgr/unyank.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::Actor;
|
||||
|
||||
pub struct Unyank;
|
||||
|
||||
impl Actor for Unyank {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "unyank";
|
||||
|
||||
fn act(cx: &mut crate::Ctx, _: Self::Options) -> Result<Data> {
|
||||
let repeek = cx.hovered().is_some_and(|f| f.is_dir() && cx.mgr.yanked.contains_in(&f.url));
|
||||
cx.mgr.yanked.clear();
|
||||
|
||||
render!(cx.mgr.yanked.catchup_revision(false));
|
||||
if repeek {
|
||||
act!(mgr:peek, cx, true)?;
|
||||
}
|
||||
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
112
yazi-actor/src/mgr/update_files.rs
Normal file
112
yazi-actor/src/mgr/update_files.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
use anyhow::Result;
|
||||
use yazi_core::{mgr::LINKED, tab::Folder};
|
||||
use yazi_fs::FilesOp;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::mgr::UpdateFilesOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct UpdateFiles;
|
||||
|
||||
impl Actor for UpdateFiles {
|
||||
type Options = UpdateFilesOpt;
|
||||
|
||||
const NAME: &'static str = "update_files";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let revision = cx.current().files.revision;
|
||||
let linked: Vec<_> = LINKED.read().from_dir(opt.op.cwd()).map(|u| opt.op.rebase(u)).collect();
|
||||
for op in [opt.op].into_iter().chain(linked) {
|
||||
cx.mgr.yanked.apply_op(&op);
|
||||
Self::update_tab(cx, op).ok();
|
||||
}
|
||||
|
||||
render!(cx.mgr.yanked.catchup_revision(false));
|
||||
cx.tab_mut().apply_files_attrs();
|
||||
|
||||
if revision != cx.current().files.revision {
|
||||
act!(mgr:hover, cx)?;
|
||||
act!(mgr:peek, cx, false)?;
|
||||
act!(mgr:watch, cx)?;
|
||||
act!(mgr:update_paged, cx)?;
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateFiles {
|
||||
fn update_tab(cx: &mut Ctx, op: FilesOp) -> Result<Data> {
|
||||
let url = op.cwd();
|
||||
cx.tab_mut().selected.apply_op(&op);
|
||||
|
||||
if url == cx.cwd() {
|
||||
Self::update_current(cx, op)
|
||||
} else if matches!(cx.parent(), Some(p) if *url == p.url) {
|
||||
Self::update_parent(cx, op)
|
||||
} else if matches!(cx.hovered(), Some(h) if *url == h.url) {
|
||||
Self::update_hovered(cx, op)
|
||||
} else {
|
||||
Self::update_history(cx, op)
|
||||
}
|
||||
}
|
||||
|
||||
fn update_parent(cx: &mut Ctx, op: FilesOp) -> Result<Data> {
|
||||
let tab = cx.tab_mut();
|
||||
|
||||
let urn = tab.current.url.urn();
|
||||
let leave = matches!(op, FilesOp::Deleting(_, ref urns) if urns.contains(urn));
|
||||
|
||||
if let Some(f) = tab.parent.as_mut() {
|
||||
render!(f.update_pub(tab.id, op));
|
||||
render!(f.hover(urn));
|
||||
}
|
||||
|
||||
if leave {
|
||||
act!(mgr:leave, cx)?;
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
|
||||
fn update_current(cx: &mut Ctx, op: FilesOp) -> Result<Data> {
|
||||
let calc = !matches!(op, FilesOp::Size(..) | FilesOp::Deleting(..));
|
||||
|
||||
let id = cx.tab().id;
|
||||
if !cx.current_mut().update_pub(id, op) {
|
||||
succ!();
|
||||
}
|
||||
|
||||
if calc {
|
||||
cx.tasks.prework_sorted(&cx.current().files);
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
|
||||
fn update_hovered(cx: &mut Ctx, op: FilesOp) -> Result<Data> {
|
||||
let (id, url) = (cx.tab().id, op.cwd());
|
||||
let folder = cx.tab_mut().history.entry(url.clone()).or_insert_with(|| Folder::from(url));
|
||||
|
||||
if folder.update_pub(id, op) {
|
||||
act!(mgr:peek, cx, true)?;
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
|
||||
fn update_history(cx: &mut Ctx, op: FilesOp) -> Result<Data> {
|
||||
let tab = &mut cx.tab_mut();
|
||||
let leave = tab.parent.as_ref().and_then(|f| f.url.parent_url().map(|p| (p, f.url.urn()))).is_some_and(
|
||||
|(p, n)| matches!(op, FilesOp::Deleting(ref parent, ref urns) if *parent == p && urns.contains(n)),
|
||||
);
|
||||
|
||||
tab
|
||||
.history
|
||||
.entry(op.cwd().clone())
|
||||
.or_insert_with(|| Folder::from(op.cwd()))
|
||||
.update_pub(tab.id, op);
|
||||
|
||||
if leave {
|
||||
act!(mgr:leave, cx)?;
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
57
yazi-actor/src/mgr/update_mimes.rs
Normal file
57
yazi-actor/src/mgr/update_mimes.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_core::mgr::LINKED;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::mgr::UpdateMimesOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct UpdateMimes;
|
||||
|
||||
impl Actor for UpdateMimes {
|
||||
type Options = UpdateMimesOpt;
|
||||
|
||||
const NAME: &'static str = "update_mimes";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let linked = LINKED.read();
|
||||
let updates = opt
|
||||
.updates
|
||||
.into_iter()
|
||||
.flat_map(|(key, value)| key.into_url().zip(value.into_string()))
|
||||
.filter(|(url, mime)| cx.mgr.mimetype.by_url(url) != Some(mime))
|
||||
.fold(HashMap::new(), |mut map, (u, m)| {
|
||||
for u in linked.from_file(&u) {
|
||||
map.insert(u, m.to_string());
|
||||
}
|
||||
map.insert(u, m.into_owned());
|
||||
map
|
||||
});
|
||||
|
||||
drop(linked);
|
||||
if updates.is_empty() {
|
||||
succ!();
|
||||
}
|
||||
|
||||
let affected: Vec<_> = cx
|
||||
.current()
|
||||
.paginate(cx.current().page)
|
||||
.iter()
|
||||
.filter(|&f| updates.contains_key(&f.url))
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let repeek = cx.hovered().is_some_and(|f| updates.contains_key(&f.url));
|
||||
cx.mgr.mimetype.extend(updates);
|
||||
|
||||
if repeek {
|
||||
act!(mgr:peek, cx, false)?;
|
||||
}
|
||||
cx.tasks.fetch_paged(&affected, &cx.mgr.mimetype);
|
||||
cx.tasks.preload_paged(&affected, &cx.mgr.mimetype);
|
||||
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
27
yazi-actor/src/mgr/update_paged.rs
Normal file
27
yazi-actor/src/mgr/update_paged.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::mgr::UpdatePagedOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct UpdatePaged;
|
||||
|
||||
impl Actor for UpdatePaged {
|
||||
type Options = UpdatePagedOpt;
|
||||
|
||||
const NAME: &'static str = "update_paged";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
if opt.only_if.is_some_and(|u| u != *cx.cwd()) {
|
||||
succ!();
|
||||
}
|
||||
|
||||
let targets = cx.current().paginate(opt.page.unwrap_or(cx.current().page));
|
||||
if !targets.is_empty() {
|
||||
cx.tasks().fetch_paged(targets, &cx.mgr.mimetype);
|
||||
cx.tasks().preload_paged(targets, &cx.mgr.mimetype);
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
27
yazi-actor/src/mgr/update_peeked.rs
Normal file
27
yazi-actor/src/mgr/update_peeked.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::tab::UpdatePeekedOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::Actor;
|
||||
|
||||
pub struct UpdatePeeked;
|
||||
|
||||
impl Actor for UpdatePeeked {
|
||||
type Options = UpdatePeekedOpt;
|
||||
|
||||
const NAME: &'static str = "update_peeked";
|
||||
|
||||
fn act(cx: &mut crate::Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let Some(hovered) = cx.hovered().map(|h| &h.url) else {
|
||||
succ!(cx.tab_mut().preview.reset());
|
||||
};
|
||||
|
||||
if opt.lock.url == *hovered {
|
||||
cx.tab_mut().preview.lock = Some(opt.lock);
|
||||
render!();
|
||||
}
|
||||
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
36
yazi-actor/src/mgr/update_spotted.rs
Normal file
36
yazi-actor/src/mgr/update_spotted.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::tab::UpdateSpottedOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct UpdateSpotted;
|
||||
|
||||
impl Actor for UpdateSpotted {
|
||||
type Options = UpdateSpottedOpt;
|
||||
|
||||
const NAME: &'static str = "update_spotted";
|
||||
|
||||
fn act(cx: &mut Ctx, mut opt: Self::Options) -> Result<Data> {
|
||||
let tab = cx.tab_mut();
|
||||
let Some(hovered) = tab.hovered().map(|h| &h.url) else {
|
||||
succ!(tab.spot.reset());
|
||||
};
|
||||
|
||||
if opt.lock.url != *hovered {
|
||||
succ!();
|
||||
}
|
||||
|
||||
if tab.spot.lock.as_ref().is_none_or(|l| l.id != opt.lock.id) {
|
||||
tab.spot.skip = opt.lock.selected().unwrap_or_default();
|
||||
} else if let Some(s) = opt.lock.selected() {
|
||||
tab.spot.skip = s;
|
||||
} else {
|
||||
opt.lock.select(Some(tab.spot.skip));
|
||||
}
|
||||
|
||||
tab.spot.lock = Some(opt.lock);
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
19
yazi-actor/src/mgr/update_tasks.rs
Normal file
19
yazi-actor/src/mgr/update_tasks.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::mgr::UpdateTasksOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct UpdateTasks;
|
||||
|
||||
impl Actor for UpdateTasks {
|
||||
type Options = UpdateTasksOpt;
|
||||
|
||||
const NAME: &'static str = "update_tasks";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
cx.mgr.watcher.push_files(opt.urls);
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
24
yazi-actor/src/mgr/update_yanked.rs
Normal file
24
yazi-actor/src/mgr/update_yanked.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use anyhow::Result;
|
||||
use yazi_core::mgr::Yanked;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::mgr::UpdateYankedOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct UpdateYanked;
|
||||
|
||||
impl Actor for UpdateYanked {
|
||||
type Options = UpdateYankedOpt;
|
||||
|
||||
const NAME: &'static str = "update_yanked";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
if opt.urls.is_empty() && cx.mgr.yanked.is_empty() {
|
||||
succ!();
|
||||
}
|
||||
|
||||
cx.mgr.yanked = Yanked::new(opt.cut, opt.urls);
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
30
yazi-actor/src/mgr/visual_mode.rs
Normal file
30
yazi-actor/src/mgr/visual_mode.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use std::collections::BTreeSet;
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_core::tab::Mode;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::tab::VisualModeOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct VisualMode;
|
||||
|
||||
impl Actor for VisualMode {
|
||||
type Options = VisualModeOpt;
|
||||
|
||||
const NAME: &'static str = "visual_mode";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let tab = cx.tab_mut();
|
||||
|
||||
let idx = tab.current.cursor;
|
||||
if opt.unset {
|
||||
tab.mode = Mode::Unset(idx, BTreeSet::from([idx]));
|
||||
} else {
|
||||
tab.mode = Mode::Select(idx, BTreeSet::from([idx]));
|
||||
};
|
||||
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
25
yazi-actor/src/mgr/watch.rs
Normal file
25
yazi-actor/src/mgr/watch.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use std::iter;
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Watch;
|
||||
|
||||
impl Actor for Watch {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "watch";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
let it = iter::once(cx.core.mgr.tabs.active().cwd())
|
||||
.chain(cx.core.mgr.tabs.parent().map(|p| &p.url))
|
||||
.chain(cx.core.mgr.tabs.hovered().filter(|h| h.is_dir()).map(|h| &h.url));
|
||||
|
||||
cx.core.mgr.watcher.watch(it);
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
24
yazi-actor/src/mgr/yank.rs
Normal file
24
yazi-actor/src/mgr/yank.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use anyhow::Result;
|
||||
use yazi_core::mgr::Yanked;
|
||||
use yazi_macro::{act, render};
|
||||
use yazi_parser::mgr::YankOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Yank;
|
||||
|
||||
impl Actor for Yank {
|
||||
type Options = YankOpt;
|
||||
|
||||
const NAME: &'static str = "yank";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
act!(mgr:escape_visual, cx)?;
|
||||
|
||||
cx.mgr.yanked = Yanked::new(opt.cut, cx.tab().selected_or_hovered().cloned().collect());
|
||||
render!(cx.mgr.yanked.catchup_revision(true));
|
||||
|
||||
act!(mgr:escape_select, cx)
|
||||
}
|
||||
}
|
||||
19
yazi-actor/src/pick/arrow.rs
Normal file
19
yazi-actor/src/pick/arrow.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::ArrowOpt;
|
||||
use yazi_shared::event::Data;
|
||||
use yazi_widgets::Scrollable;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Arrow;
|
||||
|
||||
impl Actor for Arrow {
|
||||
type Options = ArrowOpt;
|
||||
|
||||
const NAME: &'static str = "arrow";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
succ!(render!(cx.pick.scroll(opt.step)));
|
||||
}
|
||||
}
|
||||
26
yazi-actor/src/pick/close.rs
Normal file
26
yazi-actor/src/pick/close.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use anyhow::{Result, anyhow};
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::pick::CloseOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Close;
|
||||
|
||||
impl Actor for Close {
|
||||
type Options = CloseOpt;
|
||||
|
||||
const NAME: &'static str = "close";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let pick = &mut cx.pick;
|
||||
if let Some(cb) = pick.callback.take() {
|
||||
_ = cb.send(if opt.submit { Ok(pick.cursor) } else { Err(anyhow!("canceled")) });
|
||||
}
|
||||
|
||||
pick.cursor = 0;
|
||||
pick.offset = 0;
|
||||
pick.visible = false;
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
27
yazi-actor/src/pick/show.rs
Normal file
27
yazi-actor/src/pick/show.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::pick::ShowOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Show;
|
||||
|
||||
impl Actor for Show {
|
||||
type Options = ShowOpt;
|
||||
|
||||
const NAME: &'static str = "show";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
act!(pick:close, cx)?;
|
||||
|
||||
let pick = &mut cx.pick;
|
||||
pick.title = opt.cfg.title;
|
||||
pick.items = opt.cfg.items;
|
||||
pick.position = opt.cfg.position;
|
||||
|
||||
pick.callback = Some(opt.tx);
|
||||
pick.visible = true;
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
30
yazi-actor/src/spot/arrow.rs
Normal file
30
yazi-actor/src/spot/arrow.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::ArrowOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Arrow;
|
||||
|
||||
impl Actor for Arrow {
|
||||
type Options = ArrowOpt;
|
||||
|
||||
const NAME: &'static str = "arrow";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let spot = &mut cx.tab_mut().spot;
|
||||
let Some(lock) = &mut spot.lock else { succ!() };
|
||||
|
||||
let new = opt.step.add(spot.skip, lock.len().unwrap_or(u16::MAX as _), 0);
|
||||
let Some(old) = lock.selected() else {
|
||||
return act!(mgr:spot, cx, new);
|
||||
};
|
||||
|
||||
lock.select(Some(new));
|
||||
let new = lock.selected().unwrap();
|
||||
|
||||
spot.skip = new;
|
||||
succ!(render!(new != old));
|
||||
}
|
||||
}
|
||||
18
yazi-actor/src/spot/close.rs
Normal file
18
yazi-actor/src/spot/close.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Close;
|
||||
|
||||
impl Actor for Close {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "close";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
succ!(cx.tab_mut().spot.reset());
|
||||
}
|
||||
}
|
||||
36
yazi-actor/src/spot/copy.rs
Normal file
36
yazi-actor/src/spot/copy.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::spot::CopyOpt;
|
||||
use yazi_shared::event::Data;
|
||||
use yazi_widgets::CLIPBOARD;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Copy;
|
||||
|
||||
impl Actor for Copy {
|
||||
type Options = CopyOpt;
|
||||
|
||||
const NAME: &'static str = "copy";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let spot = &cx.tab().spot;
|
||||
let Some(lock) = &spot.lock else { succ!() };
|
||||
let Some(table) = lock.table() else { succ!() };
|
||||
|
||||
let mut s = String::new();
|
||||
match opt.r#type.as_ref() {
|
||||
"cell" => {
|
||||
let Some(cell) = table.selected_cell() else { succ!() };
|
||||
s = cell.to_string();
|
||||
}
|
||||
"line" => {
|
||||
// TODO
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
futures::executor::block_on(CLIPBOARD.set(s));
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
19
yazi-actor/src/spot/swipe.rs
Normal file
19
yazi-actor/src/spot/swipe.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::act;
|
||||
use yazi_parser::ArrowOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Swipe;
|
||||
|
||||
impl Actor for Swipe {
|
||||
type Options = ArrowOpt;
|
||||
|
||||
const NAME: &'static str = "swipe";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
act!(mgr:arrow, cx, opt)?;
|
||||
act!(mgr:spot, cx)
|
||||
}
|
||||
}
|
||||
24
yazi-actor/src/tasks/arrow.rs
Normal file
24
yazi-actor/src/tasks/arrow.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use anyhow::Result;
|
||||
use yazi_core::tasks::Tasks;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::ArrowOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Arrow;
|
||||
|
||||
impl Actor for Arrow {
|
||||
type Options = ArrowOpt;
|
||||
|
||||
const NAME: &'static str = "arrow";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let tasks = &mut cx.tasks;
|
||||
|
||||
let old = tasks.cursor;
|
||||
tasks.cursor = opt.step.add(tasks.cursor, tasks.summaries.len(), Tasks::limit());
|
||||
|
||||
succ!(render!(tasks.cursor != old));
|
||||
}
|
||||
}
|
||||
27
yazi-actor/src/tasks/cancel.rs
Normal file
27
yazi-actor/src/tasks/cancel.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Cancel;
|
||||
|
||||
impl Actor for Cancel {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "cancel";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
let tasks = &mut cx.tasks;
|
||||
|
||||
let id = tasks.ongoing().lock().get_id(tasks.cursor);
|
||||
if id.map(|id| tasks.scheduler.cancel(id)) != Some(true) {
|
||||
succ!();
|
||||
}
|
||||
|
||||
tasks.summaries = tasks.paginate();
|
||||
act!(tasks:arrow, cx)?;
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
27
yazi-actor/src/tasks/close.rs
Normal file
27
yazi-actor/src/tasks/close.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Close;
|
||||
|
||||
impl Actor for Close {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "close";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
let tasks = &mut cx.tasks;
|
||||
if !tasks.visible {
|
||||
succ!();
|
||||
}
|
||||
|
||||
tasks.visible = false;
|
||||
tasks.summaries = Vec::new();
|
||||
|
||||
act!(tasks:arrow, cx)?;
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,28 @@
|
|||
use std::io::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
use crossterm::{execute, terminal::{disable_raw_mode, enable_raw_mode}};
|
||||
use scopeguard::defer;
|
||||
use tokio::{io::{AsyncReadExt, stdin}, select, sync::mpsc, time};
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_proxy::{AppProxy, HIDER};
|
||||
use yazi_shared::{event::CmdCow, terminal_clear};
|
||||
use yazi_shared::{event::Data, terminal_clear};
|
||||
use yazi_term::tty::TTY;
|
||||
|
||||
use crate::tasks::Tasks;
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
impl Tasks {
|
||||
pub fn inspect(&self, _: CmdCow) {
|
||||
let ongoing = self.ongoing().clone();
|
||||
let Some(id) = ongoing.lock().get_id(self.cursor) else {
|
||||
return;
|
||||
pub struct Inspect;
|
||||
|
||||
impl Actor for Inspect {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "inspect";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
let ongoing = cx.tasks.ongoing().clone();
|
||||
let Some(id) = ongoing.lock().get_id(cx.tasks.cursor) else {
|
||||
succ!();
|
||||
};
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
|
@ -67,5 +76,6 @@ impl Tasks {
|
|||
answer = stdin.read_u8().await.unwrap_or(b'q');
|
||||
}
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
22
yazi-actor/src/tasks/open_with.rs
Normal file
22
yazi-actor/src/tasks/open_with.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_proxy::options::OpenWithOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct OpenWith;
|
||||
|
||||
impl Actor for OpenWith {
|
||||
type Options = OpenWithOpt;
|
||||
|
||||
const NAME: &'static str = "open_with";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
succ!(cx.tasks.process_from_opener(
|
||||
opt.cwd,
|
||||
opt.opener,
|
||||
opt.targets.into_iter().map(|u| u.into_path().into_os_string()).collect(),
|
||||
));
|
||||
}
|
||||
}
|
||||
18
yazi-actor/src/tasks/process_exec.rs
Normal file
18
yazi-actor/src/tasks/process_exec.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_proxy::options::ProcessExecOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct ProcessExec;
|
||||
|
||||
impl Actor for ProcessExec {
|
||||
type Options = ProcessExecOpt;
|
||||
|
||||
const NAME: &'static str = "process_exec";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
succ!(cx.tasks.scheduler.process_open(opt));
|
||||
}
|
||||
}
|
||||
27
yazi-actor/src/tasks/show.rs
Normal file
27
yazi-actor/src/tasks/show.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Show;
|
||||
|
||||
impl Actor for Show {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &'static str = "show";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
let tasks = &mut cx.tasks;
|
||||
if tasks.visible {
|
||||
succ!();
|
||||
}
|
||||
|
||||
tasks.visible = true;
|
||||
tasks.summaries = tasks.paginate();
|
||||
|
||||
act!(tasks:arrow, cx)?;
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
19
yazi-actor/src/which/callback.rs
Normal file
19
yazi-actor/src/which/callback.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::which::CallbackOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Callback;
|
||||
|
||||
impl Actor for Callback {
|
||||
type Options = CallbackOpt;
|
||||
|
||||
const NAME: &'static str = "callback";
|
||||
|
||||
fn act(_: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
opt.tx.try_send(opt.idx)?;
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
28
yazi-actor/src/which/show.rs
Normal file
28
yazi-actor/src/which/show.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::which::ShowOpt;
|
||||
use yazi_shared::event::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Show;
|
||||
|
||||
impl Actor for Show {
|
||||
type Options = ShowOpt;
|
||||
|
||||
const NAME: &'static str = "show";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
if opt.cands.is_empty() {
|
||||
succ!();
|
||||
}
|
||||
|
||||
let which = &mut cx.which;
|
||||
which.times = 0;
|
||||
which.cands = opt.cands.into_iter().map(|c| c.into()).collect();
|
||||
|
||||
which.visible = true;
|
||||
which.silent = opt.silent;
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
|
@ -10,8 +10,6 @@ repository = "https://github.com/sxyazi/yazi"
|
|||
|
||||
[dependencies]
|
||||
yazi-adapter = { path = "../yazi-adapter", version = "25.6.11" }
|
||||
yazi-boot = { path = "../yazi-boot", version = "25.6.11" }
|
||||
yazi-codegen = { path = "../yazi-codegen", version = "25.6.11" }
|
||||
yazi-config = { path = "../yazi-config", version = "25.6.11" }
|
||||
yazi-dds = { path = "../yazi-dds", version = "25.6.11" }
|
||||
yazi-fs = { path = "../yazi-fs", version = "25.6.11" }
|
||||
|
|
@ -21,7 +19,6 @@ yazi-plugin = { path = "../yazi-plugin", version = "25.6.11" }
|
|||
yazi-proxy = { path = "../yazi-proxy", version = "25.6.11" }
|
||||
yazi-scheduler = { path = "../yazi-scheduler", version = "25.6.11" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "25.6.11" }
|
||||
yazi-term = { path = "../yazi-term", version = "25.6.11" }
|
||||
yazi-widgets = { path = "../yazi-widgets", version = "25.6.11" }
|
||||
|
||||
# External dependencies
|
||||
|
|
@ -32,8 +29,6 @@ indexmap = { workspace = true }
|
|||
notify = { version = "8.1.0", default-features = false, features = [ "macos_fsevent" ] }
|
||||
parking_lot = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
scopeguard = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
tokio-util = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ use yazi_widgets::Scrollable;
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct Cmp {
|
||||
pub(super) caches: HashMap<PathBuf, Vec<CmpItem>>,
|
||||
pub(super) cands: Vec<CmpItem>,
|
||||
pub(super) offset: usize,
|
||||
pub cursor: usize,
|
||||
pub caches: HashMap<PathBuf, Vec<CmpItem>>,
|
||||
pub cands: Vec<CmpItem>,
|
||||
pub offset: usize,
|
||||
pub cursor: usize,
|
||||
|
||||
pub(super) ticket: Id,
|
||||
pub visible: bool,
|
||||
pub ticket: Id,
|
||||
pub visible: bool,
|
||||
}
|
||||
|
||||
impl Cmp {
|
||||
|
|
@ -30,3 +30,17 @@ impl Cmp {
|
|||
#[inline]
|
||||
pub fn rel_cursor(&self) -> usize { self.cursor - self.offset }
|
||||
}
|
||||
|
||||
impl Scrollable for Cmp {
|
||||
#[inline]
|
||||
fn total(&self) -> usize { self.cands.len() }
|
||||
|
||||
#[inline]
|
||||
fn limit(&self) -> usize { self.cands.len().min(10) }
|
||||
|
||||
#[inline]
|
||||
fn cursor_mut(&mut self) -> &mut usize { &mut self.cursor }
|
||||
|
||||
#[inline]
|
||||
fn offset_mut(&mut self) -> &mut usize { &mut self.offset }
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue