fix(mgr): restore hovered fallback for %s/%d shell-formatting when nothing is selected

The Splatable impl for Mgr used to fall back to the hovered item via
Tab::selected_or_hovered_urls() when the selection was empty, the same
convention used everywhere else (yank, copy, remove, open). PR #4108
rewrote this into MgrSnap/TabSnap but only ported the plain "selected"
list, dropping the hovered fallback. This made %s and %d expand to
nothing when hovering a file without an explicit selection.

Restore the fallback in MgrSnap::selected(), which both %s/%S and
%d/%D read from.

Resolves #4132

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: nabsei <nsahrane@hotmail.com>
This commit is contained in:
nabsei 2026-07-17 00:20:44 +02:00
parent 52640fcece
commit f6e7473db9

View file

@ -1,3 +1,5 @@
use std::iter;
use yazi_fs::Splatable;
use yazi_shared::url::{AsUrl, Url, UrlBuf};
@ -24,14 +26,15 @@ impl Splatable for MgrSnap {
fn selected(&self, tab: usize, mut idx: Option<usize>) -> impl Iterator<Item = Url<'_>> {
idx = idx.and_then(|i| i.checked_sub(1));
tab
.checked_sub(1)
.and_then(|tab| self.tabs.get(tab))
.map_or_else(|| &[][..], |s| &s.selected)
.iter()
.skip(idx.unwrap_or(0))
.take(if idx.is_some() { 1 } else { usize::MAX })
.map(AsUrl::as_url)
// Fall back to the hovered item when nothing is explicitly selected, same
// as `Tab::selected_or_hovered_urls()` used for file operations elsewhere.
let urls: Box<dyn Iterator<Item = &UrlBuf>> =
match tab.checked_sub(1).and_then(|tab| self.tabs.get(tab)) {
Some(s) if !s.selected.is_empty() => Box::new(s.selected.iter()),
Some(s) => Box::new(s.hovered.iter()),
None => Box::new(iter::empty()),
};
urls.skip(idx.unwrap_or(0)).take(if idx.is_some() { 1 } else { usize::MAX }).map(AsUrl::as_url)
}
fn hovered(&self, tab: usize) -> Option<Url<'_>> {
@ -52,3 +55,43 @@ impl Splatable for MgrSnap {
.map(AsUrl::as_url)
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
fn url(s: &str) -> UrlBuf { UrlBuf::from(PathBuf::from(s)) }
fn snap(selected: &[&str], hovered: Option<&str>) -> MgrSnap {
MgrSnap {
tab: 0,
tabs: vec![TabSnap {
hovered: hovered.map(url),
selected: selected.iter().map(|s| url(s)).collect(),
}],
yanked: vec![],
}
}
#[test]
fn selected_falls_back_to_hovered_when_nothing_selected() {
let s = snap(&[], Some("hovered/file"));
let urls: Vec<_> = s.selected(1, None).collect();
assert_eq!(urls, [url("hovered/file")]);
}
#[test]
fn selected_prefers_actual_selection_over_hovered() {
let s = snap(&["a", "b"], Some("hovered/file"));
let urls: Vec<_> = s.selected(1, None).collect();
assert_eq!(urls, [url("a"), url("b")]);
}
#[test]
fn selected_is_empty_when_nothing_selected_or_hovered() {
let s = snap(&[], None);
assert_eq!(s.selected(1, None).count(), 0);
}
}