mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
refactor: separate file logic from the folder module
This commit is contained in:
parent
66ac5f9d9c
commit
ac6b077bd2
23 changed files with 303 additions and 190 deletions
|
|
@ -7,7 +7,7 @@ mod pattern;
|
|||
pub mod preview;
|
||||
pub mod theme;
|
||||
|
||||
pub use pattern::*;
|
||||
pub(crate) use pattern::*;
|
||||
|
||||
pub static KEYMAP: Lazy<keymap::Keymap> = Lazy::new(|| keymap::Keymap::new());
|
||||
pub static MANAGER: Lazy<manager::Manager> = Lazy::new(|| manager::Manager::new());
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
pub mod kitty;
|
||||
mod kitty;
|
||||
|
||||
pub use kitty::*;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@ use std::path::PathBuf;
|
|||
|
||||
use anyhow::Result;
|
||||
use crossterm::event::KeyEvent;
|
||||
use indexmap::IndexMap;
|
||||
use tokio::sync::{mpsc::Sender, oneshot};
|
||||
|
||||
use super::{FolderItem, InputOpt, PreviewData};
|
||||
use super::{files::FilesOp, input::InputOpt, manager::PreviewData};
|
||||
|
||||
static mut TX: Option<Sender<Event>> = None;
|
||||
|
||||
|
|
@ -17,7 +16,7 @@ pub enum Event {
|
|||
Resize(u16, u16),
|
||||
|
||||
Refresh,
|
||||
Files(PathBuf, IndexMap<PathBuf, FolderItem>),
|
||||
Files(FilesOp),
|
||||
Hover,
|
||||
Mimetype(PathBuf, String),
|
||||
Preview(PathBuf, PreviewData),
|
||||
|
|
@ -67,8 +66,8 @@ macro_rules! emit {
|
|||
$crate::core::Event::Resize($cols, $rows).emit();
|
||||
};
|
||||
|
||||
(Files($path:expr, $items:expr)) => {
|
||||
$crate::core::Event::Files($path, $items).emit();
|
||||
(Files($op:expr)) => {
|
||||
$crate::core::Event::Files($op).emit();
|
||||
};
|
||||
(Mimetype($path:expr, $mime:expr)) => {
|
||||
$crate::core::Event::Mimetype($path, $mime).emit();
|
||||
|
|
|
|||
7
src/core/external/fd.rs
vendored
7
src/core/external/fd.rs
vendored
|
|
@ -12,7 +12,7 @@ pub struct FdOpt {
|
|||
pub subject: String,
|
||||
}
|
||||
|
||||
pub fn fd(opt: FdOpt) -> Result<(JoinHandle<()>, UnboundedReceiver<Vec<String>>)> {
|
||||
pub fn fd(opt: FdOpt) -> Result<(JoinHandle<()>, UnboundedReceiver<Vec<PathBuf>>)> {
|
||||
let mut child = Command::new("fd")
|
||||
.arg("--base-directory")
|
||||
.arg(&opt.cwd)
|
||||
|
|
@ -30,7 +30,10 @@ pub fn fd(opt: FdOpt) -> Result<(JoinHandle<()>, UnboundedReceiver<Vec<String>>)
|
|||
|
||||
let handle = tokio::spawn(async move {
|
||||
while let Ok(Some(line)) = it.next_line().await {
|
||||
buf.push(line);
|
||||
let path = PathBuf::from(line);
|
||||
if path.components().count() > 1 {
|
||||
buf.push(path);
|
||||
}
|
||||
}
|
||||
child.wait().await.ok();
|
||||
});
|
||||
|
|
|
|||
7
src/core/external/rg.rs
vendored
7
src/core/external/rg.rs
vendored
|
|
@ -11,7 +11,7 @@ pub struct RgOpt {
|
|||
pub subject: String,
|
||||
}
|
||||
|
||||
pub async fn rg(opt: RgOpt) -> Result<(JoinHandle<()>, UnboundedReceiver<Vec<String>>)> {
|
||||
pub fn rg(opt: RgOpt) -> Result<(JoinHandle<()>, UnboundedReceiver<Vec<PathBuf>>)> {
|
||||
let mut child = Command::new("rg")
|
||||
.current_dir(&opt.cwd)
|
||||
.args(&["--color=never", "--files-with-matches", "--smart-case"])
|
||||
|
|
@ -28,7 +28,10 @@ pub async fn rg(opt: RgOpt) -> Result<(JoinHandle<()>, UnboundedReceiver<Vec<Str
|
|||
|
||||
let handle = tokio::spawn(async move {
|
||||
while let Ok(Some(line)) = it.next_line().await {
|
||||
buf.push(line);
|
||||
let path = PathBuf::from(line);
|
||||
if path.components().count() > 1 {
|
||||
buf.push(path);
|
||||
}
|
||||
}
|
||||
child.wait().await.ok();
|
||||
});
|
||||
|
|
|
|||
35
src/core/files/file.rs
Normal file
35
src/core/files/file.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use std::{fs::Metadata, path::{Path, PathBuf}};
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::fs;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct File {
|
||||
pub name: String,
|
||||
pub path: PathBuf,
|
||||
pub meta: Metadata,
|
||||
pub length: Option<u64>,
|
||||
pub is_link: bool,
|
||||
pub is_hidden: bool,
|
||||
pub is_selected: bool,
|
||||
}
|
||||
|
||||
impl File {
|
||||
#[inline]
|
||||
pub async fn from(path: &Path) -> Result<File> {
|
||||
let meta = fs::metadata(path).await?;
|
||||
Ok(Self::from_meta(path, meta).await)
|
||||
}
|
||||
|
||||
pub async fn from_meta(path: &Path, mut meta: Metadata) -> File {
|
||||
let is_link = meta.is_symlink();
|
||||
if is_link {
|
||||
meta = fs::metadata(&path).await.unwrap_or(meta);
|
||||
}
|
||||
|
||||
let name = path.file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_default();
|
||||
let length = if meta.is_dir() { None } else { Some(meta.len()) };
|
||||
let is_hidden = name.starts_with('.');
|
||||
File { name, path: path.to_path_buf(), meta, length, is_link, is_hidden, is_selected: false }
|
||||
}
|
||||
}
|
||||
134
src/core/files/files.rs
Normal file
134
src/core/files/files.rs
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
use std::{ops::{Deref, DerefMut}, path::{Path, PathBuf}};
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use tokio::fs;
|
||||
|
||||
use super::File;
|
||||
use crate::{config::{manager::SortBy, MANAGER}, emit};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Files {
|
||||
items: IndexMap<PathBuf, File>,
|
||||
sort: FilesSort,
|
||||
pub show_hidden: bool,
|
||||
}
|
||||
|
||||
impl Files {
|
||||
pub async fn from(paths: Vec<PathBuf>) -> IndexMap<PathBuf, File> {
|
||||
let mut items = IndexMap::new();
|
||||
for path in paths {
|
||||
if let Ok(file) = File::from(&path).await {
|
||||
items.insert(path, file);
|
||||
}
|
||||
}
|
||||
items
|
||||
}
|
||||
|
||||
pub async fn read(path: &Path) {
|
||||
let mut iter = match fs::read_dir(path).await {
|
||||
Ok(it) => it,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let mut items = IndexMap::new();
|
||||
while let Ok(Some(item)) = iter.next_entry().await {
|
||||
if let Ok(meta) = item.metadata().await {
|
||||
let path = item.path();
|
||||
let file = File::from_meta(&path, meta).await;
|
||||
items.insert(path, file);
|
||||
}
|
||||
}
|
||||
emit!(Files(FilesOp::Update(path.to_path_buf(), items)));
|
||||
}
|
||||
|
||||
pub fn sort(&mut self) {
|
||||
fn cmp<T: Ord>(a: T, b: T, reverse: bool) -> std::cmp::Ordering {
|
||||
if reverse { b.cmp(&a) } else { a.cmp(&b) }
|
||||
}
|
||||
|
||||
let reverse = self.sort.reverse;
|
||||
match self.sort.by {
|
||||
SortBy::Alphabetical => self.items.sort_by(|_, a, _, b| cmp(&a.name, &b.name, reverse)),
|
||||
SortBy::Created => self.items.sort_by(|_, a, _, b| {
|
||||
if let (Ok(a), Ok(b)) = (a.meta.created(), b.meta.created()) {
|
||||
return cmp(a, b, reverse);
|
||||
}
|
||||
std::cmp::Ordering::Equal
|
||||
}),
|
||||
SortBy::Modified => self.items.sort_by(|_, a, _, b| {
|
||||
if let (Ok(a), Ok(b)) = (a.meta.modified(), b.meta.modified()) {
|
||||
return cmp(a, b, reverse);
|
||||
}
|
||||
std::cmp::Ordering::Equal
|
||||
}),
|
||||
SortBy::Size => {
|
||||
self.items.sort_by(|_, a, _, b| cmp(a.length.unwrap_or(0), b.length.unwrap_or(0), reverse))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, mut items: IndexMap<PathBuf, File>) -> bool {
|
||||
if !self.show_hidden {
|
||||
items.retain(|_, item| !item.is_hidden);
|
||||
}
|
||||
|
||||
for (path, item) in &mut items {
|
||||
if let Some(old) = self.items.get(path) {
|
||||
item.length = old.length;
|
||||
item.is_selected = old.is_selected;
|
||||
}
|
||||
}
|
||||
|
||||
self.items = items;
|
||||
self.sort();
|
||||
true
|
||||
}
|
||||
|
||||
pub fn append(&mut self, items: IndexMap<PathBuf, File>) -> bool {
|
||||
for (path, mut item) in items.into_iter() {
|
||||
if let Some(old) = self.items.get(&path) {
|
||||
item.length = old.length;
|
||||
item.is_selected = old.is_selected;
|
||||
}
|
||||
self.items.insert(path, item);
|
||||
}
|
||||
|
||||
self.sort();
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for 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 }
|
||||
}
|
||||
|
||||
struct FilesSort {
|
||||
pub by: SortBy,
|
||||
pub reverse: bool,
|
||||
}
|
||||
|
||||
impl Default for FilesSort {
|
||||
fn default() -> Self { Self { by: MANAGER.sort_by, reverse: MANAGER.sort_reverse } }
|
||||
}
|
||||
|
||||
pub enum FilesOp {
|
||||
Update(PathBuf, IndexMap<PathBuf, File>),
|
||||
Append(PathBuf, IndexMap<PathBuf, File>),
|
||||
}
|
||||
|
||||
impl FilesOp {
|
||||
#[inline]
|
||||
pub fn path(&self) -> PathBuf {
|
||||
match self {
|
||||
Self::Update(path, _) => path,
|
||||
Self::Append(path, _) => path,
|
||||
}
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
5
src/core/files/mod.rs
Normal file
5
src/core/files/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
mod file;
|
||||
mod files;
|
||||
|
||||
pub use file::*;
|
||||
pub use files::*;
|
||||
|
|
@ -1,142 +1,48 @@
|
|||
use std::{fs::Metadata, path::{Path, PathBuf}, usize};
|
||||
use std::{path::{Path, PathBuf}, usize};
|
||||
|
||||
use indexmap::{map::Slice, IndexMap};
|
||||
use indexmap::map::Slice;
|
||||
use ratatui::layout::Rect;
|
||||
use tokio::fs;
|
||||
|
||||
use super::{ALL_RATIO, CURRENT_RATIO, DIR_PADDING, PARENT_RATIO};
|
||||
use crate::{config::{manager::SortBy, MANAGER}, emit, misc::tty_size};
|
||||
use crate::{core::files::{File, Files, FilesOp}, emit, misc::tty_size};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Folder {
|
||||
pub cwd: PathBuf,
|
||||
items: IndexMap<PathBuf, FolderItem>,
|
||||
offset: usize,
|
||||
cursor: usize,
|
||||
pub cwd: PathBuf,
|
||||
pub files: Files,
|
||||
offset: usize,
|
||||
cursor: usize,
|
||||
|
||||
sort: FolderSort,
|
||||
show_hidden: bool,
|
||||
pub in_search: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FolderItem {
|
||||
pub name: String,
|
||||
pub path: PathBuf,
|
||||
pub meta: Metadata,
|
||||
pub length: Option<u64>,
|
||||
pub is_link: bool,
|
||||
pub is_hidden: bool,
|
||||
pub is_selected: bool,
|
||||
}
|
||||
|
||||
struct FolderSort {
|
||||
pub by: SortBy,
|
||||
pub reverse: bool,
|
||||
}
|
||||
|
||||
impl Default for FolderSort {
|
||||
fn default() -> Self { Self { by: MANAGER.sort_by, reverse: MANAGER.sort_reverse } }
|
||||
}
|
||||
|
||||
impl Folder {
|
||||
pub fn new(cwd: &Path) -> Self {
|
||||
Self { cwd: cwd.to_path_buf(), show_hidden: MANAGER.show_hidden, ..Default::default() }
|
||||
}
|
||||
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(),
|
||||
show_hidden: MANAGER.show_hidden,
|
||||
in_search: true,
|
||||
..Default::default()
|
||||
Self { cwd: cwd.to_path_buf(), in_search: true, ..Default::default() }
|
||||
}
|
||||
|
||||
pub fn update(&mut self, op: FilesOp) -> bool {
|
||||
let b = match op {
|
||||
FilesOp::Update(_, items) => self.files.update(items),
|
||||
FilesOp::Append(_, items) => self.files.append(items),
|
||||
};
|
||||
if !b {
|
||||
return false;
|
||||
}
|
||||
|
||||
let len = self.files.len();
|
||||
self.cursor = self.cursor.min(len.saturating_sub(1));
|
||||
self.offset = self.offset.min(len);
|
||||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn limit() -> usize { tty_size().ws_row.saturating_sub(DIR_PADDING) as usize }
|
||||
|
||||
pub async fn read(path: &Path) {
|
||||
let mut iter = match fs::read_dir(path).await {
|
||||
Ok(it) => it,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let mut items = IndexMap::new();
|
||||
while let Ok(Some(item)) = iter.next_entry().await {
|
||||
let mut meta = if let Ok(meta) = item.metadata().await { meta } else { continue };
|
||||
let is_link = meta.is_symlink();
|
||||
if is_link {
|
||||
meta = fs::metadata(&path).await.unwrap_or(meta);
|
||||
}
|
||||
|
||||
let path = item.path();
|
||||
let name = item.file_name().to_string_lossy().to_string();
|
||||
|
||||
let length = if meta.is_dir() { None } else { Some(meta.len()) };
|
||||
let is_hidden = name.starts_with('.');
|
||||
|
||||
items.insert(path.clone(), FolderItem {
|
||||
name,
|
||||
path,
|
||||
meta,
|
||||
length,
|
||||
is_link,
|
||||
is_hidden,
|
||||
is_selected: false,
|
||||
});
|
||||
}
|
||||
emit!(Files(path.to_path_buf(), items));
|
||||
}
|
||||
|
||||
pub fn sort(&mut self) {
|
||||
fn cmp<T: Ord>(a: T, b: T, reverse: bool) -> std::cmp::Ordering {
|
||||
if reverse { b.cmp(&a) } else { a.cmp(&b) }
|
||||
}
|
||||
|
||||
let reverse = self.sort.reverse;
|
||||
match self.sort.by {
|
||||
SortBy::Alphabetical => self.items.sort_by(|_, a, _, b| cmp(&a.name, &b.name, reverse)),
|
||||
SortBy::Created => self.items.sort_by(|_, a, _, b| {
|
||||
if let (Ok(a), Ok(b)) = (a.meta.created(), b.meta.created()) {
|
||||
return cmp(a, b, reverse);
|
||||
}
|
||||
std::cmp::Ordering::Equal
|
||||
}),
|
||||
SortBy::Modified => self.items.sort_by(|_, a, _, b| {
|
||||
if let (Ok(a), Ok(b)) = (a.meta.modified(), b.meta.modified()) {
|
||||
return cmp(a, b, reverse);
|
||||
}
|
||||
std::cmp::Ordering::Equal
|
||||
}),
|
||||
SortBy::Size => {
|
||||
self.items.sort_by(|_, a, _, b| cmp(a.length.unwrap_or(0), b.length.unwrap_or(0), reverse))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, mut items: IndexMap<PathBuf, FolderItem>) -> bool {
|
||||
if !self.show_hidden {
|
||||
items.retain(|_, item| !item.is_hidden);
|
||||
}
|
||||
|
||||
for (path, item) in &mut items {
|
||||
if let Some(old) = self.items.get(path) {
|
||||
item.length = old.length;
|
||||
item.is_selected = old.is_selected;
|
||||
}
|
||||
}
|
||||
|
||||
let len = items.len();
|
||||
self.items = items;
|
||||
self.cursor = self.cursor.min(len.saturating_sub(1));
|
||||
self.offset = self.offset.min(len);
|
||||
self.sort();
|
||||
true
|
||||
}
|
||||
|
||||
pub fn next(&mut self, step: usize) -> bool {
|
||||
let len = self.items.len();
|
||||
let len = self.files.len();
|
||||
if len == 0 {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -164,30 +70,30 @@ impl Folder {
|
|||
}
|
||||
|
||||
pub fn hidden(&mut self, show: Option<bool>) -> bool {
|
||||
if show.is_none() || self.show_hidden != show.unwrap() {
|
||||
self.show_hidden = !self.show_hidden;
|
||||
if show.is_none() || self.files.show_hidden != show.unwrap() {
|
||||
self.files.show_hidden = !self.files.show_hidden;
|
||||
emit!(Refresh);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn paginate(&self) -> &Slice<PathBuf, FolderItem> {
|
||||
let end = (self.offset + Self::limit()).min(self.items.len());
|
||||
self.items.get_range(self.offset..end).unwrap()
|
||||
pub fn paginate(&self) -> &Slice<PathBuf, File> {
|
||||
let end = (self.offset + Self::limit()).min(self.files.len());
|
||||
self.files.get_range(self.offset..end).unwrap()
|
||||
}
|
||||
|
||||
pub fn select(&mut self, idx: Option<usize>, state: Option<bool>) -> bool {
|
||||
let len = self.items.len();
|
||||
let len = self.files.len();
|
||||
let mut apply = |idx: usize, state: Option<bool>| -> bool {
|
||||
if state.is_none() {
|
||||
self.items[idx].is_selected = !self.items[idx].is_selected;
|
||||
self.files[idx].is_selected = !self.files[idx].is_selected;
|
||||
return true;
|
||||
}
|
||||
|
||||
let state = state.unwrap();
|
||||
if state != self.items[idx].is_selected {
|
||||
self.items[idx].is_selected = state;
|
||||
if state != self.files[idx].is_selected {
|
||||
self.files[idx].is_selected = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -213,7 +119,7 @@ impl Folder {
|
|||
|
||||
pub fn selected(&self) -> Option<Vec<PathBuf>> {
|
||||
let v = self
|
||||
.items
|
||||
.files
|
||||
.iter()
|
||||
.filter(|(_, item)| item.is_selected)
|
||||
.map(|(path, _)| path.clone())
|
||||
|
|
@ -234,9 +140,7 @@ impl Folder {
|
|||
|
||||
impl Folder {
|
||||
#[inline]
|
||||
pub fn hovered(&self) -> Option<&FolderItem> {
|
||||
self.items.get_index(self.cursor).map(|(_, item)| item)
|
||||
}
|
||||
pub fn hovered(&self) -> Option<&File> { self.files.get_index(self.cursor).map(|(_, item)| item) }
|
||||
|
||||
#[inline]
|
||||
pub fn cursor(&self) -> usize { self.cursor }
|
||||
|
|
@ -246,7 +150,7 @@ impl Folder {
|
|||
|
||||
#[inline]
|
||||
pub fn position(&self, path: &Path) -> Option<usize> {
|
||||
self.items.iter().position(|(p, _)| p == path)
|
||||
self.files.iter().position(|(p, _)| p == path)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
use std::{collections::{BTreeSet, HashMap, HashSet}, path::PathBuf};
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use ratatui::layout::Rect;
|
||||
use tokio::fs;
|
||||
use tracing::trace;
|
||||
|
||||
use super::{FolderItem, PreviewData, Tab, Tabs, Watcher};
|
||||
use crate::{core::{Folder, Input, InputOpt, Precache}, emit};
|
||||
use super::{PreviewData, Tab, Tabs, Watcher};
|
||||
use crate::{core::{files::{File, FilesOp}, input::{Input, InputOpt}, manager::Folder, tasks::Precache}, emit};
|
||||
|
||||
pub struct Manager {
|
||||
tabs: Tabs,
|
||||
|
|
@ -169,25 +166,20 @@ impl Manager {
|
|||
files.into_iter().map(|p| self.mimetype.get(p).cloned()).collect()
|
||||
}
|
||||
|
||||
pub fn update_files(&mut self, path: PathBuf, items: IndexMap<PathBuf, FolderItem>) -> bool {
|
||||
pub fn update_files(&mut self, op: FilesOp) -> bool {
|
||||
let path = op.path();
|
||||
let cwd = self.current().cwd.clone();
|
||||
|
||||
let hovered = self.hovered().map(|h| h.path.clone());
|
||||
|
||||
let mut b = if cwd == path && !self.current().in_search {
|
||||
self.current_mut().update(items)
|
||||
let folder = if cwd == path && !self.current().in_search {
|
||||
self.current_mut()
|
||||
} else if matches!(self.parent(), Some(p) if p.cwd == path) {
|
||||
self.active_mut().parent.as_mut().unwrap().update(items)
|
||||
self.active_mut().parent.as_mut().unwrap()
|
||||
} else {
|
||||
self
|
||||
.active_mut()
|
||||
.history
|
||||
.entry(path.clone())
|
||||
.or_insert_with(|| Folder::new(&path))
|
||||
.update(items);
|
||||
|
||||
matches!(self.hovered(), Some(h) if h.path == path)
|
||||
self.active_mut().history.entry(path.to_path_buf()).or_insert_with(|| Folder::new(&path))
|
||||
};
|
||||
|
||||
let mut b = folder.update(op) || matches!(self.hovered(), Some(h) if h.path == path);
|
||||
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));
|
||||
|
||||
|
|
@ -248,5 +240,5 @@ impl Manager {
|
|||
pub fn parent(&self) -> &Option<Folder> { &self.tabs.active().parent }
|
||||
|
||||
#[inline]
|
||||
pub fn hovered(&self) -> Option<&FolderItem> { self.tabs.active().current.hovered() }
|
||||
pub fn hovered(&self) -> Option<&File> { self.tabs.active().current.hovered() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
use std::{fs::File, io::BufReader, path::{Path, PathBuf}, sync::OnceLock};
|
||||
|
||||
use adapter::kitty::Kitty;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use image::imageops::FilterType;
|
||||
use ratatui::layout::Rect;
|
||||
use syntect::{easy::HighlightLines, highlighting::{Theme, ThemeSet}, parsing::SyntaxSet, util::as_24_bit_terminal_escaped};
|
||||
use tokio::{fs, task::JoinHandle};
|
||||
|
||||
use super::{Folder, ALL_RATIO, PREVIEW_BORDER, PREVIEW_PADDING, PREVIEW_RATIO};
|
||||
use crate::{config::{PREVIEW, THEME}, core::{adapter, Precache}, emit, misc::{first_n_lines, tty_ratio, tty_size}};
|
||||
use super::{ALL_RATIO, PREVIEW_BORDER, PREVIEW_PADDING, PREVIEW_RATIO};
|
||||
use crate::{config::{PREVIEW, THEME}, core::{adapter::Kitty, files::Files, tasks::Precache}, emit, misc::{first_n_lines, tty_ratio, tty_size}};
|
||||
|
||||
static SYNTECT_SYNTAX: OnceLock<SyntaxSet> = OnceLock::new();
|
||||
static SYNTECT_THEME: OnceLock<Theme> = OnceLock::new();
|
||||
|
|
@ -76,7 +74,7 @@ impl Preview {
|
|||
}
|
||||
|
||||
pub async fn folder(path: &Path) -> Result<PreviewData> {
|
||||
Folder::read(&path).await;
|
||||
Files::read(&path).await;
|
||||
Ok(PreviewData::Folder)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
use std::{collections::BTreeMap, mem, path::{Path, PathBuf}};
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use super::{Folder, Mode, Preview};
|
||||
use crate::emit;
|
||||
use crate::{core::{external, files::{Files, FilesOp}, input::{Input, InputOpt}}, emit};
|
||||
|
||||
pub struct Tab {
|
||||
pub(super) current: Folder,
|
||||
pub(super) parent: Option<Folder>,
|
||||
search: Option<JoinHandle<Result<()>>>,
|
||||
|
||||
pub(super) mode: Mode,
|
||||
|
||||
|
|
@ -18,6 +22,7 @@ impl Tab {
|
|||
Self {
|
||||
current: Folder::new(path),
|
||||
parent: path.parent().map(|p| Folder::new(p)),
|
||||
search: None,
|
||||
|
||||
mode: Default::default(),
|
||||
|
||||
|
|
@ -124,6 +129,40 @@ impl Tab {
|
|||
|
||||
pub fn forward(&mut self) -> bool { todo!() }
|
||||
|
||||
pub fn search(&mut self, grep: bool) -> bool {
|
||||
if let Some(handle) = self.search.take() {
|
||||
handle.abort();
|
||||
}
|
||||
|
||||
let cwd = self.current.cwd.clone();
|
||||
let hidden = self.current.files.show_hidden;
|
||||
|
||||
let pos = Input::top_position();
|
||||
self.search = Some(tokio::spawn(async move {
|
||||
let subject = emit!(Input(InputOpt {
|
||||
title: "Find:".to_string(),
|
||||
value: "".to_string(),
|
||||
position: pos,
|
||||
}))
|
||||
.await?;
|
||||
|
||||
let (handle, mut buf) = if grep {
|
||||
external::rg(external::RgOpt { cwd: cwd.clone(), hidden, subject })?
|
||||
} else {
|
||||
external::fd(external::FdOpt { cwd: cwd.clone(), hidden, regex: false, subject })?
|
||||
};
|
||||
|
||||
while let Some(chunk) = buf.recv().await {
|
||||
if chunk.is_empty() {
|
||||
break;
|
||||
}
|
||||
emit!(Files(FilesOp::Append(cwd.clone(), Files::from(chunk).await)));
|
||||
}
|
||||
Ok(handle.await?)
|
||||
}));
|
||||
false
|
||||
}
|
||||
|
||||
pub fn select(&mut self, state: Option<bool>) -> bool {
|
||||
let idx = Some(self.current.cursor());
|
||||
self.current.select(idx, state)
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ use std::{collections::BTreeSet, path::{Path, PathBuf}};
|
|||
use notify::{RecommendedWatcher, Watcher as _Watcher};
|
||||
use tokio::sync::mpsc::{self, Sender};
|
||||
|
||||
use super::Folder;
|
||||
use crate::emit;
|
||||
use crate::core::files::Files;
|
||||
|
||||
pub struct Watcher {
|
||||
tx: Sender<PathBuf>,
|
||||
|
|
@ -54,7 +53,7 @@ impl Watcher {
|
|||
|
||||
tokio::spawn(async move {
|
||||
while let Some(path) = rx.recv().await {
|
||||
Folder::read(&path).await;
|
||||
Files::read(&path).await;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
mod adapter;
|
||||
pub mod adapter;
|
||||
mod event;
|
||||
pub mod external;
|
||||
mod input;
|
||||
mod manager;
|
||||
mod tasks;
|
||||
pub mod files;
|
||||
pub mod input;
|
||||
pub mod manager;
|
||||
pub mod tasks;
|
||||
|
||||
pub use adapter::*;
|
||||
pub use event::*;
|
||||
pub use input::*;
|
||||
pub use manager::*;
|
||||
pub use tasks::*;
|
||||
|
|
|
|||
|
|
@ -31,5 +31,8 @@ impl<T> DelayedBuffer<T> {
|
|||
}
|
||||
|
||||
impl<T> Drop for DelayedBuffer<T> {
|
||||
fn drop(&mut self) { self.flush(); }
|
||||
fn drop(&mut self) {
|
||||
self.flush();
|
||||
self.tx.send(vec![]).ok();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,8 +74,8 @@ impl App {
|
|||
Event::Refresh => {
|
||||
manager.refresh();
|
||||
}
|
||||
Event::Files(path, files) => {
|
||||
if manager.update_files(path, files) {
|
||||
Event::Files(op) => {
|
||||
if manager.update_files(op) {
|
||||
emit!(Render);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::{Input, Manager, Tasks};
|
||||
use crate::core::{input::Input, manager::Manager, tasks::Tasks};
|
||||
|
||||
pub struct Ctx {
|
||||
pub cursor: Option<(u16, u16)>,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crossterm::event::KeyCode;
|
||||
|
||||
use super::Ctx;
|
||||
use crate::{config::{keymap::{Exec, Key, Single}, KEYMAP}, core::InputMode, emit, misc::optinal_bool};
|
||||
use crate::{config::{keymap::{Exec, Key, Single}, KEYMAP}, core::input::InputMode, emit, misc::optinal_bool};
|
||||
|
||||
pub struct Executor;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use ratatui::{buffer::Buffer, layout::Rect, style::{Color, Style}, text::Line, widgets::{Block, BorderType, Borders, Clear, Paragraph, Widget}};
|
||||
|
||||
use super::{Ctx, Term};
|
||||
use crate::core::InputMode;
|
||||
use crate::core::input::InputMode;
|
||||
|
||||
pub struct Input<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ use ratatui::{buffer::Buffer, layout::Rect, style::{Color, Modifier, Style}, wid
|
|||
use crate::{config::THEME, core};
|
||||
|
||||
pub struct Folder<'a> {
|
||||
folder: &'a core::Folder,
|
||||
folder: &'a core::manager::Folder,
|
||||
is_preview: bool,
|
||||
is_selection: bool,
|
||||
}
|
||||
|
||||
impl<'a> Folder<'a> {
|
||||
pub fn new(folder: &'a core::Folder) -> Self {
|
||||
pub fn new(folder: &'a core::manager::Folder) -> Self {
|
||||
Self { folder, is_preview: false, is_selection: false }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use ratatui::{buffer::Buffer, layout::{self, Constraint, Direction, Rect}, widgets::{Block, Borders, Widget}};
|
||||
|
||||
use super::{Folder, Preview};
|
||||
use crate::{core::{Mode, ALL_RATIO, CURRENT_RATIO, PARENT_RATIO, PREVIEW_RATIO}, ui::Ctx};
|
||||
use crate::{core::manager::{Mode, ALL_RATIO, CURRENT_RATIO, PARENT_RATIO, PREVIEW_RATIO}, ui::Ctx};
|
||||
|
||||
pub struct Layout<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use ansi_to_tui::IntoText;
|
|||
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Paragraph, Widget}};
|
||||
|
||||
use super::Folder;
|
||||
use crate::{core::{kitty::Kitty, PreviewData}, ui::{Ctx, Term}};
|
||||
use crate::{core::{adapter::Kitty, manager::PreviewData}, ui::{Ctx, Term}};
|
||||
|
||||
pub struct Preview<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use ratatui::{buffer::Buffer, layout::{self, Alignment, Constraint, Direction, Rect}, style::{Color, Modifier, Style}, widgets::{Block, BorderType, Borders, List, ListItem, Padding, Widget}};
|
||||
|
||||
use super::Clear;
|
||||
use crate::{core::TASKS_PERCENT, ui::Ctx};
|
||||
use crate::{core::tasks::TASKS_PERCENT, ui::Ctx};
|
||||
|
||||
pub struct Layout<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue