This commit is contained in:
sxyazi 2023-11-07 08:42:02 +08:00
parent e7e75b8365
commit 25167c383a
No known key found for this signature in database
22 changed files with 251 additions and 135 deletions

2
Cargo.lock generated
View file

@ -2487,6 +2487,7 @@ version = "0.1.5"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-channel", "async-channel",
"bitflags 2.4.1",
"clipboard-win", "clipboard-win",
"crossterm", "crossterm",
"futures", "futures",
@ -2559,6 +2560,7 @@ name = "yazi-shared"
version = "0.1.5" version = "0.1.5"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags 2.4.1",
"crossterm", "crossterm",
"futures", "futures",
"libc", "libc",

View file

@ -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"]} {"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":[]}

View file

@ -16,6 +16,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.1.5" }
# External dependencies # External dependencies
anyhow = "^1" anyhow = "^1"
async-channel = "^1" async-channel = "^1"
bitflags = "^2"
crossterm = "^0" crossterm = "^0"
futures = "^0" futures = "^0"
indexmap = "^2" indexmap = "^2"

View file

@ -1,27 +1,22 @@
use std::process::Stdio; use std::process::Stdio;
use anyhow::Result; use anyhow::{bail, Result};
use tokio::{process::Command, sync::oneshot::{self, Receiver}}; use tokio::process::Command;
use yazi_shared::Url; use yazi_shared::Url;
pub struct FzfOpt { pub struct FzfOpt {
pub cwd: Url, pub cwd: Url,
} }
pub fn fzf(opt: FzfOpt) -> Result<Receiver<Result<Url>>> { pub async fn fzf(opt: FzfOpt) -> Result<Url> {
let child = let child =
Command::new("fzf").current_dir(&opt.cwd).kill_on_drop(true).stdout(Stdio::piped()).spawn()?; Command::new("fzf").current_dir(&opt.cwd).kill_on_drop(true).stdout(Stdio::piped()).spawn()?;
let (tx, rx) = oneshot::channel(); let output = child.wait_with_output().await?;
tokio::spawn(async move { let selected = String::from_utf8_lossy(&output.stdout).trim().to_string();
if let Ok(output) = child.wait_with_output().await {
let selected = String::from_utf8_lossy(&output.stdout).trim().to_string(); if !selected.is_empty() {
if !selected.is_empty() { return Ok(Url::from(selected));
tx.send(Ok(opt.cwd.join(selected))).ok(); }
return; bail!("No match")
}
}
tx.send(Err(anyhow::anyhow!("No match"))).ok();
});
Ok(rx)
} }

View file

@ -1,14 +1,14 @@
use std::process::Stdio; use std::process::Stdio;
use anyhow::Result; use anyhow::{bail, Result};
use tokio::{process::Command, sync::oneshot::{self, Receiver}}; use tokio::process::Command;
use yazi_shared::Url; use yazi_shared::Url;
pub struct ZoxideOpt { pub struct ZoxideOpt {
pub cwd: Url, pub cwd: Url,
} }
pub fn zoxide(opt: ZoxideOpt) -> Result<Receiver<Result<Url>>> { pub async fn zoxide(opt: ZoxideOpt) -> Result<Url> {
let child = Command::new("zoxide") let child = Command::new("zoxide")
.args(["query", "-i", "--exclude"]) .args(["query", "-i", "--exclude"])
.arg(&opt.cwd) .arg(&opt.cwd)
@ -16,16 +16,11 @@ pub fn zoxide(opt: ZoxideOpt) -> Result<Receiver<Result<Url>>> {
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn()?; .spawn()?;
let (tx, rx) = oneshot::channel(); let output = child.wait_with_output().await?;
tokio::spawn(async move { let selected = String::from_utf8_lossy(&output.stdout).trim().to_string();
if let Ok(output) = child.wait_with_output().await {
let selected = String::from_utf8_lossy(&output.stdout).trim().to_string(); if !selected.is_empty() {
if !selected.is_empty() { return Ok(Url::from(selected).into_dir());
tx.send(Ok(Url::from(selected))).ok(); }
return; bail!("No match")
}
}
tx.send(Err(anyhow::anyhow!("No match"))).ok();
});
Ok(rx)
} }

View file

@ -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 anyhow::Result;
use tokio::fs; use tokio::fs;
use yazi_shared::Url; use yazi_shared::{Cha, ChaMeta, Url};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct File { pub struct File {
pub url: Url, pub url: Url,
pub meta: Metadata, pub cha: Cha,
pub(super) link_to: Option<Url>, pub(super) link_to: Option<Url>,
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 { impl File {
#[inline] #[inline]
pub async fn from(url: Url) -> Result<Self> { pub async fn from(url: Url) -> Result<Self> {
let meta = fs::metadata(&url).await?; let meta = fs::symlink_metadata(&url).await?;
Ok(Self::from_meta(url, meta).await) Ok(Self::from_meta(url, meta).await)
} }
pub async fn from_meta(url: Url, mut meta: Metadata) -> Self { pub async fn from_meta(url: Url, mut meta: Metadata) -> Self {
let is_link = meta.is_symlink(); let mut cm = ChaMeta::empty();
let mut link_to = None;
let (is_link, mut link_to) = (meta.is_symlink(), None);
if is_link { if is_link {
cm |= ChaMeta::LINK;
meta = fs::metadata(&url).await.unwrap_or(meta); meta = fs::metadata(&url).await.unwrap_or(meta);
link_to = fs::read_link(&url).await.map(Url::from).ok(); 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); if is_link && meta.is_symlink() {
Self { url, meta, link_to, is_link, is_hidden } 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] #[inline]
@ -60,13 +79,6 @@ impl File {
#[inline] #[inline]
pub fn parent(&self) -> Option<Url> { self.url.parent_url() } pub fn parent(&self) -> Option<Url> { 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 // --- Link to / Is link
#[inline] #[inline]
pub fn link_to(&self) -> Option<&Url> { self.link_to.as_ref() } pub fn link_to(&self) -> Option<&Url> { self.link_to.as_ref() }

View file

@ -128,7 +128,7 @@ impl Files {
pub fn update_full(&mut self, mut items: Vec<File>) -> bool { pub fn update_full(&mut self, mut items: Vec<File>) -> bool {
if !self.show_hidden { 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.ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed);
self.sorter.sort(&mut items, &self.sizes); self.sorter.sort(&mut items, &self.sizes);
@ -146,7 +146,7 @@ impl Files {
if self.show_hidden { if self.show_hidden {
self.items.extend(items); self.items.extend(items);
} else { } 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.items.extend(items);
self.hidden.extend(hidden); self.hidden.extend(hidden);
} }
@ -178,7 +178,7 @@ impl Files {
pub fn update_creating(&mut self, mut todo: BTreeMap<Url, File>) -> bool { pub fn update_creating(&mut self, mut todo: BTreeMap<Url, File>) -> bool {
if !self.show_hidden { if !self.show_hidden {
todo.retain(|_, f| !f.is_hidden); todo.retain(|_, f| !f.is_hidden());
} }
let b = self.update_replacing(&mut todo); let b = self.update_replacing(&mut todo);
@ -335,7 +335,7 @@ impl Files {
self.sorter.sort(&mut self.items, &self.sizes); self.sorter.sort(&mut self.items, &self.sizes);
} else { } else {
let items = mem::take(&mut self.items); 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; self.show_hidden = state;

View file

@ -33,13 +33,13 @@ impl FilesSorter {
) )
}), }),
SortBy::Created => items.sort_unstable_by(|a, b| { 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)); return self.cmp(aa, bb, self.promote(a, b));
} }
Ordering::Equal Ordering::Equal
}), }),
SortBy::Modified => items.sort_unstable_by(|a, b| { 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)); return self.cmp(aa, bb, self.promote(a, b));
} }
Ordering::Equal Ordering::Equal
@ -48,7 +48,7 @@ impl FilesSorter {
SortBy::Size => items.sort_unstable_by(|a, b| { SortBy::Size => items.sort_unstable_by(|a, b| {
let aa = if a.is_dir() { sizes.get(&a.url).copied() } else { None }; 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 }; 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 true
@ -72,13 +72,7 @@ impl FilesSorter {
if self.reverse { ordering.reverse() } else { ordering } if self.reverse { ordering.reverse() } else { ordering }
}); });
let dummy = File { let dummy = File { url: Default::default(), cha: Default::default(), link_to: None };
url: Default::default(),
meta: items[0].meta.clone(),
link_to: None,
is_link: false,
is_hidden: false,
};
let mut new = Vec::with_capacity(indices.len()); let mut new = Vec::with_capacity(indices.len());
for i in indices { for i in indices {

View file

@ -3,14 +3,14 @@ use crate::tab::Tab;
impl Tab { impl Tab {
pub fn back(&mut self) -> bool { pub fn back(&mut self) -> bool {
if let Some(url) = self.backstack.shift_backward().cloned() { if let Some(url) = self.backstack.shift_backward().cloned() {
futures::executor::block_on(self.cd(url)); self.cd(url.into_dir());
} }
false false
} }
pub fn forward(&mut self) -> bool { pub fn forward(&mut self) -> bool {
if let Some(url) = self.backstack.shift_forward().cloned() { if let Some(url) = self.backstack.shift_forward().cloned() {
futures::executor::block_on(self.cd(url)); self.cd(url.into_dir());
} }
false false
} }

View file

@ -9,16 +9,12 @@ use crate::{emit, files::{File, FilesOp}, input::InputOpt, tab::Tab};
impl Tab { impl Tab {
// TODO: change to sync, and remove `Event::Cd` // TODO: change to sync, and remove `Event::Cd`
pub async fn cd(&mut self, mut target: Url) -> bool { pub fn cd(&mut self, mut target: Url) -> bool {
let Ok(file) = File::from(target.clone()).await else {
return false;
};
let mut hovered = None; let mut hovered = None;
if !file.is_dir() { if let (false, Some(parent)) = (target.was_dir(), target.parent_url()) {
hovered = Some(file.url()); emit!(Files(FilesOp::Creating(parent.clone(), File::from_dummy(target.clone()).into_map())));
target = target.parent_url().unwrap(); hovered = Some(target);
emit!(Files(FilesOp::Creating(target.clone(), file.into_map()))); target = parent;
} }
// Already in target // Already in target

View file

@ -1,19 +1,27 @@
use bitflags::bitflags;
use yazi_config::keymap::Exec; use yazi_config::keymap::Exec;
use crate::tab::{Mode, Tab}; 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 { impl From<&Exec> for Opt {
fn from(e: &Exec) -> Self { fn from(e: &Exec) -> Self {
Self(e.named.iter().fold(0, |acc, (k, _)| match k.as_str() { e.named.iter().fold(Opt::empty(), |acc, (k, _)| match k.as_bytes() {
"all" => 0b1111, b"all" => Self::all(),
"find" => acc | 0b0001, b"find" => acc | Self::FIND,
"visual" => acc | 0b0010, b"visual" => acc | Self::VISUAL,
"select" => acc | 0b0100, b"select" => acc | Self::SELECT,
"search" => acc | 0b1000, b"search" => acc | Self::SEARCH,
_ => acc, _ => acc,
})) })
} }
} }
@ -38,8 +46,8 @@ impl Tab {
fn escape_search(&mut self) -> bool { self.search_stop() } fn escape_search(&mut self) -> bool { self.search_stop() }
pub fn escape(&mut self, opt: impl Into<Opt>) -> bool { pub fn escape(&mut self, opt: impl Into<Opt>) -> bool {
let opt = opt.into().0; let opt = opt.into() as Opt;
if opt == 0 { if opt.is_empty() {
return self.escape_find() return self.escape_find()
|| self.escape_visual() || self.escape_visual()
|| self.escape_select() || self.escape_select()
@ -47,16 +55,16 @@ impl Tab {
} }
let mut b = false; let mut b = false;
if opt & 0b0001 != 0 { if opt.contains(Opt::FIND) {
b |= self.escape_find(); b |= self.escape_find();
} }
if opt & 0b0010 != 0 { if opt.contains(Opt::VISUAL) {
b |= self.escape_visual(); b |= self.escape_visual();
} }
if opt & 0b0100 != 0 { if opt.contains(Opt::SELECT) {
b |= self.escape_select(); b |= self.escape_select();
} }
if opt & 0b1000 != 0 { if opt.contains(Opt::SEARCH) {
b |= self.escape_search(); b |= self.escape_search();
} }
b b

View file

@ -11,12 +11,13 @@ impl Tab {
let _defer = Defer::new(|| Event::Stop(false, None).emit()); let _defer = Defer::new(|| Event::Stop(false, None).emit());
emit!(Stop(true)).await; emit!(Stop(true)).await;
let rx = let url = if global {
if global { external::fzf(FzfOpt { cwd }) } else { external::zoxide(ZoxideOpt { cwd }) }?; external::fzf(FzfOpt { cwd }).await
} else {
external::zoxide(ZoxideOpt { cwd }).await
}?;
if let Ok(target) = rx.await? { emit!(Cd(url));
emit!(Cd(target));
}
Ok::<(), anyhow::Error>(()) Ok::<(), anyhow::Error>(())
}); });
false false

View file

@ -17,7 +17,7 @@ impl Tab {
self.search = Some(tokio::spawn(async move { self.search = Some(tokio::spawn(async move {
let Some(Ok(subject)) = emit!(Input(InputOpt::top("Search:"))).recv().await else { let Some(Ok(subject)) = emit!(Input(InputOpt::top("Search:"))).recv().await else {
bail!("canceled") bail!("")
}; };
cwd = cwd.into_search(subject.clone()); cwd = cwd.into_search(subject.clone());

View file

@ -252,7 +252,7 @@ impl Tasks {
pub fn precache_mime(&self, targets: &[File], mimetype: &HashMap<Url, String>) -> bool { pub fn precache_mime(&self, targets: &[File], mimetype: &HashMap<Url, String>) -> bool {
let targets: Vec<_> = targets let targets: Vec<_> = targets
.iter() .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()) .map(|f| f.url())
.collect(); .collect();

View file

@ -122,9 +122,7 @@ impl App {
let tasks = &mut self.cx.tasks; let tasks = &mut self.cx.tasks;
match event { match event {
Event::Cd(url) => { Event::Cd(url) => {
futures::executor::block_on(async { manager.active_mut().cd(expand_url(url));
manager.active_mut().cd(expand_url(url)).await;
});
} }
Event::Refresh => { Event::Refresh => {
manager.refresh(); manager.refresh();

View file

@ -1,5 +1,5 @@
use yazi_config::{keymap::{Control, Exec, Key, KeymapLayer}, KEYMAP}; 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}; use yazi_shared::{optional_bool, Url};
pub(super) struct Executor<'a> { pub(super) struct Executor<'a> {
@ -96,8 +96,7 @@ impl<'a> Executor<'a> {
if exec.named.contains_key("interactive") { if exec.named.contains_key("interactive") {
self.cx.manager.active_mut().cd_interactive(url) self.cx.manager.active_mut().cd_interactive(url)
} else { } else {
emit!(Cd(url)); self.cx.manager.active_mut().cd(url)
false
} }
} }

View file

@ -16,8 +16,8 @@ impl UserData for File {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { 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("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("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_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_hidden", |_, me| Ok(me.0.is_hidden()));
} }
} }
@ -50,36 +50,30 @@ impl Files {
LUA.register_userdata_type::<yazi_core::files::File>(|reg| { LUA.register_userdata_type::<yazi_core::files::File>(|reg| {
reg.add_field_method_get("url", |_, me| Ok(Url::from(&me.url))); 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("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_link", |_, me| Ok(me.is_link()));
reg.add_field_method_get("is_hidden", |_, me| Ok(me.is_hidden)); reg.add_field_method_get("is_hidden", |_, me| Ok(me.is_hidden()));
// Metadata // 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_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)] #[cfg(unix)]
{ {
use std::os::unix::prelude::FileTypeExt; reg.add_field_method_get("is_block_device", |_, me| Ok(me.is_block_device()));
reg.add_field_method_get("is_block_device", |_, me| { reg.add_field_method_get("is_char_device", |_, me| Ok(me.is_char_device()));
Ok(me.meta.file_type().is_block_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("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("length", |_, me| Ok(me.meta.len())); reg.add_field_method_get("length", |_, me| Ok(me.len));
reg.add_field_method_get("created", |_, me| { 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| { 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| { 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 reg.add_method("permissions", |_, me, ()| Ok(yazi_shared::permissions(me.permissions)));
.add_method("permissions", |_, me, ()| Ok(yazi_shared::permissions(me.meta.permissions())));
// Extension // Extension
reg.add_field_method_get("name", |_, me| { reg.add_field_method_get("name", |_, me| {
@ -88,7 +82,7 @@ impl Files {
reg.add_function("size", |_, me: AnyUserData| { reg.add_function("size", |_, me: AnyUserData| {
let file = me.borrow::<yazi_core::files::File>()?; let file = me.borrow::<yazi_core::files::File>()?;
if !file.is_dir() { if !file.is_dir() {
return Ok(Some(file.meta.len())); return Ok(Some(file.len));
} }
let folder = me.named_user_value::<UserDataRef<yazi_core::tab::Folder>>("folder")?; let folder = me.named_user_value::<UserDataRef<yazi_core::tab::Folder>>("folder")?;

View file

@ -10,6 +10,7 @@ repository = "https://github.com/sxyazi/yazi"
[dependencies] [dependencies]
anyhow = "^1" anyhow = "^1"
bitflags = "^2"
crossterm = "^0" crossterm = "^0"
futures = "^0" futures = "^0"
libc = "^0" libc = "^0"

108
yazi-shared/src/cha.rs Normal file
View file

@ -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<SystemTime>,
pub created: Option<SystemTime>,
pub modified: Option<SystemTime>,
#[cfg(unix)]
pub permissions: u16,
}
impl From<Metadata> 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) }
}

View file

@ -1,4 +1,4 @@
use std::{collections::VecDeque, fs::Permissions, path::{Path, PathBuf}}; use std::{collections::VecDeque, path::{Path, PathBuf}};
use anyhow::Result; use anyhow::Result;
use tokio::{fs, io, select, sync::{mpsc, oneshot}, time}; use tokio::{fs, io, select, sync::{mpsc, oneshot}, time};
@ -99,20 +99,9 @@ pub fn permissions(_: Permissions) -> Option<String> { None }
// Convert a file mode to a string representation // Convert a file mode to a string representation
#[cfg(unix)] #[cfg(unix)]
#[allow(clippy::collapsible_else_if)] #[allow(clippy::collapsible_else_if)]
pub fn permissions(permissions: Permissions) -> Option<String> { pub fn permissions(m: u16) -> Option<String> {
use std::os::unix::prelude::PermissionsExt;
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}; 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); let mut s = String::with_capacity(10);
// File type // File type

View file

@ -1,5 +1,6 @@
#![allow(clippy::option_map_unit_fn)] #![allow(clippy::option_map_unit_fn)]
mod cha;
mod chars; mod chars;
mod debounce; mod debounce;
mod defer; mod defer;
@ -14,6 +15,7 @@ mod throttle;
mod time; mod time;
mod url; mod url;
pub use cha::*;
pub use chars::*; pub use chars::*;
pub use debounce::*; pub use debounce::*;
pub use defer::*; pub use defer::*;

View file

@ -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)] #[derive(Clone, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Url { pub struct Url {
@ -95,6 +95,27 @@ impl Url {
#[inline] #[inline]
pub fn into_os_string(self) -> OsString { self.path.into_os_string() } 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 { impl Url {