mirror of
https://github.com/bobwen-dev/hunter
synced 2025-04-08 20:05:40 +02:00
1061 lines
35 KiB
Rust
1061 lines
35 KiB
Rust
use termion::event::Key;
|
|
|
|
use std::io::Write;
|
|
use std::sync::{Arc, Mutex, RwLock};
|
|
use std::path::PathBuf;
|
|
use std::ffi::OsString;
|
|
use std::collections::HashSet;
|
|
|
|
use crate::files::{File, Files, PathBufExt};
|
|
use crate::fscache::FsCache;
|
|
use crate::listview::ListView;
|
|
use crate::hbox::HBox;
|
|
use crate::widget::Widget;
|
|
use crate::tabview::{TabView, Tabbable};
|
|
use crate::preview::{Previewer, AsyncWidget};
|
|
use crate::textview::TextView;
|
|
use crate::fail::{HResult, HError, ErrorLog};
|
|
use crate::widget::{Events, WidgetCore};
|
|
use crate::proclist::ProcView;
|
|
use crate::bookmarks::BMPopup;
|
|
use crate::term;
|
|
use crate::term::ScreenExt;
|
|
use crate::foldview::LogView;
|
|
use crate::coordinates::Coordinates;
|
|
use crate::dirty::Dirtyable;
|
|
use crate::stats::{FsStat, FsExt};
|
|
|
|
#[derive(PartialEq)]
|
|
pub enum FileBrowserWidgets {
|
|
FileList(AsyncWidget<ListView<Files>>),
|
|
Previewer(Previewer),
|
|
Blank(AsyncWidget<TextView>),
|
|
}
|
|
|
|
impl Widget for FileBrowserWidgets {
|
|
fn get_core(&self) -> HResult<&WidgetCore> {
|
|
match self {
|
|
FileBrowserWidgets::FileList(widget) => widget.get_core(),
|
|
FileBrowserWidgets::Previewer(widget) => widget.get_core(),
|
|
FileBrowserWidgets::Blank(widget) => widget.get_core(),
|
|
}
|
|
}
|
|
fn get_core_mut(&mut self) -> HResult<&mut WidgetCore> {
|
|
match self {
|
|
FileBrowserWidgets::FileList(widget) => widget.get_core_mut(),
|
|
FileBrowserWidgets::Previewer(widget) => widget.get_core_mut(),
|
|
FileBrowserWidgets::Blank(widget) => widget.get_core_mut(),
|
|
}
|
|
}
|
|
fn set_coordinates(&mut self, coordinates: &Coordinates) -> HResult<()> {
|
|
match self {
|
|
FileBrowserWidgets::FileList(widget) => widget.set_coordinates(coordinates),
|
|
FileBrowserWidgets::Previewer(widget) => widget.set_coordinates(coordinates),
|
|
FileBrowserWidgets::Blank(widget) => widget.set_coordinates(coordinates),
|
|
}
|
|
}
|
|
fn refresh(&mut self) -> HResult<()> {
|
|
match self {
|
|
FileBrowserWidgets::FileList(widget) => widget.refresh(),
|
|
FileBrowserWidgets::Previewer(widget) => widget.refresh(),
|
|
FileBrowserWidgets::Blank(widget) => widget.refresh(),
|
|
}
|
|
}
|
|
fn get_drawlist(&self) -> HResult<String> {
|
|
match self {
|
|
FileBrowserWidgets::FileList(widget) => widget.get_drawlist(),
|
|
FileBrowserWidgets::Previewer(widget) => widget.get_drawlist(),
|
|
FileBrowserWidgets::Blank(widget) => widget.get_drawlist(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct FileBrowser {
|
|
pub columns: HBox<FileBrowserWidgets>,
|
|
pub cwd: File,
|
|
pub prev_cwd: Option<File>,
|
|
core: WidgetCore,
|
|
proc_view: Arc<Mutex<ProcView>>,
|
|
bookmarks: Arc<Mutex<BMPopup>>,
|
|
log_view: Arc<Mutex<LogView>>,
|
|
fs_cache: FsCache,
|
|
fs_stat: Arc<RwLock<FsStat>>
|
|
}
|
|
|
|
impl Tabbable for TabView<FileBrowser> {
|
|
fn new_tab(&mut self) -> HResult<()> {
|
|
let cur_tab = self.active_tab_();
|
|
|
|
let settings = cur_tab.fs_cache.tab_settings.read()?.clone();
|
|
let cache = cur_tab.fs_cache.new_client(settings).ok();
|
|
|
|
let mut tab = FileBrowser::new(&self.active_tab_().core, cache)?;
|
|
|
|
let proc_view = cur_tab.proc_view.clone();
|
|
let bookmarks = cur_tab.bookmarks.clone();
|
|
let log_view = cur_tab.log_view.clone();
|
|
tab.proc_view = proc_view;
|
|
tab.bookmarks = bookmarks;
|
|
tab.log_view = log_view;
|
|
tab.fs_stat = cur_tab.fs_stat.clone();
|
|
|
|
self.push_widget(tab)?;
|
|
self.active = self.widgets.len() - 1;
|
|
Ok(())
|
|
}
|
|
|
|
fn close_tab(&mut self) -> HResult<()> {
|
|
self.close_tab_().log();
|
|
Ok(())
|
|
}
|
|
|
|
fn next_tab(&mut self) -> HResult<()> {
|
|
self.next_tab_();
|
|
Ok(())
|
|
}
|
|
|
|
fn goto_tab(&mut self, index: usize) -> HResult<()> {
|
|
self.goto_tab_(index)
|
|
}
|
|
|
|
fn get_tab_names(&self) -> Vec<Option<String>> {
|
|
self.widgets.iter().map(|filebrowser| {
|
|
let path = filebrowser.cwd.path();
|
|
let last_dir = path.components().last().unwrap();
|
|
let dir_name = last_dir.as_os_str().to_string_lossy().to_string();
|
|
Some(dir_name)
|
|
}).collect()
|
|
}
|
|
|
|
fn active_tab(& self) -> & dyn Widget {
|
|
self.active_tab_()
|
|
}
|
|
|
|
fn active_tab_mut(&mut self) -> &mut dyn Widget {
|
|
self.active_tab_mut_()
|
|
}
|
|
|
|
fn on_tab_switch(&mut self) -> HResult<()> {
|
|
self.active_tab_mut().refresh()
|
|
}
|
|
|
|
fn on_key_sub(&mut self, key: Key) -> HResult<()> {
|
|
match key {
|
|
Key::Char('!') => {
|
|
let tab_dirs = self.widgets.iter().map(|w| w.cwd.clone())
|
|
.collect::<Vec<_>>();
|
|
let selected_files = self
|
|
.widgets
|
|
.iter()
|
|
.map(|w| {
|
|
w.selected_files()
|
|
.map_err(|_| Vec::<Files>::new())
|
|
.unwrap()
|
|
}).collect();
|
|
|
|
self.widgets[self.active].exec_cmd(tab_dirs, selected_files)
|
|
}
|
|
_ => { self.active_tab_mut().on_key(key) }
|
|
}
|
|
}
|
|
|
|
fn on_refresh(&mut self) -> HResult<()> {
|
|
let fs_changes = self.active_tab_()
|
|
.fs_cache
|
|
.fs_changes
|
|
.write()?
|
|
.drain(..)
|
|
.collect::<Vec<_>>();
|
|
|
|
for tab in &mut self.widgets {
|
|
for (dir, old_file, new_file) in fs_changes.iter() {
|
|
tab.replace_file(&dir,
|
|
old_file.as_ref(),
|
|
new_file.as_ref()).log()
|
|
}
|
|
}
|
|
|
|
let open_dirs = self.widgets
|
|
.iter()
|
|
.fold(HashSet::new(), |mut dirs, tab| {
|
|
tab.left_dir().map(|dir| dirs.insert(dir.clone())).ok();
|
|
dirs.insert(tab.cwd.clone());
|
|
tab.preview_widget()
|
|
.map(|preview| preview.get_file().map(|file| {
|
|
if file.is_dir() {
|
|
dirs.insert(file.clone());
|
|
}
|
|
})).ok();
|
|
dirs
|
|
});
|
|
|
|
self.active_tab_mut_().fs_cache.watch_only(open_dirs).log();
|
|
self.active_tab_mut_().fs_stat.write()?.refresh().log();
|
|
Ok(())
|
|
}
|
|
|
|
fn on_config_loaded(&mut self) -> HResult<()> {
|
|
// hack: wait a bit for widget readyness...
|
|
std::thread::sleep_ms(100);
|
|
|
|
let show_hidden = self.config().show_hidden();
|
|
for tab in self.widgets.iter_mut() {
|
|
tab.left_widget_mut().map(|w| {
|
|
w.content.show_hidden = show_hidden;
|
|
w.content.dirty_meta.set_dirty();
|
|
w.refresh().log();
|
|
}).ok();
|
|
|
|
tab.main_widget_mut().map(|w| {
|
|
w.content.show_hidden = show_hidden;
|
|
w.content.dirty_meta.set_dirty();
|
|
w.content.sort();
|
|
w.refresh().log();
|
|
}).ok();
|
|
|
|
tab.preview_widget_mut().map(|w| w.config_loaded()).ok();
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl FileBrowser {
|
|
pub fn new(core: &WidgetCore, cache: Option<FsCache>) -> HResult<FileBrowser> {
|
|
let startup = cache.is_none();
|
|
let fs_cache = cache.unwrap_or_else(|| FsCache::new(core.get_sender()));
|
|
|
|
let cwd = std::env::current_dir().unwrap();
|
|
let mut core_m = core.clone();
|
|
let mut core_l = core.clone();
|
|
let mut core_p = core.clone();
|
|
|
|
let mut columns = HBox::new(core);
|
|
columns.set_ratios(vec![20,30,49]);
|
|
let list_coords = columns.calculate_coordinates()?;
|
|
|
|
core_l.coordinates = list_coords[0].clone();
|
|
core_m.coordinates = list_coords[1].clone();
|
|
core_p.coordinates = list_coords[2].clone();
|
|
|
|
let main_path = cwd.ancestors()
|
|
.take(1)
|
|
.map(|path| {
|
|
std::path::PathBuf::from(path)
|
|
}).last()?;
|
|
let left_path = main_path.parent().map(|p| p.to_path_buf());
|
|
|
|
let cache = fs_cache.clone();
|
|
let main_widget = AsyncWidget::new(&core, Box::new(move |_| {
|
|
let name = if main_path.parent().is_none() {
|
|
"root".to_string()
|
|
} else {
|
|
main_path.file_name()?
|
|
.to_string_lossy()
|
|
.to_string()
|
|
};
|
|
let main_dir = File::new(&name,
|
|
main_path.clone(),
|
|
None);
|
|
let files = cache.get_files_sync(&main_dir)?;
|
|
let selection = cache.get_selection(&main_dir).ok();
|
|
let mut list = ListView::new(&core_m.clone(),
|
|
files);
|
|
if let Some(file) = selection {
|
|
list.select_file(&file);
|
|
}
|
|
|
|
list.content.meta_all();
|
|
list.content.dirty_meta.set_dirty();
|
|
list.refresh().log();
|
|
|
|
if startup {
|
|
list.animate_slide_up(None).log();
|
|
}
|
|
|
|
list.content.meta_all();
|
|
Ok(list)
|
|
}));
|
|
|
|
let cache = fs_cache.clone();
|
|
if let Some(left_path) = left_path {
|
|
let left_widget = AsyncWidget::new(&core, Box::new(move |_| {
|
|
let name = if left_path.parent().is_none() {
|
|
"root".to_string()
|
|
} else {
|
|
left_path.file_name()?
|
|
.to_string_lossy()
|
|
.to_string()
|
|
};
|
|
let left_dir = File::new(&name,
|
|
left_path.clone(),
|
|
None);
|
|
let files = cache.get_files_sync(&left_dir)?;
|
|
let selection = cache.get_selection(&left_dir).ok();
|
|
let mut list = ListView::new(&core_l,
|
|
files);
|
|
if let Some(file) = selection {
|
|
list.select_file(&file);
|
|
}
|
|
|
|
list.refresh().log();
|
|
|
|
if startup {
|
|
list.animate_slide_up(None).log();
|
|
}
|
|
|
|
Ok(list)
|
|
}));
|
|
let left_widget = FileBrowserWidgets::FileList(left_widget);
|
|
columns.push_widget(left_widget);
|
|
} else {
|
|
let left_widget = AsyncWidget::new(&core, Box::new(move |_| {
|
|
let blank = TextView::new_blank(&core_l);
|
|
Ok(blank)
|
|
}));
|
|
|
|
let left_widget = FileBrowserWidgets::Blank(left_widget);
|
|
columns.push_widget(left_widget);
|
|
}
|
|
|
|
let previewer = Previewer::new(&core_p, fs_cache.clone());
|
|
|
|
columns.push_widget(FileBrowserWidgets::FileList(main_widget));
|
|
columns.push_widget(FileBrowserWidgets::Previewer(previewer));
|
|
columns.set_active(1).log();
|
|
columns.refresh().log();
|
|
|
|
|
|
let cwd = File::new_from_path(&cwd, None).unwrap();
|
|
|
|
let proc_view = ProcView::new(&core);
|
|
let bookmarks = BMPopup::new(&core);
|
|
let log_view = LogView::new(&core, vec![]);
|
|
let fs_stat = FsStat::new().unwrap();
|
|
|
|
|
|
|
|
Ok(FileBrowser { columns: columns,
|
|
cwd: cwd,
|
|
prev_cwd: None,
|
|
core: core.clone(),
|
|
proc_view: Arc::new(Mutex::new(proc_view)),
|
|
bookmarks: Arc::new(Mutex::new(bookmarks)),
|
|
log_view: Arc::new(Mutex::new(log_view)),
|
|
fs_cache: fs_cache,
|
|
fs_stat: Arc::new(RwLock::new(fs_stat))
|
|
})
|
|
}
|
|
|
|
pub fn enter_dir(&mut self) -> HResult<()> {
|
|
let file = self.selected_file()?;
|
|
|
|
if file.is_dir() {
|
|
let dir = file;
|
|
match dir.is_readable() {
|
|
Ok(true) => {},
|
|
Ok(false) => {
|
|
let status =
|
|
format!("{}Stop right there, cowboy! Check your permisions!",
|
|
term::color_red());
|
|
self.show_status(&status).log();
|
|
return Ok(());
|
|
}
|
|
err @ Err(_) => err.log()
|
|
}
|
|
|
|
let previewer_files = self.preview_widget_mut()?.take_files().ok();
|
|
|
|
self.columns.remove_widget(0);
|
|
|
|
self.prev_cwd = Some(self.cwd.clone());
|
|
self.cwd = dir.clone();
|
|
|
|
let core = self.core.clone();
|
|
let cache = self.fs_cache.clone();
|
|
|
|
let main_widget = AsyncWidget::new(&core.clone(), Box::new(move |_| {
|
|
let files = match previewer_files {
|
|
Some(files) => files,
|
|
None => cache.get_files_sync(&dir)?
|
|
};
|
|
|
|
let selection = cache.get_selection(&dir).ok();
|
|
|
|
let mut list = ListView::new(&core, files);
|
|
|
|
if let Some(file) = selection {
|
|
list.select_file(&file);
|
|
}
|
|
|
|
list.content.meta_all();
|
|
|
|
Ok(list)
|
|
}));
|
|
|
|
let main_widget = FileBrowserWidgets::FileList(main_widget);
|
|
self.columns.insert_widget(1, main_widget);
|
|
|
|
} else {
|
|
self.core.get_sender().send(Events::InputEnabled(false))?;
|
|
|
|
let status = std::process::Command::new("rifle")
|
|
.args(file.path.file_name())
|
|
.status();
|
|
|
|
self.clear().log();
|
|
self.core.screen.cursor_hide().log();
|
|
|
|
self.core.get_sender().send(Events::InputEnabled(true))?;
|
|
|
|
match status {
|
|
Ok(status) =>
|
|
self.show_status(&format!("\"{}\" exited with {}",
|
|
"rifle", status)).log(),
|
|
Err(err) =>
|
|
self.show_status(&format!("Can't run this \"{}\": {}",
|
|
"rifle", err)).log()
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn open_bg(&mut self) -> HResult<()> {
|
|
let cwd = self.cwd()?;
|
|
let file = self.selected_file()?;
|
|
|
|
let cmd = crate::proclist::Cmd {
|
|
cmd: OsString::from(file.strip_prefix(&cwd)),
|
|
short_cmd: None,
|
|
args: None,
|
|
cwd: cwd.clone(),
|
|
cwd_files: None,
|
|
tab_files: None,
|
|
tab_paths: None
|
|
};
|
|
|
|
self.proc_view.lock()?.run_proc_raw(cmd)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn main_widget_goto(&mut self, dir: &File) -> HResult<()> {
|
|
self.cache_files().log();
|
|
|
|
let dir = dir.clone();
|
|
let cache = self.fs_cache.clone();
|
|
|
|
self.prev_cwd = Some(self.cwd.clone());
|
|
self.cwd = dir.clone();
|
|
|
|
let main_async_widget = self.main_async_widget_mut()?;
|
|
main_async_widget.change_to(Box::new(move |stale, core| {
|
|
let (selected_file, files) = cache.get_files(&dir, stale)?;
|
|
let files = files.wait()?;
|
|
|
|
let mut list = ListView::new(&core, files);
|
|
|
|
list.content.meta_set_fresh().log();
|
|
list.content.meta_all();
|
|
|
|
if let Some(file) = selected_file {
|
|
list.select_file(&file);
|
|
}
|
|
Ok(list)
|
|
})).log();
|
|
|
|
if let Ok(grand_parent) = self.cwd()?.parent_as_file() {
|
|
self.left_widget_goto(&grand_parent).log();
|
|
} else {
|
|
self.left_async_widget_mut()?.change_to(Box::new(move |_,_| {
|
|
HError::stale()?
|
|
})).log();
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn left_widget_goto(&mut self, dir: &File) -> HResult<()> {
|
|
let cache = self.fs_cache.clone();
|
|
let dir = dir.clone();
|
|
|
|
let left_async_widget = self.left_async_widget_mut()?;
|
|
left_async_widget.change_to(Box::new(move |stale, core| {
|
|
let cached_files = cache.get_files(&dir, stale)?;
|
|
let (_, files) = cached_files;
|
|
|
|
let files = files.wait()?;
|
|
|
|
let list = ListView::new(&core, files);
|
|
Ok(list)
|
|
}))?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn go_back(&mut self) -> HResult<()> {
|
|
if let Ok(new_cwd) = self.cwd.parent_as_file() {
|
|
let core = self.core.clone();
|
|
let preview_files = self.take_main_files();
|
|
let old_left = self.columns.remove_widget(0);
|
|
self.prev_cwd = Some(self.cwd.clone());
|
|
self.cwd = new_cwd.clone();
|
|
|
|
if let Ok(left_dir) = new_cwd.parent_as_file() {
|
|
let cache = self.fs_cache.clone();
|
|
let left_widget = AsyncWidget::new(&core.clone(), Box::new(move |_| {
|
|
let files = cache.get_files_sync(&left_dir)?;
|
|
let list = ListView::new(&core, files);
|
|
Ok(list)
|
|
}));
|
|
|
|
let left_widget = FileBrowserWidgets::FileList(left_widget);
|
|
self.columns.prepend_widget(left_widget);
|
|
} else {
|
|
let left_widget = AsyncWidget::new(&core.clone(), Box::new(move |_| {
|
|
let blank = TextView::new_blank(&core);
|
|
Ok(blank)
|
|
}));
|
|
|
|
let left_widget = FileBrowserWidgets::Blank(left_widget);
|
|
self.columns.prepend_widget(left_widget);
|
|
}
|
|
self.columns.replace_widget(1, old_left);
|
|
self.main_widget_mut()?.content.meta_all();
|
|
|
|
if let Ok(preview_files) = preview_files {
|
|
self.preview_widget_mut().map(|preview| {
|
|
preview.put_preview_files(preview_files)
|
|
}).ok();
|
|
}
|
|
}
|
|
|
|
self.columns.resize_children();
|
|
self.refresh()
|
|
}
|
|
|
|
pub fn goto_prev_cwd(&mut self) -> HResult<()> {
|
|
let prev_cwd = self.prev_cwd.take()?;
|
|
self.main_widget_goto(&prev_cwd)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn get_boomark(&mut self) -> HResult<String> {
|
|
let cwd = &match self.prev_cwd.as_ref() {
|
|
Some(cwd) => cwd,
|
|
None => &self.cwd
|
|
}.path.to_string_lossy().to_string();
|
|
|
|
self.bookmarks.lock()?.set_coordinates(&self.core.coordinates).log();
|
|
|
|
loop {
|
|
let bookmark = self.bookmarks.lock()?.pick(cwd.to_string());
|
|
|
|
if let Err(HError::TerminalResizedError) = bookmark {
|
|
self.core.screen.clear().log();
|
|
self.resize().log();
|
|
self.refresh().log();
|
|
self.draw().log();
|
|
continue;
|
|
}
|
|
|
|
if let Err(HError::WidgetResizedError) = bookmark {
|
|
let coords = &self.core.coordinates;
|
|
self.bookmarks.lock()?.set_coordinates(&coords).log();
|
|
self.core.screen.clear().log();
|
|
self.refresh().log();
|
|
self.draw().log();
|
|
continue;
|
|
}
|
|
return bookmark;
|
|
}
|
|
}
|
|
|
|
pub fn goto_bookmark(&mut self) -> HResult<()> {
|
|
let path = self.get_boomark()?;
|
|
let path = File::new_from_path(&PathBuf::from(path), None)?;
|
|
self.main_widget_goto(&path)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn add_bookmark(&mut self) -> HResult<()> {
|
|
let cwd = self.cwd.path.to_string_lossy().to_string();
|
|
let coords = &self.core.coordinates;
|
|
self.bookmarks.lock()?.set_coordinates(&coords).log();
|
|
self.bookmarks.lock()?.add(&cwd)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn set_title(&self) -> HResult<()> {
|
|
let path = self.cwd.short_string();
|
|
|
|
self.screen()?.set_title(&path)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn update_preview(&mut self) -> HResult<()> {
|
|
if !self.main_async_widget_mut()?.ready() { return Ok(()) }
|
|
if self.main_widget()?
|
|
.content
|
|
.len() == 0 {
|
|
self.preview_widget_mut()?.set_stale().log();
|
|
return Ok(());
|
|
}
|
|
let file = self.selected_file()?.clone();
|
|
let preview = self.preview_widget_mut()?;
|
|
preview.set_file(&file).log();
|
|
Ok(())
|
|
}
|
|
|
|
pub fn set_left_selection(&mut self) -> HResult<()> {
|
|
if self.cwd.parent().is_none() { return Ok(()) }
|
|
if !self.left_async_widget_mut()?.ready() { return Ok(()) }
|
|
|
|
let selection = self.cwd()?.clone();
|
|
|
|
self.left_widget_mut()?.select_file(&selection);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn take_main_files(&mut self) -> HResult<Files> {
|
|
let core = self.core.clone();
|
|
let blank = AsyncWidget::new(&core.clone(), Box::new(move |_| {
|
|
HError::no_files()
|
|
}));
|
|
let blank = FileBrowserWidgets::Blank(blank);
|
|
|
|
let old_widget = self.columns.replace_widget(1, blank);
|
|
|
|
if let FileBrowserWidgets::FileList(main_widget) = old_widget {
|
|
let files = main_widget.take_widget()?.content;
|
|
return Ok(files)
|
|
}
|
|
HError::no_files()
|
|
}
|
|
|
|
pub fn take_left_files(&mut self) -> HResult<Files> {
|
|
let core = self.core.clone();
|
|
let blank = AsyncWidget::new(&core.clone(), Box::new(move |_| {
|
|
HError::no_files()
|
|
}));
|
|
let blank = FileBrowserWidgets::FileList(blank);
|
|
|
|
let old_widget = self.columns.replace_widget(0, blank);
|
|
|
|
if let FileBrowserWidgets::FileList(left_widget) = old_widget {
|
|
let files = left_widget.take_widget()?.content;
|
|
return Ok(files)
|
|
}
|
|
HError::no_files()
|
|
}
|
|
|
|
pub fn get_files(&self) -> HResult<&Files> {
|
|
Ok(&self.main_widget()?.content)
|
|
}
|
|
|
|
pub fn get_left_files(&self) -> HResult<&Files> {
|
|
Ok(&self.left_widget()?.content)
|
|
}
|
|
|
|
pub fn cache_files(&mut self) -> HResult<()> {
|
|
let files = self.get_files()?;
|
|
let selected_file = self.selected_file().ok();
|
|
self.fs_cache.put_files(files, selected_file).log();
|
|
self.main_widget_mut()?.content.meta_updated = false;
|
|
|
|
|
|
// if self.cwd.parent().is_some() {
|
|
// let left_selection = self.left_widget()?.clone_selected_file();
|
|
// let left_files = self.get_left_files()?;
|
|
// self.fs_cache.put_files(left_files, Some(left_selection)).log();
|
|
// self.left_widget_mut()?.content.meta_updated = false;
|
|
// }
|
|
|
|
Ok(())
|
|
}
|
|
|
|
|
|
pub fn cwd(&self) -> HResult<&File> {
|
|
Ok(&self.cwd)
|
|
}
|
|
|
|
pub fn set_cwd(&mut self) -> HResult<()> {
|
|
let cwd = self.cwd()?;
|
|
std::env::set_current_dir(&cwd.path)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn left_dir(&self) -> HResult<&File> {
|
|
let widget = self.left_widget()?;
|
|
let dir = &widget.content.directory;
|
|
Ok(dir)
|
|
}
|
|
|
|
fn replace_file(&mut self,
|
|
dir: &File,
|
|
old: Option<&File>,
|
|
new: Option<&File>) -> HResult<()> {
|
|
if &self.cwd == dir {
|
|
self.main_widget_mut()?.content.replace_file(old, new.cloned()).log();
|
|
}
|
|
|
|
self.preview_widget_mut()?.replace_file(dir, old, new).ok();
|
|
|
|
if &self.left_dir()? == &dir {
|
|
self.left_widget_mut()?.content.replace_file(old, new.cloned()).log();
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn selected_file(&self) -> HResult<File> {
|
|
let widget = self.main_widget()?;
|
|
let file = widget.selected_file().clone();
|
|
Ok(file)
|
|
}
|
|
|
|
pub fn selected_files(&self) -> HResult<Vec<File>> {
|
|
let widget = self.main_widget()?;
|
|
let files = widget.content.get_selected().into_iter().map(|f| {
|
|
f.clone()
|
|
}).collect();
|
|
Ok(files)
|
|
}
|
|
|
|
pub fn main_async_widget_mut(&mut self) -> HResult<&mut AsyncWidget<ListView<Files>>> {
|
|
let widget = self.columns.active_widget_mut()?;
|
|
|
|
let widget = match widget {
|
|
FileBrowserWidgets::FileList(filelist) => filelist,
|
|
_ => { HError::wrong_widget("previewer", "filelist")? }
|
|
};
|
|
Ok(widget)
|
|
}
|
|
|
|
pub fn main_widget(&self) -> HResult<&ListView<Files>> {
|
|
let widget = self.columns.active_widget()?;
|
|
|
|
let widget = match widget {
|
|
FileBrowserWidgets::FileList(filelist) => filelist.widget(),
|
|
_ => { HError::wrong_widget("previewer", "filelist")? }
|
|
};
|
|
widget
|
|
}
|
|
|
|
pub fn main_widget_mut(&mut self) -> HResult<&mut ListView<Files>> {
|
|
let widget = self.columns.active_widget_mut()?;
|
|
|
|
let widget = match widget {
|
|
FileBrowserWidgets::FileList(filelist) => filelist.widget_mut(),
|
|
_ => { HError::wrong_widget("previewer", "filelist")? }
|
|
};
|
|
widget
|
|
}
|
|
|
|
pub fn left_async_widget_mut(&mut self) -> HResult<&mut AsyncWidget<ListView<Files>>> {
|
|
let widget = match self.columns.widgets.get_mut(0)? {
|
|
FileBrowserWidgets::FileList(filelist) => filelist,
|
|
_ => { return HError::wrong_widget("previewer", "filelist"); }
|
|
};
|
|
Ok(widget)
|
|
}
|
|
|
|
pub fn left_widget(&self) -> HResult<&ListView<Files>> {
|
|
let widget = match self.columns.widgets.get(0)? {
|
|
FileBrowserWidgets::FileList(filelist) => filelist.widget(),
|
|
_ => { return HError::wrong_widget("previewer", "filelist"); }
|
|
};
|
|
widget
|
|
}
|
|
|
|
pub fn left_widget_mut(&mut self) -> HResult<&mut ListView<Files>> {
|
|
let widget = match self.columns.widgets.get_mut(0)? {
|
|
FileBrowserWidgets::FileList(filelist) => filelist.widget_mut(),
|
|
_ => { return HError::wrong_widget("previewer", "filelist"); }
|
|
};
|
|
widget
|
|
}
|
|
|
|
pub fn preview_widget(&self) -> HResult<&Previewer> {
|
|
match self.columns.widgets.get(2)? {
|
|
FileBrowserWidgets::Previewer(previewer) => Ok(previewer),
|
|
_ => { return HError::wrong_widget("filelist", "previewer"); }
|
|
}
|
|
}
|
|
|
|
pub fn preview_widget_mut(&mut self) -> HResult<&mut Previewer> {
|
|
match self.columns.widgets.get_mut(2)? {
|
|
FileBrowserWidgets::Previewer(previewer) => Ok(previewer),
|
|
_ => { return HError::wrong_widget("filelist", "previewer"); }
|
|
}
|
|
}
|
|
|
|
pub fn toggle_colums(&mut self) {
|
|
self.preview_widget().map(|preview| preview.cancel_animation()).log();
|
|
self.columns.toggle_zoom().log();
|
|
}
|
|
|
|
pub fn quit_with_dir(&self) -> HResult<()> {
|
|
let cwd = self.cwd()?.clone().path;
|
|
let selected_file = self.selected_file()?;
|
|
let selected_file = selected_file.path.to_string_lossy();
|
|
let selected_files = self.selected_files()?;
|
|
|
|
let selected_files = selected_files.iter().map(|f| {
|
|
format!("\"{}\" ", &f.path.to_string_lossy())
|
|
}).collect::<String>();
|
|
|
|
let mut filepath = dirs_2::home_dir()?;
|
|
filepath.push(".hunter_cwd");
|
|
|
|
let output = format!("HUNTER_CWD=\"{}\"\nF=\"{}\"\nMF=({})\n",
|
|
cwd.to_str()?,
|
|
selected_file,
|
|
selected_files);
|
|
|
|
let mut file = std::fs::File::create(filepath)?;
|
|
file.write(output.as_bytes())?;
|
|
HError::quit()
|
|
}
|
|
|
|
pub fn turbo_cd(&mut self) -> HResult<()> {
|
|
let dir = self.minibuffer("cd")?;
|
|
|
|
let path = std::path::PathBuf::from(&dir);
|
|
let dir = File::new_from_path(&path.canonicalize()?, None)?;
|
|
self.main_widget_goto(&dir)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn exec_cmd(&mut self,
|
|
tab_dirs: Vec<File>,
|
|
tab_files: Vec<Vec<File>>) -> HResult<()> {
|
|
|
|
let cwd = self.cwd()?.clone();
|
|
let selected_file = self.selected_file()?;
|
|
let selected_files = self.selected_files()?;
|
|
|
|
let cmd = self.minibuffer("exec")?.trim_start().to_string() + " ";
|
|
|
|
let cwd_files = if selected_files.len() == 0 {
|
|
vec![selected_file]
|
|
} else { selected_files };
|
|
|
|
let cmd = crate::proclist::Cmd {
|
|
cmd: OsString::from(cmd),
|
|
short_cmd: None,
|
|
args: None,
|
|
cwd: cwd,
|
|
cwd_files: Some(cwd_files),
|
|
tab_files: Some(tab_files),
|
|
tab_paths: Some(tab_dirs)
|
|
};
|
|
|
|
self.proc_view.lock()?.run_proc_subshell(cmd)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn run_subshell(&mut self) -> HResult<()> {
|
|
self.core.get_sender().send(Events::InputEnabled(false))?;
|
|
|
|
self.preview_widget().map(|preview| preview.cancel_animation()).log();
|
|
self.core.screen.cursor_show().log();
|
|
self.core.screen.drop_screen();
|
|
|
|
let shell = std::env::var("SHELL").unwrap_or("bash".into());
|
|
let status = std::process::Command::new(&shell).status();
|
|
|
|
self.core.screen.reset_screen().log();
|
|
|
|
|
|
self.core.get_sender().send(Events::InputEnabled(true))?;
|
|
|
|
match status {
|
|
Ok(status) =>
|
|
self.show_status(&format!("\"{}\" exited with {}",
|
|
shell, status)).log(),
|
|
Err(err) =>
|
|
self.show_status(&format!("Can't run this \"{}\": {}",
|
|
shell, err)).log()
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn show_procview(&mut self) -> HResult<()> {
|
|
self.preview_widget().map(|preview| preview.cancel_animation()).log();
|
|
self.proc_view.lock()?.popup()?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn show_log(&mut self) -> HResult<()> {
|
|
self.preview_widget().map(|preview| preview.cancel_animation()).log();
|
|
self.log_view.lock()?.popup()?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get_footer(&self) -> HResult<String> {
|
|
let xsize = self.get_coordinates()?.xsize();
|
|
let ypos = self.get_coordinates()?.position().y();
|
|
let pos = self.main_widget()?.get_selection();
|
|
let file = self.main_widget()?.content.files.get(pos)?;
|
|
|
|
let permissions = file.pretty_print_permissions().unwrap_or("NOPERMS".into());
|
|
let user = file.pretty_user().unwrap_or("NOUSER".into());
|
|
let group = file.pretty_group().unwrap_or("NOGROUP".into());
|
|
let mtime = file.pretty_mtime().unwrap_or("NOMTIME".into());
|
|
let target = if let Some(target) = &file.target {
|
|
"--> ".to_string() + &target.short_string()
|
|
} else { "".to_string() };
|
|
|
|
let main_widget = self.main_widget()?;
|
|
let selection = main_widget.get_selection();
|
|
let file_count = main_widget.content.len();
|
|
let file_count = format!("{}", file_count);
|
|
let digits = file_count.len();
|
|
let file_count = format!("{:digits$}/{:digits$}",
|
|
selection,
|
|
file_count,
|
|
digits = digits);
|
|
let count_xpos = xsize - file_count.len() as u16;
|
|
let count_ypos = ypos + self.get_coordinates()?.ysize();
|
|
|
|
let fs = self.fs_stat.read()?.find_fs(&file.path)?.clone();
|
|
|
|
let dev = fs.get_dev();
|
|
let free_space = fs.get_free();
|
|
let total_space = fs.get_total();
|
|
let space = format!("{}: {} / {}",
|
|
dev,
|
|
free_space,
|
|
total_space);
|
|
|
|
let space_xpos = count_xpos - space.len() as u16 - 5; // - 3;
|
|
|
|
let status = format!("{} {}:{} {}{} {}{}",
|
|
permissions,
|
|
user,
|
|
group,
|
|
crate::term::header_color(),
|
|
mtime,
|
|
crate::term::color_yellow(),
|
|
target
|
|
);
|
|
let status = crate::term::sized_string_u(&status, (xsize-1) as usize);
|
|
|
|
let status = format!("{}{}{}{}{}{} | {}",
|
|
status,
|
|
crate::term::header_color(),
|
|
crate::term::goto_xy(space_xpos, count_ypos),
|
|
crate::term::color_orange(),
|
|
space,
|
|
crate::term::header_color(),
|
|
file_count);
|
|
|
|
Ok(status)
|
|
}
|
|
}
|
|
|
|
impl Widget for FileBrowser {
|
|
fn get_core(&self) -> HResult<&WidgetCore> {
|
|
Ok(&self.core)
|
|
}
|
|
fn get_core_mut(&mut self) -> HResult<&mut WidgetCore> {
|
|
Ok(&mut self.core)
|
|
}
|
|
|
|
fn set_coordinates(&mut self, coordinates: &Coordinates) -> HResult<()> {
|
|
self.core.coordinates = coordinates.clone();
|
|
self.columns.set_coordinates(&coordinates).log();
|
|
self.proc_view.lock()?.set_coordinates(&coordinates).log();
|
|
self.log_view.lock()?.set_coordinates(&coordinates).log();
|
|
self.bookmarks.lock()?.set_coordinates(&coordinates).log();
|
|
Ok(())
|
|
}
|
|
|
|
fn render_header(&self) -> HResult<String> {
|
|
let xsize = self.get_coordinates()?.xsize();
|
|
let file = self.selected_file()?;
|
|
let name = &file.name;
|
|
|
|
let color = if file.is_dir() {
|
|
crate::term::highlight_color() }
|
|
else if file.color.is_none() {
|
|
crate::term::normal_color()
|
|
} else {
|
|
crate::term::from_lscolor(file.color.as_ref().unwrap())
|
|
};
|
|
|
|
let path = self.cwd.short_string();
|
|
|
|
let mut path = path;
|
|
if &path == "" { path.clear(); }
|
|
if &path == "~/" { path.pop(); }
|
|
if &path == "/" { path.pop(); }
|
|
|
|
|
|
let pretty_path = format!("{}/{}{}", path, &color, name );
|
|
let sized_path = crate::term::sized_string(&pretty_path, xsize);
|
|
Ok(sized_path)
|
|
}
|
|
fn render_footer(&self) -> HResult<String> {
|
|
let xsize = term::xsize_u();
|
|
match self.get_core()?.status_bar_content.lock()?.as_mut().take() {
|
|
Some(status) => Ok(term::sized_string_u(&status, xsize)),
|
|
_ => { self.get_footer() },
|
|
}
|
|
}
|
|
fn refresh(&mut self) -> HResult<()> {
|
|
self.set_title().log();
|
|
self.columns.refresh().log();
|
|
self.set_left_selection().log();
|
|
self.set_cwd().log();
|
|
if !self.columns.zoom_active { self.update_preview().log(); }
|
|
self.columns.refresh().log();
|
|
self.cache_files().log();
|
|
Ok(())
|
|
}
|
|
|
|
fn get_drawlist(&self) -> HResult<String> {
|
|
self.columns.get_drawlist()
|
|
}
|
|
|
|
fn on_key(&mut self, key: Key) -> HResult<()> {
|
|
match key {
|
|
Key::Char('/') => { self.turbo_cd()?; },
|
|
Key::Char('Q') => { self.quit_with_dir()?; },
|
|
Key::Right | Key::Char('f') => { self.enter_dir()?; },
|
|
Key::Char('F') => { self.open_bg()?; },
|
|
Key::Left | Key::Char('b') => { self.go_back()?; },
|
|
Key::Char('-') => { self.goto_prev_cwd()?; },
|
|
Key::Char('`') => { self.goto_bookmark()?; },
|
|
Key::Char('m') => { self.add_bookmark()?; },
|
|
Key::Char('w') => { self.show_procview()?; },
|
|
Key::Char('l') => self.show_log()?,
|
|
Key::Char('z') => self.run_subshell()?,
|
|
Key::Char('c') => self.toggle_colums(),
|
|
_ => { self.main_widget_mut()?.on_key(key)?; },
|
|
}
|
|
if !self.columns.zoom_active { self.update_preview().log(); }
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl PartialEq for FileBrowser {
|
|
fn eq(&self, other: &FileBrowser) -> bool {
|
|
if self.columns == other.columns && self.cwd == other.cwd {
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|