mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
..
This commit is contained in:
parent
e42ec9f429
commit
e5ba8e7d27
36 changed files with 487 additions and 485 deletions
|
|
@ -1,15 +1,16 @@
|
||||||
## Yazi - ⚡️ Blazing Fast Terminal File Manager
|
## Yazi - ⚡️ Blazing Fast Terminal File Manager
|
||||||
|
|
||||||
Yazi ("duck" in Chinese) is a terminal file manager written in Rust, based on non-blocking async I/O. It aims to provide an efficient, user-friendly, and customizable file management experience.
|
Yazi (means "duck") is a terminal file manager written in Rust, based on non-blocking async I/O. It aims to provide an efficient, user-friendly, and customizable file management experience.
|
||||||
|
|
||||||
💡 A new article explaining its internal workings: [Why is Yazi Fast?](https://yazi-rs.github.io/blog/why-is-yazi-fast)
|
💡 A new article explaining its internal workings: [Why is Yazi Fast?](https://yazi-rs.github.io/blog/why-is-yazi-fast)
|
||||||
|
|
||||||
- 🚀 **Full Asynchronous Support**: All I/O operations are asynchronous, CPU tasks are spread across multiple threads, making the most of available resources.
|
- 🚀 **Full Asynchronous Support**: All I/O operations are asynchronous, CPU tasks are spread across multiple threads, making the most of available resources.
|
||||||
- 💪 **Powerful Async Task Scheduling and Management**: Provides real-time progress updates, task cancellation, and internal task priority assignment.
|
- 💪 **Powerful Async Task Scheduling and Management**: Provides real-time progress updates, task cancellation, and internal task priority assignment.
|
||||||
- 🖼️ **Built-in Support for Multiple Image Protocols**: Also integrated with Überzug++, covering almost all terminals.
|
- 🖼️ **Built-in Support for Multiple Image Protocols**: Also integrated with Überzug++, covering almost all terminals.
|
||||||
- 🌟 **Built-in Code Highlighting and Image Encoding**: Combined with the pre-caching mechanism, greatly accelerates image and normal file loading.
|
- 🌟 **Built-in Code Highlighting and Image Decoding**: Combined with the pre-loading mechanism, greatly accelerates image and normal file loading.
|
||||||
|
- 🔌 **Concurrent Plugin System**: UI plugins (rewriting most of the UI), functional plugins (coming soon), custom previewer, and custom preloader; Just some pieces of Lua.
|
||||||
- 🧰 Integration with fd, rg, fzf, zoxide
|
- 🧰 Integration with fd, rg, fzf, zoxide
|
||||||
- 💫 Vim-like Input component, and Select component
|
- 💫 Vim-like input/select component, auto-completion for cd paths
|
||||||
- 🏷️ Multi-Tab Support, Scrollable Preview (for videos, PDFs, archives, directories, code, etc.)
|
- 🏷️ Multi-Tab Support, Scrollable Preview (for videos, PDFs, archives, directories, code, etc.)
|
||||||
- 🔄 Batch Renaming, Visual Mode, File Chooser
|
- 🔄 Batch Renaming, Visual Mode, File Chooser
|
||||||
- 🎨 Theme System, Custom Layouts, Trash Bin, CSI u
|
- 🎨 Theme System, Custom Layouts, Trash Bin, CSI u
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"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","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize"],"language":"en","version":"0.2"}
|
{"language":"en","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","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting"],"version":"0.2"}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
use std::{ffi::OsString, fs, path::{Path, PathBuf}, process};
|
use std::{ffi::OsString, fs, path::{Path, PathBuf}, process};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use serde::Serialize;
|
||||||
use yazi_shared::fs::{current_cwd, expand_path};
|
use yazi_shared::fs::{current_cwd, expand_path};
|
||||||
|
|
||||||
use super::Args;
|
use super::Args;
|
||||||
use crate::{Xdg, ARGS};
|
use crate::{Xdg, ARGS};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct Boot {
|
pub struct Boot {
|
||||||
pub cwd: PathBuf,
|
pub cwd: PathBuf,
|
||||||
pub file: Option<OsString>,
|
pub file: Option<OsString>,
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ use yazi_shared::fs::{File, Url, FILES_TICKET};
|
||||||
use super::FilesSorter;
|
use super::FilesSorter;
|
||||||
|
|
||||||
pub struct Files {
|
pub struct Files {
|
||||||
items: Vec<File>,
|
hidden: Vec<File>,
|
||||||
hidden: Vec<File>,
|
items: Vec<File>,
|
||||||
ticket: u64,
|
ticket: u64,
|
||||||
pub(crate) version: u64,
|
pub(crate) revision: u64,
|
||||||
|
|
||||||
pub sizes: BTreeMap<Url, u64>,
|
pub sizes: BTreeMap<Url, u64>,
|
||||||
selected: BTreeSet<Url>,
|
selected: BTreeSet<Url>,
|
||||||
|
|
@ -23,10 +23,10 @@ pub struct Files {
|
||||||
impl Default for Files {
|
impl Default for Files {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
items: Default::default(),
|
items: Default::default(),
|
||||||
hidden: Default::default(),
|
hidden: Default::default(),
|
||||||
ticket: Default::default(),
|
ticket: Default::default(),
|
||||||
version: Default::default(),
|
revision: Default::default(),
|
||||||
|
|
||||||
sizes: Default::default(),
|
sizes: Default::default(),
|
||||||
selected: Default::default(),
|
selected: Default::default(),
|
||||||
|
|
@ -68,7 +68,7 @@ impl Files {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn select(&mut self, url: &Url, state: Option<bool>) -> bool {
|
pub fn select(&mut self, url: &Url, state: Option<bool>) -> bool {
|
||||||
let old = self.selected.contains(url);
|
let old = self.selected.contains(url);
|
||||||
let new = if let Some(new) = state { new } else { !old };
|
let new = state.unwrap_or(!old);
|
||||||
|
|
||||||
if new == old {
|
if new == old {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -126,132 +126,174 @@ impl Files {
|
||||||
applied
|
applied
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_full(&mut self, mut items: Vec<File>) -> bool {
|
pub fn update_full(&mut self, files: Vec<File>) {
|
||||||
if !self.show_hidden {
|
if files.is_empty() {
|
||||||
(self.hidden, items) = items.into_iter().partition(|f| f.is_hidden());
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
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.revision += 1;
|
||||||
self.items = items;
|
|
||||||
self.version += 1;
|
(self.hidden, self.items) = if self.show_hidden {
|
||||||
true
|
(vec![], files)
|
||||||
|
} else {
|
||||||
|
files.into_iter().partition(|f| f.is_hidden())
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_part(&mut self, version: u64, items: Vec<File>) -> bool {
|
pub fn update_part(&mut self, files: Vec<File>, ticket: u64) {
|
||||||
if !items.is_empty() {
|
if !files.is_empty() {
|
||||||
if version != self.ticket {
|
if ticket != self.ticket {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.revision += 1;
|
||||||
if self.show_hidden {
|
if self.show_hidden {
|
||||||
self.items.extend(items);
|
self.hidden.clear();
|
||||||
|
self.items.extend(files);
|
||||||
} else {
|
} else {
|
||||||
let (hidden, items): (Vec<_>, Vec<_>) = items.into_iter().partition(|f| f.is_hidden());
|
let (hidden, items): (Vec<_>, Vec<_>) = files.into_iter().partition(|f| f.is_hidden());
|
||||||
self.items.extend(items);
|
|
||||||
self.hidden.extend(hidden);
|
self.hidden.extend(hidden);
|
||||||
|
self.items.extend(items);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
self.sorter.sort(&mut self.items, &self.sizes);
|
|
||||||
self.version += 1;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ticket = version;
|
self.ticket = ticket;
|
||||||
if self.items.is_empty() && self.hidden.is_empty() {
|
if !self.items.is_empty() || !self.hidden.is_empty() {
|
||||||
return false;
|
self.revision += 1;
|
||||||
|
self.hidden.clear();
|
||||||
|
self.items.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.items.clear();
|
|
||||||
self.hidden.clear();
|
|
||||||
self.version += 1;
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_size(&mut self, items: BTreeMap<Url, u64>) -> bool {
|
pub fn update_size(&mut self, sizes: BTreeMap<Url, u64>) {
|
||||||
self.sizes.extend(items);
|
if sizes.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if self.sorter.by == SortBy::Size {
|
if self.sorter.by == SortBy::Size {
|
||||||
self.sorter.sort(&mut self.items, &self.sizes);
|
self.revision += 1;
|
||||||
self.version += 1;
|
|
||||||
}
|
}
|
||||||
true
|
self.sizes.extend(sizes);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_creating(&mut self, mut todo: BTreeMap<Url, File>) -> bool {
|
pub fn update_creating(&mut self, files: Vec<File>) {
|
||||||
if !self.show_hidden {
|
if files.is_empty() {
|
||||||
todo.retain(|_, f| !f.is_hidden());
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let b = self.update_replacing(&mut todo);
|
|
||||||
if todo.is_empty() {
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.items.extend(todo.into_values());
|
|
||||||
self.sorter.sort(&mut self.items, &self.sizes);
|
|
||||||
self.version += 1;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_deleting(&mut self, mut todo: BTreeSet<Url>) -> bool {
|
|
||||||
let mut removed = Vec::with_capacity(todo.len());
|
|
||||||
macro_rules! go {
|
macro_rules! go {
|
||||||
($name:expr) => {
|
($dist:expr, $src:expr) => {
|
||||||
removed.clear();
|
let mut todo: BTreeMap<_, _> = $src.into_iter().map(|f| (f.url(), f)).collect();
|
||||||
for i in 0..$name.len() {
|
for f in &$dist {
|
||||||
if todo.remove(&$name[i].url) {
|
if todo.remove(&f.url).is_some() && todo.is_empty() {
|
||||||
removed.push(i);
|
break;
|
||||||
if todo.is_empty() {
|
}
|
||||||
|
}
|
||||||
|
if !todo.is_empty() {
|
||||||
|
self.revision += 1;
|
||||||
|
$dist.extend(todo.into_values());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let (hidden, items) = if self.show_hidden {
|
||||||
|
(vec![], files)
|
||||||
|
} else {
|
||||||
|
files.into_iter().partition(|f| f.is_hidden())
|
||||||
|
};
|
||||||
|
|
||||||
|
if !items.is_empty() {
|
||||||
|
go!(self.items, items);
|
||||||
|
}
|
||||||
|
if !hidden.is_empty() {
|
||||||
|
go!(self.hidden, hidden);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_deleting(&mut self, urls: Vec<Url>) {
|
||||||
|
if urls.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! go {
|
||||||
|
($dist:expr, $src:expr) => {
|
||||||
|
let mut todo: BTreeSet<_> = $src.into_iter().collect();
|
||||||
|
let len = $dist.len();
|
||||||
|
|
||||||
|
$dist.retain(|f| !todo.remove(&f.url));
|
||||||
|
if $dist.len() != len {
|
||||||
|
self.revision += 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let (hidden, items) =
|
||||||
|
if self.show_hidden { (vec![], urls) } else { urls.into_iter().partition(|u| u.is_hidden()) };
|
||||||
|
|
||||||
|
if !items.is_empty() {
|
||||||
|
go!(self.items, items);
|
||||||
|
}
|
||||||
|
if !hidden.is_empty() {
|
||||||
|
go!(self.hidden, hidden);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_updating(&mut self, files: BTreeMap<Url, File>) -> [BTreeMap<Url, File>; 2] {
|
||||||
|
if files.is_empty() {
|
||||||
|
return Default::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! go {
|
||||||
|
($dist:expr, $src:expr) => {
|
||||||
|
let len = $src.len();
|
||||||
|
for i in 0..$dist.len() {
|
||||||
|
if let Some(f) = $src.remove(&$dist[i].url) {
|
||||||
|
$dist[i] = f;
|
||||||
|
if $src.is_empty() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i in (0..removed.len()).rev() {
|
if $src.len() != len {
|
||||||
$name.remove(removed[i]);
|
self.revision += 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut b = false;
|
let (mut hidden, mut items) = if self.show_hidden {
|
||||||
if !todo.is_empty() {
|
(BTreeMap::new(), files)
|
||||||
go!(self.items);
|
} else {
|
||||||
b |= !removed.is_empty();
|
files.into_iter().partition(|(_, f)| f.is_hidden())
|
||||||
}
|
};
|
||||||
|
|
||||||
if !todo.is_empty() {
|
if !items.is_empty() {
|
||||||
go!(self.hidden);
|
go!(self.items, items);
|
||||||
b |= !removed.is_empty();
|
|
||||||
}
|
}
|
||||||
b
|
if !hidden.is_empty() {
|
||||||
|
go!(self.hidden, hidden);
|
||||||
|
}
|
||||||
|
[hidden, items]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_replacing(&mut self, todo: &mut BTreeMap<Url, File>) -> bool {
|
pub fn update_upserting(&mut self, files: BTreeMap<Url, File>) {
|
||||||
if todo.is_empty() {
|
if files.is_empty() {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! go {
|
let [hidden, items] = self.update_updating(files);
|
||||||
($name:expr) => {
|
if hidden.is_empty() && items.is_empty() {
|
||||||
for i in 0..$name.len() {
|
return;
|
||||||
if let Some(f) = todo.remove(&$name[i].url) {
|
|
||||||
$name[i] = f;
|
|
||||||
if todo.is_empty() {
|
|
||||||
self.version += 1;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let old = todo.len();
|
self.revision += 1;
|
||||||
go!(self.items);
|
if !hidden.is_empty() {
|
||||||
go!(self.hidden);
|
self.hidden.extend(hidden.into_values());
|
||||||
|
}
|
||||||
if old != todo.len() {
|
if !items.is_empty() {
|
||||||
self.version += 1;
|
self.items.extend(items.into_values());
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,7 +360,7 @@ impl Files {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
self.sorter = sorter;
|
self.sorter = sorter;
|
||||||
self.version += 1;
|
self.revision += 1;
|
||||||
self.sorter.sort(&mut self.items, &self.sizes)
|
self.sorter.sort(&mut self.items, &self.sizes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -339,7 +381,7 @@ impl Files {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.show_hidden = state;
|
self.show_hidden = state;
|
||||||
self.version += 1;
|
self.revision += 1;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,17 +25,19 @@ impl From<&Url> for Folder {
|
||||||
|
|
||||||
impl Folder {
|
impl Folder {
|
||||||
pub fn update(&mut self, op: FilesOp) -> bool {
|
pub fn update(&mut self, op: FilesOp) -> bool {
|
||||||
let b = match op {
|
let revision = self.files.revision;
|
||||||
FilesOp::Full(_, items) => self.files.update_full(items),
|
match op {
|
||||||
FilesOp::Part(_, ticket, items) => self.files.update_part(ticket, items),
|
FilesOp::Full(_, files) => self.files.update_full(files),
|
||||||
FilesOp::Size(_, items) => self.files.update_size(items),
|
FilesOp::Part(_, files, ticket) => self.files.update_part(files, ticket),
|
||||||
|
FilesOp::Size(_, sizes) => self.files.update_size(sizes),
|
||||||
|
|
||||||
FilesOp::Creating(_, items) => self.files.update_creating(items),
|
FilesOp::Creating(_, files) => self.files.update_creating(files),
|
||||||
FilesOp::Deleting(_, items) => self.files.update_deleting(items),
|
FilesOp::Deleting(_, urls) => self.files.update_deleting(urls),
|
||||||
FilesOp::Replacing(_, mut items) => self.files.update_replacing(&mut items),
|
FilesOp::Updating(_, files) => _ = self.files.update_updating(files),
|
||||||
|
FilesOp::Upserting(_, files) => self.files.update_upserting(files),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
}
|
||||||
if !b {
|
if revision == self.files.revision {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::path::{PathBuf, MAIN_SEPARATOR};
|
||||||
|
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use yazi_config::popup::InputCfg;
|
use yazi_config::popup::InputCfg;
|
||||||
use yazi_shared::{emit, event::Exec, fs::{File, FilesOp}, fs::Url};
|
use yazi_shared::{event::Exec, fs::{File, FilesOp, Url}};
|
||||||
|
|
||||||
use crate::{input::Input, manager::Manager};
|
use crate::{input::Input, manager::Manager};
|
||||||
|
|
||||||
|
|
@ -42,7 +42,7 @@ impl Manager {
|
||||||
let child =
|
let child =
|
||||||
Url::from(path.components().take(cwd.components().count() + 1).collect::<PathBuf>());
|
Url::from(path.components().take(cwd.components().count() + 1).collect::<PathBuf>());
|
||||||
if let Ok(f) = File::from(child.clone()).await {
|
if let Ok(f) = File::from(child.clone()).await {
|
||||||
emit!(Files(FilesOp::Creating(cwd, f.into_map())));
|
FilesOp::Creating(cwd, vec![f]).emit();
|
||||||
Manager::_hover(Some(child));
|
Manager::_hover(Some(child));
|
||||||
}
|
}
|
||||||
Ok::<(), anyhow::Error>(())
|
Ok::<(), anyhow::Error>(())
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,6 @@ mod tab_close;
|
||||||
mod tab_create;
|
mod tab_create;
|
||||||
mod tab_swap;
|
mod tab_swap;
|
||||||
mod tab_switch;
|
mod tab_switch;
|
||||||
|
mod update_files;
|
||||||
mod update_mimetype;
|
mod update_mimetype;
|
||||||
mod yank;
|
mod yank;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
|
||||||
use yazi_config::{popup::SelectCfg, OPEN};
|
use yazi_config::{popup::SelectCfg, OPEN};
|
||||||
use yazi_plugin::external;
|
|
||||||
use yazi_shared::{event::Exec, MIME_DIR};
|
use yazi_shared::{event::Exec, MIME_DIR};
|
||||||
|
|
||||||
use crate::{manager::Manager, select::Select, tasks::Tasks};
|
use crate::{manager::Manager, select::Select, tasks::Tasks};
|
||||||
|
|
@ -44,28 +43,30 @@ impl Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
let opt = opt.into() as Opt;
|
let opt = opt.into() as Opt;
|
||||||
tokio::spawn(async move {
|
// TODO: plugin system
|
||||||
let todo: Vec<_> = files.iter().filter(|(_, m)| m.is_none()).map(|(u, _)| u).collect();
|
// tokio::spawn(async move {
|
||||||
if let Ok(mut mimes) = external::file(&todo).await {
|
// let todo: Vec<_> = files.iter().filter(|(_, m)| m.is_none()).map(|(u, _)|
|
||||||
files = files
|
// u).collect(); if let Ok(mut mimes) = external::file(&todo).await {
|
||||||
.into_iter()
|
// files = files
|
||||||
.map(|(u, m)| {
|
// .into_iter()
|
||||||
let mime = m.or_else(|| mimes.remove(&u));
|
// .map(|(u, m)| {
|
||||||
(u, mime)
|
// let mime = m.or_else(|| mimes.remove(&u));
|
||||||
})
|
// (u, mime)
|
||||||
.collect();
|
// })
|
||||||
}
|
// .collect();
|
||||||
|
// }
|
||||||
|
|
||||||
let files: Vec<_> =
|
// let files: Vec<_> =
|
||||||
files.into_iter().filter_map(|(u, m)| m.map(|m| (u.into_os_string(), m))).collect();
|
// files.into_iter().filter_map(|(u, m)| m.map(|m| (u.into_os_string(),
|
||||||
|
// m))).collect();
|
||||||
|
|
||||||
if opt.interactive {
|
// if opt.interactive {
|
||||||
Self::open_interactive(files).await;
|
// Self::open_interactive(files).await;
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
Tasks::_open(files, None);
|
// Tasks::_open(files, None);
|
||||||
});
|
// });
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ impl Manager {
|
||||||
|
|
||||||
self.active_mut().apply_files_attrs(false);
|
self.active_mut().apply_files_attrs(false);
|
||||||
|
|
||||||
if let Some(f) = self.parent() {
|
if let Some(p) = self.parent() {
|
||||||
self.watcher.trigger_dirs(&[self.cwd(), &f.cwd]);
|
self.watcher.trigger_dirs(&[self.cwd(), &p.cwd]);
|
||||||
} else {
|
} else {
|
||||||
self.watcher.trigger_dirs(&[self.cwd()]);
|
self.watcher.trigger_dirs(&[self.cwd()]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
|
||||||
use yazi_config::{popup::InputCfg, OPEN, PREVIEW};
|
use yazi_config::{popup::InputCfg, OPEN, PREVIEW};
|
||||||
use yazi_plugin::external::{self, ShellOpt};
|
use yazi_plugin::external::{self, ShellOpt};
|
||||||
use yazi_scheduler::{Scheduler, BLOCKER};
|
use yazi_scheduler::{Scheduler, BLOCKER};
|
||||||
use yazi_shared::{emit, event::Exec, fs::{max_common_root, File, FilesOp, Url}, term::Term, Defer};
|
use yazi_shared::{event::Exec, fs::{max_common_root, File, FilesOp, Url}, term::Term, Defer};
|
||||||
|
|
||||||
use crate::{input::Input, manager::Manager};
|
use crate::{input::Input, manager::Manager};
|
||||||
|
|
||||||
|
|
@ -25,7 +25,7 @@ impl Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
let file = File::from(new.clone()).await?;
|
let file = File::from(new.clone()).await?;
|
||||||
emit!(Files(FilesOp::Replacing(file.parent().unwrap(), BTreeMap::from_iter([(old, file)]))));
|
FilesOp::Upserting(file.parent().unwrap(), BTreeMap::from_iter([(old, file)])).emit();
|
||||||
Ok(Self::_hover(Some(new)))
|
Ok(Self::_hover(Some(new)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
75
yazi-core/src/manager/commands/update_files.rs
Normal file
75
yazi-core/src/manager/commands/update_files.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
use yazi_shared::{event::Exec, fs::FilesOp};
|
||||||
|
|
||||||
|
use crate::{folder::Folder, manager::Manager, tasks::Tasks};
|
||||||
|
|
||||||
|
pub struct Opt {
|
||||||
|
op: FilesOp,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&Exec> for Opt {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(e: &Exec) -> Result<Self, Self::Error> { Ok(Self { op: e.take_data().ok_or(())? }) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Manager {
|
||||||
|
fn handle_read(&mut self, op: FilesOp) -> bool {
|
||||||
|
let url = op.url().clone();
|
||||||
|
let cwd = self.cwd().to_owned();
|
||||||
|
let hovered = self.hovered().map(|h| h.url());
|
||||||
|
|
||||||
|
let mut b = if cwd == url {
|
||||||
|
self.current_mut().update(op)
|
||||||
|
} else if matches!(self.parent(), Some(p) if p.cwd == url) {
|
||||||
|
self.active_mut().parent.as_mut().unwrap().update(op)
|
||||||
|
} else if matches!(self.hovered(), Some(h) if h.url == url) {
|
||||||
|
self.active_mut().history.entry(url.clone()).or_insert_with(|| Folder::from(&url));
|
||||||
|
self.active_mut().apply_files_attrs(true);
|
||||||
|
self.active_mut().history.get_mut(&url).unwrap().update(op)
|
||||||
|
} else {
|
||||||
|
self.active_mut().history.entry(url.clone()).or_insert_with(|| Folder::from(&url)).update(op);
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
b |= self.active_mut().parent.as_mut().is_some_and(|p| p.hover(&cwd));
|
||||||
|
b |= hovered.as_ref().is_some_and(|h| self.current_mut().hover(h));
|
||||||
|
|
||||||
|
if hovered.as_ref() != self.hovered().map(|h| &h.url) {
|
||||||
|
Self::_hover(None);
|
||||||
|
}
|
||||||
|
b
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_ioerr(&mut self, op: FilesOp) -> bool {
|
||||||
|
let url = op.url();
|
||||||
|
let op = FilesOp::Full(url.clone(), Vec::new());
|
||||||
|
|
||||||
|
if url == self.cwd() {
|
||||||
|
self.current_mut().update(op);
|
||||||
|
self.active_mut().leave(());
|
||||||
|
true
|
||||||
|
} else if matches!(self.parent(), Some(p) if &p.cwd == url) {
|
||||||
|
self.active_mut().parent.as_mut().unwrap().update(op)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_files(&mut self, opt: impl TryInto<Opt>, tasks: &Tasks) -> bool {
|
||||||
|
let Ok(opt) = opt.try_into() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let calc = !matches!(opt.op, FilesOp::Size(..) | FilesOp::IOErr(_) | FilesOp::Deleting(..));
|
||||||
|
let b = match opt.op {
|
||||||
|
FilesOp::IOErr(..) => self.handle_ioerr(opt.op),
|
||||||
|
_ => self.handle_read(opt.op),
|
||||||
|
};
|
||||||
|
|
||||||
|
if calc {
|
||||||
|
tasks.preload_sorted(&self.current().files);
|
||||||
|
}
|
||||||
|
|
||||||
|
b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use yazi_shared::fs::{File, FilesOp, Url};
|
use yazi_shared::fs::{File, Url};
|
||||||
|
|
||||||
use super::{Tabs, Watcher};
|
use super::{Tabs, Watcher};
|
||||||
use crate::{folder::Folder, tab::Tab};
|
use crate::{folder::Folder, tab::Tab};
|
||||||
|
|
@ -23,48 +23,6 @@ impl Manager {
|
||||||
mimetype: Default::default(),
|
mimetype: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_read(&mut self, op: FilesOp) -> bool {
|
|
||||||
let url = op.url().clone();
|
|
||||||
let cwd = self.cwd().to_owned();
|
|
||||||
let hovered = self.hovered().map(|h| h.url());
|
|
||||||
|
|
||||||
let mut b = if cwd == url {
|
|
||||||
self.current_mut().update(op)
|
|
||||||
} else if matches!(self.parent(), Some(p) if p.cwd == url) {
|
|
||||||
self.active_mut().parent.as_mut().unwrap().update(op)
|
|
||||||
} else if matches!(self.hovered(), Some(h) if h.url == url) {
|
|
||||||
self.active_mut().history.entry(url.clone()).or_insert_with(|| Folder::from(&url));
|
|
||||||
self.active_mut().apply_files_attrs(true);
|
|
||||||
self.active_mut().history.get_mut(&url).unwrap().update(op)
|
|
||||||
} else {
|
|
||||||
self.active_mut().history.entry(url.clone()).or_insert_with(|| Folder::from(&url)).update(op);
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
b |= self.active_mut().parent.as_mut().is_some_and(|p| p.hover(&cwd));
|
|
||||||
b |= hovered.as_ref().is_some_and(|h| self.current_mut().hover(h));
|
|
||||||
|
|
||||||
if hovered.as_ref() != self.hovered().map(|h| &h.url) {
|
|
||||||
Self::_hover(None);
|
|
||||||
}
|
|
||||||
b
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_ioerr(&mut self, op: FilesOp) -> bool {
|
|
||||||
let url = op.url();
|
|
||||||
let op = FilesOp::Full(url.clone(), Vec::new());
|
|
||||||
|
|
||||||
if url == self.cwd() {
|
|
||||||
self.current_mut().update(op);
|
|
||||||
self.active_mut().leave(());
|
|
||||||
true
|
|
||||||
} else if matches!(self.parent(), Some(p) if &p.cwd == url) {
|
|
||||||
self.active_mut().parent.as_mut().unwrap().update(op)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Manager {
|
impl Manager {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
use std::{collections::BTreeSet, sync::Arc, time::Duration};
|
use std::{collections::{BTreeMap, BTreeSet}, sync::Arc, time::Duration};
|
||||||
|
|
||||||
use indexmap::IndexMap;
|
|
||||||
use notify::{event::{MetadataKind, ModifyKind}, EventKind, RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
|
use notify::{event::{MetadataKind, ModifyKind}, EventKind, RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use tokio::{fs, pin, sync::mpsc::{self, UnboundedReceiver}};
|
use tokio::{fs, pin, sync::mpsc::{self, UnboundedReceiver}};
|
||||||
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
||||||
use yazi_shared::{emit, fs::{File, FilesOp, Url}};
|
use tracing::error;
|
||||||
|
use yazi_plugin::isolate;
|
||||||
|
use yazi_shared::fs::{File, FilesOp, Url};
|
||||||
|
|
||||||
use crate::folder::Files;
|
use crate::folder::Files;
|
||||||
|
|
||||||
pub struct Watcher {
|
pub struct Watcher {
|
||||||
watcher: RecommendedWatcher,
|
watcher: RecommendedWatcher,
|
||||||
watched: Arc<RwLock<IndexMap<Url, Option<Url>>>>,
|
watched: Arc<RwLock<BTreeSet<Url>>>,
|
||||||
|
pub linked: Arc<RwLock<BTreeMap<Url, Url>>>, // from ==> to
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Watcher {
|
impl Watcher {
|
||||||
|
|
@ -25,55 +27,45 @@ impl Watcher {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(path) = event.paths.first().map(Url::from) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let parent = path.parent_url().unwrap_or_else(|| path.clone());
|
|
||||||
match event.kind {
|
match event.kind {
|
||||||
EventKind::Create(_) => {
|
EventKind::Create(_) => {}
|
||||||
tx.send(parent).ok();
|
EventKind::Modify(kind) => match kind {
|
||||||
}
|
ModifyKind::Data(_) => {}
|
||||||
EventKind::Modify(kind) => {
|
ModifyKind::Metadata(md) => match md {
|
||||||
match kind {
|
MetadataKind::WriteTime => {}
|
||||||
ModifyKind::Data(_) => {}
|
MetadataKind::Permissions => {}
|
||||||
ModifyKind::Metadata(kind) => match kind {
|
MetadataKind::Ownership => {}
|
||||||
MetadataKind::Permissions => {}
|
|
||||||
MetadataKind::Ownership => {}
|
|
||||||
MetadataKind::Extended => {}
|
|
||||||
_ => return,
|
|
||||||
},
|
|
||||||
ModifyKind::Name(_) => {}
|
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
},
|
||||||
|
ModifyKind::Name(_) => {}
|
||||||
|
_ => return,
|
||||||
|
},
|
||||||
|
EventKind::Remove(_) => {}
|
||||||
|
_ => return,
|
||||||
|
}
|
||||||
|
|
||||||
tx.send(path).ok();
|
for path in event.paths {
|
||||||
tx.send(parent).ok();
|
tx.send(Url::from(path)).ok();
|
||||||
}
|
|
||||||
EventKind::Remove(_) => {
|
|
||||||
tx.send(path).ok();
|
|
||||||
tx.send(parent).ok();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Default::default(),
|
Default::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let instance = Self { watcher: watcher.unwrap(), watched: Default::default() };
|
let instance =
|
||||||
tokio::spawn(Self::on_changed(rx, instance.watched.clone()));
|
Self { watcher: watcher.unwrap(), watched: Default::default(), linked: Default::default() };
|
||||||
|
tokio::spawn(Self::on_changed(rx));
|
||||||
instance
|
instance
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn watch(&mut self, mut watched: BTreeSet<&Url>) {
|
pub(super) fn watch(&mut self, mut new: BTreeSet<&Url>) {
|
||||||
watched.retain(|&u| u.is_regular());
|
new.retain(|&u| u.is_regular());
|
||||||
let (to_unwatch, to_watch): (BTreeSet<_>, BTreeSet<_>) = {
|
let (to_unwatch, to_watch): (BTreeSet<_>, BTreeSet<_>) = {
|
||||||
let guard = self.watched.read();
|
let guard = self.watched.read();
|
||||||
let keys = guard.keys().collect::<BTreeSet<_>>();
|
let old: BTreeSet<_> = guard.iter().collect();
|
||||||
(
|
(
|
||||||
keys.difference(&watched).map(|&x| x.clone()).collect(),
|
old.difference(&new).map(|&x| x.clone()).collect(),
|
||||||
watched.difference(&keys).map(|&x| x.clone()).collect(),
|
new.difference(&old).map(|&x| x.clone()).collect(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -82,141 +74,103 @@ impl Watcher {
|
||||||
}
|
}
|
||||||
for u in to_watch {
|
for u in to_watch {
|
||||||
if self.watcher.watch(&u, RecursiveMode::NonRecursive).is_err() {
|
if self.watcher.watch(&u, RecursiveMode::NonRecursive).is_err() {
|
||||||
watched.remove(&u);
|
new.remove(&u);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut to_resolve = Vec::new();
|
*self.watched.write() = new.into_iter().cloned().collect();
|
||||||
let mut guard = self.watched.write();
|
self.sync_linked();
|
||||||
*guard = watched
|
|
||||||
.into_iter()
|
|
||||||
.map(|k| {
|
|
||||||
if let Some(old) = guard.remove_entry(k) {
|
|
||||||
old
|
|
||||||
} else {
|
|
||||||
to_resolve.push(k.clone());
|
|
||||||
(k.clone(), None)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let lock = self.watched.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
for k in to_resolve {
|
|
||||||
match fs::canonicalize(&k).await {
|
|
||||||
Ok(v) if v != *k => {
|
|
||||||
lock.write().insert(k, Some(Url::from(v)));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn trigger_dirs(&self, dirs: &[&Url]) {
|
pub(super) fn trigger_dirs(&self, dirs: &[&Url]) {
|
||||||
let dirs: Vec<_> = dirs.iter().filter(|&u| u.is_regular()).map(|&u| u.clone()).collect();
|
let urls: Vec<_> = dirs.iter().filter(|&u| u.is_regular()).map(|&u| u.clone()).collect();
|
||||||
if dirs.is_empty() {
|
if urls.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let watched = self.watched.clone();
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let watched = watched.read().clone();
|
for u in urls {
|
||||||
for dir in dirs {
|
let Ok(rx) = Files::from_dir(&u).await else {
|
||||||
Self::dir_changed(&dir, &watched).await;
|
FilesOp::IOErr(u).emit();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let files: Vec<_> = UnboundedReceiverStream::new(rx).collect().await;
|
||||||
|
FilesOp::Full(u, files).emit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn on_changed(
|
fn sync_linked(&self) {
|
||||||
rx: UnboundedReceiver<Url>,
|
let mut new = self.watched.read().clone();
|
||||||
watched: Arc<RwLock<IndexMap<Url, Option<Url>>>>,
|
self.linked.write().retain(|k, _| new.remove(k));
|
||||||
) {
|
|
||||||
|
let watched = self.watched.clone();
|
||||||
|
let linked = self.linked.clone();
|
||||||
|
macro_rules! go {
|
||||||
|
($todo:expr) => {
|
||||||
|
for from in $todo {
|
||||||
|
match fs::canonicalize(&from).await {
|
||||||
|
Ok(to) if to != *from && watched.read().contains(&from) => {
|
||||||
|
linked.write().insert(from, Url::from(to));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let old: Vec<_> = linked.read().keys().cloned().collect();
|
||||||
|
go!(new);
|
||||||
|
go!(old);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_changed(rx: UnboundedReceiver<Url>) {
|
||||||
// TODO: revert this once a new notification is implemented
|
// TODO: revert this once a new notification is implemented
|
||||||
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(10, Duration::from_millis(20));
|
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(10, Duration::from_millis(20));
|
||||||
pin!(rx);
|
pin!(rx);
|
||||||
|
|
||||||
while let Some(urls) = rx.next().await {
|
while let Some(urls) = rx.next().await {
|
||||||
let (mut files, mut dirs): (Vec<_>, Vec<_>) = Default::default();
|
let urls = urls.into_iter().collect::<BTreeSet<_>>();
|
||||||
for url in urls.into_iter().collect::<BTreeSet<_>>() {
|
let mut reload = Vec::with_capacity(urls.len());
|
||||||
if fs::metadata(&url).await.map(|m| !m.is_dir()).unwrap_or(false) {
|
|
||||||
files.push(url);
|
for u in urls {
|
||||||
} else {
|
let Some(parent) = u.parent_url() else {
|
||||||
dirs.push(url);
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(file) = File::from(u.clone()).await else {
|
||||||
|
FilesOp::Deleting(parent, vec![u]).emit();
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !file.is_dir() {
|
||||||
|
reload.push(file.clone());
|
||||||
}
|
}
|
||||||
|
FilesOp::Upserting(parent, BTreeMap::from_iter([(u, file)])).emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
let watched = watched.read().clone();
|
if reload.is_empty() {
|
||||||
|
continue;
|
||||||
Self::files_changed(&files, &watched).await;
|
|
||||||
for file in files {
|
|
||||||
for u in Self::linked_urls(&file, &watched) {
|
|
||||||
emit!(Files(FilesOp::IOErr(u.clone())));
|
|
||||||
}
|
|
||||||
emit!(Files(FilesOp::IOErr(file)));
|
|
||||||
}
|
}
|
||||||
|
if let Err(e) = isolate::preload("mime.lua".to_string(), reload, true).await {
|
||||||
for dir in dirs {
|
error!("preload in watcher failed: {e}");
|
||||||
Self::dir_changed(&dir, &watched).await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
async fn files_changed(urls: &[Url], watched: &IndexMap<Url, Option<Url>>) {
|
|
||||||
// TODO: plugin system
|
impl Watcher {
|
||||||
// let Ok(mut mimes) = external::file(urls).await else {
|
pub fn relatives(&self, url: &Url) -> BTreeSet<Url> {
|
||||||
// return;
|
let mut map = BTreeSet::from_iter([url.clone()]);
|
||||||
// };
|
|
||||||
|
let linked = self.linked.read();
|
||||||
// let linked: Vec<_> = watched.iter().filter_map(|(k, v)|
|
if let Some(to) = linked.get(url) {
|
||||||
// v.as_ref().map(|v| (k, v))).fold( Vec::new(),
|
map.extend(linked.iter().filter(|(_, v)| *v == to).map(|(k, _)| k).cloned());
|
||||||
// |mut aac, (k, v)| {
|
}
|
||||||
// mimes
|
|
||||||
// .iter()
|
map
|
||||||
// .filter(|(u, _)| u.parent().map(|p| p == **v) == Some(true))
|
}
|
||||||
// .for_each(|(u, m)| aac.push((k.join(u.file_name().unwrap()),
|
|
||||||
// m.clone()))); aac
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
|
|
||||||
// mimes.extend(linked);
|
|
||||||
// emit!(Mimetype(mimes));
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn dir_changed(url: &Url, watched: &IndexMap<Url, Option<Url>>) {
|
|
||||||
let linked = Self::linked_urls(url, watched);
|
|
||||||
let Ok(rx) = Files::from_dir(url).await else {
|
|
||||||
emit!(Files(FilesOp::IOErr(url.clone())));
|
|
||||||
for u in linked {
|
|
||||||
emit!(Files(FilesOp::IOErr(u.clone())));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let linked_files = |files: &[File], linked: &Url| -> Vec<File> {
|
|
||||||
let mut new = Vec::with_capacity(files.len());
|
|
||||||
for file in files {
|
|
||||||
let mut file = file.clone();
|
|
||||||
file.url = linked.join(file.url.strip_prefix(url).unwrap());
|
|
||||||
new.push(file);
|
|
||||||
}
|
|
||||||
new
|
|
||||||
};
|
|
||||||
|
|
||||||
let files: Vec<_> = UnboundedReceiverStream::new(rx).collect().await;
|
|
||||||
for u in linked {
|
|
||||||
let files = linked_files(&files, u);
|
|
||||||
emit!(Files(FilesOp::Full(u.clone(), files)));
|
|
||||||
}
|
|
||||||
emit!(Files(FilesOp::Full(url.clone(), files)));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn linked_urls<'a>(url: &'a Url, watched: &'a IndexMap<Url, Option<Url>>) -> Vec<&'a Url> {
|
|
||||||
watched
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(k, v)| v.as_ref().map(|v| (k, v)))
|
|
||||||
.filter(|(_, v)| *v == url)
|
|
||||||
.map(|(k, _)| k)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use yazi_shared::{emit, event::Exec, fs::{File, FilesOp}, fs::{expand_path, Url}, Layer};
|
use yazi_shared::{emit, event::Exec, fs::{expand_path, File, FilesOp, Url}, Layer};
|
||||||
|
|
||||||
use crate::{manager::Manager, tab::Tab};
|
use crate::{manager::Manager, tab::Tab};
|
||||||
|
|
||||||
|
|
@ -34,10 +34,7 @@ impl Tab {
|
||||||
};
|
};
|
||||||
|
|
||||||
let b = self.cd(parent.clone());
|
let b = self.cd(parent.clone());
|
||||||
emit!(Files(FilesOp::Creating(
|
FilesOp::Creating(parent, vec![File::from_dummy(opt.target.clone())]).emit();
|
||||||
parent.clone(),
|
|
||||||
File::from_dummy(opt.target.clone()).into_map()
|
|
||||||
)));
|
|
||||||
Manager::_hover(Some(opt.target));
|
Manager::_hover(Some(opt.target));
|
||||||
b
|
b
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use tokio::pin;
|
||||||
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
||||||
use yazi_config::popup::InputCfg;
|
use yazi_config::popup::InputCfg;
|
||||||
use yazi_plugin::external;
|
use yazi_plugin::external;
|
||||||
use yazi_shared::{emit, event::Exec, fs::FilesOp};
|
use yazi_shared::{event::Exec, fs::FilesOp};
|
||||||
|
|
||||||
use crate::{input::Input, manager::Manager, tab::Tab};
|
use crate::{input::Input, manager::Manager, tab::Tab};
|
||||||
|
|
||||||
|
|
@ -66,7 +66,7 @@ impl Tab {
|
||||||
Tab::_cd(&cwd);
|
Tab::_cd(&cwd);
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
emit!(Files(FilesOp::Part(cwd.clone(), ticket, chunk)));
|
FilesOp::Part(cwd.clone(), chunk, ticket).emit();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ impl Finder {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn catchup(&mut self, files: &Files) -> bool {
|
pub(super) fn catchup(&mut self, files: &Files) -> bool {
|
||||||
if self.version == files.version {
|
if self.version == files.revision {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
self.matched.clear();
|
self.matched.clear();
|
||||||
|
|
@ -72,7 +72,7 @@ impl Finder {
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.version = files.version;
|
self.version = files.revision;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crossterm::event::KeyEvent;
|
||||||
use ratatui::backend::Backend;
|
use ratatui::backend::Backend;
|
||||||
use yazi_config::{keymap::Key, ARGS};
|
use yazi_config::{keymap::Key, ARGS};
|
||||||
use yazi_core::input::InputMode;
|
use yazi_core::input::InputMode;
|
||||||
use yazi_shared::{emit, event::{Event, Exec}, fs::FilesOp, term::Term, Layer, COLLISION};
|
use yazi_shared::{emit, event::{Event, Exec}, term::Term, Layer, COLLISION};
|
||||||
|
|
||||||
use crate::{lives::Lives, Ctx, Executor, Logs, Panic, Root, Signals};
|
use crate::{lives::Lives, Ctx, Executor, Logs, Panic, Root, Signals};
|
||||||
|
|
||||||
|
|
@ -132,19 +132,6 @@ impl App {
|
||||||
let manager = &mut self.cx.manager;
|
let manager = &mut self.cx.manager;
|
||||||
let tasks = &mut self.cx.tasks;
|
let tasks = &mut self.cx.tasks;
|
||||||
match event {
|
match event {
|
||||||
Event::Files(op) => {
|
|
||||||
let calc = !matches!(op, FilesOp::Size(..) | FilesOp::IOErr(_));
|
|
||||||
let b = match op {
|
|
||||||
FilesOp::IOErr(..) => manager.update_ioerr(op),
|
|
||||||
_ => manager.update_read(op),
|
|
||||||
};
|
|
||||||
if b {
|
|
||||||
emit!(Render);
|
|
||||||
}
|
|
||||||
if calc {
|
|
||||||
tasks.preload_sorted(&manager.current().files);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::Pages(page) => {
|
Event::Pages(page) => {
|
||||||
let targets = self.cx.manager.current().paginate(page);
|
let targets = self.cx.manager.current().paginate(page);
|
||||||
tasks.preload_paged(targets, &self.cx.manager.mimetype);
|
tasks.preload_paged(targets, &self.cx.manager.mimetype);
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,7 @@ impl<'a> Executor<'a> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
on!(MANAGER, update_files, &self.app.cx.tasks);
|
||||||
on!(MANAGER, update_mimetype, &self.app.cx.tasks);
|
on!(MANAGER, update_mimetype, &self.app.cx.tasks);
|
||||||
on!(MANAGER, hover);
|
on!(MANAGER, hover);
|
||||||
on!(MANAGER, peek);
|
on!(MANAGER, peek);
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
local Archive = {}
|
local M = {}
|
||||||
|
|
||||||
function Archive:peek()
|
function M:peek()
|
||||||
local _, bound = ya.preview_archive(self)
|
local _, bound = ya.preview_archive(self)
|
||||||
if bound then
|
if bound then
|
||||||
ya.manager_emit("peek", { tostring(bound), only_if = tostring(self.file.url), upper_bound = "" })
|
ya.manager_emit("peek", { tostring(bound), only_if = tostring(self.file.url), upper_bound = "" })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Archive:seek(units)
|
function M:seek(units)
|
||||||
local h = cx.active.current.hovered
|
local h = cx.active.current.hovered
|
||||||
if h and h.url == self.file.url then
|
if h and h.url == self.file.url then
|
||||||
local step = math.floor(units * self.area.h / 10)
|
local step = math.floor(units * self.area.h / 10)
|
||||||
|
|
@ -18,4 +18,4 @@ function Archive:seek(units)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return Archive
|
return M
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
local Code = {}
|
local M = {}
|
||||||
|
|
||||||
function Code:peek()
|
function M:peek()
|
||||||
local _, bound = ya.preview_code(self)
|
local _, bound = ya.preview_code(self)
|
||||||
if bound then
|
if bound then
|
||||||
ya.manager_emit("peek", { tostring(bound), only_if = tostring(self.file.url), upper_bound = "" })
|
ya.manager_emit("peek", { tostring(bound), only_if = tostring(self.file.url), upper_bound = "" })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Code:seek(units)
|
function M:seek(units)
|
||||||
local h = cx.active.current.hovered
|
local h = cx.active.current.hovered
|
||||||
if h and h.url == self.file.url then
|
if h and h.url == self.file.url then
|
||||||
local step = math.floor(units * self.area.h / 10)
|
local step = math.floor(units * self.area.h / 10)
|
||||||
|
|
@ -18,4 +18,4 @@ function Code:seek(units)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return Code
|
return M
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
local Folder_ = {}
|
local M = {}
|
||||||
|
|
||||||
function Folder_:peek()
|
function M:peek()
|
||||||
local folder = Folder:by_kind(Folder.PREVIEW)
|
local folder = Folder:by_kind(Folder.PREVIEW)
|
||||||
if folder == nil or folder.cwd ~= self.file.url then
|
if folder == nil or folder.cwd ~= self.file.url then
|
||||||
return {}
|
return {}
|
||||||
|
|
@ -24,7 +24,7 @@ function Folder_:peek()
|
||||||
ya.preview_widgets(self, { ui.List(self.area, items) })
|
ya.preview_widgets(self, { ui.List(self.area, items) })
|
||||||
end
|
end
|
||||||
|
|
||||||
function Folder_:seek(units)
|
function M:seek(units)
|
||||||
local folder = Folder:by_kind(Folder.PREVIEW)
|
local folder = Folder:by_kind(Folder.PREVIEW)
|
||||||
if folder and folder.cwd == self.file.url then
|
if folder and folder.cwd == self.file.url then
|
||||||
local step = math.floor(units * self.area.h / 10)
|
local step = math.floor(units * self.area.h / 10)
|
||||||
|
|
@ -36,4 +36,4 @@ function Folder_:seek(units)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return Folder_
|
return M
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
local Image = {}
|
local M = {}
|
||||||
|
|
||||||
function Image:cache() return ya.cache_file(self.file.url .. tostring(self.file.cha.modified)) end
|
function M:cache() return ya.cache_file(self.file.url .. tostring(self.file.cha.modified)) end
|
||||||
|
|
||||||
function Image:peek()
|
function M:peek()
|
||||||
local cache = self:cache()
|
local cache = self:cache()
|
||||||
ya.image_show(fs.symlink_metadata(cache) and cache or self.file.url, self.area)
|
ya.image_show(fs.symlink_metadata(cache) and cache or self.file.url, self.area)
|
||||||
ya.preview_widgets(self, {})
|
ya.preview_widgets(self, {})
|
||||||
end
|
end
|
||||||
|
|
||||||
function Image:seek() end
|
function M:seek() end
|
||||||
|
|
||||||
function Image:preload()
|
function M:preload()
|
||||||
local cache = self:cache()
|
local cache = self:cache()
|
||||||
if fs.symlink_metadata(cache) then
|
if fs.symlink_metadata(cache) then
|
||||||
return 1
|
return 1
|
||||||
|
|
@ -19,4 +19,4 @@ function Image:preload()
|
||||||
return ya.image_precache(self.file.url, cache) and 1 or 2
|
return ya.image_precache(self.file.url, cache) and 1 or 2
|
||||||
end
|
end
|
||||||
|
|
||||||
return Image
|
return M
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
local Json = {}
|
local M = {}
|
||||||
|
|
||||||
function Json:peek()
|
function M:peek()
|
||||||
local limit = self.area.h
|
local limit = self.area.h
|
||||||
local child = Command.new("jq")
|
local child = Command.new("jq")
|
||||||
:args({
|
:args({
|
||||||
|
|
@ -35,7 +35,7 @@ function Json:peek()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Json:seek(units)
|
function M:seek(units)
|
||||||
local h = cx.active.current.hovered
|
local h = cx.active.current.hovered
|
||||||
if h and h.url == self.file.url then
|
if h and h.url == self.file.url then
|
||||||
local step = math.floor(units * self.area.h / 10)
|
local step = math.floor(units * self.area.h / 10)
|
||||||
|
|
@ -46,4 +46,4 @@ function Json:seek(units)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return Json
|
return M
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
local Mime = {}
|
local M = {}
|
||||||
|
|
||||||
function Mime:preload()
|
function M:preload()
|
||||||
local command = Command.new("file"):arg("--mime-type"):stdout(Command.PIPED):stderr(Command.PIPED)
|
local command = Command.new("file"):arg("--mime-type"):stdout(Command.PIPED):stderr(Command.PIPED)
|
||||||
if ya.target_family() == "windows" then
|
if ya.target_family() == "windows" then
|
||||||
command:arg("-b")
|
command:arg("-b")
|
||||||
|
|
@ -13,23 +13,23 @@ function Mime:preload()
|
||||||
urls[#urls + 1] = tostring(file.url)
|
urls[#urls + 1] = tostring(file.url)
|
||||||
end
|
end
|
||||||
|
|
||||||
local i, data = 1, {}
|
local i, mimes = 1, {}
|
||||||
local output = command:args(urls):output()
|
local output = command:args(urls):output()
|
||||||
for line in output.stdout:gmatch("[^\r\n]+") do
|
for line in output.stdout:gmatch("[^\r\n]+") do
|
||||||
if i > #urls then
|
if i > #urls then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
if ya.mime_valid(line) then
|
if ya.mime_valid(line) then
|
||||||
data[urls[i]] = line
|
mimes[urls[i]] = line
|
||||||
end
|
end
|
||||||
i = i + 1
|
i = i + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
if #data then
|
if #mimes then
|
||||||
ya.manager_emit("update_mimetype", {}, data)
|
ya.manager_emit("update_mimetype", {}, mimes)
|
||||||
return 3
|
return 3
|
||||||
end
|
end
|
||||||
return 2
|
return 2
|
||||||
end
|
end
|
||||||
|
|
||||||
return Mime
|
return M
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
local Noop = {}
|
local M = {}
|
||||||
|
|
||||||
function Noop:peek() end
|
function M:peek() end
|
||||||
|
|
||||||
function Noop:seek() end
|
function M:seek() end
|
||||||
|
|
||||||
function Noop:preload() return 1 end
|
function M:preload() return 1 end
|
||||||
|
|
||||||
return Noop
|
return M
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
local Pdf = {}
|
local M = {}
|
||||||
|
|
||||||
function Pdf:cache() return ya.cache_file(self.file.url .. self.skip .. tostring(self.file.cha.modified)) end
|
function M:cache() return ya.cache_file(self.file.url .. self.skip .. tostring(self.file.cha.modified)) end
|
||||||
|
|
||||||
function Pdf:peek()
|
function M:peek()
|
||||||
if self:preload() == 1 then
|
if self:preload() == 1 then
|
||||||
ya.image_show(self:cache(), self.area)
|
ya.image_show(self:cache(), self.area)
|
||||||
ya.preview_widgets(self, {})
|
ya.preview_widgets(self, {})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Pdf:seek(units)
|
function M:seek(units)
|
||||||
local h = cx.active.current.hovered
|
local h = cx.active.current.hovered
|
||||||
if h and h.url == self.file.url then
|
if h and h.url == self.file.url then
|
||||||
local step = ya.clamp(-1, units, 1)
|
local step = ya.clamp(-1, units, 1)
|
||||||
|
|
@ -17,7 +17,7 @@ function Pdf:seek(units)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Pdf:preload()
|
function M:preload()
|
||||||
local cache = self:cache()
|
local cache = self:cache()
|
||||||
if fs.symlink_metadata(cache) then
|
if fs.symlink_metadata(cache) then
|
||||||
return 1
|
return 1
|
||||||
|
|
@ -40,4 +40,4 @@ function Pdf:preload()
|
||||||
return fs.write(cache, output.stdout) and 1 or 2
|
return fs.write(cache, output.stdout) and 1 or 2
|
||||||
end
|
end
|
||||||
|
|
||||||
return Pdf
|
return M
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
local Video = {}
|
local M = {}
|
||||||
|
|
||||||
function Video:cache() return ya.cache_file(self.file.url .. self.skip .. tostring(self.file.cha.modified)) end
|
function M:cache() return ya.cache_file(self.file.url .. self.skip .. tostring(self.file.cha.modified)) end
|
||||||
|
|
||||||
function Video:peek()
|
function M:peek()
|
||||||
if self:preload() == 1 then
|
if self:preload() == 1 then
|
||||||
ya.image_show(self:cache(), self.area)
|
ya.image_show(self:cache(), self.area)
|
||||||
ya.preview_widgets(self, {})
|
ya.preview_widgets(self, {})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Video:seek(units)
|
function M:seek(units)
|
||||||
local h = cx.active.current.hovered
|
local h = cx.active.current.hovered
|
||||||
if h and h.url == self.file.url then
|
if h and h.url == self.file.url then
|
||||||
ya.manager_emit("peek", {
|
ya.manager_emit("peek", {
|
||||||
|
|
@ -19,7 +19,7 @@ function Video:seek(units)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Video:preload()
|
function M:preload()
|
||||||
local percentage = 5 + self.skip
|
local percentage = 5 + self.skip
|
||||||
if percentage > 95 then
|
if percentage > 95 then
|
||||||
ya.manager_emit("peek", { "90", only_if = tostring(self.file.url), upper_bound = "" })
|
ya.manager_emit("peek", { "90", only_if = tostring(self.file.url), upper_bound = "" })
|
||||||
|
|
@ -52,4 +52,4 @@ function Video:preload()
|
||||||
return status:success() and 1 or 2
|
return status:success() and 1 or 2
|
||||||
end
|
end
|
||||||
|
|
||||||
return Video
|
return M
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use mlua::{Lua, LuaSerdeExt, SerializeOptions};
|
use mlua::{Lua, LuaSerdeExt, SerializeOptions};
|
||||||
use yazi_config::{MANAGER, PREVIEW, THEME};
|
use yazi_config::{BOOT, MANAGER, PREVIEW, THEME};
|
||||||
|
|
||||||
const OPTIONS: SerializeOptions =
|
const OPTIONS: SerializeOptions =
|
||||||
SerializeOptions::new().serialize_none_to_null(false).serialize_unit_to_null(false);
|
SerializeOptions::new().serialize_none_to_null(false).serialize_unit_to_null(false);
|
||||||
|
|
@ -11,6 +11,16 @@ pub struct Config<'a> {
|
||||||
impl<'a> Config<'a> {
|
impl<'a> Config<'a> {
|
||||||
pub fn new(lua: &'a Lua) -> Self { Self { lua } }
|
pub fn new(lua: &'a Lua) -> Self { Self { lua } }
|
||||||
|
|
||||||
|
pub fn install_boot(self) -> mlua::Result<Self> {
|
||||||
|
self.lua.globals().set("BOOT", self.lua.to_value_with(&*BOOT, OPTIONS)?)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn install_manager(self) -> mlua::Result<Self> {
|
||||||
|
self.lua.globals().set("MANAGER", self.lua.to_value_with(&*MANAGER, OPTIONS)?)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn install_theme(self) -> mlua::Result<Self> {
|
pub fn install_theme(self) -> mlua::Result<Self> {
|
||||||
self.lua.globals().set("THEME", self.lua.to_value_with(&*THEME, OPTIONS)?)?;
|
self.lua.globals().set("THEME", self.lua.to_value_with(&*THEME, OPTIONS)?)?;
|
||||||
Ok(self)
|
Ok(self)
|
||||||
|
|
@ -20,9 +30,4 @@ impl<'a> Config<'a> {
|
||||||
self.lua.globals().set("PREVIEW", self.lua.to_value_with(&*PREVIEW, OPTIONS)?)?;
|
self.lua.globals().set("PREVIEW", self.lua.to_value_with(&*PREVIEW, OPTIONS)?)?;
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn install_manager(self) -> mlua::Result<Self> {
|
|
||||||
self.lua.globals().set("MANAGER", self.lua.to_value_with(&*MANAGER, OPTIONS)?)?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
40
yazi-plugin/src/external/file.rs
vendored
40
yazi-plugin/src/external/file.rs
vendored
|
|
@ -1,40 +0,0 @@
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
|
||||||
use futures::TryFutureExt;
|
|
||||||
use tokio::process::Command;
|
|
||||||
use tracing::error;
|
|
||||||
use yazi_shared::{fs::Url, mime_valid};
|
|
||||||
|
|
||||||
async fn _file(files: &[&Url]) -> Result<BTreeMap<Url, String>> {
|
|
||||||
if files.is_empty() {
|
|
||||||
bail!("no files to get mime types for");
|
|
||||||
}
|
|
||||||
|
|
||||||
let output = Command::new("file")
|
|
||||||
.args([cfg!(windows).then_some("-b").unwrap_or("-bL"), "--mime-type"])
|
|
||||||
.args(files)
|
|
||||||
.kill_on_drop(true)
|
|
||||||
.output()
|
|
||||||
.inspect_err(|e| error!("failed to execute `file`: {}", e))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let output = String::from_utf8_lossy(&output.stdout);
|
|
||||||
let mimes = BTreeMap::from_iter(
|
|
||||||
files
|
|
||||||
.iter()
|
|
||||||
.zip(output.trim().lines())
|
|
||||||
.filter(|(_, m)| mime_valid(m))
|
|
||||||
.map(|(&f, m)| (f.clone(), m.to_string())),
|
|
||||||
);
|
|
||||||
|
|
||||||
if mimes.is_empty() {
|
|
||||||
error!("failed to get mime types: {:?}", files);
|
|
||||||
bail!("failed to get mime types");
|
|
||||||
}
|
|
||||||
Ok(mimes)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn file(files: &[impl AsRef<Url>]) -> Result<BTreeMap<Url, String>> {
|
|
||||||
_file(&files.iter().map(AsRef::as_ref).collect::<Vec<_>>()).await
|
|
||||||
}
|
|
||||||
2
yazi-plugin/src/external/mod.rs
vendored
2
yazi-plugin/src/external/mod.rs
vendored
|
|
@ -1,5 +1,4 @@
|
||||||
mod fd;
|
mod fd;
|
||||||
mod file;
|
|
||||||
mod fzf;
|
mod fzf;
|
||||||
mod highlighter;
|
mod highlighter;
|
||||||
mod lsar;
|
mod lsar;
|
||||||
|
|
@ -8,7 +7,6 @@ mod shell;
|
||||||
mod zoxide;
|
mod zoxide;
|
||||||
|
|
||||||
pub use fd::*;
|
pub use fd::*;
|
||||||
pub use file::*;
|
|
||||||
pub use fzf::*;
|
pub use fzf::*;
|
||||||
pub use highlighter::*;
|
pub use highlighter::*;
|
||||||
pub use lsar::*;
|
pub use lsar::*;
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ impl Loader {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let b = fs::read(BOOT.plugin_dir.join(name)).await.map(|v| v.into()).unwrap_or(Cow::from(
|
let b = fs::read(BOOT.plugin_dir.join(name)).await.map(|v| v.into()).or_else(|_| {
|
||||||
match name {
|
Ok(Cow::from(match name {
|
||||||
"noop.lua" => include_bytes!("../preset/plugins/noop.lua") as &[u8],
|
"noop.lua" => include_bytes!("../preset/plugins/noop.lua") as &[u8],
|
||||||
"archive.lua" => include_bytes!("../preset/plugins/archive.lua"),
|
"archive.lua" => include_bytes!("../preset/plugins/archive.lua"),
|
||||||
"code.lua" => include_bytes!("../preset/plugins/code.lua"),
|
"code.lua" => include_bytes!("../preset/plugins/code.lua"),
|
||||||
|
|
@ -33,9 +33,9 @@ impl Loader {
|
||||||
"mime.lua" => include_bytes!("../preset/plugins/mime.lua"),
|
"mime.lua" => include_bytes!("../preset/plugins/mime.lua"),
|
||||||
"pdf.lua" => include_bytes!("../preset/plugins/pdf.lua"),
|
"pdf.lua" => include_bytes!("../preset/plugins/pdf.lua"),
|
||||||
"video.lua" => include_bytes!("../preset/plugins/video.lua"),
|
"video.lua" => include_bytes!("../preset/plugins/video.lua"),
|
||||||
_ => bail!("plugin not found: {}", name),
|
_ => bail!("plugin not found: {name}"),
|
||||||
},
|
}))
|
||||||
));
|
})?;
|
||||||
|
|
||||||
self.loaded.write().insert(name.to_owned(), b.into_owned());
|
self.loaded.write().insert(name.to_owned(), b.into_owned());
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use mlua::{Lua, Table};
|
use mlua::Lua;
|
||||||
|
use yazi_config::BOOT;
|
||||||
use yazi_shared::RoCell;
|
use yazi_shared::RoCell;
|
||||||
|
|
||||||
pub static LUA: RoCell<Lua> = RoCell::new();
|
pub static LUA: RoCell<Lua> = RoCell::new();
|
||||||
|
|
@ -7,7 +8,7 @@ pub static LUA: RoCell<Lua> = RoCell::new();
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
fn stage_1(lua: &Lua) -> Result<()> {
|
fn stage_1(lua: &Lua) -> Result<()> {
|
||||||
crate::Loader::init();
|
crate::Loader::init();
|
||||||
crate::Config::new(lua).install_theme()?.install_manager()?;
|
crate::Config::new(lua).install_boot()?.install_manager()?.install_theme()?;
|
||||||
crate::utils::install(lua)?;
|
crate::utils::install(lua)?;
|
||||||
|
|
||||||
// Base
|
// Base
|
||||||
|
|
@ -29,14 +30,15 @@ pub fn init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stage_2(lua: &Lua) {
|
fn stage_2(lua: &Lua) {
|
||||||
let ya: Table = lua.globals().get("ya").unwrap();
|
let setup = br#"
|
||||||
ya.set("SYNC_ON", true).unwrap();
|
ya.SYNC_ON = true
|
||||||
|
package.path = BOOT.plugin_dir .. "/?.yazi/init.lua;" .. BOOT.plugin_dir .. "/?.lua;" .. package.path
|
||||||
|
"#;
|
||||||
|
lua.load(setup as &[u8]).exec().unwrap();
|
||||||
|
|
||||||
// TODO: plugin system
|
if let Ok(b) = std::fs::read(BOOT.config_dir.join("init.lua")) {
|
||||||
// PLUGIN.preload.iter().for_each(|p| {
|
lua.load(b).exec().unwrap();
|
||||||
// let b = std::fs::read(p).unwrap_or_else(|_| panic!("failed to read
|
}
|
||||||
// plugin: {p:?}")); lua.load(&b).exec().unwrap_or_else(|_| panic!("failed
|
|
||||||
// to load plugin: {p:?}")); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let lua = Lua::new();
|
let lua = Lua::new();
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
use yazi_shared::{emit, fs::{calculate_size, FilesOp, Url}, Throttle};
|
use yazi_shared::{fs::{calculate_size, FilesOp, Url}, Throttle};
|
||||||
|
|
||||||
use crate::TaskOp;
|
use crate::TaskOp;
|
||||||
|
|
||||||
|
|
@ -74,7 +74,7 @@ impl Preload {
|
||||||
}
|
}
|
||||||
|
|
||||||
let parent = buf[0].0.parent_url().unwrap();
|
let parent = buf[0].0.parent_url().unwrap();
|
||||||
emit!(Files(FilesOp::Size(parent, BTreeMap::from_iter(buf))));
|
FilesOp::Size(parent, BTreeMap::from_iter(buf)).emit();
|
||||||
});
|
});
|
||||||
|
|
||||||
self.sch.send(TaskOp::Adv(task.id, 1, 0))?;
|
self.sch.send(TaskOp::Adv(task.id, 1, 0))?;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use crossterm::event::KeyEvent;
|
||||||
use tokio::sync::{mpsc::UnboundedSender, oneshot};
|
use tokio::sync::{mpsc::UnboundedSender, oneshot};
|
||||||
|
|
||||||
use super::Exec;
|
use super::Exec;
|
||||||
use crate::{fs::FilesOp, term::Term, Layer, RoCell};
|
use crate::{term::Term, Layer, RoCell};
|
||||||
|
|
||||||
static TX: RoCell<UnboundedSender<Event>> = RoCell::new();
|
static TX: RoCell<UnboundedSender<Event>> = RoCell::new();
|
||||||
|
|
||||||
|
|
@ -15,7 +15,6 @@ pub enum Event {
|
||||||
Call(Vec<Exec>, Layer),
|
Call(Vec<Exec>, Layer),
|
||||||
|
|
||||||
// Manager
|
// Manager
|
||||||
Files(FilesOp),
|
|
||||||
Pages(usize),
|
Pages(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,9 +43,6 @@ macro_rules! emit {
|
||||||
$crate::event::Event::Call($exec, $layer).emit();
|
$crate::event::Event::Call($exec, $layer).emit();
|
||||||
};
|
};
|
||||||
|
|
||||||
(Files($op:expr)) => {
|
|
||||||
$crate::event::Event::Files($op).emit();
|
|
||||||
};
|
|
||||||
(Pages($page:expr)) => {
|
(Pages($page:expr)) => {
|
||||||
$crate::event::Event::Pages($page).emit();
|
$crate::event::Event::Pages($page).emit();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{collections::BTreeMap, ffi::OsStr, fs::Metadata, ops::Deref};
|
use std::{ffi::OsStr, fs::Metadata, ops::Deref};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
@ -52,13 +52,6 @@ impl File {
|
||||||
let ck = if url.is_hidden() { ChaKind::HIDDEN } else { ChaKind::empty() };
|
let ck = if url.is_hidden() { ChaKind::HIDDEN } else { ChaKind::empty() };
|
||||||
Self { url, cha: Cha::default().with_kind(ck), link_to: None }
|
Self { url, cha: Cha::default().with_kind(ck), link_to: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn into_map(self) -> BTreeMap<Url, File> {
|
|
||||||
let mut map = BTreeMap::new();
|
|
||||||
map.insert(self.url.clone(), self);
|
|
||||||
map
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl File {
|
impl File {
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,21 @@
|
||||||
use std::{collections::{BTreeMap, BTreeSet}, sync::atomic::{AtomicU64, Ordering}};
|
use std::{collections::BTreeMap, sync::atomic::{AtomicU64, Ordering}};
|
||||||
|
|
||||||
use super::File;
|
use super::File;
|
||||||
use crate::{emit, fs::Url};
|
use crate::{emit, event::Exec, fs::Url, Layer};
|
||||||
|
|
||||||
pub static FILES_TICKET: AtomicU64 = AtomicU64::new(0);
|
pub static FILES_TICKET: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum FilesOp {
|
pub enum FilesOp {
|
||||||
Full(Url, Vec<File>),
|
Full(Url, Vec<File>),
|
||||||
Part(Url, u64, Vec<File>),
|
Part(Url, Vec<File>, u64),
|
||||||
Size(Url, BTreeMap<Url, u64>),
|
Size(Url, BTreeMap<Url, u64>),
|
||||||
IOErr(Url),
|
IOErr(Url),
|
||||||
|
|
||||||
Creating(Url, BTreeMap<Url, File>),
|
Creating(Url, Vec<File>),
|
||||||
Deleting(Url, BTreeSet<Url>),
|
Deleting(Url, Vec<Url>),
|
||||||
Replacing(Url, BTreeMap<Url, File>),
|
Updating(Url, BTreeMap<Url, File>),
|
||||||
|
Upserting(Url, BTreeMap<Url, File>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilesOp {
|
impl FilesOp {
|
||||||
|
|
@ -28,14 +29,41 @@ impl FilesOp {
|
||||||
|
|
||||||
Self::Creating(url, _) => url,
|
Self::Creating(url, _) => url,
|
||||||
Self::Deleting(url, _) => url,
|
Self::Deleting(url, _) => url,
|
||||||
Self::Replacing(url, _) => url,
|
Self::Updating(url, _) => url,
|
||||||
|
Self::Upserting(url, _) => url,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
pub fn emit(self) {
|
||||||
|
emit!(Call(Exec::call("update_files", vec![]).with_data(self).vec(), Layer::Manager));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn prepare(url: &Url) -> u64 {
|
pub fn prepare(url: &Url) -> u64 {
|
||||||
let ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed);
|
let ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed);
|
||||||
emit!(Files(Self::Part(url.clone(), ticket, Vec::new())));
|
Self::Part(url.clone(), Vec::new(), ticket).emit();
|
||||||
ticket
|
ticket
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn chroot(&self, new: Url) -> Self {
|
||||||
|
todo!()
|
||||||
|
// Full(Url, Vec<File>),
|
||||||
|
// Part(Url, u64, Vec<File>),
|
||||||
|
// Size(Url, BTreeMap<Url, u64>),
|
||||||
|
// IOErr(Url),
|
||||||
|
|
||||||
|
// Creating(Url, BTreeMap<Url, File>),
|
||||||
|
// Deleting(Url, BTreeSet<Url>),
|
||||||
|
// Replacing(Url, BTreeMap<Url, File>),
|
||||||
|
|
||||||
|
// match self {
|
||||||
|
// FilesOp::Full(_, vec) => {}
|
||||||
|
// FilesOp::Part(_, _, vec) => todo!(),
|
||||||
|
// FilesOp::Size(_, map) => todo!(),
|
||||||
|
// FilesOp::IOErr(_) => todo!(),
|
||||||
|
// FilesOp::Creating(_, map) => todo!(),
|
||||||
|
// FilesOp::Deleting(_, set) => todo!(),
|
||||||
|
// FilesOp::Replacing(_, map) => todo!(),
|
||||||
|
// }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue