mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
..
This commit is contained in:
parent
e7e75b8365
commit
25167c383a
22 changed files with 251 additions and 135 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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":[]}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
25
yazi-core/src/external/fzf.rs
vendored
25
yazi-core/src/external/fzf.rs
vendored
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
25
yazi-core/src/external/zoxide.rs
vendored
25
yazi-core/src/external/zoxide.rs
vendored
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")?;
|
||||
|
|
|
|||
|
|
@ -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
108
yazi-shared/src/cha.rs
Normal 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) }
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue