From 4e873e62f12fba6a6692960c870e56194608279b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Wed, 6 Mar 2024 19:07:37 +0800 Subject: [PATCH 1/9] feat: `ui.Clear` component for UI plugins (#786) --- yazi-fm/src/app/commands/render.rs | 3 +- yazi-fm/src/completion/completion.rs | 6 +-- yazi-fm/src/help/layout.rs | 4 +- yazi-fm/src/input/input.rs | 4 +- yazi-fm/src/lives/selected.rs | 16 ++++-- yazi-fm/src/main.rs | 1 - yazi-fm/src/notify/layout.rs | 4 +- yazi-fm/src/root.rs | 4 -- yazi-fm/src/select/select.rs | 4 +- yazi-fm/src/tasks/layout.rs | 4 +- yazi-fm/src/which/layout.rs | 4 +- yazi-fm/src/widgets/clear.rs | 43 ---------------- yazi-fm/src/widgets/mod.rs | 3 -- yazi-plugin/src/cast.rs | 12 ++++- yazi-plugin/src/elements/bar.rs | 14 +++--- yazi-plugin/src/elements/border.rs | 26 +++++----- yazi-plugin/src/elements/clear.rs | 75 ++++++++++++++++++++++++++++ yazi-plugin/src/elements/elements.rs | 1 + yazi-plugin/src/elements/layout.rs | 7 +-- yazi-plugin/src/elements/mod.rs | 2 + yazi-plugin/src/elements/rect.rs | 7 +-- yazi-plugin/src/fs/fs.rs | 26 +++++----- yazi-plugin/src/process/child.rs | 18 +++---- yazi-plugin/src/process/command.rs | 24 ++++----- 24 files changed, 173 insertions(+), 139 deletions(-) delete mode 100644 yazi-fm/src/widgets/clear.rs delete mode 100644 yazi-fm/src/widgets/mod.rs create mode 100644 yazi-plugin/src/elements/clear.rs diff --git a/yazi-fm/src/app/commands/render.rs b/yazi-fm/src/app/commands/render.rs index e9106f4c..b2350cff 100644 --- a/yazi-fm/src/app/commands/render.rs +++ b/yazi-fm/src/app/commands/render.rs @@ -1,8 +1,9 @@ use std::sync::atomic::Ordering; use ratatui::{backend::{Backend, CrosstermBackend}, CompletedFrame}; +use yazi_plugin::elements::COLLISION; -use crate::{app::App, lives::Lives, root::{Root, COLLISION}}; +use crate::{app::App, lives::Lives, root::Root}; impl App { pub(crate) fn render(&mut self) { diff --git a/yazi-fm/src/completion/completion.rs b/yazi-fm/src/completion/completion.rs index 05936da1..7dee9d63 100644 --- a/yazi-fm/src/completion/completion.rs +++ b/yazi-fm/src/completion/completion.rs @@ -3,7 +3,7 @@ use std::path::MAIN_SEPARATOR; use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, List, ListItem, Widget}}; use yazi_config::{popup::{Offset, Position}, THEME}; -use crate::{widgets, Ctx}; +use crate::Ctx; pub(crate) struct Completion<'a> { cx: &'a Ctx, @@ -28,7 +28,7 @@ impl<'a> Widget for Completion<'a> { &THEME.completion.icon_file }; - let mut item = ListItem::new(format!(" {} {}", icon, x)); + let mut item = ListItem::new(format!(" {icon} {x}")); if i == self.cx.completion.rel_cursor() { item = item.style(THEME.completion.active); } else { @@ -54,7 +54,7 @@ impl<'a> Widget for Completion<'a> { area.height = rect.height.saturating_sub(area.y).min(area.height); } - widgets::Clear.render(area, buf); + yazi_plugin::elements::Clear::default().render(area, buf); List::new(items) .block( Block::bordered().border_type(BorderType::Rounded).border_style(THEME.completion.border), diff --git a/yazi-fm/src/help/layout.rs b/yazi-fm/src/help/layout.rs index 633b3d67..2bbfdeba 100644 --- a/yazi-fm/src/help/layout.rs +++ b/yazi-fm/src/help/layout.rs @@ -2,7 +2,7 @@ use ratatui::{buffer::Buffer, layout::{self, Constraint, Rect}, text::Line, widg use yazi_config::THEME; use super::Bindings; -use crate::{widgets, Ctx}; +use crate::Ctx; pub(crate) struct Layout<'a> { cx: &'a Ctx, @@ -15,7 +15,7 @@ impl<'a> Layout<'a> { impl<'a> Widget for Layout<'a> { fn render(self, area: Rect, buf: &mut Buffer) { let help = &self.cx.help; - widgets::Clear.render(area, buf); + yazi_plugin::elements::Clear::default().render(area, buf); let chunks = layout::Layout::vertical([Constraint::Fill(1), Constraint::Length(1)]).split(area); Line::styled( diff --git a/yazi-fm/src/input/input.rs b/yazi-fm/src/input/input.rs index f68f7bec..a0863347 100644 --- a/yazi-fm/src/input/input.rs +++ b/yazi-fm/src/input/input.rs @@ -8,7 +8,7 @@ use yazi_core::input::InputMode; use yazi_plugin::external::Highlighter; use yazi_shared::term::Term; -use crate::{widgets, Ctx}; +use crate::Ctx; pub(crate) struct Input<'a> { cx: &'a Ctx, @@ -37,7 +37,7 @@ impl<'a> Widget for Input<'a> { let input = &self.cx.input; let area = self.cx.area(&input.position); - widgets::Clear.render(area, buf); + yazi_plugin::elements::Clear::default().render(area, buf); Paragraph::new(self.highlighted_value().unwrap_or_else(|_| Line::from(input.value()))) .block( Block::bordered() diff --git a/yazi-fm/src/lives/selected.rs b/yazi-fm/src/lives/selected.rs index b20233fb..29fbfb34 100644 --- a/yazi-fm/src/lives/selected.rs +++ b/yazi-fm/src/lives/selected.rs @@ -1,6 +1,6 @@ use std::{collections::{btree_set, BTreeSet}, ops::Deref}; -use mlua::{AnyUserData, Lua, MetaMethod, UserDataMethods, UserDataRefMut}; +use mlua::{AnyUserData, IntoLuaMulti, Lua, MetaMethod, UserDataMethods, UserDataRefMut}; use yazi_plugin::{bindings::Cast, url::Url}; use super::SCOPE; @@ -28,7 +28,12 @@ impl Selected { reg.add_meta_method(MetaMethod::Pairs, |lua, me, ()| { let iter = lua.create_function(|lua, mut iter: UserDataRefMut| { - Ok(if let Some(url) = iter.0.next() { Some(Url::cast(lua, url.clone())?) } else { None }) + if let Some(url) = iter.inner.next() { + iter.next += 1; + (iter.next, Url::cast(lua, url.clone())?).into_lua_multi(lua) + } else { + ().into_lua_multi(lua) + } })?; Ok((iter, SelectedIter::make(me.inner()))) @@ -42,11 +47,14 @@ impl Selected { fn inner(&self) -> &'static BTreeSet { unsafe { &*self.inner } } } -struct SelectedIter(btree_set::Iter<'static, yazi_shared::fs::Url>); +struct SelectedIter { + next: usize, + inner: btree_set::Iter<'static, yazi_shared::fs::Url>, +} impl SelectedIter { #[inline] fn make(selected: &'static BTreeSet) -> mlua::Result> { - SCOPE.create_any_userdata(Self(selected.iter())) + SCOPE.create_any_userdata(Self { next: 0, inner: selected.iter() }) } } diff --git a/yazi-fm/src/main.rs b/yazi-fm/src/main.rs index 380754d7..95b72d1e 100644 --- a/yazi-fm/src/main.rs +++ b/yazi-fm/src/main.rs @@ -22,7 +22,6 @@ mod select; mod signals; mod tasks; mod which; -mod widgets; use context::*; use executor::*; diff --git a/yazi-fm/src/notify/layout.rs b/yazi-fm/src/notify/layout.rs index 39fbea87..33783ef5 100644 --- a/yazi-fm/src/notify/layout.rs +++ b/yazi-fm/src/notify/layout.rs @@ -5,7 +5,7 @@ use yazi_config::THEME; use yazi_core::notify::Message; use yazi_proxy::options::NotifyLevel; -use crate::{widgets::Clear, Ctx}; +use crate::Ctx; pub(crate) struct Layout<'a> { cx: &'a Ctx, @@ -53,7 +53,7 @@ impl<'a> Widget for Layout<'a> { 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); + yazi_plugin::elements::Clear::default().render(rect, buf); Paragraph::new(m.content.as_str()) .wrap(Wrap { trim: false }) .block( diff --git a/yazi-fm/src/root.rs b/yazi-fm/src/root.rs index 4b75c623..f5dfdda0 100644 --- a/yazi-fm/src/root.rs +++ b/yazi-fm/src/root.rs @@ -1,12 +1,8 @@ -use std::sync::atomic::AtomicBool; - use ratatui::{buffer::Buffer, layout::{Constraint, Layout, Rect}, widgets::Widget}; use super::{completion, input, select, tasks, which}; use crate::{components, help, Ctx}; -pub(super) static COLLISION: AtomicBool = AtomicBool::new(false); - pub(super) struct Root<'a> { cx: &'a Ctx, } diff --git a/yazi-fm/src/select/select.rs b/yazi-fm/src/select/select.rs index 52eed6e3..9bbf2997 100644 --- a/yazi-fm/src/select/select.rs +++ b/yazi-fm/src/select/select.rs @@ -1,7 +1,7 @@ use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, List, ListItem, Widget}}; use yazi_config::THEME; -use crate::{widgets, Ctx}; +use crate::Ctx; pub(crate) struct Select<'a> { cx: &'a Ctx, @@ -29,7 +29,7 @@ impl<'a> Widget for Select<'a> { }) .collect(); - widgets::Clear.render(area, buf); + yazi_plugin::elements::Clear::default().render(area, buf); List::new(items) .block( Block::bordered() diff --git a/yazi-fm/src/tasks/layout.rs b/yazi-fm/src/tasks/layout.rs index ac1cf6ab..d8785f54 100644 --- a/yazi-fm/src/tasks/layout.rs +++ b/yazi-fm/src/tasks/layout.rs @@ -2,7 +2,7 @@ use ratatui::{buffer::Buffer, layout::{self, Alignment, Constraint, Rect}, text: use yazi_config::THEME; use yazi_core::tasks::TASKS_PERCENT; -use crate::{widgets, Ctx}; +use crate::Ctx; pub(crate) struct Layout<'a> { cx: &'a Ctx, @@ -32,7 +32,7 @@ impl<'a> Widget for Layout<'a> { fn render(self, area: Rect, buf: &mut Buffer) { let area = Self::area(area); - widgets::Clear.render(area, buf); + yazi_plugin::elements::Clear::default().render(area, buf); let block = Block::bordered() .title(Line::styled("Tasks", THEME.tasks.title)) .title_alignment(Alignment::Center) diff --git a/yazi-fm/src/which/layout.rs b/yazi-fm/src/which/layout.rs index 36623ab3..26aed46b 100644 --- a/yazi-fm/src/which/layout.rs +++ b/yazi-fm/src/which/layout.rs @@ -2,7 +2,7 @@ use ratatui::{buffer::Buffer, layout, layout::{Constraint, Rect}, widgets::{Bloc use yazi_config::THEME; use super::Cand; -use crate::{widgets, Ctx}; +use crate::Ctx; const PADDING_X: u16 = 1; const PADDING_Y: u16 = 1; @@ -46,7 +46,7 @@ impl Widget for Which<'_> { .split(area) }; - widgets::Clear.render(area, buf); + yazi_plugin::elements::Clear::default().render(area, buf); Block::new().style(THEME.which.mask).render(area, buf); for y in 0..area.height { diff --git a/yazi-fm/src/widgets/clear.rs b/yazi-fm/src/widgets/clear.rs deleted file mode 100644 index 553d55f1..00000000 --- a/yazi-fm/src/widgets/clear.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::sync::atomic::Ordering; - -use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget}; -use yazi_adaptor::ADAPTOR; - -use crate::root::COLLISION; - -pub(crate) struct Clear; - -#[inline] -const fn is_overlapping(a: &Rect, b: &Rect) -> bool { - a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y -} - -fn overlap(a: &Rect, b: &Rect) -> Option { - if !is_overlapping(a, b) { - return None; - } - - let x = a.x.max(b.x); - let y = a.y.max(b.y); - let width = (a.x + a.width).min(b.x + b.width) - x; - let height = (a.y + a.height).min(b.y + b.height) - y; - Some(Rect { x, y, width, height }) -} - -impl Widget for Clear { - fn render(self, area: Rect, buf: &mut Buffer) { - ratatui::widgets::Clear.render(area, buf); - - let Some(r) = ADAPTOR.shown_load().and_then(|r| overlap(&area, &r)) else { - return; - }; - - ADAPTOR.image_erase(r).ok(); - COLLISION.store(true, Ordering::Relaxed); - for y in area.top()..area.bottom() { - for x in area.left()..area.right() { - buf.get_mut(x, y).set_skip(true); - } - } - } -} diff --git a/yazi-fm/src/widgets/mod.rs b/yazi-fm/src/widgets/mod.rs deleted file mode 100644 index cd6c2c51..00000000 --- a/yazi-fm/src/widgets/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod clear; - -pub(super) use clear::*; diff --git a/yazi-plugin/src/cast.rs b/yazi-plugin/src/cast.rs index d426f3d5..f753fa27 100644 --- a/yazi-plugin/src/cast.rs +++ b/yazi-plugin/src/cast.rs @@ -12,6 +12,8 @@ pub fn cast_to_renderable(ud: AnyUserData) -> Option> Some(Box::new(c)) } else if let Ok(c) = ud.take::() { Some(Box::new(c)) + } else if let Ok(c) = ud.take::() { + Some(Box::new(c)) } else if let Ok(c) = ud.take::() { Some(Box::new(c)) } else if let Ok(c) = ud.take::() { @@ -67,9 +69,10 @@ impl<'lua> IntoLua<'lua> for ValueSendable { ValueSendable::Number(n) => Ok(Value::Number(n)), ValueSendable::String(s) => Ok(Value::String(lua.create_string(s)?)), ValueSendable::Table(t) => { - let table = lua.create_table()?; + let seq_len = t.keys().filter(|&k| !k.is_numeric()).count(); + let table = lua.create_table_with_capacity(seq_len, t.len() - seq_len)?; for (k, v) in t { - table.raw_set(k.into_lua(lua)?, v.into_lua(lua)?)?; + table.raw_set(k, v)?; } Ok(Value::Table(table)) } @@ -113,6 +116,11 @@ pub enum ValueSendableKey { String(Vec), } +impl ValueSendableKey { + #[inline] + fn is_numeric(&self) -> bool { matches!(self, Self::Integer(_) | Self::Number(_)) } +} + impl TryInto for ValueSendable { type Error = mlua::Error; diff --git a/yazi-plugin/src/elements/bar.rs b/yazi-plugin/src/elements/bar.rs index 8428b5a8..c7562753 100644 --- a/yazi-plugin/src/elements/bar.rs +++ b/yazi-plugin/src/elements/bar.rs @@ -1,4 +1,4 @@ -use mlua::{AnyUserData, ExternalError, IntoLua, Lua, Table, UserData, Value}; +use mlua::{AnyUserData, ExternalError, Lua, Table, UserData, Value}; use ratatui::widgets::Borders; use super::{RectRef, Renderable, Style}; @@ -26,12 +26,12 @@ impl Bar { let bar = lua.create_table_from([ // Direction - ("NONE", Borders::NONE.bits().into_lua(lua)?), - ("TOP", Borders::TOP.bits().into_lua(lua)?), - ("RIGHT", Borders::RIGHT.bits().into_lua(lua)?), - ("BOTTOM", Borders::BOTTOM.bits().into_lua(lua)?), - ("LEFT", Borders::LEFT.bits().into_lua(lua)?), - ("ALL", Borders::ALL.bits().into_lua(lua)?), + ("NONE", Borders::NONE.bits()), + ("TOP", Borders::TOP.bits()), + ("RIGHT", Borders::RIGHT.bits()), + ("BOTTOM", Borders::BOTTOM.bits()), + ("LEFT", Borders::LEFT.bits()), + ("ALL", Borders::ALL.bits()), ])?; bar.set_metatable(Some(lua.create_table_from([("__call", new)])?)); diff --git a/yazi-plugin/src/elements/border.rs b/yazi-plugin/src/elements/border.rs index ec815402..973bdb7f 100644 --- a/yazi-plugin/src/elements/border.rs +++ b/yazi-plugin/src/elements/border.rs @@ -1,4 +1,4 @@ -use mlua::{AnyUserData, ExternalError, IntoLua, Lua, Table, UserData, Value}; +use mlua::{AnyUserData, ExternalError, Lua, Table, UserData, Value}; use ratatui::widgets::{Borders, Widget}; use super::{RectRef, Renderable, Style}; @@ -32,19 +32,19 @@ impl Border { let border = lua.create_table_from([ // Position - ("NONE", Borders::NONE.bits().into_lua(lua)?), - ("TOP", Borders::TOP.bits().into_lua(lua)?), - ("RIGHT", Borders::RIGHT.bits().into_lua(lua)?), - ("BOTTOM", Borders::BOTTOM.bits().into_lua(lua)?), - ("LEFT", Borders::LEFT.bits().into_lua(lua)?), - ("ALL", Borders::ALL.bits().into_lua(lua)?), + ("NONE", Borders::NONE.bits()), + ("TOP", Borders::TOP.bits()), + ("RIGHT", Borders::RIGHT.bits()), + ("BOTTOM", Borders::BOTTOM.bits()), + ("LEFT", Borders::LEFT.bits()), + ("ALL", Borders::ALL.bits()), // Type - ("PLAIN", PLAIN.into_lua(lua)?), - ("ROUNDED", ROUNDED.into_lua(lua)?), - ("DOUBLE", DOUBLE.into_lua(lua)?), - ("THICK", THICK.into_lua(lua)?), - ("QUADRANT_INSIDE", QUADRANT_INSIDE.into_lua(lua)?), - ("QUADRANT_OUTSIDE", QUADRANT_OUTSIDE.into_lua(lua)?), + ("PLAIN", PLAIN), + ("ROUNDED", ROUNDED), + ("DOUBLE", DOUBLE), + ("THICK", THICK), + ("QUADRANT_INSIDE", QUADRANT_INSIDE), + ("QUADRANT_OUTSIDE", QUADRANT_OUTSIDE), ])?; border.set_metatable(Some(lua.create_table_from([("__call", new)])?)); diff --git a/yazi-plugin/src/elements/clear.rs b/yazi-plugin/src/elements/clear.rs new file mode 100644 index 00000000..dfd0e2f4 --- /dev/null +++ b/yazi-plugin/src/elements/clear.rs @@ -0,0 +1,75 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + +use mlua::{Lua, Table, UserData}; +use ratatui::layout::Rect; +use yazi_adaptor::ADAPTOR; + +use super::{RectRef, Renderable}; + +pub static COLLISION: AtomicBool = AtomicBool::new(false); + +#[derive(Clone, Copy, Default)] +pub struct Clear { + pub area: ratatui::layout::Rect, +} + +impl Clear { + pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> { + let new = lua.create_function(|_, (_, area): (Table, RectRef)| Ok(Clear { area: *area }))?; + + let clear = lua.create_table()?; + clear.set_metatable(Some(lua.create_table_from([("__call", new)])?)); + + ui.raw_set("Clear", clear) + } +} + +impl ratatui::widgets::Widget for Clear { + fn render(self, area: Rect, buf: &mut ratatui::prelude::Buffer) + where + Self: Sized, + { + ratatui::widgets::Clear.render(area, buf); + + let Some(r) = ADAPTOR.shown_load().and_then(|r| overlap(&area, &r)) else { + return; + }; + + ADAPTOR.image_erase(r).ok(); + COLLISION.store(true, Ordering::Relaxed); + for y in area.top()..area.bottom() { + for x in area.left()..area.right() { + buf.get_mut(x, y).set_skip(true); + } + } + } +} + +impl Renderable for Clear { + fn area(&self) -> ratatui::layout::Rect { self.area } + + fn render(self: Box, buf: &mut ratatui::buffer::Buffer) { + ::render(Default::default(), self.area, buf); + } + + fn clone_render(&self, buf: &mut ratatui::buffer::Buffer) { Box::new(*self).render(buf); } +} + +impl UserData for Clear {} + +#[inline] +const fn is_overlapping(a: &Rect, b: &Rect) -> bool { + a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y +} + +fn overlap(a: &Rect, b: &Rect) -> Option { + if !is_overlapping(a, b) { + return None; + } + + let x = a.x.max(b.x); + let y = a.y.max(b.y); + let width = (a.x + a.width).min(b.x + b.width) - x; + let height = (a.y + a.height).min(b.y + b.height) - y; + Some(Rect { x, y, width, height }) +} diff --git a/yazi-plugin/src/elements/elements.rs b/yazi-plugin/src/elements/elements.rs index caa96606..0fb8fc35 100644 --- a/yazi-plugin/src/elements/elements.rs +++ b/yazi-plugin/src/elements/elements.rs @@ -12,6 +12,7 @@ pub fn pour(lua: &Lua) -> mlua::Result<()> { // Install super::Bar::install(lua, &ui)?; super::Border::install(lua, &ui)?; + super::Clear::install(lua, &ui)?; super::Constraint::install(lua, &ui)?; super::Gauge::install(lua, &ui)?; super::Layout::install(lua, &ui)?; diff --git a/yazi-plugin/src/elements/layout.rs b/yazi-plugin/src/elements/layout.rs index 0d8ac883..8b3eaa46 100644 --- a/yazi-plugin/src/elements/layout.rs +++ b/yazi-plugin/src/elements/layout.rs @@ -1,4 +1,4 @@ -use mlua::{AnyUserData, IntoLua, Lua, Table, UserData, UserDataMethods}; +use mlua::{AnyUserData, Lua, Table, UserData, UserDataMethods}; use super::{Constraint, Rect, RectRef}; use crate::bindings::Cast; @@ -17,10 +17,7 @@ impl Layout { pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> { let new = lua.create_function(|_, _: Table| Ok(Self::default()))?; - let layout = lua.create_table_from([ - ("HORIZONTAL", HORIZONTAL.into_lua(lua)?), - ("VERTICAL", VERTICAL.into_lua(lua)?), - ])?; + let layout = lua.create_table_from([("HORIZONTAL", HORIZONTAL), ("VERTICAL", VERTICAL)])?; layout.set_metatable(Some(lua.create_table_from([("__call", new)])?)); diff --git a/yazi-plugin/src/elements/mod.rs b/yazi-plugin/src/elements/mod.rs index 0ac6f401..b15488b3 100644 --- a/yazi-plugin/src/elements/mod.rs +++ b/yazi-plugin/src/elements/mod.rs @@ -2,6 +2,7 @@ mod bar; mod border; +mod clear; mod constraint; mod elements; mod gauge; @@ -16,6 +17,7 @@ mod style; pub use bar::*; pub use border::*; +pub use clear::*; pub use constraint::*; pub use elements::*; pub use gauge::*; diff --git a/yazi-plugin/src/elements/rect.rs b/yazi-plugin/src/elements/rect.rs index 45d233cf..26bb5eab 100644 --- a/yazi-plugin/src/elements/rect.rs +++ b/yazi-plugin/src/elements/rect.rs @@ -1,4 +1,4 @@ -use mlua::{AnyUserData, IntoLua, Lua, Table, UserDataFields, UserDataMethods, UserDataRef}; +use mlua::{AnyUserData, Lua, Table, UserDataFields, UserDataMethods, UserDataRef}; use super::PaddingRef; use crate::bindings::Cast; @@ -18,10 +18,7 @@ impl Rect { }) })?; - let rect = lua.create_table_from([( - "default", - Rect::cast(lua, ratatui::layout::Rect::default())?.into_lua(lua)?, - )])?; + let rect = lua.create_table_from([("default", Rect::cast(lua, Default::default())?)])?; rect.set_metatable(Some(lua.create_table_from([("__call", new)])?)); diff --git a/yazi-plugin/src/fs/fs.rs b/yazi-plugin/src/fs/fs.rs index 9e920240..aa3ecf9e 100644 --- a/yazi-plugin/src/fs/fs.rs +++ b/yazi-plugin/src/fs/fs.rs @@ -1,4 +1,4 @@ -use mlua::{IntoLua, Lua, Value}; +use mlua::{IntoLuaMulti, Lua, Value}; use tokio::fs; use crate::{bindings::{Cast, Cha}, url::UrlRef}; @@ -10,28 +10,28 @@ pub fn install(lua: &Lua) -> mlua::Result<()> { ( "write", lua.create_async_function(|lua, (url, data): (UrlRef, mlua::String)| async move { - Ok(match fs::write(&*url, data).await { - Ok(_) => (Value::Boolean(true), Value::Nil), - Err(e) => (Value::Boolean(false), e.raw_os_error().into_lua(lua)?), - }) + match fs::write(&*url, data).await { + Ok(_) => (true, Value::Nil).into_lua_multi(lua), + Err(e) => (false, e.raw_os_error()).into_lua_multi(lua), + } })?, ), ( "cha", lua.create_async_function(|lua, url: UrlRef| async move { - Ok(match fs::symlink_metadata(&*url).await { - Ok(m) => (Cha::cast(lua, m)?.into_lua(lua)?, Value::Nil), - Err(e) => (Value::Nil, e.raw_os_error().into_lua(lua)?), - }) + match fs::symlink_metadata(&*url).await { + Ok(m) => (Cha::cast(lua, m)?, Value::Nil).into_lua_multi(lua), + Err(e) => (Value::Nil, e.raw_os_error()).into_lua_multi(lua), + } })?, ), ( "cha_follow", lua.create_async_function(|lua, url: UrlRef| async move { - Ok(match fs::metadata(&*url).await { - Ok(m) => (Cha::cast(lua, m)?.into_lua(lua)?, Value::Nil), - Err(e) => (Value::Nil, e.raw_os_error().into_lua(lua)?), - }) + match fs::metadata(&*url).await { + Ok(m) => (Cha::cast(lua, m)?, Value::Nil).into_lua_multi(lua), + Err(e) => (Value::Nil, e.raw_os_error()).into_lua_multi(lua), + } })?, ), ])?, diff --git a/yazi-plugin/src/process/child.rs b/yazi-plugin/src/process/child.rs index 18c9d0e7..76242ae0 100644 --- a/yazi-plugin/src/process/child.rs +++ b/yazi-plugin/src/process/child.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use mlua::{IntoLua, Table, UserData, Value}; +use mlua::{IntoLuaMulti, Table, UserData, Value}; use tokio::{io::{AsyncBufReadExt, AsyncReadExt, BufReader}, process::{ChildStderr, ChildStdin, ChildStdout}, select}; use super::Status; @@ -68,16 +68,14 @@ impl UserData for Child { } }); methods.add_async_method_mut("wait", |lua, me, ()| async move { - Ok(match me.inner.wait().await { - Ok(status) => (Status::new(status).into_lua(lua)?, Value::Nil), - Err(e) => (Value::Nil, e.raw_os_error().into_lua(lua)?), - }) + match me.inner.wait().await { + Ok(status) => (Status::new(status), Value::Nil).into_lua_multi(lua), + Err(e) => (Value::Nil, e.raw_os_error()).into_lua_multi(lua), + } }); - methods.add_method_mut("start_kill", |lua, me, ()| { - Ok(match me.inner.start_kill() { - Ok(_) => (true, Value::Nil), - Err(e) => (false, e.raw_os_error().into_lua(lua)?), - }) + methods.add_method_mut("start_kill", |lua, me, ()| match me.inner.start_kill() { + Ok(_) => (true, Value::Nil).into_lua_multi(lua), + Err(e) => (false, e.raw_os_error()).into_lua_multi(lua), }); } } diff --git a/yazi-plugin/src/process/command.rs b/yazi-plugin/src/process/command.rs index 3b67fa5e..be57d68a 100644 --- a/yazi-plugin/src/process/command.rs +++ b/yazi-plugin/src/process/command.rs @@ -1,6 +1,6 @@ use std::process::Stdio; -use mlua::{AnyUserData, IntoLua, Lua, Table, UserData, Value}; +use mlua::{AnyUserData, IntoLuaMulti, Lua, Table, UserData, Value}; use super::{output::Output, Child}; @@ -23,9 +23,9 @@ impl Command { let command = lua.create_table_from([ // Stdio - ("NULL", NULL.into_lua(lua)?), - ("PIPED", PIPED.into_lua(lua)?), - ("INHERIT", INHERIT.into_lua(lua)?), + ("NULL", NULL), + ("PIPED", PIPED), + ("INHERIT", INHERIT), ])?; command.set_metatable(Some(lua.create_table_from([("__call", new)])?)); @@ -86,17 +86,15 @@ impl UserData for Command { }); Ok(ud) }); - methods.add_method_mut("spawn", |lua, me, ()| { - Ok(match me.inner.spawn() { - Ok(child) => (Child::new(child).into_lua(lua)?, Value::Nil), - Err(e) => (Value::Nil, e.raw_os_error().into_lua(lua)?), - }) + methods.add_method_mut("spawn", |lua, me, ()| match me.inner.spawn() { + Ok(child) => (Child::new(child), Value::Nil).into_lua_multi(lua), + Err(e) => (Value::Nil, e.raw_os_error()).into_lua_multi(lua), }); methods.add_async_method_mut("output", |lua, me, ()| async move { - Ok(match me.inner.output().await { - Ok(output) => (Output::new(output).into_lua(lua)?, Value::Nil), - Err(e) => (Value::Nil, e.raw_os_error().into_lua(lua)?), - }) + match me.inner.output().await { + Ok(output) => (Output::new(output), Value::Nil).into_lua_multi(lua), + Err(e) => (Value::Nil, e.raw_os_error()).into_lua_multi(lua), + } }); } } From 42307ee037ff60c5d32762a6d905ddf500369019 Mon Sep 17 00:00:00 2001 From: sxyazi Date: Wed, 6 Mar 2024 21:44:38 +0800 Subject: [PATCH 2/9] feat: test new color system --- yazi-config/preset/theme.toml | 44 +++++++++++++++++------------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/yazi-config/preset/theme.toml b/yazi-config/preset/theme.toml index f54ee2c9..d095533e 100644 --- a/yazi-config/preset/theme.toml +++ b/yazi-config/preset/theme.toml @@ -31,14 +31,14 @@ marker_marked = { fg = "lightyellow", bg = "lightyellow" } marker_selected = { fg = "lightblue", bg = "lightblue" } # Tab -tab_active = { fg = "black", bg = "white" } -tab_inactive = { fg = "white", bg = "darkgray" } +tab_active = { reversed = true } +tab_inactive = {} tab_width = 1 # Count -count_copied = { fg = "black", bg = "lightgreen" } -count_cut = { fg = "black", bg = "lightred" } -count_selected = { fg = "black", bg = "lightblue" } +count_copied = { fg = "white", bg = "green" } +count_cut = { fg = "white", bg = "red" } +count_selected = { fg = "white", bg = "blue" } # Border border_symbol = "│" @@ -55,12 +55,12 @@ syntect_theme = "" [status] separator_open = "" separator_close = "" -separator_style = { fg = "darkgray", bg = "darkgray" } +separator_style = { fg = "gray", bg = "gray" } # Mode -mode_normal = { fg = "black", bg = "lightblue", bold = true } -mode_select = { fg = "black", bg = "lightgreen", bold = true } -mode_unset = { fg = "black", bg = "lightmagenta", bold = true } +mode_normal = { bg = "blue", bold = true } +mode_select = { bg = "red", bold = true } +mode_unset = { bg = "red", bold = true } # Progress progress_label = { bold = true } @@ -68,10 +68,10 @@ progress_normal = { fg = "blue", bg = "black" } progress_error = { fg = "red", bg = "black" } # Permissions -permissions_t = { fg = "lightgreen" } -permissions_r = { fg = "lightyellow" } -permissions_w = { fg = "lightred" } -permissions_x = { fg = "lightcyan" } +permissions_t = { fg = "green" } +permissions_r = { fg = "yellow" } +permissions_w = { fg = "red" } +permissions_x = { fg = "cyan" } permissions_s = { fg = "darkgray" } # : }}} @@ -81,7 +81,7 @@ permissions_s = { fg = "darkgray" } [select] border = { fg = "blue" } -active = { fg = "magenta" } +active = { fg = "magenta", bold = true } inactive = {} # : }}} @@ -102,7 +102,7 @@ selected = { reversed = true } [completion] border = { fg = "blue" } -active = { bg = "darkgray" } +active = { reversed = true } inactive = {} # Icons @@ -118,7 +118,7 @@ icon_command = "" [tasks] border = { fg = "blue" } title = {} -hovered = { underline = true } +hovered = { fg = "magenta", underline = true } # : }}} @@ -130,7 +130,7 @@ cols = 3 mask = { bg = "black" } cand = { fg = "lightcyan" } rest = { fg = "darkgray" } -desc = { fg = "magenta" } +desc = { fg = "lightmagenta" } separator = "  " separator_style = { fg = "darkgray" } @@ -140,10 +140,10 @@ separator_style = { fg = "darkgray" } # : Help {{{ [help] -on = { fg = "magenta" } -run = { fg = "cyan" } -desc = { fg = "gray" } -hovered = { bg = "darkgray", bold = true } +on = { fg = "cyan" } +run = { fg = "magenta" } +desc = {} +hovered = { reversed = true, bold = true } footer = { fg = "black", bg = "white" } # : }}} @@ -172,7 +172,7 @@ rules = [ # Images { mime = "image/*", fg = "cyan" }, - # Videos + # Media { mime = "video/*", fg = "yellow" }, { mime = "audio/*", fg = "yellow" }, From b6e458f221bfd4469d9c4ec1b833db4bc81ee768 Mon Sep 17 00:00:00 2001 From: hankertrix <91734413+hankertrix@users.noreply.github.com> Date: Wed, 6 Mar 2024 23:39:52 +0800 Subject: [PATCH 3/9] feat: add `` and `` to the select component for moving the cursor up/down (#779) --- yazi-config/preset/keymap.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/yazi-config/preset/keymap.toml b/yazi-config/preset/keymap.toml index 420408fc..bce3ed3e 100644 --- a/yazi-config/preset/keymap.toml +++ b/yazi-config/preset/keymap.toml @@ -276,6 +276,9 @@ keymap = [ { on = [ "" ], run = "arrow -1", desc = "Move cursor up" }, { on = [ "" ], run = "arrow 1", desc = "Move cursor down" }, + { on = [ "" ], run = "arrow -1", desc = "Move cursor up" }, + { on = [ "" ], run = "arrow 1", desc = "Move cursor down" }, + { on = [ "~" ], run = "help", desc = "Open help" } ] From 1aed6e8b36c8c7ff2575afb49c69d9493b63dcab Mon Sep 17 00:00:00 2001 From: Prajna Date: Wed, 6 Mar 2024 23:43:39 +0800 Subject: [PATCH 4/9] feat: send a foreground notification to the user when the process fails to run (#775) --- yazi-plugin/src/external/shell.rs | 1 + yazi-scheduler/src/process/op.rs | 13 ++--- yazi-scheduler/src/process/process.rs | 70 ++++++++++++++++++--------- 3 files changed, 51 insertions(+), 33 deletions(-) diff --git a/yazi-plugin/src/external/shell.rs b/yazi-plugin/src/external/shell.rs index 2b85a757..cc7039b7 100644 --- a/yazi-plugin/src/external/shell.rs +++ b/yazi-plugin/src/external/shell.rs @@ -3,6 +3,7 @@ use std::{env, ffi::OsString, process::Stdio}; use anyhow::Result; use tokio::process::{Child, Command}; +#[derive(Default)] pub struct ShellOpt { pub cmd: OsString, pub args: Vec, diff --git a/yazi-scheduler/src/process/op.rs b/yazi-scheduler/src/process/op.rs index 8436f37a..5275b3fa 100644 --- a/yazi-scheduler/src/process/op.rs +++ b/yazi-scheduler/src/process/op.rs @@ -1,4 +1,4 @@ -use std::{ffi::OsString, mem}; +use std::ffi::OsString; use tokio::sync::oneshot; use yazi_plugin::external::ShellOpt; @@ -13,13 +13,8 @@ pub struct ProcessOpOpen { pub cancel: oneshot::Sender<()>, } -impl From<&mut ProcessOpOpen> for ShellOpt { - fn from(op: &mut ProcessOpOpen) -> Self { - Self { - cmd: mem::take(&mut op.cmd), - args: mem::take(&mut op.args), - piped: false, - orphan: op.orphan, - } +impl From for ShellOpt { + fn from(op: ProcessOpOpen) -> Self { + Self { cmd: op.cmd, args: op.args, piped: false, orphan: op.orphan } } } diff --git a/yazi-scheduler/src/process/process.rs b/yazi-scheduler/src/process/process.rs index 12582964..3d5bf253 100644 --- a/yazi-scheduler/src/process/process.rs +++ b/yazi-scheduler/src/process/process.rs @@ -2,6 +2,7 @@ use anyhow::Result; use tokio::{io::{AsyncBufReadExt, BufReader}, select, sync::mpsc}; use yazi_plugin::external::{self, ShellOpt}; use yazi_proxy::AppProxy; +use yazi_shared::Defer; use super::ProcessOpOpen; use crate::{TaskProg, BLOCKER}; @@ -14,37 +15,21 @@ impl Process { pub fn new(prog: mpsc::UnboundedSender) -> Self { Self { prog } } pub async fn open(&self, mut task: ProcessOpOpen) -> Result<()> { - let opt = ShellOpt::from(&mut task); if task.block { - let _guard = BLOCKER.acquire().await.unwrap(); - AppProxy::stop().await; - - match external::shell(opt) { - Ok(mut child) => { - child.wait().await.ok(); - self.succ(task.id)?; - } - Err(e) => { - self.prog.send(TaskProg::New(task.id, 0))?; - self.fail(task.id, format!("Failed to spawn process: {e}"))?; - } - } - return Ok(AppProxy::resume()); + return self.open_block(task).await; } if task.orphan { - match external::shell(opt) { - Ok(_) => self.succ(task.id)?, - Err(e) => { - self.prog.send(TaskProg::New(task.id, 0))?; - self.fail(task.id, format!("Failed to spawn process: {e}"))?; - } - } - return Ok(()); + return self.open_orphan(task).await; } self.prog.send(TaskProg::New(task.id, 0))?; - let mut child = external::shell(opt.with_piped())?; + let mut child = external::shell(ShellOpt { + cmd: task.cmd, + args: task.args, + piped: true, + ..Default::default() + })?; let mut stdout = BufReader::new(child.stdout.take().unwrap()).lines(); let mut stderr = BufReader::new(child.stderr.take().unwrap()).lines(); @@ -76,6 +61,43 @@ impl Process { self.prog.send(TaskProg::Adv(task.id, 1, 0))?; self.succ(task.id) } + + async fn open_block(&self, task: ProcessOpOpen) -> Result<()> { + let _guard = BLOCKER.acquire().await.unwrap(); + let _defer = Defer::new(AppProxy::resume); + AppProxy::stop().await; + + let (id, cmd) = (task.id, task.cmd.clone()); + let result = external::shell(task.into()); + if let Err(e) = result { + AppProxy::notify_warn(&cmd.to_string_lossy(), &format!("Failed to spawn process: {e}")); + return self.succ(id); + } + + let status = result.unwrap().wait().await?; + if !status.success() { + let content = match status.code() { + Some(code) => format!("Process exited with status code: {code}"), + None => "Process terminated by signal".to_string(), + }; + AppProxy::notify_warn(&cmd.to_string_lossy(), &content); + } + + self.succ(id) + } + + async fn open_orphan(&self, task: ProcessOpOpen) -> Result<()> { + let id = task.id; + match external::shell(task.into()) { + Ok(_) => self.succ(id)?, + Err(e) => { + self.prog.send(TaskProg::New(id, 0))?; + self.fail(id, format!("Failed to spawn process: {e}"))?; + } + } + + Ok(()) + } } impl Process { From 33782f12240147a84ba8158670ea074e6c644788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Thu, 7 Mar 2024 17:26:18 +0800 Subject: [PATCH 5/9] feat: `cx.yanked` plugin API (#788) --- yazi-config/src/open/open.rs | 6 ++-- yazi-core/src/completion/completion.rs | 4 +-- yazi-core/src/folder/files.rs | 20 +++++------ yazi-core/src/folder/sorter.rs | 4 +-- yazi-core/src/manager/commands/hover.rs | 4 +-- yazi-core/src/manager/commands/rename.rs | 4 +-- yazi-core/src/manager/linked.rs | 6 ++-- yazi-core/src/manager/watcher.rs | 14 ++++---- yazi-core/src/tab/commands/visual_mode.rs | 6 ++-- yazi-core/src/tab/finder.rs | 6 ++-- yazi-core/src/tab/mode.rs | 10 +++--- yazi-core/src/tab/selected.rs | 6 ++-- yazi-core/src/tab/tab.rs | 4 +-- yazi-core/src/tasks/tasks.rs | 4 +-- yazi-fm/src/lives/iter.rs | 25 +++++++++++++ yazi-fm/src/lives/lives.rs | 2 +- yazi-fm/src/lives/mod.rs | 2 ++ yazi-fm/src/lives/selected.rs | 43 +++++++++-------------- yazi-fm/src/lives/tasks.rs | 2 +- yazi-fm/src/lives/yanked.rs | 41 +++++++++++++++++++-- yazi-plugin/src/loader.rs | 6 ++-- yazi-plugin/src/utils/call.rs | 6 ++-- yazi-scheduler/src/preload/preload.rs | 6 ++-- yazi-scheduler/src/running.rs | 7 ++-- yazi-shared/src/event/cmd.rs | 4 +-- yazi-shared/src/fs/op.rs | 8 ++--- 26 files changed, 150 insertions(+), 100 deletions(-) create mode 100644 yazi-fm/src/lives/iter.rs diff --git a/yazi-config/src/open/open.rs b/yazi-config/src/open/open.rs index a18ebfed..0ccd8983 100644 --- a/yazi-config/src/open/open.rs +++ b/yazi-config/src/open/open.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, path::Path}; +use std::{collections::HashMap, path::Path}; use indexmap::IndexSet; use serde::{Deserialize, Deserializer}; @@ -10,7 +10,7 @@ use crate::{open::OpenRule, Preset, MERGED_YAZI}; #[derive(Debug)] pub struct Open { rules: Vec, - openers: BTreeMap>, + openers: HashMap>, } impl Default for Open { @@ -65,7 +65,7 @@ impl<'de> Deserialize<'de> for Open { { #[derive(Deserialize)] struct Outer { - opener: BTreeMap>, + opener: HashMap>, open: OuterOpen, } #[derive(Deserialize)] diff --git a/yazi-core/src/completion/completion.rs b/yazi-core/src/completion/completion.rs index af5279e4..5cad5a00 100644 --- a/yazi-core/src/completion/completion.rs +++ b/yazi-core/src/completion/completion.rs @@ -1,8 +1,8 @@ -use std::collections::BTreeMap; +use std::collections::HashMap; #[derive(Default)] pub struct Completion { - pub(super) caches: BTreeMap>, + pub(super) caches: HashMap>, pub(super) cands: Vec, pub(super) offset: usize, pub cursor: usize, diff --git a/yazi-core/src/folder/files.rs b/yazi-core/src/folder/files.rs index b4a61acc..6ca0d49c 100644 --- a/yazi-core/src/folder/files.rs +++ b/yazi-core/src/folder/files.rs @@ -1,4 +1,4 @@ -use std::{collections::{BTreeMap, BTreeSet}, mem, ops::Deref, sync::atomic::Ordering}; +use std::{collections::{HashMap, HashSet}, mem, ops::Deref, sync::atomic::Ordering}; use anyhow::Result; use tokio::{fs::{self, DirEntry}, select, sync::mpsc::{self, UnboundedReceiver}}; @@ -14,7 +14,7 @@ pub struct Files { version: u64, pub(crate) revision: u64, - pub sizes: BTreeMap, + pub sizes: HashMap, sorter: FilesSorter, filter: Option, @@ -128,7 +128,7 @@ impl Files { } } - pub fn update_size(&mut self, sizes: BTreeMap) { + pub fn update_size(&mut self, sizes: HashMap) { if sizes.is_empty() { return; } @@ -146,7 +146,7 @@ impl Files { macro_rules! go { ($dist:expr, $src:expr, $inc:literal) => { - let mut todo: BTreeMap<_, _> = $src.into_iter().map(|f| (f.url(), f)).collect(); + let mut todo: HashMap<_, _> = $src.into_iter().map(|f| (f.url(), f)).collect(); for f in &$dist { if todo.remove(&f.url).is_some() && todo.is_empty() { break; @@ -176,7 +176,7 @@ impl Files { macro_rules! go { ($dist:expr, $src:expr, $inc:literal) => { - let mut todo: BTreeSet<_> = $src.into_iter().collect(); + let mut todo: HashSet<_> = $src.into_iter().collect(); let len = $dist.len(); $dist.retain(|f| !todo.remove(&f.url)); @@ -217,7 +217,7 @@ impl Files { }; } - let mut urls: BTreeSet<_> = urls.into_iter().collect(); + let mut urls: HashSet<_> = urls.into_iter().collect(); if !urls.is_empty() { go!(self.items, urls, 1); } @@ -228,8 +228,8 @@ impl Files { pub fn update_updating( &mut self, - files: BTreeMap, - ) -> (BTreeMap, BTreeMap) { + files: HashMap, + ) -> (HashMap, HashMap) { if files.is_empty() { return Default::default(); } @@ -257,7 +257,7 @@ impl Files { || !f.url.file_name().is_some_and(|s| filter.matches(s)) }) } else if self.show_hidden { - (BTreeMap::new(), files) + (HashMap::new(), files) } else { files.into_iter().partition(|(_, f)| f.is_hidden()) }; @@ -271,7 +271,7 @@ impl Files { (hidden, items) } - pub fn update_upserting(&mut self, files: BTreeMap) { + pub fn update_upserting(&mut self, files: HashMap) { if files.is_empty() { return; } diff --git a/yazi-core/src/folder/sorter.rs b/yazi-core/src/folder/sorter.rs index 779b22e2..5130ac68 100644 --- a/yazi-core/src/folder/sorter.rs +++ b/yazi-core/src/folder/sorter.rs @@ -1,4 +1,4 @@ -use std::{cmp::Ordering, collections::BTreeMap, mem}; +use std::{cmp::Ordering, collections::HashMap, mem}; use yazi_config::manager::SortBy; use yazi_shared::{fs::{File, Url}, natsort}; @@ -12,7 +12,7 @@ pub struct FilesSorter { } impl FilesSorter { - pub(super) fn sort(&self, items: &mut Vec, sizes: &BTreeMap) { + pub(super) fn sort(&self, items: &mut Vec, sizes: &HashMap) { if items.is_empty() { return; } diff --git a/yazi-core/src/manager/commands/hover.rs b/yazi-core/src/manager/commands/hover.rs index 4d46e1b8..94df57d0 100644 --- a/yazi-core/src/manager/commands/hover.rs +++ b/yazi-core/src/manager/commands/hover.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeSet; +use std::collections::HashSet; use yazi_shared::{event::Cmd, fs::Url, render}; @@ -31,7 +31,7 @@ impl Manager { self.peek(false); // Refresh watcher - let mut to_watch = BTreeSet::new(); + let mut to_watch = HashSet::with_capacity(3 * self.tabs.len()); for tab in self.tabs.iter() { to_watch.insert(&tab.current.cwd); if let Some(ref p) = tab.parent { diff --git a/yazi-core/src/manager/commands/rename.rs b/yazi-core/src/manager/commands/rename.rs index a980e716..3cb990e4 100644 --- a/yazi-core/src/manager/commands/rename.rs +++ b/yazi-core/src/manager/commands/rename.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, ffi::{OsStr, OsString}, io::{stdout, BufWriter, Write}, path::PathBuf}; +use std::{collections::HashMap, ffi::{OsStr, OsString}, io::{stdout, BufWriter, Write}, path::PathBuf}; use anyhow::{anyhow, bail, Result}; use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}}; @@ -49,7 +49,7 @@ impl Manager { let file = File::from(new.clone()).await?; FilesOp::Deleting(file.parent().unwrap(), vec![new.clone()]).emit(); - FilesOp::Upserting(file.parent().unwrap(), BTreeMap::from_iter([(old, file)])).emit(); + FilesOp::Upserting(file.parent().unwrap(), HashMap::from_iter([(old, file)])).emit(); Ok(ManagerProxy::hover(Some(new))) } diff --git a/yazi-core/src/manager/linked.rs b/yazi-core/src/manager/linked.rs index 9d688feb..c29fc201 100644 --- a/yazi-core/src/manager/linked.rs +++ b/yazi-core/src/manager/linked.rs @@ -1,12 +1,12 @@ -use std::{collections::BTreeMap, ops::{Deref, DerefMut}}; +use std::{collections::HashMap, ops::{Deref, DerefMut}}; use yazi_shared::fs::Url; #[derive(Default)] -pub struct Linked(BTreeMap /* from ==> to */); +pub struct Linked(HashMap /* from ==> to */); impl Deref for Linked { - type Target = BTreeMap; + type Target = HashMap; fn deref(&self) -> &Self::Target { &self.0 } } diff --git a/yazi-core/src/manager/watcher.rs b/yazi-core/src/manager/watcher.rs index 5aece338..e8b0ecbf 100644 --- a/yazi-core/src/manager/watcher.rs +++ b/yazi-core/src/manager/watcher.rs @@ -1,4 +1,4 @@ -use std::{collections::{BTreeMap, BTreeSet}, sync::Arc, time::{Duration, SystemTime}}; +use std::{collections::{HashMap, HashSet}, sync::Arc, time::{Duration, SystemTime}}; use anyhow::Result; use notify::{event::{MetadataKind, ModifyKind}, EventKind, RecommendedWatcher, RecursiveMode, Watcher as _Watcher}; @@ -14,7 +14,7 @@ use crate::folder::{Files, Folder}; pub struct Watcher { watcher: RecommendedWatcher, - watched: Arc>>, + watched: Arc>>, pub linked: Arc>, } @@ -60,11 +60,11 @@ impl Watcher { instance } - pub(super) fn watch(&mut self, mut new: BTreeSet<&Url>) { + pub(super) fn watch(&mut self, mut new: HashSet<&Url>) { new.retain(|&u| u.is_regular()); - let (to_unwatch, to_watch): (BTreeSet<_>, BTreeSet<_>) = { + let (to_unwatch, to_watch): (HashSet<_>, HashSet<_>) = { let guard = self.watched.read(); - let old: BTreeSet<_> = guard.iter().collect(); + let old: HashSet<_> = guard.iter().collect(); ( old.difference(&new).map(|&x| x.clone()).collect(), new.difference(&old).map(|&x| x.clone()).collect(), @@ -147,7 +147,7 @@ impl Watcher { pin!(rx); while let Some(urls) = rx.next().await { - let urls: BTreeSet<_> = urls.into_iter().collect(); + let urls: HashSet<_> = urls.into_iter().collect(); let mut reload = Vec::with_capacity(urls.len()); for u in urls { @@ -163,7 +163,7 @@ impl Watcher { if !file.is_dir() { reload.push(file.clone()); } - FilesOp::Upserting(parent, BTreeMap::from_iter([(u, file)])).emit(); + FilesOp::Upserting(parent, HashMap::from_iter([(u, file)])).emit(); } if reload.is_empty() { diff --git a/yazi-core/src/tab/commands/visual_mode.rs b/yazi-core/src/tab/commands/visual_mode.rs index 8e19ace5..6d618525 100644 --- a/yazi-core/src/tab/commands/visual_mode.rs +++ b/yazi-core/src/tab/commands/visual_mode.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeSet; +use std::collections::HashSet; use yazi_shared::{event::Cmd, render}; @@ -18,9 +18,9 @@ impl Tab { let idx = self.current.cursor; if opt.unset { - self.mode = Mode::Unset(idx, BTreeSet::from([idx])); + self.mode = Mode::Unset(idx, HashSet::from([idx])); } else { - self.mode = Mode::Select(idx, BTreeSet::from([idx])); + self.mode = Mode::Select(idx, HashSet::from([idx])); }; render!(); } diff --git a/yazi-core/src/tab/finder.rs b/yazi-core/src/tab/finder.rs index 72239ce9..2da8571a 100644 --- a/yazi-core/src/tab/finder.rs +++ b/yazi-core/src/tab/finder.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::collections::HashMap; use anyhow::Result; use yazi_shared::fs::Url; @@ -7,7 +7,7 @@ use crate::folder::{Files, Filter, FilterCase}; pub struct Finder { pub filter: Filter, - matched: BTreeMap, + matched: HashMap, revision: u64, } @@ -63,7 +63,7 @@ impl Finder { impl Finder { #[inline] - pub fn matched(&self) -> &BTreeMap { &self.matched } + pub fn matched(&self) -> &HashMap { &self.matched } #[inline] pub fn matched_idx(&self, url: &Url) -> Option { self.matched.get(url).copied() } diff --git a/yazi-core/src/tab/mode.rs b/yazi-core/src/tab/mode.rs index 05c8d8e5..98f27139 100644 --- a/yazi-core/src/tab/mode.rs +++ b/yazi-core/src/tab/mode.rs @@ -1,15 +1,15 @@ -use std::{collections::BTreeSet, fmt::Display, mem}; +use std::{collections::HashSet, fmt::Display, mem}; #[derive(Clone, Debug, Default, Eq, PartialEq)] pub enum Mode { #[default] Normal, - Select(usize, BTreeSet), - Unset(usize, BTreeSet), + Select(usize, HashSet), + Unset(usize, HashSet), } impl Mode { - pub fn visual_mut(&mut self) -> Option<(usize, &mut BTreeSet)> { + pub fn visual_mut(&mut self) -> Option<(usize, &mut HashSet)> { match self { Mode::Normal => None, Mode::Select(start, indices) => Some((*start, indices)), @@ -17,7 +17,7 @@ impl Mode { } } - pub fn take_visual(&mut self) -> Option<(usize, BTreeSet)> { + pub fn take_visual(&mut self) -> Option<(usize, HashSet)> { match mem::take(self) { Mode::Normal => None, Mode::Select(start, indices) => Some((start, indices)), diff --git a/yazi-core/src/tab/selected.rs b/yazi-core/src/tab/selected.rs index 86bcd0d6..18e56f66 100644 --- a/yazi-core/src/tab/selected.rs +++ b/yazi-core/src/tab/selected.rs @@ -1,15 +1,15 @@ -use std::{collections::{BTreeSet, HashMap}, ops::Deref}; +use std::{collections::{HashMap, HashSet}, ops::Deref}; use yazi_shared::fs::Url; #[derive(Default)] pub struct Selected { - inner: BTreeSet, + inner: HashSet, parents: HashMap, } impl Deref for Selected { - type Target = BTreeSet; + type Target = HashSet; fn deref(&self) -> &Self::Target { &self.inner } } diff --git a/yazi-core/src/tab/tab.rs b/yazi-core/src/tab/tab.rs index 42445a09..0a36c1f4 100644 --- a/yazi-core/src/tab/tab.rs +++ b/yazi-core/src/tab/tab.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::collections::HashMap; use anyhow::Result; use tokio::task::JoinHandle; @@ -14,7 +14,7 @@ pub struct Tab { pub parent: Option, pub backstack: Backstack, - pub history: BTreeMap, + pub history: HashMap, pub selected: Selected, pub preview: Preview, diff --git a/yazi-core/src/tasks/tasks.rs b/yazi-core/src/tasks/tasks.rs index 22f6a6b8..5d8d20ae 100644 --- a/yazi-core/src/tasks/tasks.rs +++ b/yazi-core/src/tasks/tasks.rs @@ -1,4 +1,4 @@ -use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, mem, sync::Arc, time::Duration}; +use std::{collections::{HashMap, HashSet}, ffi::OsStr, mem, sync::Arc, time::Duration}; use tokio::time::sleep; use tracing::debug; @@ -57,7 +57,7 @@ impl Tasks { } pub fn file_open(&self, hovered: &Url, targets: &[(Url, String)]) { - let mut openers = BTreeMap::new(); + let mut openers = HashMap::new(); for (url, mime) in targets { if let Some(opener) = OPEN.openers(url, mime).and_then(|o| o.first().copied()) { openers.entry(opener).or_insert_with(|| vec![hovered]).push(url); diff --git a/yazi-fm/src/lives/iter.rs b/yazi-fm/src/lives/iter.rs new file mode 100644 index 00000000..4b0e0989 --- /dev/null +++ b/yazi-fm/src/lives/iter.rs @@ -0,0 +1,25 @@ +use mlua::AnyUserData; + +use super::SCOPE; + +pub(super) struct Iter, T> { + inner: I, + count: usize, +} + +impl + 'static, T: 'static> Iter { + #[inline] + pub(super) fn make(inner: I) -> mlua::Result> { + SCOPE.create_any_userdata(Self { inner, count: 0 }) + } +} + +impl, T> Iterator for Iter { + type Item = (usize, T); + + fn next(&mut self) -> Option { + let next = self.inner.next()?; + self.count += 1; + Some((self.count, next)) + } +} diff --git a/yazi-fm/src/lives/lives.rs b/yazi-fm/src/lives/lives.rs index 3cbc9b5d..03e12cad 100644 --- a/yazi-fm/src/lives/lives.rs +++ b/yazi-fm/src/lives/lives.rs @@ -45,7 +45,7 @@ impl Lives { ("active", super::Tab::make(cx.manager.active())?), ("tabs", super::Tabs::make(&cx.manager.tabs)?), ("tasks", super::Tasks::make(&cx.tasks)?), - ("yanked", scope.create_any_userdata_ref(&cx.manager.yanked)?), + ("yanked", super::Yanked::make(&cx.manager.yanked)?), ])?, )?; diff --git a/yazi-fm/src/lives/mod.rs b/yazi-fm/src/lives/mod.rs index 52e6ff63..d910d4c7 100644 --- a/yazi-fm/src/lives/mod.rs +++ b/yazi-fm/src/lives/mod.rs @@ -4,6 +4,7 @@ mod config; mod file; mod files; mod folder; +mod iter; mod lives; mod mode; mod preview; @@ -17,6 +18,7 @@ use config::*; use file::*; use files::*; use folder::*; +use iter::*; pub(super) use lives::*; use mode::*; use preview::*; diff --git a/yazi-fm/src/lives/selected.rs b/yazi-fm/src/lives/selected.rs index 29fbfb34..57f7e16e 100644 --- a/yazi-fm/src/lives/selected.rs +++ b/yazi-fm/src/lives/selected.rs @@ -1,24 +1,24 @@ -use std::{collections::{btree_set, BTreeSet}, ops::Deref}; +use std::{collections::{hash_set, HashSet}, ops::Deref}; use mlua::{AnyUserData, IntoLuaMulti, Lua, MetaMethod, UserDataMethods, UserDataRefMut}; use yazi_plugin::{bindings::Cast, url::Url}; -use super::SCOPE; +use super::{Iter, SCOPE}; #[derive(Clone, Copy)] pub(super) struct Selected { - inner: *const BTreeSet, + inner: *const HashSet, } impl Deref for Selected { - type Target = BTreeSet; + type Target = HashSet; fn deref(&self) -> &Self::Target { self.inner() } } impl Selected { #[inline] - pub(crate) fn make(inner: &BTreeSet) -> mlua::Result> { + pub(super) fn make(inner: &HashSet) -> mlua::Result> { SCOPE.create_any_userdata(Self { inner }) } @@ -27,16 +27,17 @@ impl Selected { reg.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len())); reg.add_meta_method(MetaMethod::Pairs, |lua, me, ()| { - let iter = lua.create_function(|lua, mut iter: UserDataRefMut| { - if let Some(url) = iter.inner.next() { - iter.next += 1; - (iter.next, Url::cast(lua, url.clone())?).into_lua_multi(lua) - } else { - ().into_lua_multi(lua) - } - })?; + let iter = lua.create_function( + |lua, mut iter: UserDataRefMut, _>>| { + if let Some(next) = iter.next() { + (next.0, Url::cast(lua, next.1.clone())?).into_lua_multi(lua) + } else { + ().into_lua_multi(lua) + } + }, + )?; - Ok((iter, SelectedIter::make(me.inner()))) + Ok((iter, Iter::make(me.inner().iter()))) }); })?; @@ -44,17 +45,5 @@ impl Selected { } #[inline] - fn inner(&self) -> &'static BTreeSet { unsafe { &*self.inner } } -} - -struct SelectedIter { - next: usize, - inner: btree_set::Iter<'static, yazi_shared::fs::Url>, -} - -impl SelectedIter { - #[inline] - fn make(selected: &'static BTreeSet) -> mlua::Result> { - SCOPE.create_any_userdata(Self { next: 0, inner: selected.iter() }) - } + fn inner(&self) -> &'static HashSet { unsafe { &*self.inner } } } diff --git a/yazi-fm/src/lives/tasks.rs b/yazi-fm/src/lives/tasks.rs index cc83fe6b..481e7004 100644 --- a/yazi-fm/src/lives/tasks.rs +++ b/yazi-fm/src/lives/tasks.rs @@ -16,7 +16,7 @@ impl Deref for Tasks { impl Tasks { #[inline] - pub(crate) fn make(inner: &yazi_core::tasks::Tasks) -> mlua::Result> { + pub(super) fn make(inner: &yazi_core::tasks::Tasks) -> mlua::Result> { SCOPE.create_any_userdata(Self { inner }) } diff --git a/yazi-fm/src/lives/yanked.rs b/yazi-fm/src/lives/yanked.rs index 3051ecae..9c35b79b 100644 --- a/yazi-fm/src/lives/yanked.rs +++ b/yazi-fm/src/lives/yanked.rs @@ -1,13 +1,48 @@ -use mlua::{Lua, MetaMethod, UserDataFields, UserDataMethods}; +use std::{collections::hash_set, ops::Deref}; -pub(super) struct Yanked; +use mlua::{AnyUserData, IntoLuaMulti, Lua, MetaMethod, UserDataFields, UserDataMethods, UserDataRefMut}; +use yazi_plugin::{bindings::Cast, url::Url}; + +use super::{Iter, SCOPE}; + +pub(super) struct Yanked { + inner: *const yazi_core::manager::Yanked, +} + +impl Deref for Yanked { + type Target = yazi_core::manager::Yanked; + + fn deref(&self) -> &Self::Target { self.inner() } +} impl Yanked { + #[inline] + pub(super) fn make(inner: &yazi_core::manager::Yanked) -> mlua::Result> { + SCOPE.create_any_userdata(Self { inner }) + } + pub(super) fn register(lua: &Lua) -> mlua::Result<()> { - lua.register_userdata_type::(|reg| { + lua.register_userdata_type::(|reg| { reg.add_field_method_get("is_cut", |_, me| Ok(me.cut)); reg.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len())); + + reg.add_meta_method(MetaMethod::Pairs, |lua, me, ()| { + let iter = lua.create_function( + |lua, mut iter: UserDataRefMut, _>>| { + if let Some(next) = iter.next() { + (next.0, Url::cast(lua, next.1.clone())?).into_lua_multi(lua) + } else { + ().into_lua_multi(lua) + } + }, + )?; + + Ok((iter, Iter::make(me.inner().iter()))) + }); }) } + + #[inline] + fn inner(&self) -> &'static yazi_core::manager::Yanked { unsafe { &*self.inner } } } diff --git a/yazi-plugin/src/loader.rs b/yazi-plugin/src/loader.rs index 3408b418..b8b20443 100644 --- a/yazi-plugin/src/loader.rs +++ b/yazi-plugin/src/loader.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, collections::BTreeMap, ops::Deref}; +use std::{borrow::Cow, collections::HashMap, ops::Deref}; use anyhow::{bail, Result}; use mlua::{ExternalError, Table}; @@ -13,7 +13,7 @@ pub static LOADED: RoCell = RoCell::new(); #[derive(Default)] pub struct Loader { - loaded: RwLock>>, + loaded: RwLock>>, } impl Loader { @@ -67,7 +67,7 @@ impl Loader { } impl Deref for Loader { - type Target = RwLock>>; + type Target = RwLock>>; #[inline] fn deref(&self) -> &Self::Target { &self.loaded } diff --git a/yazi-plugin/src/utils/call.rs b/yazi-plugin/src/utils/call.rs index aa966478..183adf6d 100644 --- a/yazi-plugin/src/utils/call.rs +++ b/yazi-plugin/src/utils/call.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::collections::HashMap; use mlua::{ExternalError, Lua, Table, Value}; use yazi_shared::{emit, event::Cmd, render, Layer}; @@ -7,9 +7,9 @@ use super::Utils; use crate::ValueSendable; impl Utils { - fn parse_args(t: Table) -> mlua::Result<(Vec, BTreeMap)> { + fn parse_args(t: Table) -> mlua::Result<(Vec, HashMap)> { let mut args = vec![]; - let mut named = BTreeMap::new(); + let mut named = HashMap::new(); for result in t.pairs::() { let (k, v) = result?; match k { diff --git a/yazi-scheduler/src/preload/preload.rs b/yazi-scheduler/src/preload/preload.rs index 19f49257..5c73a8fc 100644 --- a/yazi-scheduler/src/preload/preload.rs +++ b/yazi-scheduler/src/preload/preload.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::collections::{HashMap, HashSet}; use anyhow::Result; use parking_lot::RwLock; @@ -16,7 +16,7 @@ pub struct Preload { prog: mpsc::UnboundedSender, pub rule_loaded: RwLock>, - pub size_loading: RwLock>, + pub size_loading: RwLock>, } impl Preload { @@ -60,7 +60,7 @@ impl Preload { } let parent = buf[0].0.parent_url().unwrap(); - FilesOp::Size(parent, BTreeMap::from_iter(buf)).emit(); + FilesOp::Size(parent, HashMap::from_iter(buf)).emit(); }); self.prog.send(TaskProg::Adv(task.id, 1, 0))?; } diff --git a/yazi-scheduler/src/running.rs b/yazi-scheduler/src/running.rs index 8f771f0a..86298d3c 100644 --- a/yazi-scheduler/src/running.rs +++ b/yazi-scheduler/src/running.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::collections::HashMap; use futures::future::BoxFuture; use yazi_config::TASKS; @@ -10,9 +10,8 @@ use crate::TaskKind; pub struct Running { incr: usize, - pub(super) hooks: - BTreeMap BoxFuture<'static, ()>) + Send + Sync>>, - pub(super) all: BTreeMap, + pub(super) hooks: HashMap BoxFuture<'static, ()>) + Send + Sync>>, + pub(super) all: HashMap, } impl Running { diff --git a/yazi-shared/src/event/cmd.rs b/yazi-shared/src/event/cmd.rs index c519dd6b..f37c1941 100644 --- a/yazi-shared/src/event/cmd.rs +++ b/yazi-shared/src/event/cmd.rs @@ -1,10 +1,10 @@ -use std::{any::Any, collections::BTreeMap, fmt::{self, Display}, mem}; +use std::{any::Any, collections::HashMap, fmt::{self, Display}, mem}; #[derive(Debug, Default)] pub struct Cmd { pub name: String, pub args: Vec, - pub named: BTreeMap, + pub named: HashMap, pub data: Option>, } diff --git a/yazi-shared/src/fs/op.rs b/yazi-shared/src/fs/op.rs index 61993dce..20d3c3be 100644 --- a/yazi-shared/src/fs/op.rs +++ b/yazi-shared/src/fs/op.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, sync::atomic::{AtomicU64, Ordering}, time::SystemTime}; +use std::{collections::HashMap, sync::atomic::{AtomicU64, Ordering}, time::SystemTime}; use super::File; use crate::{emit, event::Cmd, fs::Url, Layer}; @@ -10,12 +10,12 @@ pub enum FilesOp { Full(Url, Vec, Option), Part(Url, Vec, u64), Done(Url, Option, u64), - Size(Url, BTreeMap), + Size(Url, HashMap), Creating(Url, Vec), Deleting(Url, Vec), - Updating(Url, BTreeMap), - Upserting(Url, BTreeMap), + Updating(Url, HashMap), + Upserting(Url, HashMap), } impl FilesOp { From d96af545748d4ceac55dd613add7157f39b9bd3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Fri, 8 Mar 2024 15:37:53 +0800 Subject: [PATCH 6/9] fix: track the latest file changes for the `selected`, `yanked` state when available (#791) --- yazi-config/preset/keymap.toml | 1 + yazi-config/src/validation.rs | 4 +- yazi-core/src/clipboard.rs | 23 ++- yazi-core/src/completion/commands/trigger.rs | 4 +- yazi-core/src/input/commands/show.rs | 4 +- yazi-core/src/manager/commands/bulk_rename.rs | 130 +++++++++++++++ yazi-core/src/manager/commands/create.rs | 4 +- yazi-core/src/manager/commands/mod.rs | 1 + yazi-core/src/manager/commands/paste.rs | 8 +- yazi-core/src/manager/commands/remove.rs | 7 +- yazi-core/src/manager/commands/rename.rs | 157 ++++-------------- .../src/manager/commands/update_files.rs | 47 +++--- yazi-core/src/manager/watcher.rs | 15 +- yazi-core/src/manager/yanked.rs | 23 ++- yazi-core/src/tab/commands/filter.rs | 4 +- yazi-core/src/tab/commands/jump.rs | 4 +- yazi-core/src/tab/selected.rs | 31 +++- yazi-core/src/tasks/commands/cancel.rs | 2 +- yazi-core/src/tasks/commands/inspect.rs | 14 +- yazi-core/src/tasks/progress.rs | 10 +- yazi-core/src/tasks/tasks.rs | 22 +-- yazi-fm/src/components/progress.rs | 4 +- yazi-fm/src/signals.rs | 4 +- yazi-fm/src/which/layout.rs | 2 +- yazi-scheduler/src/blocker.rs | 6 - yazi-scheduler/src/file/file.rs | 14 +- yazi-scheduler/src/lib.rs | 10 +- yazi-scheduler/src/{running.rs => ongoing.rs} | 4 +- yazi-scheduler/src/process/process.rs | 4 +- yazi-scheduler/src/scheduler.rs | 79 +++++---- yazi-scheduler/src/semaphore.rs | 11 ++ yazi-shared/src/fs/fns.rs | 21 ++- yazi-shared/src/fs/path.rs | 5 +- 33 files changed, 370 insertions(+), 309 deletions(-) create mode 100644 yazi-core/src/manager/commands/bulk_rename.rs delete mode 100644 yazi-scheduler/src/blocker.rs rename yazi-scheduler/src/{running.rs => ongoing.rs} (98%) create mode 100644 yazi-scheduler/src/semaphore.rs diff --git a/yazi-config/preset/keymap.toml b/yazi-config/preset/keymap.toml index bce3ed3e..a803ae7b 100644 --- a/yazi-config/preset/keymap.toml +++ b/yazi-config/preset/keymap.toml @@ -66,6 +66,7 @@ keymap = [ { on = [ "y" ], run = "yank", desc = "Copy the selected files" }, { on = [ "Y" ], run = "unyank", desc = "Cancel the yank status of files" }, { on = [ "x" ], run = "yank --cut", desc = "Cut the selected files" }, + { on = [ "X" ], run = "unyank", desc = "Cancel the yank status of files" }, { on = [ "p" ], run = "paste", desc = "Paste the files" }, { on = [ "P" ], run = "paste --force", desc = "Paste the files (overwrite if the destination exists)" }, { on = [ "-" ], run = "link", desc = "Symlink the absolute path of files" }, diff --git a/yazi-config/src/validation.rs b/yazi-config/src/validation.rs index 34a098f3..a0346eb9 100644 --- a/yazi-config/src/validation.rs +++ b/yazi-config/src/validation.rs @@ -3,9 +3,7 @@ use std::{borrow::Cow, process}; use validator::{ValidationErrors, ValidationErrorsKind}; pub fn check_validation(res: Result<(), ValidationErrors>) { - let Err(errors) = res else { - return; - }; + let Err(errors) = res else { return }; for (field, kind) in errors.into_errors() { match kind { diff --git a/yazi-core/src/clipboard.rs b/yazi-core/src/clipboard.rs index f7e75844..fb3a9bcd 100644 --- a/yazi-core/src/clipboard.rs +++ b/yazi-core/src/clipboard.rs @@ -1,12 +1,13 @@ -use std::{cell::RefCell, ffi::OsString}; +use std::ffi::OsString; +use parking_lot::Mutex; use yazi_shared::RoCell; pub static CLIPBOARD: RoCell = RoCell::new(); #[derive(Default)] pub struct Clipboard { - content: RefCell, + content: Mutex, } impl Clipboard { @@ -18,11 +19,11 @@ impl Clipboard { use yazi_shared::in_ssh_connection; if in_ssh_connection() { - return self.content.borrow().clone(); + return self.content.lock().clone(); } let all = [ - ("pbpaste", &[] as &[&str]), + ("pbpaste", &[][..]), ("wl-paste", &[]), ("xclip", &["-o", "-selection", "clipboard"]), ("xsel", &["-ob"]), @@ -36,7 +37,7 @@ impl Clipboard { return OsString::from_vec(output.stdout); } } - self.content.borrow().clone() + self.content.lock().clone() } #[cfg(windows)] @@ -48,7 +49,7 @@ impl Clipboard { return s.into(); } - self.content.borrow().clone() + self.content.lock().clone() } #[cfg(unix)] @@ -59,13 +60,13 @@ impl Clipboard { use tokio::{io::AsyncWriteExt, process::Command}; use yazi_shared::in_ssh_connection; - *self.content.borrow_mut() = s.as_ref().to_owned(); + *self.content.lock() = s.as_ref().to_owned(); if in_ssh_connection() { execute!(stdout(), osc52::SetClipboard::new(s.as_ref())).ok(); } let all = [ - ("pbcopy", &[] as &[&str]), + ("pbcopy", &[][..]), ("wl-copy", &[]), ("xclip", &["-selection", "clipboard"]), ("xsel", &["-ib"]), @@ -80,9 +81,7 @@ impl Clipboard { .kill_on_drop(true) .spawn(); - let Ok(mut child) = cmd else { - continue; - }; + let Ok(mut child) = cmd else { continue }; let mut stdin = child.stdin.take().unwrap(); if stdin.write_all(s.as_ref().as_encoded_bytes()).await.is_err() { @@ -101,7 +100,7 @@ impl Clipboard { use clipboard_win::{formats, set_clipboard}; let s = s.as_ref().to_owned(); - *self.content.borrow_mut() = s.clone(); + *self.content.lock() = s.clone(); tokio::task::spawn_blocking(move || set_clipboard(formats::Unicode, s.to_string_lossy())) .await diff --git a/yazi-core/src/completion/commands/trigger.rs b/yazi-core/src/completion/commands/trigger.rs index 84fea5dd..4fdf9901 100644 --- a/yazi-core/src/completion/commands/trigger.rs +++ b/yazi-core/src/completion/commands/trigger.rs @@ -40,9 +40,7 @@ impl Completion { let mut dir = fs::read_dir(&parent).await?; let mut cache = vec![]; while let Ok(Some(f)) = dir.next_entry().await { - let Ok(meta) = f.metadata().await else { - continue; - }; + let Ok(meta) = f.metadata().await else { continue }; cache.push(format!( "{}{}", diff --git a/yazi-core/src/input/commands/show.rs b/yazi-core/src/input/commands/show.rs index 7abfcafc..0e09f77f 100644 --- a/yazi-core/src/input/commands/show.rs +++ b/yazi-core/src/input/commands/show.rs @@ -5,9 +5,7 @@ use crate::input::Input; impl Input { pub fn show(&mut self, opt: impl TryInto) { - let Ok(opt) = opt.try_into() else { - return; - }; + let Ok(opt) = opt.try_into() else { return }; self.close(false); self.visible = true; diff --git a/yazi-core/src/manager/commands/bulk_rename.rs b/yazi-core/src/manager/commands/bulk_rename.rs new file mode 100644 index 00000000..8dca4fd6 --- /dev/null +++ b/yazi-core/src/manager/commands/bulk_rename.rs @@ -0,0 +1,130 @@ +use std::{collections::HashMap, ffi::{OsStr, OsString}, io::{stdout, BufWriter, Write}, path::PathBuf}; + +use anyhow::{anyhow, Result}; +use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}}; +use yazi_config::{OPEN, PREVIEW}; +use yazi_plugin::external::{self, ShellOpt}; +use yazi_proxy::AppProxy; +use yazi_scheduler::{HIDER, WATCHER}; +use yazi_shared::{fs::{accessible, max_common_root, File, FilesOp, Url}, term::Term, Defer}; + +use crate::manager::Manager; + +impl Manager { + pub(super) fn bulk_rename(&self) { + let Some(opener) = OPEN.block_opener("bulk.txt", "text/plain") else { + return AppProxy::notify_warn("Bulk rename", "No text opener found"); + }; + + let cwd = self.cwd().clone(); + let old: Vec<_> = self.selected_or_hovered(); + + let root = max_common_root(&old); + let old: Vec<_> = old.into_iter().map(|p| p.strip_prefix(&root).unwrap().to_owned()).collect(); + + tokio::spawn(async move { + let tmp = PREVIEW.tmpfile("bulk"); + let s = old.iter().map(|o| o.as_os_str()).collect::>().join(OsStr::new("\n")); + OpenOptions::new() + .write(true) + .create_new(true) + .open(&tmp) + .await? + .write_all(s.as_encoded_bytes()) + .await?; + + let _permit = HIDER.acquire().await.unwrap(); + let _defer1 = Defer::new(AppProxy::resume); + let _defer2 = Defer::new(|| tokio::spawn(fs::remove_file(tmp.clone()))); + AppProxy::stop().await; + + let mut child = external::shell(ShellOpt { + cmd: (*opener.run).into(), + args: vec![OsString::new(), tmp.to_owned().into()], + piped: false, + orphan: false, + })?; + child.wait().await?; + + let new: Vec<_> = fs::read_to_string(&tmp).await?.lines().map(PathBuf::from).collect(); + Self::bulk_rename_do(cwd, root, old, new).await + }); + } + + async fn bulk_rename_do( + cwd: Url, + root: PathBuf, + old: Vec, + new: Vec, + ) -> Result<()> { + Term::clear(&mut stdout())?; + if old.len() != new.len() { + println!("Number of old and new differ, press ENTER to exit"); + stdin().read_exact(&mut [0]).await?; + return Ok(()); + } + + let todo: Vec<_> = old.into_iter().zip(new).filter(|(o, n)| o != n).collect(); + if todo.is_empty() { + return Ok(()); + } + + { + let mut stdout = BufWriter::new(stdout().lock()); + for (o, n) in &todo { + writeln!(stdout, "{} -> {}", o.display(), n.display())?; + } + write!(stdout, "Continue to rename? (y/N): ")?; + stdout.flush()?; + } + + let mut buf = [0; 10]; + _ = stdin().read(&mut buf).await?; + if buf[0] != b'y' && buf[0] != b'Y' { + return Ok(()); + } + + let _permit = WATCHER.acquire().await.unwrap(); + let (mut failed, mut succeeded) = (Vec::new(), HashMap::with_capacity(todo.len())); + for (o, n) in todo { + let (old, new) = (root.join(&o), root.join(&n)); + + if accessible(&new).await { + failed.push((o, n, anyhow!("Destination already exists"))); + } else if let Err(e) = fs::rename(&old, &new).await { + failed.push((o, n, e.into())); + } else if let Ok(f) = File::from(new.into()).await { + succeeded.insert(Url::from(old), f); + } else { + failed.push((o, n, anyhow!("Failed to retrieve file info"))); + } + } + + if !succeeded.is_empty() { + FilesOp::Upserting(cwd, succeeded).emit(); + } + drop(_permit); + + if !failed.is_empty() { + Self::output_failed(failed).await?; + } + Ok(()) + } + + async fn output_failed(failed: Vec<(PathBuf, PathBuf, anyhow::Error)>) -> Result<()> { + Term::clear(&mut stdout())?; + + { + let mut stdout = BufWriter::new(stdout().lock()); + writeln!(stdout, "Failed to rename:")?; + for (o, n, e) in failed { + writeln!(stdout, "{} -> {}: {e}", o.display(), n.display())?; + } + writeln!(stdout, "\nPress ENTER to exit")?; + stdout.flush()?; + } + + stdin().read_exact(&mut [0]).await?; + Ok(()) + } +} diff --git a/yazi-core/src/manager/commands/create.rs b/yazi-core/src/manager/commands/create.rs index 8779a2af..6aa886d9 100644 --- a/yazi-core/src/manager/commands/create.rs +++ b/yazi-core/src/manager/commands/create.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use tokio::fs; use yazi_config::popup::InputCfg; use yazi_proxy::{InputProxy, ManagerProxy}; -use yazi_shared::{event::Cmd, fs::{File, FilesOp, Url}}; +use yazi_shared::{event::Cmd, fs::{accessible, File, FilesOp, Url}}; use crate::manager::Manager; @@ -26,7 +26,7 @@ impl Manager { }; let path = cwd.join(&name); - if !opt.force && fs::symlink_metadata(&path).await.is_ok() { + if !opt.force && accessible(&path).await { match InputProxy::show(InputCfg::overwrite()).recv().await { Some(Ok(c)) if c == "y" || c == "Y" => (), _ => return Ok(()), diff --git a/yazi-core/src/manager/commands/mod.rs b/yazi-core/src/manager/commands/mod.rs index cc4e4cef..155a22a5 100644 --- a/yazi-core/src/manager/commands/mod.rs +++ b/yazi-core/src/manager/commands/mod.rs @@ -1,3 +1,4 @@ +mod bulk_rename; mod close; mod create; mod hover; diff --git a/yazi-core/src/manager/commands/paste.rs b/yazi-core/src/manager/commands/paste.rs index e1612e37..f551f023 100644 --- a/yazi-core/src/manager/commands/paste.rs +++ b/yazi-core/src/manager/commands/paste.rs @@ -16,13 +16,15 @@ impl From for Opt { impl Manager { pub fn paste(&mut self, opt: impl Into, tasks: &Tasks) { let opt = opt.into() as Opt; + let (src, dest) = (self.yanked.iter().collect::>(), self.cwd()); - let dest = self.cwd(); if self.yanked.cut { - tasks.file_cut(&self.yanked, dest, opt.force); + tasks.file_cut(&src, dest, opt.force); + + self.tabs.iter_mut().for_each(|t| _ = t.selected.remove_many(&src, false)); self.unyank(()); } else { - tasks.file_copy(&self.yanked, dest, opt.force, opt.follow); + tasks.file_copy(&src, dest, opt.force, opt.follow); } } } diff --git a/yazi-core/src/manager/commands/remove.rs b/yazi-core/src/manager/commands/remove.rs index 5ccfc112..6ff0d18f 100644 --- a/yazi-core/src/manager/commands/remove.rs +++ b/yazi-core/src/manager/commands/remove.rs @@ -52,8 +52,13 @@ impl Manager { pub fn remove_do(&mut self, opt: impl Into, tasks: &Tasks) { let opt = opt.into() as Opt; + + self.tabs.iter_mut().for_each(|t| { + t.selected.remove_many(&opt.targets, false); + }); + for u in &opt.targets { - self.active_mut().selected.remove(u); + self.yanked.remove(u); } tasks.file_remove(opt.targets, opt.permanently); diff --git a/yazi-core/src/manager/commands/rename.rs b/yazi-core/src/manager/commands/rename.rs index 3cb990e4..385b4b2f 100644 --- a/yazi-core/src/manager/commands/rename.rs +++ b/yazi-core/src/manager/commands/rename.rs @@ -1,12 +1,11 @@ -use std::{collections::HashMap, ffi::{OsStr, OsString}, io::{stdout, BufWriter, Write}, path::PathBuf}; +use std::collections::HashMap; -use anyhow::{anyhow, bail, Result}; -use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}}; -use yazi_config::{popup::InputCfg, OPEN, PREVIEW}; -use yazi_plugin::external::{self, ShellOpt}; -use yazi_proxy::{AppProxy, InputProxy, ManagerProxy}; -use yazi_scheduler::BLOCKER; -use yazi_shared::{event::Cmd, fs::{max_common_root, File, FilesOp, Url}, term::Term, Defer}; +use anyhow::Result; +use tokio::fs; +use yazi_config::popup::InputCfg; +use yazi_proxy::{InputProxy, ManagerProxy}; +use yazi_scheduler::WATCHER; +use yazi_shared::{event::Cmd, fs::{accessible, File, FilesOp, Url}}; use crate::manager::Manager; @@ -27,32 +26,6 @@ impl From for Opt { } impl Manager { - fn empty_url_part(url: &Url, by: &str) -> String { - if by == "all" { - return String::new(); - } - - let ext = url.extension(); - match by { - "stem" => ext.map_or_else(String::new, |s| format!(".{}", s.to_string_lossy().into_owned())), - "ext" if ext.is_some() => format!("{}.", url.file_stem().unwrap().to_string_lossy()), - "dot_ext" if ext.is_some() => url.file_stem().unwrap().to_string_lossy().into_owned(), - _ => url.file_name().map_or_else(String::new, |s| s.to_string_lossy().into_owned()), - } - } - - async fn rename_and_hover(old: Url, new: Url) -> Result<()> { - fs::rename(&old, &new).await?; - if old.parent() != new.parent() { - return Ok(()); - } - - let file = File::from(new.clone()).await?; - FilesOp::Deleting(file.parent().unwrap(), vec![new.clone()]).emit(); - FilesOp::Upserting(file.parent().unwrap(), HashMap::from_iter([(old, file)])).emit(); - Ok(ManagerProxy::hover(Some(new))) - } - pub fn rename(&mut self, opt: impl Into) { if !self.active_mut().try_escape_visual() { return; @@ -84,117 +57,45 @@ impl Manager { }; let new = hovered.parent().unwrap().join(name); - if opt.force || fs::symlink_metadata(&new).await.is_err() { - Self::rename_and_hover(hovered, Url::from(new)).await.ok(); + if opt.force || !accessible(&new).await { + Self::rename_do(hovered, Url::from(new)).await.ok(); return; } let mut result = InputProxy::show(InputCfg::overwrite()); if let Some(Ok(choice)) = result.recv().await { if choice == "y" || choice == "Y" { - Self::rename_and_hover(hovered, Url::from(new)).await.ok(); + Self::rename_do(hovered, Url::from(new)).await.ok(); } }; }); } - fn bulk_rename(&self) { - let old: Vec<_> = self.selected_or_hovered(); + async fn rename_do(old: Url, new: Url) -> Result<()> { + let _permit = WATCHER.acquire().await.unwrap(); - let root = max_common_root(&old); - let old: Vec<_> = old.into_iter().map(|p| p.strip_prefix(&root).unwrap().to_owned()).collect(); + fs::rename(&old, &new).await?; + if old.parent() != new.parent() { + return Ok(()); + } - let tmp = PREVIEW.tmpfile("bulk"); - tokio::spawn(async move { - let Some(opener) = OPEN.block_opener("bulk.txt", "text/plain") else { - bail!("No opener for bulk rename"); - }; - - { - let s = old.iter().map(|o| o.as_os_str()).collect::>().join(OsStr::new("\n")); - OpenOptions::new() - .write(true) - .create_new(true) - .open(&tmp) - .await? - .write_all(s.as_encoded_bytes()) - .await?; - } - - let _guard = BLOCKER.acquire().await.unwrap(); - let _defer = Defer::new(|| { - AppProxy::resume(); - tokio::spawn(fs::remove_file(tmp.clone())) - }); - AppProxy::stop().await; - - let mut child = external::shell(ShellOpt { - cmd: (*opener.run).into(), - args: vec![OsString::new(), tmp.to_owned().into()], - piped: false, - orphan: false, - })?; - child.wait().await?; - - let new: Vec<_> = fs::read_to_string(&tmp).await?.lines().map(PathBuf::from).collect(); - Self::bulk_rename_do(root, old, new).await - }); + let file = File::from(new.clone()).await?; + FilesOp::Deleting(file.parent().unwrap(), vec![new.clone()]).emit(); + FilesOp::Upserting(file.parent().unwrap(), HashMap::from_iter([(old, file)])).emit(); + Ok(ManagerProxy::hover(Some(new))) } - async fn bulk_rename_do(root: PathBuf, old: Vec, new: Vec) -> Result<()> { - Term::clear(&mut stdout())?; - if old.len() != new.len() { - println!("Number of old and new differ, press ENTER to exit"); - stdin().read_exact(&mut [0]).await?; - return Ok(()); + fn empty_url_part(url: &Url, by: &str) -> String { + if by == "all" { + return String::new(); } - let todo: Vec<_> = old.into_iter().zip(new).filter(|(o, n)| o != n).collect(); - if todo.is_empty() { - return Ok(()); + let ext = url.extension(); + match by { + "stem" => ext.map_or_else(String::new, |s| format!(".{}", s.to_string_lossy().into_owned())), + "ext" if ext.is_some() => format!("{}.", url.file_stem().unwrap().to_string_lossy()), + "dot_ext" if ext.is_some() => url.file_stem().unwrap().to_string_lossy().into_owned(), + _ => url.file_name().map_or_else(String::new, |s| s.to_string_lossy().into_owned()), } - - { - let mut stdout = BufWriter::new(stdout().lock()); - for (o, n) in &todo { - writeln!(stdout, "{} -> {}", o.display(), n.display())?; - } - write!(stdout, "Continue to rename? (y/N): ")?; - stdout.flush()?; - } - - let mut buf = [0; 10]; - _ = stdin().read(&mut buf).await?; - if buf[0] != b'y' && buf[0] != b'Y' { - return Ok(()); - } - - let mut failed = vec![]; - for (o, n) in todo { - if fs::symlink_metadata(&n).await.is_ok() { - failed.push((o, n, anyhow!("Destination already exists"))); - continue; - } - if let Err(e) = fs::rename(root.join(&o), root.join(&n)).await { - failed.push((o, n, e.into())); - } - } - if failed.is_empty() { - return Ok(()); - } - - Term::clear(&mut stdout())?; - { - let mut stdout = BufWriter::new(stdout().lock()); - writeln!(stdout, "Failed to rename:")?; - for (o, n, e) in failed { - writeln!(stdout, "{} -> {}: {e}", o.display(), n.display())?; - } - writeln!(stdout, "\nPress ENTER to exit")?; - stdout.flush()?; - } - - stdin().read_exact(&mut [0]).await?; - Ok(()) } } diff --git a/yazi-core/src/manager/commands/update_files.rs b/yazi-core/src/manager/commands/update_files.rs index 7da81b19..097751ca 100644 --- a/yazi-core/src/manager/commands/update_files.rs +++ b/yazi-core/src/manager/commands/update_files.rs @@ -16,8 +16,33 @@ impl TryFrom for Opt { } impl Manager { + pub fn update_files(&mut self, opt: impl TryInto, tasks: &Tasks) { + let Ok(opt) = opt.try_into() else { + return; + }; + + let mut ops = vec![opt.op]; + for u in self.watcher.linked.read().from_dir(ops[0].url()) { + ops.push(ops[0].chroot(u)); + } + + for op in ops { + let idx = self.tabs.idx; + self.yanked.apply_op(&op); + + for (_, tab) in self.tabs.iter_mut().enumerate().filter(|(i, _)| *i != idx) { + Self::update_tab(tab, Cow::Borrowed(&op), tasks); + } + Self::update_tab(self.active_mut(), Cow::Owned(op), tasks); + } + + self.active_mut().apply_files_attrs(); + } + fn update_tab(tab: &mut Tab, op: Cow, tasks: &Tasks) { let url = op.url(); + tab.selected.apply_op(&op); + if tab.current.cwd == *url { Self::update_current(tab, op, tasks); } else if matches!(&tab.parent, Some(p) if p.cwd == *url) { @@ -92,26 +117,4 @@ impl Manager { tab.leave(()); } } - - pub fn update_files(&mut self, opt: impl TryInto, tasks: &Tasks) { - let Ok(opt) = opt.try_into() else { - return; - }; - - let mut ops = vec![opt.op]; - for u in self.watcher.linked.read().from_dir(ops[0].url()) { - ops.push(ops[0].chroot(u)); - } - - for op in ops { - let idx = self.tabs.idx; - for (_, tab) in self.tabs.iter_mut().enumerate().filter(|(i, _)| *i != idx) { - Self::update_tab(tab, Cow::Borrowed(&op), tasks); - } - - Self::update_tab(self.active_mut(), Cow::Owned(op), tasks); - } - - self.active_mut().apply_files_attrs(); - } } diff --git a/yazi-core/src/manager/watcher.rs b/yazi-core/src/manager/watcher.rs index e8b0ecbf..f4e57def 100644 --- a/yazi-core/src/manager/watcher.rs +++ b/yazi-core/src/manager/watcher.rs @@ -7,6 +7,7 @@ use tokio::{fs, pin, sync::mpsc::{self, UnboundedReceiver}}; use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; use tracing::error; use yazi_plugin::isolate; +use yazi_scheduler::WATCHER; use yazi_shared::fs::{File, FilesOp, Url}; use super::Linked; @@ -25,9 +26,7 @@ impl Watcher { { let tx = tx.clone(); move |res: Result| { - let Ok(event) = res else { - return; - }; + let Ok(event) = res else { return }; match event.kind { EventKind::Create(_) => {} @@ -143,17 +142,15 @@ impl Watcher { async fn on_changed(rx: UnboundedReceiver) { // TODO: revert this once a new notification is implemented - let rx = UnboundedReceiverStream::new(rx).chunks_timeout(100, Duration::from_millis(20)); + let rx = UnboundedReceiverStream::new(rx).chunks_timeout(1000, Duration::from_millis(50)); pin!(rx); while let Some(urls) = rx.next().await { - let urls: HashSet<_> = urls.into_iter().collect(); + let _permit = WATCHER.acquire().await.unwrap(); let mut reload = Vec::with_capacity(urls.len()); - for u in urls { - let Some(parent) = u.parent_url() else { - continue; - }; + for u in urls.into_iter().collect::>() { + let Some(parent) = u.parent_url() else { continue }; let Ok(file) = File::from(u.clone()).await else { FilesOp::Deleting(parent, vec![u]).emit(); diff --git a/yazi-core/src/manager/yanked.rs b/yazi-core/src/manager/yanked.rs index 438fed3d..8a33ec19 100644 --- a/yazi-core/src/manager/yanked.rs +++ b/yazi-core/src/manager/yanked.rs @@ -1,6 +1,6 @@ -use std::{collections::HashSet, ops::Deref}; +use std::{collections::HashSet, ops::{Deref, DerefMut}}; -use yazi_shared::fs::Url; +use yazi_shared::fs::{FilesOp, Url}; #[derive(Default)] pub struct Yanked { @@ -13,3 +13,22 @@ impl Deref for Yanked { fn deref(&self) -> &Self::Target { &self.urls } } + +impl DerefMut for Yanked { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.urls } +} + +impl Yanked { + pub fn apply_op(&mut self, op: &FilesOp) { + let (removal, addition) = match op { + FilesOp::Deleting(_, urls) => (urls.iter().collect(), vec![]), + FilesOp::Updating(_, urls) | FilesOp::Upserting(_, urls) => { + urls.iter().filter(|(u, _)| self.contains(u)).map(|(u, f)| (u, f.url())).unzip() + } + _ => (vec![], vec![]), + }; + + self.urls.retain(|u| !removal.contains(&u)); + self.urls.extend(addition); + } +} diff --git a/yazi-core/src/tab/commands/filter.rs b/yazi-core/src/tab/commands/filter.rs index 718a0828..56000b00 100644 --- a/yazi-core/src/tab/commands/filter.rs +++ b/yazi-core/src/tab/commands/filter.rs @@ -36,9 +36,7 @@ impl Tab { while let Some(result) = rx.next().await { let done = result.is_ok(); - let (Ok(s) | Err(InputError::Typed(s))) = result else { - continue; - }; + let (Ok(s) | Err(InputError::Typed(s))) = result else { continue }; emit!(Call( Cmd::args("filter_do", vec![s]) diff --git a/yazi-core/src/tab/commands/jump.rs b/yazi-core/src/tab/commands/jump.rs index e4df1ff2..470c0317 100644 --- a/yazi-core/src/tab/commands/jump.rs +++ b/yazi-core/src/tab/commands/jump.rs @@ -1,6 +1,6 @@ use yazi_plugin::external::{self, FzfOpt, ZoxideOpt}; use yazi_proxy::{AppProxy, TabProxy}; -use yazi_scheduler::BLOCKER; +use yazi_scheduler::HIDER; use yazi_shared::{event::Cmd, fs::ends_with_slash, Defer}; use crate::tab::Tab; @@ -37,7 +37,7 @@ impl Tab { let cwd = self.current.cwd.clone(); tokio::spawn(async move { - let _guard = BLOCKER.acquire().await.unwrap(); + let _permit = HIDER.acquire().await.unwrap(); let _defer = Defer::new(AppProxy::resume); AppProxy::stop().await; diff --git a/yazi-core/src/tab/selected.rs b/yazi-core/src/tab/selected.rs index 18e56f66..8e0f2117 100644 --- a/yazi-core/src/tab/selected.rs +++ b/yazi-core/src/tab/selected.rs @@ -1,6 +1,6 @@ use std::{collections::{HashMap, HashSet}, ops::Deref}; -use yazi_shared::fs::Url; +use yazi_shared::fs::{FilesOp, Url}; #[derive(Default)] pub struct Selected { @@ -63,27 +63,27 @@ impl Selected { #[inline] pub fn remove(&mut self, url: &Url) -> bool { self.remove_same(&[url]) == 1 } - pub fn remove_many(&mut self, urls: &[&Url], same: bool) -> usize { + pub fn remove_many(&mut self, urls: &[impl AsRef], same: bool) -> usize { if same { return self.remove_same(urls); } let mut grouped: HashMap<_, Vec<_>> = Default::default(); - for &u in urls { - if let Some(p) = u.parent_url() { + for u in urls { + if let Some(p) = u.as_ref().parent_url() { grouped.entry(p).or_default().push(u); } } grouped.into_values().map(|v| self.remove_same(&v)).sum() } - fn remove_same(&mut self, urls: &[&Url]) -> usize { - let count = urls.iter().map(|&u| self.inner.remove(u)).filter(|&b| b).count(); + fn remove_same(&mut self, urls: &[impl AsRef]) -> usize { + let count = urls.iter().map(|u| self.inner.remove(u.as_ref())).filter(|&b| b).count(); if count == 0 { return 0; } - let mut parent = urls[0].parent_url(); + let mut parent = urls[0].as_ref().parent_url(); while let Some(u) = parent { let n = self.parents.get_mut(&u).unwrap(); @@ -101,6 +101,23 @@ impl Selected { self.inner.clear(); self.parents.clear(); } + + pub fn apply_op(&mut self, op: &FilesOp) { + let (removal, addition) = match op { + FilesOp::Deleting(_, urls) => (urls.iter().collect(), vec![]), + FilesOp::Updating(_, urls) | FilesOp::Upserting(_, urls) => { + urls.iter().filter(|(u, _)| self.contains(u)).map(|(u, f)| (u, &f.url)).unzip() + } + _ => (vec![], vec![]), + }; + + if !removal.is_empty() { + self.remove_many(&removal, !op.url().is_search()); + } + if !addition.is_empty() { + self.add_many(&addition, !op.url().is_search()); + } + } } #[cfg(test)] diff --git a/yazi-core/src/tasks/commands/cancel.rs b/yazi-core/src/tasks/commands/cancel.rs index 815b58cd..e8c9aca6 100644 --- a/yazi-core/src/tasks/commands/cancel.rs +++ b/yazi-core/src/tasks/commands/cancel.rs @@ -4,7 +4,7 @@ use crate::tasks::Tasks; impl Tasks { pub fn cancel(&mut self, _: Cmd) { - let id = self.scheduler.running.lock().get_id(self.cursor); + let id = self.scheduler.ongoing.lock().get_id(self.cursor); if id.map(|id| self.scheduler.cancel(id)) != Some(true) { return; } diff --git a/yazi-core/src/tasks/commands/inspect.rs b/yazi-core/src/tasks/commands/inspect.rs index d75bfcb2..e1e0ff5a 100644 --- a/yazi-core/src/tasks/commands/inspect.rs +++ b/yazi-core/src/tasks/commands/inspect.rs @@ -3,25 +3,25 @@ use std::io::{stdout, Write}; use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time}; use yazi_proxy::AppProxy; -use yazi_scheduler::BLOCKER; +use yazi_scheduler::HIDER; use yazi_shared::{event::Cmd, term::Term, Defer}; use crate::tasks::Tasks; impl Tasks { pub fn inspect(&self, _: Cmd) { - let Some(id) = self.scheduler.running.lock().get_id(self.cursor) else { + let Some(id) = self.scheduler.ongoing.lock().get_id(self.cursor) else { return; }; let scheduler = self.scheduler.clone(); tokio::spawn(async move { - let _guard = BLOCKER.acquire().await.unwrap(); + let _permit = HIDER.acquire().await.unwrap(); let (tx, mut rx) = mpsc::unbounded_channel(); let buffered = { - let mut running = scheduler.running.lock(); - let Some(task) = running.get_mut(id) else { return }; + let mut ongoing = scheduler.ongoing.lock(); + let Some(task) = ongoing.get_mut(id) else { return }; task.logger = Some(tx); task.logs.clone() @@ -47,7 +47,7 @@ impl Tasks { stdout.write_all(b"\r\n").ok(); } _ = time::sleep(time::Duration::from_millis(500)) => { - if scheduler.running.lock().get(id).is_none() { + if scheduler.ongoing.lock().get(id).is_none() { stdout().write_all(b"Task finished, press `q` to quit\r\n").ok(); break; } @@ -61,7 +61,7 @@ impl Tasks { } } - if let Some(task) = scheduler.running.lock().get_mut(id) { + if let Some(task) = scheduler.ongoing.lock().get_mut(id) { task.logger = None; } while answer != b'q' { diff --git a/yazi-core/src/tasks/progress.rs b/yazi-core/src/tasks/progress.rs index f14eabd6..e62899a2 100644 --- a/yazi-core/src/tasks/progress.rs +++ b/yazi-core/src/tasks/progress.rs @@ -1,5 +1,5 @@ use serde::Serialize; -use yazi_scheduler::Running; +use yazi_scheduler::Ongoing; #[derive(Clone, Copy, Default, Eq, PartialEq, Serialize)] pub struct TasksProgress { @@ -11,14 +11,14 @@ pub struct TasksProgress { pub processed: u64, } -impl From<&Running> for TasksProgress { - fn from(running: &Running) -> Self { +impl From<&Ongoing> for TasksProgress { + fn from(ongoing: &Ongoing) -> Self { let mut progress = Self::default(); - if running.is_empty() { + if ongoing.is_empty() { return progress; } - for task in running.values() { + for task in ongoing.values() { progress.total += task.total; progress.succ += task.succ; progress.fail += task.fail; diff --git a/yazi-core/src/tasks/tasks.rs b/yazi-core/src/tasks/tasks.rs index 5d8d20ae..f2684c1b 100644 --- a/yazi-core/src/tasks/tasks.rs +++ b/yazi-core/src/tasks/tasks.rs @@ -29,13 +29,13 @@ impl Tasks { summaries: Default::default(), }; - let running = tasks.scheduler.running.clone(); + let ongoing = tasks.scheduler.ongoing.clone(); tokio::spawn(async move { let mut last = TasksProgress::default(); loop { sleep(Duration::from_millis(500)).await; - let new = TasksProgress::from(&*running.lock()); + let new = TasksProgress::from(&*ongoing.lock()); if last != new { last = new; emit!(Call(Cmd::new("update_progress").with_data(new), Layer::App)); @@ -52,8 +52,8 @@ impl Tasks { } pub fn paginate(&self) -> Vec { - let running = self.scheduler.running.lock(); - running.values().take(Self::limit()).map(Into::into).collect() + let ongoing = self.scheduler.ongoing.lock(); + ongoing.values().take(Self::limit()).map(Into::into).collect() } pub fn file_open(&self, hovered: &Url, targets: &[(Url, String)]) { @@ -78,10 +78,10 @@ impl Tasks { } } - pub fn file_cut(&self, src: &HashSet, dest: &Url, force: bool) { - for u in src { + pub fn file_cut(&self, src: &[&Url], dest: &Url, force: bool) { + for &u in src { let to = dest.join(u.file_name().unwrap()); - if force && u == &to { + if force && *u == to { debug!("file_cut: same file, skipping {:?}", to); } else { self.scheduler.file_cut(u.clone(), to, force); @@ -89,10 +89,10 @@ impl Tasks { } } - pub fn file_copy(&self, src: &HashSet, dest: &Url, force: bool, follow: bool) { - for u in src { + pub fn file_copy(&self, src: &[&Url], dest: &Url, force: bool, follow: bool) { + for &u in src { let to = dest.join(u.file_name().unwrap()); - if force && u == &to { + if force && *u == to { debug!("file_copy: same file, skipping {:?}", to); } else { self.scheduler.file_copy(u.clone(), to, force, follow); @@ -219,5 +219,5 @@ impl Tasks { impl Tasks { #[inline] - pub fn len(&self) -> usize { self.scheduler.running.lock().len() } + pub fn len(&self) -> usize { self.scheduler.ongoing.lock().len() } } diff --git a/yazi-fm/src/components/progress.rs b/yazi-fm/src/components/progress.rs index ce8d6d50..39de994e 100644 --- a/yazi-fm/src/components/progress.rs +++ b/yazi-fm/src/components/progress.rs @@ -14,9 +14,7 @@ impl Progress { let mut f = || { let comp: Table = LUA.globals().raw_get("Progress")?; for widget in comp.call_method::<_, Vec>("partial_render", ())? { - let Some(w) = cast_to_renderable(widget) else { - continue; - }; + let Some(w) = cast_to_renderable(widget) else { continue }; let area = w.area(); w.render(buf); diff --git a/yazi-fm/src/signals.rs b/yazi-fm/src/signals.rs index a1327e01..214e6ee5 100644 --- a/yazi-fm/src/signals.rs +++ b/yazi-fm/src/signals.rs @@ -44,7 +44,7 @@ impl Signals { fn spawn_system_task(&self) -> Result> { use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTERM}; use yazi_proxy::AppProxy; - use yazi_scheduler::BLOCKER; + use yazi_scheduler::HIDER; let mut signals = signal_hook_tokio::Signals::new([ // Terminating signals @@ -56,7 +56,7 @@ impl Signals { let tx = self.tx.clone(); Ok(tokio::spawn(async move { while let Some(signal) = signals.next().await { - if BLOCKER.try_acquire().is_err() { + if HIDER.try_acquire().is_err() { continue; } diff --git a/yazi-fm/src/which/layout.rs b/yazi-fm/src/which/layout.rs index 26aed46b..a284e1e8 100644 --- a/yazi-fm/src/which/layout.rs +++ b/yazi-fm/src/which/layout.rs @@ -39,7 +39,7 @@ impl Widget for Which<'_> { let chunks = { use Constraint::*; layout::Layout::horizontal(match cols { - 1 => &[Ratio(1, 1)] as &[Constraint], + 1 => &[Ratio(1, 1)][..], 2 => &[Ratio(1, 2), Ratio(1, 2)], _ => &[Ratio(1, 3), Ratio(1, 3), Ratio(1, 3)], }) diff --git a/yazi-scheduler/src/blocker.rs b/yazi-scheduler/src/blocker.rs deleted file mode 100644 index ecd429b5..00000000 --- a/yazi-scheduler/src/blocker.rs +++ /dev/null @@ -1,6 +0,0 @@ -use tokio::sync::Semaphore; -use yazi_shared::RoCell; - -pub static BLOCKER: RoCell = RoCell::new(); - -pub(super) fn init_blocker() { BLOCKER.init(Semaphore::new(1)) } diff --git a/yazi-scheduler/src/file/file.rs b/yazi-scheduler/src/file/file.rs index dec74cf8..4a9fb53c 100644 --- a/yazi-scheduler/src/file/file.rs +++ b/yazi-scheduler/src/file/file.rs @@ -5,7 +5,7 @@ use futures::{future::BoxFuture, FutureExt}; use tokio::{fs, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc}; use tracing::warn; use yazi_config::TASKS; -use yazi_shared::fs::{calculate_size, copy_with_progress, path_relative_to, Url}; +use yazi_shared::fs::{accessible, calculate_size, copy_with_progress, path_relative_to, Url}; use super::{FileOp, FileOpDelete, FileOpLink, FileOpPaste, FileOpTrash}; use crate::{TaskOp, TaskProg, LOW, NORMAL}; @@ -107,7 +107,7 @@ impl File { } FileOp::Delete(task) => { if let Err(e) = fs::remove_file(&task.target).await { - if e.kind() != NotFound && fs::symlink_metadata(&task.target).await.is_ok() { + if e.kind() != NotFound && accessible(&task.target).await { self.fail(task.id, format!("Delete task failed: {:?}, {e}", task))?; Err(e)? } @@ -225,16 +225,10 @@ impl File { let mut dirs = VecDeque::from([task.target]); while let Some(target) = dirs.pop_front() { - let mut it = match fs::read_dir(target).await { - Ok(it) => it, - Err(_) => continue, - }; + let Ok(mut it) = fs::read_dir(target).await else { continue }; while let Ok(Some(entry)) = it.next_entry().await { - let meta = match entry.metadata().await { - Ok(m) => m, - Err(_) => continue, - }; + let Ok(meta) = entry.metadata().await else { continue }; if meta.is_dir() { dirs.push_front(Url::from(entry.path())); diff --git a/yazi-scheduler/src/lib.rs b/yazi-scheduler/src/lib.rs index 9c807796..1d74cb35 100644 --- a/yazi-scheduler/src/lib.rs +++ b/yazi-scheduler/src/lib.rs @@ -1,23 +1,23 @@ #![allow(clippy::option_map_unit_fn, clippy::unit_arg)] -mod blocker; mod file; +mod ongoing; mod op; mod plugin; mod preload; mod process; -mod running; mod scheduler; +mod semaphore; mod task; -pub use blocker::*; +pub use ongoing::*; pub use op::*; -pub use running::*; pub use scheduler::*; +pub use semaphore::*; pub use task::*; const LOW: u8 = yazi_config::Priority::Low as u8; const NORMAL: u8 = yazi_config::Priority::Normal as u8; const HIGH: u8 = yazi_config::Priority::High as u8; -pub fn init() { init_blocker(); } +pub fn init() { init_semaphore(); } diff --git a/yazi-scheduler/src/running.rs b/yazi-scheduler/src/ongoing.rs similarity index 98% rename from yazi-scheduler/src/running.rs rename to yazi-scheduler/src/ongoing.rs index 86298d3c..21dbbd3d 100644 --- a/yazi-scheduler/src/running.rs +++ b/yazi-scheduler/src/ongoing.rs @@ -7,14 +7,14 @@ use super::{Task, TaskStage}; use crate::TaskKind; #[derive(Default)] -pub struct Running { +pub struct Ongoing { incr: usize, pub(super) hooks: HashMap BoxFuture<'static, ()>) + Send + Sync>>, pub(super) all: HashMap, } -impl Running { +impl Ongoing { pub fn add(&mut self, kind: TaskKind, name: String) -> usize { self.incr += 1; self.all.insert(self.incr, Task::new(self.incr, kind, name)); diff --git a/yazi-scheduler/src/process/process.rs b/yazi-scheduler/src/process/process.rs index 3d5bf253..02e26aa6 100644 --- a/yazi-scheduler/src/process/process.rs +++ b/yazi-scheduler/src/process/process.rs @@ -5,7 +5,7 @@ use yazi_proxy::AppProxy; use yazi_shared::Defer; use super::ProcessOpOpen; -use crate::{TaskProg, BLOCKER}; +use crate::{TaskProg, HIDER}; pub struct Process { prog: mpsc::UnboundedSender, @@ -63,7 +63,7 @@ impl Process { } async fn open_block(&self, task: ProcessOpOpen) -> Result<()> { - let _guard = BLOCKER.acquire().await.unwrap(); + let _permit = HIDER.acquire().await.unwrap(); let _defer = Defer::new(AppProxy::resume); AppProxy::stop().await; diff --git a/yazi-scheduler/src/scheduler.rs b/yazi-scheduler/src/scheduler.rs index fcca3ddb..2fdb3764 100644 --- a/yazi-scheduler/src/scheduler.rs +++ b/yazi-scheduler/src/scheduler.rs @@ -7,7 +7,7 @@ use yazi_config::{open::Opener, plugin::PluginRule, TASKS}; use yazi_plugin::ValueSendable; use yazi_shared::{fs::{unique_path, Url}, Throttle}; -use super::{Running, TaskProg, TaskStage}; +use super::{Ongoing, TaskProg, TaskStage}; use crate::{file::{File, FileOpDelete, FileOpLink, FileOpPaste, FileOpTrash}, plugin::{Plugin, PluginOpEntry}, preload::{Preload, PreloadOpRule, PreloadOpSize}, process::{Process, ProcessOpOpen}, TaskKind, TaskOp, HIGH, LOW, NORMAL}; pub struct Scheduler { @@ -18,8 +18,7 @@ pub struct Scheduler { micro: async_priority_channel::Sender, u8>, prog: mpsc::UnboundedSender, - // FIXME - pub running: Arc>, + pub ongoing: Arc>, } impl Scheduler { @@ -36,7 +35,7 @@ impl Scheduler { micro: micro_tx, prog: prog_tx, - running: Default::default(), + ongoing: Default::default(), }; for _ in 0..TASKS.micro_workers { @@ -69,7 +68,7 @@ impl Scheduler { let preload = self.preload.clone(); let prog = self.prog.clone(); - let running = self.running.clone(); + let ongoing = self.ongoing.clone(); tokio::spawn(async move { loop { @@ -79,7 +78,7 @@ impl Scheduler { } Ok((op, _)) = macro_.recv() => { let id = op.id(); - if !running.lock().exists(id) { + if !ongoing.lock().exists(id) { continue; } @@ -100,36 +99,36 @@ impl Scheduler { fn progress(&self, mut rx: UnboundedReceiver) { let micro = self.micro.clone(); - let running = self.running.clone(); + let ongoing = self.ongoing.clone(); tokio::spawn(async move { while let Some(op) = rx.recv().await { match op { TaskProg::New(id, size) => { - if let Some(task) = running.lock().get_mut(id) { + if let Some(task) = ongoing.lock().get_mut(id) { task.total += 1; task.found += size; } } TaskProg::Adv(id, succ, processed) => { - let mut running = running.lock(); - if let Some(task) = running.get_mut(id) { + let mut ongoing = ongoing.lock(); + if let Some(task) = ongoing.get_mut(id) { task.succ += succ; task.processed += processed; } if succ > 0 { - if let Some(fut) = running.try_remove(id, TaskStage::Pending) { + if let Some(fut) = ongoing.try_remove(id, TaskStage::Pending) { micro.try_send(fut, NORMAL).ok(); } } } TaskProg::Succ(id) => { - if let Some(fut) = running.lock().try_remove(id, TaskStage::Dispatched) { + if let Some(fut) = ongoing.lock().try_remove(id, TaskStage::Dispatched) { micro.try_send(fut, NORMAL).ok(); } } TaskProg::Fail(id, reason) => { - if let Some(task) = running.lock().get_mut(id) { + if let Some(task) = ongoing.lock().get_mut(id) { task.fail += 1; task.logs.push_str(&reason); task.logs.push('\n'); @@ -140,7 +139,7 @@ impl Scheduler { } } TaskProg::Log(id, line) => { - if let Some(task) = running.lock().get_mut(id) { + if let Some(task) = ongoing.lock().get_mut(id) { task.logs.push_str(&line); task.logs.push('\n'); @@ -155,29 +154,29 @@ impl Scheduler { } pub fn cancel(&self, id: usize) -> bool { - let mut running = self.running.lock(); - let b = running.all.remove(&id).is_some(); + let mut ongoing = self.ongoing.lock(); + let b = ongoing.all.remove(&id).is_some(); - if let Some(hook) = running.hooks.remove(&id) { + if let Some(hook) = ongoing.hooks.remove(&id) { self.micro.try_send(hook(true), HIGH).ok(); } b } pub fn file_cut(&self, from: Url, mut to: Url, force: bool) { - let mut running = self.running.lock(); - let id = running.add(TaskKind::User, format!("Cut {:?} to {:?}", from, to)); + let mut ongoing = self.ongoing.lock(); + let id = ongoing.add(TaskKind::User, format!("Cut {:?} to {:?}", from, to)); - running.hooks.insert(id, { + ongoing.hooks.insert(id, { let from = from.clone(); - let running = self.running.clone(); + let ongoing = self.ongoing.clone(); Box::new(move |canceled: bool| { async move { if !canceled { File::remove_empty_dirs(&from).await; } - running.lock().try_remove(id, TaskStage::Hooked); + ongoing.lock().try_remove(id, TaskStage::Hooked); } .boxed() }) @@ -198,7 +197,7 @@ impl Scheduler { pub fn file_copy(&self, from: Url, mut to: Url, force: bool, follow: bool) { let name = format!("Copy {:?} to {:?}", from, to); - let id = self.running.lock().add(TaskKind::User, name); + let id = self.ongoing.lock().add(TaskKind::User, name); let file = self.file.clone(); _ = self.micro.try_send( @@ -215,7 +214,7 @@ impl Scheduler { pub fn file_link(&self, from: Url, mut to: Url, relative: bool, force: bool) { let name = format!("Link {from:?} to {to:?}"); - let id = self.running.lock().add(TaskKind::User, name); + let id = self.ongoing.lock().add(TaskKind::User, name); let file = self.file.clone(); _ = self.micro.try_send( @@ -234,19 +233,19 @@ impl Scheduler { } pub fn file_delete(&self, target: Url) { - let mut running = self.running.lock(); - let id = running.add(TaskKind::User, format!("Delete {:?}", target)); + let mut ongoing = self.ongoing.lock(); + let id = ongoing.add(TaskKind::User, format!("Delete {:?}", target)); - running.hooks.insert(id, { + ongoing.hooks.insert(id, { let target = target.clone(); - let running = self.running.clone(); + let ongoing = self.ongoing.clone(); Box::new(move |canceled: bool| { async move { if !canceled { fs::remove_dir_all(target).await.ok(); } - running.lock().try_remove(id, TaskStage::Hooked); + ongoing.lock().try_remove(id, TaskStage::Hooked); } .boxed() }) @@ -264,7 +263,7 @@ impl Scheduler { pub fn file_trash(&self, target: Url) { let name = format!("Trash {:?}", target); - let id = self.running.lock().add(TaskKind::User, name); + let id = self.ongoing.lock().add(TaskKind::User, name); let file = self.file.clone(); _ = self.micro.try_send( @@ -277,7 +276,7 @@ impl Scheduler { } pub fn plugin_micro(&self, name: String, args: Vec) { - let id = self.running.lock().add(TaskKind::User, format!("Run micro plugin `{name}`")); + let id = self.ongoing.lock().add(TaskKind::User, format!("Run micro plugin `{name}`")); let plugin = self.plugin.clone(); _ = self.micro.try_send( @@ -290,13 +289,13 @@ impl Scheduler { } pub fn plugin_macro(&self, name: String, args: Vec) { - let id = self.running.lock().add(TaskKind::User, format!("Run macro plugin `{name}`")); + let id = self.ongoing.lock().add(TaskKind::User, format!("Run macro plugin `{name}`")); self.plugin.macro_(PluginOpEntry { id, name, args }).ok(); } pub fn preload_paged(&self, rule: &PluginRule, targets: Vec<&yazi_shared::fs::File>) { - let id = self.running.lock().add( + let id = self.ongoing.lock().add( TaskKind::Preload, format!("Run preloader `{}` with {} target(s)", rule.cmd.name, targets.len()), ); @@ -315,10 +314,10 @@ impl Scheduler { pub fn preload_size(&self, targets: Vec<&Url>) { let throttle = Arc::new(Throttle::new(targets.len(), Duration::from_millis(300))); - let mut running = self.running.lock(); + let mut ongoing = self.ongoing.lock(); for target in targets { - let id = running.add(TaskKind::Preload, format!("Calculate the size of {:?}", target)); + let id = ongoing.add(TaskKind::Preload, format!("Calculate the size of {:?}", target)); let target = target.clone(); let throttle = throttle.clone(); @@ -340,18 +339,18 @@ impl Scheduler { if args.is_empty() { s } else { format!("{s} with `{args}`") } }; - let mut running = self.running.lock(); - let id = running.add(TaskKind::User, name); + let mut ongoing = self.ongoing.lock(); + let id = ongoing.add(TaskKind::User, name); let (cancel_tx, mut cancel_rx) = oneshot::channel(); - running.hooks.insert(id, { - let running = self.running.clone(); + ongoing.hooks.insert(id, { + let ongoing = self.ongoing.clone(); Box::new(move |canceled: bool| { async move { if canceled { cancel_rx.close(); } - running.lock().try_remove(id, TaskStage::Hooked); + ongoing.lock().try_remove(id, TaskStage::Hooked); } .boxed() }) diff --git a/yazi-scheduler/src/semaphore.rs b/yazi-scheduler/src/semaphore.rs new file mode 100644 index 00000000..c98d689c --- /dev/null +++ b/yazi-scheduler/src/semaphore.rs @@ -0,0 +1,11 @@ +use tokio::sync::Semaphore; +use yazi_shared::RoCell; + +pub static HIDER: RoCell = RoCell::new(); + +pub static WATCHER: RoCell = RoCell::new(); + +pub(super) fn init_semaphore() { + HIDER.init(Semaphore::new(1)); + WATCHER.init(Semaphore::new(1)); +} diff --git a/yazi-shared/src/fs/fns.rs b/yazi-shared/src/fs/fns.rs index 94cb6e3d..9811a5bd 100644 --- a/yazi-shared/src/fs/fns.rs +++ b/yazi-shared/src/fs/fns.rs @@ -3,27 +3,26 @@ use std::{collections::VecDeque, path::{Path, PathBuf}}; use anyhow::Result; use tokio::{fs, io, select, sync::{mpsc, oneshot}, time}; +pub async fn accessible(path: &Path) -> bool { + match fs::symlink_metadata(path).await { + Ok(_) => true, + Err(e) => e.kind() != io::ErrorKind::NotFound, + } +} + pub async fn calculate_size(path: &Path) -> u64 { let mut total = 0; let mut stack = VecDeque::from([path.to_path_buf()]); while let Some(path) = stack.pop_front() { - let Ok(meta) = fs::symlink_metadata(&path).await else { - continue; - }; - + let Ok(meta) = fs::symlink_metadata(&path).await else { continue }; if !meta.is_dir() { total += meta.len(); continue; } - let Ok(mut it) = fs::read_dir(path).await else { - continue; - }; - + let Ok(mut it) = fs::read_dir(path).await else { continue }; while let Ok(Some(entry)) = it.next_entry().await { - let Ok(meta) = entry.metadata().await else { - continue; - }; + let Ok(meta) = entry.metadata().await else { continue }; if meta.is_dir() { stack.push_back(entry.path()); diff --git a/yazi-shared/src/fs/path.rs b/yazi-shared/src/fs/path.rs index da57046f..155a120b 100644 --- a/yazi-shared/src/fs/path.rs +++ b/yazi-shared/src/fs/path.rs @@ -1,7 +1,6 @@ use std::{borrow::Cow, env, ffi::OsString, path::{Component, Path, PathBuf, MAIN_SEPARATOR}}; -use tokio::fs; - +use super::accessible; use crate::fs::Url; #[inline] @@ -68,7 +67,7 @@ pub async fn unique_path(mut p: Url) -> Url { .unwrap_or_default(); let mut i = 0; - while fs::symlink_metadata(&p).await.is_ok() { + while accessible(&p).await { i += 1; let mut name = OsString::with_capacity(stem.len() + ext.len() + 5); From 3f80bc56a6cf331558ffbe8d83de51206ae87b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BF=8A=E5=B0=8F=E4=B9=85?= Date: Fri, 8 Mar 2024 17:54:29 +0800 Subject: [PATCH 7/9] feat: truncate path for deeply nested directories (#787) --- yazi-plugin/preset/components/header.lua | 33 +++++++++++-------- yazi-plugin/src/utils/text.rs | 41 +++++++++++++++--------- 2 files changed, 45 insertions(+), 29 deletions(-) diff --git a/yazi-plugin/preset/components/header.lua b/yazi-plugin/preset/components/header.lua index 8f02c035..72cffa03 100644 --- a/yazi-plugin/preset/components/header.lua +++ b/yazi-plugin/preset/components/header.lua @@ -2,16 +2,12 @@ Header = { area = ui.Rect.default, } -function Header:cwd() +function Header:cwd(max) local cwd = cx.active.current.cwd + local readable = ya.readable_path(tostring(cwd)) - local span - if not cwd.is_search then - span = ui.Span(ya.readable_path(tostring(cwd))) - else - span = ui.Span(string.format("%s (search: %s)", ya.readable_path(tostring(cwd)), cwd:frag())) - end - return span:style(THEME.manager.cwd) + local text = cwd.is_search and string.format("%s (search: %s)", readable, cwd:frag()) or readable + return ui.Span(ya.truncate(text, { max = max, rtl = true })):style(THEME.manager.cwd) end function Header:count() @@ -49,7 +45,7 @@ function Header:tabs() for i = 1, tabs do local text = i if THEME.manager.tab_width > 2 then - text = ya.truncate(text .. " " .. cx.tabs[i]:name(), THEME.manager.tab_width) + text = ya.truncate(text .. " " .. cx.tabs[i]:name(), { max = THEME.manager.tab_width }) end if i == cx.tabs.idx then spans[#spans + 1] = ui.Span(" " .. text .. " "):style(THEME.manager.tab_active) @@ -60,7 +56,18 @@ function Header:tabs() return ui.Line(spans) end +-- TODO: remove this function after v0.2.5 release function Header:layout(area) + if not ya.deprecated_header_layout then + ya.deprecated_header_layout = true + ya.notify { + title = "Deprecated API", + content = "`Header:layout()` is deprecated, please apply the latest `Header:render()` in your `init.lua`", + timeout = 5, + level = "warn", + } + end + self.area = area return ui.Layout() @@ -70,12 +77,12 @@ function Header:layout(area) end function Header:render(area) - local chunks = self:layout(area) + self.area = area - local left = ui.Line { self:cwd() } local right = ui.Line { self:count(), self:tabs() } + local left = ui.Line { self:cwd(math.max(0, area.w - right:width())) } return { - ui.Paragraph(chunks[1], { left }), - ui.Paragraph(chunks[2], { right }):align(ui.Paragraph.RIGHT), + ui.Paragraph(area, { left }), + ui.Paragraph(area, { right }):align(ui.Paragraph.RIGHT), } end diff --git a/yazi-plugin/src/utils/text.rs b/yazi-plugin/src/utils/text.rs index 401084a7..92044180 100644 --- a/yazi-plugin/src/utils/text.rs +++ b/yazi-plugin/src/utils/text.rs @@ -1,7 +1,7 @@ use std::ops::ControlFlow; use mlua::{Lua, Table}; -use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; +use unicode_width::UnicodeWidthChar; use super::Utils; @@ -20,26 +20,35 @@ impl Utils { ya.raw_set( "truncate", - lua.create_function(|_, (text, max): (mlua::String, usize)| { - let mut width = 0; - let flow = - text.to_string_lossy().chars().try_fold(String::with_capacity(max), |mut s, c| { - width += c.width().unwrap_or(0); - if s.width() < max { - s.push(c); - ControlFlow::Continue(s) - } else { - ControlFlow::Break(s) - } - }); + lua.create_function(|_, (text, t): (mlua::String, Table)| { + let (max, text) = (t.raw_get("max")?, text.to_string_lossy()); - Ok(match flow { - ControlFlow::Break(s) => s, - ControlFlow::Continue(s) => s, + Ok(if t.raw_get("rtl").unwrap_or(false) { + Self::truncate(text.chars().rev(), max).into_iter().rev().collect() + } else { + Self::truncate(text.chars(), max).into_iter().collect::() }) })?, )?; Ok(()) } + + fn truncate(mut chars: impl Iterator, max: usize) -> Vec { + let mut width = 0; + let flow = chars.try_fold(Vec::with_capacity(max), |mut v, c| { + width += c.width().unwrap_or(0); + if width < max { + v.push(c); + ControlFlow::Continue(v) + } else { + ControlFlow::Break(v) + } + }); + + match flow { + ControlFlow::Break(v) => v, + ControlFlow::Continue(v) => v, + } + } } From 9396d8760c826a6640727710f21ee80fb56367c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Sat, 9 Mar 2024 03:07:20 +0800 Subject: [PATCH 8/9] feat: `ya.hide()` plugin API (#792) --- Cargo.lock | 38 +++++++------- cspell.json | 2 +- yazi-adaptor/Cargo.toml | 6 +-- yazi-boot/Cargo.toml | 6 +-- yazi-config/Cargo.toml | 4 +- yazi-config/preset/yazi.toml | 2 + yazi-config/src/headsup/headsup.rs | 34 +++++++++++++ yazi-config/src/headsup/mod.rs | 3 ++ yazi-config/src/keymap/control.rs | 6 ++- yazi-config/src/lib.rs | 21 ++++++++ yazi-config/src/open/opener.rs | 7 +++ yazi-config/src/plugin/rule.rs | 7 ++- yazi-core/Cargo.toml | 16 +++--- yazi-core/src/manager/commands/bulk_rename.rs | 3 +- yazi-core/src/manager/commands/rename.rs | 3 +- yazi-core/src/manager/watcher.rs | 2 +- yazi-core/src/tab/commands/jump.rs | 3 +- yazi-core/src/tasks/commands/inspect.rs | 3 +- yazi-fm/Cargo.toml | 18 +++---- yazi-fm/src/main.rs | 4 +- yazi-fm/src/signals.rs | 3 +- yazi-plugin/Cargo.toml | 12 ++--- yazi-plugin/src/bindings/mod.rs | 2 + yazi-plugin/src/bindings/permit.rs | 42 ++++++++++++++++ yazi-plugin/src/lib.rs | 6 ++- yazi-plugin/src/lua.rs | 50 +++++++++++++++++++ yazi-plugin/src/plugin.rs | 50 ------------------- yazi-plugin/src/utils/app.rs | 27 ++++++++++ yazi-plugin/src/utils/mod.rs | 1 + yazi-plugin/src/utils/plugin.rs | 13 ++--- yazi-plugin/src/utils/utils.rs | 1 + yazi-proxy/Cargo.toml | 6 +-- yazi-proxy/src/lib.rs | 4 ++ .../src/semaphore.rs | 0 yazi-scheduler/Cargo.toml | 12 ++--- yazi-scheduler/src/lib.rs | 4 -- yazi-scheduler/src/process/process.rs | 4 +- yazi-shared/Cargo.toml | 2 +- 38 files changed, 287 insertions(+), 140 deletions(-) create mode 100644 yazi-config/src/headsup/headsup.rs create mode 100644 yazi-config/src/headsup/mod.rs create mode 100644 yazi-plugin/src/bindings/permit.rs create mode 100644 yazi-plugin/src/lua.rs delete mode 100644 yazi-plugin/src/plugin.rs create mode 100644 yazi-plugin/src/utils/app.rs rename {yazi-scheduler => yazi-proxy}/src/semaphore.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 28030fd8..d9d53dd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -230,9 +230,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.3" +version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "bytemuck" @@ -269,9 +269,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.89" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" [[package]] name = "cfg-if" @@ -281,9 +281,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.34" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" dependencies = [ "android-tzdata", "iana-time-zone", @@ -293,9 +293,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" dependencies = [ "clap_builder", "clap_derive", @@ -303,9 +303,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", @@ -2689,7 +2689,7 @@ dependencies = [ [[package]] name = "yazi-adaptor" -version = "0.2.3" +version = "0.2.4" dependencies = [ "anyhow", "arc-swap", @@ -2708,7 +2708,7 @@ dependencies = [ [[package]] name = "yazi-boot" -version = "0.2.3" +version = "0.2.4" dependencies = [ "clap", "clap_complete", @@ -2722,7 +2722,7 @@ dependencies = [ [[package]] name = "yazi-config" -version = "0.2.3" +version = "0.2.4" dependencies = [ "anyhow", "arc-swap", @@ -2741,7 +2741,7 @@ dependencies = [ [[package]] name = "yazi-core" -version = "0.2.3" +version = "0.2.4" dependencies = [ "anyhow", "base64 0.22.0", @@ -2772,7 +2772,7 @@ dependencies = [ [[package]] name = "yazi-fm" -version = "0.2.3" +version = "0.2.4" dependencies = [ "anyhow", "better-panic", @@ -2803,7 +2803,7 @@ dependencies = [ [[package]] name = "yazi-plugin" -version = "0.2.3" +version = "0.2.4" dependencies = [ "ansi-to-tui", "anyhow", @@ -2840,7 +2840,7 @@ checksum = "f4b6c8e12e39ac0f79fa96f36e5b88e0da8d230691abd729eec709b43c74f632" [[package]] name = "yazi-proxy" -version = "0.2.3" +version = "0.2.4" dependencies = [ "anyhow", "mlua", @@ -2851,7 +2851,7 @@ dependencies = [ [[package]] name = "yazi-scheduler" -version = "0.2.3" +version = "0.2.4" dependencies = [ "anyhow", "async-priority-channel", @@ -2873,7 +2873,7 @@ dependencies = [ [[package]] name = "yazi-shared" -version = "0.2.3" +version = "0.2.4" dependencies = [ "anyhow", "bitflags 2.4.2", diff --git a/cspell.json b/cspell.json index b5bb36bf..559b3847 100644 --- a/cspell.json +++ b/cspell.json @@ -1 +1 @@ -{"language":"en","flagWords":[],"version":"0.2","words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff"]} \ No newline at end of file +{"version":"0.2","flagWords":[],"language":"en","words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup"]} \ No newline at end of file diff --git a/yazi-adaptor/Cargo.toml b/yazi-adaptor/Cargo.toml index 911df9ee..ee6ab4f3 100644 --- a/yazi-adaptor/Cargo.toml +++ b/yazi-adaptor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-adaptor" -version = "0.2.3" +version = "0.2.4" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -9,8 +9,8 @@ homepage = "https://yazi-rs.github.io" repository = "https://github.com/sxyazi/yazi" [dependencies] -yazi-config = { path = "../yazi-config", version = "0.2.3" } -yazi-shared = { path = "../yazi-shared", version = "0.2.3" } +yazi-config = { path = "../yazi-config", version = "0.2.4" } +yazi-shared = { path = "../yazi-shared", version = "0.2.4" } # External dependencies anyhow = "^1" diff --git a/yazi-boot/Cargo.toml b/yazi-boot/Cargo.toml index 71854f4b..42c5b4c4 100644 --- a/yazi-boot/Cargo.toml +++ b/yazi-boot/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-boot" -version = "0.2.3" +version = "0.2.4" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -9,8 +9,8 @@ homepage = "https://yazi-rs.github.io" repository = "https://github.com/sxyazi/yazi" [dependencies] -yazi-config = { path = "../yazi-config", version = "0.2.3" } -yazi-shared = { path = "../yazi-shared", version = "0.2.3" } +yazi-config = { path = "../yazi-config", version = "0.2.4" } +yazi-shared = { path = "../yazi-shared", version = "0.2.4" } # External dependencies clap = { version = "^4", features = [ "derive" ] } diff --git a/yazi-config/Cargo.toml b/yazi-config/Cargo.toml index c4b47b04..5582bf93 100644 --- a/yazi-config/Cargo.toml +++ b/yazi-config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-config" -version = "0.2.3" +version = "0.2.4" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -9,7 +9,7 @@ homepage = "https://yazi-rs.github.io" repository = "https://github.com/sxyazi/yazi" [dependencies] -yazi-shared = { path = "../yazi-shared", version = "0.2.3" } +yazi-shared = { path = "../yazi-shared", version = "0.2.4" } # External dependencies anyhow = "^1" diff --git a/yazi-config/preset/yazi.toml b/yazi-config/preset/yazi.toml index f47daae3..b860d825 100644 --- a/yazi-config/preset/yazi.toml +++ b/yazi-config/preset/yazi.toml @@ -192,3 +192,5 @@ sort_reverse = false [log] enabled = false + +[headsup] diff --git a/yazi-config/src/headsup/headsup.rs b/yazi-config/src/headsup/headsup.rs new file mode 100644 index 00000000..062725fa --- /dev/null +++ b/yazi-config/src/headsup/headsup.rs @@ -0,0 +1,34 @@ +use serde::{Deserialize, Deserializer}; + +use crate::MERGED_YAZI; + +#[derive(Debug)] +pub struct Headsup { + // TODO: remove this once Yazi 0.3 is released -- + pub disable_exec_warn: bool, +} + +impl Default for Headsup { + fn default() -> Self { toml::from_str(&MERGED_YAZI).unwrap() } +} + +impl<'de> Deserialize<'de> for Headsup { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct Outer { + headsup: Shadow, + } + #[derive(Deserialize)] + struct Shadow { + #[serde(default)] + disable_exec_warn: bool, + } + + let outer = Outer::deserialize(deserializer)?; + + Ok(Self { disable_exec_warn: outer.headsup.disable_exec_warn }) + } +} diff --git a/yazi-config/src/headsup/mod.rs b/yazi-config/src/headsup/mod.rs new file mode 100644 index 00000000..82e85f0a --- /dev/null +++ b/yazi-config/src/headsup/mod.rs @@ -0,0 +1,3 @@ +mod headsup; + +pub use headsup::*; diff --git a/yazi-config/src/keymap/control.rs b/yazi-config/src/keymap/control.rs index 7b357b09..a236e5a0 100644 --- a/yazi-config/src/keymap/control.rs +++ b/yazi-config/src/keymap/control.rs @@ -1,9 +1,10 @@ -use std::{borrow::Cow, collections::VecDeque}; +use std::{borrow::Cow, collections::VecDeque, sync::atomic::Ordering}; use serde::{Deserialize, Deserializer}; use yazi_shared::event::Cmd; use super::Key; +use crate::DEPRECATED_EXEC; #[derive(Debug, Default)] pub struct Control { @@ -61,6 +62,9 @@ impl<'de> Deserialize<'de> for Control { #[derive(Deserialize)] struct VecCmd(#[serde(deserialize_with = "super::run_deserialize")] Vec); + if shadow.exec.is_some() { + DEPRECATED_EXEC.store(true, Ordering::Relaxed); + } let Some(run) = shadow.run.or(shadow.exec) else { return Err(serde::de::Error::custom("missing field `run` within `[keymap]`")); }; diff --git a/yazi-config/src/lib.rs b/yazi-config/src/lib.rs index 117de23b..385ca369 100644 --- a/yazi-config/src/lib.rs +++ b/yazi-config/src/lib.rs @@ -2,6 +2,7 @@ use yazi_shared::{RoCell, Xdg}; +pub mod headsup; pub mod keymap; mod layout; mod log; @@ -23,12 +24,17 @@ pub(crate) use pattern::*; pub(crate) use preset::*; pub use priority::*; +// TODO: remove this once Yazi 0.3 is released -- +pub static DEPRECATED_EXEC: std::sync::atomic::AtomicBool = + std::sync::atomic::AtomicBool::new(false); + static MERGED_YAZI: RoCell = RoCell::new(); static MERGED_KEYMAP: RoCell = RoCell::new(); static MERGED_THEME: RoCell = RoCell::new(); pub static LAYOUT: RoCell> = RoCell::new(); +pub static HEADSUP: RoCell = RoCell::new(); pub static KEYMAP: RoCell = RoCell::new(); pub static LOG: RoCell = RoCell::new(); pub static MANAGER: RoCell = RoCell::new(); @@ -49,6 +55,7 @@ pub fn init() { LAYOUT.with(Default::default); + HEADSUP.with(Default::default); KEYMAP.with(Default::default); LOG.with(Default::default); MANAGER.with(Default::default); @@ -60,4 +67,18 @@ pub fn init() { INPUT.with(Default::default); SELECT.with(Default::default); WHICH.with(Default::default); + + // TODO: remove this once Yazi 0.3 is released -- + if !HEADSUP.disable_exec_warn && DEPRECATED_EXEC.load(std::sync::atomic::Ordering::Relaxed) { + println!( + r#" +WARNING: `exec` will be deprecated in the next major version v0.3 and replaced by `run`. + +Please replace all `exec = ...` with `run = ...`, in your `yazi.toml` and `keymap.toml`. + +--- +Add `disable_exec_warn = true` to your `yazi.toml` under `[headsup]` to suppress this warning. +"# + ); + } } diff --git a/yazi-config/src/open/opener.rs b/yazi-config/src/open/opener.rs index cb51db58..40409c7b 100644 --- a/yazi-config/src/open/opener.rs +++ b/yazi-config/src/open/opener.rs @@ -1,5 +1,9 @@ +use std::sync::atomic::Ordering; + use serde::{Deserialize, Deserializer}; +use crate::DEPRECATED_EXEC; + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Opener { pub run: String, @@ -47,6 +51,9 @@ impl<'de> Deserialize<'de> for Opener { let shadow = Shadow::deserialize(deserializer)?; // TODO: remove this once Yazi 0.3 is released -- + if shadow.exec.is_some() { + DEPRECATED_EXEC.store(true, Ordering::Relaxed); + } let run = shadow.run.or(shadow.exec).unwrap_or_default(); // TODO: -- remove this once Yazi 0.3 is released diff --git a/yazi-config/src/plugin/rule.rs b/yazi-config/src/plugin/rule.rs index e57027ef..b29f3f8e 100644 --- a/yazi-config/src/plugin/rule.rs +++ b/yazi-config/src/plugin/rule.rs @@ -1,7 +1,9 @@ +use std::sync::atomic::Ordering; + use serde::{Deserialize, Deserializer}; use yazi_shared::{event::Cmd, Condition}; -use crate::{Pattern, Priority}; +use crate::{Pattern, Priority, DEPRECATED_EXEC}; pub struct PluginRule { pub id: u8, @@ -50,6 +52,9 @@ impl<'de> Deserialize<'de> for PluginRule { #[derive(Deserialize)] struct WrappedCmd(#[serde(deserialize_with = "super::run_deserialize")] Cmd); + if shadow.exec.is_some() { + DEPRECATED_EXEC.store(true, Ordering::Relaxed); + } let Some(run) = shadow.run.or(shadow.exec) else { return Err(serde::de::Error::custom("missing field `run` within `[plugin]`")); }; diff --git a/yazi-core/Cargo.toml b/yazi-core/Cargo.toml index c96dc59b..94eee563 100644 --- a/yazi-core/Cargo.toml +++ b/yazi-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-core" -version = "0.2.3" +version = "0.2.4" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -9,13 +9,13 @@ homepage = "https://yazi-rs.github.io" repository = "https://github.com/sxyazi/yazi" [dependencies] -yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.3" } -yazi-boot = { path = "../yazi-boot", version = "0.2.3" } -yazi-config = { path = "../yazi-config", version = "0.2.3" } -yazi-plugin = { path = "../yazi-plugin", version = "0.2.3" } -yazi-proxy = { path = "../yazi-proxy", version = "0.2.3" } -yazi-scheduler = { path = "../yazi-scheduler", version = "0.2.3" } -yazi-shared = { path = "../yazi-shared", version = "0.2.3" } +yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.4" } +yazi-boot = { path = "../yazi-boot", version = "0.2.4" } +yazi-config = { path = "../yazi-config", version = "0.2.4" } +yazi-plugin = { path = "../yazi-plugin", version = "0.2.4" } +yazi-proxy = { path = "../yazi-proxy", version = "0.2.4" } +yazi-scheduler = { path = "../yazi-scheduler", version = "0.2.4" } +yazi-shared = { path = "../yazi-shared", version = "0.2.4" } # External dependencies anyhow = "^1" diff --git a/yazi-core/src/manager/commands/bulk_rename.rs b/yazi-core/src/manager/commands/bulk_rename.rs index 8dca4fd6..ca4bf92b 100644 --- a/yazi-core/src/manager/commands/bulk_rename.rs +++ b/yazi-core/src/manager/commands/bulk_rename.rs @@ -4,8 +4,7 @@ use anyhow::{anyhow, Result}; use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}}; use yazi_config::{OPEN, PREVIEW}; use yazi_plugin::external::{self, ShellOpt}; -use yazi_proxy::AppProxy; -use yazi_scheduler::{HIDER, WATCHER}; +use yazi_proxy::{AppProxy, HIDER, WATCHER}; use yazi_shared::{fs::{accessible, max_common_root, File, FilesOp, Url}, term::Term, Defer}; use crate::manager::Manager; diff --git a/yazi-core/src/manager/commands/rename.rs b/yazi-core/src/manager/commands/rename.rs index 385b4b2f..216dc522 100644 --- a/yazi-core/src/manager/commands/rename.rs +++ b/yazi-core/src/manager/commands/rename.rs @@ -3,8 +3,7 @@ use std::collections::HashMap; use anyhow::Result; use tokio::fs; use yazi_config::popup::InputCfg; -use yazi_proxy::{InputProxy, ManagerProxy}; -use yazi_scheduler::WATCHER; +use yazi_proxy::{InputProxy, ManagerProxy, WATCHER}; use yazi_shared::{event::Cmd, fs::{accessible, File, FilesOp, Url}}; use crate::manager::Manager; diff --git a/yazi-core/src/manager/watcher.rs b/yazi-core/src/manager/watcher.rs index f4e57def..e712e038 100644 --- a/yazi-core/src/manager/watcher.rs +++ b/yazi-core/src/manager/watcher.rs @@ -7,7 +7,7 @@ use tokio::{fs, pin, sync::mpsc::{self, UnboundedReceiver}}; use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; use tracing::error; use yazi_plugin::isolate; -use yazi_scheduler::WATCHER; +use yazi_proxy::WATCHER; use yazi_shared::fs::{File, FilesOp, Url}; use super::Linked; diff --git a/yazi-core/src/tab/commands/jump.rs b/yazi-core/src/tab/commands/jump.rs index 470c0317..cd0c73ea 100644 --- a/yazi-core/src/tab/commands/jump.rs +++ b/yazi-core/src/tab/commands/jump.rs @@ -1,6 +1,5 @@ use yazi_plugin::external::{self, FzfOpt, ZoxideOpt}; -use yazi_proxy::{AppProxy, TabProxy}; -use yazi_scheduler::HIDER; +use yazi_proxy::{AppProxy, TabProxy, HIDER}; use yazi_shared::{event::Cmd, fs::ends_with_slash, Defer}; use crate::tab::Tab; diff --git a/yazi-core/src/tasks/commands/inspect.rs b/yazi-core/src/tasks/commands/inspect.rs index e1e0ff5a..3d92c5e7 100644 --- a/yazi-core/src/tasks/commands/inspect.rs +++ b/yazi-core/src/tasks/commands/inspect.rs @@ -2,8 +2,7 @@ use std::io::{stdout, Write}; use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time}; -use yazi_proxy::AppProxy; -use yazi_scheduler::HIDER; +use yazi_proxy::{AppProxy, HIDER}; use yazi_shared::{event::Cmd, term::Term, Defer}; use crate::tasks::Tasks; diff --git a/yazi-fm/Cargo.toml b/yazi-fm/Cargo.toml index e95d31df..83214a82 100644 --- a/yazi-fm/Cargo.toml +++ b/yazi-fm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-fm" -version = "0.2.3" +version = "0.2.4" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -9,14 +9,14 @@ homepage = "https://yazi-rs.github.io" repository = "https://github.com/sxyazi/yazi" [dependencies] -yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.3" } -yazi-boot = { path = "../yazi-boot", version = "0.2.3" } -yazi-config = { path = "../yazi-config", version = "0.2.3" } -yazi-core = { path = "../yazi-core", version = "0.2.3" } -yazi-plugin = { path = "../yazi-plugin", version = "0.2.3" } -yazi-proxy = { path = "../yazi-proxy", version = "0.2.3" } -yazi-scheduler = { path = "../yazi-scheduler", version = "0.2.3" } -yazi-shared = { path = "../yazi-shared", version = "0.2.3" } +yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.4" } +yazi-boot = { path = "../yazi-boot", version = "0.2.4" } +yazi-config = { path = "../yazi-config", version = "0.2.4" } +yazi-core = { path = "../yazi-core", version = "0.2.4" } +yazi-plugin = { path = "../yazi-plugin", version = "0.2.4" } +yazi-proxy = { path = "../yazi-proxy", version = "0.2.4" } +yazi-scheduler = { path = "../yazi-scheduler", version = "0.2.4" } +yazi-shared = { path = "../yazi-shared", version = "0.2.4" } # External dependencies anyhow = "^1" diff --git a/yazi-fm/src/main.rs b/yazi-fm/src/main.rs index 95b72d1e..0b6e2c59 100644 --- a/yazi-fm/src/main.rs +++ b/yazi-fm/src/main.rs @@ -45,10 +45,10 @@ async fn main() -> anyhow::Result<()> { yazi_boot::init(); - yazi_scheduler::init(); - yazi_plugin::init(); + yazi_proxy::init(); + yazi_core::init(); app::App::serve().await diff --git a/yazi-fm/src/signals.rs b/yazi-fm/src/signals.rs index 214e6ee5..35933d63 100644 --- a/yazi-fm/src/signals.rs +++ b/yazi-fm/src/signals.rs @@ -43,8 +43,7 @@ impl Signals { #[cfg(unix)] fn spawn_system_task(&self) -> Result> { use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTERM}; - use yazi_proxy::AppProxy; - use yazi_scheduler::HIDER; + use yazi_proxy::{AppProxy, HIDER}; let mut signals = signal_hook_tokio::Signals::new([ // Terminating signals diff --git a/yazi-plugin/Cargo.toml b/yazi-plugin/Cargo.toml index 3168c6cf..1727184b 100644 --- a/yazi-plugin/Cargo.toml +++ b/yazi-plugin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-plugin" -version = "0.2.3" +version = "0.2.4" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -9,11 +9,11 @@ homepage = "https://yazi-rs.github.io" repository = "https://github.com/sxyazi/yazi" [dependencies] -yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.3" } -yazi-boot = { path = "../yazi-boot", version = "0.2.3" } -yazi-config = { path = "../yazi-config", version = "0.2.3" } -yazi-proxy = { path = "../yazi-proxy", version = "0.2.3" } -yazi-shared = { path = "../yazi-shared", version = "0.2.3" } +yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.4" } +yazi-boot = { path = "../yazi-boot", version = "0.2.4" } +yazi-config = { path = "../yazi-config", version = "0.2.4" } +yazi-proxy = { path = "../yazi-proxy", version = "0.2.4" } +yazi-shared = { path = "../yazi-shared", version = "0.2.4" } # External dependencies ansi-to-tui = "^3" diff --git a/yazi-plugin/src/bindings/mod.rs b/yazi-plugin/src/bindings/mod.rs index 57e87ef7..31f2ac4c 100644 --- a/yazi-plugin/src/bindings/mod.rs +++ b/yazi-plugin/src/bindings/mod.rs @@ -5,6 +5,7 @@ mod cha; mod file; mod icon; mod input; +mod permit; mod position; mod range; mod window; @@ -14,6 +15,7 @@ pub use cha::*; pub use file::*; pub use icon::*; pub use input::*; +pub use permit::*; pub use position::*; pub use range::*; pub use window::*; diff --git a/yazi-plugin/src/bindings/permit.rs b/yazi-plugin/src/bindings/permit.rs new file mode 100644 index 00000000..4dfb0b6a --- /dev/null +++ b/yazi-plugin/src/bindings/permit.rs @@ -0,0 +1,42 @@ +use std::{mem, ops::Deref}; + +use mlua::{prelude::LuaUserDataMethods, UserData}; +use tokio::sync::SemaphorePermit; + +pub type PermitRef<'lua, F> = mlua::UserDataRef<'lua, Permit>; + +pub struct Permit { + inner: Option>, + destruct: Option, +} + +impl Deref for Permit { + type Target = Option>; + + fn deref(&self) -> &Self::Target { &self.inner } +} + +impl Permit { + pub fn new(inner: SemaphorePermit<'static>, f: F) -> Self { + Self { inner: Some(inner), destruct: Some(f) } + } + + fn dropping(&mut self) { + if let Some(f) = self.destruct.take() { + f(); + } + if let Some(p) = self.inner.take() { + mem::drop(p); + } + } +} + +impl Drop for Permit { + fn drop(&mut self) { self.dropping(); } +} + +impl UserData for Permit { + fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_method_mut("drop", |_, me, ()| Ok(me.dropping())); + } +} diff --git a/yazi-plugin/src/lib.rs b/yazi-plugin/src/lib.rs index 0c1ffddf..00f88f2a 100644 --- a/yazi-plugin/src/lib.rs +++ b/yazi-plugin/src/lib.rs @@ -8,8 +8,8 @@ pub mod external; pub mod fs; pub mod isolate; mod loader; +mod lua; mod opt; -mod plugin; pub mod process; pub mod url; pub mod utils; @@ -17,5 +17,7 @@ pub mod utils; pub use cast::*; pub use config::*; pub use loader::*; +pub use lua::*; pub use opt::*; -pub use plugin::*; + +pub fn init() { crate::init_lua(); } diff --git a/yazi-plugin/src/lua.rs b/yazi-plugin/src/lua.rs new file mode 100644 index 00000000..2a9bd1cb --- /dev/null +++ b/yazi-plugin/src/lua.rs @@ -0,0 +1,50 @@ +use anyhow::Result; +use mlua::Lua; +use yazi_boot::BOOT; +use yazi_shared::RoCell; + +pub static LUA: RoCell = RoCell::new(); + +pub(super) fn init_lua() { + let lua = Lua::new(); + stage_1(&lua).expect("failed to initialize Lua"); + stage_2(&lua); + LUA.init(lua); +} + +fn stage_1(lua: &Lua) -> Result<()> { + crate::Loader::init(); + crate::Config::new(lua).install_boot()?.install_manager()?.install_theme()?; + crate::utils::init(); + crate::utils::install(lua)?; + + // Base + lua.load(include_str!("../preset/inspect/inspect.lua")).exec()?; + lua.load(include_str!("../preset/ya.lua")).exec()?; + crate::bindings::Cha::register(lua)?; + crate::bindings::File::register(lua)?; + crate::bindings::Icon::register(lua)?; + crate::elements::pour(lua)?; + crate::url::pour(lua)?; + + // Components + lua.load(include_str!("../preset/components/current.lua")).exec()?; + lua.load(include_str!("../preset/components/file.lua")).exec()?; + lua.load(include_str!("../preset/components/folder.lua")).exec()?; + lua.load(include_str!("../preset/components/header.lua")).exec()?; + lua.load(include_str!("../preset/components/manager.lua")).exec()?; + lua.load(include_str!("../preset/components/parent.lua")).exec()?; + lua.load(include_str!("../preset/components/preview.lua")).exec()?; + lua.load(include_str!("../preset/components/progress.lua")).exec()?; + lua.load(include_str!("../preset/components/status.lua")).exec()?; + + Ok(()) +} + +fn stage_2(lua: &Lua) { + lua.load(include_str!("../preset/setup.lua")).exec().unwrap(); + + if let Ok(b) = std::fs::read(BOOT.config_dir.join("init.lua")) { + lua.load(b).exec().unwrap(); + } +} diff --git a/yazi-plugin/src/plugin.rs b/yazi-plugin/src/plugin.rs deleted file mode 100644 index abc30719..00000000 --- a/yazi-plugin/src/plugin.rs +++ /dev/null @@ -1,50 +0,0 @@ -use anyhow::Result; -use mlua::Lua; -use yazi_boot::BOOT; -use yazi_shared::RoCell; - -pub static LUA: RoCell = RoCell::new(); - -pub fn init() { - fn stage_1(lua: &Lua) -> Result<()> { - crate::Loader::init(); - crate::Config::new(lua).install_boot()?.install_manager()?.install_theme()?; - crate::utils::init(); - crate::utils::install(lua)?; - - // Base - lua.load(include_str!("../preset/inspect/inspect.lua")).exec()?; - lua.load(include_str!("../preset/ya.lua")).exec()?; - crate::bindings::Cha::register(lua)?; - crate::bindings::File::register(lua)?; - crate::bindings::Icon::register(lua)?; - crate::elements::pour(lua)?; - crate::url::pour(lua)?; - - // Components - lua.load(include_str!("../preset/components/current.lua")).exec()?; - lua.load(include_str!("../preset/components/file.lua")).exec()?; - lua.load(include_str!("../preset/components/folder.lua")).exec()?; - lua.load(include_str!("../preset/components/header.lua")).exec()?; - lua.load(include_str!("../preset/components/manager.lua")).exec()?; - lua.load(include_str!("../preset/components/parent.lua")).exec()?; - lua.load(include_str!("../preset/components/preview.lua")).exec()?; - lua.load(include_str!("../preset/components/progress.lua")).exec()?; - lua.load(include_str!("../preset/components/status.lua")).exec()?; - - Ok(()) - } - - fn stage_2(lua: &Lua) { - lua.load(include_str!("../preset/setup.lua")).exec().unwrap(); - - if let Ok(b) = std::fs::read(BOOT.config_dir.join("init.lua")) { - lua.load(b).exec().unwrap(); - } - } - - let lua = Lua::new(); - stage_1(&lua).expect("failed to initialize Lua"); - stage_2(&lua); - LUA.init(lua); -} diff --git a/yazi-plugin/src/utils/app.rs b/yazi-plugin/src/utils/app.rs new file mode 100644 index 00000000..e9f2db29 --- /dev/null +++ b/yazi-plugin/src/utils/app.rs @@ -0,0 +1,27 @@ +use mlua::{AnyUserData, ExternalError, Lua, Table}; +use yazi_proxy::{AppProxy, HIDER}; + +use super::Utils; +use crate::bindings::{Permit, PermitRef}; + +impl Utils { + pub(super) fn app(lua: &Lua, ya: &Table) -> mlua::Result<()> { + ya.raw_set( + "hide", + lua.create_async_function(|lua, ()| async move { + if lua.named_registry_value::>("HIDE_PERMIT").is_ok_and(|h| h.is_some()) { + return Err("Cannot hide while already hidden".into_lua_err()); + } + + let permit = HIDER.acquire().await.unwrap(); + AppProxy::stop().await; + + lua + .set_named_registry_value("HIDE_PERMIT", Permit::new(permit, AppProxy::resume as fn()))?; + lua.named_registry_value::("HIDE_PERMIT") + })?, + )?; + + Ok(()) + } +} diff --git a/yazi-plugin/src/utils/mod.rs b/yazi-plugin/src/utils/mod.rs index f5906355..e75695eb 100644 --- a/yazi-plugin/src/utils/mod.rs +++ b/yazi-plugin/src/utils/mod.rs @@ -1,5 +1,6 @@ #![allow(clippy::module_inception)] +mod app; mod cache; mod call; mod image; diff --git a/yazi-plugin/src/utils/plugin.rs b/yazi-plugin/src/utils/plugin.rs index e65ef7e2..95cd5fa3 100644 --- a/yazi-plugin/src/utils/plugin.rs +++ b/yazi-plugin/src/utils/plugin.rs @@ -12,7 +12,7 @@ impl Utils { lua.create_async_function( |_, (name, calls, args): (String, usize, Variadic)| async move { let args = ValueSendable::try_from_variadic(args)?; - let (tx, rx) = oneshot::channel::(); + let (tx, rx) = oneshot::channel::>(); let data = OptData { cb: Some({ @@ -27,9 +27,9 @@ impl Utils { self_args.push(arg.into_lua(lua)?); } - let value: ValueSendable = - block.call::<_, Value>(Variadic::from_iter(self_args))?.try_into()?; - tx.send(value).map_err(|_| "send failed".into_lua_err()) + let values = + ValueSendable::try_from_variadic(block.call(Variadic::from_iter(self_args))?)?; + tx.send(values).map_err(|_| "send failed".into_lua_err()) }) }), ..Default::default() @@ -40,8 +40,9 @@ impl Utils { Layer::App )); - rx.await - .map_err(|_| format!("Failed to execute sync block in `{name}` plugin").into_lua_err()) + Ok(Variadic::from_iter(rx.await.map_err(|_| { + format!("Failed to execute sync block-{calls} in `{name}` plugin").into_lua_err() + })?)) }, )?, )?; diff --git a/yazi-plugin/src/utils/utils.rs b/yazi-plugin/src/utils/utils.rs index a9be944a..75850015 100644 --- a/yazi-plugin/src/utils/utils.rs +++ b/yazi-plugin/src/utils/utils.rs @@ -8,6 +8,7 @@ pub(super) struct Utils; pub fn install(lua: &mlua::Lua) -> mlua::Result<()> { let ya: mlua::Table = lua.create_table()?; + Utils::app(lua, &ya)?; Utils::cache(lua, &ya)?; Utils::call(lua, &ya)?; Utils::image(lua, &ya)?; diff --git a/yazi-proxy/Cargo.toml b/yazi-proxy/Cargo.toml index 8fbc3400..63149a55 100644 --- a/yazi-proxy/Cargo.toml +++ b/yazi-proxy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-proxy" -version = "0.2.3" +version = "0.2.4" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -9,8 +9,8 @@ homepage = "https://yazi-rs.github.io" repository = "https://github.com/sxyazi/yazi" [dependencies] -yazi-config = { path = "../yazi-config", version = "0.2.3" } -yazi-shared = { path = "../yazi-shared", version = "0.2.3" } +yazi-config = { path = "../yazi-config", version = "0.2.4" } +yazi-shared = { path = "../yazi-shared", version = "0.2.4" } # External dependencies anyhow = "^1" diff --git a/yazi-proxy/src/lib.rs b/yazi-proxy/src/lib.rs index 8d60381d..6a0687fe 100644 --- a/yazi-proxy/src/lib.rs +++ b/yazi-proxy/src/lib.rs @@ -4,6 +4,7 @@ mod input; mod manager; pub mod options; mod select; +mod semaphore; mod tab; mod tasks; @@ -12,5 +13,8 @@ pub use completion::*; pub use input::*; pub use manager::*; pub use select::*; +pub use semaphore::*; pub use tab::*; pub use tasks::*; + +pub fn init() { crate::init_semaphore(); } diff --git a/yazi-scheduler/src/semaphore.rs b/yazi-proxy/src/semaphore.rs similarity index 100% rename from yazi-scheduler/src/semaphore.rs rename to yazi-proxy/src/semaphore.rs diff --git a/yazi-scheduler/Cargo.toml b/yazi-scheduler/Cargo.toml index e6488457..7c760a29 100644 --- a/yazi-scheduler/Cargo.toml +++ b/yazi-scheduler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-scheduler" -version = "0.2.3" +version = "0.2.4" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -9,11 +9,11 @@ homepage = "https://yazi-rs.github.io" repository = "https://github.com/sxyazi/yazi" [dependencies] -yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.3" } -yazi-config = { path = "../yazi-config", version = "0.2.3" } -yazi-plugin = { path = "../yazi-plugin", version = "0.2.3" } -yazi-proxy = { path = "../yazi-proxy", version = "0.2.3" } -yazi-shared = { path = "../yazi-shared", version = "0.2.3" } +yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.4" } +yazi-config = { path = "../yazi-config", version = "0.2.4" } +yazi-plugin = { path = "../yazi-plugin", version = "0.2.4" } +yazi-proxy = { path = "../yazi-proxy", version = "0.2.4" } +yazi-shared = { path = "../yazi-shared", version = "0.2.4" } # External dependencies anyhow = "^1" diff --git a/yazi-scheduler/src/lib.rs b/yazi-scheduler/src/lib.rs index 1d74cb35..f744e551 100644 --- a/yazi-scheduler/src/lib.rs +++ b/yazi-scheduler/src/lib.rs @@ -7,17 +7,13 @@ mod plugin; mod preload; mod process; mod scheduler; -mod semaphore; mod task; pub use ongoing::*; pub use op::*; pub use scheduler::*; -pub use semaphore::*; pub use task::*; const LOW: u8 = yazi_config::Priority::Low as u8; const NORMAL: u8 = yazi_config::Priority::Normal as u8; const HIGH: u8 = yazi_config::Priority::High as u8; - -pub fn init() { init_semaphore(); } diff --git a/yazi-scheduler/src/process/process.rs b/yazi-scheduler/src/process/process.rs index 02e26aa6..e1b0e930 100644 --- a/yazi-scheduler/src/process/process.rs +++ b/yazi-scheduler/src/process/process.rs @@ -1,11 +1,11 @@ use anyhow::Result; use tokio::{io::{AsyncBufReadExt, BufReader}, select, sync::mpsc}; use yazi_plugin::external::{self, ShellOpt}; -use yazi_proxy::AppProxy; +use yazi_proxy::{AppProxy, HIDER}; use yazi_shared::Defer; use super::ProcessOpOpen; -use crate::{TaskProg, HIDER}; +use crate::TaskProg; pub struct Process { prog: mpsc::UnboundedSender, diff --git a/yazi-shared/Cargo.toml b/yazi-shared/Cargo.toml index 52df8e0d..f40da216 100644 --- a/yazi-shared/Cargo.toml +++ b/yazi-shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-shared" -version = "0.2.3" +version = "0.2.4" edition = "2021" license = "MIT" authors = [ "sxyazi " ] From b10f2de16d46df3ed7f6efe99ac966fd49d6e919 Mon Sep 17 00:00:00 2001 From: sxyazi Date: Sat, 9 Mar 2024 19:04:46 +0800 Subject: [PATCH 9/9] feat: add `--debug` flag to print debug information (#794) --- .github/ISSUE_TEMPLATE/bug.yml | 23 +-- Cargo.lock | 1 + yazi-adaptor/src/adaptor.rs | 268 ++++++++------------------------- yazi-adaptor/src/emulator.rs | 155 +++++++++++++++++++ yazi-adaptor/src/lib.rs | 8 +- yazi-adaptor/src/ueberzug.rs | 21 +-- yazi-boot/Cargo.toml | 5 +- yazi-boot/src/args.rs | 4 + yazi-boot/src/boot.rs | 71 ++++++++- 9 files changed, 322 insertions(+), 234 deletions(-) create mode 100644 yazi-adaptor/src/emulator.rs diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index a4e315be..1c0b17e9 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -12,21 +12,14 @@ body: - Linux Wayland - macOS - Windows + - Windows WSL validations: required: true - type: input id: terminal attributes: label: What terminal are you running Yazi in? - placeholder: "ex: Kitty v0.30.1" - validations: - required: true - - type: input - id: version - attributes: - label: Yazi version - description: Please do a `yazi -V` and paste the output here. - placeholder: "ex: yazi 0.1.5 (3867c29 2023-11-25)" + placeholder: "ex: kitty v0.32.2" validations: required: true - type: dropdown @@ -38,6 +31,18 @@ body: - Not tried, and I'll explain why below validations: required: true + - type: textarea + id: debug + attributes: + label: "`yazi --debug` output" + description: Please do a `yazi --debug` and paste the output here. + value: | + + ```sh + + ``` + validations: + required: true - type: textarea id: description attributes: diff --git a/Cargo.lock b/Cargo.lock index d9d53dd8..5c03f312 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2716,6 +2716,7 @@ dependencies = [ "clap_complete_nushell", "serde", "vergen", + "yazi-adaptor", "yazi-config", "yazi-shared", ] diff --git a/yazi-adaptor/src/adaptor.rs b/yazi-adaptor/src/adaptor.rs index d24c9abb..0cece50a 100644 --- a/yazi-adaptor/src/adaptor.rs +++ b/yazi-adaptor/src/adaptor.rs @@ -1,13 +1,12 @@ -use std::{env, fmt::Display, io::{Read, Write}, path::Path, sync::Arc}; +use std::{env, fmt::Display, path::Path, sync::Arc}; -use anyhow::{anyhow, Result}; -use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; +use anyhow::Result; use ratatui::layout::Rect; use tracing::warn; use yazi_shared::{env_exists, term::Term}; use super::{Iterm2, Kitty, KittyOld}; -use crate::{ueberzug::Ueberzug, Sixel, SHOWN, TMUX}; +use crate::{ueberzug::Ueberzug, Emulator, Sixel, SHOWN, TMUX}; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Adaptor { @@ -22,82 +21,73 @@ pub enum Adaptor { Chafa, } -#[derive(Clone)] -enum Emulator { - Unknown(Vec), - Kitty, - Konsole, - Iterm2, - WezTerm, - Foot, - Ghostty, - BlackBox, - VSCode, - Tabby, - Hyper, - Mintty, - Neovim, +impl Display for Adaptor { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Kitty => write!(f, "kitty"), + Self::KittyOld => write!(f, "kitty"), + Self::Iterm2 => write!(f, "iterm2"), + Self::Sixel => write!(f, "sixel"), + Self::X11 => write!(f, "x11"), + Self::Wayland => write!(f, "wayland"), + Self::Chafa => write!(f, "chafa"), + } + } } impl Adaptor { - fn emulator() -> Emulator { - if env_exists("NVIM_LOG_FILE") && env_exists("NVIM") { - return Emulator::Neovim; + pub async fn image_show(self, path: &Path, rect: Rect) -> Result<(u32, u32)> { + match self { + Self::Kitty => Kitty::image_show(path, rect).await, + Self::KittyOld => KittyOld::image_show(path, rect).await, + Self::Iterm2 => Iterm2::image_show(path, rect).await, + Self::Sixel => Sixel::image_show(path, rect).await, + _ => Ueberzug::image_show(path, rect).await, } - - let vars = [ - ("KITTY_WINDOW_ID", Emulator::Kitty), - ("KONSOLE_VERSION", Emulator::Konsole), - ("ITERM_SESSION_ID", Emulator::Iterm2), - ("WEZTERM_EXECUTABLE", Emulator::WezTerm), - ("GHOSTTY_RESOURCES_DIR", Emulator::Ghostty), - ("VSCODE_INJECTION", Emulator::VSCode), - ("TABBY_CONFIG_DIRECTORY", Emulator::Tabby), - ]; - match vars.into_iter().find(|v| env_exists(v.0)) { - Some(var) => return var.1, - None => warn!("[Adaptor] No special environment variables detected"), - } - - let (term, program) = Self::via_env(); - match program.as_str() { - "iTerm.app" => return Emulator::Iterm2, - "WezTerm" => return Emulator::WezTerm, - "ghostty" => return Emulator::Ghostty, - "BlackBox" => return Emulator::BlackBox, - "vscode" => return Emulator::VSCode, - "Tabby" => return Emulator::Tabby, - "Hyper" => return Emulator::Hyper, - "mintty" => return Emulator::Mintty, - _ => warn!("[Adaptor] Unknown TERM_PROGRAM: {program}"), - } - match term.as_str() { - "xterm-kitty" => return Emulator::Kitty, - "foot" => return Emulator::Foot, - "foot-extra" => return Emulator::Foot, - "xterm-ghostty" => return Emulator::Ghostty, - _ => warn!("[Adaptor] Unknown TERM: {term}"), - } - - Self::via_csi().unwrap_or(Emulator::Unknown(vec![])) } - pub(super) fn detect() -> Self { - let mut protocols = match Self::emulator() { - Emulator::Unknown(adapters) => adapters, - Emulator::Kitty => vec![Self::Kitty], - Emulator::Konsole => vec![Self::KittyOld, Self::Iterm2, Self::Sixel], - Emulator::Iterm2 => vec![Self::Iterm2, Self::Sixel], - Emulator::WezTerm => vec![Self::Iterm2, Self::Sixel], - Emulator::Foot => vec![Self::Sixel], - Emulator::Ghostty => vec![Self::KittyOld], - Emulator::BlackBox => vec![Self::Sixel], - Emulator::VSCode => vec![Self::Iterm2, Self::Sixel], - Emulator::Tabby => vec![Self::Iterm2, Self::Sixel], - Emulator::Hyper => vec![Self::Iterm2, Self::Sixel], - Emulator::Mintty => vec![Self::Iterm2], - Emulator::Neovim => vec![], - }; + pub fn image_hide(self) -> Result<()> { + if let Some(rect) = SHOWN.swap(None) { self.image_erase(*rect) } else { Ok(()) } + } + + pub fn image_erase(self, rect: Rect) -> Result<()> { + match self { + Self::Kitty => Kitty::image_erase(rect), + Self::Iterm2 => Iterm2::image_erase(rect), + Self::KittyOld => KittyOld::image_erase(), + Self::Sixel => Sixel::image_erase(rect), + _ => Ueberzug::image_erase(rect), + } + } + + #[inline] + pub fn shown_load(self) -> Option { SHOWN.load_full().map(|r| *r) } + + pub(super) fn start(self) { Ueberzug::start(self); } + + #[inline] + pub(super) fn shown_store(rect: Rect, size: (u32, u32)) { + SHOWN.store(Some(Arc::new( + Term::ratio() + .map(|(r1, r2)| Rect { + x: rect.x, + y: rect.y, + width: (size.0 as f64 / r1).ceil() as u16, + height: (size.1 as f64 / r2).ceil() as u16, + }) + .unwrap_or(rect), + ))); + } + + #[inline] + pub(super) fn needs_ueberzug(self) -> bool { + !matches!(self, Self::Kitty | Self::KittyOld | Self::Iterm2 | Self::Sixel) + } +} + +impl Adaptor { + pub fn matches() -> Self { + let mut protocols = Emulator::detect().adapters(); #[cfg(windows)] protocols.retain(|p| *p == Self::Iterm2); @@ -129,134 +119,4 @@ impl Adaptor { warn!("[Adaptor] Falling back to chafa"); Self::Chafa } - - fn via_env() -> (String, String) { - fn tmux_env(name: &str) -> Result { - let output = std::process::Command::new("tmux").args(["show-environment", name]).output()?; - - String::from_utf8(output.stdout)? - .trim() - .strip_prefix(&format!("{name}=")) - .map_or_else(|| Err(anyhow!("")), |s| Ok(s.to_string())) - } - - let mut term = env::var("TERM").unwrap_or_default(); - let mut program = env::var("TERM_PROGRAM").unwrap_or_default(); - - if *TMUX { - term = tmux_env("TERM").unwrap_or(term); - program = tmux_env("TERM_PROGRAM").unwrap_or(program); - } - - (term, program) - } - - fn via_csi() -> Result { - enable_raw_mode()?; - std::io::stdout().write_all(b"\x1b[>q\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c")?; - std::io::stdout().flush()?; - - let mut stdin = std::io::stdin().lock(); - let mut buf = String::with_capacity(200); - loop { - let mut c = [0; 1]; - if stdin.read(&mut c)? == 0 { - break; - } - if c[0] == b'c' && buf.contains("\x1b[?") { - break; - } - buf.push(c[0] as char); - } - - disable_raw_mode().ok(); - let names = [ - ("kitty", Emulator::Kitty), - ("Konsole", Emulator::Konsole), - ("iTerm2", Emulator::Iterm2), - ("WezTerm", Emulator::WezTerm), - ("foot", Emulator::Foot), - ("ghostty", Emulator::Ghostty), - ]; - - for (name, emulator) in names.iter() { - if buf.contains(name) { - return Ok(emulator.clone()); - } - } - - let mut adapters = Vec::with_capacity(2); - if buf.contains("\x1b_Gi=31;OK") { - adapters.push(Adaptor::KittyOld); - } - if ["?4;", "?4c", ";4;", ";4c"].iter().any(|s| buf.contains(s)) { - adapters.push(Adaptor::Sixel); - } - - Ok(Emulator::Unknown(adapters)) - } -} - -impl Display for Adaptor { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Kitty => write!(f, "kitty"), - Self::KittyOld => write!(f, "kitty"), - Self::Iterm2 => write!(f, "iterm2"), - Self::Sixel => write!(f, "sixel"), - Self::X11 => write!(f, "x11"), - Self::Wayland => write!(f, "wayland"), - Self::Chafa => write!(f, "chafa"), - } - } -} - -impl Adaptor { - pub(super) fn start(self) { Ueberzug::start(self); } - - pub async fn image_show(self, path: &Path, rect: Rect) -> Result<(u32, u32)> { - match self { - Self::Kitty => Kitty::image_show(path, rect).await, - Self::KittyOld => KittyOld::image_show(path, rect).await, - Self::Iterm2 => Iterm2::image_show(path, rect).await, - Self::Sixel => Sixel::image_show(path, rect).await, - _ => Ueberzug::image_show(path, rect).await, - } - } - - pub fn image_hide(self) -> Result<()> { - if let Some(rect) = SHOWN.swap(None) { self.image_erase(*rect) } else { Ok(()) } - } - - pub fn image_erase(self, rect: Rect) -> Result<()> { - match self { - Self::Kitty => Kitty::image_erase(rect), - Self::Iterm2 => Iterm2::image_erase(rect), - Self::KittyOld => KittyOld::image_erase(), - Self::Sixel => Sixel::image_erase(rect), - _ => Ueberzug::image_erase(rect), - } - } - - #[inline] - pub fn shown_load(self) -> Option { SHOWN.load_full().map(|r| *r) } - - #[inline] - pub(super) fn shown_store(rect: Rect, size: (u32, u32)) { - SHOWN.store(Some(Arc::new( - Term::ratio() - .map(|(r1, r2)| Rect { - x: rect.x, - y: rect.y, - width: (size.0 as f64 / r1).ceil() as u16, - height: (size.1 as f64 / r2).ceil() as u16, - }) - .unwrap_or(rect), - ))); - } - - #[inline] - pub(super) fn needs_ueberzug(self) -> bool { - !matches!(self, Self::Kitty | Self::KittyOld | Self::Iterm2 | Self::Sixel) - } } diff --git a/yazi-adaptor/src/emulator.rs b/yazi-adaptor/src/emulator.rs new file mode 100644 index 00000000..30702fff --- /dev/null +++ b/yazi-adaptor/src/emulator.rs @@ -0,0 +1,155 @@ +use std::{env, io::{Read, Write}}; + +use anyhow::{anyhow, Result}; +use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; +use tracing::warn; +use yazi_shared::env_exists; + +use crate::{Adaptor, TMUX}; + +#[derive(Clone, Debug)] +pub enum Emulator { + Unknown(Vec), + Kitty, + Konsole, + Iterm2, + WezTerm, + Foot, + Ghostty, + BlackBox, + VSCode, + Tabby, + Hyper, + Mintty, + Neovim, +} + +impl Emulator { + pub fn adapters(self) -> Vec { + match self { + Self::Unknown(adapters) => adapters, + Self::Kitty => vec![Adaptor::Kitty], + Self::Konsole => vec![Adaptor::KittyOld, Adaptor::Iterm2, Adaptor::Sixel], + Self::Iterm2 => vec![Adaptor::Iterm2, Adaptor::Sixel], + Self::WezTerm => vec![Adaptor::Iterm2, Adaptor::Sixel], + Self::Foot => vec![Adaptor::Sixel], + Self::Ghostty => vec![Adaptor::KittyOld], + Self::BlackBox => vec![Adaptor::Sixel], + Self::VSCode => vec![Adaptor::Iterm2, Adaptor::Sixel], + Self::Tabby => vec![Adaptor::Iterm2, Adaptor::Sixel], + Self::Hyper => vec![Adaptor::Iterm2, Adaptor::Sixel], + Self::Mintty => vec![Adaptor::Iterm2], + Self::Neovim => vec![], + } + } +} + +impl Emulator { + pub fn detect() -> Self { + if env_exists("NVIM_LOG_FILE") && env_exists("NVIM") { + return Self::Neovim; + } + + let vars = [ + ("KITTY_WINDOW_ID", Self::Kitty), + ("KONSOLE_VERSION", Self::Konsole), + ("ITERM_SESSION_ID", Self::Iterm2), + ("WEZTERM_EXECUTABLE", Self::WezTerm), + ("GHOSTTY_RESOURCES_DIR", Self::Ghostty), + ("VSCODE_INJECTION", Self::VSCode), + ("TABBY_CONFIG_DIRECTORY", Self::Tabby), + ]; + match vars.into_iter().find(|v| env_exists(v.0)) { + Some(var) => return var.1, + None => warn!("[Adaptor] No special environment variables detected"), + } + + let (term, program) = Self::via_env(); + match program.as_str() { + "iTerm.app" => return Self::Iterm2, + "WezTerm" => return Self::WezTerm, + "ghostty" => return Self::Ghostty, + "BlackBox" => return Self::BlackBox, + "vscode" => return Self::VSCode, + "Tabby" => return Self::Tabby, + "Hyper" => return Self::Hyper, + "mintty" => return Self::Mintty, + _ => warn!("[Adaptor] Unknown TERM_PROGRAM: {program}"), + } + match term.as_str() { + "xterm-kitty" => return Self::Kitty, + "foot" => return Self::Foot, + "foot-extra" => return Self::Foot, + "xterm-ghostty" => return Self::Ghostty, + _ => warn!("[Adaptor] Unknown TERM: {term}"), + } + + Self::via_csi().unwrap_or(Self::Unknown(vec![])) + } + + pub fn via_env() -> (String, String) { + fn tmux_env(name: &str) -> Result { + let output = std::process::Command::new("tmux").args(["show-environment", name]).output()?; + + String::from_utf8(output.stdout)? + .trim() + .strip_prefix(&format!("{name}=")) + .map_or_else(|| Err(anyhow!("")), |s| Ok(s.to_string())) + } + + let mut term = env::var("TERM").unwrap_or_default(); + let mut program = env::var("TERM_PROGRAM").unwrap_or_default(); + + if *TMUX { + term = tmux_env("TERM").unwrap_or(term); + program = tmux_env("TERM_PROGRAM").unwrap_or(program); + } + + (term, program) + } + + pub fn via_csi() -> Result { + enable_raw_mode()?; + std::io::stdout().write_all(b"\x1b[>q\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c")?; + std::io::stdout().flush()?; + + let mut stdin = std::io::stdin().lock(); + let mut buf = String::with_capacity(200); + loop { + let mut c = [0; 1]; + if stdin.read(&mut c)? == 0 { + break; + } + if c[0] == b'c' && buf.contains("\x1b[?") { + break; + } + buf.push(c[0] as char); + } + + disable_raw_mode().ok(); + let names = [ + ("kitty", Self::Kitty), + ("Konsole", Self::Konsole), + ("iTerm2", Self::Iterm2), + ("WezTerm", Self::WezTerm), + ("foot", Self::Foot), + ("ghostty", Self::Ghostty), + ]; + + for (name, emulator) in names.iter() { + if buf.contains(name) { + return Ok(emulator.clone()); + } + } + + let mut adapters = Vec::with_capacity(2); + if buf.contains("\x1b_Gi=31;OK") { + adapters.push(Adaptor::KittyOld); + } + if ["?4;", "?4c", ";4;", ";4c"].iter().any(|s| buf.contains(s)) { + adapters.push(Adaptor::Sixel); + } + + Ok(Self::Unknown(adapters)) + } +} diff --git a/yazi-adaptor/src/lib.rs b/yazi-adaptor/src/lib.rs index b4d52f4a..700e944e 100644 --- a/yazi-adaptor/src/lib.rs +++ b/yazi-adaptor/src/lib.rs @@ -1,6 +1,7 @@ #![allow(clippy::unit_arg)] mod adaptor; +mod emulator; mod image; mod iterm2; mod kitty; @@ -8,7 +9,8 @@ mod kitty_old; mod sixel; mod ueberzug; -use adaptor::*; +pub use adaptor::*; +pub use emulator::*; use iterm2::*; use kitty::*; use kitty_old::*; @@ -20,7 +22,7 @@ pub use crate::image::*; pub static ADAPTOR: RoCell = RoCell::new(); // Tmux support -static TMUX: RoCell = RoCell::new(); +pub static TMUX: RoCell = RoCell::new(); static ESCAPE: RoCell<&'static str> = RoCell::new(); static START: RoCell<&'static str> = RoCell::new(); static CLOSE: RoCell<&'static str> = RoCell::new(); @@ -36,7 +38,7 @@ pub fn init() { SHOWN.with(Default::default); - ADAPTOR.init(Adaptor::detect()); + ADAPTOR.init(Adaptor::matches()); ADAPTOR.start(); if *TMUX { diff --git a/yazi-adaptor/src/ueberzug.rs b/yazi-adaptor/src/ueberzug.rs index 35d8c232..c2f9a6c1 100644 --- a/yazi-adaptor/src/ueberzug.rs +++ b/yazi-adaptor/src/ueberzug.rs @@ -4,7 +4,7 @@ use anyhow::{bail, Result}; use imagesize::ImageSize; use ratatui::layout::Rect; use tokio::{io::AsyncWriteExt, process::{Child, Command}, sync::mpsc::{self, UnboundedSender}}; -use tracing::debug; +use tracing::{debug, warn}; use yazi_config::PREVIEW; use yazi_shared::RoCell; @@ -71,14 +71,17 @@ impl Ueberzug { } fn create_demon(adaptor: Adaptor) -> Result { - Ok( - Command::new("ueberzug") - .args(["layer", "-so", &adaptor.to_string()]) - .kill_on_drop(true) - .stdin(Stdio::piped()) - .stderr(Stdio::null()) - .spawn()?, - ) + let result = Command::new("ueberzug") + .args(["layer", "-so", &adaptor.to_string()]) + .kill_on_drop(true) + .stdin(Stdio::piped()) + .stderr(Stdio::null()) + .spawn(); + + if let Err(ref e) = result { + warn!("ueberzug spawning failed: {}", e); + } + Ok(result?) } fn adjust_rect(mut rect: Rect) -> Rect { diff --git a/yazi-boot/Cargo.toml b/yazi-boot/Cargo.toml index 42c5b4c4..dfe8cbc0 100644 --- a/yazi-boot/Cargo.toml +++ b/yazi-boot/Cargo.toml @@ -9,8 +9,9 @@ homepage = "https://yazi-rs.github.io" repository = "https://github.com/sxyazi/yazi" [dependencies] -yazi-config = { path = "../yazi-config", version = "0.2.4" } -yazi-shared = { path = "../yazi-shared", version = "0.2.4" } +yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.4" } +yazi-config = { path = "../yazi-config", version = "0.2.4" } +yazi-shared = { path = "../yazi-shared", version = "0.2.4" } # External dependencies clap = { version = "^4", features = [ "derive" ] } diff --git a/yazi-boot/src/args.rs b/yazi-boot/src/args.rs index 08c4a11a..8c9181da 100644 --- a/yazi-boot/src/args.rs +++ b/yazi-boot/src/args.rs @@ -20,6 +20,10 @@ pub struct Args { #[arg(long, action)] pub clear_cache: bool, + /// Print debug information + #[arg(long, action)] + pub debug: bool, + /// Print version #[arg(short = 'V', long)] pub version: bool, diff --git a/yazi-boot/src/boot.rs b/yazi-boot/src/boot.rs index 65fee0f0..6380445c 100644 --- a/yazi-boot/src/boot.rs +++ b/yazi-boot/src/boot.rs @@ -1,4 +1,4 @@ -use std::{ffi::OsString, path::{Path, PathBuf}, process}; +use std::{env, ffi::OsString, path::{Path, PathBuf}, process}; use clap::Parser; use serde::Serialize; @@ -32,6 +32,63 @@ impl Boot { (parent.unwrap().to_owned(), Some(entry.file_name().unwrap().to_owned())) } + + fn action_version() { + println!( + "yazi {} ({} {})", + env!("CARGO_PKG_VERSION"), + env!("VERGEN_GIT_SHA"), + env!("VERGEN_BUILD_DATE") + ); + } + + fn action_debug() { + print!("Yazi\n "); + Self::action_version(); + + println!("\nEnvironment"); + println!( + " OS: {}-{} ({})", + std::env::consts::OS, + std::env::consts::ARCH, + std::env::consts::FAMILY + ); + println!(" Debug: {}", cfg!(debug_assertions)); + + println!("\nEmulator"); + println!(" Emulator.via_env: {:?}", yazi_adaptor::Emulator::via_env()); + println!(" Emulator.via_csi: {:?}", yazi_adaptor::Emulator::via_csi()); + println!(" Emulator.detect: {:?}", yazi_adaptor::Emulator::detect()); + + println!("\nAdaptor"); + println!(" Adaptor.matches: {:?}", yazi_adaptor::Adaptor::matches()); + + println!("\ntmux"); + println!(" TMUX: {:?}", *yazi_adaptor::TMUX); + + println!("\nZellij"); + println!(" ZELLIJ_SESSION_NAME: {:?}", env::var_os("ZELLIJ_SESSION_NAME")); + + println!("\nDesktop"); + println!(" XDG_SESSION_TYPE: {:?}", env::var_os("XDG_SESSION_TYPE")); + println!(" WAYLAND_DISPLAY: {:?}", env::var_os("WAYLAND_DISPLAY")); + println!(" DISPLAY: {:?}", env::var_os("DISPLAY")); + + println!("\nUeberzug"); + println!(" Version: {:?}", std::process::Command::new("ueberzug").arg("--version").output()); + + println!("\nWSL"); + println!( + " /proc/sys/fs/binfmt_misc/WSLInterop: {:?}", + std::fs::symlink_metadata("/proc/sys/fs/binfmt_misc/WSLInterop").is_ok() + ); + + println!("\n\n--------------------------------------------------"); + println!( + "When reporting a bug, please also upload the `yazi.log` log file - only upload the most recent content by time." + ); + println!("You can find it in the {:?} directory.", Xdg::state_dir()); + } } impl Default for Boot { @@ -58,13 +115,13 @@ impl Default for Args { fn default() -> Self { let args = Self::parse(); + if args.debug { + Boot::action_debug(); + process::exit(0); + } + if args.version { - println!( - "yazi {} ({} {})", - env!("CARGO_PKG_VERSION"), - env!("VERGEN_GIT_SHA"), - env!("VERGEN_BUILD_DATE") - ); + Boot::action_version(); process::exit(0); }