feat: context-aware icons for inputs (#4080)

This commit is contained in:
三咲雅 misaki masa 2026-06-24 23:14:16 +08:00 committed by GitHub
parent 2a106aa231
commit ef654f4420
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 59 additions and 11 deletions

View file

@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Bulk create ([#3793])
- Make help menu a command palette ([#4074])
- H/M/L Vim-like motion for moving cursor relative to viewport ([#3970])
- Context-aware icons for inputs ([#4080])
- Dynamic keymap Lua API ([#4031])
- New `ui.Input` element ([#4040])
- Image preview with Überzug++ on Niri ([#3990])
@ -1763,3 +1764,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
[#4067]: https://github.com/sxyazi/yazi/pull/4067
[#4068]: https://github.com/sxyazi/yazi/pull/4068
[#4074]: https://github.com/sxyazi/yazi/pull/4074
[#4080]: https://github.com/sxyazi/yazi/pull/4080

View file

@ -21,9 +21,10 @@ impl Actor for Show {
let area = cx.mgr.area(opt.position).padding(cx.input.padding());
let input = &mut cx.input;
input.main.visible = true;
input.main.id = opt.id.clone();
input.main.title = opt.title.clone();
input.main.position = opt.position;
input.main.visible = true;
opt.styles = (&THEME.input).into();
opt.blinking = YAZI.input.cursor_blink;

View file

@ -42,7 +42,8 @@ impl Actor for Rename {
};
let (tab, old) = (cx.tab().id, hovered.url_owned());
let mut input = input!(cx, YAZI.input.rename().with_value(name).with_cursor(cursor))?;
let mut input =
input!(cx, YAZI.input.rename(hovered.is_dir()).with_value(name).with_cursor(cursor))?;
tokio::spawn(async move {
let Some(InputEvent::Submit(name)) = input.recv().await else { return };

View file

@ -47,6 +47,7 @@ pub struct Input {
impl Input {
pub fn cd(&self, cwd: Url) -> InputOpt {
InputOpt {
id: "cd".to_owned(),
title: self.cd_title.clone(),
value: if cwd.kind().is_local() { String::new() } else { EncodeScheme(cwd).to_string() },
position: Position::new(self.cd_origin, self.cd_offset),
@ -57,14 +58,16 @@ impl Input {
pub fn create(&self, dir: bool) -> InputOpt {
InputOpt {
id: "create".to_owned(),
title: self.create_title[dir as usize].clone(),
position: Position::new(self.create_origin, self.create_offset),
..Default::default()
}
}
pub fn rename(&self) -> InputOpt {
pub fn rename(&self, is_dir: bool) -> InputOpt {
InputOpt {
id: format!("rename-{}", if is_dir { "dir" } else { "file" }),
title: self.rename_title.clone(),
position: Position::new(self.rename_origin, self.rename_offset),
..Default::default()
@ -73,6 +76,7 @@ impl Input {
pub fn filter(&self) -> InputOpt {
InputOpt {
id: "filter".to_owned(),
title: self.filter_title.clone(),
position: Position::new(self.filter_origin, self.filter_offset),
realtime: true,
@ -82,6 +86,7 @@ impl Input {
pub fn find(&self, prev: bool) -> InputOpt {
InputOpt {
id: "find".to_owned(),
title: self.find_title[prev as usize].clone(),
position: Position::new(self.find_origin, self.find_offset),
realtime: true,
@ -91,6 +96,7 @@ impl Input {
pub fn search(&self, name: &str) -> InputOpt {
InputOpt {
id: "search".to_owned(),
title: self.search_title.replace("{n}", name),
position: Position::new(self.search_origin, self.search_offset),
..Default::default()
@ -99,6 +105,7 @@ impl Input {
pub fn shell(&self, block: bool) -> InputOpt {
InputOpt {
id: "shell".to_owned(),
title: self.shell_title[block as usize].clone(),
position: Position::new(self.shell_origin, self.shell_offset),
..Default::default()
@ -107,6 +114,7 @@ impl Input {
pub fn tab_rename(&self) -> InputOpt {
InputOpt {
id: "tab-rename".to_owned(),
title: "Rename tab:".to_owned(),
position: Position::new(Origin::TopCenter, Offset {
x: 0,

View file

@ -52,9 +52,10 @@ impl Input {
#[derive(Default)]
pub struct InputMain {
inner: yazi_widgets::input::Input,
pub visible: bool,
pub id: String,
pub title: String,
pub position: Position,
pub visible: bool,
}
impl Deref for InputMain {

View file

@ -1,7 +1,11 @@
use std::path::Path;
use ratatui_core::{buffer::Buffer, layout::{Margin, Rect}, text::Line, widgets::Widget};
use ratatui_widgets::{block::Block, borders::BorderType};
use yazi_config::THEME;
use yazi_config::{Icon, THEME};
use yazi_core::Core;
use yazi_fs::{cha::{Cha, ChaKind}, file::File};
use yazi_shim::path::CROSS_SEPARATOR;
pub(crate) struct Input<'a> {
core: &'a Core,
@ -9,21 +13,50 @@ pub(crate) struct Input<'a> {
impl<'a> Input<'a> {
pub(crate) fn new(core: &'a Core) -> Self { Self { core } }
fn icon(&self) -> Option<Icon> {
let input = &self.core.input.main;
let is_dir = input.value().ends_with(CROSS_SEPARATOR);
let is_hovered = input.id.starts_with("rename-");
let (path, mode): (&str, u16) = match input.id.as_str() {
"cd" => (input.value(), if is_dir { 0o40755 } else { 0o100644 }),
"create" => (input.value(), if is_dir { 0o40755 } else { 0o100644 }),
"rename-file" => (input.value(), 0o100644),
"rename-dir" => (input.value(), 0o40755),
"shell" => ("icon.sh", 0o100644),
_ => return None,
};
THEME.icon.matches(
&File {
url: Path::new(path).into(),
cha: Cha { kind: ChaKind::empty(), mode: mode.try_into().ok()?, ..Default::default() },
link_to: None,
},
is_hovered,
)
}
}
impl Widget for Input<'_> {
fn render(self, _: Rect, buf: &mut Buffer) {
let input = &self.core.input;
let input = &self.core.input.main;
let outer = self.core.mgr.area(input.main.position);
let outer = self.core.mgr.area(input.position);
yazi_widgets::clear::Clear::default().render(outer, buf);
Block::bordered()
let mut block = Block::bordered()
.border_type(BorderType::Rounded)
.border_style(THEME.input.border.get())
.title(Line::styled(&input.main.title, THEME.input.title.get()))
.render(outer, buf);
.title(Line::styled(&input.title, THEME.input.title.get()));
input.main.render(outer.inner(Margin::new(1, 1)), buf);
if let Some(i) = self.icon() {
block = block.title_bottom(Line::raw(format!("{} ", i.text)).style(i.style).right_aligned());
}
block.render(outer, buf);
input.render(outer.inner(Margin::new(1, 1)), buf);
}
}

View file

@ -6,6 +6,7 @@ use crate::input::{InputCallback, InputStyles};
#[derive(Clone, Debug, Default)]
pub struct InputOpt {
pub id: String,
pub title: String,
pub value: String,
pub styles: InputStyles,
@ -42,6 +43,7 @@ impl TryFrom<&Table> for InputOpt {
fn try_from(t: &Table) -> Result<Self, Self::Error> {
Ok(Self {
id: t.raw_get("id").unwrap_or_default(),
title: t.raw_get("title").unwrap_or_default(),
value: t.raw_get("value").unwrap_or_default(),
styles: t.raw_get("styles")?,