mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
refactor: use Url instead of PathBuf (#107)
This commit is contained in:
parent
a2ecc9fb70
commit
9c3d5cc400
32 changed files with 532 additions and 416 deletions
|
|
@ -4,7 +4,7 @@ use std::ffi::OsString;
|
|||
use anyhow::{Ok, Result};
|
||||
use config::{keymap::{Control, Key, KeymapLayer}, BOOT};
|
||||
use crossterm::event::KeyEvent;
|
||||
use shared::{absolute_path, Term};
|
||||
use shared::{absolute_url, Term};
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use crate::{Ctx, Executor, Logs, Root, Signals};
|
||||
|
|
@ -119,26 +119,25 @@ impl App {
|
|||
let manager = &mut self.cx.manager;
|
||||
let tasks = &mut self.cx.tasks;
|
||||
match event {
|
||||
Event::Cd(path) => {
|
||||
Event::Cd(url) => {
|
||||
futures::executor::block_on(async {
|
||||
manager.active_mut().cd(absolute_path(path)).await;
|
||||
manager.active_mut().cd(absolute_url(url)).await;
|
||||
});
|
||||
}
|
||||
Event::Refresh => {
|
||||
manager.refresh();
|
||||
}
|
||||
Event::Files(op) => {
|
||||
let calc = matches!(op, FilesOp::Read(..) | FilesOp::Search(..));
|
||||
let read = matches!(op, FilesOp::Read(..));
|
||||
let b = match op {
|
||||
FilesOp::Read(..) => manager.update_read(op),
|
||||
FilesOp::Size(..) => manager.update_read(op),
|
||||
FilesOp::Search(..) => manager.update_search(op),
|
||||
FilesOp::IOErr(..) => manager.update_ioerr(op),
|
||||
};
|
||||
if b {
|
||||
emit!(Render);
|
||||
}
|
||||
if calc {
|
||||
if read {
|
||||
tasks.precache_size(&manager.current().files);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ impl Ctx {
|
|||
}
|
||||
Position::Hovered(rect @ Rect { mut x, y, width, height }) => {
|
||||
let Some(r) =
|
||||
self.manager.hovered().and_then(|h| self.manager.current().rect_current(h.path()))
|
||||
self.manager.hovered().and_then(|h| self.manager.current().rect_current(h.url()))
|
||||
else {
|
||||
return self.area(&Position::Top(*rect));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use core::{emit, files::FilesSorter, input::InputMode};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use config::{keymap::{Control, Exec, Key, KeymapLayer}, manager::SortBy, KEYMAP};
|
||||
use shared::optional_bool;
|
||||
use shared::{optional_bool, Url};
|
||||
|
||||
use super::Ctx;
|
||||
|
||||
|
|
@ -74,11 +73,11 @@ impl Executor {
|
|||
"back" => cx.manager.active_mut().back(),
|
||||
"forward" => cx.manager.active_mut().forward(),
|
||||
"cd" => {
|
||||
let path = exec.args.get(0).map(PathBuf::from).unwrap_or_default();
|
||||
let url = exec.args.get(0).map(Url::from).unwrap_or_default();
|
||||
if exec.named.contains_key("interactive") {
|
||||
cx.manager.active_mut().cd_interactive(path)
|
||||
cx.manager.active_mut().cd_interactive(url)
|
||||
} else {
|
||||
emit!(Cd(path));
|
||||
emit!(Cd(url));
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
@ -109,7 +108,7 @@ impl Executor {
|
|||
}
|
||||
}
|
||||
"remove" => {
|
||||
let targets = cx.manager.selected().into_iter().map(|f| f.path_owned()).collect();
|
||||
let targets = cx.manager.selected().into_iter().map(|f| f.url_owned()).collect();
|
||||
cx.tasks.file_remove(targets, exec.named.contains_key("permanently"))
|
||||
}
|
||||
"create" => cx.manager.create(),
|
||||
|
|
@ -155,7 +154,7 @@ impl Executor {
|
|||
let path = if exec.named.contains_key("current") {
|
||||
cx.manager.cwd().to_owned()
|
||||
} else {
|
||||
exec.args.get(0).map(|p| p.into()).unwrap_or("/".into())
|
||||
exec.args.get(0).map(Url::from).unwrap_or_else(|| Url::from("/"))
|
||||
};
|
||||
cx.manager.tabs_mut().create(&path)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ impl<'a> Widget for Layout<'a> {
|
|||
.split(area);
|
||||
|
||||
let current = &self.cx.manager.current();
|
||||
let location = if current.in_search {
|
||||
let location = if current.cwd.is_search() {
|
||||
format!("{} (search)", readable_home(¤t.cwd))
|
||||
} else {
|
||||
readable_home(¤t.cwd)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ impl<'a> Folder<'a> {
|
|||
THEME
|
||||
.filetypes
|
||||
.iter()
|
||||
.find(|x| x.matches(file.path(), mimetype.get(file.path()), file.is_dir()))
|
||||
.find(|x| x.matches(file.url(), mimetype.get(file.url()), file.is_dir()))
|
||||
.map(|x| x.style.get())
|
||||
.unwrap_or_else(Style::new)
|
||||
}
|
||||
|
|
@ -60,11 +60,11 @@ impl<'a> Widget for Folder<'a> {
|
|||
let icon = THEME
|
||||
.icons
|
||||
.iter()
|
||||
.find(|x| x.name.match_path(f.path(), Some(f.is_dir())))
|
||||
.find(|x| x.name.match_path(f.url(), Some(f.is_dir())))
|
||||
.map(|x| x.display.as_ref())
|
||||
.unwrap_or("");
|
||||
|
||||
let is_selected = self.folder.files.is_selected(f.path());
|
||||
let is_selected = self.folder.files.is_selected(f.url());
|
||||
if (!self.is_selection && is_selected)
|
||||
|| (self.is_selection && mode.pending(self.folder.offset() + i, is_selected))
|
||||
{
|
||||
|
|
@ -78,7 +78,7 @@ impl<'a> Widget for Folder<'a> {
|
|||
);
|
||||
}
|
||||
|
||||
let hovered = matches!(self.folder.hovered, Some(ref h) if h.path() == f.path());
|
||||
let hovered = matches!(self.folder.hovered, Some(ref h) if h.url() == f.url());
|
||||
let style = if self.is_preview && hovered {
|
||||
THEME.preview.hovered.get()
|
||||
} else if hovered {
|
||||
|
|
@ -87,7 +87,7 @@ impl<'a> Widget for Folder<'a> {
|
|||
self.file_style(f)
|
||||
};
|
||||
|
||||
let mut path = format!(" {icon} {}", readable_path(f.path(), &self.folder.cwd));
|
||||
let mut path = format!(" {icon} {}", readable_path(f.url(), &self.folder.cwd));
|
||||
if let Some(link_to) = f.link_to() {
|
||||
if MANAGER.show_symlink {
|
||||
path.push_str(&format!(" -> {}", link_to.display()));
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ impl<'a> Preview<'a> {
|
|||
impl<'a> Widget for Preview<'a> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let manager = &self.cx.manager;
|
||||
let Some(hovered) = manager.hovered().map(|h| h.path()) else {
|
||||
let Some(hovered) = manager.hovered().map(|h| h.url()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use std::{collections::BTreeMap, ffi::OsString, path::PathBuf};
|
||||
use std::{collections::BTreeMap, ffi::OsString};
|
||||
|
||||
use anyhow::Result;
|
||||
use config::{keymap::{Control, KeymapLayer}, open::Opener};
|
||||
use crossterm::event::KeyEvent;
|
||||
use shared::RoCell;
|
||||
use shared::{RoCell, Url};
|
||||
use tokio::sync::{mpsc::UnboundedSender, oneshot};
|
||||
|
||||
use super::{files::{File, FilesOp}, input::InputOpt, select::SelectOpt};
|
||||
|
|
@ -21,13 +21,13 @@ pub enum Event {
|
|||
Ctrl(Control, KeymapLayer),
|
||||
|
||||
// Manager
|
||||
Cd(PathBuf),
|
||||
Cd(Url),
|
||||
Refresh,
|
||||
Files(FilesOp),
|
||||
Pages(usize),
|
||||
Mimetype(BTreeMap<PathBuf, String>),
|
||||
Mimetype(BTreeMap<Url, String>),
|
||||
Hover(Option<File>),
|
||||
Peek(usize, Option<PathBuf>),
|
||||
Peek(usize, Option<Url>),
|
||||
Preview(PreviewLock),
|
||||
|
||||
// Input
|
||||
|
|
@ -92,8 +92,8 @@ macro_rules! emit {
|
|||
(Peek) => {
|
||||
$crate::Event::Peek(0, None).emit();
|
||||
};
|
||||
(Peek($skip:expr, $path:expr)) => {
|
||||
$crate::Event::Peek($skip, Some($path)).emit();
|
||||
(Peek($skip:expr, $url:expr)) => {
|
||||
$crate::Event::Peek($skip, Some($url)).emit();
|
||||
};
|
||||
(Preview($lock:expr)) => {
|
||||
$crate::Event::Preview($lock).emit();
|
||||
|
|
|
|||
8
core/src/external/fd.rs
vendored
8
core/src/external/fd.rs
vendored
|
|
@ -1,18 +1,18 @@
|
|||
use std::{path::PathBuf, process::Stdio, time::Duration};
|
||||
use std::{process::Stdio, time::Duration};
|
||||
|
||||
use anyhow::Result;
|
||||
use shared::StreamBuf;
|
||||
use shared::{StreamBuf, Url};
|
||||
use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
pub struct FdOpt {
|
||||
pub cwd: PathBuf,
|
||||
pub cwd: Url,
|
||||
pub hidden: bool,
|
||||
pub glob: bool,
|
||||
pub subject: String,
|
||||
}
|
||||
|
||||
pub fn fd(opt: FdOpt) -> Result<StreamBuf<UnboundedReceiverStream<PathBuf>>> {
|
||||
pub fn fd(opt: FdOpt) -> Result<StreamBuf<UnboundedReceiverStream<Url>>> {
|
||||
let mut child = Command::new("fd")
|
||||
.arg("--base-directory")
|
||||
.arg(&opt.cwd)
|
||||
|
|
|
|||
14
core/src/external/file.rs
vendored
14
core/src/external/file.rs
vendored
|
|
@ -1,12 +1,12 @@
|
|||
use std::{collections::BTreeMap, ffi::OsStr, path::PathBuf};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use futures::TryFutureExt;
|
||||
use shared::MimeKind;
|
||||
use shared::{MimeKind, Url};
|
||||
use tokio::process::Command;
|
||||
use tracing::error;
|
||||
|
||||
pub async fn file(files: &[impl AsRef<OsStr>]) -> Result<BTreeMap<PathBuf, String>> {
|
||||
async fn _file(files: &[&Url]) -> Result<BTreeMap<Url, String>> {
|
||||
if files.is_empty() {
|
||||
bail!("no files to get mime types for");
|
||||
}
|
||||
|
|
@ -25,12 +25,16 @@ pub async fn file(files: &[impl AsRef<OsStr>]) -> Result<BTreeMap<PathBuf, Strin
|
|||
.iter()
|
||||
.zip(output.trim().lines())
|
||||
.filter(|(_, m)| MimeKind::valid(m))
|
||||
.map(|(f, m)| (f.as_ref().into(), m.to_string())),
|
||||
.map(|(&f, m)| (f.clone(), m.to_string())),
|
||||
);
|
||||
|
||||
if mimes.is_empty() {
|
||||
error!("failed to get mime types: {:?}", files.iter().map(AsRef::as_ref).collect::<Vec<_>>());
|
||||
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
|
||||
}
|
||||
|
|
|
|||
7
core/src/external/fzf.rs
vendored
7
core/src/external/fzf.rs
vendored
|
|
@ -1,13 +1,14 @@
|
|||
use std::{path::PathBuf, process::Stdio};
|
||||
use std::process::Stdio;
|
||||
|
||||
use anyhow::Result;
|
||||
use shared::Url;
|
||||
use tokio::{process::Command, sync::oneshot::{self, Receiver}};
|
||||
|
||||
pub struct FzfOpt {
|
||||
pub cwd: PathBuf,
|
||||
pub cwd: Url,
|
||||
}
|
||||
|
||||
pub fn fzf(opt: FzfOpt) -> Result<Receiver<Result<PathBuf>>> {
|
||||
pub fn fzf(opt: FzfOpt) -> Result<Receiver<Result<Url>>> {
|
||||
let child =
|
||||
Command::new("fzf").current_dir(&opt.cwd).kill_on_drop(true).stdout(Stdio::piped()).spawn()?;
|
||||
|
||||
|
|
|
|||
4
core/src/external/pdftoppm.rs
vendored
4
core/src/external/pdftoppm.rs
vendored
|
|
@ -1,4 +1,4 @@
|
|||
use std::path::Path;
|
||||
use std::{path::Path, sync::Arc};
|
||||
|
||||
use adaptor::Image;
|
||||
use regex::Regex;
|
||||
|
|
@ -25,5 +25,5 @@ pub async fn pdftoppm(src: &Path, dest: impl AsRef<Path>, skip: usize) -> Result
|
|||
return if pages > 0 { Err(PeekError::Exceed(pages - 1)) } else { Err(s.to_string().into()) };
|
||||
}
|
||||
|
||||
Ok(Image::precache_anyway(output.stdout.into(), dest).await?)
|
||||
Ok(Image::precache_anyway(Arc::new(output.stdout), dest).await?)
|
||||
}
|
||||
|
|
|
|||
8
core/src/external/rg.rs
vendored
8
core/src/external/rg.rs
vendored
|
|
@ -1,17 +1,17 @@
|
|||
use std::{path::PathBuf, process::Stdio, time::Duration};
|
||||
use std::{process::Stdio, time::Duration};
|
||||
|
||||
use anyhow::Result;
|
||||
use shared::StreamBuf;
|
||||
use shared::{StreamBuf, Url};
|
||||
use tokio::{io::{AsyncBufReadExt, BufReader}, process::Command, sync::mpsc};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
pub struct RgOpt {
|
||||
pub cwd: PathBuf,
|
||||
pub cwd: Url,
|
||||
pub hidden: bool,
|
||||
pub subject: String,
|
||||
}
|
||||
|
||||
pub fn rg(opt: RgOpt) -> Result<StreamBuf<UnboundedReceiverStream<PathBuf>>> {
|
||||
pub fn rg(opt: RgOpt) -> Result<StreamBuf<UnboundedReceiverStream<Url>>> {
|
||||
let mut child = Command::new("rg")
|
||||
.current_dir(&opt.cwd)
|
||||
.args(["--color=never", "--files-with-matches", "--smart-case"])
|
||||
|
|
|
|||
12
core/src/external/zoxide.rs
vendored
12
core/src/external/zoxide.rs
vendored
|
|
@ -1,26 +1,28 @@
|
|||
use std::{path::PathBuf, process::Stdio};
|
||||
use std::process::Stdio;
|
||||
|
||||
use anyhow::Result;
|
||||
use shared::Url;
|
||||
use tokio::{process::Command, sync::oneshot::{self, Receiver}};
|
||||
|
||||
pub struct ZoxideOpt {
|
||||
pub cwd: PathBuf,
|
||||
pub cwd: Url,
|
||||
}
|
||||
|
||||
pub fn zoxide(opt: ZoxideOpt) -> Result<Receiver<Result<PathBuf>>> {
|
||||
pub fn zoxide(opt: ZoxideOpt) -> Result<Receiver<Result<Url>>> {
|
||||
let child = Command::new("zoxide")
|
||||
.args(["query", "-i", "--exclude"])
|
||||
.arg(opt.cwd)
|
||||
.arg(&opt.cwd)
|
||||
.kill_on_drop(true)
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let cwd = opt.cwd.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Ok(output) = child.wait_with_output().await {
|
||||
let selected = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
if !selected.is_empty() {
|
||||
tx.send(Ok(selected.into())).ok();
|
||||
tx.send(Ok(Url::new(selected, &cwd))).ok();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
use std::{borrow::Cow, ffi::OsStr, fs::Metadata, path::{Path, PathBuf}};
|
||||
use std::{borrow::Cow, ffi::OsStr, fs::Metadata, path::PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use shared::Url;
|
||||
use tokio::fs;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct File {
|
||||
pub(super) path: PathBuf,
|
||||
pub(super) url: Url,
|
||||
pub(super) meta: Metadata,
|
||||
pub(super) length: Option<u64>,
|
||||
pub(super) link_to: Option<PathBuf>,
|
||||
|
|
@ -15,53 +16,53 @@ pub struct File {
|
|||
|
||||
impl File {
|
||||
#[inline]
|
||||
pub async fn from(path: &Path) -> Result<Self> {
|
||||
let meta = fs::metadata(path).await?;
|
||||
Ok(Self::from_meta(path, meta).await)
|
||||
pub async fn from(url: Url) -> Result<Self> {
|
||||
let meta = fs::metadata(&url).await?;
|
||||
Ok(Self::from_meta(url, meta).await)
|
||||
}
|
||||
|
||||
pub async fn from_meta(path: &Path, mut meta: Metadata) -> Self {
|
||||
pub async fn from_meta(url: Url, mut meta: Metadata) -> Self {
|
||||
let is_link = meta.is_symlink();
|
||||
let mut link_to = None;
|
||||
|
||||
if is_link {
|
||||
meta = fs::metadata(&path).await.unwrap_or(meta);
|
||||
link_to = fs::read_link(&path).await.ok();
|
||||
meta = fs::metadata(&url).await.unwrap_or(meta);
|
||||
link_to = fs::read_link(&url).await.ok();
|
||||
}
|
||||
|
||||
let length = if meta.is_dir() { None } else { Some(meta.len()) };
|
||||
let is_hidden = path.file_name().map(|s| s.to_string_lossy().starts_with('.')).unwrap_or(false);
|
||||
Self { path: path.to_path_buf(), meta, length, link_to, is_link, is_hidden }
|
||||
let is_hidden = url.file_name().map(|s| s.to_string_lossy().starts_with('.')).unwrap_or(false);
|
||||
Self { url, meta, length, link_to, is_link, is_hidden }
|
||||
}
|
||||
}
|
||||
|
||||
impl File {
|
||||
// --- Path
|
||||
#[inline]
|
||||
pub fn path(&self) -> &PathBuf { &self.path }
|
||||
pub fn url(&self) -> &Url { &self.url }
|
||||
|
||||
#[inline]
|
||||
pub fn set_path(&mut self, path: PathBuf) { self.path = path; }
|
||||
pub fn set_url(&mut self, url: Url) { self.url = url; }
|
||||
|
||||
#[inline]
|
||||
pub fn path_owned(&self) -> PathBuf { self.path.clone() }
|
||||
pub fn url_owned(&self) -> Url { self.url.clone() }
|
||||
|
||||
#[inline]
|
||||
pub fn path_os_str(&self) -> &OsStr { self.path.as_os_str() }
|
||||
pub fn url_os_str(&self) -> &OsStr { self.url.as_os_str() }
|
||||
|
||||
#[inline]
|
||||
pub fn name(&self) -> Option<&OsStr> { self.path.file_name() }
|
||||
pub fn name(&self) -> Option<&OsStr> { self.url.file_name() }
|
||||
|
||||
#[inline]
|
||||
pub fn name_display(&self) -> Option<Cow<str>> {
|
||||
self.path.file_name().map(|s| s.to_string_lossy())
|
||||
self.url.file_name().map(|s| s.to_string_lossy())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stem(&self) -> Option<&OsStr> { self.path.file_stem() }
|
||||
pub fn stem(&self) -> Option<&OsStr> { self.url.file_stem() }
|
||||
|
||||
#[inline]
|
||||
pub fn parent(&self) -> Option<&Path> { self.path.parent() }
|
||||
pub fn parent(&self) -> Option<Url> { self.url.parent_url() }
|
||||
|
||||
// --- Meta
|
||||
#[inline]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
use std::{collections::{BTreeMap, BTreeSet}, mem, ops::Deref, path::{Path, PathBuf}};
|
||||
use std::{collections::{BTreeMap, BTreeSet}, mem, ops::Deref};
|
||||
|
||||
use anyhow::Result;
|
||||
use config::manager::SortBy;
|
||||
use shared::Url;
|
||||
use tokio::fs;
|
||||
|
||||
use super::{File, FilesSorter};
|
||||
|
|
@ -10,8 +11,8 @@ pub struct Files {
|
|||
items: Vec<File>,
|
||||
hidden: Vec<File>,
|
||||
|
||||
sizes: BTreeMap<PathBuf, u64>,
|
||||
selected: BTreeSet<PathBuf>,
|
||||
sizes: BTreeMap<Url, u64>,
|
||||
selected: BTreeSet<Url>,
|
||||
|
||||
sorter: FilesSorter,
|
||||
show_hidden: bool,
|
||||
|
|
@ -39,22 +40,22 @@ impl Deref for Files {
|
|||
}
|
||||
|
||||
impl Files {
|
||||
pub async fn read(paths: &[impl AsRef<Path>]) -> Vec<File> {
|
||||
let mut items = Vec::with_capacity(paths.len());
|
||||
for path in paths {
|
||||
if let Ok(file) = File::from(path.as_ref()).await {
|
||||
pub async fn read(urls: Vec<Url>) -> Vec<File> {
|
||||
let mut items = Vec::with_capacity(urls.len());
|
||||
for url in urls {
|
||||
if let Ok(file) = File::from(url).await {
|
||||
items.push(file);
|
||||
}
|
||||
}
|
||||
items
|
||||
}
|
||||
|
||||
pub async fn read_dir(path: &Path) -> Result<Vec<File>> {
|
||||
let mut it = fs::read_dir(path).await?;
|
||||
pub async fn read_dir(url: &Url) -> Result<Vec<File>> {
|
||||
let mut it = fs::read_dir(url).await?;
|
||||
let mut items = Vec::new();
|
||||
while let Ok(Some(item)) = it.next_entry().await {
|
||||
if let Ok(meta) = item.metadata().await {
|
||||
items.push(File::from_meta(&item.path(), meta).await);
|
||||
items.push(File::from_meta(Url::new(item.path(), url), meta).await);
|
||||
}
|
||||
}
|
||||
Ok(items)
|
||||
|
|
@ -63,8 +64,8 @@ impl Files {
|
|||
|
||||
impl Files {
|
||||
#[inline]
|
||||
pub fn select(&mut self, path: &Path, state: Option<bool>) -> bool {
|
||||
let old = self.selected.contains(path);
|
||||
pub fn select(&mut self, url: &Url, state: Option<bool>) -> bool {
|
||||
let old = self.selected.contains(url);
|
||||
let new = if let Some(new) = state { new } else { !old };
|
||||
|
||||
if new == old {
|
||||
|
|
@ -72,9 +73,9 @@ impl Files {
|
|||
}
|
||||
|
||||
if new {
|
||||
self.selected.insert(path.to_owned());
|
||||
self.selected.insert(url.to_owned());
|
||||
} else {
|
||||
self.selected.remove(path);
|
||||
self.selected.remove(url);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
|
@ -82,17 +83,17 @@ impl Files {
|
|||
pub fn select_all(&mut self, state: Option<bool>) -> bool {
|
||||
match state {
|
||||
Some(true) => {
|
||||
self.selected = self.iter().map(|f| f.path_owned()).collect();
|
||||
self.selected = self.iter().map(|f| f.url_owned()).collect();
|
||||
}
|
||||
Some(false) => {
|
||||
self.selected.clear();
|
||||
}
|
||||
None => {
|
||||
for item in &self.items {
|
||||
if self.selected.contains(&item.path) {
|
||||
self.selected.remove(&item.path);
|
||||
if self.selected.contains(&item.url) {
|
||||
self.selected.remove(&item.url);
|
||||
} else {
|
||||
self.selected.insert(item.path_owned());
|
||||
self.selected.insert(item.url_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -102,7 +103,7 @@ impl Files {
|
|||
|
||||
pub fn select_index(&mut self, indices: &BTreeSet<usize>, state: Option<bool>) -> bool {
|
||||
let mut applied = false;
|
||||
let paths: Vec<_> = self.pick(indices).iter().map(|f| f.path_owned()).collect();
|
||||
let paths: Vec<_> = self.pick(indices).iter().map(|f| f.url_owned()).collect();
|
||||
|
||||
for path in paths {
|
||||
applied |= self.select(&path, state);
|
||||
|
|
@ -119,7 +120,7 @@ impl Files {
|
|||
true
|
||||
}
|
||||
|
||||
pub fn update_size(&mut self, items: BTreeMap<PathBuf, u64>) -> bool {
|
||||
pub fn update_size(&mut self, items: BTreeMap<Url, u64>) -> bool {
|
||||
self.sizes.extend(items);
|
||||
if self.sorter.by == SortBy::Size {
|
||||
self.sorter.sort(&mut self.items);
|
||||
|
|
@ -127,6 +128,7 @@ impl Files {
|
|||
true
|
||||
}
|
||||
|
||||
// TODO: remove this
|
||||
pub fn update_search(&mut self, items: Vec<File>) -> bool {
|
||||
if !items.is_empty() {
|
||||
if self.show_hidden {
|
||||
|
|
@ -164,7 +166,7 @@ impl Files {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn position(&self, path: &Path) -> Option<usize> { self.iter().position(|f| f.path == path) }
|
||||
pub fn position(&self, url: &Url) -> Option<usize> { self.iter().position(|f| f.url == *url) }
|
||||
|
||||
#[inline]
|
||||
pub fn duplicate(&self, idx: usize) -> Option<File> { self.items.get(idx).cloned() }
|
||||
|
|
@ -177,7 +179,7 @@ impl Files {
|
|||
|
||||
let selected: BTreeSet<_> = self.selected.iter().collect();
|
||||
let pending: BTreeSet<_> =
|
||||
pending.iter().filter_map(|&i| self.items.get(i)).map(|f| &f.path).collect();
|
||||
pending.iter().filter_map(|&i| self.items.get(i)).map(|f| &f.url).collect();
|
||||
|
||||
let selected: BTreeSet<_> = if unset {
|
||||
selected.difference(&pending).cloned().collect()
|
||||
|
|
@ -187,7 +189,7 @@ impl Files {
|
|||
|
||||
let mut items = Vec::with_capacity(selected.len());
|
||||
for item in &self.items {
|
||||
if selected.contains(&item.path) {
|
||||
if selected.contains(&item.url) {
|
||||
items.push(item);
|
||||
}
|
||||
if items.len() == selected.len() {
|
||||
|
|
@ -198,14 +200,14 @@ impl Files {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_selected(&self, path: &Path) -> bool { self.selected.contains(path) }
|
||||
pub fn is_selected(&self, url: &Url) -> bool { self.selected.contains(url) }
|
||||
|
||||
#[inline]
|
||||
pub fn has_selected(&self) -> bool {
|
||||
if self.selected.is_empty() {
|
||||
return false;
|
||||
}
|
||||
self.iter().any(|f| self.selected.contains(&f.path))
|
||||
self.iter().any(|f| self.selected.contains(&f.url))
|
||||
}
|
||||
|
||||
// --- Sorter
|
||||
|
|
|
|||
|
|
@ -1,30 +1,27 @@
|
|||
use std::{collections::BTreeMap, path::{Path, PathBuf}};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use shared::Url;
|
||||
|
||||
use super::File;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FilesOp {
|
||||
Read(PathBuf, Vec<File>),
|
||||
Size(PathBuf, BTreeMap<PathBuf, u64>),
|
||||
Search(PathBuf, Vec<File>),
|
||||
IOErr(PathBuf),
|
||||
Read(Url, Vec<File>),
|
||||
Size(Url, BTreeMap<Url, u64>),
|
||||
IOErr(Url),
|
||||
}
|
||||
|
||||
impl FilesOp {
|
||||
#[inline]
|
||||
pub fn path(&self) -> PathBuf {
|
||||
pub fn url(&self) -> Url {
|
||||
match self {
|
||||
Self::Read(path, _) => path,
|
||||
Self::Size(path, _) => path,
|
||||
Self::Search(path, _) => path,
|
||||
Self::IOErr(path) => path,
|
||||
Self::Read(url, _) => url,
|
||||
Self::Size(url, _) => url,
|
||||
Self::IOErr(url) => url,
|
||||
}
|
||||
.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_empty(path: &Path) -> Self { Self::Read(path.to_path_buf(), Vec::new()) }
|
||||
|
||||
#[inline]
|
||||
pub fn search_empty(path: &Path) -> Self { Self::Search(path.to_path_buf(), Vec::new()) }
|
||||
pub fn clear(url: &Url) -> Self { Self::Read(url.clone(), Vec::new()) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{cmp::Ordering, mem, path::PathBuf};
|
||||
use std::{cmp::Ordering, mem};
|
||||
|
||||
use config::{manager::SortBy, MANAGER};
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ impl FilesSorter {
|
|||
|
||||
match self.by {
|
||||
SortBy::Alphabetical => {
|
||||
items.sort_unstable_by(|a, b| self.cmp(&a.path, &b.path, self.promote(a, b)))
|
||||
items.sort_unstable_by(|a, b| self.cmp(&*a.url, &*b.url, self.promote(a, b)))
|
||||
}
|
||||
SortBy::Created => items.sort_unstable_by(|a, b| {
|
||||
if let (Ok(aa), Ok(bb)) = (a.meta.created(), b.meta.created()) {
|
||||
|
|
@ -56,7 +56,7 @@ impl FilesSorter {
|
|||
let mut entities = Vec::with_capacity(items.len());
|
||||
for (i, file) in items.iter().enumerate() {
|
||||
indices.push(i);
|
||||
entities.push((file.path.to_string_lossy(), file));
|
||||
entities.push((file.url.to_string_lossy(), file));
|
||||
}
|
||||
|
||||
indices.sort_unstable_by(|&a, &b| {
|
||||
|
|
@ -71,7 +71,7 @@ impl FilesSorter {
|
|||
});
|
||||
|
||||
let dummy = File {
|
||||
path: PathBuf::new(),
|
||||
url: Default::default(),
|
||||
meta: items[0].meta.clone(),
|
||||
length: None,
|
||||
link_to: None,
|
||||
|
|
|
|||
|
|
@ -1,34 +1,34 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use config::MANAGER;
|
||||
use ratatui::layout::Rect;
|
||||
use shared::Url;
|
||||
|
||||
use crate::{emit, files::{File, Files, FilesOp}};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Folder {
|
||||
pub cwd: PathBuf,
|
||||
pub cwd: Url,
|
||||
pub files: Files,
|
||||
offset: usize,
|
||||
cursor: usize,
|
||||
|
||||
pub page: usize,
|
||||
pub hovered: Option<File>,
|
||||
pub in_search: bool,
|
||||
offset: usize,
|
||||
cursor: usize,
|
||||
|
||||
pub page: usize,
|
||||
pub hovered: Option<File>,
|
||||
}
|
||||
|
||||
impl From<Url> for Folder {
|
||||
fn from(cwd: Url) -> Self { Self { cwd, ..Default::default() } }
|
||||
}
|
||||
|
||||
impl From<&Url> for Folder {
|
||||
fn from(cwd: &Url) -> Self { Self::from(cwd.clone()) }
|
||||
}
|
||||
|
||||
impl Folder {
|
||||
pub fn new(cwd: &Path) -> Self { Self { cwd: cwd.to_path_buf(), ..Default::default() } }
|
||||
|
||||
pub fn new_search(cwd: &Path) -> Self {
|
||||
Self { cwd: cwd.to_path_buf(), in_search: true, ..Default::default() }
|
||||
}
|
||||
|
||||
pub fn update(&mut self, op: FilesOp) -> bool {
|
||||
let b = match op {
|
||||
FilesOp::Read(_, items) => self.files.update_read(items),
|
||||
FilesOp::Size(_, items) => self.files.update_size(items),
|
||||
FilesOp::Search(_, items) => self.files.update_search(items),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
if !b {
|
||||
|
|
@ -102,18 +102,18 @@ impl Folder {
|
|||
&self.files[start..end]
|
||||
}
|
||||
|
||||
pub fn hover(&mut self, path: &Path) -> bool {
|
||||
let new = self.files.position(path).unwrap_or(self.cursor);
|
||||
pub fn hover(&mut self, url: &Url) -> bool {
|
||||
let new = self.files.position(url).unwrap_or(self.cursor);
|
||||
if new > self.cursor { self.next(new - self.cursor) } else { self.prev(self.cursor - new) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hover_repos(&mut self) -> bool {
|
||||
self.hover(&self.hovered.as_ref().map(|h| h.path_owned()).unwrap_or_default())
|
||||
self.hover(&self.hovered.as_ref().map(|h| h.url_owned()).unwrap_or_default())
|
||||
}
|
||||
|
||||
pub fn hover_force(&mut self, file: File) -> bool {
|
||||
if self.hover(file.path()) {
|
||||
if self.hover(file.url()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -138,8 +138,8 @@ impl Folder {
|
|||
&self.files[start..end]
|
||||
}
|
||||
|
||||
pub fn rect_current(&self, path: &Path) -> Option<Rect> {
|
||||
let y = self.files.position(path)? - self.offset;
|
||||
pub fn rect_current(&self, url: &Url) -> Option<Rect> {
|
||||
let y = self.files.position(url)? - self.offset;
|
||||
|
||||
let mut rect = MANAGER.layout.folder_rect();
|
||||
rect.y = rect.y.saturating_sub(1) + y as u16;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use std::{collections::{BTreeMap, BTreeSet, HashMap, HashSet}, env, ffi::OsStr, io::{stdout, BufWriter, Write}, mem, path::{Path, PathBuf}};
|
||||
use std::{collections::{BTreeMap, BTreeSet, HashMap, HashSet}, env, ffi::OsStr, io::{stdout, BufWriter, Write}, path::PathBuf};
|
||||
|
||||
use anyhow::{anyhow, bail, Error, Result};
|
||||
use config::{OPEN, PREVIEW};
|
||||
use shared::{max_common_root, Defer, Term, MIME_DIR};
|
||||
use shared::{max_common_root, Defer, Term, Url, MIME_DIR};
|
||||
use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
|
||||
|
||||
use super::{Tab, Tabs, Watcher};
|
||||
|
|
@ -10,10 +10,10 @@ use crate::{emit, external::{self, ShellOpt}, files::{File, FilesOp}, input::Inp
|
|||
|
||||
pub struct Manager {
|
||||
tabs: Tabs,
|
||||
yanked: (bool, HashSet<PathBuf>),
|
||||
yanked: (bool, HashSet<Url>),
|
||||
|
||||
watcher: Watcher,
|
||||
pub mimetype: HashMap<PathBuf, String>,
|
||||
pub mimetype: HashMap<Url, String>,
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
|
|
@ -44,7 +44,7 @@ impl Manager {
|
|||
to_watch.insert(&tab.current.cwd);
|
||||
if let Some(ref h) = tab.current.hovered {
|
||||
if h.is_dir() {
|
||||
to_watch.insert(h.path());
|
||||
to_watch.insert(h.url());
|
||||
}
|
||||
}
|
||||
if let Some(ref p) = tab.parent {
|
||||
|
|
@ -56,20 +56,23 @@ impl Manager {
|
|||
|
||||
pub fn peek(&mut self, sequent: bool, show_image: bool) -> bool {
|
||||
let Some(hovered) = self.hovered().cloned() else {
|
||||
return self.active_mut().preview.reset();
|
||||
return self.active_mut().preview_reset();
|
||||
};
|
||||
|
||||
let url = hovered.url();
|
||||
if !show_image {
|
||||
self.active_mut().preview_reset_image();
|
||||
}
|
||||
|
||||
let mime = if hovered.is_dir() {
|
||||
MIME_DIR.to_owned()
|
||||
} else if let Some(m) = self.mimetype.get(hovered.path()).cloned() {
|
||||
m
|
||||
} else {
|
||||
if hovered.is_dir() {
|
||||
let len = self.active().history(url).map(|f| f.files.len());
|
||||
self.active_mut().preview.folder(url, len, sequent);
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some(mime) = self.mimetype.get(url).cloned() else {
|
||||
tokio::spawn(async move {
|
||||
if let Ok(mimes) = external::file(&[hovered.path()]).await {
|
||||
if let Ok(mimes) = external::file(&[hovered.url()]).await {
|
||||
emit!(Mimetype(mimes));
|
||||
}
|
||||
});
|
||||
|
|
@ -77,16 +80,16 @@ impl Manager {
|
|||
};
|
||||
|
||||
if sequent {
|
||||
self.active_mut().preview.sequent(hovered.path(), &mime, show_image);
|
||||
self.active_mut().preview.sequent(url, &mime, show_image);
|
||||
} else {
|
||||
self.active_mut().preview.go(hovered.path(), &mime, show_image);
|
||||
self.active_mut().preview.go(url, &mime, show_image);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn yank(&mut self, cut: bool) -> bool {
|
||||
self.yanked.0 = cut;
|
||||
self.yanked.1 = self.selected().into_iter().map(|f| f.path_owned()).collect();
|
||||
self.yanked.1 = self.selected().into_iter().map(|f| f.url_owned()).collect();
|
||||
false
|
||||
}
|
||||
|
||||
|
|
@ -124,8 +127,8 @@ impl Manager {
|
|||
.into_iter()
|
||||
.map(|f| {
|
||||
(
|
||||
f.path_os_str().to_owned(),
|
||||
if f.is_dir() { Some(MIME_DIR.to_owned()) } else { self.mimetype.get(f.path()).cloned() },
|
||||
f.url_owned(),
|
||||
if f.is_dir() { Some(MIME_DIR.to_owned()) } else { self.mimetype.get(f.url()).cloned() },
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
|
@ -135,18 +138,20 @@ impl Manager {
|
|||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
let todo = files.iter().filter(|(_, m)| m.is_none()).map(|(p, _)| p).collect::<Vec<_>>();
|
||||
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(|(p, m)| {
|
||||
let mime = m.or_else(|| mimes.remove(&PathBuf::from(&p)));
|
||||
(p, mime)
|
||||
.map(|(u, m)| {
|
||||
let mime = m.or_else(|| mimes.remove(&u));
|
||||
(u, mime)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
.collect();
|
||||
}
|
||||
|
||||
let files = files.into_iter().filter_map(|(p, m)| m.map(|m| (p, m))).collect::<Vec<_>>();
|
||||
let files: Vec<_> =
|
||||
files.into_iter().filter_map(|(u, m)| m.map(|m| (u.into_os_string(), m))).collect();
|
||||
|
||||
if !interactive {
|
||||
emit!(Open(files, None));
|
||||
return;
|
||||
|
|
@ -184,7 +189,7 @@ impl Manager {
|
|||
fs::File::create(path).await?;
|
||||
}
|
||||
|
||||
if let Ok(file) = File::from(&hovered).await {
|
||||
if let Ok(file) = File::from(Url::new(hovered, &cwd)).await {
|
||||
emit!(Hover(file));
|
||||
emit!(Refresh);
|
||||
}
|
||||
|
|
@ -199,7 +204,7 @@ impl Manager {
|
|||
return self.bulk_rename();
|
||||
}
|
||||
|
||||
let Some(hovered) = self.hovered().map(|h| h.path_owned()) else {
|
||||
let Some(hovered) = self.hovered().map(|h| h.url_owned()) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
|
@ -217,7 +222,7 @@ impl Manager {
|
|||
}
|
||||
|
||||
pub fn bulk_rename(&self) -> bool {
|
||||
let old: Vec<_> = self.selected().into_iter().map(|f| f.path()).collect();
|
||||
let old: Vec<_> = self.selected().into_iter().map(|f| f.url()).collect();
|
||||
|
||||
let root = max_common_root(&old);
|
||||
let old: Vec<_> = old.into_iter().map(|p| p.strip_prefix(&root).unwrap().to_owned()).collect();
|
||||
|
|
@ -256,7 +261,7 @@ impl Manager {
|
|||
})?;
|
||||
child.wait().await?;
|
||||
|
||||
let new: Vec<_> = fs::read_to_string(&tmp).await?.lines().map(|l| l.into()).collect();
|
||||
let new: Vec<_> = fs::read_to_string(&tmp).await?.lines().map(PathBuf::from).collect();
|
||||
Self::bulk_rename_do(root, old, new).await
|
||||
});
|
||||
|
||||
|
|
@ -321,55 +326,36 @@ impl Manager {
|
|||
}
|
||||
|
||||
pub fn update_read(&mut self, op: FilesOp) -> bool {
|
||||
let path = op.path();
|
||||
let url = op.url();
|
||||
let cwd = self.cwd().to_owned();
|
||||
let hovered = self.hovered().map(|h| h.path_owned());
|
||||
let hovered = self.hovered().map(|h| h.url_owned());
|
||||
|
||||
let mut b = if cwd == path && !self.current().in_search {
|
||||
let mut b = if cwd == url && !cwd.is_search() {
|
||||
self.current_mut().update(op)
|
||||
} else if matches!(self.parent(), Some(p) if p.cwd == path) {
|
||||
} else if matches!(self.parent(), Some(p) if p.cwd == url) {
|
||||
self.active_mut().parent.as_mut().unwrap().update(op)
|
||||
} else {
|
||||
self
|
||||
.active_mut()
|
||||
.history
|
||||
.entry(path.to_path_buf())
|
||||
.or_insert_with(|| Folder::new(&path))
|
||||
.update(op);
|
||||
self.active_mut().history.entry(url.clone()).or_insert_with(|| Folder::from(&url)).update(op);
|
||||
|
||||
matches!(self.hovered(), Some(h) if h.path() == &path)
|
||||
matches!(self.hovered(), Some(h) if *h.url() == url)
|
||||
};
|
||||
|
||||
b |= self.active_mut().parent.as_mut().map_or(false, |p| p.hover(&cwd));
|
||||
b |= hovered.as_ref().map_or(false, |h| self.current_mut().hover(h));
|
||||
|
||||
if hovered.as_ref() != self.hovered().map(|h| h.path()) {
|
||||
if hovered.as_ref() != self.hovered().map(|h| h.url()) {
|
||||
emit!(Hover);
|
||||
}
|
||||
b
|
||||
}
|
||||
|
||||
pub fn update_search(&mut self, op: FilesOp) -> bool {
|
||||
let path = op.path();
|
||||
if self.current().in_search && self.cwd() == path {
|
||||
return self.current_mut().update(op);
|
||||
}
|
||||
|
||||
let rep = mem::replace(self.current_mut(), Folder::new_search(&path));
|
||||
if !rep.in_search {
|
||||
self.active_mut().history.insert(path, rep);
|
||||
}
|
||||
self.current_mut().update(op);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn update_ioerr(&mut self, op: FilesOp) -> bool {
|
||||
let path = op.path();
|
||||
let op = FilesOp::read_empty(&path);
|
||||
let url = op.url();
|
||||
let op = FilesOp::clear(&url);
|
||||
|
||||
if path == self.cwd() {
|
||||
if url == *self.cwd() {
|
||||
self.current_mut().update(op);
|
||||
} else if matches!(self.parent(), Some(p) if p.cwd == path) {
|
||||
} else if matches!(self.parent(), Some(p) if p.cwd == url) {
|
||||
self.active_mut().parent.as_mut().unwrap().update(op);
|
||||
} else {
|
||||
return false;
|
||||
|
|
@ -379,7 +365,7 @@ impl Manager {
|
|||
true
|
||||
}
|
||||
|
||||
pub fn update_mimetype(&mut self, mut mimes: BTreeMap<PathBuf, String>, tasks: &Tasks) -> bool {
|
||||
pub fn update_mimetype(&mut self, mut mimes: BTreeMap<Url, String>, tasks: &Tasks) -> bool {
|
||||
mimes.retain(|f, m| self.mimetype.get(f) != Some(m));
|
||||
if mimes.is_empty() {
|
||||
return false;
|
||||
|
|
@ -393,22 +379,15 @@ impl Manager {
|
|||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn update_hover(&mut self, file: Option<File>) -> bool {
|
||||
let b = file.map(|f| self.current_mut().hover_force(f)).unwrap_or(false);
|
||||
let Some(hovered) = self.hovered() else {
|
||||
return b;
|
||||
};
|
||||
|
||||
if hovered.is_dir() {
|
||||
self.watcher.trigger_dirs(&[hovered.path()]);
|
||||
}
|
||||
b
|
||||
file.map(|f| self.current_mut().hover_force(f)) == Some(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
#[inline]
|
||||
pub fn cwd(&self) -> &Path { &self.current().cwd }
|
||||
pub fn cwd(&self) -> &Url { &self.current().cwd }
|
||||
|
||||
#[inline]
|
||||
pub fn tabs(&self) -> &Tabs { &self.tabs }
|
||||
|
|
@ -438,5 +417,5 @@ impl Manager {
|
|||
pub fn selected(&self) -> Vec<&File> { self.tabs.active().selected() }
|
||||
|
||||
#[inline]
|
||||
pub fn yanked(&self) -> &(bool, HashSet<PathBuf>) { &self.yanked }
|
||||
pub fn yanked(&self) -> &(bool, HashSet<Url>) { &self.yanked }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use std::{path::{Path, PathBuf}, sync::atomic::Ordering};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use adaptor::Adaptor;
|
||||
use config::MANAGER;
|
||||
use shared::{MimeKind, PeekError};
|
||||
use shared::{MimeKind, PeekError, Url, MIME_DIR};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use super::{Provider, INCR};
|
||||
use crate::emit;
|
||||
use crate::{emit, files::{Files, FilesOp}};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Preview {
|
||||
|
|
@ -17,7 +17,7 @@ pub struct Preview {
|
|||
}
|
||||
|
||||
pub struct PreviewLock {
|
||||
pub path: PathBuf,
|
||||
pub url: Url,
|
||||
pub mime: String,
|
||||
pub skip: usize,
|
||||
pub data: PreviewData,
|
||||
|
|
@ -31,52 +31,93 @@ pub enum PreviewData {
|
|||
}
|
||||
|
||||
impl Preview {
|
||||
pub fn go(&mut self, path: &Path, mime: &str, show_image: bool) {
|
||||
pub fn go(&mut self, url: &Url, mime: &str, show_image: bool) {
|
||||
let kind = MimeKind::new(mime);
|
||||
if !show_image && kind.show_as_image() {
|
||||
return;
|
||||
} else if self.same(path, mime) {
|
||||
} else if self.same(url, mime) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.reset();
|
||||
if !self.same_path(path) {
|
||||
self.reset(|_| true);
|
||||
if !self.same_mime(url, mime) {
|
||||
self.skip = 0;
|
||||
}
|
||||
|
||||
let (path, mime, skip) = (path.to_path_buf(), mime.to_owned(), self.skip);
|
||||
let (url, mime, skip) = (url.clone(), mime.to_owned(), self.skip);
|
||||
self.handle = Some(tokio::spawn(async move {
|
||||
match Provider::auto(kind, &path, skip).await {
|
||||
match Provider::auto(kind, &url, skip).await {
|
||||
Ok(data) => {
|
||||
emit!(Preview(PreviewLock { path, mime, skip, data }));
|
||||
emit!(Preview(PreviewLock { url, mime, skip, data }));
|
||||
}
|
||||
Err(PeekError::Exceed(max)) => {
|
||||
emit!(Peek(max, path));
|
||||
emit!(Peek(max, url));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn sequent(&mut self, path: &Path, mime: &str, show_image: bool) {
|
||||
pub fn folder(&mut self, url: &Url, files: Option<usize>, sequent: bool) {
|
||||
if let Some(files) = files {
|
||||
self.skip = self.skip.min(files.saturating_sub(MANAGER.layout.preview_height()));
|
||||
}
|
||||
|
||||
if self.same(url, MIME_DIR) {
|
||||
return;
|
||||
} else if !self.same_mime(url, MIME_DIR) {
|
||||
self.skip = 0;
|
||||
}
|
||||
|
||||
self.reset(|_| true);
|
||||
if files.is_some() || sequent {
|
||||
emit!(Preview(PreviewLock {
|
||||
url: url.clone(),
|
||||
mime: MIME_DIR.to_owned(),
|
||||
skip: self.skip,
|
||||
data: PreviewData::Folder,
|
||||
}));
|
||||
}
|
||||
|
||||
if sequent {
|
||||
return;
|
||||
}
|
||||
|
||||
let (url, skip) = (url.clone(), self.skip);
|
||||
self.handle = Some(tokio::spawn(async move {
|
||||
emit!(Files(match Files::read_dir(&url).await {
|
||||
Ok(items) => FilesOp::Read(url.clone(), items),
|
||||
Err(_) => FilesOp::IOErr(url.clone()),
|
||||
}));
|
||||
|
||||
emit!(Preview(PreviewLock {
|
||||
url,
|
||||
mime: MIME_DIR.to_owned(),
|
||||
skip,
|
||||
data: PreviewData::Folder,
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn sequent(&mut self, url: &Url, mime: &str, show_image: bool) {
|
||||
let kind = MimeKind::new(mime);
|
||||
if !show_image && kind.show_as_image() {
|
||||
return;
|
||||
} else if self.same(path, mime) {
|
||||
} else if self.same(url, mime) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.handle.take().map(|h| h.abort());
|
||||
INCR.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
let (path, mime, skip) = (path.to_path_buf(), mime.to_owned(), self.skip);
|
||||
let (url, mime, skip) = (url.clone(), mime.to_owned(), self.skip);
|
||||
self.handle = Some(tokio::spawn(async move {
|
||||
match Provider::auto(kind, &path, skip).await {
|
||||
match Provider::auto(kind, &url, skip).await {
|
||||
Ok(data) => {
|
||||
emit!(Preview(PreviewLock { path, mime, skip, data }));
|
||||
emit!(Preview(PreviewLock { url, mime, skip, data }));
|
||||
}
|
||||
Err(PeekError::Exceed(max)) => {
|
||||
emit!(Peek(max, path));
|
||||
emit!(Peek(max, url));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -84,37 +125,32 @@ impl Preview {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn arrow(&mut self, step: isize, absolute: bool) -> bool {
|
||||
let old = self.skip;
|
||||
if absolute {
|
||||
self.skip = step.unsigned_abs();
|
||||
} else if let Some(kind) = self.lock.as_ref().map(|l| MimeKind::new(&l.mime)) {
|
||||
let size = Provider::step_size(kind, step.unsigned_abs());
|
||||
self.skip = if step < 0 { old.saturating_sub(size) } else { old + size };
|
||||
}
|
||||
pub fn arrow(&mut self, step: isize) -> bool {
|
||||
let Some(kind) = self.lock.as_ref().map(|l| MimeKind::new(&l.mime)) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let old = self.skip;
|
||||
let size = Provider::step_size(kind, step.unsigned_abs());
|
||||
|
||||
self.skip = if step < 0 { old.saturating_sub(size) } else { old + size };
|
||||
self.skip != old
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) -> bool {
|
||||
pub fn reset<F: FnOnce(&PreviewLock) -> bool>(&mut self, f: F) -> bool {
|
||||
self.handle.take().map(|h| h.abort());
|
||||
INCR.fetch_add(1, Ordering::Relaxed);
|
||||
Adaptor::image_hide(MANAGER.layout.preview_rect()).ok();
|
||||
|
||||
let b = matches!(&self.lock, Some(l) if !l.is_image());
|
||||
self.lock = None;
|
||||
b
|
||||
}
|
||||
let Some(ref lock) = self.lock else {
|
||||
return false;
|
||||
};
|
||||
|
||||
pub fn reset_image(&mut self) -> bool {
|
||||
self.handle.take().map(|h| h.abort());
|
||||
INCR.fetch_add(1, Ordering::Relaxed);
|
||||
Adaptor::image_hide(MANAGER.layout.preview_rect()).ok();
|
||||
|
||||
if matches!(&self.lock, Some(l) if l.is_image()) {
|
||||
let b = !lock.is_image();
|
||||
if f(lock) {
|
||||
self.lock = None;
|
||||
}
|
||||
false
|
||||
b
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -126,17 +162,25 @@ impl Preview {
|
|||
pub fn skip(&self) -> usize { self.skip }
|
||||
|
||||
#[inline]
|
||||
pub fn same(&self, path: &Path, mime: &str) -> bool {
|
||||
pub fn same(&self, url: &Url, mime: &str) -> bool {
|
||||
if let Some(ref lock) = self.lock {
|
||||
return lock.path == path && lock.mime == mime && lock.skip == self.skip;
|
||||
return lock.url == *url && lock.mime == mime && lock.skip == self.skip;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn same_path(&self, path: &Path) -> bool {
|
||||
pub fn same_mime(&self, url: &Url, mime: &str) -> bool {
|
||||
if let Some(ref lock) = self.lock {
|
||||
return lock.path == path;
|
||||
return lock.url == *url && lock.mime == mime;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn same_path(&self, url: &Url) -> bool {
|
||||
if let Some(ref lock) = self.lock {
|
||||
return lock.url == *url;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ use shared::{MimeKind, PeekError};
|
|||
use syntect::{easy::HighlightFile, util::as_24_bit_terminal_escaped};
|
||||
use tokio::fs;
|
||||
|
||||
|
||||
use super::PreviewData;
|
||||
use crate::{emit, external, files::{Files, FilesOp}, highlighter};
|
||||
use crate::{external, highlighter};
|
||||
|
||||
pub(super) struct Provider;
|
||||
|
||||
|
|
@ -23,7 +24,6 @@ impl Provider {
|
|||
match kind {
|
||||
MimeKind::Empty => Err("Empty file".into()),
|
||||
MimeKind::Archive => Provider::archive(path, skip).await.map(PreviewData::Text),
|
||||
MimeKind::Dir => Provider::folder(path).await,
|
||||
MimeKind::Image => Provider::image(path).await,
|
||||
MimeKind::Video => Provider::video(path, skip).await,
|
||||
MimeKind::JSON => Provider::json(path, skip).await.map(PreviewData::Text),
|
||||
|
|
@ -37,25 +37,15 @@ impl Provider {
|
|||
match kind {
|
||||
MimeKind::Empty => 0,
|
||||
MimeKind::Archive => step * MANAGER.layout.preview_height() / 10,
|
||||
MimeKind::Dir => step * MANAGER.layout.preview_height() / 10,
|
||||
MimeKind::Image => 0,
|
||||
MimeKind::Video => step,
|
||||
MimeKind::JSON => step * MANAGER.layout.preview_height() / 10,
|
||||
MimeKind::PDF => 1,
|
||||
MimeKind::Text => step * MANAGER.layout.preview_height() / 10,
|
||||
MimeKind::Others => 0,
|
||||
MimeKind::Others => step * MANAGER.layout.preview_height() / 10,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn folder(path: &Path) -> Result<PreviewData, PeekError> {
|
||||
emit!(Files(match Files::read_dir(path).await {
|
||||
Ok(items) => FilesOp::Read(path.to_path_buf(), items),
|
||||
Err(_) => FilesOp::IOErr(path.to_path_buf()),
|
||||
}));
|
||||
|
||||
Ok(PreviewData::Folder)
|
||||
}
|
||||
|
||||
pub(super) async fn image(path: &Path) -> Result<PreviewData, PeekError> {
|
||||
Adaptor::image_show(path, MANAGER.layout.preview_rect()).await?;
|
||||
Ok(PreviewData::Image)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use std::{borrow::Cow, collections::{BTreeMap, BTreeSet}, ffi::{OsStr, OsString}, mem, path::{Path, PathBuf}};
|
||||
use std::{borrow::Cow, collections::{BTreeMap, BTreeSet}, ffi::{OsStr, OsString}, mem};
|
||||
|
||||
use anyhow::{Error, Result};
|
||||
use config::{open::Opener, MANAGER};
|
||||
use config::open::Opener;
|
||||
use futures::StreamExt;
|
||||
use shared::{Defer, MIME_DIR};
|
||||
use shared::{Defer, Url};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use super::{Folder, Mode, Preview, PreviewLock};
|
||||
|
|
@ -14,28 +14,36 @@ pub struct Tab {
|
|||
pub(super) current: Folder,
|
||||
pub(super) parent: Option<Folder>,
|
||||
|
||||
pub(super) history: BTreeMap<PathBuf, Folder>,
|
||||
pub(super) history: BTreeMap<Url, Folder>,
|
||||
pub(super) preview: Preview,
|
||||
|
||||
search: Option<JoinHandle<Result<()>>>,
|
||||
pub(super) show_hidden: bool,
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
pub fn new(path: &Path) -> Self {
|
||||
impl From<Url> for Tab {
|
||||
fn from(url: Url) -> Self {
|
||||
let parent = url.parent_url().map(Folder::from);
|
||||
|
||||
Self {
|
||||
mode: Default::default(),
|
||||
current: Folder::new(path),
|
||||
parent: path.parent().map(Folder::new),
|
||||
mode: Default::default(),
|
||||
current: Folder::from(url),
|
||||
parent,
|
||||
|
||||
history: Default::default(),
|
||||
preview: Default::default(),
|
||||
|
||||
search: None,
|
||||
search: None,
|
||||
show_hidden: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Url> for Tab {
|
||||
fn from(url: &Url) -> Self { Self::from(url.clone()) }
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
pub fn escape(&mut self) -> bool {
|
||||
if let Some((_, indices)) = self.mode.visual() {
|
||||
self.current.files.select_index(indices, Some(self.mode.is_select()));
|
||||
|
|
@ -74,19 +82,19 @@ impl Tab {
|
|||
true
|
||||
}
|
||||
|
||||
pub async fn cd(&mut self, mut target: PathBuf) -> bool {
|
||||
let Ok(file) = File::from(&target).await else {
|
||||
pub async fn cd(&mut self, mut target: Url) -> bool {
|
||||
let Ok(file) = File::from(target.clone()).await else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let mut hovered = None;
|
||||
if !file.is_dir() {
|
||||
hovered = Some(file);
|
||||
target = target.parent().unwrap().to_path_buf();
|
||||
target = target.parent_url().unwrap();
|
||||
}
|
||||
|
||||
if self.current.cwd == target {
|
||||
if hovered.map(|h| self.current.hover_force(h)).unwrap_or(false) {
|
||||
if hovered.map(|h| self.current.hover_force(h)) == Some(true) {
|
||||
emit!(Hover);
|
||||
}
|
||||
return false;
|
||||
|
|
@ -98,12 +106,12 @@ impl Tab {
|
|||
|
||||
let rep = self.history_new(&target);
|
||||
let rep = mem::replace(&mut self.current, rep);
|
||||
if !rep.in_search {
|
||||
if !rep.cwd.is_search() {
|
||||
self.history.insert(rep.cwd.clone(), rep);
|
||||
}
|
||||
|
||||
if let Some(parent) = target.parent() {
|
||||
self.parent = Some(self.history_new(parent));
|
||||
if let Some(parent) = target.parent_url() {
|
||||
self.parent = Some(self.history_new(&parent));
|
||||
}
|
||||
|
||||
if let Some(h) = hovered {
|
||||
|
|
@ -113,13 +121,13 @@ impl Tab {
|
|||
true
|
||||
}
|
||||
|
||||
pub fn cd_interactive(&mut self, target: PathBuf) -> bool {
|
||||
pub fn cd_interactive(&mut self, target: Url) -> bool {
|
||||
tokio::spawn(async move {
|
||||
let result =
|
||||
emit!(Input(InputOpt::top("Change directory:").with_value(target.to_string_lossy())));
|
||||
|
||||
if let Ok(target) = result.await {
|
||||
emit!(Cd(PathBuf::from(target)));
|
||||
if let Ok(s) = result.await {
|
||||
emit!(Cd(Url::new(s, &target)));
|
||||
}
|
||||
});
|
||||
false
|
||||
|
|
@ -133,16 +141,16 @@ impl Tab {
|
|||
return false;
|
||||
}
|
||||
|
||||
let rep = self.history_new(hovered.path());
|
||||
let rep = self.history_new(hovered.url());
|
||||
let rep = mem::replace(&mut self.current, rep);
|
||||
if !rep.in_search {
|
||||
if !rep.cwd.is_search() {
|
||||
self.history.insert(rep.cwd.clone(), rep);
|
||||
}
|
||||
|
||||
if let Some(rep) = self.parent.take() {
|
||||
self.history.insert(rep.cwd.clone(), rep);
|
||||
}
|
||||
self.parent = Some(self.history_new(hovered.parent().unwrap()));
|
||||
self.parent = Some(self.history_new(&hovered.parent().unwrap()));
|
||||
|
||||
emit!(Refresh);
|
||||
true
|
||||
|
|
@ -154,23 +162,23 @@ impl Tab {
|
|||
.hovered
|
||||
.as_ref()
|
||||
.and_then(|h| h.parent())
|
||||
.and_then(|p| if p == self.current.cwd { None } else { Some(p) })
|
||||
.or_else(|| self.current.cwd.parent());
|
||||
.filter(|p| *p != self.current.cwd)
|
||||
.or_else(|| self.current.cwd.parent_url());
|
||||
|
||||
let Some(current) = current.map(Path::to_path_buf) else {
|
||||
let Some(current) = current else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if let Some(rep) = self.parent.take() {
|
||||
self.history.insert(rep.cwd.clone(), rep);
|
||||
}
|
||||
if let Some(parent) = current.parent() {
|
||||
self.parent = Some(self.history_new(parent));
|
||||
if let Some(parent) = current.parent_url() {
|
||||
self.parent = Some(self.history_new(&parent));
|
||||
}
|
||||
|
||||
let rep = self.history_new(¤t);
|
||||
let rep = mem::replace(&mut self.current, rep);
|
||||
if !rep.in_search {
|
||||
if !rep.cwd.is_search() {
|
||||
self.history.insert(rep.cwd.clone(), rep);
|
||||
}
|
||||
|
||||
|
|
@ -186,7 +194,7 @@ impl Tab {
|
|||
|
||||
pub fn select(&mut self, state: Option<bool>) -> bool {
|
||||
if let Some(ref hovered) = self.current.hovered {
|
||||
return self.current.files.select(hovered.path(), state);
|
||||
return self.current.files.select(hovered.url(), state);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
@ -209,8 +217,8 @@ impl Tab {
|
|||
let mut it = self.selected().into_iter().peekable();
|
||||
while let Some(f) = it.next() {
|
||||
s.push(match type_ {
|
||||
"path" => f.path_os_str(),
|
||||
"dirname" => f.parent().map_or(OsStr::new(""), |p| p.as_os_str()),
|
||||
"path" => f.url_os_str(),
|
||||
"dirname" => f.url().parent().map_or(OsStr::new(""), |p| p.as_os_str()),
|
||||
"filename" => f.name().unwrap_or(OsStr::new("")),
|
||||
"name_without_ext" => f.stem().unwrap_or(OsStr::new("")),
|
||||
_ => return false,
|
||||
|
|
@ -241,9 +249,9 @@ impl Tab {
|
|||
external::fd(external::FdOpt { cwd: cwd.clone(), hidden, glob: false, subject })
|
||||
}?;
|
||||
|
||||
emit!(Files(FilesOp::search_empty(&cwd)));
|
||||
emit!(Files(FilesOp::clear(&cwd)));
|
||||
while let Some(chunk) = rx.next().await {
|
||||
emit!(Files(FilesOp::Search(cwd.clone(), Files::read(&chunk).await)));
|
||||
emit!(Files(FilesOp::Read(cwd.clone(), Files::read(chunk).await)));
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
|
|
@ -254,7 +262,7 @@ impl Tab {
|
|||
if let Some(handle) = self.search.take() {
|
||||
handle.abort();
|
||||
}
|
||||
if self.current.in_search {
|
||||
if self.current.cwd.is_search() {
|
||||
self.preview_reset_image();
|
||||
|
||||
let cwd = self.current.cwd.clone();
|
||||
|
|
@ -288,7 +296,7 @@ impl Tab {
|
|||
let selected: Vec<_> = self
|
||||
.selected()
|
||||
.into_iter()
|
||||
.map(|f| (f.path_os_str().to_owned(), Default::default()))
|
||||
.map(|f| (f.url_os_str().to_owned(), Default::default()))
|
||||
.collect();
|
||||
|
||||
let mut exec = exec.to_owned();
|
||||
|
|
@ -310,34 +318,24 @@ impl Tab {
|
|||
false
|
||||
}
|
||||
|
||||
pub fn update_peek(&mut self, step: isize, path: Option<PathBuf>) {
|
||||
pub fn update_peek(&mut self, step: isize, url: Option<Url>) -> bool {
|
||||
let Some(ref hovered) = self.current.hovered else {
|
||||
return;
|
||||
return false;
|
||||
};
|
||||
|
||||
if path.as_ref().map(|p| p != hovered.path()).unwrap_or(false) {
|
||||
return;
|
||||
} else if !self.preview.arrow(step, path.is_some()) {
|
||||
return;
|
||||
} else if !matches!(&self.preview.lock, Some(l) if l.mime == MIME_DIR) {
|
||||
return;
|
||||
if url.as_ref().map(|p| p != hovered.url()) == Some(true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let path = &self.preview.lock.as_ref().unwrap().path;
|
||||
if let Some(folder) = self.history(path) {
|
||||
let max = folder.files.len().saturating_sub(MANAGER.layout.preview_height());
|
||||
if self.preview.skip() > max {
|
||||
self.preview.arrow(max as isize, true);
|
||||
}
|
||||
}
|
||||
self.preview.arrow(step)
|
||||
}
|
||||
|
||||
pub fn update_preview(&mut self, lock: PreviewLock) -> bool {
|
||||
let Some(hovered) = self.current.hovered.as_ref().map(|h| h.path()) else {
|
||||
return self.preview.reset();
|
||||
let Some(hovered) = self.current.hovered.as_ref().map(|h| h.url()) else {
|
||||
return self.preview_reset();
|
||||
};
|
||||
|
||||
if lock.path != *hovered {
|
||||
if lock.url != *hovered {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -382,11 +380,11 @@ impl Tab {
|
|||
|
||||
// --- History
|
||||
#[inline]
|
||||
pub fn history(&self, path: &Path) -> Option<&Folder> { self.history.get(path) }
|
||||
pub fn history(&self, url: &Url) -> Option<&Folder> { self.history.get(url) }
|
||||
|
||||
#[inline]
|
||||
pub fn history_new(&mut self, path: &Path) -> Folder {
|
||||
self.history.remove(path).unwrap_or_else(|| Folder::new(path))
|
||||
pub fn history_new(&mut self, url: &Url) -> Folder {
|
||||
self.history.remove(url).unwrap_or_else(|| Folder::from(url))
|
||||
}
|
||||
|
||||
// --- Preview
|
||||
|
|
@ -394,10 +392,10 @@ impl Tab {
|
|||
pub fn preview(&self) -> &Preview { &self.preview }
|
||||
|
||||
#[inline]
|
||||
pub fn preview_reset(&mut self) -> bool { self.preview.reset() }
|
||||
pub fn preview_reset(&mut self) -> bool { self.preview.reset(|_| true) }
|
||||
|
||||
#[inline]
|
||||
pub fn preview_reset_image(&mut self) -> bool { self.preview.reset_image() }
|
||||
pub fn preview_reset_image(&mut self) -> bool { self.preview.reset(|l| l.is_image()) }
|
||||
|
||||
// --- Sorter
|
||||
pub fn set_sorter(&mut self, sorter: FilesSorter) -> bool {
|
||||
|
|
@ -433,7 +431,7 @@ impl Tab {
|
|||
|
||||
applied |= match self.current.hovered {
|
||||
Some(ref h) if h.is_dir() => {
|
||||
self.history.get_mut(h.path()).map(|f| f.files.set_show_hidden(state)).unwrap_or(false)
|
||||
self.history.get_mut(h.url()).map(|f| f.files.set_show_hidden(state)) == Some(true)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use std::path::Path;
|
||||
|
||||
use config::{BOOT, MANAGER};
|
||||
use shared::Url;
|
||||
|
||||
use super::Tab;
|
||||
use crate::emit;
|
||||
|
|
@ -14,7 +13,7 @@ pub struct Tabs {
|
|||
|
||||
impl Tabs {
|
||||
pub fn make() -> Self {
|
||||
let mut tab = Tab::new(&BOOT.cwd);
|
||||
let mut tab = Tab::from(Url::from(&BOOT.cwd));
|
||||
tab.set_show_hidden(Some(MANAGER.show_hidden));
|
||||
|
||||
let mut tabs = Self { idx: usize::MAX, items: vec![tab] };
|
||||
|
|
@ -22,12 +21,12 @@ impl Tabs {
|
|||
tabs
|
||||
}
|
||||
|
||||
pub fn create(&mut self, path: &Path) -> bool {
|
||||
pub fn create(&mut self, url: &Url) -> bool {
|
||||
if self.items.len() >= MAX_TABS {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut tab = Tab::new(path);
|
||||
let mut tab = Tab::from(url);
|
||||
tab.set_show_hidden(Some(self.active().show_hidden));
|
||||
|
||||
self.items.insert(self.idx + 1, tab);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use std::{collections::BTreeSet, path::{Path, PathBuf}, sync::Arc, time::Duration};
|
||||
use std::{collections::BTreeSet, sync::Arc, time::Duration};
|
||||
|
||||
use futures::StreamExt;
|
||||
use indexmap::IndexMap;
|
||||
use notify::{event::{MetadataKind, ModifyKind}, EventKind, RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
|
||||
use parking_lot::RwLock;
|
||||
use shared::StreamBuf;
|
||||
use shared::{StreamBuf, Url};
|
||||
use tokio::{fs, sync::mpsc};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ use crate::{emit, external, files::{Files, FilesOp}};
|
|||
|
||||
pub struct Watcher {
|
||||
watcher: RecommendedWatcher,
|
||||
watched: Arc<RwLock<IndexMap<PathBuf, Option<PathBuf>>>>,
|
||||
watched: Arc<RwLock<IndexMap<Url, Option<Url>>>>,
|
||||
}
|
||||
|
||||
impl Watcher {
|
||||
|
|
@ -28,11 +28,11 @@ impl Watcher {
|
|||
return;
|
||||
};
|
||||
|
||||
let Some(path) = event.paths.first().cloned() else {
|
||||
let Some(path) = event.paths.first().map(Url::from) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let parent = path.parent().unwrap_or(&path).to_path_buf();
|
||||
let parent = path.parent_url().unwrap_or_else(|| path.clone());
|
||||
match event.kind {
|
||||
EventKind::Create(_) => {
|
||||
tx.send(parent).ok();
|
||||
|
|
@ -69,7 +69,7 @@ impl Watcher {
|
|||
instance
|
||||
}
|
||||
|
||||
pub(super) fn watch(&mut self, mut watched: BTreeSet<&PathBuf>) {
|
||||
pub(super) fn watch(&mut self, mut watched: BTreeSet<&Url>) {
|
||||
let (to_unwatch, to_watch): (BTreeSet<_>, BTreeSet<_>) = {
|
||||
let guard = self.watched.read();
|
||||
let keys = guard.keys().collect::<BTreeSet<_>>();
|
||||
|
|
@ -79,12 +79,12 @@ impl Watcher {
|
|||
)
|
||||
};
|
||||
|
||||
for p in to_unwatch {
|
||||
self.watcher.unwatch(&p).ok();
|
||||
for u in to_unwatch {
|
||||
self.watcher.unwatch(&u).ok();
|
||||
}
|
||||
for p in to_watch {
|
||||
if self.watcher.watch(&p, RecursiveMode::NonRecursive).is_err() {
|
||||
watched.remove(&p);
|
||||
for u in to_watch {
|
||||
if self.watcher.watch(&u, RecursiveMode::NonRecursive).is_err() {
|
||||
watched.remove(&u);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ impl Watcher {
|
|||
for k in to_resolve {
|
||||
match fs::canonicalize(&k).await {
|
||||
Ok(v) if v != *k => {
|
||||
ext.insert(k, Some(v));
|
||||
ext.insert(k, Some(Url::from(v)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -121,9 +121,9 @@ impl Watcher {
|
|||
});
|
||||
}
|
||||
|
||||
pub(super) fn trigger_dirs(&self, dirs: &[&Path]) {
|
||||
pub(super) fn trigger_dirs(&self, dirs: &[&Url]) {
|
||||
let watched = self.watched.clone();
|
||||
let dirs = dirs.iter().map(|p| p.to_path_buf()).collect::<Vec<_>>();
|
||||
let dirs: Vec<_> = dirs.iter().map(|&u| u.clone()).collect();
|
||||
tokio::spawn(async move {
|
||||
for dir in dirs {
|
||||
Self::dir_changed(&dir, watched.clone()).await;
|
||||
|
|
@ -132,8 +132,8 @@ impl Watcher {
|
|||
}
|
||||
|
||||
async fn changed(
|
||||
mut rx: StreamBuf<UnboundedReceiverStream<PathBuf>>,
|
||||
watched: Arc<RwLock<IndexMap<PathBuf, Option<PathBuf>>>>,
|
||||
mut rx: StreamBuf<UnboundedReceiverStream<Url>>,
|
||||
watched: Arc<RwLock<IndexMap<Url, Option<Url>>>>,
|
||||
) {
|
||||
while let Some(paths) = rx.next().await {
|
||||
let (mut files, mut dirs): (Vec<_>, Vec<_>) = Default::default();
|
||||
|
|
@ -145,7 +145,7 @@ impl Watcher {
|
|||
}
|
||||
}
|
||||
|
||||
Self::file_changed(files.iter().map(AsRef::as_ref).collect()).await;
|
||||
Self::file_changed(files.iter().collect()).await;
|
||||
for file in files {
|
||||
emit!(Files(FilesOp::IOErr(file)));
|
||||
}
|
||||
|
|
@ -156,24 +156,24 @@ impl Watcher {
|
|||
}
|
||||
}
|
||||
|
||||
async fn file_changed(paths: Vec<&Path>) {
|
||||
if let Ok(mimes) = external::file(&paths).await {
|
||||
async fn file_changed(urls: Vec<&Url>) {
|
||||
if let Ok(mimes) = external::file(&urls).await {
|
||||
emit!(Mimetype(mimes));
|
||||
}
|
||||
}
|
||||
|
||||
async fn dir_changed(path: &Path, watched: Arc<RwLock<IndexMap<PathBuf, Option<PathBuf>>>>) {
|
||||
async fn dir_changed(url: &Url, watched: Arc<RwLock<IndexMap<Url, Option<Url>>>>) {
|
||||
let linked = watched
|
||||
.read()
|
||||
.iter()
|
||||
.map_while(|(k, v)| v.as_ref().and_then(|v| path.strip_prefix(v).ok()).map(|v| k.join(v)))
|
||||
.map_while(|(k, v)| v.as_ref().and_then(|v| url.strip_prefix(v)).map(|v| k.join(v)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let result = Files::read_dir(path).await;
|
||||
let result = Files::read_dir(url).await;
|
||||
if linked.is_empty() {
|
||||
emit!(Files(match result {
|
||||
Ok(items) => FilesOp::Read(path.into(), items),
|
||||
Err(_) => FilesOp::IOErr(path.into()),
|
||||
Ok(items) => FilesOp::Read(url.clone(), items),
|
||||
Err(_) => FilesOp::IOErr(url.clone()),
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
|
@ -184,7 +184,7 @@ impl Watcher {
|
|||
let mut files = Vec::with_capacity(items.len());
|
||||
for item in items {
|
||||
let mut file = item.clone();
|
||||
file.set_path(ori.join(item.path().strip_prefix(path).unwrap()));
|
||||
file.set_url(ori.join(item.url().strip_prefix(url).unwrap()));
|
||||
files.push(file);
|
||||
}
|
||||
FilesOp::Read(ori, files)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use std::{ffi::OsStr, path::PathBuf, sync::Arc, time::Duration};
|
||||
use std::{ffi::OsStr, sync::Arc, time::Duration};
|
||||
|
||||
use async_channel::{Receiver, Sender};
|
||||
use config::{open::Opener, TASKS};
|
||||
use futures::{future::BoxFuture, FutureExt};
|
||||
use parking_lot::RwLock;
|
||||
use shared::{unique_path, Throttle};
|
||||
use shared::{unique_path, Throttle, Url};
|
||||
use tokio::{fs, select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, time::sleep};
|
||||
use tracing::{info, trace};
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ impl Scheduler {
|
|||
b
|
||||
}
|
||||
|
||||
pub(super) fn file_cut(&self, from: PathBuf, mut to: PathBuf, force: bool) {
|
||||
pub(super) fn file_cut(&self, from: Url, mut to: Url, force: bool) {
|
||||
let mut running = self.running.write();
|
||||
let id = running.add(format!("Cut {:?} to {:?}", from, to));
|
||||
|
||||
|
|
@ -218,7 +218,7 @@ impl Scheduler {
|
|||
});
|
||||
}
|
||||
|
||||
pub(super) fn file_copy(&self, from: PathBuf, mut to: PathBuf, force: bool, follow: bool) {
|
||||
pub(super) fn file_copy(&self, from: Url, mut to: Url, force: bool, follow: bool) {
|
||||
let name = format!("Copy {:?} to {:?}", from, to);
|
||||
let id = self.running.write().add(name);
|
||||
|
||||
|
|
@ -234,7 +234,7 @@ impl Scheduler {
|
|||
});
|
||||
}
|
||||
|
||||
pub(super) fn file_delete(&self, target: PathBuf) {
|
||||
pub(super) fn file_delete(&self, target: Url) {
|
||||
let mut running = self.running.write();
|
||||
let id = running.add(format!("Delete {:?}", target));
|
||||
|
||||
|
|
@ -262,7 +262,7 @@ impl Scheduler {
|
|||
});
|
||||
}
|
||||
|
||||
pub(super) fn file_trash(&self, target: PathBuf) {
|
||||
pub(super) fn file_trash(&self, target: Url) {
|
||||
let name = format!("Trash {:?}", target);
|
||||
let id = self.running.write().add(name);
|
||||
|
||||
|
|
@ -318,7 +318,7 @@ impl Scheduler {
|
|||
});
|
||||
}
|
||||
|
||||
pub(super) fn precache_size(&self, targets: Vec<&PathBuf>) {
|
||||
pub(super) fn precache_size(&self, targets: Vec<&Url>) {
|
||||
let throttle = Arc::new(Throttle::new(targets.len(), Duration::from_millis(300)));
|
||||
let mut handing = self.precache.size_handing.lock();
|
||||
let mut running = self.running.write();
|
||||
|
|
@ -343,7 +343,7 @@ impl Scheduler {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn precache_mime(&self, targets: Vec<PathBuf>) {
|
||||
pub(super) fn precache_mime(&self, targets: Vec<Url>) {
|
||||
let name = format!("Preload mimetype for {} files", targets.len());
|
||||
let id = self.running.write().add(name);
|
||||
|
||||
|
|
@ -356,21 +356,21 @@ impl Scheduler {
|
|||
});
|
||||
}
|
||||
|
||||
pub(super) fn precache_image(&self, targets: Vec<PathBuf>) {
|
||||
pub(super) fn precache_image(&self, targets: Vec<Url>) {
|
||||
let name = format!("Precache of {} image files", targets.len());
|
||||
let id = self.running.write().add(name);
|
||||
|
||||
self.precache.image(id, targets).ok();
|
||||
}
|
||||
|
||||
pub(super) fn precache_video(&self, targets: Vec<PathBuf>) {
|
||||
pub(super) fn precache_video(&self, targets: Vec<Url>) {
|
||||
let name = format!("Precache of {} video files", targets.len());
|
||||
let id = self.running.write().add(name);
|
||||
|
||||
self.precache.video(id, targets).ok();
|
||||
}
|
||||
|
||||
pub(super) fn precache_pdf(&self, targets: Vec<PathBuf>) {
|
||||
pub(super) fn precache_pdf(&self, targets: Vec<Url>) {
|
||||
let name = format!("Precache of {} PDF files", targets.len());
|
||||
let id = self.running.write().add(name);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, io::{stdout, Write}, path::{Path, PathBuf}, sync::Arc};
|
||||
use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, io::{stdout, Write}, path::Path, sync::Arc};
|
||||
|
||||
use config::{manager::SortBy, open::Opener, OPEN};
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use shared::{Defer, MimeKind, Term};
|
||||
use shared::{Defer, MimeKind, Term, Url};
|
||||
use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time};
|
||||
use tracing::trace;
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ impl Tasks {
|
|||
|
||||
pub fn paginate(&self) -> Vec<TaskSummary> {
|
||||
let running = self.scheduler.running.read();
|
||||
running.values().take(Self::limit()).map(|t| t.into()).collect()
|
||||
running.values().take(Self::limit()).map(Into::into).collect()
|
||||
}
|
||||
|
||||
pub fn inspect(&self) -> bool {
|
||||
|
|
@ -154,7 +154,7 @@ impl Tasks {
|
|||
false
|
||||
}
|
||||
|
||||
pub fn file_cut(&self, src: &HashSet<PathBuf>, dest: PathBuf, force: bool) -> bool {
|
||||
pub fn file_cut(&self, src: &HashSet<Url>, dest: Url, force: bool) -> bool {
|
||||
for p in src {
|
||||
let to = dest.join(p.file_name().unwrap());
|
||||
if force && *p == to {
|
||||
|
|
@ -166,13 +166,7 @@ impl Tasks {
|
|||
false
|
||||
}
|
||||
|
||||
pub fn file_copy(
|
||||
&self,
|
||||
src: &HashSet<PathBuf>,
|
||||
dest: PathBuf,
|
||||
force: bool,
|
||||
follow: bool,
|
||||
) -> bool {
|
||||
pub fn file_copy(&self, src: &HashSet<Url>, dest: Url, force: bool, follow: bool) -> bool {
|
||||
for p in src {
|
||||
let to = dest.join(p.file_name().unwrap());
|
||||
if force && *p == to {
|
||||
|
|
@ -184,7 +178,7 @@ impl Tasks {
|
|||
false
|
||||
}
|
||||
|
||||
pub fn file_remove(&self, targets: Vec<PathBuf>, permanently: bool) -> bool {
|
||||
pub fn file_remove(&self, targets: Vec<Url>, permanently: bool) -> bool {
|
||||
let scheduler = self.scheduler.clone();
|
||||
tokio::spawn(async move {
|
||||
let s = if targets.len() > 1 { 's' } else { ' ' };
|
||||
|
|
@ -217,7 +211,7 @@ impl Tasks {
|
|||
}
|
||||
|
||||
let targets: Vec<_> =
|
||||
targets.iter().filter(|f| f.is_dir() && f.length().is_none()).map(|f| f.path()).collect();
|
||||
targets.iter().filter(|f| f.is_dir() && f.length().is_none()).map(|f| f.url()).collect();
|
||||
|
||||
if !targets.is_empty() {
|
||||
self.scheduler.precache_size(targets);
|
||||
|
|
@ -227,11 +221,11 @@ impl Tasks {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn precache_mime(&self, targets: &[File], mimetype: &HashMap<PathBuf, String>) -> bool {
|
||||
pub fn precache_mime(&self, targets: &[File], mimetype: &HashMap<Url, String>) -> bool {
|
||||
let targets: Vec<_> = targets
|
||||
.iter()
|
||||
.filter(|f| f.is_file() && !mimetype.contains_key(f.path()))
|
||||
.map(|f| f.path_owned())
|
||||
.filter(|f| f.is_file() && !mimetype.contains_key(f.url()))
|
||||
.map(|f| f.url_owned())
|
||||
.collect();
|
||||
|
||||
if !targets.is_empty() {
|
||||
|
|
@ -240,12 +234,12 @@ impl Tasks {
|
|||
false
|
||||
}
|
||||
|
||||
pub fn precache_image(&self, mimetype: &BTreeMap<PathBuf, String>) -> bool {
|
||||
let targets = mimetype
|
||||
pub fn precache_image(&self, mimetype: &BTreeMap<Url, String>) -> bool {
|
||||
let targets: Vec<_> = mimetype
|
||||
.iter()
|
||||
.filter(|(_, m)| MimeKind::new(m) == MimeKind::Image)
|
||||
.map(|(p, _)| p.clone())
|
||||
.collect::<Vec<_>>();
|
||||
.map(|(u, _)| u.clone())
|
||||
.collect();
|
||||
|
||||
if !targets.is_empty() {
|
||||
self.scheduler.precache_image(targets);
|
||||
|
|
@ -253,12 +247,12 @@ impl Tasks {
|
|||
false
|
||||
}
|
||||
|
||||
pub fn precache_video(&self, mimetype: &BTreeMap<PathBuf, String>) -> bool {
|
||||
let targets = mimetype
|
||||
pub fn precache_video(&self, mimetype: &BTreeMap<Url, String>) -> bool {
|
||||
let targets: Vec<_> = mimetype
|
||||
.iter()
|
||||
.filter(|(_, m)| MimeKind::new(m) == MimeKind::Video)
|
||||
.map(|(p, _)| p.clone())
|
||||
.collect::<Vec<_>>();
|
||||
.map(|(u, _)| u.clone())
|
||||
.collect();
|
||||
|
||||
if !targets.is_empty() {
|
||||
self.scheduler.precache_video(targets);
|
||||
|
|
@ -266,12 +260,12 @@ impl Tasks {
|
|||
false
|
||||
}
|
||||
|
||||
pub fn precache_pdf(&self, mimetype: &BTreeMap<PathBuf, String>) -> bool {
|
||||
let targets = mimetype
|
||||
pub fn precache_pdf(&self, mimetype: &BTreeMap<Url, String>) -> bool {
|
||||
let targets: Vec<_> = mimetype
|
||||
.iter()
|
||||
.filter(|(_, m)| MimeKind::new(m) == MimeKind::PDF)
|
||||
.map(|(p, _)| p.clone())
|
||||
.collect::<Vec<_>>();
|
||||
.map(|(u, _)| u.clone())
|
||||
.collect();
|
||||
|
||||
if !targets.is_empty() {
|
||||
self.scheduler.precache_pdf(targets);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::{collections::VecDeque, fs::Metadata, path::{Path, PathBuf}};
|
|||
use anyhow::Result;
|
||||
use config::TASKS;
|
||||
use futures::{future::BoxFuture, FutureExt};
|
||||
use shared::{calculate_size, copy_with_progress};
|
||||
use shared::{calculate_size, copy_with_progress, Url};
|
||||
use tokio::{fs, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc};
|
||||
use tracing::trace;
|
||||
|
||||
|
|
@ -27,8 +27,8 @@ pub(crate) enum FileOp {
|
|||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct FileOpPaste {
|
||||
pub id: usize,
|
||||
pub from: PathBuf,
|
||||
pub to: PathBuf,
|
||||
pub from: Url,
|
||||
pub to: Url,
|
||||
pub cut: bool,
|
||||
pub follow: bool,
|
||||
pub retry: u8,
|
||||
|
|
@ -37,8 +37,8 @@ pub(crate) struct FileOpPaste {
|
|||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct FileOpLink {
|
||||
pub id: usize,
|
||||
pub from: PathBuf,
|
||||
pub to: PathBuf,
|
||||
pub from: Url,
|
||||
pub to: Url,
|
||||
pub cut: bool,
|
||||
pub length: u64,
|
||||
}
|
||||
|
|
@ -46,14 +46,14 @@ pub(crate) struct FileOpLink {
|
|||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct FileOpDelete {
|
||||
pub id: usize,
|
||||
pub target: PathBuf,
|
||||
pub target: Url,
|
||||
pub length: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct FileOpTrash {
|
||||
pub id: usize,
|
||||
pub target: PathBuf,
|
||||
pub target: Url,
|
||||
pub length: u64,
|
||||
}
|
||||
|
||||
|
|
@ -220,7 +220,7 @@ impl File {
|
|||
};
|
||||
|
||||
while let Ok(Some(entry)) = it.next_entry().await {
|
||||
let src = entry.path();
|
||||
let src = Url::from(entry.path());
|
||||
let Ok(meta) = Self::metadata(&src, task.follow).await else {
|
||||
continue;
|
||||
};
|
||||
|
|
@ -256,7 +256,7 @@ impl File {
|
|||
|
||||
let mut dirs = VecDeque::from([task.target]);
|
||||
while let Some(target) = dirs.pop_front() {
|
||||
let mut it = match fs::read_dir(target).await {
|
||||
let mut it = match fs::read_dir(&target).await {
|
||||
Ok(it) => it,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
|
@ -268,11 +268,11 @@ impl File {
|
|||
};
|
||||
|
||||
if meta.is_dir() {
|
||||
dirs.push_front(entry.path());
|
||||
dirs.push_front(Url::new(entry.path(), &target));
|
||||
continue;
|
||||
}
|
||||
|
||||
task.target = entry.path();
|
||||
task.target = Url::new(entry.path(), &target);
|
||||
task.length = meta.len();
|
||||
self.sch.send(TaskOp::New(task.id, meta.len()))?;
|
||||
self.tx.send(FileOp::Delete(task.clone())).await?;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use std::{collections::{BTreeMap, BTreeSet}, path::PathBuf, sync::Arc};
|
||||
use std::{collections::{BTreeMap, BTreeSet}, sync::Arc};
|
||||
|
||||
use adaptor::Image;
|
||||
use anyhow::Result;
|
||||
use config::PREVIEW;
|
||||
use parking_lot::Mutex;
|
||||
use shared::{calculate_size, Throttle};
|
||||
use shared::{calculate_size, Throttle, Url};
|
||||
use tokio::{fs, sync::mpsc};
|
||||
|
||||
use crate::{emit, external, files::FilesOp, tasks::TaskOp};
|
||||
|
|
@ -15,7 +15,7 @@ pub(crate) struct Precache {
|
|||
|
||||
sch: mpsc::UnboundedSender<TaskOp>,
|
||||
|
||||
pub(crate) size_handing: Mutex<BTreeSet<PathBuf>>,
|
||||
pub(crate) size_handing: Mutex<BTreeSet<Url>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -28,32 +28,32 @@ pub(crate) enum PrecacheOp {
|
|||
#[derive(Debug)]
|
||||
pub(crate) struct PrecacheOpSize {
|
||||
pub id: usize,
|
||||
pub target: PathBuf,
|
||||
pub throttle: Arc<Throttle<(PathBuf, u64)>>,
|
||||
pub target: Url,
|
||||
pub throttle: Arc<Throttle<(Url, u64)>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PrecacheOpMime {
|
||||
pub id: usize,
|
||||
pub targets: Vec<PathBuf>,
|
||||
pub targets: Vec<Url>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PrecacheOpImage {
|
||||
pub id: usize,
|
||||
pub target: PathBuf,
|
||||
pub target: Url,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PrecacheOpVideo {
|
||||
pub id: usize,
|
||||
pub target: PathBuf,
|
||||
pub target: Url,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PrecacheOpPDF {
|
||||
pub id: usize,
|
||||
pub target: PathBuf,
|
||||
pub target: Url,
|
||||
}
|
||||
|
||||
impl Precache {
|
||||
|
|
@ -79,7 +79,7 @@ impl Precache {
|
|||
return Ok(self.sch.send(TaskOp::Adv(task.id, 1, 0))?);
|
||||
}
|
||||
if let Ok(img) = fs::read(&task.target).await {
|
||||
Image::precache(img.into(), cache).await.ok();
|
||||
Image::precache(Arc::new(img), cache).await.ok();
|
||||
}
|
||||
self.sch.send(TaskOp::Adv(task.id, 1, 0))?;
|
||||
}
|
||||
|
|
@ -128,7 +128,7 @@ impl Precache {
|
|||
handing.remove(path);
|
||||
}
|
||||
|
||||
let parent = buf[0].0.parent().unwrap().to_path_buf();
|
||||
let parent = buf[0].0.parent_url().unwrap();
|
||||
emit!(Files(FilesOp::Size(parent, BTreeMap::from_iter(buf))));
|
||||
});
|
||||
|
||||
|
|
@ -136,7 +136,7 @@ impl Precache {
|
|||
self.done(task.id)
|
||||
}
|
||||
|
||||
pub(crate) fn image(&self, id: usize, targets: Vec<PathBuf>) -> Result<()> {
|
||||
pub(crate) fn image(&self, id: usize, targets: Vec<Url>) -> Result<()> {
|
||||
for target in targets {
|
||||
self.sch.send(TaskOp::New(id, 0))?;
|
||||
self.tx.send_blocking(PrecacheOp::Image(PrecacheOpImage { id, target }))?;
|
||||
|
|
@ -144,7 +144,7 @@ impl Precache {
|
|||
self.done(id)
|
||||
}
|
||||
|
||||
pub(crate) fn video(&self, id: usize, targets: Vec<PathBuf>) -> Result<()> {
|
||||
pub(crate) fn video(&self, id: usize, targets: Vec<Url>) -> Result<()> {
|
||||
for target in targets {
|
||||
self.sch.send(TaskOp::New(id, 0))?;
|
||||
self.tx.send_blocking(PrecacheOp::Video(PrecacheOpVideo { id, target }))?;
|
||||
|
|
@ -152,7 +152,7 @@ impl Precache {
|
|||
self.done(id)
|
||||
}
|
||||
|
||||
pub(crate) fn pdf(&self, id: usize, targets: Vec<PathBuf>) -> Result<()> {
|
||||
pub(crate) fn pdf(&self, id: usize, targets: Vec<Url>) -> Result<()> {
|
||||
for target in targets {
|
||||
self.sch.send(TaskOp::New(id, 0))?;
|
||||
self.tx.send_blocking(PrecacheOp::Pdf(PrecacheOpPDF { id, target }))?;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ use std::{env, path::{Path, PathBuf}};
|
|||
|
||||
use tokio::fs;
|
||||
|
||||
use crate::Url;
|
||||
|
||||
pub fn absolute_path(p: impl AsRef<Path>) -> PathBuf {
|
||||
let p = p.as_ref();
|
||||
if let Ok(p) = p.strip_prefix("~") {
|
||||
|
|
@ -12,6 +14,12 @@ pub fn absolute_path(p: impl AsRef<Path>) -> PathBuf {
|
|||
std::fs::canonicalize(p).unwrap_or_else(|_| p.to_path_buf())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn absolute_url(mut u: Url) -> Url {
|
||||
u.set_path(absolute_path(&u));
|
||||
u
|
||||
}
|
||||
|
||||
pub fn readable_path(p: &Path, base: &Path) -> String {
|
||||
if let Ok(p) = p.strip_prefix(base) {
|
||||
return p.display().to_string();
|
||||
|
|
@ -39,7 +47,7 @@ pub fn readable_size(size: u64) -> String {
|
|||
format!("{:.1} {}", size, units[i])
|
||||
}
|
||||
|
||||
pub async fn unique_path(mut p: PathBuf) -> PathBuf {
|
||||
pub async fn unique_path(mut p: Url) -> Url {
|
||||
let Some(name) = p.file_name().map(|n| n.to_os_string()) else {
|
||||
return p;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ mod stream;
|
|||
mod term;
|
||||
mod throttle;
|
||||
mod time;
|
||||
mod url;
|
||||
|
||||
pub use chars::*;
|
||||
pub use defer::*;
|
||||
|
|
@ -23,3 +24,4 @@ pub use stream::*;
|
|||
pub use term::*;
|
||||
pub use throttle::*;
|
||||
pub use time::*;
|
||||
pub use url::*;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ pub enum MimeKind {
|
|||
Empty,
|
||||
|
||||
Archive,
|
||||
Dir,
|
||||
|
||||
Image,
|
||||
Video,
|
||||
|
|
@ -19,9 +18,7 @@ pub enum MimeKind {
|
|||
|
||||
impl MimeKind {
|
||||
pub fn new(s: &str) -> Self {
|
||||
if s == MIME_DIR {
|
||||
Self::Dir
|
||||
} else if s.starts_with("text/") || s.ends_with("/xml") {
|
||||
if s.starts_with("text/") || s.ends_with("/xml") {
|
||||
Self::Text
|
||||
} else if s.starts_with("image/") {
|
||||
Self::Image
|
||||
|
|
|
|||
100
shared/src/url.rs
Normal file
100
shared/src/url.rs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
use std::{ffi::{OsStr, OsString}, ops::{Deref, DerefMut}, path::{Path, PathBuf}};
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct Url {
|
||||
scheme: UrlScheme,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub enum UrlScheme {
|
||||
#[default]
|
||||
None,
|
||||
Search,
|
||||
Archive,
|
||||
}
|
||||
|
||||
impl Deref for Url {
|
||||
type Target = PathBuf;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.path }
|
||||
}
|
||||
|
||||
impl DerefMut for Url {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.path }
|
||||
}
|
||||
|
||||
impl From<PathBuf> for Url {
|
||||
fn from(path: PathBuf) -> Self { Self { path, ..Default::default() } }
|
||||
}
|
||||
|
||||
impl From<&PathBuf> for Url {
|
||||
fn from(path: &PathBuf) -> Self { Self::from(path.clone()) }
|
||||
}
|
||||
|
||||
impl From<&Path> for Url {
|
||||
fn from(path: &Path) -> Self { Self::from(path.to_path_buf()) }
|
||||
}
|
||||
|
||||
impl From<String> for Url {
|
||||
fn from(path: String) -> Self { Self::from(PathBuf::from(path)) }
|
||||
}
|
||||
|
||||
impl From<&String> for Url {
|
||||
fn from(path: &String) -> Self { Self::from(PathBuf::from(path)) }
|
||||
}
|
||||
|
||||
impl From<&str> for Url {
|
||||
fn from(path: &str) -> Self { Self::from(PathBuf::from(path)) }
|
||||
}
|
||||
|
||||
impl AsRef<Url> for Url {
|
||||
fn as_ref(&self) -> &Url { self }
|
||||
}
|
||||
|
||||
impl AsRef<Path> for Url {
|
||||
fn as_ref(&self) -> &Path { &self.path }
|
||||
}
|
||||
|
||||
impl AsRef<OsStr> for Url {
|
||||
fn as_ref(&self) -> &OsStr { self.path.as_os_str() }
|
||||
}
|
||||
|
||||
impl Url {
|
||||
#[inline]
|
||||
pub fn new(url: impl Into<Url>, ctx: &Url) -> Self {
|
||||
let mut url: Self = url.into();
|
||||
url.scheme = ctx.scheme;
|
||||
url
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn join(&self, path: impl AsRef<Path>) -> Self { Self::new(self.path.join(path), self) }
|
||||
|
||||
#[inline]
|
||||
pub fn strip_prefix(&self, base: impl AsRef<Path>) -> Option<&Path> {
|
||||
self.path.strip_prefix(base).ok()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_os_string(self) -> OsString { self.path.into_os_string() }
|
||||
}
|
||||
|
||||
impl Url {
|
||||
// --- Scheme
|
||||
#[inline]
|
||||
pub fn is_none(&self) -> bool { self.scheme == UrlScheme::None }
|
||||
|
||||
#[inline]
|
||||
pub fn is_search(&self) -> bool { self.scheme == UrlScheme::Search }
|
||||
|
||||
#[inline]
|
||||
pub fn is_archive(&self) -> bool { self.scheme == UrlScheme::Archive }
|
||||
|
||||
// --- Path
|
||||
#[inline]
|
||||
pub fn set_path(&mut self, path: PathBuf) { self.path = path; }
|
||||
|
||||
#[inline]
|
||||
pub fn parent_url(&self) -> Option<Url> { self.path.parent().map(|p| Self::new(p, self)) }
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue