From 25167c383a7f1a5d9a792b64b5c1ac2dd44c209e Mon Sep 17 00:00:00 2001 From: sxyazi Date: Tue, 7 Nov 2023 08:42:02 +0800 Subject: [PATCH] .. --- Cargo.lock | 2 + cspell.json | 2 +- yazi-core/Cargo.toml | 1 + yazi-core/src/external/fzf.rs | 25 +++--- yazi-core/src/external/zoxide.rs | 25 +++--- yazi-core/src/files/file.rs | 46 ++++++---- yazi-core/src/files/files.rs | 8 +- yazi-core/src/files/sorter.rs | 14 +-- yazi-core/src/tab/commands/backstack.rs | 4 +- yazi-core/src/tab/commands/cd.rs | 14 ++- yazi-core/src/tab/commands/escape.rs | 36 +++++--- yazi-core/src/tab/commands/jump.rs | 11 +-- yazi-core/src/tab/commands/search.rs | 2 +- yazi-core/src/tasks/tasks.rs | 2 +- yazi-fm/src/app.rs | 4 +- yazi-fm/src/executor.rs | 5 +- yazi-plugin/src/bindings/files.rs | 36 ++++---- yazi-shared/Cargo.toml | 1 + yazi-shared/src/cha.rs | 108 ++++++++++++++++++++++++ yazi-shared/src/fs.rs | 15 +--- yazi-shared/src/lib.rs | 2 + yazi-shared/src/url.rs | 23 ++++- 22 files changed, 251 insertions(+), 135 deletions(-) create mode 100644 yazi-shared/src/cha.rs diff --git a/Cargo.lock b/Cargo.lock index fa7939fd..6f4b2f4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2487,6 +2487,7 @@ version = "0.1.5" dependencies = [ "anyhow", "async-channel", + "bitflags 2.4.1", "clipboard-win", "crossterm", "futures", @@ -2559,6 +2560,7 @@ name = "yazi-shared" version = "0.1.5" dependencies = [ "anyhow", + "bitflags 2.4.1", "crossterm", "futures", "libc", diff --git a/cspell.json b/cspell.json index a97d04ed..89b6f851 100644 --- a/cspell.json +++ b/cspell.json @@ -1 +1 @@ -{"language":"en","version":"0.2","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ"]} \ No newline at end of file +{"version":"0.2","words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags"],"language":"en","flagWords":[]} diff --git a/yazi-core/Cargo.toml b/yazi-core/Cargo.toml index f85ed497..d034d6b7 100644 --- a/yazi-core/Cargo.toml +++ b/yazi-core/Cargo.toml @@ -16,6 +16,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.1.5" } # External dependencies anyhow = "^1" async-channel = "^1" +bitflags = "^2" crossterm = "^0" futures = "^0" indexmap = "^2" diff --git a/yazi-core/src/external/fzf.rs b/yazi-core/src/external/fzf.rs index 475367d9..625c0156 100644 --- a/yazi-core/src/external/fzf.rs +++ b/yazi-core/src/external/fzf.rs @@ -1,27 +1,22 @@ use std::process::Stdio; -use anyhow::Result; -use tokio::{process::Command, sync::oneshot::{self, Receiver}}; +use anyhow::{bail, Result}; +use tokio::process::Command; use yazi_shared::Url; pub struct FzfOpt { pub cwd: Url, } -pub fn fzf(opt: FzfOpt) -> Result>> { +pub async fn fzf(opt: FzfOpt) -> Result { let child = Command::new("fzf").current_dir(&opt.cwd).kill_on_drop(true).stdout(Stdio::piped()).spawn()?; - let (tx, rx) = oneshot::channel(); - tokio::spawn(async move { - if let Ok(output) = child.wait_with_output().await { - let selected = String::from_utf8_lossy(&output.stdout).trim().to_string(); - if !selected.is_empty() { - tx.send(Ok(opt.cwd.join(selected))).ok(); - return; - } - } - tx.send(Err(anyhow::anyhow!("No match"))).ok(); - }); - Ok(rx) + let output = child.wait_with_output().await?; + let selected = String::from_utf8_lossy(&output.stdout).trim().to_string(); + + if !selected.is_empty() { + return Ok(Url::from(selected)); + } + bail!("No match") } diff --git a/yazi-core/src/external/zoxide.rs b/yazi-core/src/external/zoxide.rs index f15ed57c..36828b57 100644 --- a/yazi-core/src/external/zoxide.rs +++ b/yazi-core/src/external/zoxide.rs @@ -1,14 +1,14 @@ use std::process::Stdio; -use anyhow::Result; -use tokio::{process::Command, sync::oneshot::{self, Receiver}}; +use anyhow::{bail, Result}; +use tokio::process::Command; use yazi_shared::Url; pub struct ZoxideOpt { pub cwd: Url, } -pub fn zoxide(opt: ZoxideOpt) -> Result>> { +pub async fn zoxide(opt: ZoxideOpt) -> Result { let child = Command::new("zoxide") .args(["query", "-i", "--exclude"]) .arg(&opt.cwd) @@ -16,16 +16,11 @@ pub fn zoxide(opt: ZoxideOpt) -> Result>> { .stdout(Stdio::piped()) .spawn()?; - let (tx, rx) = oneshot::channel(); - tokio::spawn(async move { - if let Ok(output) = child.wait_with_output().await { - let selected = String::from_utf8_lossy(&output.stdout).trim().to_string(); - if !selected.is_empty() { - tx.send(Ok(Url::from(selected))).ok(); - return; - } - } - tx.send(Err(anyhow::anyhow!("No match"))).ok(); - }); - Ok(rx) + let output = child.wait_with_output().await?; + let selected = String::from_utf8_lossy(&output.stdout).trim().to_string(); + + if !selected.is_empty() { + return Ok(Url::from(selected).into_dir()); + } + bail!("No match") } diff --git a/yazi-core/src/files/file.rs b/yazi-core/src/files/file.rs index c136a04a..468a1859 100644 --- a/yazi-core/src/files/file.rs +++ b/yazi-core/src/files/file.rs @@ -1,36 +1,55 @@ -use std::{borrow::Cow, collections::BTreeMap, ffi::OsStr, fs::Metadata}; +use std::{borrow::Cow, collections::BTreeMap, ffi::OsStr, fs::Metadata, ops::Deref}; use anyhow::Result; use tokio::fs; -use yazi_shared::Url; +use yazi_shared::{Cha, ChaMeta, Url}; #[derive(Clone, Debug)] pub struct File { pub url: Url, - pub meta: Metadata, + pub cha: Cha, pub(super) link_to: Option, - pub is_link: bool, - pub is_hidden: bool, +} + +impl Deref for File { + type Target = Cha; + + #[inline] + fn deref(&self) -> &Self::Target { &self.cha } } impl File { #[inline] pub async fn from(url: Url) -> Result { - let meta = fs::metadata(&url).await?; + let meta = fs::symlink_metadata(&url).await?; Ok(Self::from_meta(url, meta).await) } pub async fn from_meta(url: Url, mut meta: Metadata) -> Self { - let is_link = meta.is_symlink(); - let mut link_to = None; + let mut cm = ChaMeta::empty(); + let (is_link, mut link_to) = (meta.is_symlink(), None); if is_link { + cm |= ChaMeta::LINK; meta = fs::metadata(&url).await.unwrap_or(meta); link_to = fs::read_link(&url).await.map(Url::from).ok(); } - let is_hidden = url.file_name().map(|s| s.to_string_lossy().starts_with('.')).unwrap_or(false); - Self { url, meta, link_to, is_link, is_hidden } + if is_link && meta.is_symlink() { + cm |= ChaMeta::BAD_LINK; + } + + if url.was_hidden() { + cm |= ChaMeta::HIDDEN; + } + + Self { url, cha: Cha::from(meta).with_meta(cm), link_to } + } + + #[inline] + pub fn from_dummy(url: Url) -> Self { + let cm = if url.was_hidden() { ChaMeta::HIDDEN } else { ChaMeta::empty() }; + Self { url, cha: Cha::default().with_meta(cm), link_to: None } } #[inline] @@ -60,13 +79,6 @@ impl File { #[inline] pub fn parent(&self) -> Option { self.url.parent_url() } - // --- Meta - #[inline] - pub fn is_file(&self) -> bool { self.meta.is_file() } - - #[inline] - pub fn is_dir(&self) -> bool { self.meta.is_dir() } - // --- Link to / Is link #[inline] pub fn link_to(&self) -> Option<&Url> { self.link_to.as_ref() } diff --git a/yazi-core/src/files/files.rs b/yazi-core/src/files/files.rs index 944947a6..a11d89ad 100644 --- a/yazi-core/src/files/files.rs +++ b/yazi-core/src/files/files.rs @@ -128,7 +128,7 @@ impl Files { pub fn update_full(&mut self, mut items: Vec) -> bool { if !self.show_hidden { - (self.hidden, items) = items.into_iter().partition(|f| f.is_hidden); + (self.hidden, items) = items.into_iter().partition(|f| f.is_hidden()); } self.ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed); self.sorter.sort(&mut items, &self.sizes); @@ -146,7 +146,7 @@ impl Files { if self.show_hidden { self.items.extend(items); } else { - let (hidden, items): (Vec<_>, Vec<_>) = items.into_iter().partition(|f| f.is_hidden); + let (hidden, items): (Vec<_>, Vec<_>) = items.into_iter().partition(|f| f.is_hidden()); self.items.extend(items); self.hidden.extend(hidden); } @@ -178,7 +178,7 @@ impl Files { pub fn update_creating(&mut self, mut todo: BTreeMap) -> bool { if !self.show_hidden { - todo.retain(|_, f| !f.is_hidden); + todo.retain(|_, f| !f.is_hidden()); } let b = self.update_replacing(&mut todo); @@ -335,7 +335,7 @@ impl Files { self.sorter.sort(&mut self.items, &self.sizes); } else { let items = mem::take(&mut self.items); - (self.hidden, self.items) = items.into_iter().partition(|f| f.is_hidden); + (self.hidden, self.items) = items.into_iter().partition(|f| f.is_hidden()); } self.show_hidden = state; diff --git a/yazi-core/src/files/sorter.rs b/yazi-core/src/files/sorter.rs index 67436796..bc010059 100644 --- a/yazi-core/src/files/sorter.rs +++ b/yazi-core/src/files/sorter.rs @@ -33,13 +33,13 @@ impl FilesSorter { ) }), SortBy::Created => items.sort_unstable_by(|a, b| { - if let (Ok(aa), Ok(bb)) = (a.meta.created(), b.meta.created()) { + if let (Some(aa), Some(bb)) = (a.created, b.created) { return self.cmp(aa, bb, self.promote(a, b)); } Ordering::Equal }), SortBy::Modified => items.sort_unstable_by(|a, b| { - if let (Ok(aa), Ok(bb)) = (a.meta.modified(), b.meta.modified()) { + if let (Some(aa), Some(bb)) = (a.modified, b.modified) { return self.cmp(aa, bb, self.promote(a, b)); } Ordering::Equal @@ -48,7 +48,7 @@ impl FilesSorter { SortBy::Size => items.sort_unstable_by(|a, b| { let aa = if a.is_dir() { sizes.get(&a.url).copied() } else { None }; let bb = if b.is_dir() { sizes.get(&b.url).copied() } else { None }; - self.cmp(aa.unwrap_or(a.meta.len()), bb.unwrap_or(b.meta.len()), self.promote(a, b)) + self.cmp(aa.unwrap_or(a.len), bb.unwrap_or(b.len), self.promote(a, b)) }), } true @@ -72,13 +72,7 @@ impl FilesSorter { if self.reverse { ordering.reverse() } else { ordering } }); - let dummy = File { - url: Default::default(), - meta: items[0].meta.clone(), - link_to: None, - is_link: false, - is_hidden: false, - }; + let dummy = File { url: Default::default(), cha: Default::default(), link_to: None }; let mut new = Vec::with_capacity(indices.len()); for i in indices { diff --git a/yazi-core/src/tab/commands/backstack.rs b/yazi-core/src/tab/commands/backstack.rs index a54c7633..a53cb7c3 100644 --- a/yazi-core/src/tab/commands/backstack.rs +++ b/yazi-core/src/tab/commands/backstack.rs @@ -3,14 +3,14 @@ use crate::tab::Tab; impl Tab { pub fn back(&mut self) -> bool { if let Some(url) = self.backstack.shift_backward().cloned() { - futures::executor::block_on(self.cd(url)); + self.cd(url.into_dir()); } false } pub fn forward(&mut self) -> bool { if let Some(url) = self.backstack.shift_forward().cloned() { - futures::executor::block_on(self.cd(url)); + self.cd(url.into_dir()); } false } diff --git a/yazi-core/src/tab/commands/cd.rs b/yazi-core/src/tab/commands/cd.rs index 2722a6d0..45eb6a9f 100644 --- a/yazi-core/src/tab/commands/cd.rs +++ b/yazi-core/src/tab/commands/cd.rs @@ -9,16 +9,12 @@ use crate::{emit, files::{File, FilesOp}, input::InputOpt, tab::Tab}; impl Tab { // TODO: change to sync, and remove `Event::Cd` - pub async fn cd(&mut self, mut target: Url) -> bool { - let Ok(file) = File::from(target.clone()).await else { - return false; - }; - + pub fn cd(&mut self, mut target: Url) -> bool { let mut hovered = None; - if !file.is_dir() { - hovered = Some(file.url()); - target = target.parent_url().unwrap(); - emit!(Files(FilesOp::Creating(target.clone(), file.into_map()))); + if let (false, Some(parent)) = (target.was_dir(), target.parent_url()) { + emit!(Files(FilesOp::Creating(parent.clone(), File::from_dummy(target.clone()).into_map()))); + hovered = Some(target); + target = parent; } // Already in target diff --git a/yazi-core/src/tab/commands/escape.rs b/yazi-core/src/tab/commands/escape.rs index 3d073808..e8e7eca5 100644 --- a/yazi-core/src/tab/commands/escape.rs +++ b/yazi-core/src/tab/commands/escape.rs @@ -1,19 +1,27 @@ +use bitflags::bitflags; use yazi_config::keymap::Exec; use crate::tab::{Mode, Tab}; -pub struct Opt(u8); +bitflags! { + pub struct Opt: u8 { + const FIND = 0b0001; + const VISUAL = 0b0010; + const SELECT = 0b0100; + const SEARCH = 0b1000; + } +} impl From<&Exec> for Opt { fn from(e: &Exec) -> Self { - Self(e.named.iter().fold(0, |acc, (k, _)| match k.as_str() { - "all" => 0b1111, - "find" => acc | 0b0001, - "visual" => acc | 0b0010, - "select" => acc | 0b0100, - "search" => acc | 0b1000, + e.named.iter().fold(Opt::empty(), |acc, (k, _)| match k.as_bytes() { + b"all" => Self::all(), + b"find" => acc | Self::FIND, + b"visual" => acc | Self::VISUAL, + b"select" => acc | Self::SELECT, + b"search" => acc | Self::SEARCH, _ => acc, - })) + }) } } @@ -38,8 +46,8 @@ impl Tab { fn escape_search(&mut self) -> bool { self.search_stop() } pub fn escape(&mut self, opt: impl Into) -> bool { - let opt = opt.into().0; - if opt == 0 { + let opt = opt.into() as Opt; + if opt.is_empty() { return self.escape_find() || self.escape_visual() || self.escape_select() @@ -47,16 +55,16 @@ impl Tab { } let mut b = false; - if opt & 0b0001 != 0 { + if opt.contains(Opt::FIND) { b |= self.escape_find(); } - if opt & 0b0010 != 0 { + if opt.contains(Opt::VISUAL) { b |= self.escape_visual(); } - if opt & 0b0100 != 0 { + if opt.contains(Opt::SELECT) { b |= self.escape_select(); } - if opt & 0b1000 != 0 { + if opt.contains(Opt::SEARCH) { b |= self.escape_search(); } b diff --git a/yazi-core/src/tab/commands/jump.rs b/yazi-core/src/tab/commands/jump.rs index 58297e19..77042771 100644 --- a/yazi-core/src/tab/commands/jump.rs +++ b/yazi-core/src/tab/commands/jump.rs @@ -11,12 +11,13 @@ impl Tab { let _defer = Defer::new(|| Event::Stop(false, None).emit()); emit!(Stop(true)).await; - let rx = - if global { external::fzf(FzfOpt { cwd }) } else { external::zoxide(ZoxideOpt { cwd }) }?; + let url = if global { + external::fzf(FzfOpt { cwd }).await + } else { + external::zoxide(ZoxideOpt { cwd }).await + }?; - if let Ok(target) = rx.await? { - emit!(Cd(target)); - } + emit!(Cd(url)); Ok::<(), anyhow::Error>(()) }); false diff --git a/yazi-core/src/tab/commands/search.rs b/yazi-core/src/tab/commands/search.rs index c471baa2..fa2fa927 100644 --- a/yazi-core/src/tab/commands/search.rs +++ b/yazi-core/src/tab/commands/search.rs @@ -17,7 +17,7 @@ impl Tab { self.search = Some(tokio::spawn(async move { let Some(Ok(subject)) = emit!(Input(InputOpt::top("Search:"))).recv().await else { - bail!("canceled") + bail!("") }; cwd = cwd.into_search(subject.clone()); diff --git a/yazi-core/src/tasks/tasks.rs b/yazi-core/src/tasks/tasks.rs index bd6d8ad9..00a6ee02 100644 --- a/yazi-core/src/tasks/tasks.rs +++ b/yazi-core/src/tasks/tasks.rs @@ -252,7 +252,7 @@ impl Tasks { pub fn precache_mime(&self, targets: &[File], mimetype: &HashMap) -> bool { let targets: Vec<_> = targets .iter() - .filter(|f| f.is_file() && !mimetype.contains_key(&f.url)) + .filter(|f| !f.is_dir() && !mimetype.contains_key(&f.url)) .map(|f| f.url()) .collect(); diff --git a/yazi-fm/src/app.rs b/yazi-fm/src/app.rs index 3a83a297..44ce1aa6 100644 --- a/yazi-fm/src/app.rs +++ b/yazi-fm/src/app.rs @@ -122,9 +122,7 @@ impl App { let tasks = &mut self.cx.tasks; match event { Event::Cd(url) => { - futures::executor::block_on(async { - manager.active_mut().cd(expand_url(url)).await; - }); + manager.active_mut().cd(expand_url(url)); } Event::Refresh => { manager.refresh(); diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index 9ad78584..5b8062d5 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -1,5 +1,5 @@ use yazi_config::{keymap::{Control, Exec, Key, KeymapLayer}, KEYMAP}; -use yazi_core::{emit, input::InputMode, tab::FinderCase, Ctx}; +use yazi_core::{input::InputMode, tab::FinderCase, Ctx}; use yazi_shared::{optional_bool, Url}; pub(super) struct Executor<'a> { @@ -96,8 +96,7 @@ impl<'a> Executor<'a> { if exec.named.contains_key("interactive") { self.cx.manager.active_mut().cd_interactive(url) } else { - emit!(Cd(url)); - false + self.cx.manager.active_mut().cd(url) } } diff --git a/yazi-plugin/src/bindings/files.rs b/yazi-plugin/src/bindings/files.rs index 4d75df9f..974d9858 100644 --- a/yazi-plugin/src/bindings/files.rs +++ b/yazi-plugin/src/bindings/files.rs @@ -16,8 +16,8 @@ impl UserData for File { fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { fields.add_field_method_get("url", |_, me| Ok(Url::from(&me.0.url))); fields.add_field_method_get("link_to", |_, me| Ok(me.0.link_to().map(Url::from))); - fields.add_field_method_get("is_link", |_, me| Ok(me.0.is_link)); - fields.add_field_method_get("is_hidden", |_, me| Ok(me.0.is_hidden)); + fields.add_field_method_get("is_link", |_, me| Ok(me.0.is_link())); + fields.add_field_method_get("is_hidden", |_, me| Ok(me.0.is_hidden())); } } @@ -50,36 +50,30 @@ impl Files { LUA.register_userdata_type::(|reg| { reg.add_field_method_get("url", |_, me| Ok(Url::from(&me.url))); reg.add_field_method_get("link_to", |_, me| Ok(me.link_to().map(Url::from))); - reg.add_field_method_get("is_link", |_, me| Ok(me.is_link)); - reg.add_field_method_get("is_hidden", |_, me| Ok(me.is_hidden)); + reg.add_field_method_get("is_link", |_, me| Ok(me.is_link())); + reg.add_field_method_get("is_hidden", |_, me| Ok(me.is_hidden())); // Metadata - reg.add_field_method_get("is_file", |_, me| Ok(me.is_file())); reg.add_field_method_get("is_dir", |_, me| Ok(me.is_dir())); - reg.add_field_method_get("is_symlink", |_, me| Ok(me.meta.is_symlink())); + reg.add_field_method_get("is_symlink", |_, me| Ok(me.is_link())); #[cfg(unix)] { - use std::os::unix::prelude::FileTypeExt; - reg.add_field_method_get("is_block_device", |_, me| { - Ok(me.meta.file_type().is_block_device()) - }); - reg - .add_field_method_get("is_char_device", |_, me| Ok(me.meta.file_type().is_char_device())); - reg.add_field_method_get("is_fifo", |_, me| Ok(me.meta.file_type().is_fifo())); - reg.add_field_method_get("is_socket", |_, me| Ok(me.meta.file_type().is_socket())); + reg.add_field_method_get("is_block_device", |_, me| Ok(me.is_block_device())); + reg.add_field_method_get("is_char_device", |_, me| Ok(me.is_char_device())); + reg.add_field_method_get("is_fifo", |_, me| Ok(me.is_fifo())); + reg.add_field_method_get("is_socket", |_, me| Ok(me.is_socket())); } - reg.add_field_method_get("length", |_, me| Ok(me.meta.len())); + reg.add_field_method_get("length", |_, me| Ok(me.len)); reg.add_field_method_get("created", |_, me| { - Ok(me.meta.created()?.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok()) + Ok(me.created.and_then(|t| t.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok())) }); reg.add_field_method_get("modified", |_, me| { - Ok(me.meta.modified()?.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok()) + Ok(me.modified.and_then(|t| t.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok())) }); reg.add_field_method_get("accessed", |_, me| { - Ok(me.meta.accessed()?.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok()) + Ok(me.accessed.and_then(|t| t.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok())) }); - reg - .add_method("permissions", |_, me, ()| Ok(yazi_shared::permissions(me.meta.permissions()))); + reg.add_method("permissions", |_, me, ()| Ok(yazi_shared::permissions(me.permissions))); // Extension reg.add_field_method_get("name", |_, me| { @@ -88,7 +82,7 @@ impl Files { reg.add_function("size", |_, me: AnyUserData| { let file = me.borrow::()?; if !file.is_dir() { - return Ok(Some(file.meta.len())); + return Ok(Some(file.len)); } let folder = me.named_user_value::>("folder")?; diff --git a/yazi-shared/Cargo.toml b/yazi-shared/Cargo.toml index 596ff820..bd5447c9 100644 --- a/yazi-shared/Cargo.toml +++ b/yazi-shared/Cargo.toml @@ -10,6 +10,7 @@ repository = "https://github.com/sxyazi/yazi" [dependencies] anyhow = "^1" +bitflags = "^2" crossterm = "^0" futures = "^0" libc = "^0" diff --git a/yazi-shared/src/cha.rs b/yazi-shared/src/cha.rs new file mode 100644 index 00000000..ddfd6923 --- /dev/null +++ b/yazi-shared/src/cha.rs @@ -0,0 +1,108 @@ +use std::{fs::Metadata, time::SystemTime}; + +use bitflags::bitflags; + +bitflags! { + #[derive(Clone, Copy, Debug, Default)] + pub struct ChaMeta: u8 { + const DIR = 0b00000001; + + const HIDDEN = 0b00000010; + const LINK = 0b00000100; + const BAD_LINK = 0b00001000; + + const BLOCK_DEVICE = 0b00010000; + const CHAR_DEVICE = 0b00100000; + const FIFO = 0b01000000; + const SOCKET = 0b10000000; + } +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct Cha { + pub meta: ChaMeta, + pub len: u64, + pub accessed: Option, + pub created: Option, + pub modified: Option, + #[cfg(unix)] + pub permissions: u16, +} + +impl From for Cha { + fn from(m: Metadata) -> Self { + let mut cm = ChaMeta::empty(); + if m.is_dir() { + cm |= ChaMeta::DIR; + } + + #[cfg(unix)] + { + use std::os::unix::prelude::FileTypeExt; + if m.file_type().is_block_device() { + cm |= ChaMeta::BLOCK_DEVICE; + } + if m.file_type().is_char_device() { + cm |= ChaMeta::CHAR_DEVICE; + } + if m.file_type().is_fifo() { + cm |= ChaMeta::FIFO; + } + if m.file_type().is_socket() { + cm |= ChaMeta::SOCKET; + } + } + + Self { + meta: cm, + len: m.len(), + accessed: m.accessed().ok(), + created: m.created().ok(), + modified: m.modified().ok(), + + #[cfg(unix)] + permissions: { + use std::os::unix::prelude::PermissionsExt; + m.permissions().mode() as u16 + }, + } + } +} + +impl Cha { + #[inline] + pub fn with_meta(mut self, meta: ChaMeta) -> Self { + self.meta |= meta; + self + } +} + +impl Cha { + #[inline] + pub fn is_dir(self) -> bool { self.meta.contains(ChaMeta::DIR) } + + #[inline] + pub fn is_hidden(self) -> bool { self.meta.contains(ChaMeta::HIDDEN) } + + #[inline] + pub fn is_link(self) -> bool { self.meta.contains(ChaMeta::LINK) } + + #[inline] + pub fn is_bad_link(self) -> bool { self.meta.contains(ChaMeta::BAD_LINK) } + + #[cfg(unix)] + #[inline] + pub fn is_block_device(self) -> bool { self.meta.contains(ChaMeta::BLOCK_DEVICE) } + + #[cfg(unix)] + #[inline] + pub fn is_char_device(self) -> bool { self.meta.contains(ChaMeta::CHAR_DEVICE) } + + #[cfg(unix)] + #[inline] + pub fn is_fifo(self) -> bool { self.meta.contains(ChaMeta::FIFO) } + + #[cfg(unix)] + #[inline] + pub fn is_socket(self) -> bool { self.meta.contains(ChaMeta::SOCKET) } +} diff --git a/yazi-shared/src/fs.rs b/yazi-shared/src/fs.rs index 5b2ec8a5..6b83ceeb 100644 --- a/yazi-shared/src/fs.rs +++ b/yazi-shared/src/fs.rs @@ -1,4 +1,4 @@ -use std::{collections::VecDeque, fs::Permissions, path::{Path, PathBuf}}; +use std::{collections::VecDeque, path::{Path, PathBuf}}; use anyhow::Result; use tokio::{fs, io, select, sync::{mpsc, oneshot}, time}; @@ -99,20 +99,9 @@ pub fn permissions(_: Permissions) -> Option { None } // Convert a file mode to a string representation #[cfg(unix)] #[allow(clippy::collapsible_else_if)] -pub fn permissions(permissions: Permissions) -> Option { - use std::os::unix::prelude::PermissionsExt; - +pub fn permissions(m: u16) -> Option { use libc::{S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFSOCK, S_IRGRP, S_IROTH, S_IRUSR, S_ISGID, S_ISUID, S_ISVTX, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR}; - #[cfg(target_os = "macos")] - let m = permissions.mode() as u16; - #[cfg(target_os = "freebsd")] - let m = permissions.mode() as u16; - #[cfg(target_os = "netbsd")] - let m = permissions.mode(); - #[cfg(target_os = "linux")] - let m = permissions.mode(); - let mut s = String::with_capacity(10); // File type diff --git a/yazi-shared/src/lib.rs b/yazi-shared/src/lib.rs index 20f3a9e9..215001a3 100644 --- a/yazi-shared/src/lib.rs +++ b/yazi-shared/src/lib.rs @@ -1,5 +1,6 @@ #![allow(clippy::option_map_unit_fn)] +mod cha; mod chars; mod debounce; mod defer; @@ -14,6 +15,7 @@ mod throttle; mod time; mod url; +pub use cha::*; pub use chars::*; pub use debounce::*; pub use defer::*; diff --git a/yazi-shared/src/url.rs b/yazi-shared/src/url.rs index 10e45db3..3a596d9e 100644 --- a/yazi-shared/src/url.rs +++ b/yazi-shared/src/url.rs @@ -1,4 +1,4 @@ -use std::{ffi::{OsStr, OsString}, fmt::{Debug, Formatter}, ops::{Deref, DerefMut}, path::{Path, PathBuf}}; +use std::{ffi::{OsStr, OsString}, fmt::{Debug, Formatter}, ops::{Deref, DerefMut}, path::{Path, PathBuf, MAIN_SEPARATOR}}; #[derive(Clone, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct Url { @@ -95,6 +95,27 @@ impl Url { #[inline] pub fn into_os_string(self) -> OsString { self.path.into_os_string() } + + #[inline] + pub fn was_hidden(&self) -> bool { + self.file_name().map_or(false, |s| s.to_string_lossy().starts_with('.')) + } + + #[inline] + pub fn was_dir(&self) -> bool { + let b = self.path.as_os_str().as_encoded_bytes(); + if let [.., last] = b { *last == MAIN_SEPARATOR as u8 } else { false } + } + + #[inline] + pub fn into_dir(mut self) -> Self { + if self.was_dir() { + self + } else { + self.path.as_mut_os_string().push("/"); + self + } + } } impl Url {