From 67fde00a341b3c90afcad2414aaf19f2b0a0cbf1 Mon Sep 17 00:00:00 2001 From: rabite Date: Fri, 22 May 2020 14:32:56 +0200 Subject: add preview zoom mode --- config.tar.gz | Bin 3949 -> 3957 bytes src/file_browser.rs | 87 +++++++++++++++++++++++++-- src/hbox.rs | 4 ++ src/keybind.rs | 2 + src/preview.rs | 34 ++++++++--- src/textview.rs | 169 +++++++++++++++++++++++++++++++++++++++------------- src/widget.rs | 2 + 7 files changed, 243 insertions(+), 55 deletions(-) diff --git a/config.tar.gz b/config.tar.gz index f33e5b9..f95e07d 100644 Binary files a/config.tar.gz and b/config.tar.gz differ diff --git a/src/file_browser.rs b/src/file_browser.rs index fcc0c6c..f3cec19 100644 --- a/src/file_browser.rs +++ b/src/file_browser.rs @@ -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 { 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)? } diff --git a/src/hbox.rs b/src/hbox.rs index 8ad23c6..391aae0 100644 --- a/src/hbox.rs +++ b/src/hbox.rs @@ -210,4 +210,8 @@ impl Widget for HBox 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) + } } diff --git a/src/keybind.rs b/src/keybind.rs index e9e0e22..64a725a 100644 --- a/src/keybind.rs +++ b/src/keybind.rs @@ -481,6 +481,7 @@ pub enum FileBrowserAction { ShowQuickActions, RunSubshell, ToggleColumns, + ZoomPreview, ExecCmd } @@ -686,6 +687,7 @@ impl Default for Bindings { ShowQuickActions => Char('a'), RunSubshell => Char('z'), ToggleColumns => Char('c'), + ZoomPreview => Char('C'), ExecCmd => Char('!') }; diff --git a/src/preview.rs b/src/preview.rs index 7ba2010..95ad8cc 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -19,7 +19,7 @@ pub type AsyncWidgetFn = dyn FnOnce(&Stale, WidgetCore) -> HResult + Send + Sync; lazy_static! { - static ref SUBPROC: Arc>> = { Arc::new(Mutex::new(None)) }; + static ref SUBPROC: Arc>> = Arc::new(Mutex::new(None)); } fn kill_proc() -> HResult<()> { @@ -199,6 +199,10 @@ impl Widget for AsyncWidget { if self.widget().is_err() { return Ok(()) } self.widget_mut()?.on_key(key) } + fn render_footer(&self) -> HResult { + 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(file: &File) -> HResult { @@ -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 { + 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 { + 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), diff --git a/src/textview.rs b/src/textview.rs index cd9a815..b7dd0a9 100644 --- a/src/textview.rs +++ b/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, + 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 { - 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::>()?; - 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 { - 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::>()?; + 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::>()?; + 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) -> 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::(); + 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::(); Ok(output) } + + fn render_footer(&self) -> HResult { + 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 { + 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(()) + } } diff --git a/src/widget.rs b/src/widget.rs index 0efcda7..1d27dbe 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -455,6 +455,8 @@ pub trait Widget { std::thread::sleep(pause); } + self.get_core()?.write_to_screen(&clear).log(); + Ok(()) } -- cgit v1.2.3