diff --git a/README.md b/README.md index 0ef45173..0d1adc40 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,16 @@ ## 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) - 🚀 **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. - 🖼️ **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 -- 💫 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.) - 🔄 Batch Renaming, Visual Mode, File Chooser - 🎨 Theme System, Custom Layouts, Trash Bin, CSI u diff --git a/cspell.json b/cspell.json index 1a34a0b9..07e982c0 100644 --- a/cspell.json +++ b/cspell.json @@ -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"} diff --git a/yazi-config/src/boot/boot.rs b/yazi-config/src/boot/boot.rs index eb3ad213..47ddbcdc 100644 --- a/yazi-config/src/boot/boot.rs +++ b/yazi-config/src/boot/boot.rs @@ -1,12 +1,13 @@ use std::{ffi::OsString, fs, path::{Path, PathBuf}, process}; use clap::Parser; +use serde::Serialize; use yazi_shared::fs::{current_cwd, expand_path}; use super::Args; use crate::{Xdg, ARGS}; -#[derive(Debug)] +#[derive(Debug, Serialize)] pub struct Boot { pub cwd: PathBuf, pub file: Option, diff --git a/yazi-core/src/folder/files.rs b/yazi-core/src/folder/files.rs index f5e3b426..29a03ac9 100644 --- a/yazi-core/src/folder/files.rs +++ b/yazi-core/src/folder/files.rs @@ -8,10 +8,10 @@ use yazi_shared::fs::{File, Url, FILES_TICKET}; use super::FilesSorter; pub struct Files { - items: Vec, - hidden: Vec, - ticket: u64, - pub(crate) version: u64, + hidden: Vec, + items: Vec, + ticket: u64, + pub(crate) revision: u64, pub sizes: BTreeMap, selected: BTreeSet, @@ -23,10 +23,10 @@ pub struct Files { impl Default for Files { fn default() -> Self { Self { - items: Default::default(), - hidden: Default::default(), - ticket: Default::default(), - version: Default::default(), + items: Default::default(), + hidden: Default::default(), + ticket: Default::default(), + revision: Default::default(), sizes: Default::default(), selected: Default::default(), @@ -68,7 +68,7 @@ impl Files { #[inline] pub fn select(&mut self, url: &Url, state: Option) -> bool { 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 { return false; @@ -126,132 +126,174 @@ impl Files { applied } - pub fn update_full(&mut self, mut items: Vec) -> bool { - if !self.show_hidden { - (self.hidden, items) = items.into_iter().partition(|f| f.is_hidden()); + pub fn update_full(&mut self, files: Vec) { + if files.is_empty() { + return; } + self.ticket = FILES_TICKET.fetch_add(1, Ordering::Relaxed); - self.sorter.sort(&mut items, &self.sizes); - self.items = items; - self.version += 1; - true + self.revision += 1; + + (self.hidden, self.items) = if self.show_hidden { + (vec![], files) + } else { + files.into_iter().partition(|f| f.is_hidden()) + }; } - pub fn update_part(&mut self, version: u64, items: Vec) -> bool { - if !items.is_empty() { - if version != self.ticket { - return false; + pub fn update_part(&mut self, files: Vec, ticket: u64) { + if !files.is_empty() { + if ticket != self.ticket { + return; } + self.revision += 1; if self.show_hidden { - self.items.extend(items); + self.hidden.clear(); + self.items.extend(files); } else { - let (hidden, items): (Vec<_>, Vec<_>) = items.into_iter().partition(|f| f.is_hidden()); - self.items.extend(items); + let (hidden, items): (Vec<_>, Vec<_>) = files.into_iter().partition(|f| f.is_hidden()); self.hidden.extend(hidden); + self.items.extend(items); } - - self.sorter.sort(&mut self.items, &self.sizes); - self.version += 1; - return true; + return; } - self.ticket = version; - if self.items.is_empty() && self.hidden.is_empty() { - return false; + self.ticket = ticket; + if !self.items.is_empty() || !self.hidden.is_empty() { + 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) -> bool { - self.sizes.extend(items); + pub fn update_size(&mut self, sizes: BTreeMap) { + if sizes.is_empty() { + return; + } + if self.sorter.by == SortBy::Size { - self.sorter.sort(&mut self.items, &self.sizes); - self.version += 1; + self.revision += 1; } - true + self.sizes.extend(sizes); } - pub fn update_creating(&mut self, mut todo: BTreeMap) -> bool { - if !self.show_hidden { - todo.retain(|_, f| !f.is_hidden()); + pub fn update_creating(&mut self, files: Vec) { + if files.is_empty() { + 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) -> bool { - let mut removed = Vec::with_capacity(todo.len()); macro_rules! go { - ($name:expr) => { - removed.clear(); - for i in 0..$name.len() { - if todo.remove(&$name[i].url) { - removed.push(i); - if todo.is_empty() { + ($dist:expr, $src:expr) => { + let mut todo: BTreeMap<_, _> = $src.into_iter().map(|f| (f.url(), f)).collect(); + for f in &$dist { + if todo.remove(&f.url).is_some() && todo.is_empty() { + break; + } + } + 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) { + 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) -> [BTreeMap; 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; } } } - for i in (0..removed.len()).rev() { - $name.remove(removed[i]); + if $src.len() != len { + self.revision += 1; } }; } - let mut b = false; - if !todo.is_empty() { - go!(self.items); - b |= !removed.is_empty(); - } + let (mut hidden, mut items) = if self.show_hidden { + (BTreeMap::new(), files) + } else { + files.into_iter().partition(|(_, f)| f.is_hidden()) + }; - if !todo.is_empty() { - go!(self.hidden); - b |= !removed.is_empty(); + if !items.is_empty() { + go!(self.items, items); } - b + if !hidden.is_empty() { + go!(self.hidden, hidden); + } + [hidden, items] } - pub fn update_replacing(&mut self, todo: &mut BTreeMap) -> bool { - if todo.is_empty() { - return false; + pub fn update_upserting(&mut self, files: BTreeMap) { + if files.is_empty() { + return; } - macro_rules! go { - ($name:expr) => { - for i in 0..$name.len() { - if let Some(f) = todo.remove(&$name[i].url) { - $name[i] = f; - if todo.is_empty() { - self.version += 1; - return true; - } - } - } - }; + let [hidden, items] = self.update_updating(files); + if hidden.is_empty() && items.is_empty() { + return; } - let old = todo.len(); - go!(self.items); - go!(self.hidden); - - if old != todo.len() { - self.version += 1; - return true; + self.revision += 1; + if !hidden.is_empty() { + self.hidden.extend(hidden.into_values()); + } + if !items.is_empty() { + self.items.extend(items.into_values()); } - false } } @@ -318,7 +360,7 @@ impl Files { return false; } self.sorter = sorter; - self.version += 1; + self.revision += 1; self.sorter.sort(&mut self.items, &self.sizes) } @@ -339,7 +381,7 @@ impl Files { } self.show_hidden = state; - self.version += 1; + self.revision += 1; true } } diff --git a/yazi-core/src/folder/folder.rs b/yazi-core/src/folder/folder.rs index 230bc9f1..5ef367f0 100644 --- a/yazi-core/src/folder/folder.rs +++ b/yazi-core/src/folder/folder.rs @@ -25,17 +25,19 @@ impl From<&Url> for Folder { impl Folder { pub fn update(&mut self, op: FilesOp) -> bool { - let b = match op { - FilesOp::Full(_, items) => self.files.update_full(items), - FilesOp::Part(_, ticket, items) => self.files.update_part(ticket, items), - FilesOp::Size(_, items) => self.files.update_size(items), + let revision = self.files.revision; + match op { + FilesOp::Full(_, files) => self.files.update_full(files), + 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::Deleting(_, items) => self.files.update_deleting(items), - FilesOp::Replacing(_, mut items) => self.files.update_replacing(&mut items), + FilesOp::Creating(_, files) => self.files.update_creating(files), + FilesOp::Deleting(_, urls) => self.files.update_deleting(urls), + FilesOp::Updating(_, files) => _ = self.files.update_updating(files), + FilesOp::Upserting(_, files) => self.files.update_upserting(files), _ => unreachable!(), - }; - if !b { + } + if revision == self.files.revision { return false; } diff --git a/yazi-core/src/manager/commands/create.rs b/yazi-core/src/manager/commands/create.rs index 7320fd5d..fb329ce6 100644 --- a/yazi-core/src/manager/commands/create.rs +++ b/yazi-core/src/manager/commands/create.rs @@ -2,7 +2,7 @@ use std::path::{PathBuf, MAIN_SEPARATOR}; use tokio::fs; 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}; @@ -42,7 +42,7 @@ impl Manager { let child = Url::from(path.components().take(cwd.components().count() + 1).collect::()); 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)); } Ok::<(), anyhow::Error>(()) diff --git a/yazi-core/src/manager/commands/mod.rs b/yazi-core/src/manager/commands/mod.rs index 0fc34796..72cef281 100644 --- a/yazi-core/src/manager/commands/mod.rs +++ b/yazi-core/src/manager/commands/mod.rs @@ -15,5 +15,6 @@ mod tab_close; mod tab_create; mod tab_swap; mod tab_switch; +mod update_files; mod update_mimetype; mod yank; diff --git a/yazi-core/src/manager/commands/open.rs b/yazi-core/src/manager/commands/open.rs index 9275bf7f..d8365e75 100644 --- a/yazi-core/src/manager/commands/open.rs +++ b/yazi-core/src/manager/commands/open.rs @@ -1,7 +1,6 @@ use std::ffi::OsString; use yazi_config::{popup::SelectCfg, OPEN}; -use yazi_plugin::external; use yazi_shared::{event::Exec, MIME_DIR}; use crate::{manager::Manager, select::Select, tasks::Tasks}; @@ -44,28 +43,30 @@ impl Manager { } let opt = opt.into() as Opt; - tokio::spawn(async move { - let todo: Vec<_> = files.iter().filter(|(_, m)| m.is_none()).map(|(u, _)| u).collect(); - if let Ok(mut mimes) = external::file(&todo).await { - files = files - .into_iter() - .map(|(u, m)| { - let mime = m.or_else(|| mimes.remove(&u)); - (u, mime) - }) - .collect(); - } + // TODO: plugin system + // tokio::spawn(async move { + // let todo: Vec<_> = files.iter().filter(|(_, m)| m.is_none()).map(|(u, _)| + // u).collect(); if let Ok(mut mimes) = external::file(&todo).await { + // files = files + // .into_iter() + // .map(|(u, m)| { + // let mime = m.or_else(|| mimes.remove(&u)); + // (u, mime) + // }) + // .collect(); + // } - let files: Vec<_> = - files.into_iter().filter_map(|(u, m)| m.map(|m| (u.into_os_string(), m))).collect(); + // let files: Vec<_> = + // files.into_iter().filter_map(|(u, m)| m.map(|m| (u.into_os_string(), + // m))).collect(); - if opt.interactive { - Self::open_interactive(files).await; - return; - } + // if opt.interactive { + // Self::open_interactive(files).await; + // return; + // } - Tasks::_open(files, None); - }); + // Tasks::_open(files, None); + // }); false } } diff --git a/yazi-core/src/manager/commands/refresh.rs b/yazi-core/src/manager/commands/refresh.rs index a3691fe5..2cf64129 100644 --- a/yazi-core/src/manager/commands/refresh.rs +++ b/yazi-core/src/manager/commands/refresh.rs @@ -22,8 +22,8 @@ impl Manager { self.active_mut().apply_files_attrs(false); - if let Some(f) = self.parent() { - self.watcher.trigger_dirs(&[self.cwd(), &f.cwd]); + if let Some(p) = self.parent() { + self.watcher.trigger_dirs(&[self.cwd(), &p.cwd]); } else { self.watcher.trigger_dirs(&[self.cwd()]); } diff --git a/yazi-core/src/manager/commands/rename.rs b/yazi-core/src/manager/commands/rename.rs index 17e39a3d..9ad61234 100644 --- a/yazi-core/src/manager/commands/rename.rs +++ b/yazi-core/src/manager/commands/rename.rs @@ -5,7 +5,7 @@ use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}}; use yazi_config::{popup::InputCfg, OPEN, PREVIEW}; use yazi_plugin::external::{self, ShellOpt}; 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}; @@ -25,7 +25,7 @@ impl Manager { } 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))) } diff --git a/yazi-core/src/manager/commands/update_files.rs b/yazi-core/src/manager/commands/update_files.rs new file mode 100644 index 00000000..d2c2144a --- /dev/null +++ b/yazi-core/src/manager/commands/update_files.rs @@ -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 { 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, 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 + } +} diff --git a/yazi-core/src/manager/manager.rs b/yazi-core/src/manager/manager.rs index 040f0d6b..813593fd 100644 --- a/yazi-core/src/manager/manager.rs +++ b/yazi-core/src/manager/manager.rs @@ -1,6 +1,6 @@ use std::collections::{HashMap, HashSet}; -use yazi_shared::fs::{File, FilesOp, Url}; +use yazi_shared::fs::{File, Url}; use super::{Tabs, Watcher}; use crate::{folder::Folder, tab::Tab}; @@ -23,48 +23,6 @@ impl Manager { 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 { diff --git a/yazi-core/src/manager/watcher.rs b/yazi-core/src/manager/watcher.rs index 7cd1baf6..ee83fbed 100644 --- a/yazi-core/src/manager/watcher.rs +++ b/yazi-core/src/manager/watcher.rs @@ -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 parking_lot::RwLock; use tokio::{fs, pin, sync::mpsc::{self, UnboundedReceiver}}; 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; pub struct Watcher { - watcher: RecommendedWatcher, - watched: Arc>>>, + watcher: RecommendedWatcher, + watched: Arc>>, + pub linked: Arc>>, // from ==> to } impl Watcher { @@ -25,55 +27,45 @@ impl Watcher { 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 { - EventKind::Create(_) => { - tx.send(parent).ok(); - } - EventKind::Modify(kind) => { - match kind { - ModifyKind::Data(_) => {} - ModifyKind::Metadata(kind) => match kind { - MetadataKind::Permissions => {} - MetadataKind::Ownership => {} - MetadataKind::Extended => {} - _ => return, - }, - ModifyKind::Name(_) => {} + EventKind::Create(_) => {} + EventKind::Modify(kind) => match kind { + ModifyKind::Data(_) => {} + ModifyKind::Metadata(md) => match md { + MetadataKind::WriteTime => {} + MetadataKind::Permissions => {} + MetadataKind::Ownership => {} _ => return, - }; + }, + ModifyKind::Name(_) => {} + _ => return, + }, + EventKind::Remove(_) => {} + _ => return, + } - tx.send(path).ok(); - tx.send(parent).ok(); - } - EventKind::Remove(_) => { - tx.send(path).ok(); - tx.send(parent).ok(); - } - _ => (), + for path in event.paths { + tx.send(Url::from(path)).ok(); } } }, Default::default(), ); - let instance = Self { watcher: watcher.unwrap(), watched: Default::default() }; - tokio::spawn(Self::on_changed(rx, instance.watched.clone())); + let instance = + Self { watcher: watcher.unwrap(), watched: Default::default(), linked: Default::default() }; + tokio::spawn(Self::on_changed(rx)); instance } - pub(super) fn watch(&mut self, mut watched: BTreeSet<&Url>) { - watched.retain(|&u| u.is_regular()); + pub(super) fn watch(&mut self, mut new: BTreeSet<&Url>) { + new.retain(|&u| u.is_regular()); let (to_unwatch, to_watch): (BTreeSet<_>, BTreeSet<_>) = { let guard = self.watched.read(); - let keys = guard.keys().collect::>(); + let old: BTreeSet<_> = guard.iter().collect(); ( - keys.difference(&watched).map(|&x| x.clone()).collect(), - watched.difference(&keys).map(|&x| x.clone()).collect(), + old.difference(&new).map(|&x| x.clone()).collect(), + new.difference(&old).map(|&x| x.clone()).collect(), ) }; @@ -82,141 +74,103 @@ impl Watcher { } for u in to_watch { if self.watcher.watch(&u, RecursiveMode::NonRecursive).is_err() { - watched.remove(&u); + new.remove(&u); } } - let mut to_resolve = Vec::new(); - let mut guard = self.watched.write(); - *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))); - } - _ => {} - } - } - }); + *self.watched.write() = new.into_iter().cloned().collect(); + self.sync_linked(); } pub(super) fn trigger_dirs(&self, dirs: &[&Url]) { - let dirs: Vec<_> = dirs.iter().filter(|&u| u.is_regular()).map(|&u| u.clone()).collect(); - if dirs.is_empty() { + let urls: Vec<_> = dirs.iter().filter(|&u| u.is_regular()).map(|&u| u.clone()).collect(); + if urls.is_empty() { return; } - let watched = self.watched.clone(); tokio::spawn(async move { - let watched = watched.read().clone(); - for dir in dirs { - Self::dir_changed(&dir, &watched).await; + for u in urls { + let Ok(rx) = Files::from_dir(&u).await else { + FilesOp::IOErr(u).emit(); + return; + }; + + let files: Vec<_> = UnboundedReceiverStream::new(rx).collect().await; + FilesOp::Full(u, files).emit(); } }); } - async fn on_changed( - rx: UnboundedReceiver, - watched: Arc>>>, - ) { + fn sync_linked(&self) { + let mut new = self.watched.read().clone(); + 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) { // TODO: revert this once a new notification is implemented let rx = UnboundedReceiverStream::new(rx).chunks_timeout(10, Duration::from_millis(20)); pin!(rx); while let Some(urls) = rx.next().await { - let (mut files, mut dirs): (Vec<_>, Vec<_>) = Default::default(); - for url in urls.into_iter().collect::>() { - if fs::metadata(&url).await.map(|m| !m.is_dir()).unwrap_or(false) { - files.push(url); - } else { - dirs.push(url); + let urls = urls.into_iter().collect::>(); + let mut reload = Vec::with_capacity(urls.len()); + + for u in urls { + let Some(parent) = u.parent_url() else { + 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(); - - 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 reload.is_empty() { + continue; } - - for dir in dirs { - Self::dir_changed(&dir, &watched).await; + if let Err(e) = isolate::preload("mime.lua".to_string(), reload, true).await { + error!("preload in watcher failed: {e}"); } } } - - async fn files_changed(urls: &[Url], watched: &IndexMap>) { - // TODO: plugin system - // let Ok(mut mimes) = external::file(urls).await else { - // return; - // }; - - // let linked: Vec<_> = watched.iter().filter_map(|(k, v)| - // v.as_ref().map(|v| (k, v))).fold( Vec::new(), - // |mut aac, (k, v)| { - // mimes - // .iter() - // .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>) { - 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 { - 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>) -> Vec<&'a Url> { - watched - .iter() - .filter_map(|(k, v)| v.as_ref().map(|v| (k, v))) - .filter(|(_, v)| *v == url) - .map(|(k, _)| k) - .collect() - } +} + +impl Watcher { + pub fn relatives(&self, url: &Url) -> BTreeSet { + let mut map = BTreeSet::from_iter([url.clone()]); + + let linked = self.linked.read(); + if let Some(to) = linked.get(url) { + map.extend(linked.iter().filter(|(_, v)| *v == to).map(|(k, _)| k).cloned()); + } + + map + } } diff --git a/yazi-core/src/tab/commands/reveal.rs b/yazi-core/src/tab/commands/reveal.rs index 129c67a6..f75832a2 100644 --- a/yazi-core/src/tab/commands/reveal.rs +++ b/yazi-core/src/tab/commands/reveal.rs @@ -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}; @@ -34,10 +34,7 @@ impl Tab { }; let b = self.cd(parent.clone()); - emit!(Files(FilesOp::Creating( - parent.clone(), - File::from_dummy(opt.target.clone()).into_map() - ))); + FilesOp::Creating(parent, vec![File::from_dummy(opt.target.clone())]).emit(); Manager::_hover(Some(opt.target)); b } diff --git a/yazi-core/src/tab/commands/search.rs b/yazi-core/src/tab/commands/search.rs index fa7fe25d..15d40439 100644 --- a/yazi-core/src/tab/commands/search.rs +++ b/yazi-core/src/tab/commands/search.rs @@ -5,7 +5,7 @@ use tokio::pin; use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; use yazi_config::popup::InputCfg; 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}; @@ -66,7 +66,7 @@ impl Tab { Tab::_cd(&cwd); first = false; } - emit!(Files(FilesOp::Part(cwd.clone(), ticket, chunk))); + FilesOp::Part(cwd.clone(), chunk, ticket).emit(); } Ok(()) })); diff --git a/yazi-core/src/tab/finder.rs b/yazi-core/src/tab/finder.rs index f0462469..96ea9c19 100644 --- a/yazi-core/src/tab/finder.rs +++ b/yazi-core/src/tab/finder.rs @@ -53,7 +53,7 @@ impl Finder { } pub(super) fn catchup(&mut self, files: &Files) -> bool { - if self.version == files.version { + if self.version == files.revision { return false; } self.matched.clear(); @@ -72,7 +72,7 @@ impl Finder { i += 1; } - self.version = files.version; + self.version = files.revision; true } diff --git a/yazi-fm/src/app/app.rs b/yazi-fm/src/app/app.rs index 6e2d7e01..e2ab7910 100644 --- a/yazi-fm/src/app/app.rs +++ b/yazi-fm/src/app/app.rs @@ -5,7 +5,7 @@ use crossterm::event::KeyEvent; use ratatui::backend::Backend; use yazi_config::{keymap::Key, ARGS}; 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}; @@ -132,19 +132,6 @@ impl App { let manager = &mut self.cx.manager; let tasks = &mut self.cx.tasks; 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) => { let targets = self.cx.manager.current().paginate(page); tasks.preload_paged(targets, &self.cx.manager.mimetype); diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index dbabe75a..0658e78c 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -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, hover); on!(MANAGER, peek); diff --git a/yazi-plugin/preset/plugins/archive.lua b/yazi-plugin/preset/plugins/archive.lua index 1b93ab19..298c39f3 100644 --- a/yazi-plugin/preset/plugins/archive.lua +++ b/yazi-plugin/preset/plugins/archive.lua @@ -1,13 +1,13 @@ -local Archive = {} +local M = {} -function Archive:peek() +function M:peek() local _, bound = ya.preview_archive(self) if bound then ya.manager_emit("peek", { tostring(bound), only_if = tostring(self.file.url), upper_bound = "" }) end end -function Archive:seek(units) +function M:seek(units) local h = cx.active.current.hovered if h and h.url == self.file.url then local step = math.floor(units * self.area.h / 10) @@ -18,4 +18,4 @@ function Archive:seek(units) end end -return Archive +return M diff --git a/yazi-plugin/preset/plugins/code.lua b/yazi-plugin/preset/plugins/code.lua index a649a60d..cba031e6 100644 --- a/yazi-plugin/preset/plugins/code.lua +++ b/yazi-plugin/preset/plugins/code.lua @@ -1,13 +1,13 @@ -local Code = {} +local M = {} -function Code:peek() +function M:peek() local _, bound = ya.preview_code(self) if bound then ya.manager_emit("peek", { tostring(bound), only_if = tostring(self.file.url), upper_bound = "" }) end end -function Code:seek(units) +function M:seek(units) local h = cx.active.current.hovered if h and h.url == self.file.url then local step = math.floor(units * self.area.h / 10) @@ -18,4 +18,4 @@ function Code:seek(units) end end -return Code +return M diff --git a/yazi-plugin/preset/plugins/folder.lua b/yazi-plugin/preset/plugins/folder.lua index 40762a9a..6ebf14aa 100644 --- a/yazi-plugin/preset/plugins/folder.lua +++ b/yazi-plugin/preset/plugins/folder.lua @@ -1,6 +1,6 @@ -local Folder_ = {} +local M = {} -function Folder_:peek() +function M:peek() local folder = Folder:by_kind(Folder.PREVIEW) if folder == nil or folder.cwd ~= self.file.url then return {} @@ -24,7 +24,7 @@ function Folder_:peek() ya.preview_widgets(self, { ui.List(self.area, items) }) end -function Folder_:seek(units) +function M:seek(units) local folder = Folder:by_kind(Folder.PREVIEW) if folder and folder.cwd == self.file.url then local step = math.floor(units * self.area.h / 10) @@ -36,4 +36,4 @@ function Folder_:seek(units) end end -return Folder_ +return M diff --git a/yazi-plugin/preset/plugins/image.lua b/yazi-plugin/preset/plugins/image.lua index eeef648f..c787acd8 100644 --- a/yazi-plugin/preset/plugins/image.lua +++ b/yazi-plugin/preset/plugins/image.lua @@ -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() ya.image_show(fs.symlink_metadata(cache) and cache or self.file.url, self.area) ya.preview_widgets(self, {}) end -function Image:seek() end +function M:seek() end -function Image:preload() +function M:preload() local cache = self:cache() if fs.symlink_metadata(cache) then return 1 @@ -19,4 +19,4 @@ function Image:preload() return ya.image_precache(self.file.url, cache) and 1 or 2 end -return Image +return M diff --git a/yazi-plugin/preset/plugins/json.lua b/yazi-plugin/preset/plugins/json.lua index 2ddfdb28..c6ec9c59 100644 --- a/yazi-plugin/preset/plugins/json.lua +++ b/yazi-plugin/preset/plugins/json.lua @@ -1,6 +1,6 @@ -local Json = {} +local M = {} -function Json:peek() +function M:peek() local limit = self.area.h local child = Command.new("jq") :args({ @@ -35,7 +35,7 @@ function Json:peek() end end -function Json:seek(units) +function M:seek(units) local h = cx.active.current.hovered if h and h.url == self.file.url then local step = math.floor(units * self.area.h / 10) @@ -46,4 +46,4 @@ function Json:seek(units) end end -return Json +return M diff --git a/yazi-plugin/preset/plugins/mime.lua b/yazi-plugin/preset/plugins/mime.lua index ed88a3ef..d7420bf9 100644 --- a/yazi-plugin/preset/plugins/mime.lua +++ b/yazi-plugin/preset/plugins/mime.lua @@ -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) if ya.target_family() == "windows" then command:arg("-b") @@ -13,23 +13,23 @@ function Mime:preload() urls[#urls + 1] = tostring(file.url) end - local i, data = 1, {} + local i, mimes = 1, {} local output = command:args(urls):output() for line in output.stdout:gmatch("[^\r\n]+") do if i > #urls then break end if ya.mime_valid(line) then - data[urls[i]] = line + mimes[urls[i]] = line end i = i + 1 end - if #data then - ya.manager_emit("update_mimetype", {}, data) + if #mimes then + ya.manager_emit("update_mimetype", {}, mimes) return 3 end return 2 end -return Mime +return M diff --git a/yazi-plugin/preset/plugins/noop.lua b/yazi-plugin/preset/plugins/noop.lua index 7c114a96..ad93ad7f 100644 --- a/yazi-plugin/preset/plugins/noop.lua +++ b/yazi-plugin/preset/plugins/noop.lua @@ -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 diff --git a/yazi-plugin/preset/plugins/pdf.lua b/yazi-plugin/preset/plugins/pdf.lua index 2c2bacf6..fd817395 100644 --- a/yazi-plugin/preset/plugins/pdf.lua +++ b/yazi-plugin/preset/plugins/pdf.lua @@ -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 ya.image_show(self:cache(), self.area) ya.preview_widgets(self, {}) end end -function Pdf:seek(units) +function M:seek(units) local h = cx.active.current.hovered if h and h.url == self.file.url then local step = ya.clamp(-1, units, 1) @@ -17,7 +17,7 @@ function Pdf:seek(units) end end -function Pdf:preload() +function M:preload() local cache = self:cache() if fs.symlink_metadata(cache) then return 1 @@ -40,4 +40,4 @@ function Pdf:preload() return fs.write(cache, output.stdout) and 1 or 2 end -return Pdf +return M diff --git a/yazi-plugin/preset/plugins/video.lua b/yazi-plugin/preset/plugins/video.lua index 2fe4523d..a5a41073 100644 --- a/yazi-plugin/preset/plugins/video.lua +++ b/yazi-plugin/preset/plugins/video.lua @@ -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 ya.image_show(self:cache(), self.area) ya.preview_widgets(self, {}) end end -function Video:seek(units) +function M:seek(units) local h = cx.active.current.hovered if h and h.url == self.file.url then ya.manager_emit("peek", { @@ -19,7 +19,7 @@ function Video:seek(units) end end -function Video:preload() +function M:preload() local percentage = 5 + self.skip if percentage > 95 then 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 end -return Video +return M diff --git a/yazi-plugin/src/config.rs b/yazi-plugin/src/config.rs index 4b1a4a83..0571af5d 100644 --- a/yazi-plugin/src/config.rs +++ b/yazi-plugin/src/config.rs @@ -1,5 +1,5 @@ use mlua::{Lua, LuaSerdeExt, SerializeOptions}; -use yazi_config::{MANAGER, PREVIEW, THEME}; +use yazi_config::{BOOT, MANAGER, PREVIEW, THEME}; const OPTIONS: SerializeOptions = SerializeOptions::new().serialize_none_to_null(false).serialize_unit_to_null(false); @@ -11,6 +11,16 @@ pub struct Config<'a> { impl<'a> Config<'a> { pub fn new(lua: &'a Lua) -> Self { Self { lua } } + pub fn install_boot(self) -> mlua::Result { + self.lua.globals().set("BOOT", self.lua.to_value_with(&*BOOT, OPTIONS)?)?; + Ok(self) + } + + pub fn install_manager(self) -> mlua::Result { + self.lua.globals().set("MANAGER", self.lua.to_value_with(&*MANAGER, OPTIONS)?)?; + Ok(self) + } + pub fn install_theme(self) -> mlua::Result { self.lua.globals().set("THEME", self.lua.to_value_with(&*THEME, OPTIONS)?)?; Ok(self) @@ -20,9 +30,4 @@ impl<'a> Config<'a> { self.lua.globals().set("PREVIEW", self.lua.to_value_with(&*PREVIEW, OPTIONS)?)?; Ok(self) } - - pub fn install_manager(self) -> mlua::Result { - self.lua.globals().set("MANAGER", self.lua.to_value_with(&*MANAGER, OPTIONS)?)?; - Ok(self) - } } diff --git a/yazi-plugin/src/external/file.rs b/yazi-plugin/src/external/file.rs deleted file mode 100644 index 2fcc08c9..00000000 --- a/yazi-plugin/src/external/file.rs +++ /dev/null @@ -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> { - 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]) -> Result> { - _file(&files.iter().map(AsRef::as_ref).collect::>()).await -} diff --git a/yazi-plugin/src/external/mod.rs b/yazi-plugin/src/external/mod.rs index c57de1c6..92cb58b1 100644 --- a/yazi-plugin/src/external/mod.rs +++ b/yazi-plugin/src/external/mod.rs @@ -1,5 +1,4 @@ mod fd; -mod file; mod fzf; mod highlighter; mod lsar; @@ -8,7 +7,6 @@ mod shell; mod zoxide; pub use fd::*; -pub use file::*; pub use fzf::*; pub use highlighter::*; pub use lsar::*; diff --git a/yazi-plugin/src/loader.rs b/yazi-plugin/src/loader.rs index fe4b7aa2..acb7f927 100644 --- a/yazi-plugin/src/loader.rs +++ b/yazi-plugin/src/loader.rs @@ -22,8 +22,8 @@ impl Loader { return Ok(()); } - let b = fs::read(BOOT.plugin_dir.join(name)).await.map(|v| v.into()).unwrap_or(Cow::from( - match name { + let b = fs::read(BOOT.plugin_dir.join(name)).await.map(|v| v.into()).or_else(|_| { + Ok(Cow::from(match name { "noop.lua" => include_bytes!("../preset/plugins/noop.lua") as &[u8], "archive.lua" => include_bytes!("../preset/plugins/archive.lua"), "code.lua" => include_bytes!("../preset/plugins/code.lua"), @@ -33,9 +33,9 @@ impl Loader { "mime.lua" => include_bytes!("../preset/plugins/mime.lua"), "pdf.lua" => include_bytes!("../preset/plugins/pdf.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()); Ok(()) diff --git a/yazi-plugin/src/plugin.rs b/yazi-plugin/src/plugin.rs index 511b8c91..5d1676c5 100644 --- a/yazi-plugin/src/plugin.rs +++ b/yazi-plugin/src/plugin.rs @@ -1,5 +1,6 @@ use anyhow::Result; -use mlua::{Lua, Table}; +use mlua::Lua; +use yazi_config::BOOT; use yazi_shared::RoCell; pub static LUA: RoCell = RoCell::new(); @@ -7,7 +8,7 @@ pub static LUA: RoCell = RoCell::new(); pub fn init() { fn stage_1(lua: &Lua) -> Result<()> { 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)?; // Base @@ -29,14 +30,15 @@ pub fn init() { } fn stage_2(lua: &Lua) { - let ya: Table = lua.globals().get("ya").unwrap(); - ya.set("SYNC_ON", true).unwrap(); + let setup = br#" +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 - // PLUGIN.preload.iter().for_each(|p| { - // 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:?}")); }); + if let Ok(b) = std::fs::read(BOOT.config_dir.join("init.lua")) { + lua.load(b).exec().unwrap(); + } } let lua = Lua::new(); diff --git a/yazi-scheduler/src/workers/preload.rs b/yazi-scheduler/src/workers/preload.rs index 7c6c6575..610d0eca 100644 --- a/yazi-scheduler/src/workers/preload.rs +++ b/yazi-scheduler/src/workers/preload.rs @@ -4,7 +4,7 @@ use anyhow::Result; use parking_lot::RwLock; use tokio::sync::mpsc; 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; @@ -74,7 +74,7 @@ impl Preload { } 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))?; diff --git a/yazi-shared/src/event/event.rs b/yazi-shared/src/event/event.rs index 6ccca1f5..8df486c4 100644 --- a/yazi-shared/src/event/event.rs +++ b/yazi-shared/src/event/event.rs @@ -2,7 +2,7 @@ use crossterm::event::KeyEvent; use tokio::sync::{mpsc::UnboundedSender, oneshot}; use super::Exec; -use crate::{fs::FilesOp, term::Term, Layer, RoCell}; +use crate::{term::Term, Layer, RoCell}; static TX: RoCell> = RoCell::new(); @@ -15,7 +15,6 @@ pub enum Event { Call(Vec, Layer), // Manager - Files(FilesOp), Pages(usize), } @@ -44,9 +43,6 @@ macro_rules! emit { $crate::event::Event::Call($exec, $layer).emit(); }; - (Files($op:expr)) => { - $crate::event::Event::Files($op).emit(); - }; (Pages($page:expr)) => { $crate::event::Event::Pages($page).emit(); }; diff --git a/yazi-shared/src/fs/file.rs b/yazi-shared/src/fs/file.rs index 4210e8c5..3b9c7e4e 100644 --- a/yazi-shared/src/fs/file.rs +++ b/yazi-shared/src/fs/file.rs @@ -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 tokio::fs; @@ -52,13 +52,6 @@ impl File { let ck = if url.is_hidden() { ChaKind::HIDDEN } else { ChaKind::empty() }; Self { url, cha: Cha::default().with_kind(ck), link_to: None } } - - #[inline] - pub fn into_map(self) -> BTreeMap { - let mut map = BTreeMap::new(); - map.insert(self.url.clone(), self); - map - } } impl File { diff --git a/yazi-shared/src/fs/op.rs b/yazi-shared/src/fs/op.rs index db245387..19015b2b 100644 --- a/yazi-shared/src/fs/op.rs +++ b/yazi-shared/src/fs/op.rs @@ -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 crate::{emit, fs::Url}; +use crate::{emit, event::Exec, fs::Url, Layer}; pub static FILES_TICKET: AtomicU64 = AtomicU64::new(0); #[derive(Debug)] pub enum FilesOp { Full(Url, Vec), - Part(Url, u64, Vec), + Part(Url, Vec, u64), Size(Url, BTreeMap), IOErr(Url), - Creating(Url, BTreeMap), - Deleting(Url, BTreeSet), - Replacing(Url, BTreeMap), + Creating(Url, Vec), + Deleting(Url, Vec), + Updating(Url, BTreeMap), + Upserting(Url, BTreeMap), } impl FilesOp { @@ -28,14 +29,41 @@ impl FilesOp { Self::Creating(url, _) => url, Self::Deleting(url, _) => url, - Self::Replacing(url, _) => url, + Self::Updating(url, _) => url, + Self::Upserting(url, _) => url, } } #[inline] + pub fn emit(self) { + emit!(Call(Exec::call("update_files", vec![]).with_data(self).vec(), Layer::Manager)); + } + pub fn prepare(url: &Url) -> u64 { 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 } + + pub fn chroot(&self, new: Url) -> Self { + todo!() + // Full(Url, Vec), + // Part(Url, u64, Vec), + // Size(Url, BTreeMap), + // IOErr(Url), + + // Creating(Url, BTreeMap), + // Deleting(Url, BTreeSet), + // Replacing(Url, BTreeMap), + + // 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!(), + // } + } }