mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: obscure input component for inputting passwords (#2675)
This commit is contained in:
parent
bfad57d86f
commit
9e9ebf0745
13 changed files with 59 additions and 67 deletions
|
|
@ -53,6 +53,7 @@ impl Brand {
|
||||||
("WEZTERM_EXECUTABLE", B::WezTerm),
|
("WEZTERM_EXECUTABLE", B::WezTerm),
|
||||||
("GHOSTTY_RESOURCES_DIR", B::Ghostty),
|
("GHOSTTY_RESOURCES_DIR", B::Ghostty),
|
||||||
("WT_Session", B::Microsoft),
|
("WT_Session", B::Microsoft),
|
||||||
|
("WARP_HONOR_PS1", B::Warp),
|
||||||
("VSCODE_INJECTION", B::VSCode),
|
("VSCODE_INJECTION", B::VSCode),
|
||||||
("TABBY_CONFIG_DIRECTORY", B::Tabby),
|
("TABBY_CONFIG_DIRECTORY", B::Tabby),
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@ pub struct InputCfg {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub value: String,
|
pub value: String,
|
||||||
pub cursor: Option<usize>,
|
pub cursor: Option<usize>,
|
||||||
|
pub obscure: bool,
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
pub realtime: bool,
|
pub realtime: bool,
|
||||||
pub completion: bool,
|
pub completion: bool,
|
||||||
pub highlight: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -86,7 +86,6 @@ impl InputCfg {
|
||||||
Self {
|
Self {
|
||||||
title: YAZI.input.shell_title[block as usize].to_owned(),
|
title: YAZI.input.shell_title[block as usize].to_owned(),
|
||||||
position: Position::new(YAZI.input.shell_origin, YAZI.input.shell_offset),
|
position: Position::new(YAZI.input.shell_origin, YAZI.input.shell_offset),
|
||||||
highlight: true,
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,6 @@ impl Input {
|
||||||
self.tx = Some(opt.tx.clone());
|
self.tx = Some(opt.tx.clone());
|
||||||
let ticket = self.ticket.clone();
|
let ticket = self.ticket.clone();
|
||||||
|
|
||||||
// Shell
|
|
||||||
self.highlight = opt.cfg.highlight;
|
|
||||||
|
|
||||||
// Reset input
|
// Reset input
|
||||||
let cb: InputCallback = Box::new(move |before, after| {
|
let cb: InputCallback = Box::new(move |before, after| {
|
||||||
if opt.cfg.realtime {
|
if opt.cfg.realtime {
|
||||||
|
|
@ -46,6 +43,7 @@ impl Input {
|
||||||
self.inner = yazi_widgets::input::Input::new(
|
self.inner = yazi_widgets::input::Input::new(
|
||||||
opt.cfg.value,
|
opt.cfg.value,
|
||||||
opt.cfg.position.offset.width.saturating_sub(YAZI.input.border()) as usize,
|
opt.cfg.position.offset.width.saturating_sub(YAZI.input.border()) as usize,
|
||||||
|
opt.cfg.obscure,
|
||||||
cb,
|
cb,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,6 @@ pub struct Input {
|
||||||
// Typing
|
// Typing
|
||||||
pub(super) tx: Option<UnboundedSender<Result<String, InputError>>>,
|
pub(super) tx: Option<UnboundedSender<Result<String, InputError>>>,
|
||||||
pub(super) ticket: Rc<Ids>,
|
pub(super) ticket: Rc<Ids>,
|
||||||
|
|
||||||
// Shell
|
|
||||||
pub highlight: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Input {
|
impl Deref for Input {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use ratatui::layout::Rect;
|
||||||
use yazi_adapter::Dimension;
|
use yazi_adapter::Dimension;
|
||||||
use yazi_config::popup::{Origin, Position};
|
use yazi_config::popup::{Origin, Position};
|
||||||
use yazi_fs::File;
|
use yazi_fs::File;
|
||||||
use yazi_shared::{Id, url::Url};
|
use yazi_shared::url::Url;
|
||||||
|
|
||||||
use super::{Mimetype, Tabs, Watcher, Yanked};
|
use super::{Mimetype, Tabs, Watcher, Yanked};
|
||||||
use crate::tab::{Folder, Tab};
|
use crate::tab::{Folder, Tab};
|
||||||
|
|
@ -47,26 +47,12 @@ impl Mgr {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn active_mut(&mut self) -> &mut Tab { self.tabs.active_mut() }
|
pub fn active_mut(&mut self) -> &mut Tab { self.tabs.active_mut() }
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn active_or(&self, id: Option<Id>) -> &Tab { self.tabs.active_or(id) }
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn active_or_mut(&mut self, id: Option<Id>) -> &mut Tab { self.tabs.active_or_mut(id) }
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn current(&self) -> &Folder { &self.active().current }
|
pub fn current(&self) -> &Folder { &self.active().current }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn current_mut(&mut self) -> &mut Folder { &mut self.active_mut().current }
|
pub fn current_mut(&mut self) -> &mut Folder { &mut self.active_mut().current }
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn current_or(&self, id: Option<Id>) -> &Folder { &self.active_or(id).current }
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn current_or_mut(&mut self, id: Option<Id>) -> &mut Folder {
|
|
||||||
&mut self.active_or_mut(id).current
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn parent(&self) -> Option<&Folder> { self.active().parent.as_ref() }
|
pub fn parent(&self) -> Option<&Folder> { self.active().parent.as_ref() }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,20 +49,6 @@ impl Tabs {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn active_mut(&mut self) -> &mut Tab { &mut self.items[self.cursor] }
|
pub(super) fn active_mut(&mut self) -> &mut Tab { &mut self.items[self.cursor] }
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn active_or(&self, id: Option<Id>) -> &Tab {
|
|
||||||
id.and_then(|id| self.iter().find(|&t| t.id == id)).unwrap_or(self.active())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(super) fn active_or_mut(&mut self, id: Option<Id>) -> &mut Tab {
|
|
||||||
if let Some(i) = id.and_then(|id| self.iter().position(|t| t.id == id)) {
|
|
||||||
&mut self.items[i]
|
|
||||||
} else {
|
|
||||||
self.active_mut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn current(&self) -> &Folder { &self.active().current }
|
pub fn current(&self) -> &Folder { &self.active().current }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ function M:entry(job)
|
||||||
|
|
||||||
local value, event = ya.input {
|
local value, event = ya.input {
|
||||||
title = string.format('Password for "%s":', from.name),
|
title = string.format('Password for "%s":', from.name),
|
||||||
|
obscure = true,
|
||||||
position = { "center", w = 50 },
|
position = { "center", w = 50 },
|
||||||
}
|
}
|
||||||
if event == 1 then
|
if event == 1 then
|
||||||
|
|
|
||||||
|
|
@ -45,10 +45,10 @@ impl Utils {
|
||||||
title: t.raw_get("title")?,
|
title: t.raw_get("title")?,
|
||||||
value: t.raw_get("value").unwrap_or_default(),
|
value: t.raw_get("value").unwrap_or_default(),
|
||||||
cursor: None, // TODO
|
cursor: None, // TODO
|
||||||
|
obscure: t.raw_get("obscure").unwrap_or_default(),
|
||||||
position: Pos::new_input(t.raw_get::<Table>("position")?)?.into(),
|
position: Pos::new_input(t.raw_get::<Table>("position")?)?.into(),
|
||||||
realtime,
|
realtime,
|
||||||
completion: false,
|
completion: false,
|
||||||
highlight: false,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if !realtime {
|
if !realtime {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use std::{num::ParseIntError, str::FromStr};
|
use std::{num::ParseIntError, str::FromStr};
|
||||||
|
|
||||||
use unicode_width::UnicodeWidthStr;
|
|
||||||
use yazi_macro::render;
|
use yazi_macro::render;
|
||||||
use yazi_shared::event::{CmdCow, Data};
|
use yazi_shared::event::{CmdCow, Data};
|
||||||
|
|
||||||
|
|
@ -40,10 +39,10 @@ impl Input {
|
||||||
snap.offset = 0;
|
snap.offset = 0;
|
||||||
} else {
|
} else {
|
||||||
let delta = snap.mode.delta();
|
let delta = snap.mode.delta();
|
||||||
let s = snap.slice(snap.offset..snap.cursor + delta);
|
let range = snap.offset..snap.cursor + delta;
|
||||||
if s.width() >= limit {
|
if snap.width(range.clone()) >= limit as u16 {
|
||||||
let range = InputSnap::find_window(s.chars().rev(), 0, limit);
|
let it = snap.slice(range).chars().rev().map(|c| if snap.obscure { '•' } else { c });
|
||||||
snap.offset = snap.cursor - range.end.saturating_sub(delta);
|
snap.offset = snap.cursor - InputSnap::find_window(it, 0, limit).end.saturating_sub(delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use std::ops::Range;
|
use std::{borrow::Cow, ops::Range};
|
||||||
|
|
||||||
use crossterm::cursor::SetCursorStyle;
|
use crossterm::cursor::SetCursorStyle;
|
||||||
use unicode_width::UnicodeWidthStr;
|
|
||||||
use yazi_config::YAZI;
|
use yazi_config::YAZI;
|
||||||
use yazi_plugin::CLIPBOARD;
|
use yazi_plugin::CLIPBOARD;
|
||||||
|
|
||||||
|
|
@ -13,12 +12,13 @@ pub type InputCallback = Box<dyn Fn(&str, &str)>;
|
||||||
pub struct Input {
|
pub struct Input {
|
||||||
pub snaps: InputSnaps,
|
pub snaps: InputSnaps,
|
||||||
pub limit: usize,
|
pub limit: usize,
|
||||||
|
pub obscure: bool,
|
||||||
pub callback: Option<InputCallback>,
|
pub callback: Option<InputCallback>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
pub fn new(value: String, limit: usize, callback: InputCallback) -> Self {
|
pub fn new(value: String, limit: usize, obscure: bool, callback: InputCallback) -> Self {
|
||||||
Self { snaps: InputSnaps::new(value, limit), limit, callback: Some(callback) }
|
Self { snaps: InputSnaps::new(value, obscure, limit), limit, obscure, callback: Some(callback) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn handle_op(&mut self, cursor: usize, include: bool) -> bool {
|
pub(super) fn handle_op(&mut self, cursor: usize, include: bool) -> bool {
|
||||||
|
|
@ -75,16 +75,19 @@ impl Input {
|
||||||
pub fn value(&self) -> &str { &self.snap().value }
|
pub fn value(&self) -> &str { &self.snap().value }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn visible_value(&self) -> &str { self.snap().slice(self.snap().window(self.limit)) }
|
pub fn display(&self) -> Cow<str> {
|
||||||
|
if self.obscure {
|
||||||
|
"•".repeat(self.snap().window(self.limit).len()).into()
|
||||||
|
} else {
|
||||||
|
self.snap().slice(self.snap().window(self.limit)).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn mode(&self) -> InputMode { self.snap().mode }
|
pub fn mode(&self) -> InputMode { self.snap().mode }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn cursor(&self) -> u16 {
|
pub fn cursor(&self) -> u16 { self.snap().width(self.snap().offset..self.snap().cursor) }
|
||||||
let snap = self.snap();
|
|
||||||
snap.slice(snap.offset..snap.cursor).width() as u16
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cursor_shape(&self) -> SetCursorStyle {
|
pub fn cursor_shape(&self) -> SetCursorStyle {
|
||||||
use InputMode as M;
|
use InputMode as M;
|
||||||
|
|
@ -110,8 +113,8 @@ impl Input {
|
||||||
let win = snap.window(self.limit);
|
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.width(snap.offset..start);
|
||||||
Some(s..s + snap.slice(start..end).width() as u16)
|
Some(s..s + snap.width(start..end))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use unicode_width::UnicodeWidthChar;
|
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||||
|
|
||||||
use super::{InputMode, InputOp};
|
use super::{InputMode, InputOp};
|
||||||
|
|
||||||
|
|
@ -10,20 +10,24 @@ pub struct InputSnap {
|
||||||
|
|
||||||
pub op: InputOp,
|
pub op: InputOp,
|
||||||
|
|
||||||
pub mode: InputMode,
|
pub mode: InputMode,
|
||||||
|
pub obscure: bool,
|
||||||
|
|
||||||
pub offset: usize,
|
pub offset: usize,
|
||||||
pub cursor: usize,
|
pub cursor: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputSnap {
|
impl InputSnap {
|
||||||
pub(super) fn new(value: String, limit: usize) -> Self {
|
pub(super) fn new(value: String, obscure: bool, limit: usize) -> Self {
|
||||||
let mut snap = Self {
|
let mut snap = Self {
|
||||||
value,
|
value,
|
||||||
|
|
||||||
op: Default::default(),
|
op: Default::default(),
|
||||||
|
|
||||||
mode: Default::default(),
|
mode: Default::default(),
|
||||||
offset: usize::MAX,
|
obscure,
|
||||||
|
|
||||||
|
offset: 0,
|
||||||
cursor: usize::MAX,
|
cursor: usize::MAX,
|
||||||
};
|
};
|
||||||
snap.resize(limit);
|
snap.resize(limit);
|
||||||
|
|
@ -32,9 +36,19 @@ impl InputSnap {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn resize(&mut self, limit: usize) {
|
pub(super) fn resize(&mut self, limit: usize) {
|
||||||
let range = Self::find_window(self.value.chars().rev(), 0, limit);
|
let count = self.count();
|
||||||
|
let limit = if self.obscure {
|
||||||
|
count.min(limit)
|
||||||
|
} else {
|
||||||
|
Self::find_window(self.value.chars().rev(), 0, limit).end
|
||||||
|
};
|
||||||
|
|
||||||
self.cursor = self.cursor.min(self.count().saturating_sub(self.mode.delta()));
|
self.cursor = self.cursor.min(self.count().saturating_sub(self.mode.delta()));
|
||||||
self.offset = self.offset.min(self.cursor.saturating_sub(range.end));
|
self.offset = if self.cursor < (self.offset + limit).min(count) {
|
||||||
|
count.saturating_sub(limit).min(self.offset)
|
||||||
|
} else {
|
||||||
|
count.saturating_sub(limit).min(self.cursor.saturating_sub(limit) + 1)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,11 +76,19 @@ impl InputSnap {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn window(&self, limit: usize) -> Range<usize> {
|
pub(super) fn width(&self, range: Range<usize>) -> u16 {
|
||||||
Self::find_window(self.value.chars(), self.offset, limit)
|
if self.obscure { range.len() as u16 } else { self.slice(range).width() as u16 }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
pub(super) fn window(&self, limit: usize) -> Range<usize> {
|
||||||
|
Self::find_window(
|
||||||
|
self.value.chars().map(|c| if self.obscure { '•' } else { c }),
|
||||||
|
self.offset,
|
||||||
|
limit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn find_window<T>(it: T, offset: usize, limit: usize) -> Range<usize>
|
pub(super) fn find_window<T>(it: T, offset: usize, limit: usize) -> Range<usize>
|
||||||
where
|
where
|
||||||
T: Iterator<Item = char>,
|
T: Iterator<Item = char>,
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,15 @@ impl Default for InputSnaps {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
idx: 0,
|
idx: 0,
|
||||||
versions: vec![InputSnap::new(String::new(), 0)],
|
versions: vec![InputSnap::new(String::new(), false, 0)],
|
||||||
current: InputSnap::new(String::new(), 0),
|
current: InputSnap::new(String::new(), false, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputSnaps {
|
impl InputSnaps {
|
||||||
pub fn new(value: String, limit: usize) -> Self {
|
pub fn new(value: String, obscure: bool, limit: usize) -> Self {
|
||||||
let current = InputSnap::new(value, limit);
|
let current = InputSnap::new(value, obscure, limit);
|
||||||
Self { idx: 0, versions: vec![current.clone()], current }
|
Self { idx: 0, versions: vec![current.clone()], current }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ impl Widget for &Input {
|
||||||
{
|
{
|
||||||
yazi_plugin::elements::Clear::default().render(area, buf);
|
yazi_plugin::elements::Clear::default().render(area, buf);
|
||||||
|
|
||||||
Line::styled(self.visible_value(), THEME.input.value).render(area, buf);
|
Line::styled(self.display(), THEME.input.value).render(area, buf);
|
||||||
|
|
||||||
if let Some(Range { start, end }) = self.selected() {
|
if let Some(Range { start, end }) = self.selected() {
|
||||||
let s = start.min(area.width);
|
let s = start.min(area.width);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue