mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
refactor: simplify filesystem design to improve performance
This commit is contained in:
parent
a36dabfe85
commit
6f66eee596
14 changed files with 277 additions and 212 deletions
|
|
@ -131,7 +131,7 @@ impl App {
|
||||||
let calc = matches!(op, FilesOp::Read(..) | FilesOp::Search(..));
|
let calc = matches!(op, FilesOp::Read(..) | FilesOp::Search(..));
|
||||||
let b = match op {
|
let b = match op {
|
||||||
FilesOp::Read(..) => manager.update_read(op),
|
FilesOp::Read(..) => manager.update_read(op),
|
||||||
FilesOp::Sort(..) => manager.update_read(op),
|
FilesOp::Size(..) => manager.update_read(op),
|
||||||
FilesOp::Search(..) => manager.update_search(op),
|
FilesOp::Search(..) => manager.update_search(op),
|
||||||
FilesOp::IOErr(..) => manager.update_ioerr(op),
|
FilesOp::IOErr(..) => manager.update_ioerr(op),
|
||||||
};
|
};
|
||||||
|
|
@ -144,7 +144,7 @@ impl App {
|
||||||
}
|
}
|
||||||
Event::Pages(page) => {
|
Event::Pages(page) => {
|
||||||
if manager.current().page == page {
|
if manager.current().page == page {
|
||||||
let targets = self.cx.manager.current().paginate().into_iter().map(|(_, f)| f).collect();
|
let targets = self.cx.manager.current().paginate();
|
||||||
tasks.precache_mime(targets, &self.cx.manager.mimetype);
|
tasks.precache_mime(targets, &self.cx.manager.mimetype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,16 +56,17 @@ impl<'a> Widget for Folder<'a> {
|
||||||
let items = window
|
let items = window
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, (k, v))| {
|
.map(|(i, f)| {
|
||||||
let icon = THEME
|
let icon = THEME
|
||||||
.icons
|
.icons
|
||||||
.iter()
|
.iter()
|
||||||
.find(|x| x.name.match_path(k, Some(v.meta.is_dir())))
|
.find(|x| x.name.match_path(&f.path, Some(f.meta.is_dir())))
|
||||||
.map(|x| x.display.as_ref())
|
.map(|x| x.display.as_ref())
|
||||||
.unwrap_or("");
|
.unwrap_or("");
|
||||||
|
|
||||||
if (!self.is_selection && v.is_selected)
|
let is_selected = self.folder.files.is_selected(&f.path);
|
||||||
|| (self.is_selection && mode.pending(i, v.is_selected))
|
if (!self.is_selection && is_selected)
|
||||||
|
|| (self.is_selection && mode.pending(i, is_selected))
|
||||||
{
|
{
|
||||||
buf.set_style(
|
buf.set_style(
|
||||||
Rect { x: area.x.saturating_sub(1), y: i as u16 + 1, width: 1, height: 1 },
|
Rect { x: area.x.saturating_sub(1), y: i as u16 + 1, width: 1, height: 1 },
|
||||||
|
|
@ -77,17 +78,17 @@ impl<'a> Widget for Folder<'a> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let hovered = matches!(self.folder.hovered, Some(ref h) if h.path == *k);
|
let hovered = matches!(self.folder.hovered, Some(ref h) if h.path == f.path);
|
||||||
let style = if self.is_preview && hovered {
|
let style = if self.is_preview && hovered {
|
||||||
THEME.preview.hovered.get()
|
THEME.preview.hovered.get()
|
||||||
} else if hovered {
|
} else if hovered {
|
||||||
THEME.selection.hovered.get()
|
THEME.selection.hovered.get()
|
||||||
} else {
|
} else {
|
||||||
self.file_style(v)
|
self.file_style(f)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut path = format!(" {icon} {}", readable_path(k, &self.folder.cwd));
|
let mut path = format!(" {icon} {}", readable_path(&f.path, &self.folder.cwd));
|
||||||
if let Some(ref link_to) = v.link_to {
|
if let Some(ref link_to) = f.link_to {
|
||||||
if MANAGER.show_symlink {
|
if MANAGER.show_symlink {
|
||||||
path.push_str(&format!(" -> {}", link_to.display()));
|
path.push_str(&format!(" -> {}", link_to.display()));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ pub struct File {
|
||||||
pub link_to: Option<PathBuf>,
|
pub link_to: Option<PathBuf>,
|
||||||
pub is_link: bool,
|
pub is_link: bool,
|
||||||
pub is_hidden: bool,
|
pub is_hidden: bool,
|
||||||
pub is_selected: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl File {
|
impl File {
|
||||||
|
|
@ -32,7 +31,7 @@ impl File {
|
||||||
|
|
||||||
let length = if meta.is_dir() { None } else { Some(meta.len()) };
|
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);
|
let is_hidden = path.file_name().map(|s| s.to_string_lossy().starts_with('.')).unwrap_or(false);
|
||||||
File { path: path.to_path_buf(), meta, length, link_to, is_link, is_hidden, is_selected: false }
|
File { path: path.to_path_buf(), meta, length, link_to, is_link, is_hidden }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,8 +40,8 @@ impl File {
|
||||||
pub fn path(&self) -> PathBuf { self.path.clone() }
|
pub fn path(&self) -> PathBuf { self.path.clone() }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_path(mut self, path: &Path) -> Self {
|
pub fn set_path(mut self, path: PathBuf) -> Self {
|
||||||
self.path = path.to_path_buf();
|
self.path = path;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,20 @@
|
||||||
use std::{collections::BTreeMap, ops::{Deref, DerefMut}, path::{Path, PathBuf}};
|
use std::{collections::{BTreeMap, BTreeSet}, ops::Range, path::{Path, PathBuf}};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use config::MANAGER;
|
use config::MANAGER;
|
||||||
use indexmap::IndexMap;
|
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
use super::{File, FilesSorter};
|
use super::{File, FilesSorter, NonHiddenFiles};
|
||||||
|
|
||||||
pub struct Files {
|
pub struct Files {
|
||||||
items: IndexMap<PathBuf, File>,
|
items: Vec<File>,
|
||||||
|
length: usize,
|
||||||
|
|
||||||
|
sizes: BTreeMap<PathBuf, u64>,
|
||||||
|
selected: BTreeSet<PathBuf>,
|
||||||
|
|
||||||
pub sorter: FilesSorter,
|
pub sorter: FilesSorter,
|
||||||
|
// TODO: XXX
|
||||||
pub show_hidden: bool,
|
pub show_hidden: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -17,6 +22,11 @@ impl Default for Files {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
items: Default::default(),
|
items: Default::default(),
|
||||||
|
length: Default::default(),
|
||||||
|
|
||||||
|
sizes: Default::default(),
|
||||||
|
selected: Default::default(),
|
||||||
|
|
||||||
sorter: Default::default(),
|
sorter: Default::default(),
|
||||||
show_hidden: MANAGER.show_hidden,
|
show_hidden: MANAGER.show_hidden,
|
||||||
}
|
}
|
||||||
|
|
@ -24,86 +34,111 @@ impl Default for Files {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Files {
|
impl Files {
|
||||||
pub async fn read(paths: Vec<PathBuf>) -> BTreeMap<PathBuf, File> {
|
pub async fn read(paths: &[impl AsRef<Path>]) -> Vec<File> {
|
||||||
let mut items = BTreeMap::new();
|
let mut items = Vec::with_capacity(paths.len());
|
||||||
for path in paths {
|
for path in paths {
|
||||||
if let Ok(file) = File::from(&path).await {
|
if let Ok(file) = File::from(path.as_ref()).await {
|
||||||
items.insert(path, file);
|
items.push(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items
|
items
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_dir(path: &Path) -> Result<BTreeMap<PathBuf, File>> {
|
pub async fn read_dir(path: &Path) -> Result<Vec<File>> {
|
||||||
let mut it = fs::read_dir(path).await?;
|
let mut it = fs::read_dir(path).await?;
|
||||||
let mut items = BTreeMap::new();
|
let mut items = Vec::new();
|
||||||
while let Ok(Some(item)) = it.next_entry().await {
|
while let Ok(Some(item)) = it.next_entry().await {
|
||||||
if let Ok(meta) = item.metadata().await {
|
if let Ok(meta) = item.metadata().await {
|
||||||
let path = item.path();
|
items.push(File::from_meta(&item.path(), meta).await);
|
||||||
let file = File::from_meta(&path, meta).await;
|
|
||||||
items.insert(path, file);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(items)
|
Ok(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn duplicate(&self, idx: usize) -> Option<File> {
|
pub fn select(&mut self, path: &Path, state: Option<bool>) -> bool {
|
||||||
self.items.get_index(idx).map(|(_, file)| file.clone())
|
let old = self.selected.contains(path);
|
||||||
|
let new = if let Some(new) = state { new } else { !old };
|
||||||
|
|
||||||
|
if new == old {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if new {
|
||||||
|
self.selected.insert(path.to_owned());
|
||||||
|
} else {
|
||||||
|
self.selected.remove(path);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_many(&mut self, path: Option<&Path>, state: Option<bool>) -> bool {
|
||||||
|
if let Some(path) = path {
|
||||||
|
return self.select(path, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut applied = false;
|
||||||
|
for item in self.iter() {
|
||||||
|
todo!();
|
||||||
|
// applied |= self.select(&item.path, state);
|
||||||
|
}
|
||||||
|
applied
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_index(&mut self, indices: &BTreeSet<usize>, state: Option<bool>) -> bool {
|
||||||
|
let mut applied = false;
|
||||||
|
for item in self.pick(indices) {
|
||||||
|
todo!();
|
||||||
|
// applied |= self.select(&item.path, state);
|
||||||
|
}
|
||||||
|
applied
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_sorter(&mut self, sort: FilesSorter) -> bool {
|
pub fn set_sorter(&mut self, sorter: FilesSorter) -> bool {
|
||||||
if self.sorter == sort {
|
if self.sorter == sorter {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
self.sorter = sort;
|
self.sorter = sorter;
|
||||||
self.sorter.sort(&mut self.items)
|
self.sorter.sort(&mut self.items)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_read(&mut self, mut items: BTreeMap<PathBuf, File>) -> bool {
|
#[inline]
|
||||||
if !self.show_hidden {
|
pub fn set_show_hidden(&mut self, state: bool) -> bool {
|
||||||
items.retain(|_, item| !item.is_hidden);
|
if self.show_hidden == state {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (path, item) in &mut items {
|
self.length =
|
||||||
if let Some(old) = self.items.get(path) {
|
if state { self.items.len() } else { self.items.iter().filter(|f| !f.is_hidden).count() };
|
||||||
item.is_selected = old.is_selected;
|
self.show_hidden = state;
|
||||||
|
true
|
||||||
// Calculate the size of directories is expensive, so we keep the old value,
|
|
||||||
// before a new value is calculated and comes to.
|
|
||||||
if item.meta.is_dir() {
|
|
||||||
item.length = old.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.items.clear();
|
pub fn update_read(&mut self, mut items: Vec<File>) -> bool {
|
||||||
self.items.extend(items);
|
self.sorter.sort(&mut items);
|
||||||
|
self.length =
|
||||||
|
if self.show_hidden { items.len() } else { items.iter().filter(|f| !f.is_hidden).count() };
|
||||||
|
self.items = items;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_size(&mut self, items: BTreeMap<PathBuf, u64>) -> bool {
|
||||||
|
self.sizes.extend(items);
|
||||||
self.sorter.sort(&mut self.items);
|
self.sorter.sort(&mut self.items);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_sort(&mut self, mut items: BTreeMap<PathBuf, File>) -> bool {
|
pub fn update_search(&mut self, items: Vec<File>) -> bool {
|
||||||
for (path, item) in &mut items {
|
|
||||||
if let Some(old) = self.items.get(path) {
|
|
||||||
item.is_selected = old.is_selected;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.items.extend(items);
|
|
||||||
self.sorter.sort(&mut self.items);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_search(&mut self, items: BTreeMap<PathBuf, File>) -> bool {
|
|
||||||
if !items.is_empty() {
|
if !items.is_empty() {
|
||||||
|
self.length = items.len();
|
||||||
self.items.extend(items);
|
self.items.extend(items);
|
||||||
self.sorter.sort(&mut self.items);
|
self.sorter.sort(&mut self.items);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.items.is_empty() {
|
if !self.items.is_empty() {
|
||||||
|
self.length = 0;
|
||||||
self.items.clear();
|
self.items.clear();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -112,39 +147,64 @@ impl Files {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Files {
|
impl Files {
|
||||||
type Target = IndexMap<PathBuf, File>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target { &self.items }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for Files {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.items }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum FilesOp {
|
|
||||||
Read(PathBuf, BTreeMap<PathBuf, File>),
|
|
||||||
Sort(PathBuf, BTreeMap<PathBuf, File>),
|
|
||||||
Search(PathBuf, BTreeMap<PathBuf, File>),
|
|
||||||
IOErr(PathBuf),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FilesOp {
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn path(&self) -> PathBuf {
|
pub fn len(&self) -> usize { self.length }
|
||||||
match self {
|
|
||||||
Self::Read(path, _) => path,
|
#[inline]
|
||||||
Self::Sort(path, _) => path,
|
pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a File> + 'a> {
|
||||||
Self::Search(path, _) => path,
|
if self.show_hidden {
|
||||||
Self::IOErr(path) => path,
|
return Box::new(self.items.iter());
|
||||||
}
|
}
|
||||||
.clone()
|
Box::new(NonHiddenFiles::new(&self.items, self.length))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn read_empty(path: &Path) -> Self { Self::Read(path.to_path_buf(), BTreeMap::new()) }
|
pub fn range(&self, range: Range<usize>) -> Vec<&File> {
|
||||||
|
self.iter().skip(range.start).take(range.end - range.start).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pick<'a>(&'a self, indices: &BTreeSet<usize>) -> Vec<&'a File> {
|
||||||
|
let mut items = Vec::with_capacity(indices.len());
|
||||||
|
for (i, item) in self.iter().enumerate() {
|
||||||
|
if indices.contains(&i) {
|
||||||
|
items.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn search_empty(path: &Path) -> Self { Self::Search(path.to_path_buf(), BTreeMap::new()) }
|
pub fn position(&self, path: &Path) -> Option<usize> { self.iter().position(|f| f.path == path) }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn duplicate(&self, idx: usize) -> Option<File> { self.items.get(idx).cloned() }
|
||||||
|
|
||||||
|
pub fn selected(&self, pending: &BTreeSet<usize>, unset: bool) -> Vec<&File> {
|
||||||
|
if self.selected.is_empty() && (unset || pending.is_empty()) {
|
||||||
|
return Default::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut items = Vec::with_capacity(self.selected.len() + pending.len());
|
||||||
|
for (i, item) in self.iter().enumerate() {
|
||||||
|
let b = self.selected.contains(&item.path);
|
||||||
|
if !unset && (b || pending.contains(&i)) {
|
||||||
|
items.push(item);
|
||||||
|
} else if unset && b && !pending.contains(&i) {
|
||||||
|
items.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_selected(&self, path: &Path) -> bool { self.selected.contains(path) }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn has_selected(&self) -> bool {
|
||||||
|
if self.selected.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.iter().any(|f| self.selected.contains(&f.path))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
29
core/src/files/iterator.rs
Normal file
29
core/src/files/iterator.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
use super::File;
|
||||||
|
|
||||||
|
pub struct NonHiddenFiles<'a> {
|
||||||
|
items: &'a Vec<File>,
|
||||||
|
|
||||||
|
cur: usize,
|
||||||
|
max: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> NonHiddenFiles<'a> {
|
||||||
|
pub fn new(items: &'a Vec<File>, max: usize) -> Self { Self { items, cur: 0, max } }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for NonHiddenFiles<'a> {
|
||||||
|
type Item = &'a File;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
while self.cur < self.items.len() {
|
||||||
|
let item = &self.items[self.cur];
|
||||||
|
self.cur += 1;
|
||||||
|
if !item.is_hidden {
|
||||||
|
return Some(&item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) { (self.max, Some(self.max)) }
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
mod file;
|
mod file;
|
||||||
mod files;
|
mod files;
|
||||||
|
mod iterator;
|
||||||
|
mod op;
|
||||||
mod sorter;
|
mod sorter;
|
||||||
|
|
||||||
pub use file::*;
|
pub use file::*;
|
||||||
pub use files::*;
|
pub use files::*;
|
||||||
|
pub use iterator::*;
|
||||||
|
pub use op::*;
|
||||||
pub use sorter::*;
|
pub use sorter::*;
|
||||||
|
|
|
||||||
30
core/src/files/op.rs
Normal file
30
core/src/files/op.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
use std::{collections::BTreeMap, path::{Path, PathBuf}};
|
||||||
|
|
||||||
|
use super::File;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FilesOp {
|
||||||
|
Read(PathBuf, Vec<File>),
|
||||||
|
Size(PathBuf, BTreeMap<PathBuf, u64>),
|
||||||
|
Search(PathBuf, Vec<File>),
|
||||||
|
IOErr(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FilesOp {
|
||||||
|
#[inline]
|
||||||
|
pub fn path(&self) -> PathBuf {
|
||||||
|
match self {
|
||||||
|
Self::Read(path, _) => path,
|
||||||
|
Self::Size(path, _) => path,
|
||||||
|
Self::Search(path, _) => path,
|
||||||
|
Self::IOErr(path) => path,
|
||||||
|
}
|
||||||
|
.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()) }
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use std::{cmp::Ordering, path::PathBuf};
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use config::{manager::SortBy, MANAGER};
|
use config::{manager::SortBy, MANAGER};
|
||||||
use indexmap::IndexMap;
|
|
||||||
|
|
||||||
use super::File;
|
use super::File;
|
||||||
|
|
||||||
|
|
@ -23,41 +22,41 @@ impl Default for FilesSorter {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilesSorter {
|
impl FilesSorter {
|
||||||
pub(super) fn sort(&self, items: &mut IndexMap<PathBuf, File>) -> bool {
|
pub(super) fn sort(&self, items: &mut Vec<File>) -> bool {
|
||||||
if items.is_empty() {
|
if items.is_empty() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.by {
|
match self.by {
|
||||||
SortBy::Alphabetical => {
|
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.path, &b.path, self.promote(a, b)))
|
||||||
}
|
}
|
||||||
SortBy::Created => items.sort_unstable_by(|_, a, _, b| {
|
SortBy::Created => items.sort_unstable_by(|a, b| {
|
||||||
if let (Ok(aa), Ok(bb)) = (a.meta.created(), b.meta.created()) {
|
if let (Ok(aa), Ok(bb)) = (a.meta.created(), b.meta.created()) {
|
||||||
return self.cmp(aa, bb, self.promote(a, b));
|
return self.cmp(aa, bb, self.promote(a, b));
|
||||||
}
|
}
|
||||||
Ordering::Equal
|
Ordering::Equal
|
||||||
}),
|
}),
|
||||||
SortBy::Modified => items.sort_unstable_by(|_, a, _, b| {
|
SortBy::Modified => items.sort_unstable_by(|a, b| {
|
||||||
if let (Ok(aa), Ok(bb)) = (a.meta.modified(), b.meta.modified()) {
|
if let (Ok(aa), Ok(bb)) = (a.meta.modified(), b.meta.modified()) {
|
||||||
return self.cmp(aa, bb, self.promote(a, b));
|
return self.cmp(aa, bb, self.promote(a, b));
|
||||||
}
|
}
|
||||||
Ordering::Equal
|
Ordering::Equal
|
||||||
}),
|
}),
|
||||||
SortBy::Natural => self.sort_naturally(items),
|
SortBy::Natural => self.sort_naturally(items),
|
||||||
SortBy::Size => items.sort_unstable_by(|_, a, _, b| {
|
SortBy::Size => items.sort_unstable_by(|a, b| {
|
||||||
self.cmp(a.length.unwrap_or(0), b.length.unwrap_or(0), self.promote(a, b))
|
self.cmp(a.length.unwrap_or(0), b.length.unwrap_or(0), self.promote(a, b))
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sort_naturally(&self, items: &mut IndexMap<PathBuf, File>) {
|
fn sort_naturally(&self, items: &mut Vec<File>) {
|
||||||
let mut indices = Vec::with_capacity(items.len());
|
let mut indices = Vec::with_capacity(items.len());
|
||||||
let mut entities = Vec::with_capacity(items.len());
|
let mut entities = Vec::with_capacity(items.len());
|
||||||
for (i, (path, file)) in items.into_iter().enumerate() {
|
for (i, file) in items.into_iter().enumerate() {
|
||||||
indices.push(i);
|
indices.push(i);
|
||||||
entities.push((path.to_string_lossy(), file));
|
entities.push((file.path.to_string_lossy(), &*file));
|
||||||
}
|
}
|
||||||
|
|
||||||
indices.sort_unstable_by(|&a, &b| {
|
indices.sort_unstable_by(|&a, &b| {
|
||||||
|
|
@ -71,12 +70,7 @@ impl FilesSorter {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut new = IndexMap::with_capacity(indices.len());
|
items.sort_unstable_by_key(|_| indices.pop().unwrap());
|
||||||
for i in indices {
|
|
||||||
let file = entities[i].1.clone();
|
|
||||||
new.insert(file.path(), file);
|
|
||||||
}
|
|
||||||
*items = new;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use config::MANAGER;
|
use config::MANAGER;
|
||||||
use indexmap::map::Slice;
|
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
|
|
||||||
use crate::{emit, files::{File, Files, FilesOp}};
|
use crate::{emit, files::{File, Files, FilesOp}};
|
||||||
|
|
@ -28,7 +27,7 @@ impl Folder {
|
||||||
pub fn update(&mut self, op: FilesOp) -> bool {
|
pub fn update(&mut self, op: FilesOp) -> bool {
|
||||||
let b = match op {
|
let b = match op {
|
||||||
FilesOp::Read(_, items) => self.files.update_read(items),
|
FilesOp::Read(_, items) => self.files.update_read(items),
|
||||||
FilesOp::Sort(_, items) => self.files.update_sort(items),
|
FilesOp::Size(_, items) => self.files.update_size(items),
|
||||||
FilesOp::Search(_, items) => self.files.update_search(items),
|
FilesOp::Search(_, items) => self.files.update_search(items),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
@ -103,53 +102,20 @@ impl Folder {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn window(&self) -> &Slice<PathBuf, File> {
|
pub fn window(&self) -> Vec<&File> {
|
||||||
let end = (self.offset + MANAGER.layout.folder_height()).min(self.files.len());
|
let end = (self.offset + MANAGER.layout.folder_height()).min(self.files.len());
|
||||||
self.files.get_range(self.offset..end).unwrap()
|
self.files.range(self.offset..end)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn window_for(&self, offset: usize) -> &Slice<PathBuf, File> {
|
pub fn window_for(&self, offset: usize) -> Vec<&File> {
|
||||||
let start = offset.min(self.files.len().saturating_sub(1));
|
let start = offset.min(self.files.len().saturating_sub(1));
|
||||||
let end = (offset + MANAGER.layout.folder_height()).min(self.files.len());
|
let end = (offset + MANAGER.layout.folder_height()).min(self.files.len());
|
||||||
self.files.get_range(start..end).unwrap()
|
self.files.range(start..end)
|
||||||
}
|
|
||||||
|
|
||||||
pub fn select(&mut self, idx: Option<usize>, state: Option<bool>) -> bool {
|
|
||||||
let len = self.files.len();
|
|
||||||
let mut apply = |idx: usize, state: Option<bool>| -> bool {
|
|
||||||
let Some(state) = state else {
|
|
||||||
self.files[idx].is_selected = !self.files[idx].is_selected;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
if state != self.files[idx].is_selected {
|
|
||||||
self.files[idx].is_selected = state;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(idx) = idx {
|
|
||||||
if idx < len {
|
|
||||||
return apply(idx, state);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut applied = false;
|
|
||||||
for i in 0..len {
|
|
||||||
if apply(i, state) {
|
|
||||||
applied = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return applied;
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hover(&mut self, path: &Path) -> bool {
|
pub fn hover(&mut self, path: &Path) -> bool {
|
||||||
let new = self.position(path).unwrap_or(self.cursor);
|
let new = self.files.position(path).unwrap_or(self.cursor);
|
||||||
if new > self.cursor { self.next(new - self.cursor) } else { self.prev(self.cursor - new) }
|
if new > self.cursor { self.next(new - self.cursor) } else { self.prev(self.cursor - new) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -170,25 +136,17 @@ impl Folder {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn cursor(&self) -> usize { self.cursor }
|
pub fn cursor(&self) -> usize { self.cursor }
|
||||||
|
|
||||||
#[inline]
|
pub fn paginate(&self) -> Vec<&File> {
|
||||||
pub fn position(&self, path: &Path) -> Option<usize> {
|
|
||||||
self.files.iter().position(|(p, _)| p == path)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn paginate(&self) -> &Slice<PathBuf, File> {
|
|
||||||
let len = self.files.len();
|
let len = self.files.len();
|
||||||
let limit = MANAGER.layout.folder_height();
|
let limit = MANAGER.layout.folder_height();
|
||||||
|
|
||||||
let start = (self.page * limit).min(len.saturating_sub(1));
|
let start = (self.page * limit).min(len.saturating_sub(1));
|
||||||
let end = (start + limit).min(len);
|
let end = (start + limit).min(len);
|
||||||
self.files.get_range(start..end).unwrap()
|
self.files.range(start..end)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn has_selected(&self) -> bool { self.files.iter().any(|(_, f)| f.is_selected) }
|
|
||||||
|
|
||||||
pub fn rect_current(&self, path: &Path) -> Option<Rect> {
|
pub fn rect_current(&self, path: &Path) -> Option<Rect> {
|
||||||
let y = self.position(path)? - self.offset;
|
let y = self.files.position(path)? - self.offset;
|
||||||
|
|
||||||
let mut rect = MANAGER.layout.folder_rect();
|
let mut rect = MANAGER.layout.folder_rect();
|
||||||
rect.y = rect.y.saturating_sub(1) + y as u16;
|
rect.y = rect.y.saturating_sub(1) + y as u16;
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,12 @@ impl Mode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mode {
|
impl Mode {
|
||||||
|
#[inline]
|
||||||
|
pub fn is_select(&self) -> bool { matches!(self, Mode::Select(..)) }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_unset(&self) -> bool { matches!(self, Mode::Unset(..)) }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_visual(&self) -> bool { matches!(self, Mode::Select(..) | Mode::Unset(..)) }
|
pub fn is_visual(&self) -> bool { matches!(self, Mode::Select(..) | Mode::Unset(..)) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{collections::{BTreeMap, BTreeSet}, ffi::{OsStr, OsString}, mem, path::{Path, PathBuf}};
|
use std::{borrow::Cow, collections::{BTreeMap, BTreeSet}, ffi::{OsStr, OsString}, mem, path::{Path, PathBuf}};
|
||||||
|
|
||||||
use anyhow::{Error, Result};
|
use anyhow::{Error, Result};
|
||||||
use config::{open::Opener, MANAGER};
|
use config::{open::Opener, MANAGER};
|
||||||
|
|
@ -36,11 +36,7 @@ impl Tab {
|
||||||
|
|
||||||
pub fn escape(&mut self) -> bool {
|
pub fn escape(&mut self) -> bool {
|
||||||
if let Some((_, indices)) = self.mode.visual() {
|
if let Some((_, indices)) = self.mode.visual() {
|
||||||
let b = matches!(self.mode, Mode::Select(..));
|
self.current.files.select_index(indices, Some(self.mode.is_select()));
|
||||||
for idx in indices.iter() {
|
|
||||||
self.current.select(Some(*idx), Some(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.mode = Mode::Normal;
|
self.mode = Mode::Normal;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -187,11 +183,15 @@ impl Tab {
|
||||||
pub fn forward(&mut self) -> bool { false }
|
pub fn forward(&mut self) -> bool { false }
|
||||||
|
|
||||||
pub fn select(&mut self, state: Option<bool>) -> bool {
|
pub fn select(&mut self, state: Option<bool>) -> bool {
|
||||||
let idx = Some(self.current.cursor());
|
if let Some(ref hovered) = self.current.hovered {
|
||||||
self.current.select(idx, state)
|
return self.current.files.select(&hovered.path, state);
|
||||||
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_all(&mut self, state: Option<bool>) -> bool { self.current.select(None, state) }
|
pub fn select_all(&mut self, state: Option<bool>) -> bool {
|
||||||
|
self.current.files.select_many(None, state)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn visual_mode(&mut self, unset: bool) -> bool {
|
pub fn visual_mode(&mut self, unset: bool) -> bool {
|
||||||
let idx = self.current.cursor();
|
let idx = self.current.cursor();
|
||||||
|
|
@ -243,7 +243,7 @@ impl Tab {
|
||||||
|
|
||||||
emit!(Files(FilesOp::search_empty(&cwd)));
|
emit!(Files(FilesOp::search_empty(&cwd)));
|
||||||
while let Some(chunk) = rx.next().await {
|
while let Some(chunk) = rx.next().await {
|
||||||
emit!(Files(FilesOp::Search(cwd.clone(), Files::read(chunk).await)));
|
emit!(Files(FilesOp::Search(cwd.clone(), Files::read(&chunk).await)));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
|
|
@ -363,19 +363,9 @@ impl Tab {
|
||||||
|
|
||||||
pub fn selected(&self) -> Vec<&File> {
|
pub fn selected(&self) -> Vec<&File> {
|
||||||
let mode = self.mode();
|
let mode = self.mode();
|
||||||
let files = &self.current.files;
|
let pending = mode.visual().map(|(_, p)| Cow::Borrowed(p)).unwrap_or_default();
|
||||||
|
|
||||||
let selected: Vec<_> = if !mode.is_visual() {
|
|
||||||
files.iter().filter(|(_, f)| f.is_selected).map(|(_, f)| f).collect()
|
|
||||||
} else {
|
|
||||||
files
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(i, (_, f))| mode.pending(*i, f.is_selected))
|
|
||||||
.map(|(_, (_, f))| f)
|
|
||||||
.collect()
|
|
||||||
};
|
|
||||||
|
|
||||||
|
let selected = self.current.files.selected(&pending, mode.is_unset());
|
||||||
if selected.is_empty() {
|
if selected.is_empty() {
|
||||||
self.current.hovered.as_ref().map(|h| vec![h]).unwrap_or_default()
|
self.current.hovered.as_ref().map(|h| vec![h]).unwrap_or_default()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -384,7 +374,9 @@ impl Tab {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn in_selecting(&self) -> bool { self.mode().is_visual() || self.current.has_selected() }
|
pub fn in_selecting(&self) -> bool {
|
||||||
|
self.mode().is_visual() || self.current.files.has_selected()
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn history(&self, path: &Path) -> Option<&Folder> { self.history.get(path) }
|
pub fn history(&self, path: &Path) -> Option<&Folder> { self.history.get(path) }
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{collections::{BTreeMap, BTreeSet}, path::{Path, PathBuf}, sync::Arc, time::Duration};
|
use std::{collections::BTreeSet, path::{Path, PathBuf}, sync::Arc, time::Duration};
|
||||||
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
|
@ -170,11 +170,11 @@ impl Watcher {
|
||||||
for ori in linked {
|
for ori in linked {
|
||||||
emit!(Files(match &result {
|
emit!(Files(match &result {
|
||||||
Ok(items) => {
|
Ok(items) => {
|
||||||
let files = BTreeMap::from_iter(items.iter().map(|(p, f)| {
|
let mut files = Vec::with_capacity(items.len());
|
||||||
let p = ori.join(p.strip_prefix(path).unwrap());
|
for item in items {
|
||||||
let f = f.clone().set_path(&p);
|
let file = item.clone().set_path(ori.join(item.path.strip_prefix(path).unwrap()));
|
||||||
(p, f)
|
files.push(file);
|
||||||
}));
|
}
|
||||||
FilesOp::Read(ori, files)
|
FilesOp::Read(ori, files)
|
||||||
}
|
}
|
||||||
Err(_) => FilesOp::IOErr(ori),
|
Err(_) => FilesOp::IOErr(ori),
|
||||||
|
|
|
||||||
|
|
@ -216,11 +216,8 @@ impl Tasks {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let targets = targets
|
let targets: Vec<_> =
|
||||||
.iter()
|
targets.iter().filter(|f| f.meta.is_dir() && f.length.is_none()).map(|f| f.path()).collect();
|
||||||
.filter(|(_, f)| f.meta.is_dir() && f.length.is_none())
|
|
||||||
.map(|(p, _)| p.clone())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
if !targets.is_empty() {
|
if !targets.is_empty() {
|
||||||
self.scheduler.precache_size(targets);
|
self.scheduler.precache_size(targets);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use parking_lot::Mutex;
|
||||||
use shared::{calculate_size, Throttle};
|
use shared::{calculate_size, Throttle};
|
||||||
use tokio::{fs, sync::mpsc};
|
use tokio::{fs, sync::mpsc};
|
||||||
|
|
||||||
use crate::{emit, external, files::{File, FilesOp}, tasks::TaskOp};
|
use crate::{emit, external, files::FilesOp, tasks::TaskOp};
|
||||||
|
|
||||||
pub(crate) struct Precache {
|
pub(crate) struct Precache {
|
||||||
rx: async_channel::Receiver<PrecacheOp>,
|
rx: async_channel::Receiver<PrecacheOp>,
|
||||||
|
|
@ -29,7 +29,7 @@ pub(crate) enum PrecacheOp {
|
||||||
pub(crate) struct PrecacheOpSize {
|
pub(crate) struct PrecacheOpSize {
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
pub target: PathBuf,
|
pub target: PathBuf,
|
||||||
pub throttle: Arc<Throttle<(PathBuf, File)>>,
|
pub throttle: Arc<Throttle<(PathBuf, u64)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -121,21 +121,16 @@ impl Precache {
|
||||||
pub(crate) async fn size(&self, task: PrecacheOpSize) -> Result<()> {
|
pub(crate) async fn size(&self, task: PrecacheOpSize) -> Result<()> {
|
||||||
self.sch.send(TaskOp::New(task.id, 0))?;
|
self.sch.send(TaskOp::New(task.id, 0))?;
|
||||||
|
|
||||||
let length = Some(calculate_size(&task.target).await);
|
let length = calculate_size(&task.target).await;
|
||||||
if let Ok(mut file) = File::from(&task.target).await {
|
task.throttle.done((task.target, length), |buf| {
|
||||||
file.length = length;
|
|
||||||
task.throttle.done((task.target, file), |buf| {
|
|
||||||
let mut handing = self.size_handing.lock();
|
let mut handing = self.size_handing.lock();
|
||||||
for (path, _) in &buf {
|
for (path, _) in &buf {
|
||||||
handing.remove(path);
|
handing.remove(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
let parent = buf[0].0.parent().unwrap().to_path_buf();
|
let parent = buf[0].0.parent().unwrap().to_path_buf();
|
||||||
emit!(Files(FilesOp::Sort(parent, BTreeMap::from_iter(buf))));
|
emit!(Files(FilesOp::Size(parent, BTreeMap::from_iter(buf))));
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
self.size_handing.lock().remove(&task.target);
|
|
||||||
};
|
|
||||||
|
|
||||||
self.sch.send(TaskOp::Adv(task.id, 1, 0))?;
|
self.sch.send(TaskOp::Adv(task.id, 1, 0))?;
|
||||||
self.done(task.id)
|
self.done(task.id)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue