diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9a8d55dd..6e959026 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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 t for creating tabs to t ⇒ t to avoid conflict with new t ⇒ r 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
diff --git a/Cargo.lock b/Cargo.lock
index 10b5143c..d03446e4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/yazi-actor/src/lives/preference.rs b/yazi-actor/src/lives/preference.rs
index a7236363..f9f29a5c 100644
--- a/yazi-actor/src/lives/preference.rs
+++ b/yazi-actor/src/lives/preference.rs
@@ -8,8 +8,10 @@ use super::{Lives, PtrCell};
pub(super) struct Preference {
inner: PtrCell,
- v_sort_by: Option,
+ v_name: Option,
v_linemode: Option,
+
+ v_sort_by: Option,
}
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 {
- 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>(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));
}
}
diff --git a/yazi-actor/src/lives/tab.rs b/yazi-actor/src/lives/tab.rs
index 1c4ab5dd..69d5abb0 100644
--- a/yazi-actor/src/lives/tab.rs
+++ b/yazi-actor/src/lives/tab.rs
@@ -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>(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));
diff --git a/yazi-actor/src/mgr/mod.rs b/yazi-actor/src/mgr/mod.rs
index b6bf2d4e..e85db21a 100644
--- a/yazi-actor/src/mgr/mod.rs
+++ b/yazi-actor/src/mgr/mod.rs
@@ -42,6 +42,7 @@ yazi_macro::mod_flat!(
suspend
tab_close
tab_create
+ tab_rename
tab_swap
tab_switch
toggle
diff --git a/yazi-actor/src/mgr/tab_rename.rs b/yazi-actor/src/mgr/tab_rename.rs
new file mode 100644
index 00000000..b968a822
--- /dev/null
+++ b/yazi-actor/src/mgr/tab_rename.rs
@@ -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 {
+ 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!();
+ }
+}
diff --git a/yazi-config/preset/keymap-default.toml b/yazi-config/preset/keymap-default.toml
index 96eadc9d..bde93914 100644
--- a/yazi-config/preset/keymap-default.toml
+++ b/yazi-config/preset/keymap-default.toml
@@ -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" },
diff --git a/yazi-config/src/popup/options.rs b/yazi-config/src/popup/options.rs
index 25ab69e0..6170bfd9 100644
--- a/yazi-config/src/popup/options.rs
+++ b/yazi-config/src/popup/options.rs
@@ -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) -> Self {
self.value = value.into();
diff --git a/yazi-core/src/tab/preference.rs b/yazi-core/src/tab/preference.rs
index 275cd40c..99a07b18 100644
--- a/yazi-core/src/tab/preference.rs
+++ b/yazi-core/src/tab/preference.rs
@@ -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(),
}
}
}
diff --git a/yazi-core/src/tab/tab.rs b/yazi-core/src/tab/tab.rs
index 3050ad2a..156121e6 100644
--- a/yazi-core/src/tab/tab.rs
+++ b/yazi-core/src/tab/tab.rs
@@ -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() }
diff --git a/yazi-dds/src/spark/spark.rs b/yazi-dds/src/spark/spark.rs
index ae2a4c5f..f913966f 100644
--- a/yazi-dds/src/spark/spark.rs
+++ b/yazi-dds/src/spark/spark.rs
@@ -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);
diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs
index 922dd3eb..5c00c37f 100644
--- a/yazi-fm/src/executor.rs
+++ b/yazi-fm/src/executor.rs
@@ -141,6 +141,7 @@ impl<'a> Executor<'a> {
// Tabs
on!(tab_create);
+ on!(tab_rename);
on!(tab_close);
on!(tab_switch);
on!(tab_swap);
diff --git a/yazi-parser/src/mgr/mod.rs b/yazi-parser/src/mgr/mod.rs
index 8fe73145..ff9c10e9 100644
--- a/yazi-parser/src/mgr/mod.rs
+++ b/yazi-parser/src/mgr/mod.rs
@@ -30,6 +30,7 @@ yazi_macro::mod_flat!(
stash
tab_close
tab_create
+ tab_rename
tab_switch
toggle
toggle_all
diff --git a/yazi-parser/src/mgr/tab_rename.rs b/yazi-parser/src/mgr/tab_rename.rs
new file mode 100644
index 00000000..9ae2b0f0
--- /dev/null
+++ b/yazi-parser/src/mgr/tab_rename.rs
@@ -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,
+ pub interactive: bool,
+}
+
+impl TryFrom for TabRenameOpt {
+ type Error = anyhow::Error;
+
+ fn try_from(mut c: CmdCow) -> Result {
+ 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 { Err("unsupported".into_lua_err()) }
+}
+
+impl IntoLua for TabRenameOpt {
+ fn into_lua(self, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) }
+}
diff --git a/yazi-proxy/src/mgr.rs b/yazi-proxy/src/mgr.rs
index 757b0c6b..2cdd1a12 100644
--- a/yazi-proxy/src/mgr.rs
+++ b/yazi-proxy/src/mgr.rs
@@ -54,6 +54,10 @@ impl MgrProxy {
));
}
+ pub fn tab_rename(tab: Id, name: impl Into) {
+ 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)));
}
diff --git a/yazi-shared/Cargo.toml b/yazi-shared/Cargo.toml
index 3b01e66b..f610fcdd 100644
--- a/yazi-shared/Cargo.toml
+++ b/yazi-shared/Cargo.toml
@@ -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 }