refactor: pull filesystem calls out into a designated yazi_fs crate (#3013)

This commit is contained in:
三咲雅 misaki masa 2025-07-23 22:43:11 +08:00 committed by GitHub
parent 1f596a01fc
commit c2883f1e05
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 354 additions and 145 deletions

1
Cargo.lock generated
View file

@ -3530,6 +3530,7 @@ dependencies = [
"tokio", "tokio",
"tracing", "tracing",
"yazi-config", "yazi-config",
"yazi-fs",
"yazi-macro", "yazi-macro",
"yazi-shared", "yazi-shared",
"yazi-term", "yazi-term",

View file

@ -1,8 +1,7 @@
use std::{ffi::OsString, mem, path::{MAIN_SEPARATOR_STR, Path, PathBuf}}; use std::{ffi::OsString, mem, path::{MAIN_SEPARATOR_STR, Path, PathBuf}};
use anyhow::Result; use anyhow::Result;
use tokio::fs; use yazi_fs::{CWD, expand_path, services::Local};
use yazi_fs::{CWD, expand_path};
use yazi_macro::{act, render, succ}; use yazi_macro::{act, render, succ};
use yazi_parser::cmp::{CmpItem, ShowOpt, TriggerOpt}; use yazi_parser::cmp::{CmpItem, ShowOpt, TriggerOpt};
use yazi_proxy::CmpProxy; use yazi_proxy::CmpProxy;
@ -37,7 +36,8 @@ impl Actor for Trigger {
let ticket = cmp.ticket; let ticket = cmp.ticket;
tokio::spawn(async move { tokio::spawn(async move {
let mut dir = fs::read_dir(&parent).await?; // TODO: support VFS
let mut dir = Local::read_dir(&parent).await?;
let mut cache = vec![]; let mut cache = vec![];
// "/" is both a directory separator and the root directory per se // "/" is both a directory separator and the root directory per se

View file

@ -22,6 +22,7 @@ impl Actor for BulkRename {
const NAME: &str = "bulk_rename"; const NAME: &str = "bulk_rename";
// FIXME: VFS
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> { fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
let Some(opener) = YAZI.opener.block(YAZI.open.all("bulk-rename.txt", "text/plain")) else { let Some(opener) = YAZI.opener.block(YAZI.open.all("bulk-rename.txt", "text/plain")) else {
succ!(AppProxy::notify_warn("Bulk rename", "No text opener found")); succ!(AppProxy::notify_warn("Bulk rename", "No text opener found"));

View file

@ -1,7 +1,6 @@
use anyhow::Result; use anyhow::Result;
use tokio::fs;
use yazi_config::popup::{ConfirmCfg, InputCfg}; use yazi_config::popup::{ConfirmCfg, InputCfg};
use yazi_fs::{File, FilesOp, maybe_exists, ok_or_not_found, realname}; use yazi_fs::{File, FilesOp, maybe_exists, ok_or_not_found, realname, services};
use yazi_macro::succ; use yazi_macro::succ;
use yazi_parser::mgr::CreateOpt; use yazi_parser::mgr::CreateOpt;
use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy, WATCHER}; use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy, WATCHER};
@ -46,15 +45,15 @@ impl Create {
let _permit = WATCHER.acquire().await.unwrap(); let _permit = WATCHER.acquire().await.unwrap();
if dir { if dir {
fs::create_dir_all(&new).await?; services::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(services::remove_file(&new).await)?;
FilesOp::Deleting(parent.clone(), [UrnBuf::from(real)].into()).emit(); FilesOp::Deleting(parent.clone(), [UrnBuf::from(real)].into()).emit();
fs::File::create(&new).await?; services::create(&new).await?;
} else { } else {
fs::create_dir_all(&parent).await.ok(); services::create_dir_all(&parent).await.ok();
ok_or_not_found(fs::remove_file(&new).await)?; ok_or_not_found(services::remove_file(&new).await)?;
fs::File::create(&new).await?; services::create(&new).await?;
} }
if let Ok(f) = File::new(new.clone()).await { if let Ok(f) = File::new(new.clone()).await {

View file

@ -1,8 +1,7 @@
use anyhow::Result; use anyhow::Result;
use tokio::fs;
use yazi_config::popup::{ConfirmCfg, InputCfg}; use yazi_config::popup::{ConfirmCfg, InputCfg};
use yazi_dds::Pubsub; use yazi_dds::Pubsub;
use yazi_fs::{File, FilesOp, maybe_exists, ok_or_not_found, paths_to_same_file, realname}; use yazi_fs::{File, FilesOp, maybe_exists, ok_or_not_found, paths_to_same_file, realname, services};
use yazi_macro::{act, err, succ}; use yazi_macro::{act, err, succ};
use yazi_parser::mgr::RenameOpt; use yazi_parser::mgr::RenameOpt;
use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy, WATCHER}; use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy, WATCHER};
@ -66,10 +65,10 @@ impl Rename {
let _permit = WATCHER.acquire().await.unwrap(); let _permit = WATCHER.acquire().await.unwrap();
let overwritten = realname(&new).await; let overwritten = realname(&new).await;
fs::rename(&old, &new).await?; services::rename(&old, &new).await?;
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(services::rename(p_new.join(&o), &new).await)?;
FilesOp::Deleting(p_new.clone(), [UrnBuf::from(o)].into()).emit(); FilesOp::Deleting(p_new.clone(), [UrnBuf::from(o)].into()).emit();
} }

View file

@ -10,6 +10,7 @@ repository = "https://github.com/sxyazi/yazi"
[dependencies] [dependencies]
yazi-config = { path = "../yazi-config", version = "25.6.11" } yazi-config = { path = "../yazi-config", version = "25.6.11" }
yazi-fs = { path = "../yazi-fs", version = "25.6.11" }
yazi-macro = { path = "../yazi-macro", version = "25.6.11" } yazi-macro = { path = "../yazi-macro", version = "25.6.11" }
yazi-shared = { path = "../yazi-shared", version = "25.6.11" } yazi-shared = { path = "../yazi-shared", version = "25.6.11" }
yazi-term = { path = "../yazi-term", version = "25.6.11" } yazi-term = { path = "../yazi-term", version = "25.6.11" }

View file

@ -1,9 +1,9 @@
use std::{env, fmt::Display, path::Path}; use std::{env, fmt::Display};
use anyhow::Result; use anyhow::Result;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use tracing::warn; use tracing::warn;
use yazi_shared::env_exists; use yazi_shared::{env_exists, url::Url};
use crate::{Emulator, SHOWN, TMUX, drivers}; use crate::{Emulator, SHOWN, TMUX, drivers};
@ -35,18 +35,18 @@ impl Display for Adapter {
} }
impl Adapter { impl Adapter {
pub async fn image_show(self, path: &Path, max: Rect) -> Result<Rect> { pub async fn image_show(self, url: &Url, max: Rect) -> Result<Rect> {
if max.is_empty() { if max.is_empty() {
return Ok(Rect::default()); return Ok(Rect::default());
} }
match self { match self {
Self::Kgp => drivers::Kgp::image_show(path, max).await, Self::Kgp => drivers::Kgp::image_show(url, max).await,
Self::KgpOld => drivers::KgpOld::image_show(path, max).await, Self::KgpOld => drivers::KgpOld::image_show(url, max).await,
Self::Iip => drivers::Iip::image_show(path, max).await, Self::Iip => drivers::Iip::image_show(url, max).await,
Self::Sixel => drivers::Sixel::image_show(path, max).await, Self::Sixel => drivers::Sixel::image_show(url, max).await,
Self::X11 | Self::Wayland => drivers::Ueberzug::image_show(path, max).await, Self::X11 | Self::Wayland => drivers::Ueberzug::image_show(url, max).await,
Self::Chafa => drivers::Chafa::image_show(path, max).await, Self::Chafa => drivers::Chafa::image_show(url, max).await,
} }
} }

View file

@ -1,4 +1,4 @@
use std::{fmt::Write, io::Write as ioWrite, path::Path}; use std::{fmt::Write, io::Write as ioWrite};
use anyhow::Result; use anyhow::Result;
use base64::{Engine, engine::{Config, general_purpose::STANDARD}}; use base64::{Engine, engine::{Config, general_purpose::STANDARD}};
@ -6,14 +6,15 @@ use crossterm::{cursor::MoveTo, queue};
use image::{DynamicImage, ExtendedColorType, ImageEncoder, codecs::{jpeg::JpegEncoder, png::PngEncoder}}; use image::{DynamicImage, ExtendedColorType, ImageEncoder, codecs::{jpeg::JpegEncoder, png::PngEncoder}};
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_config::YAZI; use yazi_config::YAZI;
use yazi_shared::url::Url;
use crate::{CLOSE, Emulator, Image, START, adapter::Adapter}; use crate::{CLOSE, Emulator, Image, START, adapter::Adapter};
pub(crate) struct Iip; pub(crate) struct Iip;
impl Iip { impl Iip {
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> { pub(crate) async fn image_show(url: &Url, max: Rect) -> Result<Rect> {
let img = Image::downscale(path, max).await?; let img = Image::downscale(url, max).await?;
let area = Image::pixel_area((img.width(), img.height()), max); let area = Image::pixel_area((img.width(), img.height()), max);
let b = Self::encode(img).await?; let b = Self::encode(img).await?;

View file

@ -1,11 +1,12 @@
use core::str; use core::str;
use std::{io::Write, path::Path}; use std::io::Write;
use anyhow::Result; use anyhow::Result;
use base64::{Engine, engine::general_purpose}; use base64::{Engine, engine::general_purpose};
use crossterm::{cursor::MoveTo, queue}; use crossterm::{cursor::MoveTo, queue};
use image::DynamicImage; use image::DynamicImage;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_shared::url::Url;
use crate::{CLOSE, ESCAPE, Emulator, START, adapter::Adapter, image::Image}; use crate::{CLOSE, ESCAPE, Emulator, START, adapter::Adapter, image::Image};
@ -312,8 +313,8 @@ static DIACRITICS: [char; 297] = [
pub(crate) struct Kgp; pub(crate) struct Kgp;
impl Kgp { impl Kgp {
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> { pub(crate) async fn image_show(url: &Url, max: Rect) -> Result<Rect> {
let img = Image::downscale(path, max).await?; let img = Image::downscale(url, max).await?;
let area = Image::pixel_area((img.width(), img.height()), max); let area = Image::pixel_area((img.width(), img.height()), max);
let b1 = Self::encode(img).await?; let b1 = Self::encode(img).await?;

View file

@ -1,10 +1,11 @@
use core::str; use core::str;
use std::{io::Write, path::Path}; use std::io::Write;
use anyhow::Result; use anyhow::Result;
use base64::{Engine, engine::general_purpose}; use base64::{Engine, engine::general_purpose};
use image::DynamicImage; use image::DynamicImage;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_shared::url::Url;
use yazi_term::tty::TTY; use yazi_term::tty::TTY;
use crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter}; use crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter};
@ -12,8 +13,8 @@ use crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter};
pub(crate) struct KgpOld; pub(crate) struct KgpOld;
impl KgpOld { impl KgpOld {
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> { pub(crate) async fn image_show(url: &Url, max: Rect) -> Result<Rect> {
let img = Image::downscale(path, max).await?; let img = Image::downscale(url, max).await?;
let area = Image::pixel_area((img.width(), img.height()), max); let area = Image::pixel_area((img.width(), img.height()), max);
let b = Self::encode(img).await?; let b = Self::encode(img).await?;

View file

@ -1,4 +1,4 @@
use std::{io::Write, path::Path}; use std::io::Write;
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use crossterm::{cursor::MoveTo, queue}; use crossterm::{cursor::MoveTo, queue};
@ -6,14 +6,15 @@ use image::{DynamicImage, GenericImageView, RgbImage};
use palette::{Srgb, cast::ComponentsAs}; use palette::{Srgb, cast::ComponentsAs};
use quantette::{ColorSlice, PaletteSize, QuantizeOutput, wu::UIntBinner}; use quantette::{ColorSlice, PaletteSize, QuantizeOutput, wu::UIntBinner};
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_shared::url::Url;
use crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter}; use crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter};
pub(crate) struct Sixel; pub(crate) struct Sixel;
impl Sixel { impl Sixel {
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> { pub(crate) async fn image_show(url: &Url, max: Rect) -> Result<Rect> {
let img = Image::downscale(path, max).await?; let img = Image::downscale(url, max).await?;
let area = Image::pixel_area((img.width(), img.height()), max); let area = Image::pixel_area((img.width(), img.height()), max);
let b = Self::encode(img).await?; let b = Self::encode(img).await?;

View file

@ -1,17 +1,19 @@
use std::path::Path; use std::io::BufReader;
use anyhow::Result; use anyhow::Result;
use image::{DynamicImage, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageReader, ImageResult, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation}; use image::{DynamicImage, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageFormat, ImageReader, ImageResult, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation};
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_config::YAZI; use yazi_config::YAZI;
use yazi_fs::services;
use yazi_shared::url::Url;
use crate::Dimension; use crate::Dimension;
pub struct Image; pub struct Image;
impl Image { impl Image {
pub async fn precache(path: &Path, cache: &Path) -> Result<()> { pub async fn precache(src: &Url, cache: &Url) -> Result<()> {
let (mut img, orientation, icc) = Self::decode_from(path).await?; let (mut img, orientation, icc) = Self::decode_from(src).await?;
let (w, h) = Self::flip_size(orientation, (YAZI.preview.max_width, YAZI.preview.max_height)); let (w, h) = Self::flip_size(orientation, (YAZI.preview.max_width, YAZI.preview.max_height));
let buf = tokio::task::spawn_blocking(move || { let buf = tokio::task::spawn_blocking(move || {
@ -38,11 +40,11 @@ impl Image {
}) })
.await??; .await??;
Ok(tokio::fs::write(cache, buf).await?) Ok(services::write(cache, buf).await?)
} }
pub(super) async fn downscale(path: &Path, rect: Rect) -> Result<DynamicImage> { pub(super) async fn downscale(url: &Url, rect: Rect) -> Result<DynamicImage> {
let (mut img, orientation, _) = Self::decode_from(path).await?; let (mut img, orientation, _) = Self::decode_from(url).await?;
let (w, h) = Self::flip_size(orientation, Self::max_pixel(rect)); let (w, h) = Self::flip_size(orientation, Self::max_pixel(rect));
// Fast path. // Fast path.
@ -96,7 +98,7 @@ impl Image {
} }
} }
async fn decode_from(path: &Path) -> ImageResult<(DynamicImage, Orientation, Option<Vec<u8>>)> { async fn decode_from(url: &Url) -> ImageResult<(DynamicImage, Orientation, Option<Vec<u8>>)> {
let mut limits = Limits::no_limits(); let mut limits = Limits::no_limits();
if YAZI.tasks.image_alloc > 0 { if YAZI.tasks.image_alloc > 0 {
limits.max_alloc = Some(YAZI.tasks.image_alloc as u64); limits.max_alloc = Some(YAZI.tasks.image_alloc as u64);
@ -108,11 +110,13 @@ impl Image {
limits.max_image_height = Some(YAZI.tasks.image_bound[1] as u32); limits.max_image_height = Some(YAZI.tasks.image_bound[1] as u32);
} }
let path = path.to_owned(); let mut reader = ImageReader::new(BufReader::new(services::open(&url).await?.into_std().await));
tokio::task::spawn_blocking(move || { if let Ok(format) = ImageFormat::from_path(url) {
let mut reader = ImageReader::open(path)?; reader.set_format(format);
reader.limits(limits); }
reader.limits(limits);
tokio::task::spawn_blocking(move || {
let mut decoder = reader.with_guessed_format()?.into_decoder()?; let mut decoder = reader.with_guessed_format()?.into_decoder()?;
let orientation = decoder.orientation().unwrap_or(Orientation::NoTransforms); let orientation = decoder.orientation().unwrap_or(Orientation::NoTransforms);
let icc = decoder.icc_profile().unwrap_or_default(); let icc = decoder.icc_profile().unwrap_or_default();

View file

@ -25,6 +25,11 @@ impl Deref for Url {
fn deref(&self) -> &Self::Target { &self.inner } fn deref(&self) -> &Self::Target { &self.inner }
} }
impl AsRef<yazi_shared::url::Url> for Url {
fn as_ref(&self) -> &yazi_shared::url::Url { &self.inner }
}
// FIXME: remove
impl AsRef<Path> for Url { impl AsRef<Path> for Url {
fn as_ref(&self) -> &Path { self.inner.as_path() } fn as_ref(&self) -> &Path { self.inner.as_path() }
} }

View file

@ -1,6 +1,5 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use tokio::fs; use yazi_fs::{maybe_exists, ok_or_not_found, remove_dir_clean, remove_sealed, services::Local};
use yazi_fs::{maybe_exists, ok_or_not_found, remove_dir_clean, remove_sealed};
use yazi_macro::outln; use yazi_macro::outln;
use super::Dependency; use super::Dependency;
@ -23,7 +22,7 @@ impl Dependency {
pub(super) async fn delete_assets(&self) -> Result<()> { pub(super) async fn delete_assets(&self) -> Result<()> {
let assets = self.target().join("assets"); let assets = self.target().join("assets");
match fs::read_dir(&assets).await { match Local::read_dir(&assets).await {
Ok(mut it) => { Ok(mut it) => {
while let Some(entry) = it.next_entry().await? { while let Some(entry) = it.next_entry().await? {
remove_sealed(&entry.path()) remove_sealed(&entry.path())
@ -52,7 +51,7 @@ impl Dependency {
.with_context(|| format!("failed to delete `{}`", p.display()))?; .with_context(|| format!("failed to delete `{}`", p.display()))?;
} }
if ok_or_not_found(fs::remove_dir(&dir).await).is_ok() { if ok_or_not_found(Local::remove_dir(&dir).await).is_ok() {
outln!("Done!")?; outln!("Done!")?;
} else { } else {
outln!( outln!(

View file

@ -1,8 +1,7 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use tokio::fs; use yazi_fs::{copy_and_seal, maybe_exists, remove_dir_clean, services::Local};
use yazi_fs::{copy_and_seal, maybe_exists, remove_dir_clean};
use yazi_macro::outln; use yazi_macro::outln;
use super::Dependency; use super::Dependency;
@ -20,7 +19,7 @@ impl Dependency {
self.hash_check().await?; self.hash_check().await?;
} }
fs::create_dir_all(&to).await?; Local::create_dir_all(&to).await?;
self.delete_assets().await?; self.delete_assets().await?;
let res1 = Self::deploy_assets(from.join("assets"), to.join("assets")).await; let res1 = Self::deploy_assets(from.join("assets"), to.join("assets")).await;
@ -40,9 +39,9 @@ impl Dependency {
} }
async fn deploy_assets(from: PathBuf, to: PathBuf) -> Result<()> { async fn deploy_assets(from: PathBuf, to: PathBuf) -> Result<()> {
match fs::read_dir(&from).await { match Local::read_dir(&from).await {
Ok(mut it) => { Ok(mut it) => {
fs::create_dir_all(&to).await?; Local::create_dir_all(&to).await?;
while let Some(entry) = it.next_entry().await? { while let Some(entry) = it.next_entry().await? {
let (src, dist) = (entry.path(), to.join(entry.file_name())); let (src, dist) = (entry.path(), to.join(entry.file_name()));
copy_and_seal(&src, &dist).await.with_context(|| { copy_and_seal(&src, &dist).await.with_context(|| {

View file

@ -1,7 +1,6 @@
use anyhow::{Context, Result, bail}; use anyhow::{Context, Result, bail};
use tokio::fs;
use twox_hash::XxHash3_128; use twox_hash::XxHash3_128;
use yazi_fs::ok_or_not_found; use yazi_fs::{ok_or_not_found, services::Local};
use super::Dependency; use super::Dependency;
@ -26,17 +25,17 @@ impl Dependency {
for &file in files { for &file in files {
h.write(file.as_bytes()); h.write(file.as_bytes());
h.write(b"VpvFw9Atb7cWGOdqhZCra634CcJJRlsRl72RbZeV0vpG1\0"); h.write(b"VpvFw9Atb7cWGOdqhZCra634CcJJRlsRl72RbZeV0vpG1\0");
h.write(&ok_or_not_found(fs::read(dir.join(file)).await)?); h.write(&ok_or_not_found(Local::read(dir.join(file)).await)?);
} }
let mut assets = vec![]; let mut assets = vec![];
match fs::read_dir(dir.join("assets")).await { match Local::read_dir(dir.join("assets")).await {
Ok(mut it) => { Ok(mut it) => {
while let Some(entry) = it.next_entry().await? { while let Some(entry) = it.next_entry().await? {
let Ok(name) = entry.file_name().into_string() else { let Ok(name) = entry.file_name().into_string() else {
bail!("asset path is not valid UTF-8: {}", entry.path().display()); bail!("asset path is not valid UTF-8: {}", entry.path().display());
}; };
assets.push((name, fs::read(entry.path()).await?)); assets.push((name, Local::read(entry.path()).await?));
} }
} }
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {} Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}

View file

@ -2,8 +2,7 @@ use std::{path::PathBuf, str::FromStr};
use anyhow::{Context, Result, bail}; use anyhow::{Context, Result, bail};
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use tokio::fs; use yazi_fs::{Xdg, services::Local};
use yazi_fs::Xdg;
use yazi_macro::outln; use yazi_macro::outln;
use super::Dependency; use super::Dependency;
@ -16,7 +15,7 @@ pub(crate) struct Package {
impl Package { impl Package {
pub(crate) async fn load() -> Result<Self> { pub(crate) async fn load() -> Result<Self> {
Ok(match fs::read_to_string(Self::toml()).await { Ok(match Local::read_to_string(Self::toml()).await {
Ok(s) => toml::from_str(&s)?, Ok(s) => toml::from_str(&s)?,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Self::default(), Err(e) if e.kind() == std::io::ErrorKind::NotFound => Self::default(),
Err(e) => Err(e)?, Err(e) => Err(e)?,
@ -136,7 +135,7 @@ impl Package {
async fn save(&self) -> Result<()> { async fn save(&self) -> Result<()> {
let s = toml::to_string_pretty(self)?; let s = toml::to_string_pretty(self)?;
fs::write(Self::toml(), s).await.context("Failed to write package.toml") Local::write(Self::toml(), s).await.context("Failed to write package.toml")
} }
#[inline] #[inline]

View file

@ -3,10 +3,10 @@ use std::{collections::{HashMap, HashSet}, time::Duration};
use anyhow::Result; use anyhow::Result;
use notify::{PollWatcher, RecommendedWatcher, RecursiveMode, Watcher as _Watcher}; use notify::{PollWatcher, RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
use parking_lot::RwLock; use parking_lot::RwLock;
use tokio::{fs, pin, sync::{mpsc::{self, UnboundedReceiver}, watch}}; use tokio::{pin, sync::{mpsc::{self, UnboundedReceiver}, watch}};
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use tracing::error; use tracing::error;
use yazi_fs::{File, Files, FilesOp, cha::Cha, realname_unchecked}; use yazi_fs::{File, Files, FilesOp, cha::Cha, realname_unchecked, services};
use yazi_proxy::WATCHER; use yazi_proxy::WATCHER;
use yazi_shared::{RoCell, url::Url}; use yazi_shared::{RoCell, url::Url};
@ -21,6 +21,7 @@ pub struct Watcher {
out_tx: mpsc::UnboundedSender<Url>, out_tx: mpsc::UnboundedSender<Url>,
} }
// FIXME: VFS
impl Watcher { impl Watcher {
pub(super) fn serve() -> Self { pub(super) fn serve() -> Self {
let (in_tx, in_rx) = watch::channel(Default::default()); let (in_tx, in_rx) = watch::channel(Default::default());
@ -138,7 +139,7 @@ impl Watcher {
}; };
let u = &file.url; let u = &file.url;
let eq = (!file.is_link() && fs::canonicalize(u).await.is_ok_and(|p| p == ***u)) let eq = (!file.is_link() && services::canonicalize(u).await.is_ok_and(|c| c == *u))
|| 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 {
@ -193,10 +194,10 @@ impl Watcher {
async fn go(todo: HashSet<Url>) { async fn go(todo: HashSet<Url>) {
for from in todo { for from in todo {
let Ok(to) = fs::canonicalize(&from).await else { continue }; let Ok(to) = services::canonicalize(&from).await else { continue };
if to != **from && WATCHED.read().contains(&from) { if to != from && WATCHED.read().contains(&from) {
LINKED.write().insert(from, Url::from(to)); LINKED.write().insert(from, to);
} }
} }
} }

View file

@ -2,8 +2,9 @@ use std::{collections::HashMap, mem, ops::Deref, sync::atomic::{AtomicU64, Order
use anyhow::Result; use anyhow::Result;
use parking_lot::RwLock; use parking_lot::RwLock;
use tokio::{fs::{self, File, OpenOptions}, io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}}; use tokio::{fs::OpenOptions, io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}};
use yazi_boot::BOOT; use yazi_boot::BOOT;
use yazi_fs::services::Local;
use yazi_shared::{RoCell, timestamp_us}; use yazi_shared::{RoCell, timestamp_us};
use crate::CLIENTS; use crate::CLIENTS;
@ -56,8 +57,9 @@ impl State {
return Ok(()); return Ok(());
} }
fs::create_dir_all(&BOOT.state_dir).await?; Local::create_dir_all(&BOOT.state_dir).await?;
let mut buf = BufWriter::new( let mut buf = BufWriter::new(
// TODO: VFS
OpenOptions::new() OpenOptions::new()
.write(true) .write(true)
.create(true) .create(true)
@ -77,7 +79,7 @@ impl State {
} }
async fn load(&self) -> Result<()> { async fn load(&self) -> Result<()> {
let mut file = BufReader::new(File::open(BOOT.state_dir.join(".dds")).await?); let mut file = BufReader::new(Local::open(BOOT.state_dir.join(".dds")).await?);
let mut buf = String::new(); let mut buf = String::new();
let mut inner = HashMap::new(); let mut inner = HashMap::new();
@ -101,7 +103,7 @@ impl State {
} }
async fn skip(&self) -> Result<bool> { async fn skip(&self) -> Result<bool> {
let meta = fs::symlink_metadata(BOOT.state_dir.join(".dds")).await?; let meta = Local::symlink_metadata(BOOT.state_dir.join(".dds")).await?;
let modified = meta.modified()?.duration_since(UNIX_EPOCH)?.as_micros(); let modified = meta.modified()?.duration_since(UNIX_EPOCH)?.as_micros();
Ok(modified >= self.last.load(Ordering::Relaxed) as u128) Ok(modified >= self.last.load(Ordering::Relaxed) as u128)
} }

View file

@ -38,7 +38,7 @@ impl Stream {
pub(super) async fn bind() -> std::io::Result<ServerListener> { pub(super) async fn bind() -> std::io::Result<ServerListener> {
let p = Self::socket_file(); let p = Self::socket_file();
tokio::fs::remove_file(&p).await.ok(); yazi_fs::services::Local::remove_file(&p).await.ok();
tokio::net::UnixListener::bind(p) tokio::net::UnixListener::bind(p)
} }

View file

@ -1,7 +1,7 @@
use std::ffi::OsString; use std::ffi::OsString;
use tokio::fs;
use yazi_boot::ARGS; use yazi_boot::ARGS;
use yazi_fs::services::Local;
use yazi_shared::event::EventQuit; use yazi_shared::event::EventQuit;
use crate::{Term, app::App}; use crate::{Term, app::App};
@ -26,13 +26,13 @@ impl App {
async fn cwd_to_file(&self, no: bool) { async fn cwd_to_file(&self, no: bool) {
if let Some(p) = ARGS.cwd_file.as_ref().filter(|_| !no) { if let Some(p) = ARGS.cwd_file.as_ref().filter(|_| !no) {
let cwd = self.core.mgr.cwd().as_os_str(); let cwd = self.core.mgr.cwd().as_os_str();
fs::write(p, cwd.as_encoded_bytes()).await.ok(); Local::write(p, cwd.as_encoded_bytes()).await.ok();
} }
} }
async fn selected_to_file(&self, selected: Option<OsString>) { async fn selected_to_file(&self, selected: Option<OsString>) {
if let (Some(s), Some(p)) = (selected, &ARGS.chooser_file) { if let (Some(s), Some(p)) = (selected, &ARGS.chooser_file) {
fs::write(p, s.as_encoded_bytes()).await.ok(); Local::write(p, s.as_encoded_bytes()).await.ok();
} }
} }
} }

View file

@ -1,10 +1,10 @@
use std::{fs::{FileType, Metadata}, path::Path, time::SystemTime}; use std::{fs::{FileType, Metadata}, 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 yazi_shared::url::Url;
use super::ChaKind; use super::ChaKind;
use crate::services;
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Cha { pub struct Cha {
@ -53,20 +53,20 @@ impl Default for Cha {
impl Cha { impl Cha {
#[inline] #[inline]
pub fn new(path: &Path, meta: Metadata) -> Self { pub fn new(url: &Url, meta: Metadata) -> Self {
Self::from_just_meta(&meta).attach(ChaKind::hidden(path, &meta)) Self::from_just_meta(&meta).attach(ChaKind::hidden(url, &meta))
} }
#[inline] #[inline]
pub async fn from_url(url: &Url) -> std::io::Result<Self> { pub async fn from_url(url: &Url) -> std::io::Result<Self> {
Ok(Self::from_follow(url, fs::symlink_metadata(url).await?).await) Ok(Self::from_follow(url, services::symlink_metadata(url).await?).await)
} }
pub async fn from_follow(path: &Path, mut meta: Metadata) -> Self { pub async fn from_follow(url: &Url, mut meta: Metadata) -> Self {
let mut attached = ChaKind::hidden(path, &meta); let mut attached = ChaKind::hidden(url, &meta);
if meta.is_symlink() { if meta.is_symlink() {
attached |= ChaKind::LINK; attached |= ChaKind::LINK;
meta = fs::metadata(path).await.unwrap_or(meta); meta = services::metadata(url).await.unwrap_or(meta);
} }
if meta.is_symlink() { if meta.is_symlink() {
attached |= ChaKind::ORPHAN; attached |= ChaKind::ORPHAN;

View file

@ -1,6 +1,7 @@
use std::{fs::Metadata, path::Path}; use std::fs::Metadata;
use bitflags::bitflags; use bitflags::bitflags;
use yazi_shared::url::Url;
bitflags! { bitflags! {
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
@ -19,11 +20,11 @@ bitflags! {
impl ChaKind { impl ChaKind {
#[inline] #[inline]
pub(super) fn hidden(_path: &Path, _meta: &Metadata) -> Self { pub(super) fn hidden(_url: &Url, _meta: &Metadata) -> Self {
let mut me = Self::empty(); let mut me = Self::empty();
#[cfg(unix)] #[cfg(unix)]
if yazi_shared::url::Urn::new(_path).is_hidden() { if _url.urn().is_hidden() {
me |= Self::HIDDEN; me |= Self::HIDDEN;
} }
#[cfg(windows)] #[cfg(windows)]

View file

@ -1,10 +1,9 @@
use std::{ffi::OsStr, fs::{FileType, Metadata}, hash::{BuildHasher, Hash, Hasher}, ops::Deref}; use std::{ffi::OsStr, fs::{FileType, Metadata}, hash::{BuildHasher, Hash, Hasher}, ops::Deref};
use anyhow::Result; use anyhow::Result;
use tokio::fs;
use yazi_shared::url::{Url, Urn, UrnBuf}; use yazi_shared::url::{Url, Urn, UrnBuf};
use crate::cha::Cha; use crate::{cha::Cha, services};
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct File { pub struct File {
@ -23,14 +22,13 @@ impl Deref for File {
impl File { impl File {
#[inline] #[inline]
pub async fn new(url: Url) -> Result<Self> { pub async fn new(url: Url) -> Result<Self> {
let meta = fs::symlink_metadata(&url).await?; let meta = services::symlink_metadata(&url).await?;
Ok(Self::from_follow(url, meta).await) Ok(Self::from_follow(url, meta).await)
} }
#[inline] #[inline]
pub async fn from_follow(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() { services::read_link(&url).await.ok() } else { None };
if meta.is_symlink() { fs::read_link(&url).await.map(Url::from).ok() } else { None };
let cha = Cha::from_follow(&url, meta).await; let cha = Cha::from_follow(&url, meta).await;

View file

@ -1,10 +1,10 @@
use std::{collections::{HashMap, HashSet}, mem, ops::{Deref, Not}}; use std::{collections::{HashMap, HashSet}, mem, ops::{Deref, Not}};
use tokio::{fs::{self, DirEntry}, select, sync::mpsc::{self, UnboundedReceiver}}; use tokio::{select, sync::mpsc::{self, UnboundedReceiver}};
use yazi_shared::{Id, url::{Url, Urn, UrnBuf}}; use yazi_shared::{Id, url::{Url, Urn, UrnBuf}};
use super::{FilesSorter, Filter}; use super::{FilesSorter, Filter};
use crate::{FILES_TICKET, File, FilesOp, SortBy, cha::Cha, mounts::PARTITIONS}; use crate::{FILES_TICKET, File, FilesOp, SortBy, cha::Cha, mounts::PARTITIONS, services};
#[derive(Default)] #[derive(Default)]
pub struct Files { pub struct Files {
@ -31,7 +31,7 @@ impl Files {
pub fn new(show_hidden: bool) -> Self { Self { show_hidden, ..Default::default() } } pub fn new(show_hidden: bool) -> Self { Self { show_hidden, ..Default::default() } }
pub async fn from_dir(dir: &Url) -> std::io::Result<UnboundedReceiver<File>> { pub async fn from_dir(dir: &Url) -> std::io::Result<UnboundedReceiver<File>> {
let mut it = fs::read_dir(dir).await?; let mut it = services::read_dir(dir).await?;
let (tx, rx) = mpsc::unbounded_channel(); let (tx, rx) = mpsc::unbounded_channel();
tokio::spawn(async move { tokio::spawn(async move {
@ -52,7 +52,7 @@ impl Files {
} }
pub async fn from_dir_bulk(dir: &Url) -> std::io::Result<Vec<File>> { pub async fn from_dir_bulk(dir: &Url) -> std::io::Result<Vec<File>> {
let mut it = fs::read_dir(dir).await?; let mut it = services::read_dir(dir).await?;
let mut entries = Vec::with_capacity(5000); let mut entries = Vec::with_capacity(5000);
while let Ok(Some(entry)) = it.next_entry().await { while let Ok(Some(entry)) = it.next_entry().await {
entries.push(entry); entries.push(entry);
@ -60,7 +60,7 @@ impl Files {
let (first, rest) = entries.split_at(entries.len() / 3); let (first, rest) = entries.split_at(entries.len() / 3);
let (second, third) = rest.split_at(entries.len() / 3); let (second, third) = rest.split_at(entries.len() / 3);
async fn go(entries: &[DirEntry]) -> Vec<File> { async fn go(entries: &[tokio::fs::DirEntry]) -> Vec<File> {
let mut files = Vec::with_capacity(entries.len()); let mut files = Vec::with_capacity(entries.len());
for entry in entries { for entry in entries {
let url = Url::from(entry.path()); let url = Url::from(entry.path());

View file

@ -1,3 +1,5 @@
// FIXME: VFS
use std::{borrow::Cow, collections::{HashMap, HashSet}, ffi::{OsStr, OsString}, path::{Path, PathBuf}}; use std::{borrow::Cow, collections::{HashMap, HashSet}, ffi::{OsStr, OsString}, path::{Path, PathBuf}};
use anyhow::{Result, bail}; use anyhow::{Result, bail};

View file

@ -1,6 +1,6 @@
#![allow(clippy::if_same_then_else, clippy::option_map_unit_fn)] #![allow(clippy::if_same_then_else, clippy::option_map_unit_fn)]
yazi_macro::mod_pub!(cha mounts); yazi_macro::mod_pub!(cha mounts services);
yazi_macro::mod_flat!(calculator cwd file files filter fns op path sorter sorting stage xdg); yazi_macro::mod_flat!(calculator cwd file files filter fns op path sorter sorting stage xdg);

View file

@ -1,9 +1,8 @@
use std::{borrow::Cow, env, ffi::{OsStr, OsString}, future::Future, io, path::{Component, Path, PathBuf}}; use std::{borrow::Cow, env, ffi::{OsStr, OsString}, future::Future, io, path::{Component, Path, PathBuf}};
use tokio::fs; use yazi_shared::url::Url;
use yazi_shared::url::{Loc, Url};
use crate::CWD; use crate::{CWD, services};
#[inline] #[inline]
pub fn clean_url(url: &Url) -> Url { Url::from(clean_path(url)) } pub fn clean_url(url: &Url) -> Url { Url::from(clean_path(url)) }
@ -83,19 +82,19 @@ pub async fn unique_name<F>(u: Url, append: F) -> io::Result<Url>
where where
F: Future<Output = bool>, F: Future<Output = bool>,
{ {
match fs::symlink_metadata(&u).await { match services::symlink_metadata(&u).await {
Ok(_) => _unique_name(u, append.await).await, Ok(_) => _unique_name(u, append.await).await,
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(u), Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(u),
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
async fn _unique_name(mut u: Url, append: bool) -> io::Result<Url> { async fn _unique_name(mut url: Url, append: bool) -> io::Result<Url> {
let Some(stem) = u.file_stem().map(|s| s.to_owned()) else { let Some(stem) = url.file_stem().map(|s| s.to_owned()) else {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "empty file stem")); return Err(io::Error::new(io::ErrorKind::InvalidInput, "empty file stem"));
}; };
let dot_ext = u.extension().map_or_else(OsString::new, |e| { let dot_ext = url.extension().map_or_else(OsString::new, |e| {
let mut s = OsString::with_capacity(e.len() + 1); let mut s = OsString::with_capacity(e.len() + 1);
s.push("."); s.push(".");
s.push(e); s.push(e);
@ -103,9 +102,9 @@ async fn _unique_name(mut u: Url, append: bool) -> io::Result<Url> {
}); });
let mut i = 1u64; let mut i = 1u64;
let mut p = u.to_path(); let mut name = OsString::with_capacity(stem.len() + dot_ext.len() + 5);
loop { loop {
let mut name = OsString::with_capacity(stem.len() + dot_ext.len() + 5); name.clear();
name.push(&stem); name.push(&stem);
if append { if append {
@ -118,16 +117,15 @@ async fn _unique_name(mut u: Url, append: bool) -> io::Result<Url> {
name.push(&dot_ext); name.push(&dot_ext);
} }
p.set_file_name(name); url.set_name(&name);
match fs::symlink_metadata(&p).await { match services::symlink_metadata(&url).await {
Ok(_) => i += 1, Ok(_) => i += 1,
Err(e) if e.kind() == io::ErrorKind::NotFound => break, Err(e) if e.kind() == io::ErrorKind::NotFound => break,
Err(e) => return Err(e), Err(e) => return Err(e),
} }
} }
u.set_loc(Loc::from(u.base(), p)); Ok(url)
Ok(u)
} }
// Parameters // Parameters

View file

@ -0,0 +1,83 @@
use std::{io, path::{Path, PathBuf}};
pub struct Local;
impl Local {
#[inline]
pub async fn canonicalize(path: impl AsRef<Path>) -> io::Result<PathBuf> {
tokio::fs::canonicalize(path).await
}
#[inline]
pub async fn create(path: impl AsRef<Path>) -> io::Result<tokio::fs::File> {
tokio::fs::File::create(path).await
}
#[inline]
pub async fn create_dir(path: impl AsRef<Path>) -> io::Result<()> {
tokio::fs::create_dir(path).await
}
#[inline]
pub async fn create_dir_all(path: impl AsRef<Path>) -> io::Result<()> {
tokio::fs::create_dir_all(path).await
}
#[inline]
pub async fn metadata(url: impl AsRef<Path>) -> io::Result<std::fs::Metadata> {
tokio::fs::metadata(url).await
}
#[inline]
pub async fn open(path: impl AsRef<Path>) -> io::Result<tokio::fs::File> {
tokio::fs::File::open(path).await
}
#[inline]
pub async fn read(path: impl AsRef<Path>) -> io::Result<Vec<u8>> { tokio::fs::read(path).await }
#[inline]
pub async fn read_dir(path: impl AsRef<Path>) -> io::Result<tokio::fs::ReadDir> {
tokio::fs::read_dir(path).await
}
#[inline]
pub async fn read_link(url: impl AsRef<Path>) -> io::Result<PathBuf> {
tokio::fs::read_link(url).await
}
#[inline]
pub async fn read_to_string(path: impl AsRef<Path>) -> io::Result<String> {
tokio::fs::read_to_string(path).await
}
#[inline]
pub async fn remove_dir(path: impl AsRef<Path>) -> io::Result<()> {
tokio::fs::remove_dir(path).await
}
#[inline]
pub async fn remove_dir_all(path: impl AsRef<Path>) -> io::Result<()> {
tokio::fs::remove_dir_all(path).await
}
#[inline]
pub async fn remove_file(path: impl AsRef<Path>) -> io::Result<()> {
tokio::fs::remove_file(path).await
}
#[inline]
pub async fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
tokio::fs::rename(from, to).await
}
#[inline]
pub async fn symlink_metadata(path: impl AsRef<Path>) -> io::Result<std::fs::Metadata> {
tokio::fs::symlink_metadata(path).await
}
#[inline]
pub async fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> io::Result<()> {
tokio::fs::write(path, contents).await
}
}

View file

@ -0,0 +1 @@
yazi_macro::mod_flat!(local services);

View file

@ -0,0 +1,75 @@
use std::io;
use yazi_shared::url::Url;
use crate::services::Local;
#[inline]
pub async fn canonicalize(url: impl AsRef<Url>) -> io::Result<Url> {
Local::canonicalize(url.as_ref()).await.map(Into::into)
}
#[inline]
pub async fn create(url: impl AsRef<Url>) -> io::Result<tokio::fs::File> {
Local::create(url.as_ref()).await
}
#[inline]
pub async fn create_dir(url: impl AsRef<Url>) -> io::Result<()> {
Local::create_dir(url.as_ref()).await
}
#[inline]
pub async fn create_dir_all(url: impl AsRef<Url>) -> io::Result<()> {
Local::create_dir_all(url.as_ref()).await
}
#[inline]
pub async fn metadata(url: impl AsRef<Url>) -> io::Result<std::fs::Metadata> {
Local::metadata(url.as_ref()).await
}
#[inline]
pub async fn open(url: impl AsRef<Url>) -> io::Result<tokio::fs::File> {
Local::open(url.as_ref()).await
}
#[inline]
pub async fn read_dir(url: impl AsRef<Url>) -> io::Result<tokio::fs::ReadDir> {
Local::read_dir(url.as_ref()).await
}
#[inline]
pub async fn read_link(url: impl AsRef<Url>) -> io::Result<Url> {
Local::read_link(url.as_ref()).await.map(Into::into)
}
#[inline]
pub async fn remove_dir(url: impl AsRef<Url>) -> io::Result<()> {
Local::remove_dir(url.as_ref()).await
}
#[inline]
pub async fn remove_dir_all(url: impl AsRef<Url>) -> io::Result<()> {
Local::remove_dir_all(url.as_ref()).await
}
#[inline]
pub async fn remove_file(url: impl AsRef<Url>) -> io::Result<()> {
Local::remove_file(url.as_ref()).await
}
#[inline]
pub async fn rename(from: impl AsRef<Url>, to: impl AsRef<Url>) -> io::Result<()> {
Local::rename(from.as_ref(), to.as_ref()).await
}
#[inline]
pub async fn symlink_metadata(url: impl AsRef<Url>) -> io::Result<std::fs::Metadata> {
Local::symlink_metadata(url.as_ref()).await
}
#[inline]
pub async fn write(url: impl AsRef<Url>, contents: impl AsRef<[u8]>) -> io::Result<()> {
Local::write(url.as_ref(), contents).await
}

View file

@ -3,8 +3,9 @@ use std::{borrow::Cow, io::Cursor, mem, path::{Path, PathBuf}, sync::OnceLock};
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use ratatui::{layout::Size, text::{Line, Span, Text}}; use ratatui::{layout::Size, text::{Line, Span, Text}};
use syntect::{LoadingError, dumps, easy::HighlightLines, highlighting::{self, Theme, ThemeSet}, parsing::{SyntaxReference, SyntaxSet}}; use syntect::{LoadingError, dumps, easy::HighlightLines, highlighting::{self, Theme, ThemeSet}, parsing::{SyntaxReference, SyntaxSet}};
use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}}; use tokio::io::{AsyncBufReadExt, BufReader};
use yazi_config::{THEME, YAZI, preview::PreviewWrap}; use yazi_config::{THEME, YAZI, preview::PreviewWrap};
use yazi_fs::services::Local;
use yazi_shared::{Ids, errors::PeekError, replace_to_printable}; use yazi_shared::{Ids, errors::PeekError, replace_to_printable};
static INCR: Ids = Ids::new(); static INCR: Ids = Ids::new();
@ -38,7 +39,7 @@ impl Highlighter {
pub fn abort() { INCR.next(); } pub fn abort() { INCR.next(); }
pub async fn highlight(&self, skip: usize, size: Size) -> Result<Text<'static>, PeekError> { pub async fn highlight(&self, skip: usize, size: Size) -> Result<Text<'static>, PeekError> {
let mut reader = BufReader::new(File::open(&self.path).await?); let mut reader = BufReader::new(Local::open(&self.path).await?);
let syntax = Self::find_syntax(&self.path).await; let syntax = Self::find_syntax(&self.path).await;
let mut plain = syntax.is_err(); let mut plain = syntax.is_err();
@ -142,7 +143,7 @@ impl Highlighter {
} }
let mut line = String::new(); let mut line = String::new();
let mut reader = BufReader::new(File::open(&path).await?); let mut reader = BufReader::new(Local::open(&path).await?);
reader.read_line(&mut line).await?; reader.read_line(&mut line).await?;
syntaxes.find_syntax_by_first_line(&line).ok_or_else(|| anyhow!("No syntax found")) syntaxes.find_syntax_by_first_line(&line).ok_or_else(|| anyhow!("No syntax found"))
} }

View file

@ -1,8 +1,7 @@
use globset::GlobBuilder; use globset::GlobBuilder;
use mlua::{ExternalError, ExternalResult, Function, IntoLua, IntoLuaMulti, Lua, Table, Value}; use mlua::{ExternalError, ExternalResult, Function, IntoLua, IntoLuaMulti, Lua, Table, Value};
use tokio::fs;
use yazi_binding::{Cha, Composer, ComposerGet, ComposerSet, Error, File, Url, UrlRef}; use yazi_binding::{Cha, Composer, ComposerGet, ComposerSet, Error, File, Url, UrlRef};
use yazi_fs::{mounts::PARTITIONS, remove_dir_clean}; use yazi_fs::{mounts::PARTITIONS, remove_dir_clean, services};
use crate::bindings::SizeCalculator; use crate::bindings::SizeCalculator;
@ -49,9 +48,9 @@ fn cwd(lua: &Lua) -> mlua::Result<Function> {
fn cha(lua: &Lua) -> mlua::Result<Function> { fn cha(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, (url, follow): (UrlRef, Option<bool>)| async move { lua.create_async_function(|lua, (url, follow): (UrlRef, Option<bool>)| async move {
let meta = if follow.unwrap_or(false) { let meta = if follow.unwrap_or(false) {
fs::metadata(&*url).await services::metadata(&*url).await
} else { } else {
fs::symlink_metadata(&*url).await services::symlink_metadata(&*url).await
}; };
match meta { match meta {
@ -63,7 +62,7 @@ fn cha(lua: &Lua) -> mlua::Result<Function> {
fn write(lua: &Lua) -> mlua::Result<Function> { fn write(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, (url, data): (UrlRef, mlua::String)| async move { lua.create_async_function(|lua, (url, data): (UrlRef, mlua::String)| async move {
match fs::write(&*url, data.as_bytes()).await { match services::write(&*url, data.as_bytes()).await {
Ok(()) => true.into_lua_multi(&lua), Ok(()) => true.into_lua_multi(&lua),
Err(e) => (false, Error::Io(e)).into_lua_multi(&lua), Err(e) => (false, Error::Io(e)).into_lua_multi(&lua),
} }
@ -73,8 +72,8 @@ fn write(lua: &Lua) -> mlua::Result<Function> {
fn create(lua: &Lua) -> mlua::Result<Function> { fn create(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, (r#type, url): (mlua::String, UrlRef)| async move { lua.create_async_function(|lua, (r#type, url): (mlua::String, UrlRef)| async move {
let result = match r#type.as_bytes().as_ref() { let result = match r#type.as_bytes().as_ref() {
b"dir" => fs::create_dir(&*url).await, b"dir" => services::create_dir(&*url).await,
b"dir_all" => fs::create_dir_all(&*url).await, b"dir_all" => services::create_dir_all(&*url).await,
_ => Err("Creation type must be 'dir' or 'dir_all'".into_lua_err())?, _ => Err("Creation type must be 'dir' or 'dir_all'".into_lua_err())?,
}; };
@ -88,9 +87,9 @@ fn create(lua: &Lua) -> mlua::Result<Function> {
fn remove(lua: &Lua) -> mlua::Result<Function> { fn remove(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, (r#type, url): (mlua::String, UrlRef)| async move { lua.create_async_function(|lua, (r#type, url): (mlua::String, UrlRef)| async move {
let result = match r#type.as_bytes().as_ref() { let result = match r#type.as_bytes().as_ref() {
b"file" => fs::remove_file(&*url).await, b"file" => services::remove_file(&*url).await,
b"dir" => fs::remove_dir(&*url).await, b"dir" => services::remove_dir(&*url).await,
b"dir_all" => fs::remove_dir_all(&*url).await, b"dir_all" => services::remove_dir_all(&*url).await,
b"dir_clean" => Ok(remove_dir_clean(&url).await), b"dir_clean" => Ok(remove_dir_clean(&url).await),
_ => Err("Removal type must be 'file', 'dir', 'dir_all', or 'dir_clean'".into_lua_err())?, _ => Err("Removal type must be 'file', 'dir', 'dir_all', or 'dir_clean'".into_lua_err())?,
}; };
@ -122,7 +121,7 @@ fn read_dir(lua: &Lua) -> mlua::Result<Function> {
let limit = options.raw_get("limit").unwrap_or(usize::MAX); let limit = options.raw_get("limit").unwrap_or(usize::MAX);
let resolve = options.raw_get("resolve").unwrap_or(false); let resolve = options.raw_get("resolve").unwrap_or(false);
let mut it = match fs::read_dir(&*dir).await { let mut it = match services::read_dir(&*dir).await {
Ok(it) => it, Ok(it) => it,
Err(e) => return (Value::Nil, Error::Io(e)).into_lua_multi(&lua), Err(e) => return (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
}; };

View file

@ -3,8 +3,8 @@ use std::{borrow::Cow, collections::HashMap, ops::Deref};
use anyhow::{Context, Result, bail}; use anyhow::{Context, Result, bail};
use mlua::{ChunkMode, ExternalError, Lua, Table}; use mlua::{ChunkMode, ExternalError, Lua, Table};
use parking_lot::RwLock; use parking_lot::RwLock;
use tokio::fs;
use yazi_boot::BOOT; use yazi_boot::BOOT;
use yazi_fs::services::Local;
use yazi_macro::plugin_preset as preset; use yazi_macro::plugin_preset as preset;
use yazi_shared::{LOG_LEVEL, RoCell}; use yazi_shared::{LOG_LEVEL, RoCell};
@ -61,7 +61,7 @@ impl Loader {
let p = BOOT.plugin_dir.join(format!("{name}.yazi/main.lua")); let p = BOOT.plugin_dir.join(format!("{name}.yazi/main.lua"));
let chunk = let chunk =
fs::read(&p).await.with_context(|| format!("Failed to load plugin from {p:?}"))?.into(); Local::read(&p).await.with_context(|| format!("Failed to load plugin from {p:?}"))?.into();
let result = Self::compatible_or_error(name, &chunk); let result = Self::compatible_or_error(name, &chunk);
let inspect = f(&chunk); let inspect = f(&chunk);

View file

@ -1,10 +1,11 @@
use std::{borrow::Cow, collections::VecDeque, path::Path}; // FIXME: VFS, depends on yazi_fs::fns
use std::{borrow::Cow, collections::VecDeque};
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use tokio::{fs::{self, DirEntry}, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc}; use tokio::{fs::{self, DirEntry}, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc};
use tracing::warn; use tracing::warn;
use yazi_config::YAZI; use yazi_config::YAZI;
use yazi_fs::{SizeCalculator, cha::Cha, copy_with_progress, maybe_exists, ok_or_not_found, path_relative_to, skip_path}; use yazi_fs::{SizeCalculator, cha::Cha, copy_with_progress, maybe_exists, ok_or_not_found, path_relative_to, services, skip_path};
use yazi_shared::{Id, url::Url}; use yazi_shared::{Id, url::Url};
use super::{FileIn, FileInDelete, FileInHardlink, FileInLink, FileInPaste, FileInTrash}; use super::{FileIn, FileInDelete, FileInHardlink, FileInLink, FileInPaste, FileInTrash};
@ -327,17 +328,17 @@ impl File {
} }
#[inline] #[inline]
async fn cha(path: &Path, follow: bool) -> io::Result<Cha> { async fn cha(url: &Url, follow: bool) -> io::Result<Cha> {
let meta = fs::symlink_metadata(path).await?; let meta = services::symlink_metadata(url).await?;
Ok(if follow { Cha::from_follow(path, meta).await } else { Cha::new(path, meta) }) Ok(if follow { Cha::from_follow(url, meta).await } else { Cha::new(url, meta) })
} }
#[inline] #[inline]
async fn cha_from(entry: DirEntry, path: &Path, follow: bool) -> io::Result<Cha> { async fn cha_from(entry: DirEntry, url: &Url, follow: bool) -> io::Result<Cha> {
Ok(if follow { Ok(if follow {
Cha::from_follow(path, entry.metadata().await?).await Cha::from_follow(url, entry.metadata().await?).await
} else { } else {
Cha::new(path, entry.metadata().await?) Cha::new(url, entry.metadata().await?)
}) })
} }
} }

View file

@ -3,10 +3,10 @@ use std::{ffi::OsString, future::Future, sync::Arc, time::Duration};
use anyhow::Result; use anyhow::Result;
use futures::{FutureExt, future::BoxFuture}; use futures::{FutureExt, future::BoxFuture};
use parking_lot::Mutex; use parking_lot::Mutex;
use tokio::{fs, select, sync::mpsc::{self, UnboundedReceiver}, task::JoinHandle}; use tokio::{select, sync::mpsc::{self, UnboundedReceiver}, task::JoinHandle};
use yazi_config::{YAZI, plugin::{Fetcher, Preloader}}; use yazi_config::{YAZI, plugin::{Fetcher, Preloader}};
use yazi_dds::Pump; use yazi_dds::Pump;
use yazi_fs::{must_be_dir, remove_dir_clean, unique_name}; use yazi_fs::{must_be_dir, remove_dir_clean, services, unique_name};
use yazi_parser::{app::PluginOpt, tasks::ProcessExecOpt}; use yazi_parser::{app::PluginOpt, tasks::ProcessExecOpt};
use yazi_proxy::MgrProxy; use yazi_proxy::MgrProxy;
use yazi_shared::{Id, Throttle, url::Url}; use yazi_shared::{Id, Throttle, url::Url};
@ -166,7 +166,7 @@ impl Scheduler {
move |canceled: bool| { move |canceled: bool| {
async move { async move {
if !canceled { if !canceled {
fs::remove_dir_all(&target).await.ok(); services::remove_dir_all(&target).await.ok();
MgrProxy::update_tasks(&target); MgrProxy::update_tasks(&target);
Pump::push_delete(target); Pump::push_delete(target);
} }

View file

@ -95,6 +95,22 @@ impl Loc {
} }
} }
pub fn set_name(&mut self, name: impl AsRef<OsStr>) {
let name = name.as_ref();
if name == self.name() {
return;
}
if self.name > name.len() {
self.urn -= self.name - name.len();
} else {
self.urn += name.len() - self.name;
}
self.name = name.len();
self.path.set_file_name(name);
}
#[inline] #[inline]
pub fn base(&self) -> &Path { pub fn base(&self) -> &Path {
Path::new(unsafe { Path::new(unsafe {
@ -122,6 +138,8 @@ impl Loc {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::path::MAIN_SEPARATOR;
use super::*; use super::*;
#[test] #[test]
@ -159,4 +177,22 @@ mod tests {
assert_eq!(loc.name(), OsStr::new("foo")); assert_eq!(loc.name(), OsStr::new("foo"));
assert_eq!(loc.base().as_os_str(), OsStr::new("/root/")); assert_eq!(loc.base().as_os_str(), OsStr::new("/root/"));
} }
#[test]
fn test_set_name() {
let mut loc = Loc::from(Path::new("/root"), "/root/code/foo/".into());
assert_eq!(loc.urn().as_os_str(), OsStr::new("code/foo"));
assert_eq!(loc.name(), OsStr::new("foo"));
assert_eq!(loc.base().as_os_str(), OsStr::new("/root/"));
loc.set_name("bar.txt");
assert_eq!(loc.urn().as_os_str(), OsString::from(format!("code{MAIN_SEPARATOR}bar.txt")));
assert_eq!(loc.name(), OsStr::new("bar.txt"));
assert_eq!(loc.base().as_os_str(), OsStr::new("/root/"));
loc.set_name("baz");
assert_eq!(loc.urn().as_os_str(), OsString::from(format!("code{MAIN_SEPARATOR}baz")));
assert_eq!(loc.name(), OsStr::new("baz"));
assert_eq!(loc.base().as_os_str(), OsStr::new("/root/"));
}
} }

View file

@ -167,6 +167,9 @@ impl Url {
}) })
} }
#[inline]
pub fn set_name(&mut self, name: impl AsRef<OsStr>) { self.loc.set_name(name); }
#[inline] #[inline]
pub fn pair(&self) -> Option<(Self, UrnBuf)> { Some((self.parent_url()?, self.loc.urn_owned())) } pub fn pair(&self) -> Option<(Self, UrnBuf)> { Some((self.parent_url()?, self.loc.urn_owned())) }
@ -240,9 +243,7 @@ impl Url {
#[inline] #[inline]
pub fn set_loc(&mut self, loc: Loc) { self.loc = loc; } pub fn set_loc(&mut self, loc: Loc) { self.loc = loc; }
#[inline] // FIXME: remove
pub fn to_path(&self) -> PathBuf { self.loc.to_path_buf() }
#[inline] #[inline]
pub fn into_path(self) -> PathBuf { self.loc.into_path() } pub fn into_path(self) -> PathBuf { self.loc.into_path() }