mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: new confirm component (#1167)
Co-authored-by: sxyazi <sxyazi@gmail.com>
This commit is contained in:
parent
573ca5287f
commit
92112de1c0
45 changed files with 559 additions and 167 deletions
|
|
@ -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" ] }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ keymap = [
|
|||
|
||||
keymap = [
|
||||
{ on = "<C-c>", run = "close", desc = "Cancel input" },
|
||||
{ on = "<Enter>", run = "close --submit", desc = "Submit the input" },
|
||||
{ on = "<Enter>", run = "close --submit", desc = "Submit input" },
|
||||
{ on = "<Esc>", run = "escape", desc = "Go back the normal mode, or cancel input" },
|
||||
{ on = "<C-[>", run = "escape", desc = "Go back the normal mode, or cancel input" },
|
||||
|
||||
|
|
@ -262,6 +262,27 @@ keymap = [
|
|||
{ on = "<F1>", run = "help", desc = "Open help" },
|
||||
]
|
||||
|
||||
[confirm]
|
||||
keymap = [
|
||||
{ on = "<Esc>", run = "close", desc = "Cancel the confirm" },
|
||||
{ on = "<C-[>", run = "close", desc = "Cancel the confirm" },
|
||||
{ on = "<C-c>", run = "close", desc = "Cancel the confirm" },
|
||||
{ on = "<Enter>", 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 = "<Up>", run = "arrow -1", desc = "Move cursor up" },
|
||||
{ on = "<Down>", run = "arrow 1", desc = "Move cursor down" },
|
||||
|
||||
# Help
|
||||
{ on = "~", run = "help", desc = "Open help" },
|
||||
{ on = "<F1>", run = "help", desc = "Open help" },
|
||||
]
|
||||
|
||||
[completion]
|
||||
|
||||
keymap = [
|
||||
|
|
|
|||
|
|
@ -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:"
|
||||
|
|
|
|||
|
|
@ -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)?);
|
||||
|
||||
|
|
|
|||
47
yazi-config/src/popup/confirm.rs
Normal file
47
yazi-config/src/popup/confirm.rs
Normal file
|
|
@ -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<Self, Self::Err> {
|
||||
#[derive(Deserialize)]
|
||||
struct Outer {
|
||||
confirm: Confirm,
|
||||
}
|
||||
|
||||
Ok(toml::from_str::<Outer>(s)?.confirm)
|
||||
}
|
||||
}
|
||||
|
||||
impl Confirm {
|
||||
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,
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,7 +1,7 @@
|
|||
use anyhow::bail;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Copy, Default, Deserialize)]
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize)]
|
||||
#[serde(try_from = "Vec<i16>")]
|
||||
pub struct Offset {
|
||||
pub x: i16,
|
||||
|
|
|
|||
|
|
@ -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<String>) -> Self {
|
||||
self.value = value.into();
|
||||
|
|
@ -139,13 +104,69 @@ impl InputCfg {
|
|||
}
|
||||
}
|
||||
|
||||
impl ConfirmCfg {
|
||||
fn new(
|
||||
title: String,
|
||||
(origin, offset): (Origin, Offset),
|
||||
content: Option<Text<'static>>,
|
||||
list: Option<Text<'static>>,
|
||||
) -> 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<String>) -> 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<String>) -> Self {
|
||||
let max_height = Self::max_height(items.len());
|
||||
Self {
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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!(),
|
||||
};
|
||||
|
|
|
|||
41
yazi-core/src/confirm/commands/arrow.rs
Normal file
41
yazi-core/src/confirm/commands/arrow.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
use yazi_shared::{event::{Cmd, Data}, render};
|
||||
|
||||
use crate::{confirm::Confirm, manager::Manager};
|
||||
|
||||
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, 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<Opt>, 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
26
yazi-core/src/confirm/commands/close.rs
Normal file
26
yazi-core/src/confirm/commands/close.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
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(opt.submit);
|
||||
}
|
||||
|
||||
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;
|
||||
38
yazi-core/src/confirm/commands/show.rs
Normal file
38
yazi-core/src/confirm/commands/show.rs
Normal file
|
|
@ -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<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.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!();
|
||||
}
|
||||
}
|
||||
16
yazi-core/src/confirm/confirm.rs
Normal file
16
yazi-core/src/confirm/confirm.rs
Normal file
|
|
@ -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<Sender<bool>>,
|
||||
pub visible: bool,
|
||||
}
|
||||
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 help;
|
||||
pub mod input;
|
||||
pub mod manager;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<String> = 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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()); }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Rect> {
|
||||
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<dyn Iterator<Item = &Url> + '_> {
|
||||
if self.selected.is_empty() {
|
||||
Box::new(self.current.hovered().map(|h| vec![&h.url]).unwrap_or_default().into_iter())
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
13
yazi-fm/src/confirm/buttons.rs
Normal file
13
yazi-fm/src/confirm/buttons.rs
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
41
yazi-fm/src/confirm/confirm.rs
Normal file
41
yazi-fm/src/confirm/confirm.rs
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
22
yazi-fm/src/confirm/content.rs
Normal file
22
yazi-fm/src/confirm/content.rs
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
44
yazi-fm/src/confirm/list.rs
Normal file
44
yazi-fm/src/confirm/list.rs
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
9
yazi-fm/src/confirm/mod.rs
Normal file
9
yazi-fm/src/confirm/mod.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
mod buttons;
|
||||
mod confirm;
|
||||
mod content;
|
||||
mod list;
|
||||
|
||||
use buttons::*;
|
||||
pub(super) use confirm::*;
|
||||
use content::*;
|
||||
use list::*;
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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())))
|
||||
|
|
|
|||
|
|
@ -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 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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<Rect> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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| {
|
||||
|
|
|
|||
17
yazi-proxy/src/confirm.rs
Normal file
17
yazi-proxy/src/confirm.rs
Normal file
|
|
@ -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<bool> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
emit!(Call(Cmd::new("show").with_any("tx", tx).with_any("cfg", cfg), Layer::Confirm));
|
||||
rx
|
||||
}
|
||||
}
|
||||
|
|
@ -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