From 297239c7c7cd9949f3f5d4efd03ba163ab3bd8d7 Mon Sep 17 00:00:00 2001 From: rabite Date: Fri, 15 Mar 2019 14:22:05 +0100 Subject: [PATCH] log/foldview --- Cargo.toml | 1 + src/bookmarks.rs | 178 ++++++++++++++++++++++++++++++++ src/coordinates.rs | 18 ++++ src/fail.rs | 74 +++++++++++--- src/file_browser.rs | 18 ++-- src/foldview.rs | 242 ++++++++++++++++++++++++++++++++++++++++++++ src/listview.rs | 109 ++++++++++---------- src/main.rs | 31 +++--- src/minibuffer.rs | 4 +- src/paths.rs | 29 ++++++ src/term.rs | 94 ++++++++++++++++- src/widget.rs | 32 ++++-- 12 files changed, 723 insertions(+), 107 deletions(-) create mode 100644 src/bookmarks.rs create mode 100644 src/foldview.rs create mode 100644 src/paths.rs diff --git a/Cargo.toml b/Cargo.toml index 571155b..3a94f37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ libc = "*" failure = "0.1.5" failure_derive = "0.1.1" notify = "4.0.9" +parse-ansi = "0.1.6" #[profile.release] #debug = true diff --git a/src/bookmarks.rs b/src/bookmarks.rs new file mode 100644 index 0000000..a561841 --- /dev/null +++ b/src/bookmarks.rs @@ -0,0 +1,178 @@ +use termion::event::Key; + +use std::collections::HashMap; + +use crate::fail::{HResult, HError, ErrorLog}; +use crate::widget::{Widget, WidgetCore}; +use crate::files::{Files, File}; +use crate::term; + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct Bookmarks { + mapping: HashMap, +} + +impl Bookmarks { + pub fn new() -> Bookmarks { + let mut bm = Bookmarks { mapping: HashMap::new() }; + bm.load().log(); + bm + } + pub fn add(&mut self, key: char, path: &str) -> HResult<()> { + self.mapping.insert(key, path.to_string()); + self.save()?; + Ok(()) + } + pub fn get(&self, key: char) -> HResult<&String> { + let path = self.mapping.get(&key)?; + Ok(path) + } + pub fn load(&mut self) -> HResult<()> { + let bm_file = crate::paths::bookmark_path()?; + let bm_content = std::fs::read_to_string(bm_file)?; + + + let keys = bm_content.lines().step_by(2).map(|k| k); + let paths = bm_content.lines().skip(1).step_by(2).map(|p| p); + + let mapping = keys.zip(paths).fold(HashMap::new(), |mut mapping, (key, path)| { + if let Some(key) = key.chars().next() { + let path = path.to_string(); + mapping.insert(key, path); + } + mapping + }); + + self.mapping = mapping; + Ok(()) + } + pub fn save(&self) -> HResult<()> { + let bm_file = crate::paths::bookmark_path()?; + let bookmarks = self.mapping.iter().map(|(key, path)| { + format!("{}\n{}\n", key, path) + }).collect::(); + + std::fs::write(bm_file, bookmarks)?; + + Ok(()) + } +} + + +pub struct BMPopup { + core: WidgetCore, + bookmarks: Bookmarks, + bookmark_path: Option, + add_mode: bool, +} + +impl BMPopup { + pub fn new(core: &WidgetCore) -> BMPopup { + let bmpopup = BMPopup { + core: core.clone(), + bookmarks: Bookmarks::new(), + bookmark_path: None, + add_mode: false + }; + bmpopup + } + + pub fn pick(&mut self, cwd: String) -> HResult { + self.bookmark_path = Some(cwd); + self.refresh()?; + self.popup()?; + self.clear()?; + + let bookmark = self.bookmark_path.take(); + Ok(bookmark?) + } + + pub fn add(&mut self, path: &str) -> HResult<()> { + self.add_mode = true; + self.bookmark_path = Some(path.to_string()); + self.refresh()?; + self.clear()?; + self.popup()?; + self.clear()?; + Ok(()) + } + + pub fn render_line(&self, n: u16, key: &char, path: &str) -> String { + let xsize = term::xsize(); + let padding = xsize - 4; + + format!( + "{}{}{}: {:padding$}", + crate::term::goto_xy(1, n), + crate::term::reset(), + key, + path, + padding = padding as usize) + } +} + + +impl Widget for BMPopup { + fn get_core(&self) -> HResult<&WidgetCore> { + Ok(&self.core) + } + fn get_core_mut(&mut self) -> HResult<&mut WidgetCore> { + Ok(&mut self.core) + } + fn refresh(&mut self) -> HResult<()> { + let tysize = crate::term::ysize(); + let txsize = crate::term::xsize(); + let len = self.bookmarks.mapping.len() as u16; + let ysize = tysize - (len + 1); + + self.core.coordinates.set_position(1, ysize); + self.core.coordinates.set_size(txsize, len+1); + + Ok(()) + } + fn get_drawlist(&self) -> HResult { + let ypos = self.get_coordinates()?.ypos(); + + let mut drawlist = String::new(); + + if !self.add_mode { + let cwd = self.bookmark_path.as_ref()?; + drawlist += &self.render_line(ypos, &'`', cwd); + } + + let bm_list = self.bookmarks.mapping.iter().enumerate().map(|(i, (key, path))| { + let line = i as u16 + ypos + 1; + self.render_line(line, key, path) + }).collect::(); + + drawlist += &bm_list; + + Ok(drawlist) + } + fn on_key(&mut self, key: Key) -> HResult<()> { + match key { + Key::Ctrl('c') => { + self.bookmark_path = None; + return HError::popup_finnished() + }, + Key::Char('`') => return HError::popup_finnished(), + Key::Char(key) => { + if self.add_mode { + let path = self.bookmark_path.take()?; + self.bookmarks.add(key, &path)?; + self.add_mode = false; + return HError::popup_finnished(); + } + if let Ok(path) = self.bookmarks.get(key) { + self.bookmark_path.replace(path.clone()); + return HError::popup_finnished(); + } + } + Key::Alt(key) => { + self.bookmarks.mapping.remove(&key); + } + _ => {} + } + Ok(()) + } +} diff --git a/src/coordinates.rs b/src/coordinates.rs index df16791..3a76030 100644 --- a/src/coordinates.rs +++ b/src/coordinates.rs @@ -76,6 +76,11 @@ impl Coordinates { self.position.position() } + pub fn position_u(&self) -> (usize, usize) { + let (xpos, ypos) = self.u16position(); + ((xpos-1) as usize, (ypos-1) as usize) + } + pub fn size(&self) -> &Size { &self.size } @@ -84,6 +89,11 @@ impl Coordinates { self.size.size() } + pub fn size_u(&self) -> (usize, usize) { + let (xsize, ysize) = self.u16size(); + ((xsize-1) as usize, (ysize-1) as usize) + } + pub fn top(&self) -> Position { self.position().clone() } @@ -95,6 +105,10 @@ impl Size { pub fn size(&self) -> (u16, u16) { self.0 } + pub fn size_u(&self) -> (usize, usize) { + let (xsize, ysize) = self.0; + (xsize as usize, ysize as usize) + } pub fn xsize(&self) -> u16 { (self.0).0 } @@ -107,6 +121,10 @@ impl Position { pub fn position(&self) -> (u16, u16) { self.0 } + pub fn position_u(&self) -> (usize, usize) { + let (xpos, ypos) = self.0; + (xpos as usize, ypos as usize) + } pub fn x(&self) -> u16 { (self.0).0 } diff --git a/src/fail.rs b/src/fail.rs index 1b1544f..a376c09 100644 --- a/src/fail.rs +++ b/src/fail.rs @@ -2,14 +2,19 @@ use failure; use failure::Fail; use failure::Backtrace; +use termion::event::Key; + use std::path::PathBuf; +use std::sync::Mutex; + +use crate::foldview::LogEntry; pub type HResult = Result; #[derive(Fail, Debug)] pub enum HError { - #[fail(display = "IO error: {}", error)] - IoError{#[cause] error: std::io::Error}, + #[fail(display = "IO error: {} ", error)] + IoError{#[cause] error: std::io::Error, backtrace: Backtrace}, #[fail(display = "Mutex failed")] MutexError, #[fail(display = "Can't lock!")] @@ -59,7 +64,9 @@ pub enum HError { #[fail(display = "Input cancelled!")] MiniBufferCancelledInput, #[fail(display = "Empty input!")] - MiniBufferEmptyInput + MiniBufferEmptyInput, + #[fail(display = "Undefined key: {:?}", key)] + WidgetUndefinedKeyError{key: Key} } impl HError { @@ -88,6 +95,24 @@ impl HError { pub fn minibuffer_empty() -> HResult { Err(HError::MiniBufferEmptyInput) } + pub fn undefined_key(key: Key) -> HResult { + Err(HError::WidgetUndefinedKeyError { key: key }) + } +} + + +lazy_static! { + static ref LOG: Mutex> = Mutex::new(vec![]); +} + +pub fn get_logs() -> HResult> { + let logs = LOG.lock()?.drain(..).collect(); + Ok(logs) +} + +pub fn put_log>(log: L) -> HResult<()> { + LOG.lock()?.push(log.into()); + Ok(()) } pub trait ErrorLog where Self: Sized { @@ -98,6 +123,7 @@ impl ErrorLog for HResult { fn log(self) { if let Err(err) = self { eprintln!("{:?}", err); + put_log(&err).ok(); } } } @@ -115,69 +141,89 @@ impl ErrorLog for HResult { impl From for HError { fn from(error: std::io::Error) -> Self { dbg!(&error); - HError::IoError { error: error } + let err = HError::IoError { error: error, backtrace: Backtrace::new() }; + put_log(&err).ok(); + err } } impl From for HError { fn from(error: failure::Error) -> Self { dbg!(&error); - HError::Error { error: error } + let err = HError::Error { error: error }; + put_log(&err).ok(); + err } } impl From for HError { fn from(error: std::sync::mpsc::TryRecvError) -> Self { dbg!(&error); - HError::ChannelTryRecvError { error: error } + let err = HError::ChannelTryRecvError { error: error }; + put_log(&err).ok(); + err } } impl From for HError { fn from(error: std::sync::mpsc::RecvError) -> Self { dbg!(&error); - HError::ChannelRecvError { error: error } + let err = HError::ChannelRecvError { error: error }; + put_log(&err).ok(); + err } } impl From> for HError { fn from(error: std::sync::mpsc::SendError) -> Self { dbg!(&error); - HError::ChannelSendError + let err = HError::ChannelSendError; + put_log(&err).ok(); + err } } impl From> for HError { fn from(_: std::sync::PoisonError) -> Self { dbg!("Poisoned Mutex"); - HError::MutexError + let err = HError::MutexError; + put_log(&err).ok(); + err } } impl From> for HError { fn from(error: std::sync::TryLockError) -> Self { dbg!(&error); - HError::TryLockError + let err = HError::TryLockError; + put_log(&err).ok(); + err } } impl From for HError { fn from(error: std::option::NoneError) -> Self { - dbg!(&error); - HError::NoneError + //dbg!(&error); + let err = HError::NoneError; + //put_log(&err).ok(); + err } } impl From for HError { fn from(error: std::path::StripPrefixError) -> Self { dbg!(&error); - HError::StripPrefixError{error: error} + let err = HError::StripPrefixError{error: error}; + put_log(&err).ok(); + err } } impl From for HError { fn from(error: notify::Error) -> Self { dbg!(&error); - HError::INotifyError{error: error} + let err = HError::INotifyError{error: error}; + put_log(&err).ok(); + err } } diff --git a/src/file_browser.rs b/src/file_browser.rs index d1c597c..ca29eda 100644 --- a/src/file_browser.rs +++ b/src/file_browser.rs @@ -19,6 +19,7 @@ use crate::widget::{Events, WidgetCore}; use crate::proclist::ProcView; use crate::bookmarks::BMPopup; use crate::term::ScreenExt; +use crate::foldview::LogView; #[derive(PartialEq)] pub enum FileBrowserWidgets { @@ -64,7 +65,8 @@ pub struct FileBrowser { watches: Vec, dir_events: Arc>>, proc_view: Arc>, - bookmarks: Arc> + bookmarks: Arc>, + log_view: Arc> } impl Tabbable for TabView { @@ -73,8 +75,10 @@ impl Tabbable for TabView { let proc_view = self.active_tab_().proc_view.clone(); let bookmarks = self.active_tab_().bookmarks.clone(); + let log_view = self.active_tab_().log_view.clone(); tab.proc_view = proc_view; tab.bookmarks = bookmarks; + tab.log_view = log_view; self.push_widget(tab)?; self.active += 1; @@ -209,6 +213,7 @@ impl FileBrowser { let proc_view = ProcView::new(&core); let bookmarks = BMPopup::new(&core); + let log_view = LogView::new(&core, vec![]); @@ -222,7 +227,8 @@ impl FileBrowser { watches: vec![], dir_events: dir_events, proc_view: Arc::new(Mutex::new(proc_view)), - bookmarks: Arc::new(Mutex::new(bookmarks)) }) + bookmarks: Arc::new(Mutex::new(bookmarks)), + log_view: Arc::new(Mutex::new(log_view)) }) } pub fn enter_dir(&mut self) -> HResult<()> { @@ -342,7 +348,7 @@ impl FileBrowser { pub fn set_title(&self) -> HResult<()> { let cwd = &self.cwd.path.to_string_lossy(); - self.core.screen.lock()?.set_title(cwd)?; + self.screen()?.set_title(cwd)?; Ok(()) } @@ -716,10 +722,8 @@ impl Widget for FileBrowser { Key::Char('-') => { self.goto_prev_cwd()?; }, Key::Char('`') => { self.goto_bookmark()?; }, Key::Char('m') => { self.add_bookmark()?; }, - Key::Char('w') => { - self.proc_view.lock()?.popup()?; - } - , + Key::Char('w') => { self.proc_view.lock()?.popup()?; }, + Key::Char('l') => self.log_view.lock()?.popup()?, _ => { self.main_widget_mut()?.on_key(key)?; }, } self.update_preview()?; diff --git a/src/foldview.rs b/src/foldview.rs new file mode 100644 index 0000000..ffe5281 --- /dev/null +++ b/src/foldview.rs @@ -0,0 +1,242 @@ +use termion::event::Key; +use failure::Fail; +use chrono::{DateTime, TimeZone, Local}; + +use crate::term; +use crate::widget::Widget; +use crate::listview::{ListView, Listable}; +use crate::fail::{HResult, HError}; + +pub type LogView = ListView>; + + +#[derive(Debug)] +pub struct LogEntry { + description: String, + content: Option, + lines: usize, + folded: bool +} + + +impl Foldable for LogEntry { + fn description(&self) -> &String { + &self.description + } + fn content(&self) -> Option<&String> { + self.content.as_ref() + } + fn lines(&self) -> usize { + if self.is_folded() { 1 } else { + self.lines + } + } + fn toggle_fold(&mut self) { + self.folded = !self.folded; + } + fn is_folded(&self) -> bool { + self.folded + } +} + + +impl From<&HError> for LogEntry { + fn from(from: &HError) -> LogEntry { + let time: DateTime = Local::now(); + let description = format!("{}{}{}: {}", + term::color_green(), + time.format("%F %R"), + term::color_red(), + from).lines().take(1).collect(); + let mut content = format!("{}{}{}: {}\n", + term::color_green(), + time.format("%F %R"), + term::color_red(), + from); + + + if let Some(cause) = from.cause() { + content += &format!("{}\n", cause); + } + + if let Some(backtrace) = from.backtrace() { + content += &format!("{}\n", backtrace); + } + + let lines = content.lines().count(); + + LogEntry { + description: description, + content: Some(content), + lines: lines, + folded: true + } + } +} + + + +pub trait FoldableWidgetExt { + fn on_refresh(&mut self) -> HResult<()> { Ok(()) } +} + +impl FoldableWidgetExt for ListView> { + fn on_refresh(&mut self) -> HResult<()> { + self.content.refresh_logs() + } +} + +trait LogList { + fn refresh_logs(&mut self) -> HResult<()>; +} + +impl LogList for Vec { + fn refresh_logs(&mut self) -> HResult<()> { + let logs = crate::fail::get_logs()?; + + let mut logentries = logs.into_iter().map(|log| { + LogEntry::from(log) + }).collect(); + + self.append(&mut logentries); + //self.truncate(10); + Ok(()) + } +} + + +pub trait Foldable { + fn description(&self) -> &String; + fn content(&self) -> Option<&String>; + fn lines(&self) -> usize; + fn toggle_fold(&mut self); + fn is_folded(&self) -> bool; + + fn text(&self) -> &String { + if !self.is_folded() && self.content().is_some() { + self.content().unwrap() + } else { self.description() } + } + + fn render_description(&self) -> String { + self.description().to_string() + } + + fn render_content(&self) -> Vec { + if let Some(content) = self.content() { + content + .lines() + .map(|line| line.to_string()) + .collect() + } else { vec![self.render_description()] } + } + + fn render(&self) -> Vec { + if self.is_folded() { + vec![self.render_description()] + } else { + self.render_content() + } + } +} + +impl ListView> +where + ListView>: FoldableWidgetExt { + + fn toggle_fold(&mut self) -> HResult<()> { + let fold = self.current_fold(); + let fold_pos = self.fold_start_pos(fold); + + self.content[fold].toggle_fold(); + + if self.content[fold].is_folded() { + self.set_selection(fold_pos); + } + + self.refresh() + } + + fn fold_start_pos(&self, fold: usize) -> usize { + self.content + .iter() + .take(fold) + .fold(0, |pos, foldable| { + pos + (foldable.lines()) + }) + } + + fn current_fold(&self) -> usize { + let pos = self.get_selection(); + + let fold_lines = self + .content + .iter() + .map(|f| f.lines()) + .collect::>(); + + fold_lines + .iter() + .enumerate() + .fold((0, None), |(lines, fold_pos), (i, current_fold_lines)| { + if fold_pos.is_some() { + (lines, fold_pos) + } else { + if lines + current_fold_lines > pos { + (lines, Some(i)) + } else { + (lines + current_fold_lines, None) + } + }}).1.unwrap() + } +} + + +impl Listable for ListView> +where + ListView>: FoldableWidgetExt { + + fn len(&self) -> usize { + self.content.iter().map(|f| f.lines()).sum() + } + + fn render(&self) -> Vec { + let (xsize, ysize) = self.core.coordinates.size_u(); + let offset = self.offset; + self.content + .iter() + //.skip(offset) + .map(|foldable| + foldable + .render() + .iter() + .map(|line| term::sized_string_u(line, xsize)) + .collect::>()) + .flatten() + .skip(offset) + .take(ysize+1) + .collect() + } + fn on_refresh(&mut self) -> HResult<()> { + FoldableWidgetExt::on_refresh(self) + } + + fn on_key(&mut self, key: Key) -> HResult<()> { + match key { + Key::Up | Key::Char('p') => { + self.move_up(); + self.refresh()?; + } + Key::Char('P') => { for _ in 0..10 { self.move_up() } self.refresh()?; } + Key::Char('N') => { for _ in 0..10 { self.move_down() } self.refresh()?; } + Key::Down | Key::Char('n') => { + self.move_down(); + self.refresh()?; + }, + Key::Char('t') => { self.toggle_fold()?; } + Key::Char('l') => { self.popup_finnished()?; } + _ => {} + } + Ok(()) + } +} diff --git a/src/listview.rs b/src/listview.rs index b0db7ae..e41eb8e 100644 --- a/src/listview.rs +++ b/src/listview.rs @@ -63,11 +63,11 @@ impl Listable for ListView { pub struct ListView where ListView: Listable { pub content: T, - lines: usize, + pub lines: usize, selection: usize, - offset: usize, - buffer: Vec, - core: WidgetCore, + pub offset: usize, + pub buffer: Vec, + pub core: WidgetCore, seeking: bool, } @@ -121,7 +121,7 @@ where self.selection } - fn set_selection(&mut self, position: usize) { + pub fn set_selection(&mut self, position: usize) { let ysize = self.get_coordinates().unwrap().ysize() as usize; let mut offset = 0; @@ -134,55 +134,6 @@ where self.selection = position; } - fn render_line(&self, file: &File) -> String { - let name = &file.name; - let (size, unit) = file.calculate_size().unwrap_or((0, "".to_string())); - let tag = match file.is_tagged() { - Ok(true) => term::color_red() + "*", - _ => "".to_string() - }; - let tag_len = if tag != "" { 1 } else { 0 }; - - let selection_gap = " ".to_string(); - let (name, selection_color) = if file.is_selected() { - (selection_gap + name, crate::term::color_yellow()) - } else { (name.clone(), "".to_string()) }; - - - let xsize = self.get_coordinates().unwrap().xsize(); - let sized_string = term::sized_string(&name, xsize); - let size_pos = xsize - (size.to_string().len() as u16 - + unit.to_string().len() as u16); - let padding = sized_string.len() - sized_string.width_cjk(); - let padding = xsize - padding as u16; - let padding = padding - tag_len; - - format!( - "{}{}{}{}{}{}{}", - termion::cursor::Save, - match &file.color { - Some(color) => format!("{}{}{}{:padding$}{}", - tag, - term::from_lscolor(color), - selection_color, - &sized_string, - term::normal_color(), - padding = padding as usize), - _ => format!("{}{}{}{:padding$}{}", - tag, - term::normal_color(), - selection_color, - &sized_string, - term::normal_color(), - padding = padding as usize), - } , - termion::cursor::Restore, - termion::cursor::Right(size_pos), - term::highlight_color(), - size, - unit - ) - } } impl ListView @@ -364,6 +315,56 @@ impl ListView Ok(()) } + fn render_line(&self, file: &File) -> String { + let name = &file.name; + let (size, unit) = file.calculate_size().unwrap_or((0, "".to_string())); + let tag = match file.is_tagged() { + Ok(true) => term::color_red() + "*", + _ => "".to_string() + }; + let tag_len = if tag != "" { 1 } else { 0 }; + + let selection_gap = " ".to_string(); + let (name, selection_color) = if file.is_selected() { + (selection_gap + name, crate::term::color_yellow()) + } else { (name.clone(), "".to_string()) }; + + + let xsize = self.get_coordinates().unwrap().xsize(); + let sized_string = term::sized_string(&name, xsize); + let size_pos = xsize - (size.to_string().len() as u16 + + unit.to_string().len() as u16); + let padding = sized_string.len() - sized_string.width_cjk(); + let padding = xsize - padding as u16; + let padding = padding - tag_len; + + format!( + "{}{}{}{}{}{}{}", + termion::cursor::Save, + match &file.color { + Some(color) => format!("{}{}{}{:padding$}{}", + tag, + term::from_lscolor(color), + selection_color, + &sized_string, + term::normal_color(), + padding = padding as usize), + _ => format!("{}{}{}{:padding$}{}", + tag, + term::normal_color(), + selection_color, + &sized_string, + term::normal_color(), + padding = padding as usize), + } , + termion::cursor::Restore, + termion::cursor::Right(size_pos), + term::highlight_color(), + size, + unit + ) + } + fn render(&self) -> Vec { let ysize = self.get_coordinates().unwrap().ysize() as usize; let offset = self.offset; diff --git a/src/main.rs b/src/main.rs index 45679eb..7b1583e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ extern crate mime_detective; extern crate rayon; extern crate libc; extern crate notify; +extern crate parse_ansi; use failure::Fail; @@ -44,6 +45,8 @@ mod minibuffer; mod proclist; mod bookmarks; mod paths; +mod foldview; + @@ -55,8 +58,14 @@ use fail::HResult; use file_browser::FileBrowser; use tabview::TabView; + fn main() -> HResult<()> { - match run() { + // do this early so it might be ready when needed + crate::files::load_tags().ok(); + + let core = WidgetCore::new().expect("Can't create WidgetCore!"); + + match run(core.clone()) { Ok(_) => Ok(()), Err(err) => { eprintln!("{:?}\n{:?}", err, err.cause()); @@ -65,29 +74,15 @@ fn main() -> HResult<()> { } } -fn run() -> HResult<()> { - // do this early so it might be ready when needed - crate::files::load_tags()?; - - - let bufout = std::io::BufWriter::new(std::io::stdout()); - // Need to do this here to actually turn terminal into raw mode... - let mut screen = AlternateScreen::from(bufout); - let mut _stdout = MouseTerminal::from(stdout().into_raw_mode()?); - screen.cursor_hide()?; - screen.clear()?; - screen.flush()?; - - let core = WidgetCore::new()?; - +fn run(mut core: WidgetCore) -> HResult<()> { let filebrowser = FileBrowser::new_cored(&core)?; let mut tabview = TabView::new(&core); tabview.push_widget(filebrowser)?; tabview.handle_input()?; - screen.cursor_show()?; - screen.flush()?; + core.screen.cursor_show()?; + core.screen.flush()?; Ok(()) } diff --git a/src/minibuffer.rs b/src/minibuffer.rs index bf915aa..ac5c819 100644 --- a/src/minibuffer.rs +++ b/src/minibuffer.rs @@ -164,7 +164,7 @@ impl MiniBuffer { self.completions.clear(); self.last_completion = None; - self.get_core()?.screen.lock()?.cursor_hide().log(); + self.screen()?.cursor_hide().log(); match self.popup() { Err(HError::MiniBufferCancelledInput) => self.input_cancelled()?, @@ -476,7 +476,7 @@ impl Widget for MiniBuffer { ": ".len() + self.position; - let mut screen = self.get_core()?.screen.lock()?; + let mut screen = self.screen()?; let ysize = screen.ysize()?; screen.goto_xy(cursor_pos, ysize).log(); diff --git a/src/paths.rs b/src/paths.rs new file mode 100644 index 0000000..be40658 --- /dev/null +++ b/src/paths.rs @@ -0,0 +1,29 @@ +use dirs_2; + +use std::path::PathBuf; + +use crate::fail::HResult; + +pub fn hunter_path() -> HResult { + let mut config_dir = dirs_2::config_dir()?; + config_dir.push("hunter/"); + Ok(config_dir) +} + +pub fn bookmark_path() -> HResult { + let mut bookmark_path = hunter_path()?; + bookmark_path.push("bookmarks"); + Ok(bookmark_path) +} + +pub fn tagfile_path() -> HResult { + let mut tagfile_path = hunter_path()?; + tagfile_path.push("tags"); + Ok(tagfile_path) +} + +pub fn history_path() -> HResult { + let mut history_path = hunter_path()?; + history_path.push("history"); + Ok(history_path) +} diff --git a/src/term.rs b/src/term.rs index 641b4b1..2cfd70e 100644 --- a/src/term.rs +++ b/src/term.rs @@ -1,10 +1,43 @@ use std::io::{Stdout, Write, BufWriter}; +use std::sync::{Arc, Mutex}; use termion; use termion::screen::AlternateScreen; +use termion::input::MouseTerminal; +use termion::raw::{IntoRawMode, RawTerminal}; + +use parse_ansi::parse_bytes; use crate::fail::HResult; +#[derive(Clone)] +pub struct Screen { + screen: Arc>>>>> +} + +impl Screen { + pub fn new() -> HResult { + let screen = BufWriter::new(std::io::stdout()); + let screen = AlternateScreen::from(screen); + let mut screen = MouseTerminal::from(screen).into_raw_mode()?; + screen.cursor_hide()?; + screen.flush()?; + screen.clear()?; + Ok(Screen { + screen: Arc::new(Mutex::new(screen)) + }) + } +} + +impl Write for Screen { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.screen.lock().unwrap().write(buf) + } + fn flush(&mut self) -> std::io::Result<()> { + self.screen.lock().unwrap().flush() + } +} + pub trait ScreenExt: Write { fn cursor_hide(&mut self) -> HResult<()> { write!(self, "{}", termion::cursor::Hide)?; @@ -36,12 +69,17 @@ pub trait ScreenExt: Write { write!(self, "{}", goto_xy(x + 1, y + 1))?; Ok(()) } + fn xsize(&self) -> HResult { + let (xsize, _) = termion::terminal_size()?; + Ok((xsize - 1) as usize) + } fn ysize(&self) -> HResult { let (_, ysize) = termion::terminal_size()?; Ok((ysize - 1) as usize) } fn set_title(&mut self, title: &str) -> HResult<()> { - write!(self, "\x1b]2;{}", title)?; + write!(self, "\x1b]2;hunter: {}", title)?; + write!(self, "\x1bkhunter: {}\x1b\\", title)?; Ok(()) } } @@ -49,6 +87,7 @@ pub trait ScreenExt: Write { impl ScreenExt for AlternateScreen> {} impl ScreenExt for AlternateScreen {} impl ScreenExt for AlternateScreen> {} +impl ScreenExt for Screen {} pub fn xsize() -> u16 { let (xsize, _) = termion::terminal_size().unwrap(); @@ -71,6 +110,43 @@ pub fn sized_string(string: &str, xsize: u16) -> String { }) } +fn is_ansi(ansi_pos: &Vec<(usize, usize)>, char_pos: &usize) -> bool { + ansi_pos.iter().fold(false, |is_ansi, (start, end)| { + if char_pos >= start && char_pos <= end { + true + } else { is_ansi } + }) +} + +fn ansi_len_at(ansi_pos: &Vec<(usize, usize)>, char_pos: &usize) -> usize { + ansi_pos.iter().fold(0, |len, (start, end)| { + if char_pos >= end { + len + (end-start) + } else { len } + }) +} + +pub fn sized_string_u(string: &str, xsize: usize) -> String { + let ansi_pos = parse_bytes(string.as_bytes()).map(|m| { + (m.start(), m.end()) + }).collect(); + + let sized = string.chars().enumerate().fold("".to_string(), |acc, (i, ch)| { + let width: usize = unicode_width::UnicodeWidthStr::width_cjk(acc.as_str()); + let ansi_len = ansi_len_at(&ansi_pos, &i); + + if width >= xsize as usize + ansi_len { + acc + } else { + acc + &ch.to_string() + } + + }); + let ansi_len = ansi_len_at(&ansi_pos, &(sized.len().saturating_sub(1))); + let padded = format!("{:padding$}", sized, padding=xsize + ansi_len + 1); + padded +} + // Do these as constants pub fn highlight_color() -> String { @@ -84,7 +160,7 @@ pub fn highlight_color() -> String { pub fn normal_color() -> String { format!( "{}", - termion::color::Fg(termion::color::LightBlue), + termion::color::Fg(termion::color::White), //termion::color::Bg(termion::color::Black) ) } @@ -127,6 +203,12 @@ pub fn goto_xy(x: u16, y: u16) -> String { format!("{}", termion::cursor::Goto(x, y)) } +pub fn goto_xy_u(x: usize, y: usize) -> String { + let x = (x+1) as u16; + let y = (y+1) as u16; + format!("{}", termion::cursor::Goto(x, y)) +} + // pub fn move_top() -> String { // gotoy(1) // } @@ -143,6 +225,14 @@ pub fn invert() -> String { format!("{}", termion::style::Invert) } +pub fn cursor_save() -> String { + format!("{}", termion::cursor::Save) +} + +pub fn cursor_restore() -> String { + format!("{}", termion::cursor::Restore) +} + pub fn header_color() -> String { format!( "{}{}", diff --git a/src/widget.rs b/src/widget.rs index 7000cfb..f4b18b9 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -3,16 +3,14 @@ use std::sync::mpsc::{Sender, Receiver, channel}; use termion::event::{Event, Key, MouseEvent}; use termion::input::TermRead; -use termion::screen::AlternateScreen; - use crate::coordinates::{Coordinates, Position, Size}; use crate::fail::{HResult, HError, ErrorLog}; use crate::minibuffer::MiniBuffer; use crate::term; -use crate::term::ScreenExt; +use crate::term::{Screen, ScreenExt}; -use std::io::{BufWriter, stdin, stdout, Stdout}; +use std::io::stdin; #[derive(Debug)] pub enum Events { @@ -44,7 +42,7 @@ impl std::fmt::Debug for WidgetCore { #[derive(Clone)] pub struct WidgetCore { - pub screen: Arc>>>, + pub screen: Screen, pub coordinates: Coordinates, pub minibuffer: Arc>>, pub event_sender: Sender, @@ -54,7 +52,7 @@ pub struct WidgetCore { impl WidgetCore { pub fn new() -> HResult { - let screen = AlternateScreen::from(BufWriter::new(stdout())); + let screen = Screen::new()?; let coords = Coordinates::new_at(term::xsize(), term::ysize() - 2, 1, @@ -63,7 +61,7 @@ impl WidgetCore { let status_bar_content = Arc::new(Mutex::new(None)); let core = WidgetCore { - screen: Arc::new(Mutex::new(screen)), + screen: screen, coordinates: coords, minibuffer: Arc::new(Mutex::new(None)), event_sender: sender, @@ -209,6 +207,10 @@ pub trait Widget { result } + fn popup_finnished(&self) -> HResult<()> { + HError::popup_finnished() + } + fn run_widget(&mut self) -> HResult<()> { let (tx_event, rx_event) = channel(); self.get_core()?.get_sender().send(Events::ExclusiveEvent(Some(tx_event)))?; @@ -220,7 +222,13 @@ pub trait Widget { for event in rx_event.iter() { match event { Events::InputEvent(input) => { - self.on_event(input)?; + match self.on_event(input) { + err @ Err(HError::PopupFinnished) | + err @ Err(HError::Quit) | + err @ Err(HError::MiniBufferCancelledInput) => err?, + err @ Err(_) => err.log(), + Ok(_) => {} + } } Events::WidgetReady => { self.refresh().log(); @@ -342,13 +350,17 @@ pub trait Widget { fn minibuffer(&self, query: &str) -> HResult { let answer = self.get_core()?.minibuffer.lock()?.as_mut()?.query(query); - let mut screen = self.get_core()?.screen.lock()?; + let mut screen = self.screen()?; screen.cursor_hide().log(); answer } + fn screen(&self) -> HResult { + Ok(self.get_core()?.screen.clone()) + } + fn write_to_screen(&self, s: &str) -> HResult<()> { - let mut screen = self.get_core()?.screen.lock()?; + let mut screen = self.screen()?; screen.write_str(s) } }