mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: introduce confirm popup
Confirm popup that takes a '\n' separated string message renders a popup requesting user for confirmation The message is displayed as a Paragraph with vertical scrollbar (if needed) - Lua binding: ya.confrm(title, message, position) - Position::try_from now parses "h" value (default 3)
This commit is contained in:
parent
b5b6c9642a
commit
4008ca2e04
29 changed files with 423 additions and 54 deletions
|
|
@ -263,6 +263,27 @@ keymap = [
|
|||
{ on = [ "~" ], run = "help", desc = "Open help" }
|
||||
]
|
||||
|
||||
[confirm]
|
||||
keymap = [
|
||||
{ on = [ "<Esc>" ], run = "close", desc = "Cancel 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 = [ "K" ], run = "arrow -5", desc = "Move cursor up 5 lines" },
|
||||
{ on = [ "J" ], run = "arrow 5", desc = "Move cursor down 5 lines" },
|
||||
|
||||
{ on = [ "<Up>" ], run = "arrow -1", desc = "Move cursor up" },
|
||||
{ on = [ "<Down>" ], run = "arrow 1", desc = "Move cursor down" },
|
||||
|
||||
{ on = [ "<S-Up>" ], run = "arrow -5", desc = "Move cursor up 5 lines" },
|
||||
{ on = [ "<S-Down>" ], run = "arrow 5", desc = "Move cursor down 5 lines" },
|
||||
|
||||
{ on = [ "~" ], run = "help", desc = "Open help" }
|
||||
]
|
||||
|
||||
[completion]
|
||||
|
||||
keymap = [
|
||||
|
|
|
|||
|
|
@ -139,16 +139,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"
|
||||
|
|
@ -179,6 +169,17 @@ quit_title = "{n} task{s} running, sure to quit? (y/N)"
|
|||
quit_origin = "top-center"
|
||||
quit_offset = [ 0, 2, 50, 3 ]
|
||||
|
||||
[confirm]
|
||||
# trash
|
||||
trash_title = "Move {n} selected file{s} to trash? (y/N)"
|
||||
trash_origin = "top-center"
|
||||
trash_offset = [ 0, 5, 70, 20 ]
|
||||
|
||||
# delete
|
||||
delete_title = "Delete {n} selected file{s} permanently? (y/N)"
|
||||
delete_origin = "top-center"
|
||||
delete_offset = [ 0, 5, 70, 20 ]
|
||||
|
||||
[select]
|
||||
open_title = "Open with:"
|
||||
open_origin = "hovered"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ pub struct Keymap {
|
|||
pub tasks: Vec<Control>,
|
||||
pub select: Vec<Control>,
|
||||
pub input: Vec<Control>,
|
||||
pub confirm: Vec<Control>,
|
||||
pub help: Vec<Control>,
|
||||
pub completion: Vec<Control>,
|
||||
}
|
||||
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ pub static PREVIEW: RoCell<preview::Preview> = RoCell::new();
|
|||
pub static TASKS: RoCell<tasks::Tasks> = RoCell::new();
|
||||
pub static THEME: RoCell<theme::Theme> = RoCell::new();
|
||||
pub static INPUT: RoCell<popup::Input> = RoCell::new();
|
||||
pub static CONFIRM: RoCell<popup::Confirm> = RoCell::new();
|
||||
pub static SELECT: RoCell<popup::Select> = RoCell::new();
|
||||
pub static WHICH: RoCell<which::Which> = 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)?);
|
||||
|
||||
|
|
|
|||
33
yazi-config/src/popup/confirm.rs
Normal file
33
yazi-config/src/popup/confirm.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
use super::{Offset, Origin};
|
||||
use crate::MERGED_YAZI;
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
impl Default for Confirm {
|
||||
fn default() -> Self {
|
||||
#[derive(Deserialize)]
|
||||
struct Outer {
|
||||
confirm: Confirm,
|
||||
}
|
||||
|
||||
toml::from_str::<Outer>(&MERGED_YAZI).unwrap().confirm
|
||||
}
|
||||
}
|
||||
|
||||
impl Confirm {
|
||||
#[inline]
|
||||
pub const fn border(&self) -> u16 { 2 }
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use super::{Offset, Position};
|
||||
use crate::{INPUT, SELECT};
|
||||
use crate::{CONFIRM, INPUT, SELECT};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InputCfg {
|
||||
|
|
@ -19,6 +19,13 @@ pub struct SelectCfg {
|
|||
pub position: Position,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ConfirmCfg {
|
||||
pub title: String,
|
||||
pub message: String,
|
||||
pub position: Position,
|
||||
}
|
||||
|
||||
impl InputCfg {
|
||||
#[inline]
|
||||
pub fn cd() -> Self {
|
||||
|
|
@ -48,26 +55,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 {
|
||||
|
|
@ -139,6 +126,26 @@ impl InputCfg {
|
|||
}
|
||||
}
|
||||
|
||||
impl ConfirmCfg {
|
||||
#[inline]
|
||||
pub fn delete(targets: &[yazi_shared::fs::Url]) -> Self {
|
||||
Self {
|
||||
title: CONFIRM.delete_title.replace("{n}", &targets.len().to_string()),
|
||||
position: Position::new(CONFIRM.delete_origin, CONFIRM.delete_offset),
|
||||
message: targets.iter().map(|t| t.to_string()).collect::<Vec<_>>().join("\n"),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn trash(targets: &[yazi_shared::fs::Url]) -> Self {
|
||||
Self {
|
||||
title: CONFIRM.trash_title.replace("{n}", &targets.len().to_string()),
|
||||
position: Position::new(CONFIRM.trash_origin, CONFIRM.trash_offset),
|
||||
message: targets.iter().map(|t| t.to_string()).collect::<Vec<_>>().join("\n"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectCfg {
|
||||
#[inline]
|
||||
fn max_height(len: usize) -> u16 {
|
||||
|
|
|
|||
38
yazi-core/src/confirm/commands/arrow.rs
Normal file
38
yazi-core/src/confirm/commands/arrow.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use yazi_shared::{event::{Cmd, Data}, render};
|
||||
|
||||
use crate::confirm::Confirm;
|
||||
|
||||
pub struct Opt {
|
||||
step: isize,
|
||||
}
|
||||
|
||||
impl From<Cmd> 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) {
|
||||
let len = self.message_num_lines();
|
||||
if len == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let old = self.vertical_scroll;
|
||||
self.vertical_scroll = (self.vertical_scroll + step).min(len - 1);
|
||||
|
||||
render!(old != self.vertical_scroll);
|
||||
}
|
||||
|
||||
fn prev(&mut self, step: usize) {
|
||||
let old = self.vertical_scroll;
|
||||
|
||||
self.vertical_scroll -= step.min(self.vertical_scroll);
|
||||
|
||||
render!(old != self.vertical_scroll);
|
||||
}
|
||||
|
||||
pub fn arrow(&mut self, opt: impl Into<Opt>) {
|
||||
let opt = opt.into() as Opt;
|
||||
if opt.step > 0 { self.next(opt.step as usize) } else { self.prev(opt.step.unsigned_abs()) }
|
||||
}
|
||||
}
|
||||
27
yazi-core/src/confirm/commands/close.rs
Normal file
27
yazi-core/src/confirm/commands/close.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use anyhow::anyhow;
|
||||
use yazi_shared::{event::Cmd, render};
|
||||
|
||||
use crate::confirm::Confirm;
|
||||
|
||||
pub struct Opt {
|
||||
submit: bool,
|
||||
}
|
||||
|
||||
impl From<Cmd> for Opt {
|
||||
fn from(c: Cmd) -> Self { Self { submit: c.bool("submit") } }
|
||||
}
|
||||
impl From<bool> for Opt {
|
||||
fn from(submit: bool) -> Self { Self { submit } }
|
||||
}
|
||||
|
||||
impl Confirm {
|
||||
pub fn close(&mut self, opt: impl Into<Opt>) {
|
||||
let opt = opt.into() as Opt;
|
||||
if let Some(cb) = self.callback.take() {
|
||||
_ = cb.send(if opt.submit { Ok(true) } else { Err(anyhow!("canceled")) });
|
||||
}
|
||||
|
||||
self.visible = false;
|
||||
render!();
|
||||
}
|
||||
}
|
||||
3
yazi-core/src/confirm/commands/mod.rs
Normal file
3
yazi-core/src/confirm/commands/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
mod arrow;
|
||||
mod close;
|
||||
mod show;
|
||||
36
yazi-core/src/confirm/commands/show.rs
Normal file
36
yazi-core/src/confirm/commands/show.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
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<anyhow::Result<bool>>,
|
||||
}
|
||||
|
||||
impl TryFrom<Cmd> for Opt {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
|
||||
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<Opt>) {
|
||||
let Ok(opt) = opt.try_into() else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.close(false);
|
||||
self.title = opt.cfg.title;
|
||||
self.set_message(&opt.cfg.message);
|
||||
self.vertical_scroll = 0;
|
||||
|
||||
self.position = opt.cfg.position;
|
||||
self.callback = Some(opt.tx);
|
||||
self.visible = true;
|
||||
render!();
|
||||
}
|
||||
}
|
||||
34
yazi-core/src/confirm/confirm.rs
Normal file
34
yazi-core/src/confirm/confirm.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use anyhow::Result;
|
||||
use tokio::sync::oneshot::Sender;
|
||||
use yazi_config::popup::Position;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Confirm {
|
||||
pub(super) title: String,
|
||||
message: String,
|
||||
message_num_lines: usize,
|
||||
|
||||
pub position: Position,
|
||||
|
||||
pub vertical_scroll: usize,
|
||||
pub(super) callback: Option<Sender<Result<bool>>>,
|
||||
|
||||
pub visible: bool,
|
||||
}
|
||||
|
||||
impl Confirm {
|
||||
#[inline]
|
||||
pub fn set_message(&mut self, message: &str) {
|
||||
self.message = message.to_string();
|
||||
self.message_num_lines = self.message.split('\n').count();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn message(&self) -> String { self.message.clone() }
|
||||
|
||||
#[inline]
|
||||
pub fn message_num_lines(&self) -> usize { self.message_num_lines }
|
||||
|
||||
#[inline]
|
||||
pub fn title(&self) -> String { self.title.clone() }
|
||||
}
|
||||
4
yazi-core/src/confirm/mod.rs
Normal file
4
yazi-core/src/confirm/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
mod commands;
|
||||
mod confirm;
|
||||
|
||||
pub use confirm::*;
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
)]
|
||||
|
||||
pub mod completion;
|
||||
pub mod confirm;
|
||||
pub mod folder;
|
||||
pub mod help;
|
||||
pub mod input;
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
@ -27,21 +27,21 @@ impl Manager {
|
|||
}
|
||||
|
||||
let mut opt = opt.into() as Opt;
|
||||
opt.targets = self.selected_or_hovered(false).cloned().collect();
|
||||
opt.targets = self.selected_or_hovered(true).cloned().collect();
|
||||
|
||||
if opt.force {
|
||||
return self.remove_do(opt, tasks);
|
||||
}
|
||||
|
||||
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" {
|
||||
if let Ok(choice) = result.await {
|
||||
if !choice {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
64
yazi-fm/src/confirm/confirm.rs
Normal file
64
yazi-fm/src/confirm/confirm.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
use ratatui::{buffer::Buffer, layout::{Constraint, Layout, Margin, Rect}, text::Line, widgets::{Block, BorderType, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget, Widget, Wrap}};
|
||||
use yazi_config::THEME;
|
||||
|
||||
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.area(&confirm.position);
|
||||
|
||||
yazi_plugin::elements::Clear::default().render(area, buf);
|
||||
|
||||
Block::bordered()
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(THEME.input.border)
|
||||
.title(Line::styled(&confirm.title(), THEME.input.title))
|
||||
.render(area, buf);
|
||||
|
||||
let popup_layout =
|
||||
Layout::vertical(vec![Constraint::Percentage(70), Constraint::Percentage(30)])
|
||||
.vertical_margin(1)
|
||||
.horizontal_margin(2)
|
||||
.split(area);
|
||||
|
||||
let button_layout = Layout::horizontal(vec![
|
||||
Constraint::Percentage(10),
|
||||
Constraint::Percentage(30),
|
||||
Constraint::Percentage(20),
|
||||
Constraint::Percentage(30),
|
||||
Constraint::Percentage(10),
|
||||
])
|
||||
.vertical_margin(1)
|
||||
.split(popup_layout[1]);
|
||||
|
||||
Paragraph::new(confirm.message().split('\n').map(Line::from).collect::<Vec<Line>>())
|
||||
.block(Block::bordered().border_type(BorderType::Rounded).border_style(THEME.input.border))
|
||||
.scroll((confirm.vertical_scroll as u16, 0))
|
||||
.wrap(Wrap { trim: false })
|
||||
.render(popup_layout[0], buf);
|
||||
|
||||
const BORDER_SIZE: usize = 2;
|
||||
if confirm.message_num_lines() > popup_layout[0].as_size().height as usize - BORDER_SIZE {
|
||||
let mut scrollbar_state =
|
||||
ScrollbarState::new(confirm.message().split('\n').collect::<Vec<&str>>().len())
|
||||
.position(confirm.vertical_scroll);
|
||||
|
||||
Scrollbar::new(ScrollbarOrientation::VerticalRight).render(
|
||||
popup_layout[0].inner(&Margin { vertical: 1, horizontal: 0 }),
|
||||
buf,
|
||||
&mut scrollbar_state,
|
||||
);
|
||||
}
|
||||
|
||||
Paragraph::new("[Y]es").block(Block::bordered()).centered().render(button_layout[1], buf);
|
||||
Paragraph::new("(N)o").block(Block::bordered()).centered().render(button_layout[3], buf);
|
||||
}
|
||||
}
|
||||
3
yazi-fm/src/confirm/mod.rs
Normal file
3
yazi-fm/src/confirm/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
mod confirm;
|
||||
|
||||
pub(super) use confirm::*;
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
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 +22,7 @@ impl Ctx {
|
|||
tasks: Tasks::serve(),
|
||||
select: Default::default(),
|
||||
input: Default::default(),
|
||||
confirm: Default::default(),
|
||||
help: Default::default(),
|
||||
completion: Default::default(),
|
||||
which: Default::default(),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
@ -246,6 +247,20 @@ impl<'a> Executor<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn confirm(&mut self, cmd: Cmd) {
|
||||
macro_rules! on {
|
||||
($name:ident) => {
|
||||
if cmd.name == stringify!($name) {
|
||||
return self.app.cx.confirm.$name(cmd);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
on!(arrow);
|
||||
on!(show);
|
||||
on!(close);
|
||||
}
|
||||
|
||||
fn help(&mut self, cmd: Cmd) {
|
||||
macro_rules! on {
|
||||
($name:ident) => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use mlua::{Table, TableExt};
|
|||
use ratatui::{buffer::Buffer, layout::{Constraint, Layout, Rect}, widgets::Widget};
|
||||
use yazi_plugin::{bindings::{Cast, MouseEvent}, 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> {
|
||||
|
|
@ -37,6 +37,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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
37
yazi-plugin/src/bindings/confirm.rs
Normal file
37
yazi-plugin/src/bindings/confirm.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
use std::pin::Pin;
|
||||
|
||||
use mlua::{prelude::LuaUserDataMethods, UserData};
|
||||
use tokio::pin;
|
||||
use tokio_stream::StreamExt;
|
||||
use yazi_shared::InputError;
|
||||
|
||||
pub struct ConfirmRx<T: StreamExt<Item = Result<String, InputError>>> {
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T: StreamExt<Item = Result<String, InputError>>> InputRx<T> {
|
||||
pub fn new(inner: T) -> Self { Self { inner } }
|
||||
|
||||
pub async fn consume(inner: T) -> (Option<String>, u8) {
|
||||
pin!(inner);
|
||||
inner.next().await.map(Self::parse).unwrap_or((None, 0))
|
||||
}
|
||||
|
||||
fn parse(res: Result<String, InputError>) -> (Option<String>, u8) {
|
||||
match res {
|
||||
Ok(s) => (Some(s), 1),
|
||||
Err(InputError::Canceled(s)) => (Some(s), 2),
|
||||
Err(InputError::Typed(s)) => (Some(s), 3),
|
||||
_ => (None, 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: StreamExt<Item = Result<String, InputError>> + 'static> UserData for InputRx<T> {
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_async_method_mut("recv", |_, me, ()| async move {
|
||||
let mut inner = unsafe { Pin::new_unchecked(&mut me.inner) };
|
||||
Ok(inner.next().await.map(Self::parse).unwrap_or((None, 0)))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ impl<'a> TryFrom<mlua::Table<'a>> 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),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ use std::{str::FromStr, time::Duration};
|
|||
use mlua::{ExternalError, ExternalResult, IntoLuaMulti, Lua, Table, Value};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
use yazi_config::{keymap::{Control, Key}, popup::InputCfg};
|
||||
use yazi_proxy::{AppProxy, InputProxy};
|
||||
use yazi_config::{keymap::{Control, Key}, popup::{ConfirmCfg, InputCfg}};
|
||||
use yazi_proxy::{AppProxy, ConfirmProxy, InputProxy};
|
||||
use yazi_shared::{emit, event::Cmd, Debounce, Layer};
|
||||
|
||||
use super::Utils;
|
||||
|
|
@ -86,6 +86,23 @@ impl Utils {
|
|||
})?,
|
||||
)?;
|
||||
|
||||
ya.raw_set(
|
||||
"confirm",
|
||||
lua.create_async_function(|lua, t: Table| async move {
|
||||
let result = ConfirmProxy::show(ConfirmCfg {
|
||||
title: t.raw_get("title")?,
|
||||
message: t.raw_get("message")?,
|
||||
position: Position::try_from(t.raw_get::<_, Table>("position")?)?.into(),
|
||||
});
|
||||
|
||||
if let Ok(_answer) = result.await {
|
||||
true.into_lua_multi(lua)
|
||||
} else {
|
||||
false.into_lua_multi(lua)
|
||||
}
|
||||
})?,
|
||||
)?;
|
||||
|
||||
ya.raw_set(
|
||||
"notify",
|
||||
lua.create_function(|_, t: Table| {
|
||||
|
|
|
|||
14
yazi-proxy/src/confirm.rs
Normal file
14
yazi-proxy/src/confirm.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
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) -> anyhow::Result<bool> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
emit!(Call(Cmd::new("show").with_any("tx", tx).with_any("cfg", cfg), Layer::Confirm));
|
||||
rx.await?
|
||||
}
|
||||
}
|
||||
|
|
@ -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::*;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue