mirror of
https://github.com/bobwen-dev/hunter
synced 2025-04-12 00:55:41 +02:00
preview v3
This commit is contained in:
parent
ad44f6f2fc
commit
b4ed9cd689
43
src/fail.rs
Normal file
43
src/fail.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
use failure;
|
||||||
|
|
||||||
|
pub type HResult<T> = Result<T, HError>;
|
||||||
|
|
||||||
|
#[derive(Fail, Debug)]
|
||||||
|
pub enum HError {
|
||||||
|
#[fail(display = "IO error: {}", error)]
|
||||||
|
IoError{#[cause] error: std::io::Error},
|
||||||
|
#[fail(display = "Mutex failed")]
|
||||||
|
MutexError,
|
||||||
|
#[fail(display = "Channel failed: {}", error)]
|
||||||
|
ChannelTryRecvError{#[cause] error: std::sync::mpsc::TryRecvError},
|
||||||
|
#[fail(display = "Previewer failed on file: {}", file)]
|
||||||
|
PreviewFailed{file: String},
|
||||||
|
#[fail(display = "StalePreviewer for file: {}", file)]
|
||||||
|
StalePreviewError{file: String},
|
||||||
|
#[fail(display = "Failed: {}", error)]
|
||||||
|
Error{#[cause] error: failure::Error }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for HError {
|
||||||
|
fn from(error: std::io::Error) -> Self {
|
||||||
|
HError::IoError { error: error }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<failure::Error> for HError {
|
||||||
|
fn from(error: failure::Error) -> Self {
|
||||||
|
HError::Error { error: error }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::sync::mpsc::TryRecvError> for HError {
|
||||||
|
fn from(error: std::sync::mpsc::TryRecvError) -> Self {
|
||||||
|
HError::ChannelTryRecvError { error: error }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<std::sync::PoisonError<T>> for HError {
|
||||||
|
fn from(_: std::sync::PoisonError<T>) -> Self {
|
||||||
|
HError::MutexError
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@ use termion::event::Key;
|
|||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use crate::coordinates::{Coordinates, Position, Size};
|
use crate::coordinates::{Coordinates};
|
||||||
use crate::files::{File, Files};
|
use crate::files::{File, Files};
|
||||||
use crate::listview::ListView;
|
use crate::listview::ListView;
|
||||||
use crate::miller_columns::MillerColumns;
|
use crate::miller_columns::MillerColumns;
|
||||||
@ -212,10 +212,10 @@ impl Widget for FileBrowser {
|
|||||||
let ypos = self.get_coordinates().position().y();
|
let ypos = self.get_coordinates().position().y();
|
||||||
let file = self.selected_file();
|
let file = self.selected_file();
|
||||||
|
|
||||||
let permissions = file.pretty_print_permissions();
|
let permissions = file.pretty_print_permissions().unwrap_or("NOPERMS".into());
|
||||||
let user = file.pretty_user().unwrap_or("NOUSER".into());
|
let user = file.pretty_user().unwrap_or("NOUSER".into());
|
||||||
let group = file.pretty_group().unwrap_or("NOGROUP".into());
|
let group = file.pretty_group().unwrap_or("NOGROUP".into());
|
||||||
let mtime = file.pretty_mtime();
|
let mtime = file.pretty_mtime().unwrap_or("NOMTIME".into());
|
||||||
|
|
||||||
|
|
||||||
let selection = self.main_column().get_selection();
|
let selection = self.main_column().get_selection();
|
||||||
|
214
src/files.rs
214
src/files.rs
@ -9,7 +9,9 @@ use users;
|
|||||||
use chrono::TimeZone;
|
use chrono::TimeZone;
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
|
|
||||||
use crate::fail::HError;
|
use crate::fail::HResult;
|
||||||
|
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
@ -33,26 +35,9 @@ impl Index<usize> for Files {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_kind(file: &std::fs::DirEntry) -> Kind {
|
|
||||||
let file = file.file_type().unwrap();
|
|
||||||
if file.is_file() {
|
|
||||||
return Kind::File;
|
|
||||||
}
|
|
||||||
if file.is_dir() {
|
|
||||||
return Kind::Directory;
|
|
||||||
}
|
|
||||||
if file.is_symlink() {
|
|
||||||
return Kind::Link;
|
|
||||||
}
|
|
||||||
Kind::Pipe
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_color(path: &Path, meta: &std::fs::Metadata) -> Option<lscolors::Color> {
|
|
||||||
match COLORS.style_for_path_with_metadata(path, Some(&meta)) {
|
|
||||||
Some(style) => style.clone().foreground,
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Files {
|
impl Files {
|
||||||
pub fn new_from_path(path: &Path) -> Result<Files, Error> {
|
pub fn new_from_path(path: &Path) -> Result<Files, Error> {
|
||||||
@ -61,20 +46,10 @@ impl Files {
|
|||||||
let files: Vec<_> = direntries?
|
let files: Vec<_> = direntries?
|
||||||
.iter()
|
.iter()
|
||||||
.map(|file| {
|
.map(|file| {
|
||||||
//let file = file?;
|
|
||||||
let name = file.file_name();
|
let name = file.file_name();
|
||||||
let name = name.to_string_lossy();
|
let name = name.to_string_lossy();
|
||||||
let kind = get_kind(&file);
|
|
||||||
let path = file.path();
|
let path = file.path();
|
||||||
let meta = file.metadata().unwrap();
|
File::new(&name, path)
|
||||||
let mode = meta.mode();
|
|
||||||
let size = meta.len();
|
|
||||||
let mtime = meta.mtime();
|
|
||||||
let user = meta.uid();
|
|
||||||
let group = meta.gid();
|
|
||||||
let color = get_color(&path, &meta);
|
|
||||||
File::new(&name, path, kind, size as usize, mtime, color, mode,
|
|
||||||
user, group)
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@ -96,25 +71,70 @@ impl Files {
|
|||||||
Ok(files)
|
Ok(files)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_from_path_cancellable(path: &Path, stale: Arc<Mutex<bool>>) -> Result<Files, Error> {
|
||||||
|
let direntries: Result<Vec<_>, _> = std::fs::read_dir(&path)?.collect();
|
||||||
|
|
||||||
|
let files: Vec<_> = direntries?
|
||||||
|
.iter()
|
||||||
|
.map(|file| {
|
||||||
|
if crate::preview::is_stale(&stale).unwrap() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let name = file.file_name();
|
||||||
|
let name = name.to_string_lossy();
|
||||||
|
let path = file.path();
|
||||||
|
Some(File::new(&name, path))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.fuse()
|
||||||
|
.flatten()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if crate::preview::is_stale(&stale).unwrap() {
|
||||||
|
return Err(crate::fail::HError::StalePreviewError {
|
||||||
|
file: path.to_string_lossy().to_string()
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut files = Files {
|
||||||
|
directory: File::new_from_path(&path)?,
|
||||||
|
files: files,
|
||||||
|
sort: SortBy::Name,
|
||||||
|
dirs_first: true,
|
||||||
|
reverse: false,
|
||||||
|
show_hidden: true
|
||||||
|
};
|
||||||
|
|
||||||
|
files.sort();
|
||||||
|
|
||||||
|
if files.files.len() == 0 {
|
||||||
|
files.files = vec![File::new_placeholder(&path)?];
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(files)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sort(&mut self) {
|
pub fn sort(&mut self) {
|
||||||
match self.sort {
|
match self.sort {
|
||||||
SortBy::Name => self
|
SortBy::Name => self
|
||||||
.files
|
.files
|
||||||
.sort_by(|a, b| alphanumeric_sort::compare_str(&a.name, &b.name)),
|
.sort_by(|a, b| alphanumeric_sort::compare_str(&a.name, &b.name)),
|
||||||
SortBy::Size => {
|
SortBy::Size => {
|
||||||
|
self.meta_all();
|
||||||
self.files.sort_by(|a, b| {
|
self.files.sort_by(|a, b| {
|
||||||
if a.size == b.size {
|
if a.meta().unwrap().size() == b.meta().unwrap().size() {
|
||||||
return alphanumeric_sort::compare_str(&b.name, &a.name);
|
return alphanumeric_sort::compare_str(&b.name, &a.name);
|
||||||
}
|
}
|
||||||
a.size.cmp(&b.size).reverse()
|
a.meta().unwrap().size().cmp(&b.meta().unwrap().size()).reverse()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
SortBy::MTime => {
|
SortBy::MTime => {
|
||||||
|
self.meta_all();
|
||||||
self.files.sort_by(|a, b| {
|
self.files.sort_by(|a, b| {
|
||||||
if a.mtime == b.mtime {
|
if a.meta().unwrap().mtime() == b.meta().unwrap().mtime() {
|
||||||
return alphanumeric_sort::compare_str(&a.name, &b.name);
|
return alphanumeric_sort::compare_str(&a.name, &b.name);
|
||||||
}
|
}
|
||||||
a.mtime.cmp(&b.mtime)
|
a.meta().unwrap().mtime().cmp(&b.meta().unwrap().mtime())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -169,6 +189,17 @@ impl Files {
|
|||||||
self.files = files;
|
self.files = files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn meta_all(&mut self) {
|
||||||
|
let len = self.files.len();
|
||||||
|
self.meta_upto(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn meta_upto(&mut self, to: usize) {
|
||||||
|
for file in self.files.iter_mut().take(to) {
|
||||||
|
file.get_meta().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.files.len()
|
self.files.len()
|
||||||
}
|
}
|
||||||
@ -182,8 +213,6 @@ impl Files {
|
|||||||
pub enum Kind {
|
pub enum Kind {
|
||||||
Directory,
|
Directory,
|
||||||
File,
|
File,
|
||||||
Link,
|
|
||||||
Pipe,
|
|
||||||
Placeholder
|
Placeholder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,17 +234,24 @@ pub enum SortBy {
|
|||||||
MTime,
|
MTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
|
||||||
|
impl PartialEq for File {
|
||||||
|
fn eq(&self, other: &File) -> bool {
|
||||||
|
if self.path == other.path {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct File {
|
pub struct File {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub size: Option<usize>,
|
|
||||||
pub kind: Kind,
|
pub kind: Kind,
|
||||||
pub mtime: i64,
|
|
||||||
pub color: Option<lscolors::Color>,
|
pub color: Option<lscolors::Color>,
|
||||||
pub mode: u32,
|
pub meta: Option<std::fs::Metadata>,
|
||||||
pub user: u32,
|
|
||||||
pub group: u32,
|
|
||||||
pub selected: bool
|
pub selected: bool
|
||||||
// flags: Option<String>,
|
// flags: Option<String>,
|
||||||
}
|
}
|
||||||
@ -224,24 +260,13 @@ impl File {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
name: &str,
|
name: &str,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
kind: Kind,
|
|
||||||
size: usize,
|
|
||||||
mtime: i64,
|
|
||||||
color: Option<lscolors::Color>,
|
|
||||||
mode: u32,
|
|
||||||
user: u32,
|
|
||||||
group: u32
|
|
||||||
) -> File {
|
) -> File {
|
||||||
File {
|
File {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
|
kind: if path.is_dir() { Kind::Directory } else { Kind::File },
|
||||||
path: path,
|
path: path,
|
||||||
size: Some(size),
|
meta: None,
|
||||||
kind: kind,
|
color: None,
|
||||||
mtime: mtime,
|
|
||||||
color: color,
|
|
||||||
mode: mode,
|
|
||||||
user: user,
|
|
||||||
group: group,
|
|
||||||
selected: false
|
selected: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,18 +278,7 @@ impl File {
|
|||||||
.map(|name| name.to_string_lossy().to_string())
|
.map(|name| name.to_string_lossy().to_string())
|
||||||
.unwrap_or("/".to_string());
|
.unwrap_or("/".to_string());
|
||||||
|
|
||||||
let kind = Kind::Directory; //get_kind(&path);
|
Ok(File::new(&name, pathbuf))
|
||||||
let meta = &path.metadata()?;
|
|
||||||
let size = meta.len();
|
|
||||||
let user = meta.uid();
|
|
||||||
let group = meta.gid();
|
|
||||||
let color = get_color(&path, meta);
|
|
||||||
let mode = meta.mode();
|
|
||||||
let mtime = meta.mtime();
|
|
||||||
Ok(
|
|
||||||
File::new(&name, pathbuf, kind, size as usize, mtime, color, mode, user
|
|
||||||
, group)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_placeholder(path: &Path) -> Result<File, Error> {
|
pub fn new_placeholder(path: &Path) -> Result<File, Error> {
|
||||||
@ -274,17 +288,44 @@ impl File {
|
|||||||
Ok(file)
|
Ok(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_size(&self) -> (usize, String) {
|
pub fn meta(&self) -> HResult<std::fs::Metadata> {
|
||||||
if self.is_dir() {
|
match &self.meta {
|
||||||
let dir_iterator = std::fs::read_dir(&self.path);
|
Some(meta) => Ok(meta.clone()),
|
||||||
match dir_iterator {
|
None => { Ok(std::fs::metadata(&self.path)?) }
|
||||||
Ok(dir_iterator) => return (dir_iterator.count(), "".to_string()),
|
|
||||||
Err(_) => return (0, "".to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_meta(&mut self) -> HResult<()> {
|
||||||
|
if let Some(_) = self.meta { return Ok(()) }
|
||||||
|
|
||||||
|
let meta = std::fs::metadata(&self.path)?;
|
||||||
|
let color = self.get_color(&meta);
|
||||||
|
|
||||||
|
self.meta = Some(meta);
|
||||||
|
self.color = color;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_color(&self, meta: &std::fs::Metadata) -> Option<lscolors::Color> {
|
||||||
|
match COLORS.style_for_path_with_metadata(&self.path, Some(&meta)) {
|
||||||
|
Some(style) => style.clone().foreground,
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_size(&self) -> HResult<(u64, String)> {
|
||||||
|
if self.is_dir() {
|
||||||
|
let dir_iterator = std::fs::read_dir(&self.path);
|
||||||
|
match dir_iterator {
|
||||||
|
Ok(dir_iterator) => return Ok((dir_iterator.count() as u64,
|
||||||
|
"".to_string())),
|
||||||
|
Err(_) => return Ok((0, "".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let mut unit = 0;
|
let mut unit = 0;
|
||||||
let mut size = self.size.unwrap();
|
let mut size = self.meta()?.size();
|
||||||
while size > 1024 {
|
while size > 1024 {
|
||||||
size /= 1024;
|
size /= 1024;
|
||||||
unit += 1;
|
unit += 1;
|
||||||
@ -299,7 +340,7 @@ impl File {
|
|||||||
_ => "",
|
_ => "",
|
||||||
}
|
}
|
||||||
.to_string();
|
.to_string();
|
||||||
(size, unit)
|
Ok((size, unit))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mime(&self) -> Option<String> {
|
pub fn get_mime(&self) -> Option<String> {
|
||||||
@ -333,8 +374,8 @@ impl File {
|
|||||||
self.selected
|
self.selected
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pretty_print_permissions(&self) -> String {
|
pub fn pretty_print_permissions(&self) -> HResult<String> {
|
||||||
let perms: usize = format!("{:o}", self.mode).parse().unwrap();
|
let perms: usize = format!("{:o}", self.meta()?.mode()).parse().unwrap();
|
||||||
let perms: usize = perms % 800;
|
let perms: usize = perms % 800;
|
||||||
let perms = format!("{}", perms);
|
let perms = format!("{}", perms);
|
||||||
|
|
||||||
@ -354,11 +395,12 @@ impl File {
|
|||||||
_ => format!("---")
|
_ => format!("---")
|
||||||
}).collect();
|
}).collect();
|
||||||
|
|
||||||
perms
|
Ok(perms)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pretty_user(&self) -> Option<String> {
|
pub fn pretty_user(&self) -> Option<String> {
|
||||||
let uid = self.user;
|
if self.meta().is_err() { return None }
|
||||||
|
let uid = self.meta().unwrap().uid();
|
||||||
let file_user = users::get_user_by_uid(uid)?;
|
let file_user = users::get_user_by_uid(uid)?;
|
||||||
let cur_user = users::get_current_username()?;
|
let cur_user = users::get_current_username()?;
|
||||||
let color =
|
let color =
|
||||||
@ -370,7 +412,8 @@ impl File {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn pretty_group(&self) -> Option<String> {
|
pub fn pretty_group(&self) -> Option<String> {
|
||||||
let gid = self.group;
|
if self.meta().is_err() { return None }
|
||||||
|
let gid = self.meta().unwrap().gid();
|
||||||
let file_group = users::get_group_by_gid(gid)?;
|
let file_group = users::get_group_by_gid(gid)?;
|
||||||
let cur_group = users::get_current_groupname()?;
|
let cur_group = users::get_current_groupname()?;
|
||||||
let color =
|
let color =
|
||||||
@ -381,10 +424,11 @@ impl File {
|
|||||||
Some(format!("{}{}", color, file_group.name().to_string_lossy()))
|
Some(format!("{}{}", color, file_group.name().to_string_lossy()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pretty_mtime(&self) -> String {
|
pub fn pretty_mtime(&self) -> Option<String> {
|
||||||
|
if self.meta().is_err() { return None }
|
||||||
//let time = chrono::DateTime::from_timestamp(self.mtime, 0);
|
//let time = chrono::DateTime::from_timestamp(self.mtime, 0);
|
||||||
let time: chrono::DateTime<chrono::Local>
|
let time: chrono::DateTime<chrono::Local>
|
||||||
= chrono::Local.timestamp(self.mtime, 0);
|
= chrono::Local.timestamp(self.meta().unwrap().mtime(), 0);
|
||||||
time.format("%F %R").to_string()
|
Some(time.format("%F %R").to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,14 @@
|
|||||||
use rayon::prelude::*;
|
|
||||||
use termion::event::{Event, Key};
|
use termion::event::{Event, Key};
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
//use std::sync::mpsc::{channel, Sender, Receiver};
|
|
||||||
|
|
||||||
use crate::coordinates::{Coordinates, Position, Size};
|
use crate::coordinates::{Coordinates, Position, Size};
|
||||||
use crate::files::{File, Files};
|
use crate::files::{File, Files};
|
||||||
use crate::term;
|
use crate::term;
|
||||||
use crate::widget::{Widget};
|
use crate::widget::{Widget};
|
||||||
|
|
||||||
// Maybe also buffer drawlist for efficiency when it doesn't change every draw
|
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
pub struct ListView<T>
|
pub struct ListView<T>
|
||||||
where
|
where
|
||||||
@ -95,7 +91,7 @@ where
|
|||||||
|
|
||||||
fn render_line(&self, file: &File) -> String {
|
fn render_line(&self, file: &File) -> String {
|
||||||
let name = &file.name;
|
let name = &file.name;
|
||||||
let (size, unit) = file.calculate_size();
|
let (size, unit) = file.calculate_size().unwrap();
|
||||||
|
|
||||||
let selection_gap = " ".to_string();
|
let selection_gap = " ".to_string();
|
||||||
let (name, selection_color) = if file.is_selected() {
|
let (name, selection_color) = if file.is_selected() {
|
||||||
@ -137,10 +133,6 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ListView<Files>
|
impl ListView<Files>
|
||||||
where
|
|
||||||
ListView<Files>: Widget,
|
|
||||||
Files: std::ops::Index<usize, Output = File>,
|
|
||||||
Files: std::marker::Sized,
|
|
||||||
{
|
{
|
||||||
pub fn selected_file(&self) -> &File {
|
pub fn selected_file(&self) -> &File {
|
||||||
let selection = self.selection;
|
let selection = self.selection;
|
||||||
@ -366,6 +358,8 @@ impl Widget for ListView<Files> {
|
|||||||
self.refresh();
|
self.refresh();
|
||||||
}
|
}
|
||||||
fn refresh(&mut self) {
|
fn refresh(&mut self) {
|
||||||
|
let visible_file_num = self.selection + self.get_coordinates().ysize() as usize;
|
||||||
|
self.content.meta_upto(visible_file_num);
|
||||||
self.lines = self.content.len();
|
self.lines = self.content.len();
|
||||||
self.buffer = self.render();
|
self.buffer = self.render();
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ extern crate termion;
|
|||||||
extern crate unicode_width;
|
extern crate unicode_width;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
#[macro_use]
|
|
||||||
extern crate failure;
|
extern crate failure;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate failure_derive;
|
extern crate failure_derive;
|
||||||
@ -23,7 +22,6 @@ use termion::raw::IntoRawMode;
|
|||||||
use termion::screen::AlternateScreen;
|
use termion::screen::AlternateScreen;
|
||||||
|
|
||||||
use std::io::{stdout, Write};
|
use std::io::{stdout, Write};
|
||||||
use std::marker::Send;
|
|
||||||
|
|
||||||
mod coordinates;
|
mod coordinates;
|
||||||
mod file_browser;
|
mod file_browser;
|
||||||
@ -42,17 +40,15 @@ mod async_widget;
|
|||||||
mod fail;
|
mod fail;
|
||||||
|
|
||||||
use window::Window;
|
use window::Window;
|
||||||
///use async_widget::AsyncPlug;
|
|
||||||
use widget::Widget;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut bufout = std::io::BufWriter::new(std::io::stdout());
|
let bufout = std::io::BufWriter::new(std::io::stdout());
|
||||||
// Need to do this here to actually turn terminal into raw mode...
|
// Need to do this here to actually turn terminal into raw mode...
|
||||||
let mut _screen = AlternateScreen::from(Box::new(bufout));
|
let mut _screen = AlternateScreen::from(Box::new(bufout));
|
||||||
let mut _stdout = MouseTerminal::from(stdout().into_raw_mode().unwrap());
|
let mut _stdout = MouseTerminal::from(stdout().into_raw_mode().unwrap());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let filebrowser = crate::file_browser::FileBrowser::new().unwrap();
|
let filebrowser = crate::file_browser::FileBrowser::new().unwrap();
|
||||||
let mut tabview = crate::tabview::TabView::new();
|
let mut tabview = crate::tabview::TabView::new();
|
||||||
tabview.push_widget(filebrowser);
|
tabview.push_widget(filebrowser);
|
||||||
|
103
src/preview.rs
103
src/preview.rs
@ -1,7 +1,5 @@
|
|||||||
use failure::Error;
|
use failure::Error;
|
||||||
use failure::Fail;
|
|
||||||
|
|
||||||
use std::io::Write;
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -14,14 +12,11 @@ use crate::fail::HError;
|
|||||||
|
|
||||||
|
|
||||||
type HResult<T> = Result<T, HError>;
|
type HResult<T> = Result<T, HError>;
|
||||||
type HClosure<T> = Box<Fn() -> Result<T, HError> + Send>;
|
type HClosure<T> = Box<Fn(Arc<Mutex<bool>>) -> Result<T, HError> + Send>;
|
||||||
type WClosure = HClosure<Box<dyn Widget>>;
|
|
||||||
type WidgetO = Box<dyn Widget + Send>;
|
type WidgetO = Box<dyn Widget + Send>;
|
||||||
type WidgetFn = Box<Fn() -> Box<dyn Widget + Send>>;
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref SUBPROC: Arc<Mutex<Option<u32>>> = { Arc::new(Mutex::new(None)) };
|
static ref SUBPROC: Arc<Mutex<Option<u32>>> = { Arc::new(Mutex::new(None)) };
|
||||||
static ref CURFILE: Arc<Mutex<Option<File>>> = { Arc::new(Mutex::new(None)) };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kill_proc() -> HResult<()> {
|
fn kill_proc() -> HResult<()> {
|
||||||
@ -33,17 +28,8 @@ fn kill_proc() -> HResult<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_current(file: &File) -> bool {
|
pub fn is_stale(stale: &Arc<Mutex<bool>>) -> HResult<bool> {
|
||||||
match CURFILE.lock().unwrap().as_ref() {
|
Ok(*(stale.lock()?))
|
||||||
Some(curfile) => curfile == file,
|
|
||||||
None => true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_current(file: &File) -> HResult<()> {
|
|
||||||
let mut curfile = CURFILE.lock()?;
|
|
||||||
*curfile = Some(file.clone());
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum State<T: Send> {
|
enum State<T: Send> {
|
||||||
@ -56,7 +42,7 @@ enum State<T: Send> {
|
|||||||
struct WillBe<T: Send> {
|
struct WillBe<T: Send> {
|
||||||
pub state: State<T>,
|
pub state: State<T>,
|
||||||
rx: std::sync::mpsc::Receiver<T>,
|
rx: std::sync::mpsc::Receiver<T>,
|
||||||
cancel: bool
|
stale: Arc<Mutex<bool>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Send + 'static> WillBe<T> where {
|
impl<T: Send + 'static> WillBe<T> where {
|
||||||
@ -65,14 +51,15 @@ impl<T: Send + 'static> WillBe<T> where {
|
|||||||
let (tx,rx) = std::sync::mpsc::channel();
|
let (tx,rx) = std::sync::mpsc::channel();
|
||||||
let mut willbe = WillBe { state: State::Becoming,
|
let mut willbe = WillBe { state: State::Becoming,
|
||||||
rx: rx,
|
rx: rx,
|
||||||
cancel: false };
|
stale: Arc::new(Mutex::new(false)) };
|
||||||
willbe.run(closure, tx);
|
willbe.run(closure, tx);
|
||||||
willbe
|
willbe
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&mut self, closure: HClosure<T>, tx: std::sync::mpsc::Sender<T>) {
|
fn run(&mut self, closure: HClosure<T>, tx: std::sync::mpsc::Sender<T>) {
|
||||||
|
let stale = self.stale.clone();
|
||||||
std::thread::spawn(move|| {
|
std::thread::spawn(move|| {
|
||||||
let thing = closure();
|
let thing = closure(stale);
|
||||||
match thing {
|
match thing {
|
||||||
Ok(thing) => { tx.send(thing).ok(); },
|
Ok(thing) => { tx.send(thing).ok(); },
|
||||||
Err(err) => { dbg!(err); }
|
Err(err) => { dbg!(err); }
|
||||||
@ -80,6 +67,11 @@ impl<T: Send + 'static> WillBe<T> where {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_stale(&mut self) -> HResult<()> {
|
||||||
|
*self.stale.lock()? = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn check(&mut self) -> Result<(), Error> {
|
pub fn check(&mut self) -> Result<(), Error> {
|
||||||
match self.state {
|
match self.state {
|
||||||
State::Is(_) => Ok(()),
|
State::Is(_) => Ok(()),
|
||||||
@ -91,16 +83,9 @@ impl<T: Send + 'static> WillBe<T> where {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wait(mut self) -> Result<T, std::sync::mpsc::RecvError> {
|
pub fn wait(self) -> Result<T, std::sync::mpsc::RecvError> {
|
||||||
self.rx.recv()
|
self.rx.recv()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn take(mut self) -> Option<T> {
|
|
||||||
match self.state {
|
|
||||||
State::Is(thing) => Some(thing),
|
|
||||||
_ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: Widget + Send> PartialEq for WillBeWidget<W> {
|
impl<W: Widget + Send> PartialEq for WillBeWidget<W> {
|
||||||
@ -121,10 +106,13 @@ struct WillBeWidget<T: Widget + Send> {
|
|||||||
impl<T: Widget + Send + 'static> WillBeWidget<T> {
|
impl<T: Widget + Send + 'static> WillBeWidget<T> {
|
||||||
fn new(closure: HClosure<T>) -> WillBeWidget<T> {
|
fn new(closure: HClosure<T>) -> WillBeWidget<T> {
|
||||||
WillBeWidget {
|
WillBeWidget {
|
||||||
willbe: WillBe::new_become(Box::new(move || closure())),
|
willbe: WillBe::new_become(Box::new(move |stale| closure(stale))),
|
||||||
coordinates: Coordinates::new()
|
coordinates: Coordinates::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn set_stale(&mut self) -> HResult<()> {
|
||||||
|
self.willbe.set_stale()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl<T: Widget + Send> WillBeWidget<T> {
|
// impl<T: Widget + Send> WillBeWidget<T> {
|
||||||
@ -177,8 +165,16 @@ impl<T: Widget + Send> Widget for WillBeWidget<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl PartialEq for Previewer {
|
||||||
|
fn eq(&self, other: &Previewer) -> bool {
|
||||||
|
if self.widget.coordinates == other.widget.coordinates {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
pub struct Previewer {
|
pub struct Previewer {
|
||||||
widget: WillBeWidget<Box<dyn Widget + Send>>,
|
widget: WillBeWidget<Box<dyn Widget + Send>>,
|
||||||
}
|
}
|
||||||
@ -186,12 +182,9 @@ pub struct Previewer {
|
|||||||
|
|
||||||
impl Previewer {
|
impl Previewer {
|
||||||
pub fn new() -> Previewer {
|
pub fn new() -> Previewer {
|
||||||
let willbe = WillBeWidget::new(Box::new(move || {
|
let willbe = WillBeWidget::new(Box::new(move |_| {
|
||||||
Ok(Box::new(crate::textview::TextView {
|
Ok(Box::new(crate::textview::TextView::new_blank())
|
||||||
lines: vec![],
|
as Box<dyn Widget + Send>)
|
||||||
buffer: String::new(),
|
|
||||||
coordinates: Coordinates::new()
|
|
||||||
}) as Box<dyn Widget + Send>)
|
|
||||||
}));
|
}));
|
||||||
Previewer { widget: willbe }
|
Previewer { widget: willbe }
|
||||||
}
|
}
|
||||||
@ -206,22 +199,26 @@ impl Previewer {
|
|||||||
pub fn set_file(&mut self, file: &File) {
|
pub fn set_file(&mut self, file: &File) {
|
||||||
let coordinates = self.get_coordinates().clone();
|
let coordinates = self.get_coordinates().clone();
|
||||||
let file = file.clone();
|
let file = file.clone();
|
||||||
set_current(&file).unwrap();
|
|
||||||
|
|
||||||
self.become_preview(Ok(WillBeWidget::new(Box::new(move || {
|
self.widget.set_stale().ok();
|
||||||
|
|
||||||
|
self.become_preview(Ok(WillBeWidget::new(Box::new(move |stale| {
|
||||||
kill_proc().unwrap();
|
kill_proc().unwrap();
|
||||||
|
|
||||||
let file = file.clone();
|
let file = file.clone();
|
||||||
|
|
||||||
if file.kind == Kind::Directory {
|
if file.kind == Kind::Directory {
|
||||||
let preview = Previewer::preview_dir(&file, &coordinates);
|
let preview = Previewer::preview_dir(&file, &coordinates, stale.clone());
|
||||||
return preview;
|
return preview;
|
||||||
}
|
}
|
||||||
|
|
||||||
if file.get_mime() == Some("text".to_string()) {
|
if file.get_mime() == Some("text".to_string()) {
|
||||||
return Previewer::preview_text(&file, &coordinates)
|
return Previewer::preview_text(&file, &coordinates, stale.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
let preview = Previewer::preview_external(&file, &coordinates);
|
let preview = Previewer::preview_external(&file,
|
||||||
|
&coordinates,
|
||||||
|
stale.clone());
|
||||||
if preview.is_ok() { return preview; }
|
if preview.is_ok() { return preview; }
|
||||||
else {
|
else {
|
||||||
let mut blank = Box::new(TextView::new_blank());
|
let mut blank = Box::new(TextView::new_blank());
|
||||||
@ -237,39 +234,40 @@ impl Previewer {
|
|||||||
Err(HError::PreviewFailed { file: file.name.clone() })
|
Err(HError::PreviewFailed { file: file.name.clone() })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn preview_dir(file: &File, coordinates: &Coordinates)
|
fn preview_dir(file: &File, coordinates: &Coordinates, stale: Arc<Mutex<bool>>)
|
||||||
-> Result<WidgetO, HError> {
|
-> Result<WidgetO, HError> {
|
||||||
let files = Files::new_from_path(&file.path)?;
|
let files = Files::new_from_path_cancellable(&file.path,
|
||||||
|
stale.clone())?;
|
||||||
let len = files.len();
|
let len = files.len();
|
||||||
|
|
||||||
if len == 0 || !is_current(&file) { return Previewer::preview_failed(&file) }
|
if len == 0 || is_stale(&stale)? { return Previewer::preview_failed(&file) }
|
||||||
|
|
||||||
let mut file_list = ListView::new(files);
|
let mut file_list = ListView::new(files);
|
||||||
file_list.set_coordinates(&coordinates);
|
file_list.set_coordinates(&coordinates);
|
||||||
file_list.refresh();
|
file_list.refresh();
|
||||||
if !is_current(&file) { return Previewer::preview_failed(&file) }
|
if is_stale(&stale)? { return Previewer::preview_failed(&file) }
|
||||||
file_list.animate_slide_up();
|
file_list.animate_slide_up();
|
||||||
Ok(Box::new(file_list) as Box<dyn Widget + Send>)
|
Ok(Box::new(file_list) as Box<dyn Widget + Send>)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn preview_text(file: &File, coordinates: &Coordinates)
|
fn preview_text(file: &File, coordinates: &Coordinates, stale: Arc<Mutex<bool>>)
|
||||||
-> HResult<WidgetO> {
|
-> HResult<WidgetO> {
|
||||||
let lines = coordinates.ysize() as usize;
|
let lines = coordinates.ysize() as usize;
|
||||||
let mut textview
|
let mut textview
|
||||||
= TextView::new_from_file_limit_lines(&file,
|
= TextView::new_from_file_limit_lines(&file,
|
||||||
lines);
|
lines);
|
||||||
if !is_current(&file) { return Previewer::preview_failed(&file) }
|
if is_stale(&stale)? { return Previewer::preview_failed(&file) }
|
||||||
|
|
||||||
textview.set_coordinates(&coordinates);
|
textview.set_coordinates(&coordinates);
|
||||||
textview.refresh();
|
textview.refresh();
|
||||||
|
|
||||||
if !is_current(&file) { return Previewer::preview_failed(&file) }
|
if is_stale(&stale)? { return Previewer::preview_failed(&file) }
|
||||||
|
|
||||||
textview.animate_slide_up();
|
textview.animate_slide_up();
|
||||||
Ok(Box::new(textview))
|
Ok(Box::new(textview))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn preview_external(file: &File, coordinates: &Coordinates)
|
fn preview_external(file: &File, coordinates: &Coordinates, stale: Arc<Mutex<bool>>)
|
||||||
-> Result<Box<dyn Widget + Send>, HError> {
|
-> Result<Box<dyn Widget + Send>, HError> {
|
||||||
let process =
|
let process =
|
||||||
std::process::Command::new("scope.sh")
|
std::process::Command::new("scope.sh")
|
||||||
@ -289,12 +287,11 @@ impl Previewer {
|
|||||||
*pid_ = Some(pid);
|
*pid_ = Some(pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !is_current(&file) { return Previewer::preview_failed(&file) }
|
if is_stale(&stale)? { return Previewer::preview_failed(&file) }
|
||||||
|
|
||||||
let output = process.wait_with_output()?;
|
let output = process.wait_with_output()?;
|
||||||
|
|
||||||
if !is_current(&file) { return Previewer::preview_failed(&file) }
|
if is_stale(&stale)? { return Previewer::preview_failed(&file) }
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut pid_ = SUBPROC.lock()?;
|
let mut pid_ = SUBPROC.lock()?;
|
||||||
*pid_ = None;
|
*pid_ = None;
|
||||||
@ -303,7 +300,7 @@ impl Previewer {
|
|||||||
let status = output.status.code()
|
let status = output.status.code()
|
||||||
.ok_or(HError::PreviewFailed{file: file.name.clone()})?;
|
.ok_or(HError::PreviewFailed{file: file.name.clone()})?;
|
||||||
|
|
||||||
if status == 0 || status == 5 && is_current(&file) {
|
if status == 0 || status == 5 && !is_stale(&stale)? { //is_current(&file) {
|
||||||
let output = std::str::from_utf8(&output.stdout)
|
let output = std::str::from_utf8(&output.stdout)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use termion::event::Key;
|
use termion::event::Key;
|
||||||
|
|
||||||
use crate::coordinates::{Coordinates, Position, Size};
|
use crate::coordinates::{Coordinates};
|
||||||
use crate::widget::Widget;
|
use crate::widget::Widget;
|
||||||
|
|
||||||
pub trait Tabbable<T: Widget> {
|
pub trait Tabbable<T: Widget> {
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
use ::rayon::prelude::*;
|
|
||||||
|
|
||||||
use std::io::BufRead;
|
use std::io::BufRead;
|
||||||
|
|
||||||
use crate::coordinates::{Coordinates, Position, Size};
|
use crate::coordinates::{Coordinates};
|
||||||
use crate::files::File;
|
use crate::files::File;
|
||||||
use crate::term::sized_string;
|
use crate::term::sized_string;
|
||||||
use crate::widget::Widget;
|
use crate::widget::Widget;
|
||||||
|
@ -6,6 +6,9 @@ use std::io::{BufWriter, Write};
|
|||||||
|
|
||||||
|
|
||||||
pub trait Widget {
|
pub trait Widget {
|
||||||
|
fn get_widget(&self) -> Box<dyn Widget> {
|
||||||
|
Box::new(crate::textview::TextView::new_blank())
|
||||||
|
}
|
||||||
fn get_coordinates(&self) -> &Coordinates;
|
fn get_coordinates(&self) -> &Coordinates;
|
||||||
fn set_coordinates(&mut self, coordinates: &Coordinates);
|
fn set_coordinates(&mut self, coordinates: &Coordinates);
|
||||||
fn render_header(&self) -> String;
|
fn render_header(&self) -> String;
|
||||||
@ -120,7 +123,7 @@ pub trait Widget {
|
|||||||
let ysize = coords.ysize();
|
let ysize = coords.ysize();
|
||||||
let clear = self.get_clearlist();
|
let clear = self.get_clearlist();
|
||||||
let pause = std::time::Duration::from_millis(5);
|
let pause = std::time::Duration::from_millis(5);
|
||||||
let mut bufout = std::io::BufWriter::new(std::io::stdout());
|
let mut bufout = BufWriter::new(std::io::stdout());
|
||||||
|
|
||||||
for i in (0..10).rev() {
|
for i in (0..10).rev() {
|
||||||
let coords = Coordinates { size: Size((xsize,ysize-i)),
|
let coords = Coordinates { size: Size((xsize,ysize-i)),
|
||||||
@ -132,8 +135,7 @@ pub trait Widget {
|
|||||||
let buffer = self.get_drawlist();
|
let buffer = self.get_drawlist();
|
||||||
write!(bufout, "{}{}",
|
write!(bufout, "{}{}",
|
||||||
clear, buffer).unwrap();
|
clear, buffer).unwrap();
|
||||||
bufout.flush();
|
bufout.flush().ok();
|
||||||
|
|
||||||
|
|
||||||
std::thread::sleep(pause);
|
std::thread::sleep(pause);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user