mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-23 07:41:03 +00:00
feat: cross-directory selection (#693)
This commit is contained in:
parent
6aae7683ba
commit
3f27532c57
21 changed files with 275 additions and 209 deletions
70
Cargo.lock
generated
70
Cargo.lock
generated
|
|
@ -72,9 +72,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.11"
|
||||
version = "0.6.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
|
||||
checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
|
|
@ -224,9 +224,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.14.0"
|
||||
version = "3.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
|
||||
checksum = "d32a994c2b3ca201d9b263612a374263f05e7adde37c4707f693dcd375076d1f"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
|
|
@ -290,9 +290,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.0"
|
||||
version = "4.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f"
|
||||
checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
|
@ -300,9 +300,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.0"
|
||||
version = "4.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99"
|
||||
checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
|
@ -312,9 +312,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.0"
|
||||
version = "4.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "299353be8209bd133b049bf1c63582d184a8b39fd9c04f15fe65f50f88bdfe6c"
|
||||
checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
|
@ -331,9 +331,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_complete_nushell"
|
||||
version = "4.5.0"
|
||||
version = "4.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92cd8ac7c321605310e4b3bdc047daf27c6ca1b50f6ae59b22aa0661ab45e295"
|
||||
checksum = "80d0e48e026ce7df2040239117d25e4e79714907420c70294a5ce4b6bbe6a7b6"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"clap_complete",
|
||||
|
|
@ -348,7 +348,7 @@ dependencies = [
|
|||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.49",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -749,7 +749,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.49",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1221,7 +1221,7 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.49",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1295,9 +1295,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
|
@ -1424,9 +1424,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
|||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.29"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb"
|
||||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||
|
||||
[[package]]
|
||||
name = "plist"
|
||||
|
|
@ -1444,9 +1444,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.11"
|
||||
version = "0.17.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f6c3c3e617595665b8ea2ff95a86066be38fb121ff920a9c0eb282abcd1da5a"
|
||||
checksum = "78c2378060fb13acff3ba0325b83442c1d2c44fbb76df481160ddc1687cce160"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crc32fast",
|
||||
|
|
@ -1695,7 +1695,7 @@ checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.49",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1868,7 +1868,7 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.49",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1884,9 +1884,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.48"
|
||||
version = "2.0.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||
checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -1931,7 +1931,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.49",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2050,7 +2050,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.49",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2101,9 +2101,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.5"
|
||||
version = "0.22.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99e68c159e8f5ba8a28c4eb7b0c0c190d77bb479047ca713270048145a9ad28a"
|
||||
checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
|
|
@ -2143,7 +2143,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.49",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2366,7 +2366,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.49",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
|
|
@ -2388,7 +2388,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.49",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
|
@ -2658,9 +2658,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
|||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b1dbce9e90e5404c5a52ed82b1d13fc8cfbdad85033b6f57546ffd1265f8451"
|
||||
checksum = "d90f4e0f530c4c69f62b80d839e9ef3855edc9cba471a160c4d692deed62b401"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
@ -2857,7 +2857,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
"syn 2.0.49",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -37,7 +37,10 @@ impl Pattern {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_wildcard(&self) -> bool { self.inner.as_str() == "*" || self.inner.as_str() == "*/" }
|
||||
pub fn any_file(&self) -> bool { self.inner.as_str() == "*" }
|
||||
|
||||
#[inline]
|
||||
pub fn any_dir(&self) -> bool { self.inner.as_str() == "*/" }
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Pattern {
|
||||
|
|
|
|||
|
|
@ -11,24 +11,6 @@ pub struct Plugin {
|
|||
pub previewers: Vec<PluginRule>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct PluginRule {
|
||||
#[serde(default)]
|
||||
pub id: u8,
|
||||
pub cond: Option<Condition>,
|
||||
pub name: Option<Pattern>,
|
||||
pub mime: Option<Pattern>,
|
||||
#[serde(rename = "exec")]
|
||||
#[serde(deserialize_with = "super::exec_deserialize")]
|
||||
pub cmd: Cmd,
|
||||
#[serde(default)]
|
||||
pub sync: bool,
|
||||
#[serde(default)]
|
||||
pub multi: bool,
|
||||
#[serde(default)]
|
||||
pub prio: Priority,
|
||||
}
|
||||
|
||||
impl Default for Plugin {
|
||||
fn default() -> Self {
|
||||
#[derive(Deserialize)]
|
||||
|
|
@ -52,11 +34,17 @@ impl Default for Plugin {
|
|||
}
|
||||
|
||||
let mut shadow = toml::from_str::<Outer>(&MERGED_YAZI).unwrap().plugin;
|
||||
if shadow.append_preloaders.iter().any(|r| r.name.as_ref().is_some_and(|p| p.is_wildcard())) {
|
||||
shadow.preloaders.retain(|r| !r.name.as_ref().is_some_and(|p| p.is_wildcard()));
|
||||
if shadow.append_preloaders.iter().any(|r| r.any_file()) {
|
||||
shadow.preloaders.retain(|r| !r.any_file());
|
||||
}
|
||||
if shadow.append_previewers.iter().any(|r| r.name.as_ref().is_some_and(|p| p.is_wildcard())) {
|
||||
shadow.previewers.retain(|r| !r.name.as_ref().is_some_and(|p| p.is_wildcard()));
|
||||
if shadow.append_preloaders.iter().any(|r| r.any_dir()) {
|
||||
shadow.preloaders.retain(|r| !r.any_dir());
|
||||
}
|
||||
if shadow.append_previewers.iter().any(|r| r.any_file()) {
|
||||
shadow.previewers.retain(|r| !r.any_file());
|
||||
}
|
||||
if shadow.append_previewers.iter().any(|r| r.any_dir()) {
|
||||
shadow.previewers.retain(|r| !r.any_dir());
|
||||
}
|
||||
|
||||
Preset::mix(&mut shadow.preloaders, shadow.prepend_preloaders, shadow.append_preloaders);
|
||||
|
|
@ -104,3 +92,29 @@ impl Plugin {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct PluginRule {
|
||||
#[serde(default)]
|
||||
pub id: u8,
|
||||
pub cond: Option<Condition>,
|
||||
pub name: Option<Pattern>,
|
||||
pub mime: Option<Pattern>,
|
||||
#[serde(rename = "exec")]
|
||||
#[serde(deserialize_with = "super::exec_deserialize")]
|
||||
pub cmd: Cmd,
|
||||
#[serde(default)]
|
||||
pub sync: bool,
|
||||
#[serde(default)]
|
||||
pub multi: bool,
|
||||
#[serde(default)]
|
||||
pub prio: Priority,
|
||||
}
|
||||
|
||||
impl PluginRule {
|
||||
#[inline]
|
||||
fn any_file(&self) -> bool { self.name.as_ref().is_some_and(|p| p.any_file()) }
|
||||
|
||||
#[inline]
|
||||
fn any_dir(&self) -> bool { self.name.as_ref().is_some_and(|p| p.any_dir()) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,13 @@ impl Icon {
|
|||
}
|
||||
|
||||
let mut outer = IconOuter::deserialize(deserializer)?;
|
||||
if outer.append_rules.iter().any(|r| r.name.any_file()) {
|
||||
outer.rules.retain(|r| !r.name.any_file());
|
||||
}
|
||||
if outer.append_rules.iter().any(|r| r.name.any_dir()) {
|
||||
outer.rules.retain(|r| !r.name.any_dir());
|
||||
}
|
||||
|
||||
Preset::mix(&mut outer.rules, outer.prepend_rules, outer.append_rules);
|
||||
|
||||
Ok(
|
||||
|
|
|
|||
|
|
@ -71,12 +71,12 @@ impl Manager {
|
|||
error!("preload in watcher failed: {e}");
|
||||
}
|
||||
|
||||
Self::_open_do(opt.interactive, done);
|
||||
Self::_open_do(done, opt.interactive);
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn _open_do(interactive: bool, targets: Vec<(Url, String)>) {
|
||||
pub fn _open_do(targets: Vec<(Url, String)>, interactive: bool) {
|
||||
emit!(Call(
|
||||
Cmd::new("open_do").with_bool("interactive", interactive).with_data(targets),
|
||||
Layer::Manager
|
||||
|
|
|
|||
|
|
@ -1,17 +1,20 @@
|
|||
use yazi_shared::event::Cmd;
|
||||
use yazi_config::popup::InputCfg;
|
||||
use yazi_shared::{emit, event::Cmd, fs::Url, Layer};
|
||||
|
||||
use crate::{manager::Manager, tasks::Tasks};
|
||||
use crate::{input::Input, manager::Manager, tasks::Tasks};
|
||||
|
||||
pub struct Opt {
|
||||
force: bool,
|
||||
permanently: bool,
|
||||
targets: Vec<Url>,
|
||||
}
|
||||
|
||||
impl From<Cmd> for Opt {
|
||||
fn from(c: Cmd) -> Self {
|
||||
fn from(mut c: Cmd) -> Self {
|
||||
Self {
|
||||
force: c.named.contains_key("force"),
|
||||
permanently: c.named.contains_key("permanently"),
|
||||
targets: c.take_data().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,8 +25,44 @@ impl Manager {
|
|||
return;
|
||||
}
|
||||
|
||||
let mut opt = opt.into() as Opt;
|
||||
opt.targets = self.selected_or_hovered().into_iter().cloned().collect();
|
||||
|
||||
if opt.force {
|
||||
return self.remove_do(opt, tasks);
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut result = Input::_show(if opt.permanently {
|
||||
InputCfg::delete(opt.targets.len())
|
||||
} else {
|
||||
InputCfg::trash(opt.targets.len())
|
||||
});
|
||||
|
||||
if let Some(Ok(choice)) = result.recv().await {
|
||||
if choice != "y" && choice != "Y" {
|
||||
return;
|
||||
}
|
||||
|
||||
Self::_remove_do(opt.targets, opt.permanently);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn _remove_do(targets: Vec<Url>, permanently: bool) {
|
||||
emit!(Call(
|
||||
Cmd::new("remove_do").with_bool("permanently", permanently).with_data(targets),
|
||||
Layer::Manager
|
||||
));
|
||||
}
|
||||
|
||||
pub fn remove_do(&mut self, opt: impl Into<Opt>, tasks: &Tasks) {
|
||||
let opt = opt.into() as Opt;
|
||||
let targets = self.selected_or_hovered().into_iter().cloned().collect();
|
||||
tasks.file_remove(targets, opt.force, opt.permanently);
|
||||
for u in &opt.targets {
|
||||
self.active_mut().selected.remove(u);
|
||||
}
|
||||
|
||||
tasks.file_remove(opt.targets, opt.permanently);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,18 @@ use yazi_shared::{emit, event::Cmd, Layer};
|
|||
use crate::notify::{Message, Notify};
|
||||
|
||||
impl Notify {
|
||||
#[inline]
|
||||
pub fn _push_warn(title: &str, content: &str) {
|
||||
emit!(Call(
|
||||
Cmd::new("notify")
|
||||
.with("title", title)
|
||||
.with("content", content)
|
||||
.with("level", "warn")
|
||||
.with("timeout", 5),
|
||||
Layer::App
|
||||
));
|
||||
}
|
||||
|
||||
pub fn push(&mut self, msg: impl TryInto<Message>) {
|
||||
let Ok(mut msg) = msg.try_into() else {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_shared::{emit, event::Cmd, Layer};
|
||||
|
||||
use crate::notify::Notify;
|
||||
|
|
@ -22,13 +23,13 @@ impl TryFrom<Cmd> for Opt {
|
|||
}
|
||||
|
||||
impl Notify {
|
||||
pub fn tick(&mut self, opt: impl TryInto<Opt>) {
|
||||
pub fn tick(&mut self, opt: impl TryInto<Opt>, area: Rect) {
|
||||
self.tick_handle.take().map(|h| h.abort());
|
||||
let Ok(opt) = opt.try_into() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let limit = self.limit();
|
||||
let limit = self.limit(area);
|
||||
if limit == 0 {
|
||||
return;
|
||||
}
|
||||
|
|
@ -37,14 +38,14 @@ impl Notify {
|
|||
if m.timeout.is_zero() {
|
||||
m.percent = m.percent.saturating_sub(20);
|
||||
} else if m.percent < 100 {
|
||||
m.percent = m.percent.saturating_add(20);
|
||||
m.percent += 20;
|
||||
} else {
|
||||
m.timeout = m.timeout.saturating_sub(opt.interval);
|
||||
}
|
||||
}
|
||||
|
||||
self.messages.retain(|m| m.percent > 0 || !m.timeout.is_zero());
|
||||
let limit = self.limit();
|
||||
let limit = self.limit(area);
|
||||
let timeouts: Vec<_> = self.messages[..limit]
|
||||
.iter()
|
||||
.filter(|&m| m.percent == 100 && !m.timeout.is_zero())
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use yazi_shared::event::Cmd;
|
||||
|
||||
use super::{Level, NOTIFY_BORDER};
|
||||
|
|
@ -12,7 +13,6 @@ pub struct Message {
|
|||
pub instant: Instant,
|
||||
pub timeout: Duration,
|
||||
|
||||
pub lines: usize,
|
||||
pub percent: u8,
|
||||
}
|
||||
|
||||
|
|
@ -26,7 +26,6 @@ impl TryFrom<Cmd> for Message {
|
|||
}
|
||||
|
||||
let content = c.take_name("content").ok_or(())?;
|
||||
let lines = content.lines().count();
|
||||
Ok(Self {
|
||||
title: c.take_name("title").ok_or(())?,
|
||||
content,
|
||||
|
|
@ -35,7 +34,6 @@ impl TryFrom<Cmd> for Message {
|
|||
instant: Instant::now(),
|
||||
timeout: Duration::from_secs_f64(timeout),
|
||||
|
||||
lines,
|
||||
percent: 0,
|
||||
})
|
||||
}
|
||||
|
|
@ -43,5 +41,8 @@ impl TryFrom<Cmd> for Message {
|
|||
|
||||
impl Message {
|
||||
#[inline]
|
||||
pub fn height(&self) -> usize { self.lines + NOTIFY_BORDER as usize }
|
||||
pub fn height(&self, width: u16) -> usize {
|
||||
let lines = (self.content.width() as f64 / width as f64).ceil();
|
||||
lines as usize + NOTIFY_BORDER as usize
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::ops::ControlFlow;
|
||||
|
||||
use ratatui::layout::Rect;
|
||||
use tokio::task::JoinHandle;
|
||||
use yazi_shared::term::Term;
|
||||
|
||||
use super::{Message, NOTIFY_SPACING};
|
||||
|
||||
|
|
@ -12,14 +12,14 @@ pub struct Notify {
|
|||
}
|
||||
|
||||
impl Notify {
|
||||
pub fn limit(&self) -> usize {
|
||||
pub fn limit(&self, area: Rect) -> usize {
|
||||
if self.messages.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut height = Term::size().height as usize;
|
||||
let mut height = area.height as usize;
|
||||
let flow = (0..self.messages.len().min(3)).try_fold(0, |acc, i| {
|
||||
match height.checked_sub(self.messages[i].height() + NOTIFY_SPACING as usize) {
|
||||
match height.checked_sub(self.messages[i].height(area.width) + NOTIFY_SPACING as usize) {
|
||||
Some(h) => {
|
||||
height = h;
|
||||
ControlFlow::Continue(acc + 1)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use bitflags::bitflags;
|
||||
use yazi_shared::{event::Cmd, render, render_and};
|
||||
|
||||
use crate::{manager::Manager, tab::{Mode, Tab}};
|
||||
use crate::{manager::Manager, notify::Notify, tab::Tab};
|
||||
|
||||
bitflags! {
|
||||
pub struct Opt: u8 {
|
||||
|
|
@ -56,29 +56,17 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn escape_find(&mut self) -> bool { render_and!(self.finder.take().is_some()) }
|
||||
|
||||
#[inline]
|
||||
pub fn escape_visual(&mut self) -> bool {
|
||||
let Some((_, indices)) = self.mode.visual() else {
|
||||
if !self.mode.is_visual() {
|
||||
return false;
|
||||
};
|
||||
|
||||
let state = self.mode.is_select();
|
||||
for f in indices.iter().filter_map(|i| self.current.files.get(*i)) {
|
||||
if state {
|
||||
self.selected.add(&f.url);
|
||||
} else {
|
||||
self.selected.remove(&f.url);
|
||||
}
|
||||
}
|
||||
|
||||
self.mode = Mode::Normal;
|
||||
render_and!(true)
|
||||
self.try_escape_visual();
|
||||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn escape_select(&mut self) -> bool {
|
||||
if self.selected.is_empty() {
|
||||
return false;
|
||||
|
|
@ -91,7 +79,6 @@ impl Tab {
|
|||
render_and!(true)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn escape_filter(&mut self) -> bool {
|
||||
if self.current.files.filter().is_none() {
|
||||
return false;
|
||||
|
|
@ -101,7 +88,6 @@ impl Tab {
|
|||
render_and!(true)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn escape_search(&mut self) -> bool {
|
||||
if !self.current.cwd.is_search() {
|
||||
return false;
|
||||
|
|
@ -111,9 +97,29 @@ impl Tab {
|
|||
render_and!(true)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn try_escape_visual(&mut self) -> bool {
|
||||
self.escape_visual();
|
||||
true
|
||||
let state = self.mode.is_select();
|
||||
let Some((_, indices)) = self.mode.take_visual() else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let mut success = true;
|
||||
for f in indices.iter().filter_map(|i| self.current.files.get(*i)) {
|
||||
if state {
|
||||
success &= self.selected.add(&f.url);
|
||||
} else {
|
||||
self.selected.remove(&f.url);
|
||||
}
|
||||
}
|
||||
|
||||
if !success {
|
||||
Notify::_push_warn(
|
||||
"Escape visual mode",
|
||||
"Some files cannot be selected, due to path nesting conflict.",
|
||||
);
|
||||
}
|
||||
|
||||
render!();
|
||||
success
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::BTreeSet;
|
||||
use std::{collections::BTreeSet, mem};
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub enum Mode {
|
||||
|
|
@ -9,8 +9,7 @@ pub enum Mode {
|
|||
}
|
||||
|
||||
impl Mode {
|
||||
#[inline]
|
||||
pub fn visual(&self) -> Option<(usize, &BTreeSet<usize>)> {
|
||||
pub fn visual_mut(&mut self) -> Option<(usize, &mut BTreeSet<usize>)> {
|
||||
match self {
|
||||
Mode::Normal => None,
|
||||
Mode::Select(start, indices) => Some((*start, indices)),
|
||||
|
|
@ -18,12 +17,11 @@ impl Mode {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn visual_mut(&mut self) -> Option<(usize, &mut BTreeSet<usize>)> {
|
||||
match self {
|
||||
pub fn take_visual(&mut self) -> Option<(usize, BTreeSet<usize>)> {
|
||||
match mem::take(self) {
|
||||
Mode::Normal => None,
|
||||
Mode::Select(start, indices) => Some((*start, indices)),
|
||||
Mode::Unset(start, indices) => Some((*start, indices)),
|
||||
Mode::Select(start, indices) => Some((start, indices)),
|
||||
Mode::Unset(start, indices) => Some((start, indices)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use tokio::task::JoinHandle;
|
|||
use yazi_shared::{fs::Url, render};
|
||||
|
||||
use super::{Backstack, Config, Finder, Mode, Preview};
|
||||
use crate::{folder::{Folder, FolderStage}, tab::selected::Selected};
|
||||
use crate::{folder::{Folder, FolderStage}, tab::Selected};
|
||||
|
||||
pub struct Tab {
|
||||
pub mode: Mode,
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, mem, path::Path
|
|||
|
||||
use tokio::time::sleep;
|
||||
use tracing::debug;
|
||||
use yazi_config::{manager::SortBy, open::Opener, plugin::{PluginRule, MAX_PRELOADERS}, popup::InputCfg, OPEN, PLUGIN};
|
||||
use yazi_config::{manager::SortBy, open::Opener, plugin::{PluginRule, MAX_PRELOADERS}, OPEN, PLUGIN};
|
||||
use yazi_plugin::ValueSendable;
|
||||
use yazi_scheduler::{Scheduler, TaskSummary};
|
||||
use yazi_shared::{emit, event::Cmd, fs::{File, Url}, term::Term, Layer, MIME_DIR};
|
||||
|
||||
use super::{TasksProgress, TASKS_BORDER, TASKS_PADDING, TASKS_PERCENT};
|
||||
use crate::{folder::Files, input::Input};
|
||||
use crate::folder::Files;
|
||||
|
||||
pub struct Tasks {
|
||||
pub(super) scheduler: Arc<Scheduler>,
|
||||
|
|
@ -113,39 +113,14 @@ impl Tasks {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn file_remove(&self, targets: Vec<Url>, force: bool, permanently: bool) {
|
||||
if force {
|
||||
for u in targets {
|
||||
if permanently {
|
||||
self.scheduler.file_delete(u);
|
||||
} else {
|
||||
self.scheduler.file_trash(u);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let scheduler = self.scheduler.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut result = Input::_show(if permanently {
|
||||
InputCfg::delete(targets.len())
|
||||
pub fn file_remove(&self, targets: Vec<Url>, permanently: bool) {
|
||||
for u in targets {
|
||||
if permanently {
|
||||
self.scheduler.file_delete(u);
|
||||
} else {
|
||||
InputCfg::trash(targets.len())
|
||||
});
|
||||
|
||||
if let Some(Ok(choice)) = result.recv().await {
|
||||
if choice != "y" && choice != "Y" {
|
||||
return;
|
||||
}
|
||||
for u in targets {
|
||||
if permanently {
|
||||
scheduler.file_delete(u);
|
||||
} else {
|
||||
scheduler.file_trash(u);
|
||||
}
|
||||
}
|
||||
self.scheduler.file_trash(u);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::sync::atomic::Ordering;
|
|||
|
||||
use ratatui::{backend::{Backend, CrosstermBackend}, CompletedFrame};
|
||||
|
||||
use crate::{app::App, lives::Lives, notify::Notify, root::{Root, COLLISION}};
|
||||
use crate::{app::App, lives::Lives, root::{Root, COLLISION}};
|
||||
|
||||
impl App {
|
||||
pub(crate) fn render(&mut self) {
|
||||
|
|
@ -45,7 +45,7 @@ impl App {
|
|||
|
||||
let frame = term
|
||||
.draw_partial(|f| {
|
||||
f.render_widget(Notify::new(&self.cx), f.size());
|
||||
f.render_widget(crate::notify::Layout::new(&self.cx), f.size());
|
||||
|
||||
if let Some((x, y)) = self.cx.cursor() {
|
||||
f.set_cursor(x, y);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
use yazi_shared::event::Cmd;
|
||||
use crossterm::terminal::WindowSize;
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_shared::{event::Cmd, term::Term};
|
||||
|
||||
use crate::app::App;
|
||||
|
||||
impl App {
|
||||
pub(crate) fn update_notify(&mut self, cmd: Cmd) {
|
||||
self.cx.notify.tick(cmd);
|
||||
let WindowSize { width, height, .. } = Term::size();
|
||||
let area = crate::notify::Layout::available(Rect { x: 0, y: 0, width, height });
|
||||
|
||||
self.cx.notify.tick(cmd, area);
|
||||
|
||||
if self.cx.notify.messages.is_empty() {
|
||||
self.render();
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ impl<'a> Executor<'a> {
|
|||
on!(MANAGER, paste, &self.app.cx.tasks);
|
||||
on!(MANAGER, link, &self.app.cx.tasks);
|
||||
on!(MANAGER, remove, &self.app.cx.tasks);
|
||||
on!(MANAGER, remove_do, &self.app.cx.tasks);
|
||||
on!(MANAGER, create);
|
||||
on!(MANAGER, rename);
|
||||
on!(ACTIVE, copy);
|
||||
|
|
|
|||
67
yazi-fm/src/notify/layout.rs
Normal file
67
yazi-fm/src/notify/layout.rs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use ratatui::{buffer::Buffer, layout::{self, Constraint, Offset, Rect}, style::{Style, Stylize}, widgets::{Block, BorderType, Paragraph, Widget, Wrap}};
|
||||
use yazi_core::notify::{Level, Message};
|
||||
|
||||
use crate::{widgets::Clear, Ctx};
|
||||
|
||||
pub(crate) struct Layout<'a> {
|
||||
cx: &'a Ctx,
|
||||
}
|
||||
|
||||
impl<'a> Layout<'a> {
|
||||
pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } }
|
||||
|
||||
pub(crate) fn available(area: Rect) -> Rect {
|
||||
let chunks =
|
||||
layout::Layout::horizontal([Constraint::Fill(1), Constraint::Length(40), Constraint::Max(1)])
|
||||
.split(area);
|
||||
|
||||
let chunks =
|
||||
layout::Layout::vertical([Constraint::Max(1), Constraint::Min(1)]).split(chunks[1]);
|
||||
|
||||
chunks[1]
|
||||
}
|
||||
|
||||
fn tile(area: Rect, messages: &[Message]) -> Rc<[Rect]> {
|
||||
layout::Layout::vertical(
|
||||
messages.iter().map(|m| Constraint::Length(m.height(area.width) as u16)),
|
||||
)
|
||||
.spacing(1)
|
||||
.split(area)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Layout<'a> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let notify = &self.cx.notify;
|
||||
|
||||
let available = Self::available(area);
|
||||
let limit = notify.limit(available);
|
||||
let tile = Self::tile(available, ¬ify.messages[..limit]);
|
||||
|
||||
for (i, m) in notify.messages.iter().enumerate().take(limit) {
|
||||
let (icon, style) = match m.level {
|
||||
Level::Info => ("", Style::default().green()),
|
||||
Level::Warn => ("", Style::default().yellow()),
|
||||
Level::Error => ("", Style::default().red()),
|
||||
};
|
||||
|
||||
let mut rect =
|
||||
tile[i].offset(Offset { x: (100 - m.percent) as i32 * tile[i].width as i32 / 100, y: 0 });
|
||||
rect.width = area.width.saturating_sub(rect.x);
|
||||
|
||||
Clear.render(rect, buf);
|
||||
Paragraph::new(m.content.as_str())
|
||||
.wrap(Wrap { trim: false })
|
||||
.block(
|
||||
Block::bordered()
|
||||
.border_type(BorderType::Rounded)
|
||||
.title(format!("{} {}", icon, m.title))
|
||||
.title_style(style)
|
||||
.border_style(style),
|
||||
)
|
||||
.render(rect, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
mod notify;
|
||||
mod layout;
|
||||
|
||||
pub(super) use notify::*;
|
||||
pub(super) use layout::*;
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use ratatui::{buffer::Buffer, layout::{Constraint, Layout, Offset, Rect}, style::{Style, Stylize}, widgets::{Block, BorderType, Paragraph, Widget}};
|
||||
use yazi_core::notify::{Level, Message};
|
||||
|
||||
use crate::{widgets::Clear, Ctx};
|
||||
|
||||
pub(crate) struct Notify<'a> {
|
||||
cx: &'a Ctx,
|
||||
}
|
||||
|
||||
impl<'a> Notify<'a> {
|
||||
pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } }
|
||||
|
||||
fn chunks(area: Rect, messages: &[Message]) -> Rc<[Rect]> {
|
||||
let chunks =
|
||||
Layout::horizontal([Constraint::Fill(1), Constraint::Length(40), Constraint::Max(1)])
|
||||
.split(area);
|
||||
|
||||
let chunks = Layout::vertical([Constraint::Max(1), Constraint::Min(1)]).split(chunks[1]);
|
||||
|
||||
Layout::vertical(messages.iter().map(|m| Constraint::Length(m.height() as u16)))
|
||||
.spacing(1)
|
||||
.split(chunks[1])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Notify<'a> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let notify = &self.cx.notify;
|
||||
|
||||
let limit = notify.limit();
|
||||
let chunks = Self::chunks(area, ¬ify.messages[..limit]);
|
||||
|
||||
for (i, m) in notify.messages.iter().enumerate().take(limit) {
|
||||
let (icon, style) = match m.level {
|
||||
Level::Info => ("", Style::default().green()),
|
||||
Level::Warn => ("", Style::default().yellow()),
|
||||
Level::Error => ("", Style::default().red()),
|
||||
};
|
||||
|
||||
let mut rect = chunks[i]
|
||||
.offset(Offset { x: (100 - m.percent) as i32 * chunks[i].width as i32 / 100, y: 0 });
|
||||
rect.width = area.width.saturating_sub(rect.x);
|
||||
|
||||
Clear.render(rect, buf);
|
||||
Paragraph::new(m.content.as_str())
|
||||
.block(
|
||||
Block::bordered()
|
||||
.border_type(BorderType::Rounded)
|
||||
.title(format!("{} {}", icon, m.title))
|
||||
.title_style(style)
|
||||
.border_style(style),
|
||||
)
|
||||
.render(rect, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,14 +7,9 @@ mod icon;
|
|||
mod range;
|
||||
mod window;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub use bindings::*;
|
||||
pub use cha::*;
|
||||
pub use file::*;
|
||||
pub use icon::*;
|
||||
pub use range::*;
|
||||
pub use window::*;
|
||||
|
||||
pub trait Cast<T> {
|
||||
fn cast(lua: &mlua::Lua, data: T) -> mlua::Result<mlua::AnyUserData>;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue