summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrabite <rabite@posteo.de>2019-04-06 17:22:28 +0200
committerrabite <rabite@posteo.de>2019-04-06 17:22:28 +0200
commit7f70fa29046e09be414b52ba572eb9675979cd46 (patch)
tree49c753d75c71c43b6ca2d3093e5b361822e7361a
parentcab0de7a05fd446e68fc10d37253d67400c33417 (diff)
parent6098ad301595c0c808634fe03e2d194d3fc65c3c (diff)
Merge branch 'master' into evil
-rw-r--r--Cargo.toml3
-rw-r--r--LICENSE13
-rw-r--r--README.md128
-rw-r--r--docs/hunter.pngbin0 -> 107710 bytes
-rwxr-xr-xextra/scope.sh217
-rw-r--r--src/bookmarks.rs49
-rw-r--r--src/config.rs58
-rw-r--r--src/dirty.rs63
-rw-r--r--src/fail.rs130
-rw-r--r--src/file_browser.rs708
-rw-r--r--src/files.rs490
-rw-r--r--src/foldview.rs2
-rw-r--r--src/fscache.rs366
-rw-r--r--src/hbox.rs13
-rw-r--r--src/listview.rs118
-rw-r--r--src/main.rs16
-rw-r--r--src/minibuffer.rs2
-rw-r--r--src/paths.rs6
-rw-r--r--src/preview.rs773
-rw-r--r--src/proclist.rs62
-rw-r--r--src/stats.rs84
-rw-r--r--src/tabview.rs13
-rw-r--r--src/term.rs95
-rw-r--r--src/textview.rs6
-rw-r--r--src/widget.rs121
25 files changed, 2643 insertions, 893 deletions
diff --git a/Cargo.toml b/Cargo.toml
index ff594f2..278f2ea 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,6 +12,7 @@ x11-clipboard = "*"
alphanumeric-sort = "1.0.6"
lscolors = { version = "0.5.0", features = [ "ansi_term" ] }
mime-detective = "*"
+tree_magic = "0.2.1"
rayon = "1.0.3"
dirs-2 = "1.1.0"
users = "0.8"
@@ -22,6 +23,8 @@ failure_derive = "0.1.1"
notify = "4.0.9"
parse-ansi = "0.1.6"
signal-notify = "0.1.3"
+systemstat = "0.1.4"
+
#[profile.release]
#debug = true
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f0fdee5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ Version 2, December 2004
+
+ Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
+
+ Everyone is permitted to copy and distribute verbatim or modified
+ copies of this license document, and changing it is allowed as long
+ as the name is changed.
+
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. You just DO WHAT THE FUCK YOU WANT TO. \ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..072a69c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,128 @@
+hunter
+======
+
+![hunter](https://raw.githubusercontent.com/rabite0/hunter/master/docs/hunter.png)
+
+hunter is a fast and lag-free file browser/manager for the terminal. It features a heavily asychronous and multi-threaded design and all disk IO happens off the main thread in a non-blocking fashion, so that hunter will always stay responsive, even under heavy load on a slow spinning rust disk, even with all the previews enabled.
+
+It's heavily inspired by the excellent ranger, but a little more Emacs-flavoured, and written in Rust to make sure it starts up quickly and to take advantage of it's strong guarantees around concurrency. It's so fast I actually built in animations for some parts as a joke, but in fact it turned out to look really nice and makes it look much smoother. YMMV, of course, and this can be disabled.
+
+Most things you would expect are implementend, among them tabs, bookmarks (with ranger-import), search/filter, previews of files/directories (including size information in previewed directories), a minibuffer at the bottom with file name completion, multi file selection, etc., etc. There are also a few original ideas, especially around subprocess handling. The process viewer actually shows the output of started subprocesses, their pids and exit codes, notifies on new output and process completion. It's somewhat of a primitive TUI shell. File names are handled using raw OsString, so there is no file it can't handle, no matter what garbage the name contains. It also sets the tmux/terminal title to the current directory on supported terminals.
+
+To speed up the loading of direcories metadata in the preview/backview is only loaded for files you can see, except in the main view. Still, metadata is also loaded asynchronously, so you can sometimes see it updating file listings while browsing through your files. I think this is better than waiting though :).
+
+Technically hunter is not a file "manager" itself. It has no built in primitives for file manipulation like delete, rename, move, and so on. Instead it relies on it's easy and extensive integration with the standard cli tools to do it's job. For that purpose there are various file name/path substitution patterns and an auto-completing for executables you want to run.
+
+This is a young project and probably (definitely) has some bugs and edge cases. It hasn't been tested on a lot of terminals, but at least alacritty, kitty and urxvt work fine. It should work on most Unix-flavoured systems supported by Rust, but was only tested on GNU/Linux. I haven't lost any files so far, at least.
+
+A big thanks to ranger and its developers. Without its inspiration this wouldn't have been possible. hunter not a drop-in replacement and doesn't cover every use-care, especially if you're into advanced customization, since hunter has basically none unless you modify the code, but if you just need fast above all else it's might be a good coice.
+
+## Features:
+* Lag-free architecture, always responsive
+* Asynchronous multi-threaded IO
+* Tabs
+* Multi-file selection
+* ranger import for bookmarks/tags
+* minibuffer with completion and filename/selection/tab/direcory substitution
+* subprocess viewer that shows output of started subprocesses
+* exit and cd into last directory and put seleceted files into shell variables
+* slide up animation for previews for a smoother experience (configurable)
+* fffast
+
+## NOTE:
+hunter uses ranger's rifle to open files if rifle is in your $PATH. If it can't find rifle it uses xdg-open. It also uses ranger's scope.sh to generate previews for non-text files. A slightly modified version is included in the "extra" directory. Put it in your $PATH somewhere if you want previews for non-text files.
+
+
+## Configuration
+hunter reads $XDG_CONFIG_HOME/hunter/config at startup. There are two options, which can be set. The configuration file is read asynchronously, so if it's not read by the time hunter starts drawing you will see its default configuration until the config file is read. Options can be set like this (default config):
+
+animation=on
+show_hidden=off
+
+
+
+Keybindings:
+============
+
+## evil mode
+By default hunter uses emacs style keybindings. If you use a QWERTY-like keyboard layout this is probably not what you want. In that case use the "evil" branch which remaps movement keys to vi-style.
+
+## Main view:
+
+| Key | Action |
+| ------------------- |:---------------------------------|
+|n/p (evil: n/j) |move up/down |
+|N/P (evil: N/J) |5x move up/5x move down |
+|< |move to top |
+|> |mve to bottom |
+|f/b (evil: h/l) |enter (run executable) |
+|S |search file |
+|Alt(s) |search next |
+|Alt(S) |search prev |
+|Ctrl(f) |filer |
+|space |multi select file |
+|v |invert selections |
+|t |toggle tag |
+|h |toggle show hidden |
+|r |reverse sort |
+|s |cycle sort (name/size/mtime) |
+|K |select next by mtime |
+|k |select prev by mtime |
+|d |toggle dirs first |
+|/ |turbo cd |
+|Q |quit with dir/selections |
+|F |start executabe in background |
+|- |goto prev cwd |
+|` |goto bookmark |
+|m |add bookmark |
+|w |show processes |
+|l evil(L) |show log |
+|z |open subshell in cwd |
+|c |toggle columns |
+|F(n) |switch to tab |
+
+
+
+## Keybindings in bookmark popup:
+
+| Key | Action |
+| ------------------- |:---------------------------------|
+|(key) |open bookmark |
+|` |goto last cwd |
+|Ctrl(c) |cancel |
+|Alt(key) |delete bookmark |
+
+## Keybindings in process viewer:
+
+| Key | Action |
+| ------------------- |:---------------------------------|
+|w |close process viewer |
+|d |remove process |
+|k |kill process |
+|p evil(k) |move up |
+|n evil(j) |move down |
+|f |toggle follow outupt |
+|Ctrl(n) evil(Ctrl(j) |scroll output down |
+|Ctrl(p) evil(Ctrl(k) |scroll output up |
+|Ctrl(v) |page down |
+|Alt(v) |page up |
+|< |scroll to bottom |
+|> |scroll to top |
+
+
+## Keybindings in minibuffer:
+
+| Key | Action |
+| ------------------- |:---------------------------------|
+|Esc/Ctrl(c) |cancel input |
+|Tab |complete |
+|F(n) |insert tab substitution |
+|Ctrl(d) |delete char |
+|Ctrl(b) |mvoe cursor left |
+|Ctrl(f) |move cursor right |
+|Ctrl(p)/Alt(p) |history up |
+|Ctrl(n)/Alt(n) |history down |
+|Ctrl(u) |clear line |
+|Ctrl(h) |delete word |
+|Ctrl(a) |move cursor to beginning |
+|Ctrl(e) |move cursor to end |
diff --git a/docs/hunter.png b/docs/hunter.png
new file mode 100644
index 0000000..d10fb7a
--- /dev/null
+++ b/docs/hunter.png
Binary files differ
diff --git a/extra/scope.sh b/extra/scope.sh
new file mode 100755
index 0000000..0edac07
--- /dev/null
+++ b/extra/scope.sh
@@ -0,0 +1,217 @@
+#!/usr/bin/env bash
+
+set -o noclobber -o noglob -o nounset -o pipefail
+IFS=$'\n'
+
+# If the option `use_preview_script` is set to `true`,
+# then this script will be called and its output will be displayed in ranger.
+# ANSI color codes are supported.
+# STDIN is disabled, so interactive scripts won't work properly
+
+# This script is considered a configuration file and must be updated manually.
+# It will be left untouched if you upgrade ranger.
+
+# Meanings of exit codes:
+# code | meaning | action of ranger
+# -----+------------+-------------------------------------------
+# 0 | success | Display stdout as preview
+# 1 | no preview | Display no preview at all
+# 2 | plain text | Display the plain content of the file
+# 3 | fix width | Don't reload when width changes
+# 4 | fix height | Don't reload when height changes
+# 5 | fix both | Don't ever reload
+# 6 | image | Display the image `$IMAGE_CACHE_PATH` points to as an image preview
+# 7 | image | Display the file directly as an image
+
+# Script arguments
+FILE_PATH="${1}" # Full path of the highlighted file
+PV_WIDTH="${2}" # Width of the preview pane (number of fitting characters)
+PV_HEIGHT="${3}" # Height of the preview pane (number of fitting characters)
+IMAGE_CACHE_PATH="${4}" # Full path that should be used to cache image preview
+PV_IMAGE_ENABLED="${5}" # 'True' if image previews are enabled, 'False' otherwise.
+
+FILE_EXTENSION="${FILE_PATH##*.}"
+FILE_EXTENSION_LOWER=$(echo ${FILE_EXTENSION} | tr '[:upper:]' '[:lower:]')
+
+# Settings
+HIGHLIGHT_SIZE_MAX=262143 # 256KiB
+HIGHLIGHT_TABWIDTH=8
+HIGHLIGHT_STYLE='pablo'
+PYGMENTIZE_STYLE='autumn'
+
+
+handle_extension() {
+ case "${FILE_EXTENSION_LOWER}" in
+ # Archive
+ a|ace|bz2|xz|gz|alz|arc|arj|cab|cpio|deb|jar|lha|lz|lzh|lzma|lzo|\
+ rpm|rz|t7z|tar|tlz|txz|tZ|tzo|war|xpi|Z|zip)
+ 7z l -p -- "${FILE_PATH}" && exit 5
+ #atool --list -- "${FILE_PATH}" && exit 5
+ #bsdtar --list --file "${FILE_PATH}" && exit 5
+ exit 1;;
+ rar)
+ # Avoid password prompt by providing empty password
+ unrar lt -p- -- "${FILE_PATH}" && exit 5
+ exit 1;;
+ 7z)
+ # Avoid password prompt by providing empty password
+ 7z l -p -- "${FILE_PATH}" && exit 5
+ exit 1;;
+
+ # PDF
+ pdf)
+ # Preview as text conversion
+ pdftotext -l 10 -nopgbrk -q -- "${FILE_PATH}" - | fmt -w ${PV_WIDTH} && exit 5
+ mutool draw -F txt -i -- "${FILE_PATH}" 1-10 | fmt -w ${PV_WIDTH} && exit 5
+ exiftool "${FILE_PATH}" && exit 5
+ exit 1;;
+
+ # BitTorrent
+ torrent)
+ transmission-show -- "${FILE_PATH}" && exit 5
+ exit 1;;
+
+ # OpenDocument
+ odt|ods|odp|sxw)
+ # Preview as text conversion
+ odt2txt "${FILE_PATH}" && exit 5
+ exit 1;;
+
+ # HTML
+ htm|html|xhtml)
+ # Preview as text conversion
+ w3m -dump "${FILE_PATH}" && exit 5
+ lynx -dump -- "${FILE_PATH}" && exit 5
+ elinks -dump "${FILE_PATH}" && exit 5
+ ;; # Continue with next handler on failure
+ esac
+}
+
+handle_image() {
+ local mimetype="${1}"
+ case "${mimetype}" in
+ # SVG
+ # image/svg+xml)
+ # convert "${FILE_PATH}" "${IMAGE_CACHE_PATH}" && exit 6
+ # exit 1;;
+
+ # Image
+ image/*)
+ local orientation
+ orientation="$( identify -format '%[EXIF:Orientation]\n' -- "${FILE_PATH}" )"
+ # If orientation data is present and the image actually
+ # needs rotating ("1" means no rotation)...
+ if [[ -n "$orientation" && "$orientation" != 1 ]]; then
+ # ...auto-rotate the image according to the EXIF data.
+ convert -- "${FILE_PATH}" -auto-orient "${IMAGE_CACHE_PATH}" && exit 6
+ fi
+
+ # `w3mimgdisplay` will be called for all images (unless overriden as above),
+ # but might fail for unsupported types.
+ exit 7;;
+
+ # Video
+ # video/*)
+ # # Thumbnail
+ # ffmpegthumbnailer -i "${FILE_PATH}" -o "${IMAGE_CACHE_PATH}" -s 0 && exit 6
+ # exit 1;;
+ # PDF
+ # application/pdf)
+ # pdftoppm -f 1 -l 1 \
+ # -scale-to-x 1920 \
+ # -scale-to-y -1 \
+ # -singlefile \
+ # -jpeg -tiffcompression jpeg \
+ # -- "${FILE_PATH}" "${IMAGE_CACHE_PATH%.*}" \
+ # && exit 6 || exit 1;;
+
+ # Preview archives using the first image inside.
+ # (Very useful for comic book collections for example.)
+ # application/zip|application/x-rar|application/x-7z-compressed|\
+ # application/x-xz|application/x-bzip2|application/x-gzip|application/x-tar)
+ # local fn=""; local fe=""
+ # local zip=""; local rar=""; local tar=""; local bsd=""
+ # case "${mimetype}" in
+ # application/zip) zip=1 ;;
+ # application/x-rar) rar=1 ;;
+ # application/x-7z-compressed) ;;
+ # *) tar=1 ;;
+ # esac
+ # { [ "$tar" ] && fn=$(tar --list --file "${FILE_PATH}"); } || \
+ # { fn=$(bsdtar --list --file "${FILE_PATH}") && bsd=1 && tar=""; } || \
+ # { [ "$rar" ] && fn=$(unrar lb -p- -- "${FILE_PATH}"); } || \
+ # { [ "$zip" ] && fn=$(zipinfo -1 -- "${FILE_PATH}"); } || return
+ #
+ # fn=$(echo "$fn" | python -c "import sys; import mimetypes as m; \
+ # [ print(l, end='') for l in sys.stdin if \
+ # (m.guess_type(l[:-1])[0] or '').startswith('image/') ]" |\
+ # sort -V | head -n 1)
+ # [ "$fn" = "" ] && return
+ # [ "$bsd" ] && fn=$(printf '%b' "$fn")
+ #
+ # [ "$tar" ] && tar --extract --to-stdout \
+ # --file "${FILE_PATH}" -- "$fn" > "${IMAGE_CACHE_PATH}" && exit 6
+ # fe=$(echo -n "$fn" | sed 's/[][*?\]/\\\0/g')
+ # [ "$bsd" ] && bsdtar --extract --to-stdout \
+ # --file "${FILE_PATH}" -- "$fe" > "${IMAGE_CACHE_PATH}" && exit 6
+ # [ "$bsd" ] || [ "$tar" ] && rm -- "${IMAGE_CACHE_PATH}"
+ # [ "$rar" ] && unrar p -p- -inul -- "${FILE_PATH}" "$fn" > \
+ # "${IMAGE_CACHE_PATH}" && exit 6
+ # [ "$zip" ] && unzip -pP "" -- "${FILE_PATH}" "$fe" > \
+ # "${IMAGE_CACHE_PATH}" && exit 6
+ # [ "$rar" ] || [ "$zip" ] && rm -- "${IMAGE_CACHE_PATH}"
+ # ;;
+ esac
+}
+
+handle_mime() {
+ local mimetype="${1}"
+ case "${mimetype}" in
+ # Text
+ text/* | */xml)
+ # Syntax highlight
+ if [[ "$( stat --printf='%s' -- "${FILE_PATH}" )" -gt "${HIGHLIGHT_SIZE_MAX}" ]]; then
+ exit 2
+ fi
+ if [[ "$( tput colors )" -ge 256 ]]; then
+ local pygmentize_format='terminal256'
+ local highlight_format='xterm256'
+ else
+ local pygmentize_format='terminal'
+ local highlight_format='ansi'
+ fi
+ highlight --replace-tabs="${HIGHLIGHT_TABWIDTH}" --out-format="${highlight_format}" \
+ --style="${HIGHLIGHT_STYLE}" --force -- "${FILE_PATH}" && exit 5
+ # pygmentize -f "${pygmentize_format}" -O "style=${PYGMENTIZE_STYLE}" -- "${FILE_PATH}" && exit 5
+ exit 2;;
+
+ # Image
+ image/*)
+ # Preview as text conversion
+ # img2txt --gamma=0.6 --width="${PV_WIDTH}" -- "${FILE_PATH}" && exit 4
+ exiftool "${FILE_PATH}" && exit 5
+ exit 1;;
+
+ # Video and audio
+ video/* | audio/*)
+ mediainfo "${FILE_PATH}" && exit 5
+ exiftool "${FILE_PATH}" && exit 5
+ exit 1;;
+ esac
+}
+
+handle_fallback() {
+ echo '----- File Type Classification -----' && file --dereference --brief -- "${FILE_PATH}" && exit 5
+ exit 1
+}
+
+
+MIMETYPE="$( file --dereference --brief --mime-type -- "${FILE_PATH}" )"
+if [[ "${PV_IMAGE_ENABLED}" == 'True' ]]; then
+ handle_image "${MIMETYPE}"
+fi
+handle_extension
+handle_mime "${MIMETYPE}"
+handle_fallback
+
+exit 1
diff --git a/src/bookmarks.rs b/src/bookmarks.rs
index 379565e..326be7c 100644
--- a/src/bookmarks.rs
+++ b/src/bookmarks.rs
@@ -5,7 +5,6 @@ use std::collections::HashMap;
use crate::fail::{HResult, HError, ErrorLog};
use crate::widget::{Widget, WidgetCore};
use crate::coordinates::Coordinates;
-use crate::files::{Files, File};
use crate::term;
#[derive(PartialEq, Eq, Clone, Debug)]
@@ -30,27 +29,41 @@ impl Bookmarks {
}
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);
+ if !bm_file.exists() {
+ self.import().log();
+ }
- 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);
+ let bm_content = std::fs::read_to_string(bm_file)?;
+ let mapping = bm_content.lines()
+ .fold(HashMap::new(), |mut bm, line| {
+ let parts = line.splitn(2, ":").collect::<Vec<&str>>();
+ if parts.len() == 2 {
+ if let Some(key) = parts[0].chars().next() {
+ let path = parts[1].to_string();
+ bm.insert(key, path);
+ }
}
- mapping
+ bm
});
self.mapping = mapping;
Ok(())
}
+ pub fn import(&self) -> HResult<()> {
+ let mut ranger_bm_path = crate::paths::ranger_path()?;
+ ranger_bm_path.push("bookmarks");
+
+ if ranger_bm_path.exists() {
+ let bm_file = crate::paths::bookmark_path()?;
+ std::fs::copy(ranger_bm_path, bm_file)?;
+ }
+ 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)
+ format!("{}:{}\n", key, path)
}).collect::<String>();
std::fs::write(bm_file, bookmarks)?;
@@ -86,6 +99,7 @@ impl BMPopup {
Ok(_) => {},
Err(HError::PopupFinnished) => {},
err @ Err(HError::TerminalResizedError) => err?,
+ err @ Err(HError::WidgetResizedError) => err?,
err @ Err(_) => err?,
}
self.clear()?;
@@ -104,6 +118,10 @@ impl BMPopup {
Ok(())
}
+ fn resize(&mut self) -> HResult<()> {
+ HError::terminal_resized()?
+ }
+
pub fn render_line(&self, n: u16, key: &char, path: &str) -> String {
let xsize = term::xsize();
let padding = xsize - 4;
@@ -134,13 +152,13 @@ impl Widget for BMPopup {
HError::terminal_resized()
}
- fn set_coordinates(&mut self, coordinates: &Coordinates) -> HResult<()> {
- let (xsize, ysize) = coordinates.size_u();
+ fn set_coordinates(&mut self, _: &Coordinates) -> HResult<()> {
+ let (xsize, ysize) = crate::term::size()?;
let len = self.bookmarks.mapping.len();
let ysize = ysize.saturating_sub( len + 1 );
self.core.coordinates.set_size_u(xsize.saturating_sub(1), len);
- self.core.coordinates.set_position_u(1, ysize+2);
+ self.core.coordinates.set_position_u(1, ysize);
Ok(())
}
@@ -176,6 +194,7 @@ impl Widget for BMPopup {
let path = self.bookmark_path.take()?;
self.bookmarks.add(key, &path)?;
self.add_mode = false;
+ self.bookmarks.save().log();
return HError::popup_finnished();
}
if let Ok(path) = self.bookmarks.get(key) {
@@ -185,6 +204,8 @@ impl Widget for BMPopup {
}
Key::Alt(key) => {
self.bookmarks.mapping.remove(&key);
+ self.bookmarks.save().log();
+ return HError::widget_resized();
}
_ => {}
}
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..4801286
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,58 @@
+use crate::paths;
+use crate::fail::{HError, HResult, ErrorLog};
+
+#[derive(Debug, Clone)]
+pub struct Config {
+ pub animation: bool,
+ pub show_hidden: bool,
+}
+
+
+impl Config {
+ pub fn new() -> Config {
+ Config {
+ animation: true,
+ show_hidden: false
+ }
+ }
+
+ pub fn load() -> HResult<Config> {
+ let config_path = paths::config_path()?;
+
+ if !config_path.exists() {
+ return Ok(Config::new());
+ }
+
+ let config_string = std::fs::read_to_string(config_path)?;
+
+ let config = config_string.lines().fold(Config::new(), |mut config, line| {
+ match Config::prep_line(line) {
+ Ok(("animation", "on")) => { config.animation = true; },
+ Ok(("animation", "off")) => { config.animation = false; },
+ Ok(("show_hidden", "on")) => { config.show_hidden = true; },
+ Ok(("show_hidden", "off")) => { config.show_hidden = false; },
+ _ => { HError::config_error::<Config>(line.to_string()).log(); }
+ }
+ config
+ });
+ Ok(config)
+ }
+
+ fn prep_line<'a>(line: &'a str) -> HResult<(&'a str, &'a str)> {
+ let setting = line.split("=").collect::<Vec<&str>>();
+ if setting.len() == 2 {
+ Ok((setting[0], setting[1]))
+ } else {
+ HError::config_error(line.to_string())
+ }
+
+ }
+
+ pub fn animate(&self) -> bool {
+ self.animation
+ }
+
+ pub fn show_hidden(&self) -> bool {
+ self.show_hidden
+ }
+}
diff --git a/src/dirty.rs b/src/dirty.rs
index fe6e4d1..14c6245 100644
--- a/src/dirty.rs
+++ b/src/dirty.rs
@@ -1,22 +1,38 @@
+use std::sync::{Arc, RwLock};
+use std::hash::{Hash, Hasher};
+
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct DirtyBit(bool);
-pub trait Dirtyable {
- fn get_bit(&self) -> &DirtyBit;
- fn get_bit_mut(&mut self) -> &mut DirtyBit;
+#[derive(Debug)]
+pub struct AsyncDirtyBit(pub Arc<RwLock<DirtyBit>>);
- fn is_dirty(&self) -> bool {
- self.get_bit().0
+impl PartialEq for AsyncDirtyBit {
+ fn eq(&self, other: &AsyncDirtyBit) -> bool {
+ *self.0.read().unwrap() == *other.0.read().unwrap()
}
+}
- fn set_dirty(&mut self) {
- self.get_bit_mut().0 = true;
+impl Eq for AsyncDirtyBit {}
+
+impl Hash for AsyncDirtyBit {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.0.read().unwrap().hash(state)
}
- fn set_clean(&mut self) {
- self.get_bit_mut().0 = false;
+}
+
+impl Clone for AsyncDirtyBit {
+ fn clone(&self) -> Self {
+ AsyncDirtyBit(self.0.clone())
}
}
+pub trait Dirtyable {
+ fn is_dirty(&self) -> bool;
+ fn set_dirty(&mut self);
+ fn set_clean(&mut self);
+}
+
impl DirtyBit {
pub fn new() -> DirtyBit {
@@ -24,12 +40,33 @@ impl DirtyBit {
}
}
+impl AsyncDirtyBit {
+ pub fn new() -> AsyncDirtyBit {
+ AsyncDirtyBit(Arc::new(RwLock::new(DirtyBit::new())))
+ }
+}
+
impl Dirtyable for DirtyBit {
- fn get_bit(&self) -> &DirtyBit {
- self
+ fn is_dirty(&self) -> bool {
+ self.0
+ }
+ fn set_dirty(&mut self) {
+ self.0 = true;
}
- fn get_bit_mut(&mut self) -> &mut DirtyBit {
- self
+ fn set_clean(&mut self) {
+ self.0 = false;
+ }
+}
+
+impl Dirtyable for AsyncDirtyBit {
+ fn is_dirty(&self) -> bool {
+ self.0.read().unwrap().is_dirty()
+ }
+ fn set_dirty(&mut self) {
+ self.0.write().unwrap().set_dirty();
+ }
+ fn set_clean(&mut self) {
+ self.0.write().unwrap().set_clean();
}
}
diff --git a/src/fail.rs b/src/fail.rs
index 6cf4afe..15a8b2c 100644
--- a/src/fail.rs
+++ b/src/fail.rs
@@ -1,6 +1,6 @@
use failure;
use failure::Fail;
-use failure::Backtrace;
+//use failure::Backtrace;
use termion::event::Key;
@@ -11,36 +11,46 @@ use crate::foldview::LogEntry;
pub type HResult<T> = Result<T, HError>;
-#[derive(Fail, Debug)]
+#[derive(Fail, Debug, Clone)]
pub enum HError {
- #[fail(display = "IO error: {} ", error)]
- IoError{#[cause] error: std::io::Error, backtrace: Backtrace},
+ #[fail(display = "IO error: {} ", _0)]
+ IoError(String),
#[fail(display = "Mutex failed")]
- MutexError(Backtrace),
+ MutexError,
#[fail(display = "Can't lock!")]
- TryLockError(Backtrace),
+ TryLockError,
#[fail(display = "Channel failed: {}", error)]
ChannelTryRecvError{#[cause] error: std::sync::mpsc::TryRecvError},
#[fail(display = "Channel failed: {}", error)]
ChannelRecvError{#[cause] error: std::sync::mpsc::RecvError},
#[fail(display = "Channel failed")]
- ChannelSendError(Backtrace),
+ ChannelSendError,
#[fail(display = "Previewer failed on file: {}", file)]
- PreviewFailed{file: String, backtrace: Backtrace},
+ PreviewFailed{file: String},
#[fail(display = "StalePreviewer for file: {}", file)]
StalePreviewError{file: String},
#[fail(display = "Accessed stale value")]
- StaleError(Backtrace),
- #[fail(display = "Failed: {}", error)]
- Error{#[cause] error: failure::Error , backtrace: Backtrace},
+ StaleError,
+ #[fail(display = "Failed: {}", _0)]
+ Error(String),
#[fail(display = "Was None!")]
- NoneError(Backtrace),
+ NoneError,
#[fail(display = "Not ready yet!")]
- WillBeNotReady(Backtrace),
+ WillBeNotReady,
+ #[fail(display = "Not ready yet!")]
+ AsyncNotReadyError,
+ #[fail(display = "Async is stale!")]
+ AsyncStaleError,
+ #[fail(display = "Value has already been taken!")]
+ AsyncAlreadyTakenError,
+ #[fail(display = "Async has already been started!")]
+ AsyncAlreadyStartedError,
+ #[fail(display = "Async Error: {}", _0)]
+ AsyncError(String),
#[fail(display = "No widget found")]
- NoWidgetError(Backtrace),
+ NoWidgetError,
#[fail(display = "Path: {:?} not in this directory: {:?}", path, dir)]
- WrongDirectoryError{ path: PathBuf, dir: PathBuf , backtrace: Backtrace},
+ WrongDirectoryError{ path: PathBuf, dir: PathBuf},
#[fail(display = "Widget finnished")]
PopupFinnished,
#[fail(display = "No completions found")]
@@ -48,7 +58,7 @@ pub enum HError {
#[fail(display = "No more history")]
NoHistoryError,
#[fail(display = "No core for widget")]
- NoWidgetCoreError(Backtrace),
+ NoWidgetCoreError,
#[fail(display = "No header for widget")]
NoHeaderError,
#[fail(display = "You wanted this!")]
@@ -56,11 +66,11 @@ pub enum HError {
#[fail(display = "HBox ratio mismatch: {} widgets, ratio is {:?}", wnum, ratio)]
HBoxWrongRatioError{ wnum: usize, ratio: Vec<usize> },
#[fail(display = "Got wrong widget: {}! Wanted: {}", got, wanted)]
- WrongWidgetError{got: String, wanted: String, backtrace: Backtrace},
+ WrongWidgetError{got: String, wanted: String},
#[fail(display = "Strip Prefix Error: {}", error)]
- StripPrefixError{#[cause] error: std::path::StripPrefixError, backtrace: Backtrace},
- #[fail(display = "INofify failed: {}", error)]
- INotifyError{#[cause] error: notify::Error, backtrace: Backtrace},
+ StripPrefixError{#[cause] error: std::path::StripPrefixError},
+ #[fail(display = "INofify failed: {}", _0)]
+ INotifyError(String),
#[fail(display = "Tags not loaded yet")]
TagsNotLoadedYetError,
#[fail(display = "Input cancelled!")]
@@ -71,12 +81,20 @@ pub enum HError {
WidgetUndefinedKeyError{key: Key},
#[fail(display = "Terminal has been resized!")]
TerminalResizedError,
+ #[fail(display = "Widget has been resized!")]
+ WidgetResizedError,
#[fail(display = "{}", _0)]
- Log(String)
+ Log(String),
+ #[fail(display = "Metadata already processed")]
+ MetadataProcessedError,
+ #[fail(display = "No files to take from widget")]
+ WidgetNoFilesError,
+ #[fail(display = "Invalid line in settings file: {}", _0)]
+ ConfigLineError(String),
}
impl HError {
- pub fn log(log: String) -> HResult<()> {
+ pub fn log<T>(log: String) -> HResult<T> {
Err(HError::Log(log))
}
pub fn quit() -> HResult<()> {
@@ -86,12 +104,12 @@ impl HError {
Err(HError::HBoxWrongRatioError{ wnum: wnum, ratio: ratio })
}
pub fn no_widget<T>() -> HResu