diff --git a/Cargo.lock b/Cargo.lock index fede7822..2bc18dec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -348,6 +348,8 @@ dependencies = [ "crossterm 0.27.0", "futures", "glob", + "indexmap 2.0.0", + "md-5", "once_cell", "ratatui", "regex", diff --git a/adaptor/src/adaptor.rs b/adaptor/src/adaptor.rs index 1604fdae..621aba7f 100644 --- a/adaptor/src/adaptor.rs +++ b/adaptor/src/adaptor.rs @@ -1,13 +1,13 @@ use std::{path::{Path, PathBuf}, sync::atomic::{AtomicBool, Ordering}}; use anyhow::Result; -use config::{preview::PreviewAdaptor, PREVIEW}; +use config::{preview::PreviewAdaptor, BOOT, PREVIEW}; use once_cell::sync::Lazy; use ratatui::prelude::Rect; use tokio::{fs, sync::mpsc::UnboundedSender}; use super::{Iterm2, Kitty, Ueberzug}; -use crate::{Image, Sixel}; +use crate::Sixel; static IMAGE_SHOWN: AtomicBool = AtomicBool::new(false); @@ -25,7 +25,7 @@ impl Adaptor { Self::image_hide(rect); } - let cache = Image::cache(path); + let cache = BOOT.cache(path); if fs::metadata(&cache).await.is_ok() { path = cache.as_path(); } diff --git a/adaptor/src/image.rs b/adaptor/src/image.rs index b1d6cbc3..ee410126 100644 --- a/adaptor/src/image.rs +++ b/adaptor/src/image.rs @@ -1,9 +1,8 @@ -use std::{path::{Path, PathBuf}, sync::Arc}; +use std::{path::Path, sync::Arc}; use anyhow::Result; -use config::{BOOT, PREVIEW}; +use config::PREVIEW; use image::{imageops::FilterType, DynamicImage, ImageFormat}; -use md5::{Digest, Md5}; use shared::tty_ratio; use tokio::fs; @@ -54,11 +53,4 @@ impl Image { _ => fs::write(cache, &*img).await?, }) } - - #[inline] - pub fn cache(path: &Path) -> PathBuf { - BOOT - .cache_dir - .join(format!("{:x}", Md5::new_with_prefix(path.to_string_lossy().as_bytes()).finalize())) - } } diff --git a/config/Cargo.toml b/config/Cargo.toml index b3b1c65b..7d279f30 100644 --- a/config/Cargo.toml +++ b/config/Cargo.toml @@ -12,6 +12,8 @@ clap = { version = "^4", features = [ "derive" ] } crossterm = "^0" futures = "^0" glob = "^0" +indexmap = "^2" +md-5 = "^0" once_cell = "^1" ratatui = "^0" regex = "^1" diff --git a/config/src/boot/boot.rs b/config/src/boot/boot.rs index dc41c5d0..57bcf326 100644 --- a/config/src/boot/boot.rs +++ b/config/src/boot/boot.rs @@ -1,6 +1,7 @@ -use std::{env, fs, path::PathBuf}; +use std::{env, fs, path::{Path, PathBuf}, time::{self, SystemTime}}; use clap::Parser; +use md5::{Digest, Md5}; #[derive(Debug)] pub struct Boot { @@ -37,3 +38,18 @@ impl Default for Boot { boot } } + +impl Boot { + #[inline] + pub fn cache(&self, path: &Path) -> PathBuf { + self + .cache_dir + .join(format!("{:x}", Md5::new_with_prefix(path.to_string_lossy().as_bytes()).finalize())) + } + + #[inline] + pub fn tmpfile(&self) -> PathBuf { + let nanos = SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_nanos(); + self.cache_dir.join(format!("{:x}", Md5::new_with_prefix(nanos.to_le_bytes()).finalize())) + } +} diff --git a/config/src/open/open.rs b/config/src/open/open.rs index f5280cec..8575301c 100644 --- a/config/src/open/open.rs +++ b/config/src/open/open.rs @@ -1,5 +1,6 @@ -use std::{collections::{BTreeMap, BTreeSet}, path::Path}; +use std::{collections::BTreeMap, path::Path}; +use indexmap::IndexSet; use serde::{Deserialize, Deserializer}; use shared::MIME_DIR; @@ -8,7 +9,7 @@ use crate::{Pattern, MERGED_YAZI}; #[derive(Debug)] pub struct Open { - openers: BTreeMap>, + openers: BTreeMap>, rules: Vec, } @@ -25,26 +26,36 @@ impl Default for Open { } impl Open { - pub fn openers(&self, path: P, mime: M) -> Option> + pub fn openers(&self, path: P, mime: M) -> Option<&IndexSet> where P: AsRef, M: AsRef, { self.rules.iter().find_map(|rule| { - if rule.name.as_ref().map_or(false, |e| e.match_path(&path, Some(mime.as_ref() == MIME_DIR))) - || rule.mime.as_ref().map_or(false, |m| m.matches(&mime)) + let is_folder = Some(mime.as_ref() == MIME_DIR); + if rule.mime.as_ref().map_or(false, |m| m.matches(&mime)) + || rule.name.as_ref().map_or(false, |n| n.match_path(&path, is_folder)) { - self.openers.get(&rule.use_).map(|v| v.iter().collect()) + self.openers.get(&rule.use_) } else { None } }) } + #[inline] + pub fn block_opener(&self, path: P, mime: M) -> Option<&Opener> + where + P: AsRef, + M: AsRef, + { + self.openers(path, mime).and_then(|o| o.iter().find(|o| o.block)) + } + pub fn common_openers(&self, targets: &[(impl AsRef, impl AsRef)]) -> Vec<&Opener> { let grouped = targets.iter().filter_map(|(p, m)| self.openers(p, m)).collect::>(); - let flat = grouped.iter().flatten().cloned().collect::>(); - flat.into_iter().filter(|o| grouped.iter().all(|g| g.contains(o))).collect() + let flat = grouped.iter().flat_map(|&g| g).collect::>(); + flat.into_iter().filter(|&o| grouped.iter().all(|g| g.contains(o))).collect() } } @@ -64,6 +75,7 @@ impl<'de> Deserialize<'de> for Open { } let outer = Outer::deserialize(deserializer)?; - Ok(Self { openers: outer.opener, rules: outer.open.rules }) + let openers = outer.opener.into_iter().map(|(k, v)| (k, IndexSet::from_iter(v))).collect(); + Ok(Self { openers, rules: outer.open.rules }) } } diff --git a/core/src/lib.rs b/core/src/lib.rs index d2b49c8b..35f0aa5b 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -2,7 +2,8 @@ clippy::if_same_then_else, clippy::len_without_is_empty, clippy::module_inception, - clippy::option_map_unit_fn + clippy::option_map_unit_fn, + clippy::unit_arg )] mod blocker; diff --git a/core/src/manager/preview.rs b/core/src/manager/preview.rs index 1d152180..7f3313d0 100644 --- a/core/src/manager/preview.rs +++ b/core/src/manager/preview.rs @@ -1,8 +1,8 @@ use std::{io::BufRead, mem, path::{Path, PathBuf}, sync::{atomic::{AtomicUsize, Ordering}, Arc}}; -use adaptor::{Adaptor, Image}; +use adaptor::Adaptor; use anyhow::{anyhow, bail, Result}; -use config::PREVIEW; +use config::{BOOT, PREVIEW}; use ratatui::prelude::Rect; use shared::{tty_size, MimeKind}; use syntect::{easy::HighlightFile, util::as_24_bit_terminal_escaped}; @@ -113,7 +113,7 @@ impl Preview { } pub async fn video(path: &Path) -> Result { - let cache = Image::cache(path); + let cache = BOOT.cache(path); if fs::metadata(&cache).await.is_err() { external::ffmpegthumbnailer(path, &cache).await?; } @@ -122,7 +122,7 @@ impl Preview { } pub async fn pdf(path: &Path) -> Result { - let cache = Image::cache(path); + let cache = BOOT.cache(path); if fs::metadata(&cache).await.is_err() { external::pdftoppm(path, &cache).await?; } diff --git a/core/src/tasks/tasks.rs b/core/src/tasks/tasks.rs index d526c222..6e3ac6b5 100644 --- a/core/src/tasks/tasks.rs +++ b/core/src/tasks/tasks.rs @@ -2,12 +2,12 @@ use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, io::{stdout, Wr use config::{manager::SortBy, open::Opener, OPEN}; use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; -use shared::{tty_size, MimeKind}; +use shared::{tty_size, Defer, MimeKind}; use tokio::{io::AsyncReadExt, select, sync::mpsc, time}; use tracing::trace; use super::{task::TaskSummary, Scheduler, TASKS_PADDING, TASKS_PERCENT}; -use crate::{emit, files::{File, Files}, input::InputOpt, BLOCKER}; +use crate::{emit, files::{File, Files}, input::InputOpt, Event, BLOCKER}; pub struct Tasks { scheduler: Arc, @@ -80,6 +80,11 @@ impl Tasks { }; emit!(Stop(true)).await; + let _defer = Defer::new(|| { + disable_raw_mode().ok(); + Event::Stop(false, None).emit() + }); + stdout().write_all("\n".repeat(tty_size().ws_row as usize).as_bytes()).ok(); stdout().write_all(buffered.as_bytes()).ok(); enable_raw_mode().ok(); @@ -112,9 +117,6 @@ impl Tasks { while quit[0] != b'q' { stdin.read(&mut quit).await.ok(); } - - disable_raw_mode().ok(); - emit!(Stop(false)).await; }); false } @@ -132,7 +134,7 @@ impl Tasks { pub fn file_open(&self, targets: &[(impl AsRef, impl AsRef)]) -> bool { let mut openers = BTreeMap::new(); for (path, mime) in targets { - if let Some(opener) = OPEN.openers(path, mime).and_then(|o| o.first().cloned()) { + if let Some(opener) = OPEN.openers(path, mime).and_then(|o| o.first()) { openers.entry(opener).or_insert_with(Vec::new).push(path.as_ref().as_os_str()); } } diff --git a/core/src/tasks/workers/precache.rs b/core/src/tasks/workers/precache.rs index 30951710..f38e6e42 100644 --- a/core/src/tasks/workers/precache.rs +++ b/core/src/tasks/workers/precache.rs @@ -2,6 +2,7 @@ use std::{collections::{BTreeMap, BTreeSet}, path::PathBuf, sync::Arc}; use adaptor::Image; use anyhow::Result; +use config::BOOT; use parking_lot::Mutex; use shared::{calculate_size, Throttle}; use tokio::{fs, sync::mpsc}; @@ -73,7 +74,7 @@ impl Precache { pub(crate) async fn work(&self, task: &mut PrecacheOp) -> Result<()> { match task { PrecacheOp::Image(task) => { - let cache = Image::cache(&task.target); + let cache = BOOT.cache(&task.target); if fs::metadata(&cache).await.is_ok() { return Ok(self.sch.send(TaskOp::Adv(task.id, 1, 0))?); } @@ -83,7 +84,7 @@ impl Precache { self.sch.send(TaskOp::Adv(task.id, 1, 0))?; } PrecacheOp::Video(task) => { - let cache = Image::cache(&task.target); + let cache = BOOT.cache(&task.target); if fs::metadata(&cache).await.is_ok() { return Ok(self.sch.send(TaskOp::Adv(task.id, 1, 0))?); } @@ -92,7 +93,7 @@ impl Precache { self.sch.send(TaskOp::Adv(task.id, 1, 0))?; } PrecacheOp::Pdf(task) => { - let cache = Image::cache(&task.target); + let cache = BOOT.cache(&task.target); if fs::metadata(&cache).await.is_ok() { return Ok(self.sch.send(TaskOp::Adv(task.id, 1, 0))?); } diff --git a/cspell.json b/cspell.json index cb823e74..01207a52 100644 --- a/cspell.json +++ b/cspell.json @@ -1 +1 @@ -{"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","nocapture"],"version":"0.2"} +{"language":"en","version":"0.2","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos"]}