mirror of https://github.com/bobwen-dev/hunter
add preview zoom mode
This commit is contained in:
parent
f434936e62
commit
67fde00a34
BIN
config.tar.gz
BIN
config.tar.gz
Binary file not shown.
|
@ -72,6 +72,14 @@ impl Widget for FileBrowserWidgets {
|
|||
FileBrowserWidgets::Blank(widget) => widget.get_drawlist(),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_key(&mut self, key: Key) -> HResult<()> {
|
||||
match self {
|
||||
FileBrowserWidgets::FileList(widget) => widget.on_key(key),
|
||||
FileBrowserWidgets::Previewer(widget) => widget.on_key(key),
|
||||
FileBrowserWidgets::Blank(widget) => widget.on_key(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FileBrowser {
|
||||
|
@ -854,9 +862,45 @@ impl FileBrowser {
|
|||
}
|
||||
}
|
||||
|
||||
fn cancel_preview_animation(&mut self) {
|
||||
self.preview_widget_mut()
|
||||
.map(|preview| preview.cancel_animation())
|
||||
.log();
|
||||
}
|
||||
|
||||
fn activate_main_widget(&mut self) {
|
||||
const MAIN_INDEX: usize = 1;
|
||||
self.columns
|
||||
.set_active(MAIN_INDEX)
|
||||
.log();
|
||||
}
|
||||
|
||||
fn activate_preview_widget(&mut self) {
|
||||
const PREVIEW_INDEX: usize = 2;
|
||||
self.columns
|
||||
.set_active(PREVIEW_INDEX)
|
||||
.log();
|
||||
}
|
||||
|
||||
pub fn toggle_colums(&mut self) {
|
||||
self.preview_widget().map(|preview| preview.cancel_animation()).log();
|
||||
self.columns.toggle_zoom().log();
|
||||
self.cancel_preview_animation();
|
||||
self.activate_main_widget();
|
||||
self.columns
|
||||
.toggle_zoom()
|
||||
.log();
|
||||
}
|
||||
|
||||
pub fn zoom_preview(&mut self) {
|
||||
self.cancel_preview_animation();
|
||||
self.activate_preview_widget();
|
||||
self.preview_widget_mut()
|
||||
.map(|preview| {
|
||||
preview.reload_text();
|
||||
}).log();
|
||||
|
||||
self.columns
|
||||
.toggle_zoom()
|
||||
.log();
|
||||
}
|
||||
|
||||
pub fn quit_with_dir(&self) -> HResult<()> {
|
||||
|
@ -1419,13 +1463,25 @@ impl Widget for FileBrowser {
|
|||
let sized_path = crate::term::sized_string(&pretty_path, xsize);
|
||||
Ok(sized_path.to_string())
|
||||
}
|
||||
|
||||
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() },
|
||||
let mut status = self.get_core()?
|
||||
.status_bar_content
|
||||
.lock()?;
|
||||
let status = status.as_mut()
|
||||
.take();
|
||||
let active = self.columns
|
||||
.active
|
||||
.unwrap_or(1);
|
||||
|
||||
match (status, active) {
|
||||
(Some(status), _) => Ok(term::sized_string_u(&status, xsize)),
|
||||
(_, 2) => self.preview_widget()?.render_footer(),
|
||||
_ => self.get_footer(),
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh(&mut self) -> HResult<()> {
|
||||
self.set_title().log();
|
||||
self.columns.refresh().log();
|
||||
|
@ -1441,6 +1497,26 @@ impl Widget for FileBrowser {
|
|||
}
|
||||
|
||||
fn on_key(&mut self, key: Key) -> HResult<()> {
|
||||
// Special handling for preview zoom
|
||||
let binds = self.search_in();
|
||||
let action = binds.get(key);
|
||||
|
||||
match (action, self.columns.active) {
|
||||
(Some(FileBrowserAction::ZoomPreview), Some(2)) => {
|
||||
self.toggle_colums();
|
||||
return Ok(());
|
||||
}
|
||||
(Some(FileBrowserAction::ZoomPreview), Some(1)) => {
|
||||
self.zoom_preview();
|
||||
return Ok(());
|
||||
}
|
||||
(_, Some(2)) => {
|
||||
self.columns.active_widget_mut()?.on_key(key)?;
|
||||
return Ok(());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match self.do_key(key) {
|
||||
Err(HError::WidgetUndefinedKeyError{..}) => {
|
||||
match self.main_widget_mut()?.on_key(key) {
|
||||
|
@ -1509,6 +1585,7 @@ impl Acting for FileBrowser {
|
|||
ShowQuickActions => self.quick_action()?,
|
||||
RunSubshell => self.run_subshell()?,
|
||||
ToggleColumns => self.toggle_colums(),
|
||||
ZoomPreview => self.zoom_preview(),
|
||||
// Tab implementation needs to call exec_cmd because ALL files are needed
|
||||
ExecCmd => Err(HError::FileBrowserNeedTabFiles)?
|
||||
}
|
||||
|
|
|
@ -210,4 +210,8 @@ impl<T> Widget for HBox<T> where T: Widget + PartialEq {
|
|||
self.active_widget_mut()?.on_event(event)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_key(&mut self, key: termion::event::Key) -> HResult<()> {
|
||||
self.active_widget_mut()?.on_key(key)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -481,6 +481,7 @@ pub enum FileBrowserAction {
|
|||
ShowQuickActions,
|
||||
RunSubshell,
|
||||
ToggleColumns,
|
||||
ZoomPreview,
|
||||
ExecCmd
|
||||
}
|
||||
|
||||
|
@ -686,6 +687,7 @@ impl Default for Bindings<FileBrowserAction> {
|
|||
ShowQuickActions => Char('a'),
|
||||
RunSubshell => Char('z'),
|
||||
ToggleColumns => Char('c'),
|
||||
ZoomPreview => Char('C'),
|
||||
ExecCmd => Char('!')
|
||||
};
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ pub type AsyncWidgetFn<W> = dyn FnOnce(&Stale, WidgetCore)
|
|||
-> HResult<W> + Send + Sync;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
fn kill_proc() -> HResult<()> {
|
||||
|
@ -199,6 +199,10 @@ impl<T: Widget + Send + 'static> Widget for AsyncWidget<T> {
|
|||
if self.widget().is_err() { return Ok(()) }
|
||||
self.widget_mut()?.on_key(key)
|
||||
}
|
||||
fn render_footer(&self) -> HResult<String> {
|
||||
if self.widget().is_err() { return Ok(String::new()) }
|
||||
self.widget()?.render_footer()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -454,6 +458,12 @@ impl Previewer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn reload_text(&mut self) {
|
||||
match self.widget.widget_mut() {
|
||||
Ok(PreviewWidget::TextView(w)) => w.load_full(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn preview_failed<T>(file: &File) -> HResult<T> {
|
||||
|
@ -564,14 +574,11 @@ impl Previewer {
|
|||
match previewer {
|
||||
ExtPreviewer::Text(previewer) => {
|
||||
if stale.is_stale()? { return Previewer::preview_failed(&file) }
|
||||
let lines = Previewer::run_external(previewer, file, stale);
|
||||
let lines = Previewer::run_external(previewer, file, stale)?;
|
||||
if stale.is_stale()? { return Previewer::preview_failed(&file) }
|
||||
|
||||
let mut textview = TextView {
|
||||
lines: lines?,
|
||||
core: core.clone(),
|
||||
follow: false,
|
||||
offset: 0};
|
||||
let mut textview = TextView::new_blank(&core);
|
||||
textview.set_lines(lines)?;
|
||||
textview.set_coordinates(&core.coordinates).log();
|
||||
textview.refresh().log();
|
||||
textview.animate_slide_up(Some(animator)).log();
|
||||
|
@ -633,6 +640,10 @@ impl Widget for Previewer {
|
|||
self.widget.get_drawlist()
|
||||
}
|
||||
|
||||
fn render_footer(&self) -> HResult<String> {
|
||||
self.widget.render_footer()
|
||||
}
|
||||
|
||||
fn on_key(&mut self, key: Key) -> HResult<()> {
|
||||
self.widget.on_key(key)
|
||||
}
|
||||
|
@ -680,6 +691,15 @@ impl Widget for PreviewWidget {
|
|||
}
|
||||
}
|
||||
|
||||
fn render_footer(&self) -> HResult<String> {
|
||||
match self {
|
||||
PreviewWidget::FileList(widget) => widget.render_footer(),
|
||||
PreviewWidget::TextView(widget) => widget.render_footer(),
|
||||
PreviewWidget::ImgView(widget) => widget.render_footer(),
|
||||
PreviewWidget::MediaView(widget) => widget.render_footer()
|
||||
}
|
||||
}
|
||||
|
||||
fn on_key(&mut self, key: Key) -> HResult<()> {
|
||||
match self {
|
||||
PreviewWidget::FileList(widget) => widget.on_key(key),
|
||||
|
|
169
src/textview.rs
169
src/textview.rs
|
@ -1,6 +1,7 @@
|
|||
use std::io::{BufRead, BufReader};
|
||||
use std::io::BufRead;
|
||||
|
||||
use strip_ansi_escapes::strip;
|
||||
use termion::event::Key;
|
||||
|
||||
use crate::files::File;
|
||||
use crate::term::sized_string_u;
|
||||
|
@ -15,6 +16,8 @@ pub struct TextView {
|
|||
pub core: WidgetCore,
|
||||
pub follow: bool,
|
||||
pub offset: usize,
|
||||
file: Option<File>,
|
||||
limited: bool,
|
||||
}
|
||||
|
||||
impl TextView {
|
||||
|
@ -24,56 +27,78 @@ impl TextView {
|
|||
core: core.clone(),
|
||||
follow: false,
|
||||
offset: 0,
|
||||
file: None,
|
||||
limited: false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_from_file(core: &WidgetCore, file: &File) -> HResult<TextView> {
|
||||
let file = std::fs::File::open(&file.path)?;
|
||||
let file = std::io::BufReader::new(file);
|
||||
let lines = file.lines()
|
||||
.map(|line| line
|
||||
.and_then(|l| strip(l))
|
||||
.map_err(HError::from)
|
||||
.and_then(|s| std::str::from_utf8(&s)
|
||||
.map(|s| s.to_string())
|
||||
.map_err(HError::from)))
|
||||
.collect::<HResult<_>>()?;
|
||||
Ok(TextView {
|
||||
lines: lines,
|
||||
core: core.clone(),
|
||||
follow: false,
|
||||
offset: 0,
|
||||
})
|
||||
let mut view = TextView::new_from_file_limit_lines(core, file, 0)?;
|
||||
view.limited = false;
|
||||
Ok(view)
|
||||
}
|
||||
|
||||
pub fn new_from_file_limit_lines(core: &WidgetCore,
|
||||
file: &File,
|
||||
num: usize) -> HResult<TextView> {
|
||||
let file = std::fs::File::open(&file.path)?;
|
||||
let file = BufReader::new(file);
|
||||
let lines = file.lines()
|
||||
.take(num)
|
||||
.map(|line| line
|
||||
.and_then(|l| strip(l))
|
||||
.map_err(HError::from)
|
||||
.and_then(|s| std::str::from_utf8(&s)
|
||||
.map(|s| s.to_string())
|
||||
.map_err(HError::from)))
|
||||
.collect::<HResult<_>>()?;
|
||||
let buf = std::fs::File::open(&file.path)
|
||||
.map(|f| std::io::BufReader::new(f))?;
|
||||
|
||||
let lines = buf.lines()
|
||||
.enumerate()
|
||||
.take_while(|(i, _)| num == 0 || i <= &num)
|
||||
.map(|(_, l)| {
|
||||
l.map_err(HError::from)
|
||||
.and_then(|l| {
|
||||
let l = strip(&l);
|
||||
Ok(String::from_utf8_lossy(&l?).to_string())
|
||||
})
|
||||
.map_err(HError::from)
|
||||
|
||||
})
|
||||
.collect::<HResult<_>>()?;
|
||||
|
||||
Ok(TextView {
|
||||
lines: lines,
|
||||
core: core.clone(),
|
||||
follow: false,
|
||||
offset: 0,
|
||||
file: Some(file.clone()),
|
||||
limited: true
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, text: &str) -> HResult<()> {
|
||||
let lines = text.lines().map(|l| l.to_string()).collect();
|
||||
self.lines = lines;
|
||||
self.limited = false;
|
||||
self.file = None;
|
||||
self.core.set_dirty();
|
||||
self.refresh()
|
||||
}
|
||||
|
||||
pub fn set_lines(&mut self, lines: Vec<String>) -> HResult<()> {
|
||||
self.lines = lines;
|
||||
self.limited = false;
|
||||
self.file = None;
|
||||
self.core.set_dirty();
|
||||
self.refresh()
|
||||
}
|
||||
|
||||
pub fn load_full(&mut self) {
|
||||
if self.limited {
|
||||
self.file
|
||||
.as_ref()
|
||||
.and_then(|f| {
|
||||
TextView::new_from_file(&self.core, f).ok()
|
||||
})
|
||||
.map(|v| {
|
||||
*self = v;
|
||||
self.limited = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_follow(&mut self) {
|
||||
self.follow = !self.follow
|
||||
}
|
||||
|
@ -159,21 +184,79 @@ impl Widget for TextView {
|
|||
let (xsize, ysize) = self.get_coordinates()?.size().size();
|
||||
let (xpos, ypos) = self.get_coordinates()?.position().position();
|
||||
|
||||
let output = self.core.get_clearlist()? +
|
||||
&self
|
||||
.lines
|
||||
.iter()
|
||||
.skip(self.offset)
|
||||
.take(ysize as usize)
|
||||
.enumerate()
|
||||
.map(|(i, line)| {
|
||||
format!(
|
||||
"{}{}{}",
|
||||
crate::term::goto_xy(xpos, i as u16 + ypos),
|
||||
crate::term::reset(),
|
||||
sized_string_u(&line, (xsize-1) as usize))
|
||||
})
|
||||
.collect::<String>();
|
||||
let mut output = crate::term::reset();
|
||||
|
||||
output += &self.lines
|
||||
.iter()
|
||||
.skip(self.offset)
|
||||
.take(ysize as usize)
|
||||
.enumerate()
|
||||
.map(|(i, line)| {
|
||||
format!(
|
||||
"{}{}",
|
||||
crate::term::goto_xy(xpos, i as u16 + ypos),
|
||||
sized_string_u(&line, (xsize-1) as usize))
|
||||
})
|
||||
.collect::<String>();
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn render_footer(&self) -> HResult<String> {
|
||||
let (xsize, ysize) = self.core.coordinates.size_u();
|
||||
let (_, ypos) = self.core.coordinates.position_u();
|
||||
let lines = self.lines
|
||||
.len()
|
||||
.saturating_sub(1);
|
||||
let current_line_top = self.offset;
|
||||
let current_line_bot = std::cmp::min(current_line_top + ysize + 1,
|
||||
lines);
|
||||
let line_hint = format!("{} - {} / {}",
|
||||
current_line_top,
|
||||
current_line_bot,
|
||||
lines);
|
||||
let hint_xpos = xsize - line_hint.len();
|
||||
let hint_ypos = ysize + ypos + 1;
|
||||
|
||||
let footer = format!("{}{}",
|
||||
crate::term::goto_xy_u(hint_xpos, hint_ypos),
|
||||
line_hint);
|
||||
|
||||
Ok(footer)
|
||||
}
|
||||
|
||||
fn on_key(&mut self, key: Key) -> HResult<()> {
|
||||
self.do_key(key)
|
||||
}
|
||||
}
|
||||
|
||||
use crate::keybind::{Acting, Bindings, Movement};
|
||||
|
||||
impl Acting for TextView {
|
||||
type Action=Movement;
|
||||
|
||||
fn search_in(&self) -> Bindings<Self::Action> {
|
||||
Bindings::default()
|
||||
}
|
||||
|
||||
fn movement(&mut self, movement: &Movement) -> HResult<()> {
|
||||
use Movement::*;
|
||||
|
||||
self.load_full();
|
||||
|
||||
match movement {
|
||||
Up(n) => { for _ in 0..*n { self.scroll_up(); }; self.refresh()?; }
|
||||
Down(n) => { for _ in 0..*n { self.scroll_down(); }; self.refresh()?; }
|
||||
PageUp => self.page_up(),
|
||||
PageDown => self.page_down(),
|
||||
Top => self.scroll_top(),
|
||||
Bottom => self.scroll_bottom(),
|
||||
Left | Right => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_action(&mut self, _action: &Self::Action) -> HResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -455,6 +455,8 @@ pub trait Widget {
|
|||
std::thread::sleep(pause);
|
||||
}
|
||||
|
||||
self.get_core()?.write_to_screen(&clear).log();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue