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 tt to avoid conflict with new tr 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 }