From 92112de1c05c288c631f669523fc40fd4eb709d2 Mon Sep 17 00:00:00 2001 From: thelamb Date: Thu, 22 Aug 2024 16:20:31 +0200 Subject: [PATCH] feat: new `confirm` component (#1167) Co-authored-by: sxyazi --- Cargo.toml | 2 +- README.md | 2 +- yazi-config/preset/keymap.toml | 23 ++++- yazi-config/preset/yazi.toml | 35 ++++--- yazi-config/src/keymap/keymap.rs | 6 ++ yazi-config/src/lib.rs | 2 + yazi-config/src/popup/confirm.rs | 47 +++++++++ yazi-config/src/popup/input.rs | 20 ---- yazi-config/src/popup/mod.rs | 2 + yazi-config/src/popup/offset.rs | 2 +- yazi-config/src/popup/options.rs | 119 +++++++++++++---------- yazi-config/src/popup/origin.rs | 2 +- yazi-config/src/popup/position.rs | 4 +- yazi-core/src/confirm/commands/arrow.rs | 41 ++++++++ yazi-core/src/confirm/commands/close.rs | 26 +++++ yazi-core/src/confirm/commands/mod.rs | 3 + yazi-core/src/confirm/commands/show.rs | 38 ++++++++ yazi-core/src/confirm/confirm.rs | 16 +++ yazi-core/src/confirm/mod.rs | 4 + yazi-core/src/lib.rs | 1 + yazi-core/src/manager/commands/create.rs | 14 +-- yazi-core/src/manager/commands/quit.rs | 20 ++-- yazi-core/src/manager/commands/remove.rs | 18 ++-- yazi-core/src/manager/commands/rename.rs | 18 ++-- yazi-core/src/manager/manager.rs | 11 +++ yazi-core/src/tab/tab.rs | 21 ++++ yazi-fm/src/completion/completion.rs | 2 +- yazi-fm/src/confirm/buttons.rs | 13 +++ yazi-fm/src/confirm/confirm.rs | 41 ++++++++ yazi-fm/src/confirm/content.rs | 22 +++++ yazi-fm/src/confirm/list.rs | 44 +++++++++ yazi-fm/src/confirm/mod.rs | 9 ++ yazi-fm/src/context.rs | 23 +---- yazi-fm/src/executor.rs | 15 +++ yazi-fm/src/input/input.rs | 2 +- yazi-fm/src/main.rs | 1 + yazi-fm/src/root.rs | 6 +- yazi-fm/src/router.rs | 2 + yazi-fm/src/select/select.rs | 2 +- yazi-fs/src/folder.rs | 10 -- yazi-plugin/src/bindings/position.rs | 2 +- yazi-plugin/src/utils/layer.rs | 13 +++ yazi-proxy/src/confirm.rs | 17 ++++ yazi-proxy/src/lib.rs | 2 + yazi-shared/src/layer.rs | 3 + 45 files changed, 559 insertions(+), 167 deletions(-) create mode 100644 yazi-config/src/popup/confirm.rs create mode 100644 yazi-core/src/confirm/commands/arrow.rs create mode 100644 yazi-core/src/confirm/commands/close.rs create mode 100644 yazi-core/src/confirm/commands/mod.rs create mode 100644 yazi-core/src/confirm/commands/show.rs create mode 100644 yazi-core/src/confirm/confirm.rs create mode 100644 yazi-core/src/confirm/mod.rs create mode 100644 yazi-fm/src/confirm/buttons.rs create mode 100644 yazi-fm/src/confirm/confirm.rs create mode 100644 yazi-fm/src/confirm/content.rs create mode 100644 yazi-fm/src/confirm/list.rs create mode 100644 yazi-fm/src/confirm/mod.rs create mode 100644 yazi-proxy/src/confirm.rs diff --git a/Cargo.toml b/Cargo.toml index 39ac725b..3648a468 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ libc = "0.2.158" md-5 = "0.10.6" mlua = { version = "0.9.9", features = [ "lua54", "serialize", "macros", "async" ] } parking_lot = "0.12.3" -ratatui = "0.27.0" +ratatui = { version = "0.27.0", features = [ "unstable-rendered-line-info" ] } regex = "1.10.6" scopeguard = "1.2.0" serde = { version = "1.0.208", features = [ "derive" ] } diff --git a/README.md b/README.md index 01a61769..46f6b1ff 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Yazi (means "duck") is a terminal file manager written in Rust, based on non-blo - 📡 **Data Distribution Service**: Built on a client-server architecture (no additional server process required), integrated with a Lua-based publish-subscribe model, achieving cross-instance communication and state persistence. - 📦 **Package Manager**: Install plugins and themes with one command, keeping them up to date, or pin them to a specific version. - 🧰 Integration with ripgrep, fd, fzf, zoxide -- 💫 Vim-like input/select/which/notify component, auto-completion for cd paths +- 💫 Vim-like input/select/confirm/which/notify component, auto-completion for cd paths - 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, directories, code, etc.) - 🔄 Bulk Renaming, Visual Mode, File Chooser - 🎨 Theme System, Mouse Support, Trash Bin, Custom Layouts, CSI u diff --git a/yazi-config/preset/keymap.toml b/yazi-config/preset/keymap.toml index 7091faaf..7c08299e 100644 --- a/yazi-config/preset/keymap.toml +++ b/yazi-config/preset/keymap.toml @@ -196,7 +196,7 @@ keymap = [ keymap = [ { on = "", run = "close", desc = "Cancel input" }, - { on = "", run = "close --submit", desc = "Submit the input" }, + { on = "", run = "close --submit", desc = "Submit input" }, { on = "", run = "escape", desc = "Go back the normal mode, or cancel input" }, { on = "", run = "escape", desc = "Go back the normal mode, or cancel input" }, @@ -262,6 +262,27 @@ keymap = [ { on = "", run = "help", desc = "Open help" }, ] +[confirm] +keymap = [ + { on = "", run = "close", desc = "Cancel the confirm" }, + { on = "", run = "close", desc = "Cancel the confirm" }, + { on = "", run = "close", desc = "Cancel the confirm" }, + { on = "", run = "close --submit", desc = "Submit the confirm" }, + + { on = "n", run = "close", desc = "Cancel the confirm" }, + { on = "y", run = "close --submit", desc = "Submit the confirm" }, + + { on = "k", run = "arrow -1", desc = "Move cursor up" }, + { on = "j", run = "arrow 1", desc = "Move cursor down" }, + + { on = "", run = "arrow -1", desc = "Move cursor up" }, + { on = "", run = "arrow 1", desc = "Move cursor down" }, + + # Help + { on = "~", run = "help", desc = "Open help" }, + { on = "", run = "help", desc = "Open help" }, +] + [completion] keymap = [ diff --git a/yazi-config/preset/yazi.toml b/yazi-config/preset/yazi.toml index e11aa703..06d75096 100644 --- a/yazi-config/preset/yazi.toml +++ b/yazi-config/preset/yazi.toml @@ -147,16 +147,6 @@ rename_title = "Rename:" rename_origin = "hovered" rename_offset = [ 0, 1, 50, 3 ] -# trash -trash_title = "Move {n} selected file{s} to trash? (y/N)" -trash_origin = "top-center" -trash_offset = [ 0, 2, 50, 3 ] - -# delete -delete_title = "Delete {n} selected file{s} permanently? (y/N)" -delete_origin = "top-center" -delete_offset = [ 0, 2, 50, 3 ] - # filter filter_title = "Filter:" filter_origin = "top-center" @@ -177,15 +167,28 @@ shell_title = [ "Shell:", "Shell (block):" ] shell_origin = "top-center" shell_offset = [ 0, 2, 50, 3 ] +[confirm] +# trash +trash_title = "Trash {n} selected file{s}?" +trash_origin = "center" +trash_offset = [ 0, 0, 70, 20 ] + +# delete +delete_title = "Permanently delete {n} selected file{s}?" +delete_origin = "center" +delete_offset = [ 0, 0, 70, 20 ] + # overwrite -overwrite_title = "Overwrite an existing file? (y/N)" -overwrite_origin = "top-center" -overwrite_offset = [ 0, 2, 50, 3 ] +overwrite_title = "Overwrite file?" +overwrite_content = "Will overwrite the following file:" +overwrite_origin = "center" +overwrite_offset = [ 0, 0, 50, 20 ] # quit -quit_title = "{n} task{s} running, sure to quit? (y/N)" -quit_origin = "top-center" -quit_offset = [ 0, 2, 50, 3 ] +quit_title = "Quit?" +quit_content = "The following task is still running, are you sure you want to quit?" +quit_origin = "center" +quit_offset = [ 0, 0, 50, 20 ] [select] open_title = "Open with:" diff --git a/yazi-config/src/keymap/keymap.rs b/yazi-config/src/keymap/keymap.rs index f5542281..c92ae526 100644 --- a/yazi-config/src/keymap/keymap.rs +++ b/yazi-config/src/keymap/keymap.rs @@ -12,6 +12,7 @@ pub struct Keymap { pub tasks: Vec, pub select: Vec, pub input: Vec, + pub confirm: Vec, pub help: Vec, pub completion: Vec, } @@ -25,6 +26,7 @@ impl Keymap { Layer::Tasks => &self.tasks, Layer::Select => &self.select, Layer::Input => &self.input, + Layer::Confirm => &self.confirm, Layer::Help => &self.help, Layer::Completion => &self.completion, Layer::Which => unreachable!(), @@ -49,6 +51,7 @@ impl<'de> Deserialize<'de> for Keymap { tasks: Inner, select: Inner, input: Inner, + confirm: Inner, help: Inner, completion: Inner, } @@ -72,6 +75,8 @@ impl<'de> Deserialize<'de> for Keymap { #[rustfmt::skip] Preset::mix(&mut shadow.input.keymap, shadow.input.prepend_keymap, shadow.input.append_keymap); #[rustfmt::skip] + Preset::mix(&mut shadow.confirm.keymap, shadow.confirm.prepend_keymap, shadow.confirm.append_keymap); + #[rustfmt::skip] Preset::mix(&mut shadow.help.keymap, shadow.help.prepend_keymap, shadow.help.append_keymap); #[rustfmt::skip] Preset::mix(&mut shadow.completion.keymap, shadow.completion.prepend_keymap, shadow.completion.append_keymap); @@ -81,6 +86,7 @@ impl<'de> Deserialize<'de> for Keymap { tasks: shadow.tasks.keymap, select: shadow.select.keymap, input: shadow.input.keymap, + confirm: shadow.confirm.keymap, help: shadow.help.keymap, completion: shadow.completion.keymap, }) diff --git a/yazi-config/src/lib.rs b/yazi-config/src/lib.rs index 72b5cd25..5dcdc77c 100644 --- a/yazi-config/src/lib.rs +++ b/yazi-config/src/lib.rs @@ -35,6 +35,7 @@ pub static PREVIEW: RoCell = RoCell::new(); pub static TASKS: RoCell = RoCell::new(); pub static THEME: RoCell = RoCell::new(); pub static INPUT: RoCell = RoCell::new(); +pub static CONFIRM: RoCell = RoCell::new(); pub static SELECT: RoCell = RoCell::new(); pub static WHICH: RoCell = RoCell::new(); @@ -55,6 +56,7 @@ pub fn init() -> anyhow::Result<()> { TASKS.init(<_>::from_str(yazi_toml)?); THEME.init(<_>::from_str(theme_toml)?); INPUT.init(<_>::from_str(yazi_toml)?); + CONFIRM.init(<_>::from_str(yazi_toml)?); SELECT.init(<_>::from_str(yazi_toml)?); WHICH.init(<_>::from_str(yazi_toml)?); diff --git a/yazi-config/src/popup/confirm.rs b/yazi-config/src/popup/confirm.rs new file mode 100644 index 00000000..b55686f7 --- /dev/null +++ b/yazi-config/src/popup/confirm.rs @@ -0,0 +1,47 @@ +use std::str::FromStr; + +use serde::Deserialize; + +use super::{Offset, Origin}; + +#[derive(Deserialize)] +pub struct Confirm { + // trash + pub trash_title: String, + pub trash_origin: Origin, + pub trash_offset: Offset, + + // delete + pub delete_title: String, + pub delete_origin: Origin, + pub delete_offset: Offset, + + // overwrite + pub overwrite_title: String, + pub overwrite_content: String, + pub overwrite_origin: Origin, + pub overwrite_offset: Offset, + + // quit + pub quit_title: String, + pub quit_content: String, + pub quit_origin: Origin, + pub quit_offset: Offset, +} + +impl FromStr for Confirm { + type Err = toml::de::Error; + + fn from_str(s: &str) -> Result { + #[derive(Deserialize)] + struct Outer { + confirm: Confirm, + } + + Ok(toml::from_str::(s)?.confirm) + } +} + +impl Confirm { + pub const fn border(&self) -> u16 { 2 } +} diff --git a/yazi-config/src/popup/input.rs b/yazi-config/src/popup/input.rs index 9ec27cdc..633066b1 100644 --- a/yazi-config/src/popup/input.rs +++ b/yazi-config/src/popup/input.rs @@ -23,16 +23,6 @@ pub struct Input { pub rename_origin: Origin, pub rename_offset: Offset, - // trash - pub trash_title: String, - pub trash_origin: Origin, - pub trash_offset: Offset, - - // delete - pub delete_title: String, - pub delete_origin: Origin, - pub delete_offset: Offset, - // filter pub filter_title: String, pub filter_origin: Origin, @@ -52,16 +42,6 @@ pub struct Input { pub shell_title: [String; 2], pub shell_origin: Origin, pub shell_offset: Offset, - - // overwrite - pub overwrite_title: String, - pub overwrite_origin: Origin, - pub overwrite_offset: Offset, - - // quit - pub quit_title: String, - pub quit_origin: Origin, - pub quit_offset: Offset, } impl Input { diff --git a/yazi-config/src/popup/mod.rs b/yazi-config/src/popup/mod.rs index 8308b911..2bcca1b2 100644 --- a/yazi-config/src/popup/mod.rs +++ b/yazi-config/src/popup/mod.rs @@ -1,3 +1,4 @@ +mod confirm; mod input; mod offset; mod options; @@ -5,6 +6,7 @@ mod origin; mod position; mod select; +pub use confirm::*; pub use input::*; pub use offset::*; pub use options::*; diff --git a/yazi-config/src/popup/offset.rs b/yazi-config/src/popup/offset.rs index 1a36860b..c81e5ddd 100644 --- a/yazi-config/src/popup/offset.rs +++ b/yazi-config/src/popup/offset.rs @@ -1,7 +1,7 @@ use anyhow::bail; use serde::Deserialize; -#[derive(Clone, Copy, Default, Deserialize)] +#[derive(Clone, Copy, Debug, Default, Deserialize)] #[serde(try_from = "Vec")] pub struct Offset { pub x: i16, diff --git a/yazi-config/src/popup/options.rs b/yazi-config/src/popup/options.rs index 2c7e72c6..6e54c247 100644 --- a/yazi-config/src/popup/options.rs +++ b/yazi-config/src/popup/options.rs @@ -1,5 +1,8 @@ -use super::{Offset, Position}; -use crate::{INPUT, SELECT}; +use ratatui::{text::Text, widgets::{Paragraph, Wrap}}; +use yazi_shared::fs::Url; + +use super::{Offset, Origin, Position}; +use crate::{CONFIRM, INPUT, SELECT}; #[derive(Default)] pub struct InputCfg { @@ -19,8 +22,15 @@ pub struct SelectCfg { pub position: Position, } +#[derive(Default)] +pub struct ConfirmCfg { + pub title: String, + pub position: Position, + pub content: Paragraph<'static>, + pub list: Paragraph<'static>, +} + impl InputCfg { - #[inline] pub fn cd() -> Self { Self { title: INPUT.cd_title.to_owned(), @@ -30,7 +40,6 @@ impl InputCfg { } } - #[inline] pub fn create() -> Self { Self { title: INPUT.create_title.to_owned(), @@ -39,7 +48,6 @@ impl InputCfg { } } - #[inline] pub fn rename() -> Self { Self { title: INPUT.rename_title.to_owned(), @@ -48,27 +56,6 @@ impl InputCfg { } } - #[inline] - pub fn trash(n: usize) -> Self { - let title = INPUT.trash_title.replace("{n}", &n.to_string()); - Self { - title: title.replace("{s}", if n > 1 { "s" } else { "" }), - position: Position::new(INPUT.trash_origin, INPUT.trash_offset), - ..Default::default() - } - } - - #[inline] - pub fn delete(n: usize) -> Self { - let title = INPUT.delete_title.replace("{n}", &n.to_string()); - Self { - title: title.replace("{s}", if n > 1 { "s" } else { "" }), - position: Position::new(INPUT.delete_origin, INPUT.delete_offset), - ..Default::default() - } - } - - #[inline] pub fn filter() -> Self { Self { title: INPUT.filter_title.to_owned(), @@ -78,7 +65,6 @@ impl InputCfg { } } - #[inline] pub fn find(prev: bool) -> Self { Self { title: INPUT.find_title[prev as usize].to_owned(), @@ -88,7 +74,6 @@ impl InputCfg { } } - #[inline] pub fn search(name: &str) -> Self { Self { title: INPUT.search_title.replace("{n}", name), @@ -97,7 +82,6 @@ impl InputCfg { } } - #[inline] pub fn shell(block: bool) -> Self { Self { title: INPUT.shell_title[block as usize].to_owned(), @@ -107,25 +91,6 @@ impl InputCfg { } } - #[inline] - pub fn overwrite() -> Self { - Self { - title: INPUT.overwrite_title.to_owned(), - position: Position::new(INPUT.overwrite_origin, INPUT.overwrite_offset), - ..Default::default() - } - } - - #[inline] - pub fn quit(n: usize) -> Self { - let title = INPUT.quit_title.replace("{n}", &n.to_string()); - Self { - title: title.replace("{s}", if n > 1 { "s" } else { "" }), - position: Position::new(INPUT.quit_origin, INPUT.quit_offset), - ..Default::default() - } - } - #[inline] pub fn with_value(mut self, value: impl Into) -> Self { self.value = value.into(); @@ -139,13 +104,69 @@ impl InputCfg { } } +impl ConfirmCfg { + fn new( + title: String, + (origin, offset): (Origin, Offset), + content: Option>, + list: Option>, + ) -> Self { + Self { + title, + position: Position::new(origin, offset), + content: content.map(|c| Paragraph::new(c).wrap(Wrap { trim: false })).unwrap_or_default(), + list: list.map(|l| Paragraph::new(l).wrap(Wrap { trim: false })).unwrap_or_default(), + } + } + + pub fn trash(urls: &[yazi_shared::fs::Url]) -> Self { + Self::new( + Self::replace_number(&CONFIRM.trash_title, urls.len(), usize::MAX), + (CONFIRM.trash_origin, CONFIRM.trash_offset), + None, + Some(urls.iter().map(ToString::to_string).collect()), + ) + } + + pub fn delete(urls: &[yazi_shared::fs::Url]) -> Self { + Self::new( + Self::replace_number(&CONFIRM.delete_title, urls.len(), usize::MAX), + (CONFIRM.delete_origin, CONFIRM.delete_offset), + None, + Some(urls.iter().map(ToString::to_string).collect()), + ) + } + + pub fn overwrite(url: &Url) -> Self { + Self::new( + CONFIRM.overwrite_title.to_owned(), + (CONFIRM.overwrite_origin, CONFIRM.overwrite_offset), + Some(Text::raw(&CONFIRM.overwrite_content)), + Some(url.to_string().into()), + ) + } + + pub fn quit(left: Vec) -> Self { + Self::new( + Self::replace_number(&CONFIRM.quit_title, left.len(), 10), + (CONFIRM.quit_origin, CONFIRM.quit_offset), + Some(Text::raw(&CONFIRM.quit_content)), + Some(left.into_iter().collect()), + ) + } + + fn replace_number(tpl: &str, n: usize, max: usize) -> String { + let s = tpl.replace("{s}", if n > 1 { "s" } else { "" }); + s.replace("{n}", &if n > max { format!("{max}+") } else { n.to_string() }) + } +} + impl SelectCfg { #[inline] fn max_height(len: usize) -> u16 { SELECT.open_offset.height.min(SELECT.border().saturating_add(len as u16)) } - #[inline] pub fn open(items: Vec) -> Self { let max_height = Self::max_height(items.len()); Self { diff --git a/yazi-config/src/popup/origin.rs b/yazi-config/src/popup/origin.rs index 70231c88..39ffa1d6 100644 --- a/yazi-config/src/popup/origin.rs +++ b/yazi-config/src/popup/origin.rs @@ -3,7 +3,7 @@ use std::{fmt::Display, str::FromStr}; use anyhow::bail; use serde::Deserialize; -#[derive(Clone, Copy, Default, Deserialize, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)] #[serde(try_from = "String")] pub enum Origin { #[default] diff --git a/yazi-config/src/popup/position.rs b/yazi-config/src/popup/position.rs index d8974947..b37a2e0a 100644 --- a/yazi-config/src/popup/position.rs +++ b/yazi-config/src/popup/position.rs @@ -3,7 +3,7 @@ use ratatui::layout::Rect; use super::{Offset, Origin}; -#[derive(Clone, Copy, Default)] +#[derive(Clone, Copy, Debug, Default)] pub struct Position { pub origin: Origin, pub offset: Offset, @@ -30,7 +30,7 @@ impl Position { let max_y = rows.saturating_sub(height); let new_y = match self.origin { TopLeft | TopCenter | TopRight => y.clamp(0, max_y as i16) as u16, - Center => (max_y / 2).saturating_sub(height / 2).saturating_add_signed(y).clamp(0, max_y), + Center => (rows / 2).saturating_sub(height / 2).saturating_add_signed(y).clamp(0, max_y), BottomLeft | BottomCenter | BottomRight => max_y.saturating_add_signed(y).clamp(0, max_y), Hovered => unreachable!(), }; diff --git a/yazi-core/src/confirm/commands/arrow.rs b/yazi-core/src/confirm/commands/arrow.rs new file mode 100644 index 00000000..fe5ae899 --- /dev/null +++ b/yazi-core/src/confirm/commands/arrow.rs @@ -0,0 +1,41 @@ +use yazi_shared::{event::{Cmd, Data}, render}; + +use crate::{confirm::Confirm, manager::Manager}; + +pub struct Opt { + step: isize, +} + +impl From for Opt { + fn from(c: Cmd) -> Self { Self { step: c.first().and_then(Data::as_isize).unwrap_or(0) } } +} + +impl Confirm { + fn next(&mut self, step: usize, width: u16) { + let height = self.list.line_count(width); + if height == 0 { + return; + } + + let old = self.offset; + self.offset = (self.offset + step).min(height - 1); + + render!(old != self.offset); + } + + fn prev(&mut self, step: usize) { + let old = self.offset; + self.offset -= step.min(self.offset); + + render!(old != self.offset); + } + + pub fn arrow(&mut self, opt: impl Into, manager: &Manager) { + let opt = opt.into() as Opt; + if opt.step > 0 { + self.next(opt.step as usize, manager.area(self.position).width) + } else { + self.prev(opt.step.unsigned_abs()) + } + } +} diff --git a/yazi-core/src/confirm/commands/close.rs b/yazi-core/src/confirm/commands/close.rs new file mode 100644 index 00000000..665b08ac --- /dev/null +++ b/yazi-core/src/confirm/commands/close.rs @@ -0,0 +1,26 @@ +use yazi_shared::{event::Cmd, render}; + +use crate::confirm::Confirm; + +pub struct Opt { + submit: bool, +} + +impl From for Opt { + fn from(c: Cmd) -> Self { Self { submit: c.bool("submit") } } +} +impl From for Opt { + fn from(submit: bool) -> Self { Self { submit } } +} + +impl Confirm { + pub fn close(&mut self, opt: impl Into) { + let opt = opt.into() as Opt; + if let Some(cb) = self.callback.take() { + _ = cb.send(opt.submit); + } + + self.visible = false; + render!(); + } +} diff --git a/yazi-core/src/confirm/commands/mod.rs b/yazi-core/src/confirm/commands/mod.rs new file mode 100644 index 00000000..d36c6ae8 --- /dev/null +++ b/yazi-core/src/confirm/commands/mod.rs @@ -0,0 +1,3 @@ +mod arrow; +mod close; +mod show; diff --git a/yazi-core/src/confirm/commands/show.rs b/yazi-core/src/confirm/commands/show.rs new file mode 100644 index 00000000..5ec84803 --- /dev/null +++ b/yazi-core/src/confirm/commands/show.rs @@ -0,0 +1,38 @@ +use tokio::sync::oneshot; +use yazi_config::popup::ConfirmCfg; +use yazi_shared::{event::Cmd, render}; + +use crate::confirm::Confirm; + +pub struct Opt { + cfg: ConfirmCfg, + tx: oneshot::Sender, +} + +impl TryFrom for Opt { + type Error = (); + + fn try_from(mut c: Cmd) -> Result { + Ok(Self { cfg: c.take_any("cfg").ok_or(())?, tx: c.take_any("tx").ok_or(())? }) + } +} + +impl Confirm { + pub fn show(&mut self, opt: impl TryInto) { + let Ok(opt) = opt.try_into() else { + return; + }; + + self.close(false); + self.title = opt.cfg.title; + self.content = opt.cfg.content; + self.list = opt.cfg.list; + + self.offset = 0; + self.position = opt.cfg.position; + + self.callback = Some(opt.tx); + self.visible = true; + render!(); + } +} diff --git a/yazi-core/src/confirm/confirm.rs b/yazi-core/src/confirm/confirm.rs new file mode 100644 index 00000000..fe7649c4 --- /dev/null +++ b/yazi-core/src/confirm/confirm.rs @@ -0,0 +1,16 @@ +use ratatui::widgets::Paragraph; +use tokio::sync::oneshot::Sender; +use yazi_config::popup::Position; + +#[derive(Default)] +pub struct Confirm { + pub title: String, + pub content: Paragraph<'static>, + pub list: Paragraph<'static>, + + pub offset: usize, + pub position: Position, + + pub(super) callback: Option>, + pub visible: bool, +} diff --git a/yazi-core/src/confirm/mod.rs b/yazi-core/src/confirm/mod.rs new file mode 100644 index 00000000..cebfd9fa --- /dev/null +++ b/yazi-core/src/confirm/mod.rs @@ -0,0 +1,4 @@ +mod commands; +mod confirm; + +pub use confirm::*; diff --git a/yazi-core/src/lib.rs b/yazi-core/src/lib.rs index 88bdc2b0..e8c1106d 100644 --- a/yazi-core/src/lib.rs +++ b/yazi-core/src/lib.rs @@ -7,6 +7,7 @@ )] pub mod completion; +pub mod confirm; pub mod help; pub mod input; pub mod manager; diff --git a/yazi-core/src/manager/commands/create.rs b/yazi-core/src/manager/commands/create.rs index a9e223ce..f13f0036 100644 --- a/yazi-core/src/manager/commands/create.rs +++ b/yazi-core/src/manager/commands/create.rs @@ -2,8 +2,8 @@ use std::collections::HashMap; use anyhow::Result; use tokio::fs; -use yazi_config::popup::InputCfg; -use yazi_proxy::{InputProxy, TabProxy, WATCHER}; +use yazi_config::popup::{ConfirmCfg, InputCfg}; +use yazi_proxy::{ConfirmProxy, InputProxy, TabProxy, WATCHER}; use yazi_shared::{event::Cmd, fs::{maybe_exists, ok_or_not_found, symlink_realpath, File, FilesOp, Url}}; use crate::manager::Manager; @@ -31,11 +31,11 @@ impl Manager { } let new = cwd.join(&name); - if !opt.force && maybe_exists(&new).await { - match InputProxy::show(InputCfg::overwrite()).recv().await { - Some(Ok(c)) if c == "y" || c == "Y" => (), - _ => return Ok(()), - } + if !opt.force + && maybe_exists(&new).await + && !ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await + { + return Ok(()); } Self::create_do(new, opt.dir || name.ends_with('/') || name.ends_with('\\')).await diff --git a/yazi-core/src/manager/commands/quit.rs b/yazi-core/src/manager/commands/quit.rs index d2691f2f..fbbdb929 100644 --- a/yazi-core/src/manager/commands/quit.rs +++ b/yazi-core/src/manager/commands/quit.rs @@ -1,8 +1,8 @@ use std::time::Duration; use tokio::{select, time}; -use yazi_config::popup::InputCfg; -use yazi_proxy::InputProxy; +use yazi_config::popup::ConfirmCfg; +use yazi_proxy::ConfirmProxy; use yazi_shared::{emit, event::{Cmd, EventQuit}}; use crate::{manager::Manager, tasks::Tasks}; @@ -23,16 +23,16 @@ impl Manager { let opt = EventQuit { no_cwd_file: opt.into().no_cwd_file, ..Default::default() }; let ongoing = tasks.ongoing().clone(); - let left = ongoing.lock().len(); + let left: Vec = ongoing.lock().values().take(11).map(|t| t.name.clone()).collect(); - if left == 0 { + if left.is_empty() { emit!(Quit(opt)); return; } tokio::spawn(async move { let mut i = 0; - let mut result = InputProxy::show(InputCfg::quit(left)); + let mut rx = ConfirmProxy::show_rx(ConfirmCfg::quit(left)); loop { select! { _ = time::sleep(Duration::from_millis(100)) => { @@ -43,8 +43,8 @@ impl Manager { return; } } - choice = result.recv() => { - if matches!(choice, Some(Ok(s)) if s == "y" || s == "Y") { + b = &mut rx => { + if b.unwrap_or(false) { emit!(Quit(opt)); } return; @@ -52,10 +52,8 @@ impl Manager { } } - if let Some(Ok(choice)) = result.recv().await { - if choice == "y" || choice == "Y" { - emit!(Quit(opt)); - } + if rx.await.unwrap_or(false) { + emit!(Quit(opt)); } }); } diff --git a/yazi-core/src/manager/commands/remove.rs b/yazi-core/src/manager/commands/remove.rs index e67c135d..af383722 100644 --- a/yazi-core/src/manager/commands/remove.rs +++ b/yazi-core/src/manager/commands/remove.rs @@ -1,5 +1,5 @@ -use yazi_config::popup::InputCfg; -use yazi_proxy::{InputProxy, ManagerProxy}; +use yazi_config::popup::ConfirmCfg; +use yazi_proxy::{ConfirmProxy, ManagerProxy}; use yazi_shared::{event::Cmd, fs::Url}; use crate::{manager::Manager, tasks::Tasks}; @@ -35,7 +35,7 @@ impl Manager { opt.targets = if opt.hovered { vec![hovered.clone()] } else { - self.selected_or_hovered(false).cloned().collect() + self.selected_or_hovered(true).cloned().collect() }; if opt.force { @@ -43,17 +43,13 @@ impl Manager { } tokio::spawn(async move { - let mut result = InputProxy::show(if opt.permanently { - InputCfg::delete(opt.targets.len()) + let result = ConfirmProxy::show(if opt.permanently { + ConfirmCfg::delete(&opt.targets) } else { - InputCfg::trash(opt.targets.len()) + ConfirmCfg::trash(&opt.targets) }); - if let Some(Ok(choice)) = result.recv().await { - if choice != "y" && choice != "Y" { - return; - } - + if result.await { ManagerProxy::remove_do(opt.targets, opt.permanently); } }); diff --git a/yazi-core/src/manager/commands/rename.rs b/yazi-core/src/manager/commands/rename.rs index 3fd5efc9..a15773c9 100644 --- a/yazi-core/src/manager/commands/rename.rs +++ b/yazi-core/src/manager/commands/rename.rs @@ -2,9 +2,9 @@ use std::collections::HashMap; use anyhow::Result; use tokio::fs; -use yazi_config::popup::InputCfg; +use yazi_config::popup::{ConfirmCfg, InputCfg}; use yazi_dds::Pubsub; -use yazi_proxy::{InputProxy, TabProxy, WATCHER}; +use yazi_proxy::{ConfirmProxy, InputProxy, TabProxy, WATCHER}; use yazi_shared::{event::Cmd, fs::{maybe_exists, ok_or_not_found, paths_to_same_file, symlink_realpath, File, FilesOp, Url}}; use crate::manager::Manager; @@ -64,18 +64,12 @@ impl Manager { return; } - let new = hovered.parent().unwrap().join(name); + let new = Url::from(hovered.parent().unwrap().join(name)); if opt.force || !maybe_exists(&new).await || paths_to_same_file(&hovered, &new).await { - Self::rename_do(tab, hovered, Url::from(new)).await.ok(); - return; + Self::rename_do(tab, hovered, new).await.ok(); + } else if ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await { + Self::rename_do(tab, hovered, new).await.ok(); } - - let mut result = InputProxy::show(InputCfg::overwrite()); - if let Some(Ok(choice)) = result.recv().await { - if choice == "y" || choice == "Y" { - Self::rename_do(tab, hovered, Url::from(new)).await.ok(); - } - }; }); } diff --git a/yazi-core/src/manager/manager.rs b/yazi-core/src/manager/manager.rs index 4214e1bf..0b5545f8 100644 --- a/yazi-core/src/manager/manager.rs +++ b/yazi-core/src/manager/manager.rs @@ -1,5 +1,8 @@ use std::collections::HashMap; +use ratatui::layout::Rect; +use yazi_adapter::Dimension; +use yazi_config::popup::{Origin, Position}; use yazi_fs::Folder; use yazi_shared::fs::{File, Url}; @@ -25,6 +28,14 @@ impl Manager { } } + pub fn area(&self, pos: Position) -> Rect { + if pos.origin == Origin::Hovered { + self.active().hovered_rect_based(pos) + } else { + pos.rect(Dimension::available()) + } + } + pub fn shutdown(&mut self) { self.tabs.iter_mut().for_each(|t| t.shutdown()); } } diff --git a/yazi-core/src/tab/tab.rs b/yazi-core/src/tab/tab.rs index c6c553bf..0df8a13a 100644 --- a/yazi-core/src/tab/tab.rs +++ b/yazi-core/src/tab/tab.rs @@ -1,7 +1,10 @@ use std::{collections::HashMap, iter}; use anyhow::Result; +use ratatui::layout::Rect; use tokio::task::JoinHandle; +use yazi_adapter::Dimension; +use yazi_config::{popup::{Origin, Position}, LAYOUT}; use yazi_fs::{Folder, FolderStage}; use yazi_shared::{fs::Url, render}; @@ -35,6 +38,24 @@ impl Tab { impl Tab { // --- Current + pub fn hovered_rect(&self) -> Option { + let y = self.current.files.position(&self.current.hovered()?.url)? - self.current.offset; + + let mut rect = LAYOUT.load().current; + rect.y = rect.y.saturating_sub(1) + y as u16; + rect.height = 1; + Some(rect) + } + + pub fn hovered_rect_based(&self, pos: Position) -> Rect { + let ws = Dimension::available(); + if let Some(r) = self.hovered_rect() { + Position::sticky(ws, r, pos.offset) + } else { + Position::new(Origin::TopCenter, pos.offset).rect(ws) + } + } + pub fn selected_or_hovered(&self, reorder: bool) -> Box + '_> { if self.selected.is_empty() { Box::new(self.current.hovered().map(|h| vec![&h.url]).unwrap_or_default().into_iter()) diff --git a/yazi-fm/src/completion/completion.rs b/yazi-fm/src/completion/completion.rs index 75d2b6bb..1ee5cadd 100644 --- a/yazi-fm/src/completion/completion.rs +++ b/yazi-fm/src/completion/completion.rs @@ -40,7 +40,7 @@ impl<'a> Widget for Completion<'a> { }) .collect(); - let input_area = self.cx.area(&self.cx.input.position); + let input_area = self.cx.manager.area(self.cx.input.position); let mut area = Position::sticky(Dimension::available(), input_area, Offset { x: 1, y: 0, diff --git a/yazi-fm/src/confirm/buttons.rs b/yazi-fm/src/confirm/buttons.rs new file mode 100644 index 00000000..13997eaf --- /dev/null +++ b/yazi-fm/src/confirm/buttons.rs @@ -0,0 +1,13 @@ +use ratatui::{buffer::Buffer, layout::{Constraint, Rect}, style::Stylize, text::Span, widgets::{Paragraph, Widget}}; + +pub(crate) struct Buttons; + +impl Widget for Buttons { + fn render(self, area: Rect, buf: &mut Buffer) { + let chunks = + ratatui::layout::Layout::horizontal([Constraint::Fill(1), Constraint::Fill(1)]).split(area); + + Paragraph::new(Span::raw(" [Y]es ").reversed()).centered().render(chunks[0], buf); + Paragraph::new(Span::raw(" (N)o ")).centered().render(chunks[1], buf); + } +} diff --git a/yazi-fm/src/confirm/confirm.rs b/yazi-fm/src/confirm/confirm.rs new file mode 100644 index 00000000..450a3f20 --- /dev/null +++ b/yazi-fm/src/confirm/confirm.rs @@ -0,0 +1,41 @@ +use ratatui::{buffer::Buffer, layout::{Alignment, Constraint, Layout, Margin, Rect}, style::{Style, Stylize}, text::Line, widgets::{Block, BorderType, Widget}}; + +use crate::Ctx; + +pub(crate) struct Confirm<'a> { + cx: &'a Ctx, +} + +impl<'a> Confirm<'a> { + pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } } +} + +impl<'a> Widget for Confirm<'a> { + fn render(self, _win: Rect, buf: &mut Buffer) { + let confirm = &self.cx.confirm; + let area = self.cx.manager.area(confirm.position); + + yazi_plugin::elements::Clear::default().render(area, buf); + + Block::bordered() + .border_type(BorderType::Rounded) + .border_style(Style::new().blue()) + .title(Line::styled(&confirm.title, Style::new().blue())) + .title_alignment(Alignment::Center) + .render(area, buf); + + let content = confirm.content.clone(); + let content_height = content.line_count(area.width).saturating_add(1) as u16; + + let chunks = Layout::vertical([ + Constraint::Length(if content_height == 1 { 0 } else { content_height }), + Constraint::Fill(1), + Constraint::Length(1), + ]) + .split(area.inner(Margin::new(0, 1))); + + super::Content::new(content).render(chunks[0], buf); + super::List::new(self.cx).render(chunks[1], buf); + super::Buttons.render(chunks[2], buf); + } +} diff --git a/yazi-fm/src/confirm/content.rs b/yazi-fm/src/confirm/content.rs new file mode 100644 index 00000000..c1a553d4 --- /dev/null +++ b/yazi-fm/src/confirm/content.rs @@ -0,0 +1,22 @@ +use ratatui::{buffer::Buffer, layout::{Margin, Rect}, style::{Style, Stylize}, widgets::{Block, Borders, Paragraph, Widget}}; + +pub(crate) struct Content<'a> { + p: Paragraph<'a>, +} + +impl<'a> Content<'a> { + pub(crate) fn new(p: Paragraph<'a>) -> Self { Self { p } } +} + +impl<'a> Widget for Content<'a> { + fn render(self, area: Rect, buf: &mut Buffer) { + // Content area + let inner = area.inner(Margin::new(1, 0)); + + // Bottom border + let block = Block::new().borders(Borders::BOTTOM).border_style(Style::new().blue()); + block.clone().render(area.inner(Margin::new(1, 0)), buf); + + self.p.alignment(ratatui::layout::Alignment::Center).block(block).render(inner, buf); + } +} diff --git a/yazi-fm/src/confirm/list.rs b/yazi-fm/src/confirm/list.rs new file mode 100644 index 00000000..22709c05 --- /dev/null +++ b/yazi-fm/src/confirm/list.rs @@ -0,0 +1,44 @@ +use ratatui::{buffer::Buffer, layout::{Margin, Rect}, style::{Style, Stylize}, widgets::{Block, Borders, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget, Widget, Wrap}}; + +use crate::Ctx; + +pub(crate) struct List<'a> { + cx: &'a Ctx, +} + +impl<'a> List<'a> { + pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } } +} + +impl<'a> Widget for List<'a> { + fn render(self, mut area: Rect, buf: &mut Buffer) { + // List content area + let inner = area.inner(Margin::new(2, 0)); + + // Bottom border + let block = Block::new().borders(Borders::BOTTOM).border_style(Style::new().blue()); + block.clone().render(area.inner(Margin::new(1, 0)), buf); + + let list = self + .cx + .confirm + .list + .clone() + .scroll((self.cx.confirm.offset as u16, 0)) + .block(block) + .wrap(Wrap { trim: false }); + + // Vertical scrollbar + let lines = list.line_count(inner.width); + if lines >= inner.height as usize { + area.height = area.height.saturating_sub(1); + Scrollbar::new(ScrollbarOrientation::VerticalRight).render( + area, + buf, + &mut ScrollbarState::new(lines).position(self.cx.confirm.offset), + ); + } + + list.render(inner, buf); + } +} diff --git a/yazi-fm/src/confirm/mod.rs b/yazi-fm/src/confirm/mod.rs new file mode 100644 index 00000000..567a133e --- /dev/null +++ b/yazi-fm/src/confirm/mod.rs @@ -0,0 +1,9 @@ +mod buttons; +mod confirm; +mod content; +mod list; + +use buttons::*; +pub(super) use confirm::*; +use content::*; +use list::*; diff --git a/yazi-fm/src/context.rs b/yazi-fm/src/context.rs index 0fec1dd8..c97336e2 100644 --- a/yazi-fm/src/context.rs +++ b/yazi-fm/src/context.rs @@ -1,13 +1,12 @@ use ratatui::layout::Rect; -use yazi_adapter::Dimension; -use yazi_config::popup::{Origin, Position}; -use yazi_core::{completion::Completion, help::Help, input::Input, manager::Manager, notify::Notify, select::Select, tasks::Tasks, which::Which}; +use yazi_core::{completion::Completion, confirm::Confirm, help::Help, input::Input, manager::Manager, notify::Notify, select::Select, tasks::Tasks, which::Which}; pub struct Ctx { pub manager: Manager, pub tasks: Tasks, pub select: Select, pub input: Input, + pub confirm: Confirm, pub help: Help, pub completion: Completion, pub which: Which, @@ -21,6 +20,7 @@ impl Ctx { tasks: Tasks::serve(), select: Default::default(), input: Default::default(), + confirm: Default::default(), help: Default::default(), completion: Default::default(), which: Default::default(), @@ -28,25 +28,10 @@ impl Ctx { } } - pub fn area(&self, position: &Position) -> Rect { - let ws = Dimension::available(); - if position.origin != Origin::Hovered { - return position.rect(ws); - } - - if let Some(r) = - self.manager.hovered().and_then(|h| self.manager.current().rect_current(&h.url)) - { - Position::sticky(ws, r, position.offset) - } else { - Position::new(Origin::TopCenter, position.offset).rect(ws) - } - } - #[inline] pub fn cursor(&self) -> Option<(u16, u16)> { if self.input.visible { - let Rect { x, y, .. } = self.area(&self.input.position); + let Rect { x, y, .. } = self.manager.area(self.input.position); return Some((x + 1 + self.input.cursor(), y + 1)); } if let Some((x, y)) = self.help.cursor() { diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index b2b6fc02..5f2bc339 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -19,6 +19,7 @@ impl<'a> Executor<'a> { Layer::Tasks => self.tasks(cmd), Layer::Select => self.select(cmd), Layer::Input => self.input(cmd), + Layer::Confirm => self.confirm(cmd), Layer::Help => self.help(cmd), Layer::Completion => self.completion(cmd), Layer::Which => self.which(cmd), @@ -249,6 +250,20 @@ impl<'a> Executor<'a> { } } + fn confirm(&mut self, cmd: Cmd) { + macro_rules! on { + ($name:ident $(,$args:expr)*) => { + if cmd.name == stringify!($name) { + return self.app.cx.confirm.$name(cmd, $($args),*); + } + }; + } + + on!(arrow, &self.app.cx.manager); + on!(show); + on!(close); + } + fn help(&mut self, cmd: Cmd) { macro_rules! on { ($name:ident) => { diff --git a/yazi-fm/src/input/input.rs b/yazi-fm/src/input/input.rs index 9a9ee778..f82dea06 100644 --- a/yazi-fm/src/input/input.rs +++ b/yazi-fm/src/input/input.rs @@ -34,7 +34,7 @@ impl<'a> Input<'a> { impl<'a> Widget for Input<'a> { fn render(self, win: Rect, buf: &mut Buffer) { let input = &self.cx.input; - let area = self.cx.area(&input.position); + let area = self.cx.manager.area(input.position); yazi_plugin::elements::Clear::default().render(area, buf); Paragraph::new(self.highlighted_value().unwrap_or_else(|_| Line::from(input.value()))) diff --git a/yazi-fm/src/main.rs b/yazi-fm/src/main.rs index 86363ab6..8bf39235 100644 --- a/yazi-fm/src/main.rs +++ b/yazi-fm/src/main.rs @@ -8,6 +8,7 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; mod app; mod completion; mod components; +mod confirm; mod context; mod executor; mod help; diff --git a/yazi-fm/src/root.rs b/yazi-fm/src/root.rs index 684e997f..d119b7be 100644 --- a/yazi-fm/src/root.rs +++ b/yazi-fm/src/root.rs @@ -3,7 +3,7 @@ use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget}; use tracing::error; use yazi_plugin::{bindings::Cast, elements::render_widgets, LUA}; -use super::{completion, input, select, tasks, which}; +use super::{completion, confirm, input, select, tasks, which}; use crate::{components, help, Ctx}; pub(super) struct Root<'a> { @@ -41,6 +41,10 @@ impl<'a> Widget for Root<'a> { input::Input::new(self.cx).render(area, buf); } + if self.cx.confirm.visible { + confirm::Confirm::new(self.cx).render(area, buf); + } + if self.cx.help.visible { help::Layout::new(self.cx).render(area, buf); } diff --git a/yazi-fm/src/router.rs b/yazi-fm/src/router.rs index 84db76a4..f6079a69 100644 --- a/yazi-fm/src/router.rs +++ b/yazi-fm/src/router.rs @@ -31,6 +31,8 @@ impl<'a> Router<'a> { self.matches(Layer::Help, key) } else if cx.input.visible { self.matches(Layer::Input, key) + } else if cx.confirm.visible { + self.matches(Layer::Confirm, key) } else if cx.select.visible { self.matches(Layer::Select, key) } else if cx.tasks.visible { diff --git a/yazi-fm/src/select/select.rs b/yazi-fm/src/select/select.rs index 9bbf2997..2fc04a45 100644 --- a/yazi-fm/src/select/select.rs +++ b/yazi-fm/src/select/select.rs @@ -14,7 +14,7 @@ impl<'a> Select<'a> { impl<'a> Widget for Select<'a> { fn render(self, _: Rect, buf: &mut Buffer) { let select = &self.cx.select; - let area = self.cx.area(&select.position); + let area = self.cx.manager.area(select.position); let items: Vec<_> = select .window() diff --git a/yazi-fs/src/folder.rs b/yazi-fs/src/folder.rs index 8b8380fe..d6be6652 100644 --- a/yazi-fs/src/folder.rs +++ b/yazi-fs/src/folder.rs @@ -1,6 +1,5 @@ use std::mem; -use ratatui::layout::Rect; use yazi_config::{LAYOUT, MANAGER}; use yazi_proxy::ManagerProxy; use yazi_shared::fs::{Cha, File, FilesOp, Url}; @@ -151,13 +150,4 @@ impl Folder { let end = ((page + 2) * limit).min(len); &self.files[start..end] } - - pub fn rect_current(&self, url: &Url) -> Option { - let y = self.files.position(url)? - self.offset; - - let mut rect = LAYOUT.load().current; - rect.y = rect.y.saturating_sub(1) + y as u16; - rect.height = 1; - Some(rect) - } } diff --git a/yazi-plugin/src/bindings/position.rs b/yazi-plugin/src/bindings/position.rs index 935fbc28..e5151bda 100644 --- a/yazi-plugin/src/bindings/position.rs +++ b/yazi-plugin/src/bindings/position.rs @@ -26,7 +26,7 @@ impl<'a> TryFrom> for Position { x: t.raw_get("x").unwrap_or_default(), y: t.raw_get("y").unwrap_or_default(), width: t.raw_get("w")?, - height: 3, + height: t.raw_get("h").unwrap_or(3), }, })) } diff --git a/yazi-plugin/src/utils/layer.rs b/yazi-plugin/src/utils/layer.rs index 2ec96b91..3af6c71d 100644 --- a/yazi-plugin/src/utils/layer.rs +++ b/yazi-plugin/src/utils/layer.rs @@ -86,6 +86,19 @@ impl Utils { })?, )?; + // TODO: redesign the confirm API + // ya.raw_set( + // "confirm", + // lua.create_async_function(|_, t: Table| async move { + // let result = ConfirmProxy::show(ConfirmCfg { + // title: t.raw_get("title")?, + // content: t.raw_get("content")?, + // position: Position::try_from(t.raw_get::<_, Table>("position")?)?.into(), + // }); + // Ok(result.await) + // })?, + // )?; + ya.raw_set( "notify", lua.create_function(|_, t: Table| { diff --git a/yazi-proxy/src/confirm.rs b/yazi-proxy/src/confirm.rs new file mode 100644 index 00000000..60ecd93a --- /dev/null +++ b/yazi-proxy/src/confirm.rs @@ -0,0 +1,17 @@ +use tokio::sync::oneshot; +use yazi_config::popup::ConfirmCfg; +use yazi_shared::{emit, event::Cmd, Layer}; + +pub struct ConfirmProxy; + +impl ConfirmProxy { + #[inline] + pub async fn show(cfg: ConfirmCfg) -> bool { Self::show_rx(cfg).await.unwrap_or(false) } + + #[inline] + pub fn show_rx(cfg: ConfirmCfg) -> oneshot::Receiver { + let (tx, rx) = oneshot::channel(); + emit!(Call(Cmd::new("show").with_any("tx", tx).with_any("cfg", cfg), Layer::Confirm)); + rx + } +} diff --git a/yazi-proxy/src/lib.rs b/yazi-proxy/src/lib.rs index 6a0687fe..e87d6b60 100644 --- a/yazi-proxy/src/lib.rs +++ b/yazi-proxy/src/lib.rs @@ -1,5 +1,6 @@ mod app; mod completion; +mod confirm; mod input; mod manager; pub mod options; @@ -10,6 +11,7 @@ mod tasks; pub use app::*; pub use completion::*; +pub use confirm::*; pub use input::*; pub use manager::*; pub use select::*; diff --git a/yazi-shared/src/layer.rs b/yazi-shared/src/layer.rs index ea549571..525fc051 100644 --- a/yazi-shared/src/layer.rs +++ b/yazi-shared/src/layer.rs @@ -10,6 +10,7 @@ pub enum Layer { Tasks, Select, Input, + Confirm, Help, Completion, Which, @@ -23,6 +24,7 @@ impl Display for Layer { Self::Tasks => "tasks", Self::Select => "select", Self::Input => "input", + Self::Confirm => "confirm", Self::Help => "help", Self::Completion => "completion", Self::Which => "which", @@ -40,6 +42,7 @@ impl FromStr for Layer { "tasks" => Self::Tasks, "select" => Self::Select, "input" => Self::Input, + "confirm" => Self::Confirm, "help" => Self::Help, "completion" => Self::Completion, "which" => Self::Which,