mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41: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]
|
[plugins]
|
||||||
preload = []
|
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]
|
[log]
|
||||||
enabled = false
|
enabled = false
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ pub mod manager;
|
||||||
pub mod open;
|
pub mod open;
|
||||||
mod pattern;
|
mod pattern;
|
||||||
pub mod plugins;
|
pub mod plugins;
|
||||||
|
pub mod popup;
|
||||||
mod preset;
|
mod preset;
|
||||||
pub mod preview;
|
pub mod preview;
|
||||||
mod tasks;
|
mod tasks;
|
||||||
|
|
@ -32,6 +33,8 @@ pub static PLUGINS: RoCell<plugins::Plugins> = RoCell::new();
|
||||||
pub static PREVIEW: RoCell<preview::Preview> = RoCell::new();
|
pub static PREVIEW: RoCell<preview::Preview> = RoCell::new();
|
||||||
pub static TASKS: RoCell<tasks::Tasks> = RoCell::new();
|
pub static TASKS: RoCell<tasks::Tasks> = RoCell::new();
|
||||||
pub static THEME: RoCell<theme::Theme> = 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();
|
pub static BOOT: RoCell<boot::Boot> = RoCell::new();
|
||||||
|
|
||||||
|
|
@ -48,6 +51,8 @@ pub fn init() {
|
||||||
PREVIEW.with(Default::default);
|
PREVIEW.with(Default::default);
|
||||||
TASKS.with(Default::default);
|
TASKS.with(Default::default);
|
||||||
THEME.with(Default::default);
|
THEME.with(Default::default);
|
||||||
|
INPUT.with(Default::default);
|
||||||
|
SELECT.with(Default::default);
|
||||||
|
|
||||||
BOOT.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 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 struct Ctx {
|
||||||
pub manager: Manager,
|
pub manager: Manager,
|
||||||
|
|
@ -27,36 +26,18 @@ impl Ctx {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn area(&self, pos: &Position) -> Rect {
|
pub fn area(&self, position: &Position) -> Rect {
|
||||||
let WindowSize { columns, rows, .. } = Term::size();
|
if position.origin != Origin::Hovered {
|
||||||
|
return position.rect();
|
||||||
|
}
|
||||||
|
|
||||||
let (x, y) = match pos {
|
if let Some(r) =
|
||||||
Position::Top(Rect { mut x, mut y, width, height }) => {
|
self.manager.hovered().and_then(|h| self.manager.current().rect_current(&h.url))
|
||||||
x = x.min(columns.saturating_sub(*width));
|
{
|
||||||
y = y.min(rows.saturating_sub(*height));
|
Position::sticky(r, position.offset)
|
||||||
((columns / 2).saturating_sub(width / 2) + x, y)
|
} else {
|
||||||
}
|
Position::new(Origin::TopCenter, position.offset).rect()
|
||||||
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)) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ use std::{collections::BTreeMap, ffi::OsString};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossterm::event::KeyEvent;
|
use crossterm::event::KeyEvent;
|
||||||
use tokio::sync::{mpsc::{self, UnboundedSender}, oneshot};
|
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 yazi_shared::{InputError, RoCell, Url};
|
||||||
|
|
||||||
use super::{files::FilesOp, input::InputOpt, select::SelectOpt};
|
use super::files::FilesOp;
|
||||||
use crate::{preview::PreviewLock, tasks::TasksProgress};
|
use crate::{preview::PreviewLock, tasks::TasksProgress};
|
||||||
|
|
||||||
static TX: RoCell<UnboundedSender<Event>> = RoCell::new();
|
static TX: RoCell<UnboundedSender<Event>> = RoCell::new();
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ impl Input {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.snaps.tag();
|
self.snaps.tag(self.limit());
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ impl Input {
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
let snap = self.snap_mut();
|
let (limit, snap) = (self.limit(), self.snap_mut());
|
||||||
if snap.offset > snap.cursor {
|
if snap.offset > snap.cursor {
|
||||||
snap.offset = snap.cursor;
|
snap.offset = snap.cursor;
|
||||||
} else if snap.value.is_empty() {
|
} else if snap.value.is_empty() {
|
||||||
|
|
@ -46,9 +46,9 @@ impl Input {
|
||||||
} else {
|
} else {
|
||||||
let delta = snap.mode.delta();
|
let delta = snap.mode.delta();
|
||||||
let s = snap.slice(snap.offset..snap.cursor + 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>();
|
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 tokio::sync::mpsc::UnboundedSender;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
use yazi_config::{popup::{InputOpt, Position}, INPUT};
|
||||||
use yazi_shared::InputError;
|
use yazi_shared::InputError;
|
||||||
|
|
||||||
use super::{mode::InputMode, op::InputOp, InputOpt, InputSnap, InputSnaps};
|
use super::{mode::InputMode, op::InputOp, InputSnap, InputSnaps};
|
||||||
use crate::{external, Position};
|
use crate::external;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Input {
|
pub struct Input {
|
||||||
|
|
@ -28,9 +29,7 @@ pub struct Input {
|
||||||
impl Input {
|
impl Input {
|
||||||
pub fn show(&mut self, opt: InputOpt, tx: UnboundedSender<Result<String, InputError>>) {
|
pub fn show(&mut self, opt: InputOpt, tx: UnboundedSender<Result<String, InputError>>) {
|
||||||
self.close(false);
|
self.close(false);
|
||||||
self.snaps.reset(opt.value);
|
|
||||||
self.visible = true;
|
self.visible = true;
|
||||||
|
|
||||||
self.title = opt.title;
|
self.title = opt.title;
|
||||||
self.position = opt.position;
|
self.position = opt.position;
|
||||||
|
|
||||||
|
|
@ -41,6 +40,14 @@ impl Input {
|
||||||
|
|
||||||
// Shell
|
// Shell
|
||||||
self.highlight = opt.highlight;
|
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 {
|
pub fn type_str(&mut self, s: &str) -> bool {
|
||||||
|
|
@ -92,7 +99,7 @@ impl Input {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if !matches!(old.op, InputOp::None | InputOp::Select(_)) {
|
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
|
true
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +122,7 @@ impl Input {
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
#[inline]
|
#[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]
|
#[inline]
|
||||||
pub fn mode(&self) -> InputMode { self.snap().mode }
|
pub fn mode(&self) -> InputMode { self.snap().mode }
|
||||||
|
|
@ -133,7 +140,7 @@ impl Input {
|
||||||
let (start, end) =
|
let (start, end) =
|
||||||
if start < snap.cursor { (start, snap.cursor) } else { (snap.cursor + 1, start + 1) };
|
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 Range { start, end } = start.max(win.start)..end.min(win.end);
|
||||||
|
|
||||||
let s = snap.slice(snap.offset..start).width() as u16;
|
let s = snap.slice(snap.offset..start).width() as u16;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ mod commands;
|
||||||
mod input;
|
mod input;
|
||||||
mod mode;
|
mod mode;
|
||||||
mod op;
|
mod op;
|
||||||
mod option;
|
|
||||||
mod shell;
|
mod shell;
|
||||||
mod snap;
|
mod snap;
|
||||||
mod snaps;
|
mod snaps;
|
||||||
|
|
@ -10,6 +9,5 @@ mod snaps;
|
||||||
pub use input::*;
|
pub use input::*;
|
||||||
pub use mode::*;
|
pub use mode::*;
|
||||||
use op::*;
|
use op::*;
|
||||||
pub use option::*;
|
|
||||||
use snap::*;
|
use snap::*;
|
||||||
use snaps::*;
|
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 {
|
impl InputSnap {
|
||||||
pub(super) fn new(value: String) -> Self {
|
pub(super) fn new(value: String, limit: usize) -> Self {
|
||||||
let mut snap = Self {
|
let mut snap = Self {
|
||||||
value,
|
value,
|
||||||
|
|
||||||
|
|
@ -26,15 +26,15 @@ impl InputSnap {
|
||||||
offset: usize::MAX,
|
offset: usize::MAX,
|
||||||
cursor: usize::MAX,
|
cursor: usize::MAX,
|
||||||
};
|
};
|
||||||
snap.reset();
|
snap.reset(limit);
|
||||||
snap
|
snap
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[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.cursor = self.cursor.min(self.value.chars().count().saturating_sub(self.mode.delta()));
|
||||||
self.offset =
|
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>() }
|
pub(super) fn rev(&self) -> String { self.value.chars().rev().collect::<String>() }
|
||||||
|
|
||||||
#[inline]
|
#[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]
|
#[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 mut width = 0;
|
||||||
let v = s
|
let v = s
|
||||||
.chars()
|
.chars()
|
||||||
|
|
@ -76,7 +78,7 @@ impl InputSnap {
|
||||||
.skip(offset)
|
.skip(offset)
|
||||||
.map_while(|(i, c)| {
|
.map_while(|(i, c)| {
|
||||||
width += c.width().unwrap_or(0);
|
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<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,33 @@ pub(super) struct InputSnaps {
|
||||||
|
|
||||||
impl InputSnaps {
|
impl InputSnaps {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn reset(&mut self, value: String) {
|
pub(super) fn reset(&mut self, value: String, limit: usize) {
|
||||||
self.idx = 0;
|
self.idx = 0;
|
||||||
self.versions.clear();
|
self.versions.clear();
|
||||||
self.versions.push(InputSnap::new(value));
|
self.versions.push(InputSnap::new(value, limit));
|
||||||
self.current = self.versions[0].clone();
|
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 {
|
pub(super) fn undo(&mut self) -> bool {
|
||||||
if self.idx == 0 {
|
if self.idx == 0 {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -37,26 +57,6 @@ impl InputSnaps {
|
||||||
self.current = self.versions[self.idx].clone();
|
self.current = self.versions[self.idx].clone();
|
||||||
true
|
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 {
|
impl InputSnaps {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ pub mod help;
|
||||||
mod highlighter;
|
mod highlighter;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod manager;
|
pub mod manager;
|
||||||
mod position;
|
|
||||||
pub mod preview;
|
pub mod preview;
|
||||||
pub mod select;
|
pub mod select;
|
||||||
mod step;
|
mod step;
|
||||||
|
|
@ -28,7 +27,6 @@ pub use blocker::*;
|
||||||
pub use context::*;
|
pub use context::*;
|
||||||
pub use event::*;
|
pub use event::*;
|
||||||
pub use highlighter::*;
|
pub use highlighter::*;
|
||||||
pub use position::*;
|
|
||||||
pub use step::*;
|
pub use step::*;
|
||||||
|
|
||||||
pub fn init() { init_blocker(); }
|
pub fn init() { init_blocker(); }
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use std::path::{PathBuf, MAIN_SEPARATOR};
|
use std::path::{PathBuf, MAIN_SEPARATOR};
|
||||||
|
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use yazi_config::keymap::Exec;
|
use yazi_config::{keymap::Exec, popup::InputOpt};
|
||||||
use yazi_shared::Url;
|
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 {
|
pub struct Opt {
|
||||||
force: bool,
|
force: bool,
|
||||||
|
|
@ -19,14 +19,14 @@ impl Manager {
|
||||||
let opt = opt.into() as Opt;
|
let opt = opt.into() as Opt;
|
||||||
let cwd = self.cwd().to_owned();
|
let cwd = self.cwd().to_owned();
|
||||||
tokio::spawn(async move {
|
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 {
|
let Some(Ok(name)) = result.recv().await else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = cwd.join(&name);
|
let path = cwd.join(&name);
|
||||||
if !opt.force && fs::symlink_metadata(&path).await.is_ok() {
|
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" => (),
|
Some(Ok(c)) if c == "y" || c == "Y" => (),
|
||||||
_ => return Ok(()),
|
_ => return Ok(()),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
|
||||||
use yazi_config::{keymap::Exec, OPEN};
|
use yazi_config::{keymap::Exec, popup::SelectOpt, OPEN};
|
||||||
use yazi_shared::MIME_DIR;
|
use yazi_shared::MIME_DIR;
|
||||||
|
|
||||||
use crate::{emit, external, manager::Manager, select::SelectOpt};
|
use crate::{emit, external, manager::Manager};
|
||||||
|
|
||||||
pub struct Opt {
|
pub struct Opt {
|
||||||
interactive: bool,
|
interactive: bool,
|
||||||
|
|
@ -20,11 +20,7 @@ impl Manager {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = emit!(Select(SelectOpt::hovered(
|
let result = emit!(Select(SelectOpt::open(openers.iter().map(|o| o.desc.clone()).collect())));
|
||||||
"Open with:",
|
|
||||||
openers.iter().map(|o| o.desc.clone()).collect()
|
|
||||||
)));
|
|
||||||
|
|
||||||
if let Ok(choice) = result.await {
|
if let Ok(choice) = result.await {
|
||||||
emit!(Open(files, Some(openers[choice].clone())));
|
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)]
|
#[derive(Default)]
|
||||||
pub struct Opt {
|
pub struct Opt {
|
||||||
|
|
@ -24,9 +24,7 @@ impl Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut result =
|
let mut result = emit!(Input(InputOpt::quit(tasks)));
|
||||||
emit!(Input(InputOpt::top(format!("{tasks} tasks running, sure to quit? (y/N)"))));
|
|
||||||
|
|
||||||
if let Some(Ok(choice)) = result.recv().await {
|
if let Some(Ok(choice)) = result.recv().await {
|
||||||
if choice == "y" || choice == "Y" {
|
if choice == "y" || choice == "Y" {
|
||||||
emit!(Quit(opt.no_cwd_file));
|
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 anyhow::{anyhow, bail, Result};
|
||||||
use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
|
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 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 {
|
pub struct Opt {
|
||||||
force: bool,
|
force: bool,
|
||||||
|
|
@ -42,9 +42,8 @@ impl Manager {
|
||||||
|
|
||||||
let opt = opt.into() as Opt;
|
let opt = opt.into() as Opt;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut result = emit!(Input(
|
let mut result =
|
||||||
InputOpt::hovered("Rename:").with_value(hovered.file_name().unwrap().to_string_lossy())
|
emit!(Input(InputOpt::rename().with_value(hovered.file_name().unwrap().to_string_lossy())));
|
||||||
));
|
|
||||||
|
|
||||||
let Some(Ok(name)) = result.recv().await else {
|
let Some(Ok(name)) = result.recv().await else {
|
||||||
return;
|
return;
|
||||||
|
|
@ -56,7 +55,7 @@ impl Manager {
|
||||||
return;
|
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 let Some(Ok(choice)) = result.recv().await {
|
||||||
if choice == "y" || choice == "Y" {
|
if choice == "y" || choice == "Y" {
|
||||||
Self::rename_and_hover(hovered, Url::from(new)).await.ok();
|
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 commands;
|
||||||
mod option;
|
|
||||||
mod select;
|
mod select;
|
||||||
|
|
||||||
pub use option::*;
|
|
||||||
pub use select::*;
|
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 anyhow::Result;
|
||||||
use tokio::sync::oneshot::Sender;
|
use tokio::sync::oneshot::Sender;
|
||||||
|
use yazi_config::{popup::{Position, SelectOpt}, SELECT};
|
||||||
use super::SelectOpt;
|
|
||||||
use crate::Position;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Select {
|
pub struct Select {
|
||||||
|
|
@ -36,7 +34,9 @@ impl Select {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[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 {
|
impl Select {
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ use std::{mem, time::Duration};
|
||||||
|
|
||||||
use tokio::{fs, pin};
|
use tokio::{fs, pin};
|
||||||
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
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 yazi_shared::{expand_path, Debounce, InputError, Url};
|
||||||
|
|
||||||
use crate::{emit, input::InputOpt, tab::Tab};
|
use crate::{emit, tab::Tab};
|
||||||
|
|
||||||
pub struct Opt {
|
pub struct Opt {
|
||||||
target: Url,
|
target: Url,
|
||||||
|
|
@ -65,11 +65,7 @@ impl Tab {
|
||||||
let opt = opt.into() as Opt;
|
let opt = opt.into() as Opt;
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let rx = emit!(Input(
|
let rx = emit!(Input(InputOpt::cd().with_value(opt.target.to_string_lossy())));
|
||||||
InputOpt::top("Change directory:")
|
|
||||||
.with_value(opt.target.to_string_lossy())
|
|
||||||
.with_completion()
|
|
||||||
));
|
|
||||||
|
|
||||||
let rx = Debounce::new(UnboundedReceiverStream::new(rx), Duration::from_millis(50));
|
let rx = Debounce::new(UnboundedReceiverStream::new(rx), Duration::from_millis(50));
|
||||||
pin!(rx);
|
pin!(rx);
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ use std::time::Duration;
|
||||||
|
|
||||||
use tokio::pin;
|
use tokio::pin;
|
||||||
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
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 yazi_shared::{Debounce, InputError};
|
||||||
|
|
||||||
use crate::{emit, input::InputOpt, tab::{Finder, FinderCase, Tab}};
|
use crate::{emit, tab::{Finder, FinderCase, Tab}};
|
||||||
|
|
||||||
pub struct Opt<'a> {
|
pub struct Opt<'a> {
|
||||||
query: Option<&'a str>,
|
query: Option<&'a str>,
|
||||||
|
|
@ -39,9 +39,7 @@ impl Tab {
|
||||||
pub fn find<'a>(&mut self, opt: impl Into<Opt<'a>>) -> bool {
|
pub fn find<'a>(&mut self, opt: impl Into<Opt<'a>>) -> bool {
|
||||||
let opt = opt.into() as Opt;
|
let opt = opt.into() as Opt;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let rx = emit!(Input(
|
let rx = emit!(Input(InputOpt::find(opt.prev)));
|
||||||
InputOpt::top(if opt.prev { "Find previous:" } else { "Find next:" }).with_realtime()
|
|
||||||
));
|
|
||||||
|
|
||||||
let rx = Debounce::new(UnboundedReceiverStream::new(rx), Duration::from_millis(50));
|
let rx = Debounce::new(UnboundedReceiverStream::new(rx), Duration::from_millis(50));
|
||||||
pin!(rx);
|
pin!(rx);
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ use std::{mem, time::Duration};
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use tokio::pin;
|
use tokio::pin;
|
||||||
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
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 struct Opt {
|
||||||
pub type_: OptType,
|
pub type_: OptType,
|
||||||
|
|
@ -45,9 +45,7 @@ impl Tab {
|
||||||
let hidden = self.conf.show_hidden;
|
let hidden = self.conf.show_hidden;
|
||||||
|
|
||||||
self.search = Some(tokio::spawn(async move {
|
self.search = Some(tokio::spawn(async move {
|
||||||
let Some(Ok(subject)) = emit!(Input(InputOpt::top("Search:"))).recv().await else {
|
let Some(Ok(subject)) = emit!(Input(InputOpt::search())).recv().await else { bail!("") };
|
||||||
bail!("")
|
|
||||||
};
|
|
||||||
|
|
||||||
cwd = cwd.into_search(subject.clone());
|
cwd = cwd.into_search(subject.clone());
|
||||||
let rx = if opt.type_ == OptType::Rg {
|
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 {
|
pub struct Opt {
|
||||||
cmd: String,
|
cmd: String,
|
||||||
|
|
@ -29,11 +29,7 @@ impl Tab {
|
||||||
let mut opt = opt.into() as Opt;
|
let mut opt = opt.into() as Opt;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if !opt.confirm || opt.cmd.is_empty() {
|
if !opt.confirm || opt.cmd.is_empty() {
|
||||||
let mut result = emit!(Input(
|
let mut result = emit!(Input(InputOpt::shell(opt.block).with_value(opt.cmd)));
|
||||||
InputOpt::top(if opt.block { "Shell (block):" } else { "Shell:" })
|
|
||||||
.with_value(opt.cmd)
|
|
||||||
.with_highlight()
|
|
||||||
));
|
|
||||||
match result.recv().await {
|
match result.recv().await {
|
||||||
Some(Ok(e)) => opt.cmd = e,
|
Some(Ok(e)) => opt.cmd = e,
|
||||||
_ => return,
|
_ => return,
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, path::Path, syn
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tracing::debug;
|
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 yazi_shared::{MimeKind, Term, Url};
|
||||||
|
|
||||||
use super::{running::Running, task::TaskSummary, Scheduler, TASKS_PADDING, TASKS_PERCENT};
|
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 struct Tasks {
|
||||||
pub(super) scheduler: Arc<Scheduler>,
|
pub(super) scheduler: Arc<Scheduler>,
|
||||||
|
|
@ -110,12 +110,11 @@ impl Tasks {
|
||||||
|
|
||||||
let scheduler = self.scheduler.clone();
|
let scheduler = self.scheduler.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let s = if targets.len() > 1 { "s" } else { "" };
|
let mut result = emit!(Input(if permanently {
|
||||||
let mut result = emit!(Input(InputOpt::hovered(if permanently {
|
InputOpt::delete(targets.len())
|
||||||
format!("Delete {} selected file{s} permanently? (y/N)", targets.len())
|
|
||||||
} else {
|
} 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 let Some(Ok(choice)) = result.recv().await {
|
||||||
if choice != "y" && choice != "Y" {
|
if choice != "y" && choice != "Y" {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use std::path::MAIN_SEPARATOR;
|
use std::path::MAIN_SEPARATOR;
|
||||||
|
|
||||||
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, Borders, Clear, List, ListItem, Widget}};
|
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, Borders, Clear, List, ListItem, Widget}};
|
||||||
use yazi_config::THEME;
|
use yazi_config::{popup::{Offset, Position}, THEME};
|
||||||
use yazi_core::{Ctx, Position};
|
use yazi_core::Ctx;
|
||||||
|
|
||||||
pub(crate) struct Completion<'a> {
|
pub(crate) struct Completion<'a> {
|
||||||
cx: &'a Ctx,
|
cx: &'a Ctx,
|
||||||
|
|
@ -39,15 +39,12 @@ impl<'a> Widget for Completion<'a> {
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let input_area = self.cx.area(&self.cx.input.position);
|
let input_area = self.cx.area(&self.cx.input.position);
|
||||||
let mut area = self.cx.area(&Position::Sticky(
|
let mut area = Position::sticky(input_area, Offset {
|
||||||
Rect {
|
x: 1,
|
||||||
x: 1,
|
y: 0,
|
||||||
y: 0,
|
width: input_area.width.saturating_sub(2),
|
||||||
width: input_area.width.saturating_sub(2),
|
height: items.len() as u16 + 2,
|
||||||
height: items.len() as u16 + 2,
|
});
|
||||||
},
|
|
||||||
input_area,
|
|
||||||
));
|
|
||||||
|
|
||||||
if area.y > input_area.y {
|
if area.y > input_area.y {
|
||||||
area.y = area.y.saturating_sub(1);
|
area.y = area.y.saturating_sub(1);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue