fix: make every effort to carry hidden states for dummy files (#2814)

This commit is contained in:
三咲雅 misaki masa 2025-05-31 00:08:50 +08:00 committed by GitHub
parent 4076e35a2f
commit 4fdc0f120f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 175 additions and 165 deletions

View file

@ -94,7 +94,7 @@ impl Mgr {
failed.push((o, n, anyhow!("Destination already exists"))); failed.push((o, n, anyhow!("Destination already exists")));
} else if let Err(e) = fs::rename(&old, &new).await { } else if let Err(e) = fs::rename(&old, &new).await {
failed.push((o, n, e.into())); failed.push((o, n, e.into()));
} else if let Ok(f) = File::from(new.into()).await { } else if let Ok(f) = File::new(new.into()).await {
succeeded.insert(Url::from(old), f); succeeded.insert(Url::from(old), f);
} else { } else {
failed.push((o, n, anyhow!("Failed to retrieve file info"))); failed.push((o, n, anyhow!("Failed to retrieve file info")));

View file

@ -1,5 +1,3 @@
use std::collections::{HashMap, HashSet};
use anyhow::Result; use anyhow::Result;
use tokio::fs; use tokio::fs;
use yazi_config::popup::{ConfirmCfg, InputCfg}; use yazi_config::popup::{ConfirmCfg, InputCfg};
@ -50,7 +48,7 @@ impl Mgr {
fs::create_dir_all(&new).await?; fs::create_dir_all(&new).await?;
} else if let Some(real) = realname(&new).await { } else if let Some(real) = realname(&new).await {
ok_or_not_found(fs::remove_file(&new).await)?; ok_or_not_found(fs::remove_file(&new).await)?;
FilesOp::Deleting(parent.clone(), HashSet::from_iter([UrnBuf::from(real)])).emit(); FilesOp::Deleting(parent.clone(), [UrnBuf::from(real)].into()).emit();
fs::File::create(&new).await?; fs::File::create(&new).await?;
} else { } else {
fs::create_dir_all(&parent).await.ok(); fs::create_dir_all(&parent).await.ok();
@ -58,8 +56,8 @@ impl Mgr {
fs::File::create(&new).await?; fs::File::create(&new).await?;
} }
if let Ok(f) = File::from(new.clone()).await { if let Ok(f) = File::new(new.clone()).await {
FilesOp::Upserting(parent, HashMap::from_iter([(f.urn_owned(), f)])).emit(); FilesOp::Upserting(parent, [(f.urn_owned(), f)].into()).emit();
TabProxy::reveal(&new) TabProxy::reveal(&new)
} }
Ok(()) Ok(())

View file

@ -56,7 +56,7 @@ impl Mgr {
tokio::spawn(async move { tokio::spawn(async move {
let mut files = Vec::with_capacity(todo.len()); let mut files = Vec::with_capacity(todo.len());
for u in todo { for u in todo {
if let Ok(f) = File::from(u).await { if let Ok(f) = File::new(u).await {
files.push(f); files.push(f);
} }
} }

View file

@ -1,4 +1,4 @@
use std::{borrow::Cow, collections::{HashMap, HashSet}}; use std::borrow::Cow;
use anyhow::Result; use anyhow::Result;
use tokio::fs; use tokio::fs;
@ -80,15 +80,15 @@ impl Mgr {
if let Some(o) = overwritten { if let Some(o) = overwritten {
ok_or_not_found(fs::rename(p_new.join(&o), &new).await)?; ok_or_not_found(fs::rename(p_new.join(&o), &new).await)?;
FilesOp::Deleting(p_new.clone(), HashSet::from_iter([UrnBuf::from(o)])).emit(); FilesOp::Deleting(p_new.clone(), [UrnBuf::from(o)].into()).emit();
} }
let file = File::from(new.clone()).await?; let file = File::new(new.clone()).await?;
if p_new == p_old { if p_new == p_old {
FilesOp::Upserting(p_old, HashMap::from_iter([(n_old, file)])).emit(); FilesOp::Upserting(p_old, [(n_old, file)].into()).emit();
} else { } else {
FilesOp::Deleting(p_old, HashSet::from_iter([n_old])).emit(); FilesOp::Deleting(p_old, [n_old].into()).emit();
FilesOp::Upserting(p_new, HashMap::from_iter([(n_new, file)])).emit(); FilesOp::Upserting(p_new, [(n_new, file)].into()).emit();
} }
TabProxy::reveal(&new); TabProxy::reveal(&new);

View file

@ -134,8 +134,8 @@ impl Watcher {
for u in urls { for u in urls {
let Some((parent, urn)) = u.pair() else { continue }; let Some((parent, urn)) = u.pair() else { continue };
let Ok(file) = File::from(u).await else { let Ok(file) = File::new(u).await else {
ops.push(FilesOp::Deleting(parent, HashSet::from_iter([urn]))); ops.push(FilesOp::Deleting(parent, [urn].into()));
continue; continue;
}; };
@ -144,11 +144,11 @@ impl Watcher {
|| realname_unchecked(u, &mut cached).await.is_ok_and(|s| urn.as_urn() == s); || realname_unchecked(u, &mut cached).await.is_ok_and(|s| urn.as_urn() == s);
if !eq { if !eq {
ops.push(FilesOp::Deleting(parent, HashSet::from_iter([urn]))); ops.push(FilesOp::Deleting(parent, [urn].into()));
continue; continue;
} }
ops.push(FilesOp::Upserting(parent, HashMap::from_iter([(urn, file)]))); ops.push(FilesOp::Upserting(parent, [(urn, file)].into()));
} }
FilesOp::mutate(ops); FilesOp::mutate(ops);

View file

@ -1,10 +1,10 @@
use std::{mem, time::Duration}; use std::{mem, time::Duration};
use tokio::{fs, pin}; use tokio::pin;
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg; use yazi_config::popup::InputCfg;
use yazi_dds::Pubsub; use yazi_dds::Pubsub;
use yazi_fs::expand_path; use yazi_fs::{File, FilesOp, expand_path};
use yazi_macro::render; use yazi_macro::render;
use yazi_proxy::{CmpProxy, InputProxy, MgrProxy, TabProxy}; use yazi_proxy::{CmpProxy, InputProxy, MgrProxy, TabProxy};
use yazi_shared::{Debounce, errors::InputError, event::CmdCow, url::Url}; use yazi_shared::{Debounce, errors::InputError, event::CmdCow, url::Url};
@ -94,16 +94,17 @@ impl Tab {
while let Some(result) = rx.next().await { while let Some(result) = rx.next().await {
match result { match result {
Ok(s) => { Ok(s) => {
let u = Url::from(expand_path(s)); let url = Url::from(expand_path(s));
let Ok(meta) = fs::metadata(&u).await else {
return;
};
if meta.is_dir() { let Ok(file) = File::new(url.clone()).await else { return };
TabProxy::cd(&u); if file.is_dir() {
} else { return TabProxy::cd(&url);
TabProxy::reveal(&u);
} }
if let Some(p) = url.parent_url() {
FilesOp::Upserting(p, [(url.urn_owned(), file)].into()).emit();
}
TabProxy::reveal(&url);
} }
Err(InputError::Completed(before, ticket)) => { Err(InputError::Completed(before, ticket)) => {
CmpProxy::trigger(&before, ticket); CmpProxy::trigger(&before, ticket);

View file

@ -70,7 +70,7 @@ impl Tab {
while let Some(chunk) = rx.next().await { while let Some(chunk) = rx.next().await {
FilesOp::Part(cwd.clone(), chunk, ticket).emit(); FilesOp::Part(cwd.clone(), chunk, ticket).emit();
} }
FilesOp::Done(cwd, Cha::dummy(), ticket).emit(); FilesOp::Done(cwd, Cha::default(), ticket).emit();
Ok(()) Ok(())
})); }));

View file

@ -46,7 +46,7 @@ impl Folder {
(self.cha, self.stage) = (cha, FolderStage::Loaded); (self.cha, self.stage) = (cha, FolderStage::Loaded);
} }
FilesOp::Part(_, ref files, _) if files.is_empty() => { FilesOp::Part(_, ref files, _) if files.is_empty() => {
(self.cha, self.stage) = (Cha::dummy(), FolderStage::Loading); (self.cha, self.stage) = (Cha::default(), FolderStage::Loading);
} }
FilesOp::Part(_, _, ticket) if ticket == self.files.ticket() => { FilesOp::Part(_, _, ticket) if ticket == self.files.ticket() => {
self.stage = FolderStage::Loading; self.stage = FolderStage::Loading;
@ -55,7 +55,7 @@ impl Folder {
(self.cha, self.stage) = (cha, FolderStage::Loaded); (self.cha, self.stage) = (cha, FolderStage::Loaded);
} }
FilesOp::IOErr(_, kind) => { FilesOp::IOErr(_, kind) => {
(self.cha, self.stage) = (Cha::dummy(), FolderStage::Failed(kind)); (self.cha, self.stage) = (Cha::default(), FolderStage::Failed(kind));
} }
_ => {} _ => {}
} }

View file

@ -48,7 +48,7 @@ impl Preview {
Some(PreviewLock { url: wd.clone(), cha, mime: MIME_DIR.to_owned(), ..Default::default() }); Some(PreviewLock { url: wd.clone(), cha, mime: MIME_DIR.to_owned(), ..Default::default() });
self.folder_loader.take().map(|h| h.abort()); self.folder_loader.take().map(|h| h.abort());
self.folder_loader = Some(tokio::spawn(async move { self.folder_loader = Some(tokio::spawn(async move {
let Some(new) = Files::assert_stale(&wd, dir.unwrap_or(Cha::dummy())).await else { return }; let Some(new) = Files::assert_stale(&wd, dir.unwrap_or_default()).await else { return };
let rx = match Files::from_dir(&wd).await { let rx = match Files::from_dir(&wd).await {
Ok(rx) => rx, Ok(rx) => rx,

View file

@ -1,10 +1,12 @@
use std::{fs::{FileType, Metadata}, path::Path, time::SystemTime}; use std::{fs::{FileType, Metadata}, path::Path, time::SystemTime};
use tokio::fs;
use yazi_macro::{unix_either, win_either}; use yazi_macro::{unix_either, win_either};
use yazi_shared::url::Url;
use super::ChaKind; use super::ChaKind;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Cha { pub struct Cha {
pub kind: ChaKind, pub kind: ChaKind,
pub len: u64, pub len: u64,
@ -25,8 +27,110 @@ pub struct Cha {
pub nlink: libc::nlink_t, pub nlink: libc::nlink_t,
} }
impl From<&Metadata> for Cha { impl Default for Cha {
fn from(m: &Metadata) -> Self { fn default() -> Self {
Self {
kind: ChaKind::DUMMY,
len: 0,
atime: None,
btime: None,
#[cfg(unix)]
ctime: None,
mtime: None,
#[cfg(unix)]
mode: 0,
#[cfg(unix)]
dev: 0,
#[cfg(unix)]
uid: 0,
#[cfg(unix)]
gid: 0,
#[cfg(unix)]
nlink: 0,
}
}
}
impl Cha {
#[inline]
pub fn new(path: &Path, meta: Metadata) -> Self {
Self::from_just_meta(&meta).attach(ChaKind::hidden(path, &meta))
}
#[inline]
pub async fn from_url(url: &Url) -> std::io::Result<Self> {
Ok(Self::from_follow(url, fs::symlink_metadata(url).await?).await)
}
pub async fn from_follow(path: &Path, mut meta: Metadata) -> Self {
let mut attached = ChaKind::hidden(path, &meta);
if meta.is_symlink() {
attached |= ChaKind::LINK;
meta = fs::metadata(path).await.unwrap_or(meta);
}
if meta.is_symlink() {
attached |= ChaKind::ORPHAN;
}
Self::from_just_meta(&meta).attach(attached)
}
#[inline]
pub fn from_dummy(url: &Url, ft: Option<FileType>) -> Self {
let mut me = ft.map(Self::from_half_ft).unwrap_or_default();
#[cfg(unix)]
if yazi_shared::url::Urn::new(url).is_hidden() {
me.kind |= ChaKind::HIDDEN;
}
me
}
fn from_half_ft(ft: FileType) -> Self {
let mut kind = ChaKind::DUMMY;
#[cfg(unix)]
let mode = {
use std::os::unix::fs::FileTypeExt;
if ft.is_dir() {
kind |= ChaKind::DIR;
libc::S_IFDIR
} else if ft.is_symlink() {
kind |= ChaKind::LINK;
libc::S_IFLNK
} else if ft.is_block_device() {
libc::S_IFBLK
} else if ft.is_char_device() {
libc::S_IFCHR
} else if ft.is_fifo() {
libc::S_IFIFO
} else if ft.is_socket() {
libc::S_IFSOCK
} else {
0
}
};
#[cfg(windows)]
{
if ft.is_dir() {
kind |= ChaKind::DIR;
} else if ft.is_symlink() {
kind |= ChaKind::LINK;
}
}
Self {
kind,
#[cfg(unix)]
mode,
..Default::default()
}
}
fn from_just_meta(m: &Metadata) -> Self {
#[cfg(unix)]
use std::{os::unix::{fs::MetadataExt, prelude::PermissionsExt}, time::{Duration, UNIX_EPOCH}};
let mut kind = ChaKind::empty(); let mut kind = ChaKind::empty();
if m.is_dir() { if m.is_dir() {
kind |= ChaKind::DIR; kind |= ChaKind::DIR;
@ -40,115 +144,20 @@ impl From<&Metadata> for Cha {
atime: m.accessed().ok(), atime: m.accessed().ok(),
btime: m.created().ok(), btime: m.created().ok(),
#[cfg(unix)] #[cfg(unix)]
ctime: { ctime: UNIX_EPOCH.checked_add(Duration::new(m.ctime() as u64, m.ctime_nsec() as u32)),
use std::{os::unix::fs::MetadataExt, time::{Duration, UNIX_EPOCH}};
UNIX_EPOCH.checked_add(Duration::new(m.ctime() as u64, m.ctime_nsec() as u32))
},
mtime: m.modified().ok(), mtime: m.modified().ok(),
#[cfg(unix)] #[cfg(unix)]
mode: { mode: m.permissions().mode() as _,
use std::os::unix::prelude::PermissionsExt;
m.permissions().mode() as _
},
#[cfg(unix)] #[cfg(unix)]
dev: { dev: m.dev() as _,
use std::os::unix::fs::MetadataExt;
m.dev() as _
},
#[cfg(unix)] #[cfg(unix)]
uid: { uid: m.uid() as _,
use std::os::unix::fs::MetadataExt;
m.uid() as _
},
#[cfg(unix)] #[cfg(unix)]
gid: { gid: m.gid() as _,
use std::os::unix::fs::MetadataExt;
m.gid() as _
},
#[cfg(unix)] #[cfg(unix)]
nlink: { nlink: m.nlink() as _,
use std::os::unix::fs::MetadataExt;
m.nlink() as _
},
} }
} }
}
impl From<Metadata> for Cha {
fn from(m: Metadata) -> Self { Self::from(&m) }
}
impl From<FileType> for Cha {
fn from(t: FileType) -> Self {
let mut kind = ChaKind::DUMMY;
#[cfg(unix)]
let mode = {
use std::os::unix::fs::FileTypeExt;
if t.is_dir() {
kind |= ChaKind::DIR;
libc::S_IFDIR
} else if t.is_symlink() {
kind |= ChaKind::LINK;
libc::S_IFLNK
} else if t.is_block_device() {
libc::S_IFBLK
} else if t.is_char_device() {
libc::S_IFCHR
} else if t.is_fifo() {
libc::S_IFIFO
} else if t.is_socket() {
libc::S_IFSOCK
} else {
0
}
};
#[cfg(windows)]
{
if t.is_dir() {
kind |= ChaKind::DIR;
} else if t.is_symlink() {
kind |= ChaKind::LINK;
}
}
Self {
kind,
#[cfg(unix)]
mode,
..Default::default()
}
}
}
impl Cha {
pub async fn new(path: &Path, mut meta: Metadata) -> Self {
let mut attached = ChaKind::hidden(path, &meta);
if meta.is_symlink() {
attached |= ChaKind::LINK;
meta = tokio::fs::metadata(path).await.unwrap_or(meta);
}
if meta.is_symlink() {
attached |= ChaKind::ORPHAN;
}
let mut me = Self::from(meta);
me.kind |= attached;
me
}
#[inline]
pub fn new_nofollow(_path: &Path, meta: Metadata) -> Self {
let mut me = Self::from(&meta);
me.kind |= ChaKind::hidden(_path, &meta);
me
}
#[inline]
pub fn dummy() -> Self { Self { kind: ChaKind::DUMMY, ..Default::default() } }
#[inline] #[inline]
pub fn hits(self, c: Self) -> bool { pub fn hits(self, c: Self) -> bool {
@ -159,6 +168,12 @@ impl Cha {
&& self.kind == c.kind && self.kind == c.kind
&& unix_either!(self.mode == c.mode, true) && unix_either!(self.mode == c.mode, true)
} }
#[inline]
fn attach(mut self, kind: ChaKind) -> Self {
self.kind |= kind;
self
}
} }
impl Cha { impl Cha {

View file

@ -23,29 +23,25 @@ impl Deref for File {
impl File { impl File {
#[inline] #[inline]
pub async fn from(url: Url) -> Result<Self> { pub async fn new(url: Url) -> Result<Self> {
let meta = fs::symlink_metadata(&url).await?; let meta = fs::symlink_metadata(&url).await?;
Ok(Self::from_meta(url, meta).await) Ok(Self::from_follow(url, meta).await)
} }
#[inline] #[inline]
pub async fn from_meta(url: Url, meta: Metadata) -> Self { pub async fn from_follow(url: Url, meta: Metadata) -> Self {
let link_to = let link_to =
if meta.is_symlink() { fs::read_link(&url).await.map(Url::from).ok() } else { None }; if meta.is_symlink() { fs::read_link(&url).await.map(Url::from).ok() } else { None };
let cha = Cha::new(&url, meta).await; let cha = Cha::from_follow(&url, meta).await;
Self { url, cha, link_to, icon: Default::default() } Self { url, cha, link_to, icon: Default::default() }
} }
#[inline] #[inline]
pub fn from_dummy(url: Url, ft: Option<FileType>) -> Self { pub fn from_dummy(url: Url, ft: Option<FileType>) -> Self {
Self { let cha = Cha::from_dummy(&url, ft);
url, Self { url, cha, link_to: None, icon: Default::default() }
cha: ft.map_or_else(Cha::dummy, Cha::from),
link_to: None,
icon: Default::default(),
}
} }
#[inline] #[inline]

View file

@ -41,7 +41,7 @@ impl Files {
result = item.metadata() => { result = item.metadata() => {
let url = Url::from(item.path()); let url = Url::from(item.path());
_ = tx.send(match result { _ = tx.send(match result {
Ok(meta) => File::from_meta(url, meta).await, Ok(meta) => File::from_follow(url, meta).await,
Err(_) => File::from_dummy(url, item.file_type().await.ok()) Err(_) => File::from_dummy(url, item.file_type().await.ok())
}); });
} }
@ -65,7 +65,7 @@ impl Files {
for entry in entries { for entry in entries {
let url = Url::from(entry.path()); let url = Url::from(entry.path());
files.push(match entry.metadata().await { files.push(match entry.metadata().await {
Ok(meta) => File::from_meta(url, meta).await, Ok(meta) => File::from_follow(url, meta).await,
Err(_) => File::from_dummy(url, entry.file_type().await.ok()), Err(_) => File::from_dummy(url, entry.file_type().await.ok()),
}); });
} }
@ -83,7 +83,7 @@ impl Files {
pub async fn assert_stale(dir: &Url, cha: Cha) -> Option<Cha> { pub async fn assert_stale(dir: &Url, cha: Cha) -> Option<Cha> {
use std::io::ErrorKind; use std::io::ErrorKind;
match fs::metadata(dir).await.map(Cha::from) { match Cha::from_url(dir).await {
Ok(c) if !c.is_dir() => FilesOp::issue_error(dir, ErrorKind::NotADirectory).await, Ok(c) if !c.is_dir() => FilesOp::issue_error(dir, ErrorKind::NotADirectory).await,
Ok(c) if c.hits(cha) && PARTITIONS.read().heuristic(cha) => {} Ok(c) if c.hits(cha) && PARTITIONS.read().heuristic(cha) => {}
Ok(c) => return Some(c), Ok(c) => return Some(c),

View file

@ -125,7 +125,7 @@ impl FilesOp {
} else if maybe_exists(cwd).await { } else if maybe_exists(cwd).await {
Self::IOErr(cwd.clone(), kind).emit(); Self::IOErr(cwd.clone(), kind).emit();
} else if let Some((p, n)) = cwd.pair() { } else if let Some((p, n)) = cwd.pair() {
Self::Deleting(p, HashSet::from_iter([n])).emit(); Self::Deleting(p, [n].into()).emit();
} }
} }

View file

@ -4,7 +4,7 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, UserData, UserDataFields
use yazi_fs::cha::ChaKind; use yazi_fs::cha::ChaKind;
#[derive(Clone, Copy, FromLua)] #[derive(Clone, Copy, FromLua)]
pub struct Cha(yazi_fs::cha::Cha); pub struct Cha(pub yazi_fs::cha::Cha);
impl Deref for Cha { impl Deref for Cha {
type Target = yazi_fs::cha::Cha; type Target = yazi_fs::cha::Cha;
@ -12,10 +12,6 @@ impl Deref for Cha {
fn deref(&self) -> &Self::Target { &self.0 } fn deref(&self) -> &Self::Target { &self.0 }
} }
impl<T: Into<yazi_fs::cha::Cha>> From<T> for Cha {
fn from(cha: T) -> Self { Self(cha.into()) }
}
impl Cha { impl Cha {
pub fn install(lua: &Lua) -> mlua::Result<()> { pub fn install(lua: &Lua) -> mlua::Result<()> {
#[inline] #[inline]
@ -33,7 +29,7 @@ impl Cha {
let kind = let kind =
ChaKind::from_bits(t.raw_get("kind")?).ok_or_else(|| "Invalid kind".into_lua_err())?; ChaKind::from_bits(t.raw_get("kind")?).ok_or_else(|| "Invalid kind".into_lua_err())?;
Self::from(yazi_fs::cha::Cha { Self(yazi_fs::cha::Cha {
kind, kind,
len: t.raw_get("len").unwrap_or_default(), len: t.raw_get("len").unwrap_or_default(),
atime: parse_time(t.raw_get("atime").ok())?, atime: parse_time(t.raw_get("atime").ok())?,

View file

@ -20,7 +20,7 @@ pub fn fd(opt: FdOpt) -> Result<UnboundedReceiver<File>> {
tokio::spawn(async move { tokio::spawn(async move {
while let Ok(Some(line)) = it.next_line().await { while let Ok(Some(line)) = it.next_line().await {
if let Ok(file) = File::from(opt.cwd.join(line)).await { if let Ok(file) = File::new(opt.cwd.join(line)).await {
tx.send(file).ok(); tx.send(file).ok();
} }
} }

View file

@ -29,7 +29,7 @@ pub fn rg(opt: RgOpt) -> Result<UnboundedReceiver<File>> {
tokio::spawn(async move { tokio::spawn(async move {
while let Ok(Some(line)) = it.next_line().await { while let Ok(Some(line)) = it.next_line().await {
if let Ok(file) = File::from(opt.cwd.join(line)).await { if let Ok(file) = File::new(opt.cwd.join(line)).await {
tx.send(file).ok(); tx.send(file).ok();
} }
} }

View file

@ -29,7 +29,7 @@ pub fn rga(opt: RgaOpt) -> Result<UnboundedReceiver<File>> {
tokio::spawn(async move { tokio::spawn(async move {
while let Ok(Some(line)) = it.next_line().await { while let Ok(Some(line)) = it.next_line().await {
if let Ok(file) = File::from(opt.cwd.join(line)).await { if let Ok(file) = File::new(opt.cwd.join(line)).await {
tx.send(file).ok(); tx.send(file).ok();
} }
} }

View file

@ -51,7 +51,7 @@ fn cha(lua: &Lua) -> mlua::Result<Function> {
}; };
match meta { match meta {
Ok(m) => Cha::from(m).into_lua_multi(&lua), Ok(m) => Cha(yazi_fs::cha::Cha::new(&url, m)).into_lua_multi(&lua),
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua), Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
} }
}) })
@ -138,7 +138,7 @@ fn read_dir(lua: &Lua) -> mlua::Result<Function> {
let file = if !resolve { let file = if !resolve {
yazi_fs::File::from_dummy(url, next.file_type().await.ok()) yazi_fs::File::from_dummy(url, next.file_type().await.ok())
} else if let Ok(meta) = next.metadata().await { } else if let Ok(meta) = next.metadata().await {
yazi_fs::File::from_meta(url, meta).await yazi_fs::File::from_follow(url, meta).await
} else { } else {
yazi_fs::File::from_dummy(url, next.file_type().await.ok()) yazi_fs::File::from_dummy(url, next.file_type().await.ok())
}; };

View file

@ -83,7 +83,7 @@ macro_rules! impl_style_shorthands {
#[macro_export] #[macro_export]
macro_rules! impl_file_fields { macro_rules! impl_file_fields {
($fields:ident) => { ($fields:ident) => {
yazi_binding::cached_field!($fields, cha, |_, me| Ok($crate::bindings::Cha::from(me.cha))); yazi_binding::cached_field!($fields, cha, |_, me| Ok($crate::bindings::Cha(me.cha)));
yazi_binding::cached_field!($fields, url, |_, me| Ok(yazi_binding::Url::new(me.url_owned()))); yazi_binding::cached_field!($fields, url, |_, me| Ok(yazi_binding::Url::new(me.url_owned())));
yazi_binding::cached_field!($fields, link_to, |_, me| Ok( yazi_binding::cached_field!($fields, link_to, |_, me| Ok(
me.link_to.clone().map(yazi_binding::Url::new) me.link_to.clone().map(yazi_binding::Url::new)

View file

@ -13,7 +13,7 @@ impl TabProxy {
#[inline] #[inline]
pub fn reveal(target: &Url) { pub fn reveal(target: &Url) {
emit!(Call(Cmd::args("mgr:reveal", &[target]))); emit!(Call(Cmd::args("mgr:reveal", &[target]).with("no-dummy", true)));
} }
#[inline] #[inline]

View file

@ -328,15 +328,15 @@ impl File {
#[inline] #[inline]
async fn cha(path: &Path, follow: bool) -> io::Result<Cha> { async fn cha(path: &Path, follow: bool) -> io::Result<Cha> {
let meta = fs::symlink_metadata(path).await?; let meta = fs::symlink_metadata(path).await?;
Ok(if follow { Cha::new(path, meta).await } else { Cha::new_nofollow(path, meta) }) Ok(if follow { Cha::from_follow(path, meta).await } else { Cha::new(path, meta) })
} }
#[inline] #[inline]
async fn cha_from(entry: DirEntry, path: &Path, follow: bool) -> io::Result<Cha> { async fn cha_from(entry: DirEntry, path: &Path, follow: bool) -> io::Result<Cha> {
Ok(if follow { Ok(if follow {
Cha::new(path, entry.metadata().await?).await Cha::from_follow(path, entry.metadata().await?).await
} else { } else {
Cha::new_nofollow(path, entry.metadata().await?) Cha::new(path, entry.metadata().await?)
}) })
} }
} }

View file

@ -86,6 +86,10 @@ impl Data {
} }
} }
impl From<bool> for Data {
fn from(value: bool) -> Self { Self::Boolean(value) }
}
impl From<usize> for Data { impl From<usize> for Data {
fn from(value: usize) -> Self { Self::Id(value.into()) } fn from(value: usize) -> Self { Self::Id(value.into()) }
} }