use std::{borrow::Cow, env::{current_dir, set_current_dir}, ops::Deref, path::{Path, PathBuf}, sync::{Arc, atomic::{AtomicBool, Ordering}}}; use arc_swap::ArcSwap; use yazi_shared::url::{AsUrl, Url, UrlBuf, UrlLike}; use yazi_shim::cell::RoCell; use crate::{FsUrl, Xdg, path::clean_url}; pub static CWD: RoCell = RoCell::new(); pub struct Cwd(ArcSwap); impl Deref for Cwd { type Target = ArcSwap; fn deref(&self) -> &Self::Target { &self.0 } } impl Default for Cwd { fn default() -> Self { let u = std::env::var_os("PWD") .map(PathBuf::from) .filter(|p| p.is_absolute()) .map(clean_url) .or_else(|| current_dir().ok().map(UrlBuf::from)) .expect("failed to get current working directory"); Self(ArcSwap::new(Arc::new(u))) } } impl Cwd { pub fn path(&self) -> PathBuf { self.0.load().as_url().unified_path().into_owned() } pub fn set(&self, url: &UrlBuf, callback: fn()) -> bool { if !url.is_absolute() { return false; } else if self.0.load().as_ref() == url { return false; } self.0.store(Arc::new(url.clone())); Self::sync_cwd(callback); true } pub fn ensure(url: Url) -> Cow { use std::{io::ErrorKind::{AlreadyExists, NotADirectory, NotFound}, path::Component as C}; let Some(cache) = url.cache() else { return url.unified_path(); }; if !matches!(std::fs::create_dir_all(&cache), Err(e) if e.kind() == NotADirectory || e.kind() == AlreadyExists) { return cache.into(); } let latter = cache.strip_prefix(Xdg::temp_dir()).expect("under temp dir"); let mut it = latter.components().peekable(); while it.peek() == Some(&C::CurDir) { it.next().unwrap(); } let mut count = 0; for c in it { match c { C::Prefix(_) | C::RootDir | C::ParentDir => return cache.into(), C::CurDir | C::Normal(_) => count += 1, } } for n in (0..count).rev() { let mut it = cache.components(); for _ in 0..n { it.next_back().unwrap(); } match std::fs::remove_file(it.as_path()) { Ok(_) => break, Err(e) if e.kind() == NotFound => break, Err(_) => {} } } std::fs::create_dir_all(&cache).ok(); cache.into() } fn sync_cwd(callback: fn()) { static SYNCING: AtomicBool = AtomicBool::new(false); if SYNCING.swap(true, Ordering::Relaxed) { return; } tokio::task::spawn_blocking(move || { let cwd = CWD.load(); let path = Self::ensure(cwd.as_url()); set_current_dir(&path).ok(); let cur = current_dir().unwrap_or_default(); unsafe { std::env::set_var("PWD", path.as_ref()) } SYNCING.store(false, Ordering::Relaxed); let cwd = CWD.load(); let path = Self::ensure(cwd.as_url()); if cur != path { set_current_dir(&path).ok(); unsafe { std::env::set_var("PWD", path.as_ref()) } } callback(); }); } }