mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: make Input and Select positions customizable (#361)
This commit is contained in:
parent
6a64b162be
commit
bb2353c7b9
33 changed files with 554 additions and 265 deletions
|
|
@ -73,5 +73,61 @@ bizarre_retry = 5
|
|||
[plugins]
|
||||
preload = []
|
||||
|
||||
[input]
|
||||
# cd
|
||||
cd_title = "Change directory:"
|
||||
cd_origin = "top-center"
|
||||
cd_offset = [ 0, 2, 50, 3 ]
|
||||
|
||||
# create
|
||||
create_title = "Create:"
|
||||
create_origin = "top-center"
|
||||
create_offset = [ 0, 2, 50, 3 ]
|
||||
|
||||
# rename
|
||||
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 ]
|
||||
|
||||
# find
|
||||
find_title = [ "Find next:", "Find previous:" ]
|
||||
find_origin = "top-center"
|
||||
find_offset = [ 0, 2, 50, 3 ]
|
||||
|
||||
# search
|
||||
search_title = "Search:"
|
||||
search_origin = "top-center"
|
||||
search_offset = [ 0, 2, 50, 3 ]
|
||||
|
||||
# shell
|
||||
shell_title = [ "Shell:", "Shell (block):" ]
|
||||
shell_origin = "top-center"
|
||||
shell_offset = [ 0, 2, 50, 3 ]
|
||||
|
||||
# overwrite
|
||||
overwrite_title = "Overwrite an existing file? (y/N)"
|
||||
overwrite_origin = "top-center"
|
||||
overwrite_offset = [ 0, 2, 50, 3 ]
|
||||
|
||||
# quit
|
||||
quit_title = "{n} task{s} running, sure to quit? (y/N)"
|
||||
quit_origin = "top-center"
|
||||
quit_offset = [ 0, 2, 50, 3 ]
|
||||
|
||||
[select]
|
||||
open_title = "Open with:"
|
||||
open_origin = "hovered"
|
||||
open_offset = [ 0, 1, 50, 7 ]
|
||||
|
||||
[log]
|
||||
enabled = false
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ pub mod manager;
|
|||
pub mod open;
|
||||
mod pattern;
|
||||
pub mod plugins;
|
||||
pub mod popup;
|
||||
mod preset;
|
||||
pub mod preview;
|
||||
mod tasks;
|
||||
|
|
@ -32,6 +33,8 @@ pub static PLUGINS: RoCell<plugins::Plugins> = RoCell::new();
|
|||
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 SELECT: RoCell<popup::Select> = RoCell::new();
|
||||
|
||||
pub static BOOT: RoCell<boot::Boot> = RoCell::new();
|
||||
|
||||
|
|
@ -48,6 +51,8 @@ pub fn init() {
|
|||
PREVIEW.with(Default::default);
|
||||
TASKS.with(Default::default);
|
||||
THEME.with(Default::default);
|
||||
INPUT.with(Default::default);
|
||||
SELECT.with(Default::default);
|
||||
|
||||
BOOT.with(Default::default);
|
||||
}
|
||||
|
|
|
|||
73
yazi-config/src/popup/input.rs
Normal file
73
yazi-config/src/popup/input.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
use super::{Offset, Origin};
|
||||
use crate::MERGED_YAZI;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Input {
|
||||
// cd
|
||||
pub cd_title: String,
|
||||
pub cd_origin: Origin,
|
||||
pub cd_offset: Offset,
|
||||
|
||||
// create
|
||||
pub create_title: String,
|
||||
pub create_origin: Origin,
|
||||
pub create_offset: Offset,
|
||||
|
||||
// rename
|
||||
pub rename_title: String,
|
||||
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,
|
||||
|
||||
// find
|
||||
pub find_title: [String; 2],
|
||||
pub find_origin: Origin,
|
||||
pub find_offset: Offset,
|
||||
|
||||
// search
|
||||
pub search_title: String,
|
||||
pub search_origin: Origin,
|
||||
pub search_offset: Offset,
|
||||
|
||||
// shell
|
||||
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 Default for Input {
|
||||
fn default() -> Self {
|
||||
#[derive(Deserialize)]
|
||||
struct Outer {
|
||||
input: Input,
|
||||
}
|
||||
|
||||
toml::from_str::<Outer>(&MERGED_YAZI).unwrap().input
|
||||
}
|
||||
}
|
||||
|
||||
impl Input {
|
||||
#[inline]
|
||||
pub const fn border(&self) -> u16 { 2 }
|
||||
}
|
||||
13
yazi-config/src/popup/mod.rs
Normal file
13
yazi-config/src/popup/mod.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
mod input;
|
||||
mod offset;
|
||||
mod options;
|
||||
mod origin;
|
||||
mod position;
|
||||
mod select;
|
||||
|
||||
pub use input::*;
|
||||
pub use offset::*;
|
||||
pub use options::*;
|
||||
pub use origin::*;
|
||||
pub use position::*;
|
||||
pub use select::*;
|
||||
34
yazi-config/src/popup/offset.rs
Normal file
34
yazi-config/src/popup/offset.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use anyhow::bail;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Copy, Default, Deserialize)]
|
||||
#[serde(try_from = "Vec<i16>")]
|
||||
pub struct Offset {
|
||||
pub x: i16,
|
||||
pub y: i16,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<i16>> for Offset {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(values: Vec<i16>) -> Result<Self, Self::Error> {
|
||||
if values.len() != 4 {
|
||||
bail!("offset must have 4 values: {:?}", values);
|
||||
}
|
||||
if values[2] < 0 || values[3] < 0 {
|
||||
bail!("offset width and height must be positive: {:?}", values);
|
||||
}
|
||||
if values[3] < 3 {
|
||||
bail!("offset height must be at least 3: {:?}", values);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
x: values[0],
|
||||
y: values[1],
|
||||
width: values[2] as u16,
|
||||
height: values[3] as u16,
|
||||
})
|
||||
}
|
||||
}
|
||||
143
yazi-config/src/popup/options.rs
Normal file
143
yazi-config/src/popup/options.rs
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
use super::{Offset, Position};
|
||||
use crate::{INPUT, SELECT};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InputOpt {
|
||||
pub title: String,
|
||||
pub value: String,
|
||||
pub position: Position,
|
||||
pub realtime: bool,
|
||||
pub completion: bool,
|
||||
pub highlight: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SelectOpt {
|
||||
pub title: String,
|
||||
pub items: Vec<String>,
|
||||
pub position: Position,
|
||||
}
|
||||
|
||||
impl InputOpt {
|
||||
#[inline]
|
||||
pub fn cd() -> Self {
|
||||
Self {
|
||||
title: INPUT.cd_title.to_owned(),
|
||||
position: Position::new(INPUT.cd_origin, INPUT.cd_offset),
|
||||
completion: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create() -> Self {
|
||||
Self {
|
||||
title: INPUT.create_title.to_owned(),
|
||||
position: Position::new(INPUT.create_origin, INPUT.create_offset),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rename() -> Self {
|
||||
Self {
|
||||
title: INPUT.rename_title.to_owned(),
|
||||
position: Position::new(INPUT.rename_origin, INPUT.rename_offset),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[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 find(prev: bool) -> Self {
|
||||
Self {
|
||||
title: INPUT.find_title[prev as usize].to_owned(),
|
||||
position: Position::new(INPUT.find_origin, INPUT.find_offset),
|
||||
realtime: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn search() -> Self {
|
||||
Self {
|
||||
title: INPUT.search_title.to_owned(),
|
||||
position: Position::new(INPUT.search_origin, INPUT.search_offset),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shell(block: bool) -> Self {
|
||||
Self {
|
||||
title: INPUT.shell_title[block as usize].to_owned(),
|
||||
position: Position::new(INPUT.shell_origin, INPUT.shell_offset),
|
||||
highlight: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[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();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectOpt {
|
||||
#[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 {
|
||||
title: SELECT.open_title.to_owned(),
|
||||
items,
|
||||
position: Position::new(SELECT.open_origin, Offset {
|
||||
height: max_height,
|
||||
..SELECT.open_offset
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
24
yazi-config/src/popup/origin.rs
Normal file
24
yazi-config/src/popup/origin.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Copy, Default, Deserialize, PartialEq, Eq)]
|
||||
pub enum Origin {
|
||||
#[default]
|
||||
#[serde(rename = "top-left")]
|
||||
TopLeft,
|
||||
#[serde(rename = "top-center")]
|
||||
TopCenter,
|
||||
#[serde(rename = "top-right")]
|
||||
TopRight,
|
||||
|
||||
#[serde(rename = "bottom-left")]
|
||||
BottomLeft,
|
||||
#[serde(rename = "bottom-center")]
|
||||
BottomCenter,
|
||||
#[serde(rename = "bottom-right")]
|
||||
BottomRight,
|
||||
|
||||
#[serde(rename = "center")]
|
||||
Center,
|
||||
#[serde(rename = "hovered")]
|
||||
Hovered,
|
||||
}
|
||||
69
yazi-config/src/popup/position.rs
Normal file
69
yazi-config/src/popup/position.rs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
use crossterm::terminal::WindowSize;
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_shared::Term;
|
||||
|
||||
use super::{Offset, Origin};
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct Position {
|
||||
pub origin: Origin,
|
||||
pub offset: Offset,
|
||||
}
|
||||
|
||||
impl Position {
|
||||
#[inline]
|
||||
pub fn new(origin: Origin, offset: Offset) -> Self { Self { origin, offset } }
|
||||
|
||||
pub fn rect(&self) -> Rect {
|
||||
use Origin::*;
|
||||
let Offset { x, y, width, height } = self.offset;
|
||||
let WindowSize { columns, rows, .. } = Term::size();
|
||||
|
||||
let max_x = columns.saturating_sub(width);
|
||||
let new_x = match self.origin {
|
||||
TopLeft | BottomLeft => x.clamp(0, max_x as i16) as u16,
|
||||
TopCenter | BottomCenter | Center => {
|
||||
(columns / 2).saturating_sub(width / 2).saturating_add_signed(x).clamp(0, max_x)
|
||||
}
|
||||
TopRight | BottomRight => max_x.saturating_add_signed(x).clamp(0, max_x),
|
||||
Hovered => unreachable!(),
|
||||
};
|
||||
|
||||
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),
|
||||
BottomLeft | BottomCenter | BottomRight => max_y.saturating_add_signed(y).clamp(0, max_y),
|
||||
Hovered => unreachable!(),
|
||||
};
|
||||
|
||||
Rect {
|
||||
x: new_x,
|
||||
y: new_y,
|
||||
width: width.min(columns.saturating_sub(new_x)),
|
||||
height: height.min(rows.saturating_sub(new_y)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sticky(base: Rect, offset: Offset) -> Rect {
|
||||
let Offset { x, y, width, height } = offset;
|
||||
let WindowSize { columns, rows, .. } = Term::size();
|
||||
|
||||
let above =
|
||||
base.y.saturating_add(base.height).saturating_add(height).saturating_add_signed(y) > rows;
|
||||
|
||||
let new_x = base.x.saturating_add_signed(x).clamp(0, columns.saturating_sub(width));
|
||||
let new_y = if above {
|
||||
base.y.saturating_sub(height.saturating_sub(y.unsigned_abs()))
|
||||
} else {
|
||||
base.y.saturating_add(base.height).saturating_add_signed(y)
|
||||
};
|
||||
|
||||
Rect {
|
||||
x: new_x,
|
||||
y: new_y,
|
||||
width: width.min(columns.saturating_sub(new_x)),
|
||||
height: height.min(rows.saturating_sub(new_y)),
|
||||
}
|
||||
}
|
||||
}
|
||||
28
yazi-config/src/popup/select.rs
Normal file
28
yazi-config/src/popup/select.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
use super::{Offset, Origin};
|
||||
use crate::MERGED_YAZI;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Select {
|
||||
// open
|
||||
pub open_title: String,
|
||||
pub open_origin: Origin,
|
||||
pub open_offset: Offset,
|
||||
}
|
||||
|
||||
impl Default for Select {
|
||||
fn default() -> Self {
|
||||
#[derive(Deserialize)]
|
||||
struct Outer {
|
||||
select: Select,
|
||||
}
|
||||
|
||||
toml::from_str::<Outer>(&MERGED_YAZI).unwrap().select
|
||||
}
|
||||
}
|
||||
|
||||
impl Select {
|
||||
#[inline]
|
||||
pub const fn border(&self) -> u16 { 2 }
|
||||
}
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
use crossterm::terminal::WindowSize;
|
||||
use ratatui::prelude::Rect;
|
||||
use yazi_shared::Term;
|
||||
use yazi_config::popup::{Origin, Position};
|
||||
|
||||
use crate::{completion::Completion, help::Help, input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which, Position};
|
||||
use crate::{completion::Completion, help::Help, input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which};
|
||||
|
||||
pub struct Ctx {
|
||||
pub manager: Manager,
|
||||
|
|
@ -27,36 +26,18 @@ impl Ctx {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn area(&self, pos: &Position) -> Rect {
|
||||
let WindowSize { columns, rows, .. } = Term::size();
|
||||
pub fn area(&self, position: &Position) -> Rect {
|
||||
if position.origin != Origin::Hovered {
|
||||
return position.rect();
|
||||
}
|
||||
|
||||
let (x, y) = match pos {
|
||||
Position::Top(Rect { mut x, mut y, width, height }) => {
|
||||
x = x.min(columns.saturating_sub(*width));
|
||||
y = y.min(rows.saturating_sub(*height));
|
||||
((columns / 2).saturating_sub(width / 2) + x, y)
|
||||
}
|
||||
Position::Sticky(Rect { mut x, y, width, height }, r) => {
|
||||
x = x.min(columns.saturating_sub(*width));
|
||||
if y + height + r.y + r.height > rows {
|
||||
(x + r.x, r.y.saturating_sub(height.saturating_sub(*y)))
|
||||
} else {
|
||||
(x + r.x, y + r.y + r.height)
|
||||
}
|
||||
}
|
||||
Position::Hovered(rect) => {
|
||||
return self.area(&if let Some(r) =
|
||||
self.manager.hovered().and_then(|h| self.manager.current().rect_current(&h.url))
|
||||
{
|
||||
Position::Sticky(*rect, r)
|
||||
} else {
|
||||
Position::Top(*rect)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let (w, h) = pos.dimension();
|
||||
Rect { x, y, width: w.min(columns.saturating_sub(x)), height: h.min(rows.saturating_sub(y)) }
|
||||
if let Some(r) =
|
||||
self.manager.hovered().and_then(|h| self.manager.current().rect_current(&h.url))
|
||||
{
|
||||
Position::sticky(r, position.offset)
|
||||
} else {
|
||||
Position::new(Origin::TopCenter, position.offset).rect()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ use std::{collections::BTreeMap, ffi::OsString};
|
|||
use anyhow::Result;
|
||||
use crossterm::event::KeyEvent;
|
||||
use tokio::sync::{mpsc::{self, UnboundedSender}, oneshot};
|
||||
use yazi_config::{keymap::{Exec, KeymapLayer}, open::Opener};
|
||||
use yazi_config::{keymap::{Exec, KeymapLayer}, open::Opener, popup::{InputOpt, SelectOpt}};
|
||||
use yazi_shared::{InputError, RoCell, Url};
|
||||
|
||||
use super::{files::FilesOp, input::InputOpt, select::SelectOpt};
|
||||
use super::files::FilesOp;
|
||||
use crate::{preview::PreviewLock, tasks::TasksProgress};
|
||||
|
||||
static TX: RoCell<UnboundedSender<Event>> = RoCell::new();
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ impl Input {
|
|||
}
|
||||
}
|
||||
}
|
||||
self.snaps.tag();
|
||||
self.snaps.tag(self.limit());
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ impl Input {
|
|||
false,
|
||||
);
|
||||
|
||||
let snap = self.snap_mut();
|
||||
let (limit, snap) = (self.limit(), self.snap_mut());
|
||||
if snap.offset > snap.cursor {
|
||||
snap.offset = snap.cursor;
|
||||
} else if snap.value.is_empty() {
|
||||
|
|
@ -46,9 +46,9 @@ impl Input {
|
|||
} else {
|
||||
let delta = snap.mode.delta();
|
||||
let s = snap.slice(snap.offset..snap.cursor + delta);
|
||||
if s.width() >= /*TODO: hardcode*/ 50 - 2 {
|
||||
if s.width() >= limit {
|
||||
let s = s.chars().rev().collect::<String>();
|
||||
snap.offset = snap.cursor - InputSnap::find_window(&s, 0).end.saturating_sub(delta);
|
||||
snap.offset = snap.cursor - InputSnap::find_window(&s, 0, limit).end.saturating_sub(delta);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ use std::ops::Range;
|
|||
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use yazi_config::{popup::{InputOpt, Position}, INPUT};
|
||||
use yazi_shared::InputError;
|
||||
|
||||
use super::{mode::InputMode, op::InputOp, InputOpt, InputSnap, InputSnaps};
|
||||
use crate::{external, Position};
|
||||
use super::{mode::InputMode, op::InputOp, InputSnap, InputSnaps};
|
||||
use crate::external;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Input {
|
||||
|
|
@ -28,9 +29,7 @@ pub struct Input {
|
|||
impl Input {
|
||||
pub fn show(&mut self, opt: InputOpt, tx: UnboundedSender<Result<String, InputError>>) {
|
||||
self.close(false);
|
||||
self.snaps.reset(opt.value);
|
||||
self.visible = true;
|
||||
|
||||
self.title = opt.title;
|
||||
self.position = opt.position;
|
||||
|
||||
|
|
@ -41,6 +40,14 @@ impl Input {
|
|||
|
||||
// Shell
|
||||
self.highlight = opt.highlight;
|
||||
|
||||
// Reset snaps
|
||||
self.snaps.reset(opt.value, self.limit());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn limit(&self) -> usize {
|
||||
self.position.offset.width.saturating_sub(INPUT.border()) as usize
|
||||
}
|
||||
|
||||
pub fn type_str(&mut self, s: &str) -> bool {
|
||||
|
|
@ -92,7 +99,7 @@ impl Input {
|
|||
return false;
|
||||
}
|
||||
if !matches!(old.op, InputOp::None | InputOp::Select(_)) {
|
||||
self.snaps.tag().then(|| self.flush_value());
|
||||
self.snaps.tag(self.limit()).then(|| self.flush_value());
|
||||
}
|
||||
true
|
||||
}
|
||||
|
|
@ -115,7 +122,7 @@ impl Input {
|
|||
|
||||
impl Input {
|
||||
#[inline]
|
||||
pub fn value(&self) -> &str { self.snap().slice(self.snap().window()) }
|
||||
pub fn value(&self) -> &str { self.snap().slice(self.snap().window(self.limit())) }
|
||||
|
||||
#[inline]
|
||||
pub fn mode(&self) -> InputMode { self.snap().mode }
|
||||
|
|
@ -133,7 +140,7 @@ impl Input {
|
|||
let (start, end) =
|
||||
if start < snap.cursor { (start, snap.cursor) } else { (snap.cursor + 1, start + 1) };
|
||||
|
||||
let win = snap.window();
|
||||
let win = snap.window(self.limit());
|
||||
let Range { start, end } = start.max(win.start)..end.min(win.end);
|
||||
|
||||
let s = snap.slice(snap.offset..start).width() as u16;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ mod commands;
|
|||
mod input;
|
||||
mod mode;
|
||||
mod op;
|
||||
mod option;
|
||||
mod shell;
|
||||
mod snap;
|
||||
mod snaps;
|
||||
|
|
@ -10,6 +9,5 @@ mod snaps;
|
|||
pub use input::*;
|
||||
pub use mode::*;
|
||||
use op::*;
|
||||
pub use option::*;
|
||||
use snap::*;
|
||||
use snaps::*;
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
use ratatui::prelude::Rect;
|
||||
|
||||
use crate::Position;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InputOpt {
|
||||
pub title: String,
|
||||
pub value: String,
|
||||
pub position: Position,
|
||||
pub realtime: bool,
|
||||
pub completion: bool,
|
||||
pub highlight: bool,
|
||||
}
|
||||
|
||||
impl InputOpt {
|
||||
pub fn top(title: impl AsRef<str>) -> Self {
|
||||
Self {
|
||||
title: title.as_ref().to_owned(),
|
||||
position: Position::Top(/* TODO: hardcode */ Rect { x: 0, y: 2, width: 50, height: 3 }),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hovered(title: impl AsRef<str>) -> Self {
|
||||
Self {
|
||||
title: title.as_ref().to_owned(),
|
||||
position: Position::Hovered(
|
||||
// TODO: hardcode
|
||||
Rect { x: 0, y: 1, width: 50, height: 3 },
|
||||
),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn with_value(mut self, value: impl AsRef<str>) -> Self {
|
||||
self.value = value.as_ref().to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn with_realtime(mut self) -> Self {
|
||||
self.realtime = true;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn with_completion(mut self) -> Self {
|
||||
self.completion = true;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn with_highlight(mut self) -> Self {
|
||||
self.highlight = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ pub(super) struct InputSnap {
|
|||
}
|
||||
|
||||
impl InputSnap {
|
||||
pub(super) fn new(value: String) -> Self {
|
||||
pub(super) fn new(value: String, limit: usize) -> Self {
|
||||
let mut snap = Self {
|
||||
value,
|
||||
|
||||
|
|
@ -26,15 +26,15 @@ impl InputSnap {
|
|||
offset: usize::MAX,
|
||||
cursor: usize::MAX,
|
||||
};
|
||||
snap.reset();
|
||||
snap.reset(limit);
|
||||
snap
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn reset(&mut self) {
|
||||
pub(super) fn reset(&mut self, limit: usize) {
|
||||
self.cursor = self.cursor.min(self.value.chars().count().saturating_sub(self.mode.delta()));
|
||||
self.offset =
|
||||
self.offset.min(self.cursor.saturating_sub(Self::find_window(&self.rev(), 0).end));
|
||||
self.offset.min(self.cursor.saturating_sub(Self::find_window(&self.rev(), 0, limit).end));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -65,10 +65,12 @@ impl InputSnap {
|
|||
pub(super) fn rev(&self) -> String { self.value.chars().rev().collect::<String>() }
|
||||
|
||||
#[inline]
|
||||
pub(super) fn window(&self) -> Range<usize> { Self::find_window(&self.value, self.offset) }
|
||||
pub(super) fn window(&self, limit: usize) -> Range<usize> {
|
||||
Self::find_window(&self.value, self.offset, limit)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn find_window(s: &str, offset: usize) -> Range<usize> {
|
||||
pub(super) fn find_window(s: &str, offset: usize, limit: usize) -> Range<usize> {
|
||||
let mut width = 0;
|
||||
let v = s
|
||||
.chars()
|
||||
|
|
@ -76,7 +78,7 @@ impl InputSnap {
|
|||
.skip(offset)
|
||||
.map_while(|(i, c)| {
|
||||
width += c.width().unwrap_or(0);
|
||||
if width < /*TODO: hardcode*/ 50 - 2 { Some(i) } else { None }
|
||||
if width < limit { Some(i) } else { None }
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
|
|
|||
|
|
@ -11,13 +11,33 @@ pub(super) struct InputSnaps {
|
|||
|
||||
impl InputSnaps {
|
||||
#[inline]
|
||||
pub(super) fn reset(&mut self, value: String) {
|
||||
pub(super) fn reset(&mut self, value: String, limit: usize) {
|
||||
self.idx = 0;
|
||||
self.versions.clear();
|
||||
self.versions.push(InputSnap::new(value));
|
||||
self.versions.push(InputSnap::new(value, limit));
|
||||
self.current = self.versions[0].clone();
|
||||
}
|
||||
|
||||
pub(super) fn tag(&mut self, limit: usize) -> bool {
|
||||
// Sync *current* cursor position to the *last* version:
|
||||
// Save offset/cursor/ect. of the *current* as the last version,
|
||||
// while keeping the *last* value unchanged.
|
||||
let value = mem::take(&mut self.versions[self.idx].value);
|
||||
self.versions[self.idx] = self.current.clone();
|
||||
self.versions[self.idx].value = value;
|
||||
self.versions[self.idx].reset(limit);
|
||||
|
||||
// If the *current* value is the same as the *last* version
|
||||
if self.versions[self.idx].value == self.current.value {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.versions.truncate(self.idx + 1);
|
||||
self.versions.push(self.current().clone());
|
||||
self.idx += 1;
|
||||
true
|
||||
}
|
||||
|
||||
pub(super) fn undo(&mut self) -> bool {
|
||||
if self.idx == 0 {
|
||||
return false;
|
||||
|
|
@ -37,26 +57,6 @@ impl InputSnaps {
|
|||
self.current = self.versions[self.idx].clone();
|
||||
true
|
||||
}
|
||||
|
||||
pub(super) fn tag(&mut self) -> bool {
|
||||
// Sync *current* cursor position to the *last* version:
|
||||
// Save offset/cursor/ect. of the *current* as the last version,
|
||||
// while keeping the *last* value unchanged.
|
||||
let value = mem::take(&mut self.versions[self.idx].value);
|
||||
self.versions[self.idx] = self.current.clone();
|
||||
self.versions[self.idx].value = value;
|
||||
self.versions[self.idx].reset();
|
||||
|
||||
// If the *current* value is the same as the *last* version
|
||||
if self.versions[self.idx].value == self.current.value {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.versions.truncate(self.idx + 1);
|
||||
self.versions.push(self.current().clone());
|
||||
self.idx += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl InputSnaps {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ pub mod help;
|
|||
mod highlighter;
|
||||
pub mod input;
|
||||
pub mod manager;
|
||||
mod position;
|
||||
pub mod preview;
|
||||
pub mod select;
|
||||
mod step;
|
||||
|
|
@ -28,7 +27,6 @@ pub use blocker::*;
|
|||
pub use context::*;
|
||||
pub use event::*;
|
||||
pub use highlighter::*;
|
||||
pub use position::*;
|
||||
pub use step::*;
|
||||
|
||||
pub fn init() { init_blocker(); }
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use std::path::{PathBuf, MAIN_SEPARATOR};
|
||||
|
||||
use tokio::fs;
|
||||
use yazi_config::keymap::Exec;
|
||||
use yazi_config::{keymap::Exec, popup::InputOpt};
|
||||
use yazi_shared::Url;
|
||||
|
||||
use crate::{emit, files::{File, FilesOp}, input::InputOpt, manager::Manager};
|
||||
use crate::{emit, files::{File, FilesOp}, manager::Manager};
|
||||
|
||||
pub struct Opt {
|
||||
force: bool,
|
||||
|
|
@ -19,14 +19,14 @@ impl Manager {
|
|||
let opt = opt.into() as Opt;
|
||||
let cwd = self.cwd().to_owned();
|
||||
tokio::spawn(async move {
|
||||
let mut result = emit!(Input(InputOpt::top("Create:")));
|
||||
let mut result = emit!(Input(InputOpt::create()));
|
||||
let Some(Ok(name)) = result.recv().await else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let path = cwd.join(&name);
|
||||
if !opt.force && fs::symlink_metadata(&path).await.is_ok() {
|
||||
match emit!(Input(InputOpt::top("Overwrite an existing file? (y/N)"))).recv().await {
|
||||
match emit!(Input(InputOpt::overwrite())).recv().await {
|
||||
Some(Ok(c)) if c == "y" || c == "Y" => (),
|
||||
_ => return Ok(()),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use std::ffi::OsString;
|
||||
|
||||
use yazi_config::{keymap::Exec, OPEN};
|
||||
use yazi_config::{keymap::Exec, popup::SelectOpt, OPEN};
|
||||
use yazi_shared::MIME_DIR;
|
||||
|
||||
use crate::{emit, external, manager::Manager, select::SelectOpt};
|
||||
use crate::{emit, external, manager::Manager};
|
||||
|
||||
pub struct Opt {
|
||||
interactive: bool,
|
||||
|
|
@ -20,11 +20,7 @@ impl Manager {
|
|||
return;
|
||||
}
|
||||
|
||||
let result = emit!(Select(SelectOpt::hovered(
|
||||
"Open with:",
|
||||
openers.iter().map(|o| o.desc.clone()).collect()
|
||||
)));
|
||||
|
||||
let result = emit!(Select(SelectOpt::open(openers.iter().map(|o| o.desc.clone()).collect())));
|
||||
if let Ok(choice) = result.await {
|
||||
emit!(Open(files, Some(openers[choice].clone())));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use yazi_config::keymap::Exec;
|
||||
use yazi_config::{keymap::Exec, popup::InputOpt};
|
||||
|
||||
use crate::{emit, input::InputOpt, manager::Manager, tasks::Tasks};
|
||||
use crate::{emit, manager::Manager, tasks::Tasks};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Opt {
|
||||
|
|
@ -24,9 +24,7 @@ impl Manager {
|
|||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut result =
|
||||
emit!(Input(InputOpt::top(format!("{tasks} tasks running, sure to quit? (y/N)"))));
|
||||
|
||||
let mut result = emit!(Input(InputOpt::quit(tasks)));
|
||||
if let Some(Ok(choice)) = result.recv().await {
|
||||
if choice == "y" || choice == "Y" {
|
||||
emit!(Quit(opt.no_cwd_file));
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ use std::{collections::BTreeSet, ffi::OsStr, io::{stdout, BufWriter, Write}, pat
|
|||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
|
||||
use yazi_config::{keymap::Exec, OPEN, PREVIEW};
|
||||
use yazi_config::{keymap::Exec, popup::InputOpt, OPEN, PREVIEW};
|
||||
use yazi_shared::{max_common_root, Defer, Term, Url};
|
||||
|
||||
use crate::{emit, external::{self, ShellOpt}, files::{File, FilesOp}, input::InputOpt, manager::Manager, Event, BLOCKER};
|
||||
use crate::{emit, external::{self, ShellOpt}, files::{File, FilesOp}, manager::Manager, Event, BLOCKER};
|
||||
|
||||
pub struct Opt {
|
||||
force: bool,
|
||||
|
|
@ -42,9 +42,8 @@ impl Manager {
|
|||
|
||||
let opt = opt.into() as Opt;
|
||||
tokio::spawn(async move {
|
||||
let mut result = emit!(Input(
|
||||
InputOpt::hovered("Rename:").with_value(hovered.file_name().unwrap().to_string_lossy())
|
||||
));
|
||||
let mut result =
|
||||
emit!(Input(InputOpt::rename().with_value(hovered.file_name().unwrap().to_string_lossy())));
|
||||
|
||||
let Some(Ok(name)) = result.recv().await else {
|
||||
return;
|
||||
|
|
@ -56,7 +55,7 @@ impl Manager {
|
|||
return;
|
||||
}
|
||||
|
||||
let mut result = emit!(Input(InputOpt::hovered("Overwrite an existing file? (y/N)")));
|
||||
let mut result = emit!(Input(InputOpt::overwrite()));
|
||||
if let Some(Ok(choice)) = result.recv().await {
|
||||
if choice == "y" || choice == "Y" {
|
||||
Self::rename_and_hover(hovered, Url::from(new)).await.ok();
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
use ratatui::prelude::Rect;
|
||||
|
||||
pub enum Position {
|
||||
Top(Rect),
|
||||
Sticky(Rect, Rect),
|
||||
Hovered(Rect),
|
||||
}
|
||||
|
||||
impl Default for Position {
|
||||
fn default() -> Self { Self::Top(Rect::default()) }
|
||||
}
|
||||
|
||||
impl Position {
|
||||
#[inline]
|
||||
pub fn rect(&self) -> Rect {
|
||||
match self {
|
||||
Position::Top(rect) => *rect,
|
||||
Position::Sticky(rect, _) => *rect,
|
||||
Position::Hovered(rect) => *rect,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn dimension(&self) -> (u16, u16) { (self.rect().width, self.rect().height) }
|
||||
}
|
||||
|
|
@ -1,8 +1,4 @@
|
|||
mod commands;
|
||||
mod option;
|
||||
mod select;
|
||||
|
||||
pub use option::*;
|
||||
pub use select::*;
|
||||
|
||||
pub const SELECT_PADDING: u16 = 2;
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
use ratatui::prelude::Rect;
|
||||
|
||||
use crate::Position;
|
||||
|
||||
pub struct SelectOpt {
|
||||
pub title: String,
|
||||
pub items: Vec<String>,
|
||||
pub position: Position,
|
||||
}
|
||||
|
||||
impl SelectOpt {
|
||||
pub fn top(title: &str, items: Vec<String>) -> Self {
|
||||
let height = 2 + items.len().min(/* TODO: hardcode */ 5) as u16;
|
||||
Self {
|
||||
title: title.to_owned(),
|
||||
items,
|
||||
position: Position::Top(/* TODO: hardcode */ Rect { x: 0, y: 2, width: 50, height }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hovered(title: &str, items: Vec<String>) -> Self {
|
||||
let height = 2 + items.len().min(/* TODO: hardcode */ 5) as u16;
|
||||
Self {
|
||||
title: title.to_owned(),
|
||||
items,
|
||||
position: Position::Hovered(
|
||||
// TODO: hardcode
|
||||
Rect { x: 0, y: 1, width: 50, height },
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use tokio::sync::oneshot::Sender;
|
||||
|
||||
use super::SelectOpt;
|
||||
use crate::Position;
|
||||
use yazi_config::{popup::{Position, SelectOpt}, SELECT};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Select {
|
||||
|
|
@ -36,7 +34,9 @@ impl Select {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn limit(&self) -> usize { self.items.len().min(5) }
|
||||
pub(super) fn limit(&self) -> usize {
|
||||
self.position.offset.height.saturating_sub(SELECT.border()) as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl Select {
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ use std::{mem, time::Duration};
|
|||
|
||||
use tokio::{fs, pin};
|
||||
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
||||
use yazi_config::keymap::{Exec, KeymapLayer};
|
||||
use yazi_config::{keymap::{Exec, KeymapLayer}, popup::InputOpt};
|
||||
use yazi_shared::{expand_path, Debounce, InputError, Url};
|
||||
|
||||
use crate::{emit, input::InputOpt, tab::Tab};
|
||||
use crate::{emit, tab::Tab};
|
||||
|
||||
pub struct Opt {
|
||||
target: Url,
|
||||
|
|
@ -65,11 +65,7 @@ impl Tab {
|
|||
let opt = opt.into() as Opt;
|
||||
|
||||
tokio::spawn(async move {
|
||||
let rx = emit!(Input(
|
||||
InputOpt::top("Change directory:")
|
||||
.with_value(opt.target.to_string_lossy())
|
||||
.with_completion()
|
||||
));
|
||||
let rx = emit!(Input(InputOpt::cd().with_value(opt.target.to_string_lossy())));
|
||||
|
||||
let rx = Debounce::new(UnboundedReceiverStream::new(rx), Duration::from_millis(50));
|
||||
pin!(rx);
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ use std::time::Duration;
|
|||
|
||||
use tokio::pin;
|
||||
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
||||
use yazi_config::keymap::{Exec, KeymapLayer};
|
||||
use yazi_config::{keymap::{Exec, KeymapLayer}, popup::InputOpt};
|
||||
use yazi_shared::{Debounce, InputError};
|
||||
|
||||
use crate::{emit, input::InputOpt, tab::{Finder, FinderCase, Tab}};
|
||||
use crate::{emit, tab::{Finder, FinderCase, Tab}};
|
||||
|
||||
pub struct Opt<'a> {
|
||||
query: Option<&'a str>,
|
||||
|
|
@ -39,9 +39,7 @@ impl Tab {
|
|||
pub fn find<'a>(&mut self, opt: impl Into<Opt<'a>>) -> bool {
|
||||
let opt = opt.into() as Opt;
|
||||
tokio::spawn(async move {
|
||||
let rx = emit!(Input(
|
||||
InputOpt::top(if opt.prev { "Find previous:" } else { "Find next:" }).with_realtime()
|
||||
));
|
||||
let rx = emit!(Input(InputOpt::find(opt.prev)));
|
||||
|
||||
let rx = Debounce::new(UnboundedReceiverStream::new(rx), Duration::from_millis(50));
|
||||
pin!(rx);
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ use std::{mem, time::Duration};
|
|||
use anyhow::bail;
|
||||
use tokio::pin;
|
||||
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
||||
use yazi_config::keymap::{Exec, KeymapLayer};
|
||||
use yazi_config::{keymap::{Exec, KeymapLayer}, popup::InputOpt};
|
||||
|
||||
use crate::{emit, external, files::FilesOp, input::InputOpt, tab::Tab};
|
||||
use crate::{emit, external, files::FilesOp, tab::Tab};
|
||||
|
||||
pub struct Opt {
|
||||
pub type_: OptType,
|
||||
|
|
@ -45,9 +45,7 @@ impl Tab {
|
|||
let hidden = self.conf.show_hidden;
|
||||
|
||||
self.search = Some(tokio::spawn(async move {
|
||||
let Some(Ok(subject)) = emit!(Input(InputOpt::top("Search:"))).recv().await else {
|
||||
bail!("")
|
||||
};
|
||||
let Some(Ok(subject)) = emit!(Input(InputOpt::search())).recv().await else { bail!("") };
|
||||
|
||||
cwd = cwd.into_search(subject.clone());
|
||||
let rx = if opt.type_ == OptType::Rg {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use yazi_config::{keymap::Exec, open::Opener};
|
||||
use yazi_config::{keymap::Exec, open::Opener, popup::InputOpt};
|
||||
|
||||
use crate::{emit, input::InputOpt, tab::Tab};
|
||||
use crate::{emit, tab::Tab};
|
||||
|
||||
pub struct Opt {
|
||||
cmd: String,
|
||||
|
|
@ -29,11 +29,7 @@ impl Tab {
|
|||
let mut opt = opt.into() as Opt;
|
||||
tokio::spawn(async move {
|
||||
if !opt.confirm || opt.cmd.is_empty() {
|
||||
let mut result = emit!(Input(
|
||||
InputOpt::top(if opt.block { "Shell (block):" } else { "Shell:" })
|
||||
.with_value(opt.cmd)
|
||||
.with_highlight()
|
||||
));
|
||||
let mut result = emit!(Input(InputOpt::shell(opt.block).with_value(opt.cmd)));
|
||||
match result.recv().await {
|
||||
Some(Ok(e)) => opt.cmd = e,
|
||||
_ => return,
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, path::Path, syn
|
|||
|
||||
use serde::Serialize;
|
||||
use tracing::debug;
|
||||
use yazi_config::{manager::SortBy, open::Opener, OPEN};
|
||||
use yazi_config::{manager::SortBy, open::Opener, popup::InputOpt, OPEN};
|
||||
use yazi_shared::{MimeKind, Term, Url};
|
||||
|
||||
use super::{running::Running, task::TaskSummary, Scheduler, TASKS_PADDING, TASKS_PERCENT};
|
||||
use crate::{emit, files::{File, Files}, input::InputOpt};
|
||||
use crate::{emit, files::{File, Files}};
|
||||
|
||||
pub struct Tasks {
|
||||
pub(super) scheduler: Arc<Scheduler>,
|
||||
|
|
@ -110,12 +110,11 @@ impl Tasks {
|
|||
|
||||
let scheduler = self.scheduler.clone();
|
||||
tokio::spawn(async move {
|
||||
let s = if targets.len() > 1 { "s" } else { "" };
|
||||
let mut result = emit!(Input(InputOpt::hovered(if permanently {
|
||||
format!("Delete {} selected file{s} permanently? (y/N)", targets.len())
|
||||
let mut result = emit!(Input(if permanently {
|
||||
InputOpt::delete(targets.len())
|
||||
} else {
|
||||
format!("Move {} selected file{s} to trash? (y/N)", targets.len())
|
||||
})));
|
||||
InputOpt::trash(targets.len())
|
||||
}));
|
||||
|
||||
if let Some(Ok(choice)) = result.recv().await {
|
||||
if choice != "y" && choice != "Y" {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use std::path::MAIN_SEPARATOR;
|
||||
|
||||
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, Borders, Clear, List, ListItem, Widget}};
|
||||
use yazi_config::THEME;
|
||||
use yazi_core::{Ctx, Position};
|
||||
use yazi_config::{popup::{Offset, Position}, THEME};
|
||||
use yazi_core::Ctx;
|
||||
|
||||
pub(crate) struct Completion<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
@ -39,15 +39,12 @@ impl<'a> Widget for Completion<'a> {
|
|||
.collect::<Vec<_>>();
|
||||
|
||||
let input_area = self.cx.area(&self.cx.input.position);
|
||||
let mut area = self.cx.area(&Position::Sticky(
|
||||
Rect {
|
||||
x: 1,
|
||||
y: 0,
|
||||
width: input_area.width.saturating_sub(2),
|
||||
height: items.len() as u16 + 2,
|
||||
},
|
||||
input_area,
|
||||
));
|
||||
let mut area = Position::sticky(input_area, Offset {
|
||||
x: 1,
|
||||
y: 0,
|
||||
width: input_area.width.saturating_sub(2),
|
||||
height: items.len() as u16 + 2,
|
||||
});
|
||||
|
||||
if area.y > input_area.y {
|
||||
area.y = area.y.saturating_sub(1);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue