log/foldview

This commit is contained in:
rabite 2019-03-15 14:22:05 +01:00
parent 370064387b
commit 297239c7c7
12 changed files with 723 additions and 107 deletions

View File

@ -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

178
src/bookmarks.rs Normal file
View File

@ -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<char, String>,
}
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::<String>();
std::fs::write(bm_file, bookmarks)?;
Ok(())
}
}
pub struct BMPopup {
core: WidgetCore,
bookmarks: Bookmarks,
bookmark_path: Option<String>,
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<String> {
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<String> {
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::<String>();
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(())
}
}

View File

@ -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
}

View File

@ -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<T> = Result<T, HError>;
#[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<T>() -> HResult<T> {
Err(HError::MiniBufferEmptyInput)
}
pub fn undefined_key<T>(key: Key) -> HResult<T> {
Err(HError::WidgetUndefinedKeyError { key: key })
}
}
lazy_static! {
static ref LOG: Mutex<Vec<LogEntry>> = Mutex::new(vec![]);
}
pub fn get_logs() -> HResult<Vec<LogEntry>> {
let logs = LOG.lock()?.drain(..).collect();
Ok(logs)
}
pub fn put_log<L: Into<LogEntry>>(log: L) -> HResult<()> {
LOG.lock()?.push(log.into());
Ok(())
}
pub trait ErrorLog where Self: Sized {
@ -98,6 +123,7 @@ impl<T> ErrorLog for HResult<T> {
fn log(self) {
if let Err(err) = self {
eprintln!("{:?}", err);
put_log(&err).ok();
}
}
}
@ -115,69 +141,89 @@ impl<T> ErrorLog for HResult<T> {
impl From<std::io::Error> 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<failure::Error> 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<std::sync::mpsc::TryRecvError> 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<std::sync::mpsc::RecvError> 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<T> From<std::sync::mpsc::SendError<T>> for HError {
fn from(error: std::sync::mpsc::SendError<T>) -> Self {
dbg!(&error);
HError::ChannelSendError
let err = HError::ChannelSendError;
put_log(&err).ok();
err
}
}
impl<T> From<std::sync::PoisonError<T>> for HError {
fn from(_: std::sync::PoisonError<T>) -> Self {
dbg!("Poisoned Mutex");
HError::MutexError
let err = HError::MutexError;
put_log(&err).ok();
err
}
}
impl<T> From<std::sync::TryLockError<T>> for HError {
fn from(error: std::sync::TryLockError<T>) -> Self {
dbg!(&error);
HError::TryLockError
let err = HError::TryLockError;
put_log(&err).ok();
err
}
}
impl From<std::option::NoneError> 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<std::path::StripPrefixError> 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<notify::Error> 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
}
}

View File

@ -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<PathBuf>,
dir_events: Arc<Mutex<Vec<DebouncedEvent>>>,
proc_view: Arc<Mutex<ProcView>>,
bookmarks: Arc<Mutex<BMPopup>>
bookmarks: Arc<Mutex<BMPopup>>,
log_view: Arc<Mutex<LogView>>
}
impl Tabbable for TabView<FileBrowser> {
@ -73,8 +75,10 @@ impl Tabbable for TabView<FileBrowser> {
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()?;

242
src/foldview.rs Normal file
View File

@ -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<Vec<LogEntry>>;
#[derive(Debug)]
pub struct LogEntry {
description: String,
content: Option<String>,
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> = 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<Vec<LogEntry>> {
fn on_refresh(&mut self) -> HResult<()> {
self.content.refresh_logs()
}
}
trait LogList {
fn refresh_logs(&mut self) -> HResult<()>;
}
impl LogList for Vec<LogEntry> {
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<String> {
if let Some(content) = self.content() {
content
.lines()
.map(|line| line.to_string())
.collect()
} else { vec![self.render_description()] }
}
fn render(&self) -> Vec<String> {
if self.is_folded() {
vec![self.render_description()]
} else {
self.render_content()
}
}
}
impl<F: Foldable> ListView<Vec<F>>
where
ListView<Vec<F>>: 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::<Vec<usize>>();
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<F: Foldable> Listable for ListView<Vec<F>>
where
ListView<Vec<F>>: FoldableWidgetExt {
fn len(&self) -> usize {
self.content.iter().map(|f| f.lines()).sum()
}
fn render(&self) -> Vec<String> {
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::<Vec<_>>())
.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(())
}
}

View File

@ -63,11 +63,11 @@ impl Listable for ListView<Files> {
pub struct ListView<T> where ListView<T>: Listable
{
pub content: T,
lines: usize,
pub lines: usize,
selection: usize,
offset: usize,
buffer: Vec<String>,
core: WidgetCore,
pub offset: usize,
pub buffer: Vec<String>,
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<Files>
@ -364,6 +315,56 @@ impl ListView<Files>
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<String> {
let ysize = self.get_coordinates().unwrap().ysize() as usize;
let offset = self.offset;

View File

@ -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(())
}

View File

@ -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();

29
src/paths.rs Normal file
View File

@ -0,0 +1,29 @@
use dirs_2;
use std::path::PathBuf;
use crate::fail::HResult;
pub fn hunter_path() -> HResult<PathBuf> {
let mut config_dir = dirs_2::config_dir()?;
config_dir.push("hunter/");
Ok(config_dir)
}
pub fn bookmark_path() -> HResult<PathBuf> {
let mut bookmark_path = hunter_path()?;
bookmark_path.push("bookmarks");
Ok(bookmark_path)
}
pub fn tagfile_path() -> HResult<PathBuf> {
let mut tagfile_path = hunter_path()?;
tagfile_path.push("tags");
Ok(tagfile_path)
}
pub fn history_path() -> HResult<PathBuf> {
let mut history_path = hunter_path()?;
history_path.push("history");
Ok(history_path)
}

View File

@ -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<Mutex<RawTerminal<MouseTerminal<AlternateScreen<BufWriter<Stdout>>>>>>
}
impl Screen {
pub fn new() -> HResult<Screen> {
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<usize> {
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<usize> {
let (xsize, _) = termion::terminal_size()?;
Ok((xsize - 1) as usize)
}
fn ysize(&self) -> HResult<usize> {
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<Box<Stdout>> {}
impl ScreenExt for AlternateScreen<Stdout> {}
impl ScreenExt for AlternateScreen<BufWriter<Stdout>> {}
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!(
"{}{}",

View File

@ -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<Mutex<AlternateScreen<BufWriter<Stdout>>>>,
pub screen: Screen,
pub coordinates: Coordinates,
pub minibuffer: Arc<Mutex<Option<MiniBuffer>>>,
pub event_sender: Sender<Events>,
@ -54,7 +52,7 @@ pub struct WidgetCore {
impl WidgetCore {
pub fn new() -> HResult<WidgetCore> {
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<String> {
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<Screen> {
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)
}
}