feat!: custom tab name (#3666)

This commit is contained in:
三咲雅 misaki masa 2026-02-08 10:46:38 +08:00 committed by GitHub
parent dc66f79cef
commit 0aeeb3b0f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 148 additions and 23 deletions

View file

@ -16,12 +16,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Allow using `ps.sub()` in `init.lua` directly without a plugin ([#3638])
- New `relay-notify-push` DDS event to allow custom notification handlers ([#3642])
- Custom tab name ([#3666])
- New `cx.which` API to access the which component state ([#3617])
- New `ind-which-activate` DDS event to change the which component behavior ([#3608])
### Changed
- Upgrade Lua to 5.5 ([#3633])
- Change preset <kbd>t</kbd> for creating tabs to <kbd>t</kbd><kbd>t</kbd> to avoid conflict with new <kbd>t</kbd><kbd>r</kbd> for renaming tabs ([#3666])
- Remove `micro_workers` and `macro_workers` in favor of finer control over concurrent workers ([#3661])
### Fixed
@ -1642,3 +1644,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
[#3642]: https://github.com/sxyazi/yazi/pull/3642
[#3648]: https://github.com/sxyazi/yazi/pull/3648
[#3661]: https://github.com/sxyazi/yazi/pull/3661
[#3666]: https://github.com/sxyazi/yazi/pull/3666

4
Cargo.lock generated
View file

@ -2404,9 +2404,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]]
name = "memchr"
version = "2.7.6"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "memmem"

View file

@ -8,8 +8,10 @@ use super::{Lives, PtrCell};
pub(super) struct Preference {
inner: PtrCell<yazi_core::tab::Preference>,
v_sort_by: Option<Value>,
v_name: Option<Value>,
v_linemode: Option<Value>,
v_sort_by: Option<Value>,
}
impl Deref for Preference {
@ -20,19 +22,29 @@ impl Deref for Preference {
impl Preference {
pub(super) fn make(inner: &yazi_core::tab::Preference) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { inner: inner.into(), v_sort_by: None, v_linemode: None })
Lives::scoped_userdata(Self {
inner: inner.into(),
v_name: None,
v_linemode: None,
v_sort_by: None,
})
}
}
impl UserData for Preference {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
// Display
cached_field!(fields, name, |lua, me| lua.create_string(&me.name));
cached_field!(fields, linemode, |lua, me| lua.create_string(&me.linemode));
fields.add_field_method_get("show_hidden", |_, me| Ok(me.show_hidden));
// Sorting
cached_field!(fields, sort_by, |_, me| Ok(me.sort_by.to_string()));
fields.add_field_method_get("sort_sensitive", |_, me| Ok(me.sort_sensitive));
fields.add_field_method_get("sort_reverse", |_, me| Ok(me.sort_reverse));
fields.add_field_method_get("sort_dir_first", |_, me| Ok(me.sort_dir_first));
fields.add_field_method_get("sort_translit", |_, me| Ok(me.sort_translit));
cached_field!(fields, linemode, |lua, me| lua.create_string(&me.linemode));
fields.add_field_method_get("show_hidden", |_, me| Ok(me.show_hidden));
}
}

View file

@ -1,8 +1,7 @@
use std::ops::Deref;
use std::{borrow::Cow, ops::Deref};
use mlua::{AnyUserData, UserData, UserDataFields, UserDataMethods, Value};
use yazi_binding::{Id, UrlRef, cached_field};
use yazi_shared::url::UrlLike;
use super::{Finder, Folder, Lives, Mode, Preference, Preview, PtrCell, Selected};
@ -46,8 +45,10 @@ impl UserData for Tab {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
cached_field!(fields, name, |lua, me| {
let url = &me.current.url;
lua.create_string(url.name().map_or_else(|| url.loc().encoded_bytes(), |n| n.encoded_bytes()))
match me.name() {
Cow::Borrowed(s) => lua.create_string(s),
Cow::Owned(s) => lua.create_external_string(s),
}
});
cached_field!(fields, mode, |_, me| Mode::make(&me.mode));

View file

@ -42,6 +42,7 @@ yazi_macro::mod_flat!(
suspend
tab_close
tab_create
tab_rename
tab_swap
tab_switch
toggle

View file

@ -0,0 +1,38 @@
use std::borrow::Cow;
use anyhow::Result;
use yazi_config::popup::InputCfg;
use yazi_macro::{render, succ};
use yazi_parser::mgr::TabRenameOpt;
use yazi_proxy::{InputProxy, MgrProxy};
use yazi_shared::data::Data;
use crate::{Actor, Ctx};
pub struct TabRename;
impl Actor for TabRename {
type Options = TabRenameOpt;
const NAME: &str = "tab_rename";
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
let tab = cx.tab().id;
let pref = &mut cx.tab_mut().pref;
if !opt.interactive {
pref.name = opt.name.unwrap_or_default().into_owned();
succ!(render!());
}
let mut input = InputProxy::show(
InputCfg::tab_rename().with_value(opt.name.unwrap_or(Cow::Borrowed(&pref.name))),
);
tokio::spawn(async move {
if let Some(Ok(name)) = input.recv().await {
MgrProxy::tab_rename(tab, name);
}
});
succ!();
}
}

View file

@ -131,7 +131,8 @@ keymap = [
{ on = [ "g", "f" ], run = "follow", desc = "Follow hovered symlink" },
# Tabs
{ on = "t", run = "tab_create --current", desc = "Create a new tab with CWD" },
{ on = [ "t", "t" ], run = "tab_create --current", desc = "Create a new tab in CWD" },
{ on = [ "t", "r" ], run = "tab_rename --interactive", desc = "Rename current tab" },
{ on = "1", run = "tab_switch 0", desc = "Switch to first tab" },
{ on = "2", run = "tab_switch 1", desc = "Switch to second tab" },

View file

@ -2,7 +2,7 @@ use ratatui::{text::{Line, Text}, widgets::{Paragraph, Wrap}};
use yazi_shared::{scheme::Encode as EncodeScheme, strand::ToStrand, url::{Url, UrlBuf}};
use super::{Offset, Position};
use crate::YAZI;
use crate::{YAZI, popup::Origin};
#[derive(Clone, Debug, Default)]
pub struct InputCfg {
@ -91,6 +91,19 @@ impl InputCfg {
}
}
pub fn tab_rename() -> Self {
Self {
title: "Rename tab:".to_owned(),
position: Position::new(Origin::TopCenter, Offset {
x: 0,
y: 2,
width: 50,
height: 3,
}),
..Default::default()
}
}
#[inline]
pub fn with_value(mut self, value: impl Into<String>) -> Self {
self.value = value.into();

View file

@ -3,31 +3,33 @@ use yazi_fs::{FilesSorter, SortBy};
#[derive(Clone, PartialEq)]
pub struct Preference {
// Display
pub name: String,
pub linemode: String,
pub show_hidden: bool,
// Sorting
pub sort_by: SortBy,
pub sort_sensitive: bool,
pub sort_reverse: bool,
pub sort_dir_first: bool,
pub sort_translit: bool,
// Display
pub linemode: String,
pub show_hidden: bool,
}
impl Default for Preference {
fn default() -> Self {
Self {
// Display
name: String::new(),
linemode: YAZI.mgr.linemode.clone(),
show_hidden: YAZI.mgr.show_hidden.get(),
// Sorting
sort_by: YAZI.mgr.sort_by.get(),
sort_sensitive: YAZI.mgr.sort_sensitive.get(),
sort_reverse: YAZI.mgr.sort_reverse.get(),
sort_dir_first: YAZI.mgr.sort_dir_first.get(),
sort_translit: YAZI.mgr.sort_translit.get(),
// Display
linemode: YAZI.mgr.linemode.clone(),
show_hidden: YAZI.mgr.show_hidden.get(),
}
}
}

View file

@ -1,10 +1,12 @@
use std::borrow::Cow;
use anyhow::Result;
use ratatui::layout::Rect;
use tokio::task::JoinHandle;
use yazi_config::{LAYOUT, popup::{Origin, Position}};
use yazi_emulator::Dimension;
use yazi_fs::File;
use yazi_shared::{Id, Ids, url::UrlBuf};
use yazi_shared::{Id, Ids, url::{UrlBuf, UrlLike}};
use super::{Backstack, Finder, Folder, History, Mode, Preference, Preview};
use crate::{spot::Spot, tab::Selected};
@ -61,6 +63,17 @@ impl Tab {
#[inline]
pub fn cwd(&self) -> &UrlBuf { &self.current.url }
pub fn name(&self) -> Cow<'_, str> {
let url = &self.current.url;
if !self.pref.name.is_empty() {
Cow::Borrowed(&self.pref.name)
} else if let Some(s) = url.name() {
s.to_string_lossy()
} else {
url.loc().to_string_lossy()
}
}
#[inline]
pub fn hovered(&self) -> Option<&File> { self.current.hovered() }

View file

@ -74,6 +74,7 @@ pub enum Spark<'a> {
Suspend(yazi_parser::VoidOpt),
TabClose(yazi_parser::mgr::TabCloseOpt),
TabCreate(yazi_parser::mgr::TabCreateOpt),
TabRename(yazi_parser::mgr::TabRenameOpt),
TabSwap(yazi_parser::ArrowOpt),
TabSwitch(yazi_parser::mgr::TabSwitchOpt),
Toggle(yazi_parser::mgr::ToggleOpt),
@ -245,6 +246,7 @@ impl<'a> IntoLua for Spark<'a> {
Self::Suspend(b) => b.into_lua(lua),
Self::TabClose(b) => b.into_lua(lua),
Self::TabCreate(b) => b.into_lua(lua),
Self::TabRename(b) => b.into_lua(lua),
Self::TabSwap(b) => b.into_lua(lua),
Self::TabSwitch(b) => b.into_lua(lua),
Self::Toggle(b) => b.into_lua(lua),
@ -396,6 +398,7 @@ try_from_spark!(yazi_parser::mgr::SpotOpt, mgr:spot);
try_from_spark!(yazi_parser::mgr::StashOpt, mgr:stash);
try_from_spark!(yazi_parser::mgr::TabCloseOpt, mgr:tab_close);
try_from_spark!(yazi_parser::mgr::TabCreateOpt, mgr:tab_create);
try_from_spark!(yazi_parser::mgr::TabRenameOpt, mgr:tab_rename);
try_from_spark!(yazi_parser::mgr::TabSwitchOpt, mgr:tab_switch);
try_from_spark!(yazi_parser::mgr::ToggleAllOpt, mgr:toggle_all);
try_from_spark!(yazi_parser::mgr::ToggleOpt, mgr:toggle);

View file

@ -141,6 +141,7 @@ impl<'a> Executor<'a> {
// Tabs
on!(tab_create);
on!(tab_rename);
on!(tab_close);
on!(tab_switch);
on!(tab_swap);

View file

@ -30,6 +30,7 @@ yazi_macro::mod_flat!(
stash
tab_close
tab_create
tab_rename
tab_switch
toggle
toggle_all

View file

@ -0,0 +1,32 @@
use anyhow::bail;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use yazi_shared::{SStr, event::CmdCow};
#[derive(Debug)]
pub struct TabRenameOpt {
pub name: Option<SStr>,
pub interactive: bool,
}
impl TryFrom<CmdCow> for TabRenameOpt {
type Error = anyhow::Error;
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
let name = c.take_first().ok();
let interactive = c.bool("interactive");
if name.is_none() && !interactive {
bail!("either name or interactive must be specified in TabRenameOpt");
}
Ok(Self { name, interactive })
}
}
impl FromLua for TabRenameOpt {
fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err("unsupported".into_lua_err()) }
}
impl IntoLua for TabRenameOpt {
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
}

View file

@ -54,6 +54,10 @@ impl MgrProxy {
));
}
pub fn tab_rename(tab: Id, name: impl Into<SStr>) {
emit!(Call(relay!(mgr:tab_rename, [name.into()]).with("tab", tab)));
}
pub fn update_paged_by(page: usize, only_if: &UrlBuf) {
emit!(Call(relay!(mgr:update_paged, [page]).with("only-if", only_if)));
}

View file

@ -22,7 +22,7 @@ dyn-clone = { workspace = true }
foldhash = { workspace = true }
futures = { workspace = true }
hashbrown = { workspace = true }
memchr = "2.7.6"
memchr = "2.8.0"
ordered-float = { workspace = true }
parking_lot = { workspace = true }
percent-encoding = { workspace = true }