mirror of
https://github.com/bobwen-dev/hunter
synced 2025-04-12 00:55:41 +02:00
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(),
|
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 {
|
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) {
|
pub fn toggle_colums(&mut self) {
|
||||||
self.preview_widget().map(|preview| preview.cancel_animation()).log();
|
self.cancel_preview_animation();
|
||||||
self.columns.toggle_zoom().log();
|
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<()> {
|
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);
|
let sized_path = crate::term::sized_string(&pretty_path, xsize);
|
||||||
Ok(sized_path.to_string())
|
Ok(sized_path.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_footer(&self) -> HResult<String> {
|
fn render_footer(&self) -> HResult<String> {
|
||||||
let xsize = term::xsize_u();
|
let xsize = term::xsize_u();
|
||||||
match self.get_core()?.status_bar_content.lock()?.as_mut().take() {
|
let mut status = self.get_core()?
|
||||||
Some(status) => Ok(term::sized_string_u(&status, xsize)),
|
.status_bar_content
|
||||||
_ => { self.get_footer() },
|
.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<()> {
|
fn refresh(&mut self) -> HResult<()> {
|
||||||
self.set_title().log();
|
self.set_title().log();
|
||||||
self.columns.refresh().log();
|
self.columns.refresh().log();
|
||||||
@ -1441,6 +1497,26 @@ impl Widget for FileBrowser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_key(&mut self, key: Key) -> HResult<()> {
|
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) {
|
match self.do_key(key) {
|
||||||
Err(HError::WidgetUndefinedKeyError{..}) => {
|
Err(HError::WidgetUndefinedKeyError{..}) => {
|
||||||
match self.main_widget_mut()?.on_key(key) {
|
match self.main_widget_mut()?.on_key(key) {
|
||||||
@ -1509,6 +1585,7 @@ impl Acting for FileBrowser {
|
|||||||
ShowQuickActions => self.quick_action()?,
|
ShowQuickActions => self.quick_action()?,
|
||||||
RunSubshell => self.run_subshell()?,
|
RunSubshell => self.run_subshell()?,
|
||||||
ToggleColumns => self.toggle_colums(),
|
ToggleColumns => self.toggle_colums(),
|
||||||
|
ZoomPreview => self.zoom_preview(),
|
||||||
// Tab implementation needs to call exec_cmd because ALL files are needed
|
// Tab implementation needs to call exec_cmd because ALL files are needed
|
||||||
ExecCmd => Err(HError::FileBrowserNeedTabFiles)?
|
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)?;
|
self.active_widget_mut()?.on_event(event)?;
|
||||||
Ok(())
|
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,
|
ShowQuickActions,
|
||||||
RunSubshell,
|
RunSubshell,
|
||||||
ToggleColumns,
|
ToggleColumns,
|
||||||
|
ZoomPreview,
|
||||||
ExecCmd
|
ExecCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -686,6 +687,7 @@ impl Default for Bindings<FileBrowserAction> {
|
|||||||
ShowQuickActions => Char('a'),
|
ShowQuickActions => Char('a'),
|
||||||
RunSubshell => Char('z'),
|
RunSubshell => Char('z'),
|
||||||
ToggleColumns => Char('c'),
|
ToggleColumns => Char('c'),
|
||||||
|
ZoomPreview => Char('C'),
|
||||||
ExecCmd => Char('!')
|
ExecCmd => Char('!')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ pub type AsyncWidgetFn<W> = dyn FnOnce(&Stale, WidgetCore)
|
|||||||
-> HResult<W> + Send + Sync;
|
-> HResult<W> + Send + Sync;
|
||||||
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kill_proc() -> HResult<()> {
|
fn kill_proc() -> HResult<()> {
|
||||||
@ -199,6 +199,10 @@ impl<T: Widget + Send + 'static> Widget for AsyncWidget<T> {
|
|||||||
if self.widget().is_err() { return Ok(()) }
|
if self.widget().is_err() { return Ok(()) }
|
||||||
self.widget_mut()?.on_key(key)
|
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> {
|
fn preview_failed<T>(file: &File) -> HResult<T> {
|
||||||
@ -564,14 +574,11 @@ impl Previewer {
|
|||||||
match previewer {
|
match previewer {
|
||||||
ExtPreviewer::Text(previewer) => {
|
ExtPreviewer::Text(previewer) => {
|
||||||
if stale.is_stale()? { return Previewer::preview_failed(&file) }
|
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) }
|
if stale.is_stale()? { return Previewer::preview_failed(&file) }
|
||||||
|
|
||||||
let mut textview = TextView {
|
let mut textview = TextView::new_blank(&core);
|
||||||
lines: lines?,
|
textview.set_lines(lines)?;
|
||||||
core: core.clone(),
|
|
||||||
follow: false,
|
|
||||||
offset: 0};
|
|
||||||
textview.set_coordinates(&core.coordinates).log();
|
textview.set_coordinates(&core.coordinates).log();
|
||||||
textview.refresh().log();
|
textview.refresh().log();
|
||||||
textview.animate_slide_up(Some(animator)).log();
|
textview.animate_slide_up(Some(animator)).log();
|
||||||
@ -633,6 +640,10 @@ impl Widget for Previewer {
|
|||||||
self.widget.get_drawlist()
|
self.widget.get_drawlist()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_footer(&self) -> HResult<String> {
|
||||||
|
self.widget.render_footer()
|
||||||
|
}
|
||||||
|
|
||||||
fn on_key(&mut self, key: Key) -> HResult<()> {
|
fn on_key(&mut self, key: Key) -> HResult<()> {
|
||||||
self.widget.on_key(key)
|
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<()> {
|
fn on_key(&mut self, key: Key) -> HResult<()> {
|
||||||
match self {
|
match self {
|
||||||
PreviewWidget::FileList(widget) => widget.on_key(key),
|
PreviewWidget::FileList(widget) => widget.on_key(key),
|
||||||
|
145
src/textview.rs
145
src/textview.rs
@ -1,6 +1,7 @@
|
|||||||
use std::io::{BufRead, BufReader};
|
use std::io::BufRead;
|
||||||
|
|
||||||
use strip_ansi_escapes::strip;
|
use strip_ansi_escapes::strip;
|
||||||
|
use termion::event::Key;
|
||||||
|
|
||||||
use crate::files::File;
|
use crate::files::File;
|
||||||
use crate::term::sized_string_u;
|
use crate::term::sized_string_u;
|
||||||
@ -15,6 +16,8 @@ pub struct TextView {
|
|||||||
pub core: WidgetCore,
|
pub core: WidgetCore,
|
||||||
pub follow: bool,
|
pub follow: bool,
|
||||||
pub offset: usize,
|
pub offset: usize,
|
||||||
|
file: Option<File>,
|
||||||
|
limited: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextView {
|
impl TextView {
|
||||||
@ -24,56 +27,78 @@ impl TextView {
|
|||||||
core: core.clone(),
|
core: core.clone(),
|
||||||
follow: false,
|
follow: false,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
file: None,
|
||||||
|
limited: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_from_file(core: &WidgetCore, file: &File) -> HResult<TextView> {
|
pub fn new_from_file(core: &WidgetCore, file: &File) -> HResult<TextView> {
|
||||||
let file = std::fs::File::open(&file.path)?;
|
let mut view = TextView::new_from_file_limit_lines(core, file, 0)?;
|
||||||
let file = std::io::BufReader::new(file);
|
view.limited = false;
|
||||||
let lines = file.lines()
|
Ok(view)
|
||||||
.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,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_from_file_limit_lines(core: &WidgetCore,
|
pub fn new_from_file_limit_lines(core: &WidgetCore,
|
||||||
file: &File,
|
file: &File,
|
||||||
num: usize) -> HResult<TextView> {
|
num: usize) -> HResult<TextView> {
|
||||||
let file = std::fs::File::open(&file.path)?;
|
let buf = std::fs::File::open(&file.path)
|
||||||
let file = BufReader::new(file);
|
.map(|f| std::io::BufReader::new(f))?;
|
||||||
let lines = file.lines()
|
|
||||||
.take(num)
|
let lines = buf.lines()
|
||||||
.map(|line| line
|
.enumerate()
|
||||||
.and_then(|l| strip(l))
|
.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)
|
.map_err(HError::from)
|
||||||
.and_then(|s| std::str::from_utf8(&s)
|
|
||||||
.map(|s| s.to_string())
|
})
|
||||||
.map_err(HError::from)))
|
|
||||||
.collect::<HResult<_>>()?;
|
.collect::<HResult<_>>()?;
|
||||||
|
|
||||||
Ok(TextView {
|
Ok(TextView {
|
||||||
lines: lines,
|
lines: lines,
|
||||||
core: core.clone(),
|
core: core.clone(),
|
||||||
follow: false,
|
follow: false,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
file: Some(file.clone()),
|
||||||
|
limited: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_text(&mut self, text: &str) -> HResult<()> {
|
pub fn set_text(&mut self, text: &str) -> HResult<()> {
|
||||||
let lines = text.lines().map(|l| l.to_string()).collect();
|
let lines = text.lines().map(|l| l.to_string()).collect();
|
||||||
self.lines = lines;
|
self.lines = lines;
|
||||||
|
self.limited = false;
|
||||||
|
self.file = None;
|
||||||
self.core.set_dirty();
|
self.core.set_dirty();
|
||||||
self.refresh()
|
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) {
|
pub fn toggle_follow(&mut self) {
|
||||||
self.follow = !self.follow
|
self.follow = !self.follow
|
||||||
}
|
}
|
||||||
@ -159,21 +184,79 @@ impl Widget for TextView {
|
|||||||
let (xsize, ysize) = self.get_coordinates()?.size().size();
|
let (xsize, ysize) = self.get_coordinates()?.size().size();
|
||||||
let (xpos, ypos) = self.get_coordinates()?.position().position();
|
let (xpos, ypos) = self.get_coordinates()?.position().position();
|
||||||
|
|
||||||
let output = self.core.get_clearlist()? +
|
let mut output = crate::term::reset();
|
||||||
&self
|
|
||||||
.lines
|
output += &self.lines
|
||||||
.iter()
|
.iter()
|
||||||
.skip(self.offset)
|
.skip(self.offset)
|
||||||
.take(ysize as usize)
|
.take(ysize as usize)
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, line)| {
|
.map(|(i, line)| {
|
||||||
format!(
|
format!(
|
||||||
"{}{}{}",
|
"{}{}",
|
||||||
crate::term::goto_xy(xpos, i as u16 + ypos),
|
crate::term::goto_xy(xpos, i as u16 + ypos),
|
||||||
crate::term::reset(),
|
|
||||||
sized_string_u(&line, (xsize-1) as usize))
|
sized_string_u(&line, (xsize-1) as usize))
|
||||||
})
|
})
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
Ok(output)
|
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);
|
std::thread::sleep(pause);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.get_core()?.write_to_screen(&clear).log();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user