mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: add show_icons and truncate_paths options to [confirm]
The trash/delete/overwrite confirmation dialogs (icons added in #4096) had no way to disable icons, and long file paths would hard word-wrap onto a new line right after the icon since a path has no internal spaces to break on cleanly. Add two options under [confirm], both independently toggleable from the existing [input] show_icons: - show_icons (default true): skip the icon lookup for confirm dialogs. - truncate_paths (default false): RTL-truncate each path with a leading "…" to fit the popup's configured width, instead of word-wrapping. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
b53a1b2ea7
commit
4a128fc1f5
6 changed files with 64 additions and 9 deletions
|
|
@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
||||||
- H/M/L Vim-like motion for moving cursor relative to viewport ([#3970])
|
- H/M/L Vim-like motion for moving cursor relative to viewport ([#3970])
|
||||||
- Context-aware icons for inputs ([#4080])
|
- Context-aware icons for inputs ([#4080])
|
||||||
- New `show_icons` option under `[input]` to toggle icons on input popups ([#4100])
|
- New `show_icons` option under `[input]` to toggle icons on input popups ([#4100])
|
||||||
|
- New `show_icons` and `truncate_paths` options under `[confirm]` to toggle icons and RTL-ellipsis-truncate long paths on trash/delete/overwrite confirmations ([#4100])
|
||||||
- Show file icons in trash/delete/overwrite confirmations ([#4096])
|
- Show file icons in trash/delete/overwrite confirmations ([#4096])
|
||||||
- Dynamic keymap Lua API ([#4031])
|
- Dynamic keymap Lua API ([#4031])
|
||||||
- New `ui.Input` element ([#4040])
|
- New `ui.Input` element ([#4040])
|
||||||
|
|
|
||||||
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -4882,6 +4882,7 @@ dependencies = [
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"toml",
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"unicode-width",
|
||||||
"yazi-binding",
|
"yazi-binding",
|
||||||
"yazi-codegen",
|
"yazi-codegen",
|
||||||
"yazi-fs",
|
"yazi-fs",
|
||||||
|
|
|
||||||
|
|
@ -39,3 +39,4 @@ serde = { workspace = true }
|
||||||
serde_with = { workspace = true }
|
serde_with = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
unicode-width = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -214,6 +214,9 @@ shell_origin = "top-center"
|
||||||
shell_offset = [ 0, 2, 50, 3 ]
|
shell_offset = [ 0, 2, 50, 3 ]
|
||||||
|
|
||||||
[confirm]
|
[confirm]
|
||||||
|
truncate_paths = false
|
||||||
|
show_icons = true
|
||||||
|
|
||||||
# trash
|
# trash
|
||||||
trash_title = "Trash {n} selected file{s}?"
|
trash_title = "Trash {n} selected file{s}?"
|
||||||
trash_origin = "center"
|
trash_origin = "center"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ use yazi_codegen::{DeserializeOver, DeserializeOver2};
|
||||||
|
|
||||||
#[derive(Deserialize, DeserializeOver, DeserializeOver2)]
|
#[derive(Deserialize, DeserializeOver, DeserializeOver2)]
|
||||||
pub struct Confirm {
|
pub struct Confirm {
|
||||||
|
pub truncate_paths: bool,
|
||||||
|
pub show_icons: bool,
|
||||||
|
|
||||||
// trash
|
// trash
|
||||||
pub trash_title: String,
|
pub trash_title: String,
|
||||||
pub trash_origin: Origin,
|
pub trash_origin: Origin,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use std::slice;
|
||||||
|
|
||||||
use ratatui_core::text::{Line, Span, Text};
|
use ratatui_core::text::{Line, Span, Text};
|
||||||
use ratatui_widgets::paragraph::{Paragraph, Wrap};
|
use ratatui_widgets::paragraph::{Paragraph, Wrap};
|
||||||
|
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||||
use yazi_binding::position::Position;
|
use yazi_binding::position::Position;
|
||||||
use yazi_fs::file::File;
|
use yazi_fs::file::File;
|
||||||
use yazi_macro::impl_data_any;
|
use yazi_macro::impl_data_any;
|
||||||
|
|
@ -9,6 +10,11 @@ use yazi_shared::strand::ToStrand;
|
||||||
|
|
||||||
use crate::{THEME, YAZI};
|
use crate::{THEME, YAZI};
|
||||||
|
|
||||||
|
// Mirrors the horizontal `Margin::new(2, 0)` the confirm list widget applies
|
||||||
|
// around its content (yazi-fm/src/confirm/list.rs), so a path truncated here
|
||||||
|
// to the popup's configured width doesn't still overflow once rendered.
|
||||||
|
const LIST_MARGIN: u16 = 4;
|
||||||
|
|
||||||
// --- PickCfg
|
// --- PickCfg
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct PickCfg {
|
pub struct PickCfg {
|
||||||
|
|
@ -46,29 +52,32 @@ impl ConfirmCfg {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trash(files: &[File]) -> Self {
|
pub fn trash(files: &[File]) -> Self {
|
||||||
|
let position = YAZI.confirm.trash_position();
|
||||||
Self::new(
|
Self::new(
|
||||||
Self::replace_number(&YAZI.confirm.trash_title, files.len()),
|
Self::replace_number(&YAZI.confirm.trash_title, files.len()),
|
||||||
YAZI.confirm.trash_position(),
|
position,
|
||||||
None,
|
None,
|
||||||
Self::truncate_files(files, 100),
|
Self::truncate_files(files, 100, position.width),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(files: &[File]) -> Self {
|
pub fn delete(files: &[File]) -> Self {
|
||||||
|
let position = YAZI.confirm.delete_position();
|
||||||
Self::new(
|
Self::new(
|
||||||
Self::replace_number(&YAZI.confirm.delete_title, files.len()),
|
Self::replace_number(&YAZI.confirm.delete_title, files.len()),
|
||||||
YAZI.confirm.delete_position(),
|
position,
|
||||||
None,
|
None,
|
||||||
Self::truncate_files(files, 100),
|
Self::truncate_files(files, 100, position.width),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn overwrite(file: &File) -> Self {
|
pub fn overwrite(file: &File) -> Self {
|
||||||
|
let position = YAZI.confirm.overwrite_position();
|
||||||
Self::new(
|
Self::new(
|
||||||
YAZI.confirm.overwrite_title.clone(),
|
YAZI.confirm.overwrite_title.clone(),
|
||||||
YAZI.confirm.overwrite_position(),
|
position,
|
||||||
Some(Text::raw(&YAZI.confirm.overwrite_body)),
|
Some(Text::raw(&YAZI.confirm.overwrite_body)),
|
||||||
Self::truncate_files(slice::from_ref(file), 1),
|
Self::truncate_files(slice::from_ref(file), 1, position.width),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,7 +110,9 @@ impl ConfirmCfg {
|
||||||
Some(Text::from_iter(lines))
|
Some(Text::from_iter(lines))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn truncate_files(files: &[File], max: usize) -> Option<Text<'static>> {
|
fn truncate_files(files: &[File], max: usize, width: u16) -> Option<Text<'static>> {
|
||||||
|
let budget = (width as usize).saturating_sub(LIST_MARGIN as usize);
|
||||||
|
|
||||||
let mut lines = Vec::with_capacity(files.len().min(max + 1));
|
let mut lines = Vec::with_capacity(files.len().min(max + 1));
|
||||||
for (i, f) in files.iter().enumerate() {
|
for (i, f) in files.iter().enumerate() {
|
||||||
if i >= max {
|
if i >= max {
|
||||||
|
|
@ -110,12 +121,47 @@ impl ConfirmCfg {
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.push(Line::default());
|
lines.push(Line::default());
|
||||||
if let Some(icon) = THEME.icon.matches(f, false) {
|
let mut prefix = 0;
|
||||||
|
if YAZI.confirm.show_icons
|
||||||
|
&& let Some(icon) = THEME.icon.matches(f, false)
|
||||||
|
{
|
||||||
|
prefix = icon.text.width() + 1;
|
||||||
lines[i].push_span(Span::styled(icon.text, icon.style));
|
lines[i].push_span(Span::styled(icon.text, icon.style));
|
||||||
lines[i].push_span(" ");
|
lines[i].push_span(" ");
|
||||||
}
|
}
|
||||||
lines[i].push_span(f.url.to_strand().into_string_lossy());
|
|
||||||
|
let path = f.url.to_strand().into_string_lossy();
|
||||||
|
let path = if YAZI.confirm.truncate_paths {
|
||||||
|
Self::truncate_rtl(&path, budget.saturating_sub(prefix))
|
||||||
|
} else {
|
||||||
|
path
|
||||||
|
};
|
||||||
|
lines[i].push_span(path);
|
||||||
}
|
}
|
||||||
Some(lines.into())
|
Some(lines.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Truncates `s` from the left with a leading `…`, keeping its tail, since
|
||||||
|
// the end of a path is usually more identifying than its start.
|
||||||
|
fn truncate_rtl(s: &str, max: usize) -> String {
|
||||||
|
if s.width() <= max {
|
||||||
|
return s.to_owned();
|
||||||
|
}
|
||||||
|
if max == 0 {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut adv = 0;
|
||||||
|
let mut idx = s.len();
|
||||||
|
for (i, c) in s.char_indices().rev() {
|
||||||
|
let w = c.width().unwrap_or(0);
|
||||||
|
if adv + w > max.saturating_sub(1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
adv += w;
|
||||||
|
idx = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
format!("…{}", &s[idx..])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue