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 = [
"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",

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
anyhow = "^1"
async-channel = "^1"
bitflags = "^2"
crossterm = "^0"
futures = "^0"
indexmap = "^2"

View file

@ -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<Receiver<Result<Url>>> {
pub async fn fzf(opt: FzfOpt) -> Result<Url> {
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")
}

View file

@ -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<Receiver<Result<Url>>> {
pub async fn zoxide(opt: ZoxideOpt) -> Result<Url> {
let child = Command::new("zoxide")
.args(["query", "-i", "--exclude"])
.arg(&opt.cwd)
@ -16,16 +16,11 @@ pub fn zoxide(opt: ZoxideOpt) -> Result<Receiver<Result<Url>>> {
.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")
}

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 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<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 {
#[inline]
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)
}
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<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
#[inline]
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 {
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<Url, File>) -> 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;

View file

@ -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 {

View file

@ -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
}

View file

@ -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

View file

@ -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<Opt>) -> 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

View file

@ -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

View file

@ -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());

View file

@ -252,7 +252,7 @@ impl Tasks {
pub fn precache_mime(&self, targets: &[File], mimetype: &HashMap<Url, String>) -> 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();

View file

@ -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();

View file

@ -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)
}
}

View file

@ -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::<yazi_core::files::File>(|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::<yazi_core::files::File>()?;
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")?;

View file

@ -10,6 +10,7 @@ repository = "https://github.com/sxyazi/yazi"
[dependencies]
anyhow = "^1"
bitflags = "^2"
crossterm = "^0"
futures = "^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 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
#[cfg(unix)]
#[allow(clippy::collapsible_else_if)]
pub fn permissions(permissions: Permissions) -> Option<String> {
use std::os::unix::prelude::PermissionsExt;
pub fn permissions(m: u16) -> Option<String> {
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

View file

@ -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::*;

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)]
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 {