mirror of https://github.com/bobwen-dev/hunter
Merge branch 'master' into evil
This commit is contained in:
commit
7f70fa2904
|
@ -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
|
||||
|
|
|
@ -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.
|
|
@ -0,0 +1,128 @@
|
|||
hunter
|
||||
======
|
||||
|
||||

|
||||
|
||||
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 |
|
Binary file not shown.
After Width: | Height: | Size: 105 KiB |
|
@ -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
|
|
@ -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()?;
|
||||
|
||||
if !bm_file.exists() {
|
||||
self.import().log();
|
||||
}
|
||||
|
||||
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);
|
||||
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();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
77
src/dirty.rs
77
src/dirty.rs
|
@ -1,20 +1,36 @@
|
|||
use std::sync::{Arc, RwLock};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
|
||||
pub struct DirtyBit(bool);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AsyncDirtyBit(pub Arc<RwLock<DirtyBit>>);
|
||||
|
||||
impl PartialEq for AsyncDirtyBit {
|
||||
fn eq(&self, other: &AsyncDirtyBit) -> bool {
|
||||
*self.0.read().unwrap() == *other.0.read().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for AsyncDirtyBit {}
|
||||
|
||||
impl Hash for AsyncDirtyBit {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.read().unwrap().hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for AsyncDirtyBit {
|
||||
fn clone(&self) -> Self {
|
||||
AsyncDirtyBit(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Dirtyable {
|
||||
fn get_bit(&self) -> &DirtyBit;
|
||||
fn get_bit_mut(&mut self) -> &mut DirtyBit;
|
||||
|
||||
fn is_dirty(&self) -> bool {
|
||||
self.get_bit().0
|
||||
}
|
||||
|
||||
fn set_dirty(&mut self) {
|
||||
self.get_bit_mut().0 = true;
|
||||
}
|
||||
fn set_clean(&mut self) {
|
||||
self.get_bit_mut().0 = false;
|
||||
}
|
||||
fn is_dirty(&self) -> bool;
|
||||
fn set_dirty(&mut self);
|
||||
fn set_clean(&mut self);
|
||||
}
|
||||
|
||||
|
||||
|
@ -24,12 +40,33 @@ impl DirtyBit {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
impl Dirtyable for DirtyBit {
|
||||
fn get_bit(&self) -> &DirtyBit {
|
||||
self
|
||||
}
|
||||
fn get_bit_mut(&mut self) -> &mut DirtyBit {
|
||||
self
|
||||
impl AsyncDirtyBit {
|
||||
pub fn new() -> AsyncDirtyBit {
|
||||
AsyncDirtyBit(Arc::new(RwLock::new(DirtyBit::new())))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Dirtyable for DirtyBit {
|
||||
fn is_dirty(&self) -> bool {
|
||||
self.0
|
||||
}
|
||||
fn set_dirty(&mut self) {
|
||||
self.0 = true;
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
130
src/fail.rs
130
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>() -> HResult<T> {
|
||||
Err(HError::NoWidgetError(Backtrace::new()))
|
||||
Err(HError::NoWidgetError)
|
||||
}
|
||||
pub fn wrong_widget<T>(got: &str, wanted: &str) -> HResult<T> {
|
||||
Err(HError::WrongWidgetError{ got: got.to_string(),
|
||||
wanted: wanted.to_string(),
|
||||
backtrace: Backtrace::new()})
|
||||
wanted: wanted.to_string() })
|
||||
|
||||
}
|
||||
pub fn popup_finnished<T>() -> HResult<T> {
|
||||
Err(HError::PopupFinnished)
|
||||
|
@ -110,21 +128,53 @@ impl HError {
|
|||
}
|
||||
pub fn wrong_directory<T>(path: PathBuf, dir: PathBuf) -> HResult<T> {
|
||||
Err(HError::WrongDirectoryError{ path: path,
|
||||
dir: dir,
|
||||
backtrace: Backtrace::new() })
|
||||
dir: dir })
|
||||
|
||||
}
|
||||
pub fn preview_failed<T>(file: &crate::files::File) -> HResult<T> {
|
||||
let name = file.name.clone();
|
||||
Err(HError::PreviewFailed{ file: name,
|
||||
backtrace: Backtrace::new() })
|
||||
Err(HError::PreviewFailed{ file: name })
|
||||
|
||||
}
|
||||
|
||||
pub fn terminal_resized<T>() -> HResult<T> {
|
||||
Err(HError::TerminalResizedError)
|
||||
}
|
||||
|
||||
pub fn widget_resized<T>() -> HResult<T> {
|
||||
Err(HError::WidgetResizedError)
|
||||
}
|
||||
|
||||
pub fn stale<T>() -> HResult<T> {
|
||||
Err(HError::StaleError(Backtrace::new()))
|
||||
Err(HError::StaleError)
|
||||
}
|
||||
|
||||
pub fn config_error<T>(line: String) -> HResult<T> {
|
||||
Err(HError::ConfigLineError(line))
|
||||
}
|
||||
|
||||
pub fn async_not_ready<T>() -> HResult<T> {
|
||||
Err(HError::AsyncNotReadyError)
|
||||
}
|
||||
|
||||
pub fn async_taken<T>() -> HResult<T> {
|
||||
Err(HError::AsyncAlreadyTakenError)
|
||||
}
|
||||
|
||||
pub fn async_error<T>(error: &HError) -> HResult<T> {
|
||||
Err(HError::AsyncError(format!("{}", error)))
|
||||
}
|
||||
|
||||
pub fn async_started<T>() -> HResult<T> {
|
||||
Err(HError::AsyncAlreadyStartedError)
|
||||
}
|
||||
|
||||
pub fn metadata_processed<T>() -> HResult<T> {
|
||||
Err(HError::MetadataProcessedError)
|
||||
}
|
||||
|
||||
pub fn no_files<T>() -> HResult<T> {
|
||||
Err(HError::WidgetNoFilesError)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -177,7 +227,7 @@ impl<T> ErrorLog for HResult<T> {
|
|||
impl From<std::io::Error> for HError {
|
||||
fn from(error: std::io::Error) -> Self {
|
||||
// dbg!(&error);
|
||||
let err = HError::IoError { error: error, backtrace: Backtrace::new() };
|
||||
let err = HError::IoError(format!("{}", error));
|
||||
put_log(&err).ok();
|
||||
err
|
||||
}
|
||||
|
@ -186,7 +236,7 @@ impl From<std::io::Error> for HError {
|
|||
impl From<failure::Error> for HError {
|
||||
fn from(error: failure::Error) -> Self {
|
||||
// dbg!(&error);
|
||||
let err = HError::Error { error: error, backtrace: Backtrace::new() };
|
||||
let err = HError::Error(format!("{}", error));
|
||||
put_log(&err).ok();
|
||||
err
|
||||
}
|
||||
|
@ -213,7 +263,7 @@ impl From<std::sync::mpsc::RecvError> for HError {
|
|||
impl<T> From<std::sync::mpsc::SendError<T>> for HError {
|
||||
fn from(error: std::sync::mpsc::SendError<T>) -> Self {
|
||||
dbg!(&error);
|
||||
let err = HError::ChannelSendError(Backtrace::new());
|
||||
let err = HError::ChannelSendError;
|
||||
put_log(&err).ok();
|
||||
err
|
||||
}
|
||||
|
@ -222,25 +272,25 @@ impl<T> From<std::sync::mpsc::SendError<T>> for HError {
|
|||
impl<T> From<std::sync::PoisonError<T>> for HError {
|
||||
fn from(_: std::sync::PoisonError<T>) -> Self {
|
||||
// dbg!("Poisoned Mutex");
|
||||
let err = HError::MutexError(Backtrace::new());
|
||||
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 {
|
||||
fn from(_error: std::sync::TryLockError<T>) -> Self {
|
||||
// dbg!(&error);
|
||||
let err = HError::TryLockError(Backtrace::new());
|
||||
let err = HError::TryLockError;
|
||||
put_log(&err).ok();
|
||||
err
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::option::NoneError> for HError {
|
||||
fn from(error: std::option::NoneError) -> Self {
|
||||
fn from(_error: std::option::NoneError) -> Self {
|
||||
//dbg!(&error);
|
||||
let err = HError::NoneError(Backtrace::new());
|
||||
let err = HError::NoneError;
|
||||
//put_log(&err).ok();
|
||||
err
|
||||
}
|
||||
|
@ -249,7 +299,7 @@ impl From<std::option::NoneError> for HError {
|
|||
impl From<std::path::StripPrefixError> for HError {
|
||||
fn from(error: std::path::StripPrefixError) -> Self {
|
||||
// dbg!(&error);
|
||||
let err = HError::StripPrefixError{error: error, backtrace: Backtrace::new() };
|
||||
let err = HError::StripPrefixError{error: error };
|
||||
put_log(&err).ok();
|
||||
err
|
||||
}
|
||||
|
@ -258,7 +308,7 @@ impl From<std::path::StripPrefixError> for HError {
|
|||
impl From<notify::Error> for HError {
|
||||
fn from(error: notify::Error) -> Self {
|
||||
// dbg!(&error);
|
||||
let err = HError::INotifyError{error: error, backtrace: Backtrace::new() };
|
||||
let err = HError::INotifyError(format!("{}", error));
|
||||
put_log(&err).ok();
|
||||
err
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
496
src/files.rs
496
src/files.rs
|
@ -1,14 +1,16 @@
|
|||
use std::cmp::{Ord, Ordering};
|
||||
use std::ops::Index;
|
||||
use std::fs::Metadata;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::os::unix::ffi::{OsStringExt, OsStrExt};
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
||||
use lscolors::LsColors;
|
||||
use mime_detective;
|
||||
use tree_magic;
|
||||
use users::{get_current_username,
|
||||
get_current_groupname,
|
||||
get_user_by_uid,
|
||||
|
@ -16,24 +18,50 @@ use users::{get_current_username,
|
|||
use chrono::TimeZone;
|
||||
use failure::Error;
|
||||
use notify::DebouncedEvent;
|
||||
use rayon::{ThreadPool, ThreadPoolBuilder};
|
||||
|
||||
use crate::fail::{HResult, HError};
|
||||
use crate::dirty::{DirtyBit, Dirtyable};
|
||||
|
||||
|
||||
use crate::fail::{HResult, HError, ErrorLog};
|
||||
use crate::dirty::{AsyncDirtyBit, DirtyBit, Dirtyable};
|
||||
use crate::preview::{Async, Stale};
|
||||
use crate::widget::Events;
|
||||
|
||||
|
||||
lazy_static! {
|
||||
static ref COLORS: LsColors = LsColors::from_env().unwrap();
|
||||
static ref TAGS: Mutex<(bool, Vec<PathBuf>)> = Mutex::new((false, vec![]));
|
||||
static ref TAGS: RwLock<(bool, Vec<PathBuf>)> = RwLock::new((false, vec![]));
|
||||
}
|
||||
|
||||
fn make_pool(sender: Option<Sender<Events>>) -> ThreadPool {
|
||||
let sender = Arc::new(Mutex::new(sender));
|
||||
ThreadPoolBuilder::new()
|
||||
.num_threads(8)
|
||||
.exit_handler(move |thread_num| {
|
||||
if thread_num == 0 {
|
||||
if let Ok(lock) = sender.lock() {
|
||||
if let Some(sender) = lock.as_ref() {
|
||||
sender.send(Events::WidgetReady).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.build()
|
||||
.expect("Failed to create thread pool")
|
||||
}
|
||||
|
||||
pub fn load_tags() -> HResult<()> {
|
||||
std::thread::spawn(|| -> HResult<()> {
|
||||
let tag_path = crate::paths::tagfile_path()?;
|
||||
|
||||
if !tag_path.exists() {
|
||||
import_tags().log();
|
||||
}
|
||||
|
||||
let tags = std::fs::read_to_string(tag_path)?;
|
||||
let mut tags = tags.lines().map(|f| PathBuf::from(f)).collect::<Vec<PathBuf>>();
|
||||
let mut tag_lock = TAGS.lock()?;
|
||||
let mut tags = tags.lines()
|
||||
.map(|f|
|
||||
PathBuf::from(f))
|
||||
.collect::<Vec<PathBuf>>();
|
||||
let mut tag_lock = TAGS.write()?;
|
||||
tag_lock.0 = true;
|
||||
tag_lock.1.append(&mut tags);
|
||||
Ok(())
|
||||
|
@ -41,28 +69,43 @@ pub fn load_tags() -> HResult<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn import_tags() -> HResult<()> {
|
||||
let mut ranger_tags = crate::paths::ranger_path()?;
|
||||
ranger_tags.push("tagged");
|
||||
|
||||
if ranger_tags.exists() {
|
||||
let tag_path = crate::paths::tagfile_path()?;
|
||||
std::fs::copy(ranger_tags, tag_path)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_tag(path: &PathBuf) -> HResult<bool> {
|
||||
tags_loaded()?;
|
||||
let tagged = TAGS.lock()?.1.contains(path);
|
||||
let tagged = TAGS.read()?.1.contains(path);
|
||||
Ok(tagged)
|
||||
}
|
||||
|
||||
pub fn tags_loaded() -> HResult<()> {
|
||||
let loaded = TAGS.lock()?.0;
|
||||
let loaded = TAGS.read()?.0;
|
||||
if loaded { Ok(()) }
|
||||
else { HError::tags_not_loaded() }
|
||||
}
|
||||
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
|
||||
pub struct Files {
|
||||
pub directory: File,
|
||||
pub files: Vec<File>,
|
||||
pub meta_upto: Option<usize>,
|
||||
pub meta_updated: bool,
|
||||
pub sort: SortBy,
|
||||
pub dirs_first: bool,
|
||||
pub reverse: bool,
|
||||
pub show_hidden: bool,
|
||||
pub filter: Option<String>,
|
||||
pub dirty: DirtyBit
|
||||
pub dirty: DirtyBit,
|
||||
pub dirty_meta: AsyncDirtyBit,
|
||||
}
|
||||
|
||||
impl Index<usize> for Files {
|
||||
|
@ -74,12 +117,16 @@ impl Index<usize> for Files {
|
|||
|
||||
|
||||
impl Dirtyable for Files {
|
||||
fn get_bit(&self) -> &DirtyBit {
|
||||
&self.dirty
|
||||
fn is_dirty(&self) -> bool {
|
||||
self.dirty.is_dirty()
|
||||
}
|
||||
|
||||
fn get_bit_mut(&mut self) -> &mut DirtyBit {
|
||||
&mut self.dirty
|
||||
fn set_dirty(&mut self) {
|
||||
self.dirty.set_dirty();
|
||||
}
|
||||
|
||||
fn set_clean(&mut self) {
|
||||
self.dirty.set_clean();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,6 +134,8 @@ impl Dirtyable for Files {
|
|||
impl Files {
|
||||
pub fn new_from_path(path: &Path) -> Result<Files, Error> {
|
||||
let direntries: Result<Vec<_>, _> = std::fs::read_dir(&path)?.collect();
|
||||
let dirty = DirtyBit::new();
|
||||
let dirty_meta = AsyncDirtyBit::new();
|
||||
|
||||
let files: Vec<_> = direntries?
|
||||
.iter()
|
||||
|
@ -94,23 +143,30 @@ impl Files {
|
|||
let name = file.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
let path = file.path();
|
||||
File::new(&name, path)
|
||||
File::new(&name,
|
||||
path,
|
||||
Some(dirty_meta.clone()))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut files = Files {
|
||||
directory: File::new_from_path(&path)?,
|
||||
directory: File::new_from_path(&path, None)?,
|
||||
files: files,
|
||||
meta_upto: None,
|
||||
meta_updated: false,
|
||||
sort: SortBy::Name,
|
||||
dirs_first: true,
|
||||
reverse: false,
|
||||
show_hidden: true,
|
||||
filter: None,
|
||||
dirty: DirtyBit::new()
|
||||
dirty: dirty,
|
||||
dirty_meta: dirty_meta,
|
||||
};
|
||||
|
||||
files.sort();
|
||||
|
||||
|
||||
|
||||
if files.files.len() == 0 {
|
||||
files.files = vec![File::new_placeholder(&path)?];
|
||||
}
|
||||
|
@ -118,8 +174,12 @@ impl Files {
|
|||
Ok(files)
|
||||
}
|
||||
|
||||
pub fn new_from_path_cancellable(path: &Path, stale: Arc<Mutex<bool>>) -> Result<Files, Error> {
|
||||
pub fn new_from_path_cancellable(path: &Path,
|
||||
stale: Stale)
|
||||
-> Result<Files, Error> {
|
||||
let direntries: Result<Vec<_>, _> = std::fs::read_dir(&path)?.collect();
|
||||
let dirty = DirtyBit::new();
|
||||
let dirty_meta = AsyncDirtyBit::new();
|
||||
|
||||
let files: Vec<_> = direntries?
|
||||
.iter()
|
||||
|
@ -130,7 +190,10 @@ impl Files {
|
|||
let name = file.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
let path = file.path();
|
||||
Some(File::new(&name, path))
|
||||
Some(File::new_with_stale(&name,
|
||||
path,
|
||||
Some(dirty_meta.clone()),
|
||||
stale.clone()))
|
||||
}
|
||||
})
|
||||
.fuse()
|
||||
|
@ -144,14 +207,17 @@ impl Files {
|
|||
}
|
||||
|
||||
let mut files = Files {
|
||||
directory: File::new_from_path(&path)?,
|
||||
directory: File::new_from_path(&path, None)?,
|
||||
files: files,
|
||||
meta_upto: None,
|
||||
meta_updated: false,
|
||||
sort: SortBy::Name,
|
||||
dirs_first: true,
|
||||
reverse: false,
|
||||
show_hidden: true,
|
||||
filter: None,
|
||||
dirty: DirtyBit::new()
|
||||
dirty: dirty,
|
||||
dirty_meta: dirty_meta,
|
||||
};
|
||||
|
||||
files.sort();
|
||||
|
@ -163,13 +229,46 @@ impl Files {
|
|||
Ok(files)
|
||||
}
|
||||
|
||||
pub fn get_file_mut(&mut self, index: usize) -> Option<&mut File> {
|
||||
let filter = self.filter.clone();
|
||||
let show_hidden = self.show_hidden;
|
||||
|
||||
let file = self.files
|
||||
.iter_mut()
|
||||
.filter(|f| !(filter.is_some() &&
|
||||
!f.name.contains(filter.as_ref().unwrap())))
|
||||
.filter(|f| !(!show_hidden && f.name.starts_with(".")))
|
||||
.nth(index);
|
||||
file
|
||||
}
|
||||
|
||||
pub fn get_files(&self) -> Vec<&File> {
|
||||
self.files
|
||||
.iter()
|
||||
.filter(|f| !(self.filter.is_some() &&
|
||||
!f.name.contains(self.filter.as_ref().unwrap())))
|
||||
.filter(|f| !(!self.show_hidden && f.name.starts_with(".")))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_files_mut(&mut self) -> Vec<&mut File> {
|
||||
let filter = self.filter.clone();
|
||||
let show_hidden = self.show_hidden;
|
||||
self.files
|
||||
.iter_mut()
|
||||
.filter(|f| !(filter.is_some() &&
|
||||
!f.name.contains(filter.as_ref().unwrap())))
|
||||
.filter(|f| !(!show_hidden && f.name.starts_with(".")))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn sort(&mut self) {
|
||||
match self.sort {
|
||||
SortBy::Name => self
|
||||
.files
|
||||
.sort_by(|a, b| alphanumeric_sort::compare_str(&a.name, &b.name)),
|
||||
SortBy::Size => {
|
||||
self.meta_all();
|
||||
self.meta_all_sync().log();
|
||||
self.files.sort_by(|a, b| {
|
||||
if a.meta().unwrap().size() == b.meta().unwrap().size() {
|
||||
return alphanumeric_sort::compare_str(&b.name, &a.name);
|
||||
|
@ -178,7 +277,7 @@ impl Files {
|
|||
});
|
||||
}
|
||||
SortBy::MTime => {
|
||||
self.meta_all();
|
||||
self.meta_all_sync().log();
|
||||
self.files.sort_by(|a, b| {
|
||||
if a.meta().unwrap().mtime() == b.meta().unwrap().mtime() {
|
||||
return alphanumeric_sort::compare_str(&a.name, &b.name);
|
||||
|
@ -227,25 +326,37 @@ impl Files {
|
|||
self.show_hidden = !self.show_hidden
|
||||
}
|
||||
|
||||
pub fn reload_files(&mut self) {
|
||||
let dir = self.directory.clone();
|
||||
let files = Files::new_from_path(&dir.path()).unwrap();
|
||||
let files = files
|
||||
.files
|
||||
.into_iter()
|
||||
.skip_while(|f| f.name.starts_with(".") && !self.show_hidden )
|
||||
.collect();
|
||||
|
||||
self.files = files;
|
||||
self.set_dirty();
|
||||
pub fn replace_file(&mut self,
|
||||
old: Option<&File>,
|
||||
new: Option<File>) -> HResult<()> {
|
||||
let (tag, selected) = if let Some(old) = old {
|
||||
if let Some(old) = self.find_file_with_path(&old.path) {
|
||||
(old.tag, old.selected)
|
||||
} else {
|
||||
(None, false)
|
||||
}
|
||||
} else {
|
||||
(None, false)
|
||||
};
|
||||
old.map(|old| self.files.remove_item(old));
|
||||
new.map(|mut new| {
|
||||
new.tag = tag;
|
||||
new.selected = selected;
|
||||
self.files.push(new);
|
||||
});
|
||||
self.sort();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_event(&mut self, event: &DebouncedEvent) -> HResult<()> {
|
||||
pub fn handle_event(&mut self,
|
||||
event: &DebouncedEvent) -> HResult<()> {
|
||||
match event {
|
||||
DebouncedEvent::Create(path) => {
|
||||
self.path_in_here(&path)?;
|
||||
let file = File::new_from_path(&path)?;
|
||||
let file = File::new_from_path(&path,
|
||||
Some(self.dirty_meta.clone()))?;
|
||||
self.files.push(file);
|
||||
self.sort();
|
||||
},
|
||||
DebouncedEvent::Write(path) | DebouncedEvent::Chmod(path) => {
|
||||
self.path_in_here(&path)?;
|
||||
|
@ -262,6 +373,7 @@ impl Files {
|
|||
let mut file = self.find_file_with_path(&old_path)?;
|
||||
file.name = new_path.file_name()?.to_string_lossy().to_string();
|
||||
file.path = new_path.into();
|
||||
file.reload_meta()?;
|
||||
},
|
||||
DebouncedEvent::Error(err, path) => {
|
||||
dbg!(err);
|
||||
|
@ -274,12 +386,12 @@ impl Files {
|
|||
}
|
||||
|
||||
pub fn path_in_here(&self, path: &Path) -> HResult<bool> {
|
||||
let dir = self.directory.path();
|
||||
let dir = &self.directory.path;
|
||||
let path = if path.is_dir() { path } else { path.parent().unwrap() };
|
||||
if dir == path {
|
||||
Ok(true)
|
||||
} else {
|
||||
HError::wrong_directory(path.into(), dir)?
|
||||
HError::wrong_directory(path.into(), dir.to_path_buf())?
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -287,17 +399,62 @@ impl Files {
|
|||
self.files.iter_mut().find(|file| file.path == path)
|
||||
}
|
||||
|
||||
pub fn meta_all(&mut self) {
|
||||
let len = self.files.len();
|
||||
self.meta_upto(len);
|
||||
pub fn meta_all_sync(&mut self) -> HResult<()> {
|
||||
for file in self.files.iter_mut() {
|
||||
if !file.meta_processed {
|
||||
file.meta_sync().log();
|
||||
}
|
||||
}
|
||||
self.set_dirty();
|
||||
self.meta_updated = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn meta_upto(&mut self, to: usize) {
|
||||
for file in self.files.iter_mut().take(to) {
|
||||
file.get_meta().ok();
|
||||
}
|
||||
pub fn meta_all(&mut self) {
|
||||
let len = self.len();
|
||||
self.meta_upto(len, None);
|
||||
}
|
||||
|
||||
pub fn meta_upto(&mut self, to: usize, sender: Option<Sender<Events>>) {
|
||||
let meta_files = if self.meta_upto > Some(to) {
|
||||
self.meta_upto.unwrap()
|
||||
} else {
|
||||
if to > self.len() {
|
||||
self.len()
|
||||
} else {
|
||||
to
|
||||
}
|
||||
};
|
||||
|
||||
if self.meta_upto >= Some(meta_files) && !self.dirty_meta.is_dirty() { return }
|
||||
|
||||
self.set_dirty();
|
||||
self.dirty_meta.set_clean();
|
||||
|
||||
let meta_pool = make_pool(sender.clone());
|
||||
let show_hidden = self.show_hidden;
|
||||
|
||||
for file in self.files
|
||||
.iter_mut()
|
||||
.filter(|f| !(!show_hidden && f.name.starts_with(".")))
|
||||
.take(meta_files) {
|
||||
if !file.meta_processed {
|
||||
file.take_meta(&meta_pool, &mut self.meta_updated).ok();
|
||||
}
|
||||
if file.is_dir() {
|
||||
file.take_dirsize(&meta_pool, &mut self.meta_updated).ok();
|
||||
}
|
||||
}
|
||||
|
||||
self.meta_upto = Some(meta_files);
|
||||
}
|
||||
|
||||
pub fn meta_set_fresh(&self) -> HResult<()> {
|
||||
self.files.get(0)?.meta.set_fresh()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub fn set_filter(&mut self, filter: Option<String>) {
|
||||
self.filter = filter;
|
||||
self.set_dirty();
|
||||
|
@ -308,15 +465,7 @@ impl Files {
|
|||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
match &self.filter {
|
||||
None => self.files.len(),
|
||||
Some(filter) => {
|
||||
self.files
|
||||
.iter()
|
||||
.filter(|f| f.name.contains(filter))
|
||||
.count()
|
||||
}
|
||||
}
|
||||
self.get_files().len()
|
||||
}
|
||||
|
||||
pub fn get_selected(&self) -> Vec<&File> {
|
||||
|
@ -364,86 +513,224 @@ impl Hash for File {
|
|||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.name.hash(state);
|
||||
self.path.hash(state);
|
||||
self.selected.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for File {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct File {
|
||||
pub name: String,
|
||||
pub path: PathBuf,
|
||||
pub kind: Kind,
|
||||
pub dirsize: Option<Async<usize>>,
|
||||
pub target: Option<PathBuf>,
|
||||
pub color: Option<lscolors::Color>,
|
||||
pub meta: Option<std::fs::Metadata>,
|
||||
pub meta: Async<Metadata>,
|
||||
pub dirty_meta: Option<AsyncDirtyBit>,
|
||||
pub meta_processed: bool,
|
||||
pub selected: bool,
|
||||
pub tag: Option<bool>
|
||||
// flags: Option<String>,
|
||||
}
|
||||
|
||||
impl File {
|
||||
pub fn new(
|
||||
name: &str,
|
||||
path: PathBuf,
|
||||
) -> File {
|
||||
dirty_meta: Option<AsyncDirtyBit>) -> File {
|
||||
let tag = check_tag(&path).ok();
|
||||
let meta = File::make_async_meta(&path, dirty_meta.clone(), None);
|
||||
let dirsize = if path.is_dir() {
|
||||
Some(File::make_async_dirsize(&path, dirty_meta.clone(), None))
|
||||
} else { None };
|
||||
|
||||
File {
|
||||
name: name.to_string(),
|
||||
kind: if path.is_dir() { Kind::Directory } else { Kind::File },
|
||||
path: path,
|
||||
dirsize: dirsize,
|
||||
target: None,
|
||||
meta: None,
|
||||
meta: meta,
|
||||
meta_processed: false,
|
||||
dirty_meta: dirty_meta,
|
||||
color: None,
|
||||
selected: false,
|
||||
tag: tag,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_from_path(path: &Path) -> HResult<File> {
|
||||
pub fn new_with_stale(name: &str,
|
||||
path: PathBuf,
|
||||
dirty_meta: Option<AsyncDirtyBit>,
|
||||
stale: Stale) -> File {
|
||||
let tag = check_tag(&path).ok();
|
||||
let meta = File::make_async_meta(&path,
|
||||
dirty_meta.clone(),
|
||||
Some(stale.clone()));
|
||||
let dirsize = if path.is_dir() {
|
||||
Some(File::make_async_dirsize(&path,
|
||||
dirty_meta.clone(),
|
||||
Some(stale)))
|
||||
} else { None };
|
||||
|
||||
File {
|
||||
name: name.to_string(),
|
||||
kind: if path.is_dir() { Kind::Directory } else { Kind::File },
|
||||
path: path,
|
||||
dirsize: dirsize,
|
||||
target: None,
|
||||
meta: meta,
|
||||
meta_processed: false,
|
||||
dirty_meta: dirty_meta,
|
||||
color: None,
|
||||
selected: false,
|
||||
tag: tag,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_from_path(path: &Path,
|
||||
dirty_meta: Option<AsyncDirtyBit>) -> HResult<File> {
|
||||
let pathbuf = path.to_path_buf();
|
||||
let name = path
|
||||
.file_name()
|
||||
.map(|name| name.to_string_lossy().to_string())
|
||||
.unwrap_or("/".to_string());
|
||||
|
||||
Ok(File::new(&name, pathbuf))
|
||||
Ok(File::new(&name, pathbuf, dirty_meta))
|
||||
}
|
||||
|
||||
pub fn new_placeholder(path: &Path) -> Result<File, Error> {
|
||||
let mut file = File::new_from_path(path)?;
|
||||
let mut file = File::new_from_path(path, None)?;
|
||||
file.name = "<empty>".to_string();
|
||||
file.kind = Kind::Placeholder;
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
pub fn meta(&self) -> HResult<std::fs::Metadata> {
|
||||
match &self.meta {
|
||||
Some(meta) => Ok(meta.clone()),
|
||||
None => { Ok(std::fs::symlink_metadata(&self.path)?) }
|
||||
}
|
||||
pub fn meta_sync(&mut self) -> HResult<()> {
|
||||
let stale = self.meta.get_stale();
|
||||
let meta = std::fs::metadata(&self.path)?;
|
||||
self.meta = Async::new_with_value(meta);
|
||||
self.meta.put_stale(stale);
|
||||
self.process_meta()
|
||||
}
|
||||
|
||||
pub fn get_meta(&mut self) -> HResult<()> {
|
||||
if let Some(_) = self.meta { return Ok(()) }
|
||||
pub fn make_async_meta(path: &PathBuf,
|
||||
dirty_meta: Option<AsyncDirtyBit>,
|
||||
stale_preview: Option<Stale>) -> Async<Metadata> {
|
||||
let path = path.clone();
|
||||
|
||||
let meta = std::fs::symlink_metadata(&self.path)?;
|
||||
let color = self.get_color(&meta);
|
||||
let target = if meta.file_type().is_symlink() {
|
||||
self.path.read_link().ok()
|
||||
} else { None };
|
||||
let meta_closure = Box::new(move |stale: Stale| {
|
||||
if stale.is_stale()? { HError::stale()? }
|
||||
Ok(std::fs::symlink_metadata(&path)?)
|
||||
});
|
||||
|
||||
self.meta = Some(meta);
|
||||
self.color = color;
|
||||
self.target = target;
|
||||
let mut meta = match stale_preview {
|
||||
Some(stale) => Async::new_with_stale(meta_closure, stale),
|
||||
None => Async::new(meta_closure)
|
||||
};
|
||||
if let Some(dirty_meta) = dirty_meta {
|
||||
meta.on_ready(Box::new(move || {
|
||||
let mut dirty_meta = dirty_meta.clone();
|
||||
dirty_meta.set_dirty();
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
meta
|
||||
}
|
||||
|
||||
pub fn make_async_dirsize(path: &PathBuf,
|
||||
dirty_meta: Option<AsyncDirtyBit>,
|
||||
stale_preview: Option<Stale>) -> Async<usize> {
|
||||
let path = path.clone();
|
||||
|
||||
let dirsize_closure = Box::new(move |stale: Stale| {
|
||||
if stale.is_stale()? { HError::stale()? }
|
||||
Ok(std::fs::read_dir(&path)?.count())
|
||||
});
|
||||
|
||||
let mut dirsize = match stale_preview {
|
||||
Some(stale) => Async::new_with_stale(dirsize_closure, stale),
|
||||
None => Async::new(dirsize_closure)
|
||||
};
|
||||
|
||||
if let Some(dirty_meta) = dirty_meta {
|
||||
dirsize.on_ready(Box::new(move || {
|
||||
let mut dirty_meta = dirty_meta.clone();
|
||||
dirty_meta.set_dirty();
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
dirsize
|
||||
}
|
||||
|
||||
pub fn meta(&self) -> HResult<&Metadata> {
|
||||
self.meta.get()
|
||||
}
|
||||
|
||||
fn take_dirsize(&mut self,
|
||||
pool: &ThreadPool,
|
||||
meta_updated: &mut bool) -> HResult<()> {
|
||||
let dirsize = self.dirsize.as_mut()?;
|
||||
if let Ok(_) = dirsize.value { return Ok(()) }
|
||||
|
||||
match dirsize.take_async() {
|
||||
Ok(_) => { *meta_updated = true; },
|
||||
Err(HError::AsyncNotReadyError) => { dirsize.run_pooled(&*pool).ok(); },
|
||||
Err(HError::AsyncAlreadyTakenError) => {},
|
||||
Err(HError::NoneError) => {},
|
||||
err @ Err(_) => { err?; }
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn take_meta(&mut self,
|
||||
pool: &ThreadPool,
|
||||
meta_updated: &mut bool) -> HResult<()> {
|
||||
if self.meta_processed { return Ok(()) }
|
||||
|
||||
match self.meta.take_async() {
|
||||
Ok(_) => { *meta_updated = true; },
|
||||
Err(HError::AsyncNotReadyError) => { self.meta.run_pooled(&*pool).ok(); },
|
||||
Err(HError::AsyncAlreadyTakenError) => {},
|
||||
Err(HError::NoneError) => {},
|
||||
err @ Err(_) => { err?; }
|
||||
}
|
||||
|
||||
self.process_meta()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_meta(&mut self) -> HResult<()> {
|
||||
if let Ok(meta) = self.meta.get() {
|
||||
let color = self.get_color(&meta);
|
||||
let target = if meta.file_type().is_symlink() {
|
||||
self.path.read_link().ok()
|
||||
} else { None };
|
||||
|
||||
self.color = color;
|
||||
self.target = target;
|
||||
self.meta_processed = true;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reload_meta(&mut self) -> HResult<()> {
|
||||
self.meta = None;
|
||||
self.get_meta()
|
||||
self.meta_processed = false;
|
||||
self.meta = File::make_async_meta(&self.path,
|
||||
self.dirty_meta.clone(),
|
||||
None);
|
||||
self.meta.run().log();
|
||||
|
||||
if self.dirsize.is_some() {
|
||||
self.dirsize
|
||||
= Some(File::make_async_dirsize(&self.path, self.dirty_meta.clone(), None));
|
||||
self.dirsize.as_mut()?.run().log();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_color(&self, meta: &std::fs::Metadata) -> Option<lscolors::Color> {
|
||||
|
@ -454,13 +741,8 @@ impl File {
|
|||
}
|
||||
|
||||
pub fn calculate_size(&self) -> HResult<(u64, String)> {
|
||||
if self.is_dir() {
|
||||
let dir_iterator = std::fs::read_dir(&self.path);
|
||||
match dir_iterator {
|
||||
Ok(dir_iterator) => return Ok((dir_iterator.count() as u64,
|
||||
"".to_string())),
|
||||
Err(_) => return Ok((0, "".to_string()))
|
||||
}
|
||||
if let Some(ref dirsize) = self.dirsize {
|
||||
return Ok((dirsize.value.clone()? as u64, "".to_string()))
|
||||
}
|
||||
|
||||
|
||||
|
@ -483,10 +765,12 @@ impl File {
|
|||
Ok((size, unit))
|
||||
}
|
||||
|
||||
pub fn get_mime(&self) -> Option<String> {
|
||||
let detective = mime_detective::MimeDetective::new().ok()?;
|
||||
let mime = detective.detect_filepath(&self.path).ok()?;
|
||||
Some(mime.type_().as_str().to_string())
|
||||
// pub fn get_mime(&self) -> String {
|
||||
// tree_magic::from_filepath(&self.path)
|
||||
// }
|
||||
|
||||
pub fn is_text(&self) -> bool {
|
||||
tree_magic::match_filepath("text/plain", &self.path)
|
||||
}
|
||||
|
||||
|
||||
|
@ -496,7 +780,7 @@ impl File {
|
|||
|
||||
pub fn parent_as_file(&self) -> HResult<File> {
|
||||
let pathbuf = self.parent()?;
|
||||
File::new_from_path(&pathbuf)
|
||||
File::new_from_path(&pathbuf, None)
|
||||
}
|
||||
|
||||
pub fn grand_parent(&self) -> Option<PathBuf> {
|
||||
|
@ -505,7 +789,7 @@ impl File {
|
|||
|
||||
pub fn grand_parent_as_file(&self) -> HResult<File> {
|
||||
let pathbuf = self.grand_parent()?;
|
||||
File::new_from_path(&pathbuf)
|
||||
File::new_from_path(&pathbuf, None)
|
||||
}
|
||||
|
||||
pub fn is_dir(&self) -> bool {
|
||||
|
@ -559,8 +843,8 @@ impl File {
|
|||
self.tag = Some(new_state);
|
||||
|
||||
match new_state {
|
||||
true => TAGS.lock()?.1.push(self.path.clone()),
|
||||
false => { TAGS.lock()?.1.remove_item(&self.path); },
|
||||
true => TAGS.write()?.1.push(self.path.clone()),
|
||||
false => { TAGS.write()?.1.remove_item(&self.path); },
|
||||
}
|
||||
self.save_tags()?;
|
||||
Ok(())
|
||||
|
@ -569,7 +853,7 @@ impl File {
|
|||
pub fn save_tags(&self) -> HResult<()> {
|
||||
std::thread::spawn(|| -> HResult<()> {
|
||||
let tagfile_path = crate::paths::tagfile_path()?;
|
||||
let tags = TAGS.lock()?.clone();
|
||||
let tags = TAGS.read()?.clone();
|
||||
let tags_str = tags.1.iter().map(|p| {
|
||||
let path = p.to_string_lossy().to_string();
|
||||
format!("{}\n", path)
|
||||
|
@ -642,7 +926,7 @@ impl File {
|
|||
if file_user.name() == cur_user {
|
||||
crate::term::color_green()
|
||||
} else {
|
||||
crate::term::color_yellow() };
|
||||
crate::term::color_red() };
|
||||
Some(format!("{}{}", color, file_user.name().to_string_lossy()))
|
||||
}
|
||||
|
||||
|
@ -655,13 +939,12 @@ impl File {
|
|||
if file_group.name() == cur_group {
|
||||
crate::term::color_green()
|
||||
} else {
|
||||
crate::term::color_yellow() };
|
||||
crate::term::color_red() };
|
||||
Some(format!("{}{}", color, file_group.name().to_string_lossy()))
|
||||
}
|
||||
|
||||
pub fn pretty_mtime(&self) -> Option<String> {
|
||||
if self.meta().is_err() { return None }
|
||||
//let time = chrono::DateTime::from_timestamp(self.mtime, 0);
|
||||
let time: chrono::DateTime<chrono::Local>
|
||||
= chrono::Local.timestamp(self.meta().unwrap().mtime(), 0);
|
||||
Some(time.format("%F %R").to_string())
|
||||
|
@ -719,7 +1002,6 @@ impl PathBufExt for PathBuf {
|
|||
if let Some(name) = self.file_name() {
|
||||
let mut name = name.as_bytes().to_vec();
|
||||
let mut quote = "\"".as_bytes().to_vec();
|
||||
//let mut quote_after = "\"".as_bytes().to_vec();
|
||||
let mut quoted = vec![];
|
||||
quoted.append(&mut quote.clone());
|
||||
quoted.append(&mut name);
|
||||
|
@ -761,14 +1043,10 @@ impl OsStrTools for OsStr {
|
|||
let pat = pat.as_bytes().to_vec();
|
||||
let pat_len = pat.len();
|
||||
|
||||
dbg!(&self);
|
||||
|
||||
let split_string = orig_string
|
||||
.windows(pat_len)
|
||||
.enumerate()
|
||||
.fold(Vec::new(), |mut split_pos, (i, chars)| {
|
||||
dbg!(&chars);
|
||||
dbg!(&split_pos);
|
||||
if chars == pat.as_slice() {
|
||||
if split_pos.len() == 0 {
|
||||
split_pos.push((0, i));
|
||||
|
@ -781,7 +1059,6 @@ impl OsStrTools for OsStr {
|
|||
split_pos
|
||||
}).iter()
|
||||
.map(|(start, end)| {
|
||||
//let orig_string = orig_string.clone();
|
||||
OsString::from_vec(orig_string[*start..*end]
|
||||
.to_vec()).replace(&OsString::from_vec(pat.clone()),
|
||||
&OsString::from(""))
|
||||
|
@ -816,8 +1093,6 @@ impl OsStrTools for OsStr {
|
|||
return vec![OsString::from(self)];
|
||||
}
|
||||
|
||||
dbg!(&self);
|
||||
|
||||
let pos = pos.unwrap();
|
||||
let string = self.as_bytes().to_vec();
|
||||
let from = from.as_bytes().to_vec();
|
||||
|
@ -826,9 +1101,6 @@ impl OsStrTools for OsStr {
|
|||
let lpart = OsString::from_vec(string[0..pos].to_vec());
|
||||
let rpart = OsString::from_vec(string[pos+fromlen..].to_vec());
|
||||
|
||||
dbg!(&lpart);
|
||||
dbg!(&rpart);
|
||||
|
||||
let mut result = vec![
|
||||
vec![lpart.trim_last_space()],
|
||||
to,
|
||||
|
|
|
@ -5,7 +5,7 @@ use chrono::{DateTime, Local};
|
|||
use crate::term;
|
||||
use crate::widget::Widget;
|
||||
use crate::listview::{ListView, Listable};
|
||||
use crate::fail::{HResult, HError, ErrorLog};
|
||||
use crate::fail::{HResult, HError};
|
||||
use crate::dirty::Dirtyable;
|
||||
|
||||
pub type LogView = ListView<Vec<LogEntry>>;
|
||||
|
|
|
@ -0,0 +1,366 @@
|
|||
use notify::{INotifyWatcher, Watcher, DebouncedEvent, RecursiveMode};
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::sync::mpsc::{channel, Sender, Receiver};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::time::Duration;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::preview::{Async, Stale};
|
||||
use crate::files::{Files, File, SortBy};
|
||||
use crate::widget::Events;
|
||||
use crate::fail::{HResult, HError, ErrorLog};
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DirSettings {
|
||||
sort: SortBy,
|
||||
dirs_first: bool,
|
||||
reverse: bool,
|
||||
show_hidden: bool,
|
||||
filter: Option<String>,
|
||||
}
|
||||
|
||||
impl DirSettings {
|
||||
fn new() -> DirSettings {
|
||||
DirSettings {
|
||||
sort: SortBy::Name,
|
||||
dirs_first: true,
|
||||
reverse: false,
|
||||
show_hidden: true,
|
||||
filter: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TabSettings {
|
||||
selection: Option<File>,
|
||||
multi_selections: Vec<File>,
|
||||
dir_settings: DirSettings,
|
||||
}
|
||||
|
||||
impl TabSettings {
|
||||
fn new() -> TabSettings {
|
||||
TabSettings {
|
||||
selection: None,
|
||||
multi_selections: vec![],
|
||||
dir_settings: DirSettings::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl std::fmt::Debug for FsCache {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(formatter,
|
||||
"{:?}\n{:?}\n{:?}",
|
||||
self.tab_settings,
|
||||
self.watched_dirs,
|
||||
self.files)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Sync for FsCache {}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FsCache {
|
||||
files: Arc<RwLock<HashMap<File, Files>>>,
|
||||
pub tab_settings: Arc<RwLock<HashMap<File, TabSettings>>>,
|
||||
watched_dirs: Arc<RwLock<HashSet<File>>>,
|
||||
watcher: Arc<RwLock<INotifyWatcher>>,
|
||||
pub fs_changes: Arc<RwLock<Vec<(File, Option<File>, Option<File>)>>>,
|
||||
sender: Sender<Events>,
|
||||
}
|
||||
|
||||
impl FsCache {
|
||||
pub fn new(sender: Sender<Events>) -> FsCache {
|
||||
let (tx_fs_event, rx_fs_event) = channel();
|
||||
let watcher = INotifyWatcher::new(tx_fs_event,
|
||||
Duration::from_secs(2)).unwrap();
|
||||
|
||||
|
||||
let fs_cache = FsCache {
|
||||
files: Arc::new(RwLock::new(HashMap::new())),
|
||||
tab_settings: Arc::new(RwLock::new(HashMap::new())),
|
||||
watched_dirs: Arc::new(RwLock::new(HashSet::new())),
|
||||
watcher: Arc::new(RwLock::new(watcher)),
|
||||
fs_changes: Arc::new(RwLock::new(vec![])),
|
||||
sender: sender.clone(),
|
||||
};
|
||||
|
||||
watch_fs(rx_fs_event,
|
||||
fs_cache.files.clone(),
|
||||
fs_cache.fs_changes.clone(),
|
||||
sender.clone());
|
||||
|
||||
fs_cache
|
||||
}
|
||||
|
||||
pub fn new_client(&self, settings: HashMap<File, TabSettings>) -> HResult<FsCache> {
|
||||
let mut cache = self.clone();
|
||||
cache.tab_settings = Arc::new(RwLock::new(settings));
|
||||
Ok(cache)
|
||||
}
|
||||
}
|
||||
|
||||
pub type CachedFiles = (Option<File>, Async<Files>);
|
||||
|
||||
impl FsCache {
|
||||
pub fn get_files(&self, dir: &File, stale: Stale) -> HResult<CachedFiles> {
|
||||
if self.files.read()?.contains_key(dir) {
|
||||
self.get_cached_files(dir)
|
||||
} else {
|
||||
self.add_watch(&dir).log();
|
||||
let dir = dir.clone();
|
||||
let selection = self.get_selection(&dir).ok();
|
||||
let cache = self.clone();
|
||||
let files = Async::new(Box::new(move |_| {
|
||||
let mut files = Files::new_from_path_cancellable(&dir.path, stale)?;
|
||||
FsCache::apply_settingss(&cache, &mut files).ok();
|
||||
Ok(files)
|
||||
}));
|
||||
Ok((selection, files))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_files_sync(&self, dir: &File) -> HResult<Files> {
|
||||
self.add_watch(&dir).log();
|
||||
let files = self.get_files(&dir, Stale::new())?.1;
|
||||
let mut files = files.wait()?;
|
||||
FsCache::apply_settingss(&self, &mut files).ok();
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
pub fn get_selection(&self, dir: &File) -> HResult<File> {
|
||||
Ok(self.tab_settings.read()?.get(&dir).as_ref()?.selection.as_ref()?.clone())
|
||||
}
|
||||
|
||||
pub fn save_settings(&self, files: &Files, selection: Option<File>) -> HResult<()> {
|
||||
let dir = files.directory.clone();
|
||||
let tab_settings = FsCache::extract_tab_settings(&files, selection);
|
||||
self.tab_settings.write()?.insert(dir, tab_settings);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn put_files(&self, files: &Files, selection: Option<File>) -> HResult<()> {
|
||||
let dir = files.directory.clone();
|
||||
|
||||
let tab_settings = FsCache::extract_tab_settings(&files, selection);
|
||||
|
||||
self.tab_settings.write()?.insert(dir.clone(), tab_settings);
|
||||
|
||||
// let mut file_cache = self.files.write()?;
|
||||
|
||||
// if file_cache.contains_key(&files.directory) {
|
||||
// if files.meta_updated {
|
||||
// let mut files = files.clone();
|
||||
// files.meta_updated = false;
|
||||
// file_cache.insert(dir, files);
|
||||
// }
|
||||
// } else {
|
||||
// file_cache.insert(dir, files.clone());
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_cached(&self, dir: &File) -> HResult<bool> {
|
||||
Ok(self.files.read()?.contains_key(dir))
|
||||
}
|
||||
|
||||
pub fn watch_only(&self, open_dirs: HashSet<File>) -> HResult<()> {
|
||||
let removable = self.watched_dirs
|
||||
.read()?
|
||||
.difference(&open_dirs)
|
||||
.map(|dir| dir.clone())
|
||||
.collect::<Vec<File>>();
|
||||
|
||||
for watch in removable {
|
||||
self.remove_watch(&watch).log();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_watch(&self, dir: &File) -> HResult<()> {
|
||||
if !self.watched_dirs.read()?.contains(&dir) {
|
||||
self.watched_dirs.write()?.insert(dir.clone());
|
||||
self.watcher.write()?.watch(&dir.path, RecursiveMode::NonRecursive)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_watch(&self, dir: &File) -> HResult<()> {
|
||||
if self.watched_dirs.read()?.contains(&dir) {
|
||||
self.watched_dirs.write()?.remove(dir);
|
||||
self.watcher.write()?.unwatch(&dir.path)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_cached_files(&self, dir: &File) -> HResult<CachedFiles> {
|
||||
let tab_settings = match self.tab_settings.read()?.get(&dir) {
|
||||
Some(tab_settings) => tab_settings.clone(),
|
||||
None => TabSettings::new()
|
||||
};
|
||||
let selection = tab_settings.selection.clone();
|
||||
let file_cache = self.files.clone();
|
||||
let dir = dir.clone();
|
||||
|
||||
let files = Async::new(Box::new(move |_| {
|
||||
let mut files = file_cache.read()?.get(&dir)?.clone();
|
||||
let tab_settings = &tab_settings;
|
||||
|
||||
files.sort = tab_settings.dir_settings.sort;
|
||||
files.dirs_first = tab_settings.dir_settings.dirs_first;
|
||||
files.reverse = tab_settings.dir_settings.reverse;
|
||||
files.show_hidden = tab_settings.dir_settings.show_hidden;
|
||||
files.filter = tab_settings.dir_settings.filter.clone();
|
||||
|
||||
if tab_settings.multi_selections.len() > 0 {
|
||||
for file in &mut files.files {
|
||||
for selected_files in &tab_settings.multi_selections {
|
||||
if file.path == selected_files.path {
|
||||
file.selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
files.sort();
|
||||
Ok(files)
|
||||
}));
|
||||
|
||||
Ok((selection, files))
|
||||
}
|
||||
|
||||
|
||||
pub fn apply_settingss(cache: &FsCache,
|
||||
files: &mut Files)
|
||||
-> HResult<()> {
|
||||
let dir = &files.directory;
|
||||
let tab_settings = cache.tab_settings.read()?.get(&dir).cloned();
|
||||
if tab_settings.is_none() { return Ok(()) }
|
||||
let tab_settings = tab_settings?;
|
||||
|
||||
files.sort = tab_settings.dir_settings.sort;
|
||||
files.dirs_first = tab_settings.dir_settings.dirs_first;
|
||||
files.reverse = tab_settings.dir_settings.reverse;
|
||||
files.show_hidden = tab_settings.dir_settings.show_hidden;
|
||||
files.filter = tab_settings.dir_settings.filter.clone();
|
||||
|
||||
if tab_settings.multi_selections.len() > 0 {
|
||||
for file in &mut files.files {
|
||||
for selected_files in &tab_settings.multi_selections {
|
||||
if file.path == selected_files.path {
|
||||
file.selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
files.sort();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
fn extract_tab_settings(files: &Files, selection: Option<File>) -> TabSettings {
|
||||
TabSettings {
|
||||
selection: selection,
|
||||
multi_selections: files.get_selected().into_iter().cloned().collect(),
|
||||
dir_settings: DirSettings {
|
||||
sort: files.sort,
|
||||
dirs_first: files.dirs_first,
|
||||
reverse: files.reverse,
|
||||
show_hidden: files.show_hidden,
|
||||
filter: files.filter.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn watch_fs(rx_fs_events: Receiver<DebouncedEvent>,
|
||||
fs_cache: Arc<RwLock<HashMap<File, Files>>>,
|
||||
fs_changes: Arc<RwLock<Vec<(File, Option<File>, Option<File>)>>>,
|
||||
sender: Sender<Events>) {
|
||||
std::thread::spawn(move || -> HResult<()> {
|
||||
for event in rx_fs_events.iter() {
|
||||
apply_event(&fs_cache, &fs_changes, event).log();
|
||||
|
||||
Ok(sender.send(Events::WidgetReady)?).log();
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
fn apply_event(_fs_cache: &Arc<RwLock<HashMap<File, Files>>>,
|
||||
fs_changes: &Arc<RwLock<Vec<(File, Option<File>, Option<File>)>>>,
|
||||
event: DebouncedEvent)
|
||||
-> HResult<()> {
|
||||
let path = &event.get_source_path()?;
|
||||
|
||||
let dirpath = path.parent()
|
||||
.map(|path| path.to_path_buf())
|
||||
.unwrap_or_else(|| PathBuf::from("/"));
|
||||
let dir = File::new_from_path(&dirpath, None)?;
|
||||
|
||||
let old_file = File::new_from_path(&path, None)?;
|
||||
let mut new_file = match event {
|
||||
DebouncedEvent::Remove(_) => None,
|
||||
_ => Some(File::new_from_path(&path, None)?)
|
||||
};
|
||||
|
||||
new_file.as_mut().map(|file| file.meta_sync());
|
||||
|
||||
fs_changes.write()?.push((dir,
|
||||
Some(old_file),
|
||||
new_file));
|
||||
|
||||
// for dir in fs_cache.write()?.values_mut() {
|
||||
// if dir.path_in_here(&path).unwrap_or(false) {
|
||||
// let old_file = dir.find_file_with_path(&path).cloned();
|
||||
// let dirty_meta = old_file
|
||||
// .as_ref()
|
||||
// .map(|f| f.dirty_meta.clone())
|
||||
// .unwrap_or(None);
|
||||
// let mut new_file = match event {
|
||||
// DebouncedEvent::Remove(_) => None,
|
||||
// _ => Some(File::new_from_path(&path, dirty_meta)?)
|
||||
// };
|
||||
|
||||
// new_file.as_mut().map(|file| file.meta_sync());
|
||||
// dir.replace_file(old_file.as_ref(), new_file.clone()).log();
|
||||
|
||||
// fs_changes.write()?.push((dir.directory.clone(),
|
||||
// old_file,
|
||||
// new_file));
|
||||
// }
|
||||
// }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
trait PathFromEvent {
|
||||
fn get_source_path(&self) -> HResult<&PathBuf>;
|
||||
}
|
||||
|
||||
impl PathFromEvent for DebouncedEvent {
|
||||
fn get_source_path(&self) -> HResult<&PathBuf> {
|
||||
match self {
|
||||
DebouncedEvent::Create(path) |
|
||||
DebouncedEvent::Write(path) |
|
||||
DebouncedEvent::Chmod(path) |
|
||||
DebouncedEvent::Remove(path) |
|
||||
DebouncedEvent::NoticeWrite(path) |
|
||||
DebouncedEvent::NoticeRemove(path) => Ok(path),
|
||||
DebouncedEvent::Rename(old_path, _) => Ok(old_path),
|
||||
DebouncedEvent::Error(err, path)
|
||||
=> Err(HError::INotifyError(format!("{}, {:?}", err, path))),
|
||||
DebouncedEvent::Rescan
|
||||
=> Err(HError::INotifyError("Need to rescan".to_string()))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
13
src/hbox.rs
13
src/hbox.rs
|
@ -54,10 +54,23 @@ impl<T> HBox<T> where T: Widget + PartialEq {
|
|||
widget
|
||||
}
|
||||
|
||||
pub fn remove_widget(&mut self, index: usize) -> T {
|
||||
self.widgets.remove(index)
|
||||
}
|
||||
|
||||
pub fn prepend_widget(&mut self, widget: T) {
|
||||
self.widgets.insert(0, widget);
|
||||
}
|
||||
|
||||
pub fn insert_widget(&mut self, index: usize, widget: T) {
|
||||
self.widgets.insert(index, widget);
|
||||
}
|
||||
|
||||
pub fn replace_widget(&mut self, index: usize, mut widget: T) -> T {
|
||||
std::mem::swap(&mut self.widgets[index], &mut widget);
|
||||
widget
|
||||
}
|
||||
|
||||
pub fn toggle_zoom(&mut self) -> HResult<()> {
|
||||
self.clear().log();
|
||||
self.zoom_active = !self.zoom_active;
|
||||
|
|
118
src/listview.rs
118
src/listview.rs
|
@ -14,6 +14,7 @@ pub trait Listable {
|
|||
fn render(&self) -> Vec<String>;
|
||||
fn render_header(&self) -> HResult<String> { Ok("".to_string()) }
|
||||
fn render_footer(&self) -> HResult<String> { Ok("".to_string()) }
|
||||
fn on_new(&mut self) -> HResult<()> { Ok(()) }
|
||||
fn on_refresh(&mut self) -> HResult<()> { Ok(()) }
|
||||
fn on_key(&mut self, _key: Key) -> HResult<()> { Ok(()) }
|
||||
}
|
||||
|
@ -27,13 +28,27 @@ impl Listable for ListView<Files> {
|
|||
self.render()
|
||||
}
|
||||
|
||||
fn on_new(&mut self) -> HResult<()> {
|
||||
let show_hidden = self.config().show_hidden();
|
||||
self.content.show_hidden = show_hidden;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_refresh(&mut self) -> HResult<()> {
|
||||
let visible_file_num = self.selection + self.get_coordinates()?.ysize() as usize;
|
||||
self.content.meta_upto(visible_file_num);
|
||||
let sender = self.core.get_sender();
|
||||
|
||||
let visible_files = self.core.coordinates.size_u().1 + self.offset + 1;
|
||||
|
||||
self.content.meta_upto(visible_files, Some(sender.clone()));
|
||||
|
||||
if self.content.is_dirty() {
|
||||
self.core.set_dirty();
|
||||
self.content.set_clean();
|
||||
self.core.set_dirty();
|
||||
}
|
||||
|
||||
if self.content.dirty_meta.is_dirty() {
|
||||
self.content.meta_upto(visible_files, Some(sender.clone()));
|
||||
self.core.set_dirty();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -50,12 +65,16 @@ impl Listable for ListView<Files> {
|
|||
self.move_down();
|
||||
self.refresh()?;
|
||||
},
|
||||
Key::Char('<') => self.move_top(),
|
||||
Key::Char('>') => self.move_bottom(),
|
||||
Key::Char('S') => { self.search_file().log(); }
|
||||
Key::Alt('s') => { self.search_next().log(); }
|
||||
Key::Char('F') => { self.filter().log(); }
|
||||
Key::Alt('S') => { self.search_prev().log(); }
|
||||
Key::Ctrl('f') => { self.filter().log(); }
|
||||
Key::Left => self.goto_grand_parent()?,
|
||||
Key::Right => self.goto_selected()?,
|
||||
Key::Char(' ') => self.multi_select_file(),
|
||||
Key::Char('v') => self.invert_selection(),
|
||||
Key::Char('t') => self.toggle_tag()?,
|
||||
Key::Char('h') => self.toggle_hidden(),
|
||||
Key::Char('r') => self.reverse_sort(),
|
||||
|
@ -88,7 +107,7 @@ where
|
|||
ListView<T>: Listable
|
||||
{
|
||||
pub fn new(core: &WidgetCore, content: T) -> ListView<T> {
|
||||
let view = ListView::<T> {
|
||||
let mut view = ListView::<T> {
|
||||
content: content,
|
||||
lines: 0,
|
||||
selection: 0,
|
||||
|
@ -98,6 +117,7 @@ where
|
|||
seeking: false,
|
||||
searching: None
|
||||
};
|
||||
view.on_new().log();
|
||||
view
|
||||
}
|
||||
|
||||
|
@ -129,6 +149,15 @@ where
|
|||
self.seeking = false;
|
||||
}
|
||||
|
||||
pub fn move_top(&mut self) {
|
||||
self.set_selection(0);
|
||||
}
|
||||
|
||||
pub fn move_bottom(&mut self) {
|
||||
let lines = self.lines;
|
||||
self.set_selection(lines - 1);
|
||||
}
|
||||
|
||||
pub fn get_selection(&self) -> usize {
|
||||
self.selection
|
||||
}
|
||||
|
@ -137,8 +166,7 @@ where
|
|||
let ysize = self.get_coordinates().unwrap().ysize() as usize;
|
||||
let mut offset = 0;
|
||||
|
||||
while position + 2
|
||||
>= ysize + offset {
|
||||
while position >= ysize + offset {
|
||||
offset += 1
|
||||
}
|
||||
|
||||
|
@ -152,19 +180,18 @@ impl ListView<Files>
|
|||
{
|
||||
pub fn selected_file(&self) -> &File {
|
||||
let selection = self.selection;
|
||||
let file = &self.content[selection];
|
||||
let file = &self.content.get_files()[selection];
|
||||
file
|
||||
}
|
||||
|
||||
pub fn selected_file_mut(&mut self) -> &mut File {
|
||||
let selection = self.selection;
|
||||
let file = &mut self.content.files[selection];
|
||||
file
|
||||
let file = self.content.get_file_mut(selection);
|
||||
file.unwrap()
|
||||
}
|
||||
|
||||
pub fn clone_selected_file(&self) -> File {
|
||||
let selection = self.selection;
|
||||
let file = self.content[selection].clone();
|
||||
let file = self.selected_file().clone();
|
||||
file
|
||||
}
|
||||
|
||||
|
@ -202,9 +229,9 @@ impl ListView<Files>
|
|||
pub fn select_file(&mut self, file: &File) {
|
||||
let pos = self
|
||||
.content
|
||||
.files
|
||||
.get_files()
|
||||
.iter()
|
||||
.position(|item| item == file)
|
||||
.position(|item| item == &file)
|
||||
.unwrap_or(0);
|
||||
self.set_selection(pos);
|
||||
}
|
||||
|
@ -282,10 +309,9 @@ impl ListView<Files>
|
|||
self.refresh().log();
|
||||
}
|
||||
|
||||
fn toggle_hidden(&mut self) {
|
||||
pub fn toggle_hidden(&mut self) {
|
||||
let file = self.clone_selected_file();
|
||||
self.content.toggle_hidden();
|
||||
self.content.reload_files();
|
||||
self.select_file(&file);
|
||||
self.refresh().log();
|
||||
}
|
||||
|
@ -310,6 +336,14 @@ impl ListView<Files>
|
|||
self.move_down();
|
||||
}
|
||||
|
||||
pub fn invert_selection(&mut self) {
|
||||
for file in self.content.get_files_mut() {
|
||||
file.toggle_selection();
|
||||
}
|
||||
self.content.set_dirty();
|
||||
self.refresh().log();
|
||||
}
|
||||
|
||||
fn toggle_tag(&mut self) -> HResult<()> {
|
||||
self.selected_file_mut().toggle_tag()?;
|
||||
|
||||
|
@ -364,6 +398,40 @@ impl ListView<Files>
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn search_prev(&mut self) -> HResult<()> {
|
||||
if self.searching.is_none() {
|
||||
self.show_status("No search pattern set!").log();
|
||||
}
|
||||
let prev_search = self.searching.clone()?;
|
||||
|
||||
|
||||
self.reverse_sort();
|
||||
|
||||
let selection = self.get_selection();
|
||||
|
||||
let file = self.content
|
||||
.files
|
||||
.iter()
|
||||
.skip(selection+1)
|
||||
.find(|file| {
|
||||
if file.name.to_lowercase().contains(&prev_search) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}).cloned();
|
||||
|
||||
self.reverse_sort();
|
||||
|
||||
if let Some(file) = file {
|
||||
let file = file.clone();
|
||||
self.select_file(&file);
|
||||
} else {
|
||||
self.show_status("Reached last search result!").log();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn filter(&mut self) -> HResult<()> {
|
||||
let filter = self.minibuffer("filter").ok();
|
||||
|
||||
|
@ -438,19 +506,11 @@ impl ListView<Files>
|
|||
}
|
||||
|
||||
fn render(&self) -> Vec<String> {
|
||||
match self.content.get_filter() {
|
||||
Some(filter) => self.content
|
||||
.files
|
||||
.iter()
|
||||
.filter(|f| f.name.contains(&filter))
|
||||
.map(|file| self.render_line(&file))
|
||||
.collect(),
|
||||
None => self.content
|
||||
.files
|
||||
.iter()
|
||||
.map(|file| self.render_line(&file))
|
||||
.collect()
|
||||
}
|
||||
self.content
|
||||
.get_files()
|
||||
.iter()
|
||||
.map(|file| self.render_line(&file))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
16
src/main.rs
16
src/main.rs
|
@ -1,6 +1,8 @@
|
|||
#![feature(vec_remove_item)]
|
||||
#![feature(trivial_bounds)]
|
||||
#![feature(try_trait)]
|
||||
#![feature(fnbox)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
extern crate termion;
|
||||
extern crate unicode_width;
|
||||
|
@ -19,14 +21,12 @@ extern crate libc;
|
|||
extern crate notify;
|
||||
extern crate parse_ansi;
|
||||
extern crate signal_notify;
|
||||
extern crate tree_magic;
|
||||
extern crate systemstat;
|
||||
|
||||
use failure::Fail;
|
||||
|
||||
use termion::input::MouseTerminal;
|
||||
use termion::raw::IntoRawMode;
|
||||
use termion::screen::AlternateScreen;
|
||||
|
||||
use std::io::{stdout, Write};
|
||||
use std::io::Write;
|
||||
|
||||
mod coordinates;
|
||||
mod file_browser;
|
||||
|
@ -46,7 +46,9 @@ mod bookmarks;
|
|||
mod paths;
|
||||
mod foldview;
|
||||
mod dirty;
|
||||
|
||||
mod fscache;
|
||||
mod config;
|
||||
mod stats;
|
||||
|
||||
|
||||
|
||||
|
@ -83,7 +85,7 @@ fn main() -> HResult<()> {
|
|||
fn run(mut core: WidgetCore) -> HResult<()> {
|
||||
core.screen.clear()?;
|
||||
|
||||
let filebrowser = FileBrowser::new_cored(&core)?;
|
||||
let filebrowser = FileBrowser::new(&core, None)?;
|
||||
let mut tabview = TabView::new(&core);
|
||||
tabview.push_widget(filebrowser)?;
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ impl History {
|
|||
let hpath = crate::paths::history_path()?;
|
||||
|
||||
let history = self.history.iter().map(|(htype, hlines)| {
|
||||
hlines.iter().map(|hline| format!("{}: {}\n", htype, hline))
|
||||
hlines.iter().map(|hline| format!("{}:{}\n", htype, hline))
|
||||
.collect::<String>()
|
||||
}).collect::<String>();
|
||||
|
||||
|
|
|
@ -9,6 +9,12 @@ pub fn home_path() -> HResult<PathBuf> {
|
|||
Ok(home)
|
||||
}
|
||||
|
||||
pub fn ranger_path() -> HResult<PathBuf> {
|
||||
let mut ranger_path = dirs_2::config_dir()?;
|
||||
ranger_path.push("ranger/");
|
||||
Ok(ranger_path)
|
||||
}
|
||||
|
||||
pub fn hunter_path() -> HResult<PathBuf> {
|
||||
let mut hunter_path = dirs_2::config_dir()?;
|
||||
hunter_path.push("hunter/");
|
||||
|
|
775
src/preview.rs
775
src/preview.rs
|
@ -1,18 +1,25 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::boxed::FnBox;
|
||||
|
||||
use failure::Backtrace;
|
||||
use rayon::ThreadPool;
|
||||
|
||||
use crate::files::{File, Files, Kind};
|
||||
use crate::fscache::FsCache;
|
||||
use crate::listview::ListView;
|
||||
use crate::textview::TextView;
|
||||
use crate::widget::{Widget, WidgetCore};
|
||||
use crate::coordinates::Coordinates;
|
||||
use crate::fail::{HResult, HError, ErrorLog};
|
||||
use crate::dirty::Dirtyable;
|
||||
|
||||
|
||||
pub type AsyncValueFn<T> = Box<dyn FnBox(Stale) -> HResult<T> + Send + Sync>;
|
||||
pub type AsyncValue<T> = Arc<Mutex<Option<HResult<T>>>>;
|
||||
pub type AsyncReadyFn = Box<dyn FnBox() -> HResult<()> + Send + Sync>;
|
||||
pub type AsyncWidgetFn<W> = Box<dyn FnBox(Stale, WidgetCore)
|
||||
-> HResult<W> + Send + Sync>;
|
||||
|
||||
|
||||
type HClosure<T> = Box<Fn(Arc<Mutex<bool>>) -> Result<T, HError> + Send>;
|
||||
type HCClosure<T> = Box<Fn(Arc<Mutex<bool>>, WidgetCore) -> Result<T, HError> + Send>;
|
||||
type WidgetO = Box<dyn Widget + Send>;
|
||||
|
||||
lazy_static! {
|
||||
|
@ -28,91 +35,234 @@ fn kill_proc() -> HResult<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_stale(stale: &Arc<Mutex<bool>>) -> HResult<bool> {
|
||||
let stale = *(stale.lock().unwrap());
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Stale(Arc<RwLock<bool>>);
|
||||
|
||||
impl Stale {
|
||||
pub fn new() -> Stale {
|
||||
Stale(Arc::new(RwLock::new(false)))
|
||||
}
|
||||
pub fn is_stale(&self) -> HResult<bool> {
|
||||
Ok(*self.0.read()?)
|
||||
}
|
||||
pub fn set_stale(&self) -> HResult<()> {
|
||||
*self.0.write()? = true;
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_fresh(&self) -> HResult<()> {
|
||||
*self.0.write()? = false;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn is_stale(stale: &Stale) -> HResult<bool> {
|
||||
let stale = stale.is_stale()?;
|
||||
Ok(stale)
|
||||
}
|
||||
|
||||
enum State {
|
||||
Is,
|
||||
Becoming,
|
||||
Fail(HError)
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
impl<T: Send + Debug> Debug for Async<T> {
|
||||
fn fmt(&self, formatter: &mut Formatter) -> Result<(), std::fmt::Error> {
|
||||
write!(formatter,
|
||||
"{:?}, {:?} {:?}",
|
||||
self.value,
|
||||
self.async_value,
|
||||
self.stale)
|
||||
}
|
||||
}
|
||||
|
||||
struct WillBe<T: Send> {
|
||||
pub state: Arc<Mutex<State>>,
|
||||
pub thing: Arc<Mutex<Option<T>>>,
|
||||
on_ready: Arc<Mutex<Option<Box<Fn(Arc<Mutex<Option<T>>>) -> HResult<()> + Send>>>>,
|
||||
stale: Arc<Mutex<bool>>
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Async<T: Send> {
|
||||
pub value: HResult<T>,
|
||||
async_value: AsyncValue<T>,
|
||||
async_closure: Arc<Mutex<Option<AsyncValueFn<T>>>>,
|
||||
on_ready: Arc<Mutex<Option<AsyncReadyFn>>>,
|
||||
started: bool,
|
||||
stale: Stale,
|
||||
}
|
||||
|
||||
impl<T: Send + 'static> WillBe<T> where {
|
||||
pub fn new_become(closure: HClosure<T>)
|
||||
-> WillBe<T> {
|
||||
let mut willbe = WillBe { state: Arc::new(Mutex::new(State::Becoming)),
|
||||
thing: Arc::new(Mutex::new(None)),
|
||||
on_ready: Arc::new(Mutex::new(None)),
|
||||
stale: Arc::new(Mutex::new(false)) };
|
||||
willbe.run(closure);
|
||||
willbe
|
||||
|
||||
|
||||
impl<T: Send + 'static> Async<T> {
|
||||
pub fn new(closure: AsyncValueFn<T>)
|
||||
-> Async<T> {
|
||||
let async_value = Async {
|
||||
value: HError::async_not_ready(),
|
||||
async_value: Arc::new(Mutex::new(None)),
|
||||
async_closure: Arc::new(Mutex::new(Some(closure))),
|
||||
on_ready: Arc::new(Mutex::new(None)),
|
||||
started: false,
|
||||
stale: Stale::new() };
|
||||
|
||||
async_value
|
||||
}
|
||||
|
||||
fn run(&mut self, closure: HClosure<T>) {
|
||||
let state = self.state.clone();
|
||||
pub fn new_with_stale(closure: AsyncValueFn<T>,
|
||||
stale: Stale)
|
||||
-> Async<T> {
|
||||
let async_value = Async {
|
||||
value: HError::async_not_ready(),
|
||||
async_value: Arc::new(Mutex::new(None)),
|
||||
async_closure: Arc::new(Mutex::new(Some(closure))),
|
||||
on_ready: Arc::new(Mutex::new(None)),
|
||||
started: false,
|
||||
stale: stale };
|
||||
|
||||
async_value
|
||||
}
|
||||
|
||||
pub fn new_with_value(val: T) -> Async<T> {
|
||||
Async {
|
||||
value: Ok(val),
|
||||
async_value: Arc::new(Mutex::new(None)),
|
||||
async_closure: Arc::new(Mutex::new(None)),
|
||||
on_ready: Arc::new(Mutex::new(None)),
|
||||
started: false,
|
||||
stale: Stale::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_async(async_fn: Arc<Mutex<Option<AsyncValueFn<T>>>>,
|
||||
async_value: AsyncValue<T>,
|
||||
on_ready_fn: Arc<Mutex<Option<AsyncReadyFn>>>,
|
||||
stale: Stale) -> HResult<()> {
|
||||
let value_fn = async_fn.lock()?.take()?;
|
||||
let value = value_fn.call_box((stale.clone(),));
|
||||
async_value.lock()?.replace(value);
|
||||
on_ready_fn.lock()?
|
||||
.take()
|
||||
.map(|on_ready| on_ready.call_box(()).log());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> HResult<()> {
|
||||
if self.started {
|
||||
HError::async_started()?
|
||||
}
|
||||
|
||||
let closure = self.async_closure.clone();
|
||||
let async_value = self.async_value.clone();
|
||||
let stale = self.stale.clone();
|
||||
let thing = self.thing.clone();
|
||||
let on_ready_fn = self.on_ready.clone();
|
||||
std::thread::spawn(move|| {
|
||||
let got_thing = closure(stale);
|
||||
match got_thing {
|
||||
Ok(got_thing) => {
|
||||
*thing.lock().unwrap() = Some(got_thing);
|
||||
*state.lock().unwrap() = State::Is;
|
||||
match *on_ready_fn.lock().unwrap() {
|
||||
Some(ref on_ready) => { on_ready(thing.clone()).ok(); },
|
||||
None => {}
|
||||
}
|
||||
},
|
||||
Err(err) => *state.lock().unwrap() = State::Fail(err)
|
||||
}
|
||||
self.started = true;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
Async::run_async(closure,
|
||||
async_value,
|
||||
on_ready_fn,
|
||||
stale).log();
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_pooled(&mut self, pool: &ThreadPool) -> HResult<()> {
|
||||
if self.started {
|
||||
HError::async_started()?
|
||||
}
|
||||
|
||||
let closure = self.async_closure.clone();
|
||||
let async_value = self.async_value.clone();
|
||||
let stale = self.stale.clone();
|
||||
let on_ready_fn = self.on_ready.clone();
|
||||
self.started = true;
|
||||
|
||||
pool.spawn(move || {
|
||||
Async::run_async(closure,
|
||||
async_value,
|
||||
on_ready_fn,
|
||||
stale).log();
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub fn wait(self) -> HResult<T> {
|
||||
Async::run_async(self.async_closure,
|
||||
self.async_value.clone(),
|
||||
self.on_ready,
|
||||
self.stale).log();
|
||||
let value = self.async_value.lock()?.take()?;
|
||||
value
|
||||
}
|
||||
|
||||
pub fn set_stale(&mut self) -> HResult<()> {
|
||||
*self.stale.lock()? = true;
|
||||
self.stale.set_stale()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_fresh(&self) -> HResult<()> {
|
||||
self.stale.set_fresh()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_stale(&self) -> HResult<bool> {
|
||||
is_stale(&self.stale)
|
||||
self.stale.is_stale()
|
||||
}
|
||||
|
||||
pub fn take(&mut self) -> HResult<T> {
|
||||
self.check()?;
|
||||
Ok(self.thing.lock()?.take()?)
|
||||
pub fn get_stale(&self) -> Stale {
|
||||
self.stale.clone()
|
||||
}
|
||||
|
||||
pub fn check(&self) -> HResult<()> {
|
||||
match *self.state.lock()? {
|
||||
State::Is => Ok(()),
|
||||
_ => Err(HError::WillBeNotReady(Backtrace::new()))
|
||||
pub fn put_stale(&mut self, stale: Stale) {
|
||||
self.stale = stale;
|
||||
}
|
||||
|
||||
pub fn is_started(&self) -> bool {
|
||||
self.started
|
||||
}
|
||||
|
||||
pub fn set_unstarted(&mut self) {
|
||||
self.started = false;
|
||||
}
|
||||
|
||||
pub fn take_async(&mut self) -> HResult<()> {
|
||||
if self.value.is_ok() { HError::async_taken()? }
|
||||
|
||||
let mut async_value = self.async_value.lock()?;
|
||||
match async_value.as_ref() {
|
||||
Some(Ok(_)) => {
|
||||
let value = async_value.take()?;
|
||||
self.value = value;
|
||||
}
|
||||
Some(Err(HError::AsyncAlreadyTakenError)) => HError::async_taken()?,
|
||||
Some(Err(_)) => {
|
||||
let value = async_value.take()?;
|
||||
self.value = value;
|
||||
}
|
||||
None => HError::async_not_ready()?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get(&self) -> HResult<&T> {
|
||||
match self.value {
|
||||
Ok(ref value) => Ok(value),
|
||||
Err(ref err) => HError::async_error(err)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self) -> HResult<&mut T> {
|
||||
self.take_async().ok();
|
||||
|
||||
match self.value {
|
||||
Ok(ref mut value) => Ok(value),
|
||||
Err(ref err) => HError::async_error(err)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_ready(&mut self,
|
||||
fun: Box<Fn(Arc<Mutex<Option<T>>>) -> HResult<()> + Send>)
|
||||
-> HResult<()> {
|
||||
if self.check().is_ok() {
|
||||
fun(self.thing.clone())?;
|
||||
} else {
|
||||
*self.on_ready.lock()? = Some(fun);
|
||||
}
|
||||
Ok(())
|
||||
fun: AsyncReadyFn) {
|
||||
*self.on_ready.lock().unwrap() = Some(fun);
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Widget + Send + 'static> PartialEq for WillBeWidget<W> {
|
||||
fn eq(&self, other: &WillBeWidget<W>) -> bool {
|
||||
impl<W: Widget + Send + 'static> PartialEq for AsyncWidget<W> {
|
||||
fn eq(&self, other: &AsyncWidget<W>) -> bool {
|
||||
if self.get_coordinates().unwrap() ==
|
||||
other.get_coordinates().unwrap() {
|
||||
true
|
||||
|
@ -122,80 +272,81 @@ impl<W: Widget + Send + 'static> PartialEq for WillBeWidget<W> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct WillBeWidget<T: Widget + Send + 'static> {
|
||||
willbe: WillBe<T>,
|
||||
|
||||
pub struct AsyncWidget<W: Widget + Send + 'static> {
|
||||
widget: Async<W>,
|
||||
core: WidgetCore
|
||||
}
|
||||
|
||||
impl<T: Widget + Send + 'static> WillBeWidget<T> {
|
||||
pub fn new(core: &WidgetCore, closure: HClosure<T>) -> WillBeWidget<T> {
|
||||
let sender = core.get_sender();
|
||||
let mut willbe = WillBe::new_become(Box::new(move |stale| closure(stale)));
|
||||
willbe.on_ready(Box::new(move |_| {
|
||||
sender.send(crate::widget::Events::WidgetReady)?;
|
||||
Ok(()) })).ok();
|
||||
impl<W: Widget + Send + 'static> AsyncWidget<W> {
|
||||
pub fn new(core: &WidgetCore, closure: AsyncValueFn<W>) -> AsyncWidget<W> {
|
||||
let sender = Mutex::new(core.get_sender());
|
||||
let mut widget = Async::new(Box::new(move |stale|
|
||||
closure.call_box((stale,))));
|
||||
widget.on_ready(Box::new(move || {
|
||||
sender.lock()?.send(crate::widget::Events::WidgetReady)?;
|
||||
Ok(())
|
||||
}));
|
||||
widget.run().log();
|
||||
|
||||
WillBeWidget {
|
||||
willbe: willbe,
|
||||
AsyncWidget {
|
||||
widget: widget,
|
||||
core: core.clone()
|
||||
}
|
||||
}
|
||||
pub fn change_to(&mut self, closure: HCClosure<T>) -> HResult<()> {
|
||||
pub fn change_to(&mut self, closure: AsyncWidgetFn<W>) -> HResult<()> {
|
||||
self.set_stale().log();
|
||||
|
||||
let sender = self.get_core()?.get_sender();
|
||||
let sender = Mutex::new(self.get_core()?.get_sender());
|
||||
let core = self.get_core()?.clone();
|
||||
|
||||
let mut willbe = WillBe::new_become(Box::new(move |stale| {
|
||||
let core = core.clone();
|
||||
closure(stale, core)
|
||||
let mut widget = Async::new(Box::new(move |stale| {
|
||||
closure.call_box((stale, core.clone(),))
|
||||
}));
|
||||
willbe.on_ready(Box::new(move |_| {
|
||||
sender.send(crate::widget::Events::WidgetReady)?;
|
||||
Ok(())
|
||||
}))?;
|
||||
|
||||
self.willbe = willbe;
|
||||
widget.on_ready(Box::new(move || {
|
||||
sender.lock()?.send(crate::widget::Events::WidgetReady)?;
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
widget.run().log();
|
||||
|
||||
self.widget = widget;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_stale(&mut self) -> HResult<()> {
|
||||
self.willbe.set_stale()
|
||||
self.widget.set_stale()
|
||||
}
|
||||
|
||||
pub fn is_stale(&self) -> HResult<bool> {
|
||||
self.willbe.is_stale()
|
||||
self.widget.is_stale()
|
||||
}
|
||||
|
||||
pub fn widget(&self) -> HResult<Arc<Mutex<Option<T>>>> {
|
||||
self.willbe.check()?;
|
||||
Ok(self.willbe.thing.clone())
|
||||
pub fn get_stale(&self) -> Stale {
|
||||
self.widget.get_stale()
|
||||
}
|
||||
|
||||
pub fn widget(&self) -> HResult<&W> {
|
||||
self.widget.get()
|
||||
}
|
||||
|
||||
pub fn widget_mut(&mut self) -> HResult<&mut W> {
|
||||
self.widget.get_mut()
|
||||
}
|
||||
|
||||
pub fn take_widget(self) -> HResult<W> {
|
||||
Ok(self.widget.value?)
|
||||
}
|
||||
|
||||
pub fn ready(&self) -> bool {
|
||||
match self.willbe.check() {
|
||||
Ok(_) => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take(&mut self) -> HResult<T> {
|
||||
self.willbe.take()
|
||||
self.widget().is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
// impl<T: Widget + Send> WillBeWidget<T> {
|
||||
// fn is_widget(&self) -> bool {
|
||||
// self.willbe.check().is_ok()
|
||||
// }
|
||||
// fn take_widget(self) {
|
||||
// if self.is_widget() {
|
||||
// let widget = self.willbe.take();
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
impl<T: Widget + Send + 'static> Widget for WillBeWidget<T> {
|
||||
|
||||
impl<T: Widget + Send + 'static> Widget for AsyncWidget<T> {
|
||||
fn get_core(&self) -> HResult<&WidgetCore> {
|
||||
Ok(&self.core)
|
||||
}
|
||||
|
@ -205,22 +356,19 @@ impl<T: Widget + Send + 'static> Widget for WillBeWidget<T> {
|
|||
|
||||
fn set_coordinates(&mut self, coordinates: &Coordinates) -> HResult<()> {
|
||||
self.core.coordinates = coordinates.clone();
|
||||
if let Ok(widget) = self.widget() {
|
||||
let mut widget = widget.lock()?;
|
||||
let widget = widget.as_mut()?;
|
||||
if let Ok(widget) = self.widget_mut() {
|
||||
widget.set_coordinates(&coordinates)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn refresh(&mut self) -> HResult<()> {
|
||||
let coords = self.get_coordinates()?;
|
||||
if let Ok(widget) = self.widget() {
|
||||
let mut widget = widget.lock()?;
|
||||
let widget = widget.as_mut()?;
|
||||
self.widget.take_async().ok();
|
||||
|
||||
if widget.get_coordinates()? != coords {
|
||||
widget.set_coordinates(self.get_coordinates()?)?;
|
||||
let coords = self.get_coordinates()?.clone();
|
||||
if let Ok(widget) = self.widget_mut() {
|
||||
if widget.get_coordinates()? != &coords {
|
||||
widget.set_coordinates(&coords)?;
|
||||
widget.refresh()?;
|
||||
} else {
|
||||
widget.refresh()?;
|
||||
|
@ -229,7 +377,7 @@ impl<T: Widget + Send + 'static> Widget for WillBeWidget<T> {
|
|||
Ok(())
|
||||
}
|
||||
fn get_drawlist(&self) -> HResult<String> {
|
||||
if self.willbe.check().is_err() {
|
||||
if self.widget().is_err() {
|
||||
let clear = self.get_clearlist()?;
|
||||
let (xpos, ypos) = self.get_coordinates()?.u16position();
|
||||
let pos = crate::term::goto_xy(xpos, ypos);
|
||||
|
@ -240,17 +388,11 @@ impl<T: Widget + Send + 'static> Widget for WillBeWidget<T> {
|
|||
return self.get_clearlist()
|
||||
}
|
||||
|
||||
let widget = self.widget()?;
|
||||
let widget = widget.lock()?;
|
||||
let widget = widget.as_ref()?;
|
||||
widget.get_drawlist()
|
||||
self.widget()?.get_drawlist()
|
||||
}
|
||||
fn on_key(&mut self, key: termion::event::Key) -> HResult<()> {
|
||||
if self.willbe.check().is_err() { return Ok(()) }
|
||||
let widget = self.widget()?;
|
||||
let mut widget = widget.lock()?;
|
||||
let widget = widget.as_mut()?;
|
||||
widget.on_key(key)
|
||||
if self.widget().is_err() { return Ok(()) }
|
||||
self.widget_mut()?.on_key(key)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,110 +408,181 @@ impl PartialEq for Previewer {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum PreviewWidget {
|
||||
FileList(ListView<Files>),
|
||||
TextView(TextView)
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub struct Previewer {
|
||||
widget: WillBeWidget<Box<dyn Widget + Send>>,
|
||||
widget: AsyncWidget<PreviewWidget>,
|
||||
core: WidgetCore,
|
||||
file: Option<File>,
|
||||
selection: Option<File>,
|
||||
cached_files: Option<Files>
|
||||
pub cache: FsCache,
|
||||
animator: Stale
|
||||
}
|
||||
|
||||
|
||||
impl Previewer {
|
||||
pub fn new(core: &WidgetCore) -> Previewer {
|
||||
pub fn new(core: &WidgetCore, cache: FsCache) -> Previewer {
|
||||
let core_ = core.clone();
|
||||
let willbe = WillBeWidget::new(&core, Box::new(move |_| {
|
||||
Ok(Box::new(crate::textview::TextView::new_blank(&core_))
|
||||
as Box<dyn Widget + Send>)
|
||||
let widget = AsyncWidget::new(&core, Box::new(move |_| {
|
||||
let blank = TextView::new_blank(&core_);
|
||||
let blank = PreviewWidget::TextView(blank);
|
||||
Ok(blank)
|
||||
}));
|
||||
Previewer { widget: willbe,
|
||||
Previewer { widget: widget,
|
||||
core: core.clone(),
|
||||
file: None,
|
||||
selection: None,
|
||||
cached_files: None }
|
||||
cache: cache,
|
||||
animator: Stale::new()}
|
||||
}
|
||||
|
||||
fn become_preview(&mut self,
|
||||
widget: HResult<WillBeWidget<WidgetO>>) {
|
||||
let coordinates = self.get_coordinates().unwrap().clone();
|
||||
self.widget = widget.unwrap();
|
||||
self.widget.set_coordinates(&coordinates).ok();
|
||||
widget: HResult<AsyncWidget<PreviewWidget>>) -> HResult<()> {
|
||||
let coordinates = self.get_coordinates()?.clone();
|
||||
self.widget = widget?;
|
||||
self.widget.set_coordinates(&coordinates)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_stale(&mut self) -> HResult<()> {
|
||||
self.widget.set_stale()
|
||||
}
|
||||
|
||||
pub fn get_file(&self) -> Option<&File> {
|
||||
self.file.as_ref()
|
||||
}
|
||||
|
||||
pub fn cancel_animation(&self) -> HResult<()> {
|
||||
self.animator.set_stale()
|
||||
}
|
||||
|
||||
pub fn take_files(&mut self) -> HResult<Files> {
|
||||
let core = self.core.clone();
|
||||
let mut widget = AsyncWidget::new(&core.clone(), Box::new(move |_| {
|
||||
let widget = TextView::new_blank(&core);
|
||||
let widget = PreviewWidget::TextView(widget);
|
||||
Ok(widget)
|
||||
}));
|
||||
std::mem::swap(&mut self.widget, &mut widget);
|
||||
|
||||
match widget.take_widget() {
|
||||
Ok(PreviewWidget::FileList(file_list)) => {
|
||||
let files = file_list.content;
|
||||
Ok(files)
|
||||
}
|
||||
_ => HError::no_files()?
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace_file(&mut self, dir: &File,
|
||||
old: Option<&File>,
|
||||
new: Option<&File>) -> HResult<()> {
|
||||
if self.file.as_ref() != Some(dir) { return Ok(()) }
|
||||
self.widget.widget_mut().map(|widget| {
|
||||
match widget {
|
||||
PreviewWidget::FileList(filelist) => {
|
||||
filelist.content.replace_file(old, new.cloned()).map(|_| {
|
||||
filelist.refresh().ok();
|
||||
}).ok();
|
||||
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn put_preview_files(&mut self, files: Files) {
|
||||
let core = self.core.clone();
|
||||
let dir = files.directory.clone();
|
||||
let cache = self.cache.clone();
|
||||
self.file = Some(dir);
|
||||
|
||||
self.widget = AsyncWidget::new(&self.core, Box::new(move |_| {
|
||||
let selected_file = cache.get_selection(&files.directory);
|
||||
let mut filelist = ListView::new(&core, files);
|
||||
|
||||
selected_file.map(|file| filelist.select_file(&file)).log();
|
||||
|
||||
Ok(PreviewWidget::FileList(filelist))
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn set_file(&mut self,
|
||||
file: &File,
|
||||
selection: Option<File>,
|
||||
cached_files: Option<Files>) -> HResult<()> {
|
||||
file: &File) -> HResult<()> {
|
||||
if Some(file) == self.file.as_ref() && !self.widget.is_stale()? { return Ok(()) }
|
||||
self.file = Some(file.clone());
|
||||
self.selection = selection.clone();
|
||||
self.cached_files = cached_files.clone();
|
||||
|
||||
let coordinates = self.get_coordinates().unwrap().clone();
|
||||
let file = file.clone();
|
||||
let core = self.core.clone();
|
||||
let cache = self.cache.clone();
|
||||
let animator = self.animator.clone();
|
||||
|
||||
self.widget.set_stale().ok();
|
||||
self.animator.set_fresh().log();
|
||||
|
||||
self.become_preview(Ok(WillBeWidget::new(&self.core, Box::new(move |stale| {
|
||||
self.become_preview(Ok(AsyncWidget::new(&self.core,
|
||||
Box::new(move |stale: Stale| {
|
||||
kill_proc().unwrap();
|
||||
|
||||
let file = file.clone();
|
||||
let selection = selection.clone();
|
||||
let cached_files = cached_files.clone();
|
||||
|
||||
if file.kind == Kind::Directory {
|
||||
let preview = Previewer::preview_dir(&file,
|
||||
selection,
|
||||
cached_files,
|
||||
cache,
|
||||
&core,
|
||||
stale.clone());
|
||||
stale,
|
||||
animator);
|
||||
return preview;
|
||||
}
|
||||
|
||||
if file.get_mime() == Some("text".to_string()) {
|
||||
return Previewer::preview_text(&file, &core, stale.clone())
|
||||
if file.is_text() {
|
||||
return Previewer::preview_text(&file,
|
||||
&core,
|
||||
stale,
|
||||
animator);
|
||||
}
|
||||
|
||||
let preview = Previewer::preview_external(&file, &core, stale.clone());
|
||||
let preview = Previewer::preview_external(&file,
|
||||
&core,
|
||||
stale,
|
||||
animator.clone());
|
||||
if preview.is_ok() { return preview; }
|
||||
else {
|
||||
let mut blank = Box::new(TextView::new_blank(&core));
|
||||
let mut blank = TextView::new_blank(&core);
|
||||
blank.set_coordinates(&coordinates).log();
|
||||
blank.refresh().log();
|
||||
blank.animate_slide_up().log();
|
||||
return Ok(blank)
|
||||
blank.animate_slide_up(Some(animator)).log();
|
||||
return Ok(PreviewWidget::TextView(blank))
|
||||
}
|
||||
}))));
|
||||
Ok(())
|
||||
}))))
|
||||
}
|
||||
|
||||
pub fn reload(&mut self) {
|
||||
if let Some(file) = self.file.clone() {
|
||||
self.file = None;
|
||||
let cache = self.cached_files.take();
|
||||
self.set_file(&file, self.selection.clone(), cache);
|
||||
self.set_file(&file).log();
|
||||
}
|
||||
}
|
||||
|
||||
fn preview_failed(file: &File) -> HResult<WidgetO> {
|
||||
|
||||
|
||||
fn preview_failed(file: &File) -> HResult<PreviewWidget> {
|
||||
HError::preview_failed(file)
|
||||
}
|
||||
|
||||
fn preview_dir(file: &File,
|
||||
selection: Option<File>,
|
||||
cached_files: Option<Files>,
|
||||
cache: FsCache,
|
||||
core: &WidgetCore,
|
||||
stale: Arc<Mutex<bool>>)
|
||||
-> Result<WidgetO, HError> {
|
||||
let files = cached_files.or_else(|| {
|
||||
Files::new_from_path_cancellable(&file.path,
|
||||
stale.clone()).ok()
|
||||
})?;
|
||||
stale: Stale,
|
||||
animator: Stale)
|
||||
-> HResult<PreviewWidget> {
|
||||
let (selection, cached_files) = cache.get_files(&file, stale.clone())?;
|
||||
|
||||
let files = cached_files.wait()?;
|
||||
|
||||
let len = files.len();
|
||||
|
||||
if len == 0 || is_stale(&stale)? { return Previewer::preview_failed(&file) }
|
||||
|
@ -381,12 +594,15 @@ impl Previewer {
|
|||
file_list.set_coordinates(&core.coordinates)?;
|
||||
file_list.refresh()?;
|
||||
if is_stale(&stale)? { return Previewer::preview_failed(&file) }
|
||||
file_list.animate_slide_up()?;
|
||||
Ok(Box::new(file_list) as Box<dyn Widget + Send>)
|
||||
file_list.animate_slide_up(Some(animator))?;
|
||||
Ok(PreviewWidget::FileList(file_list))
|
||||
}
|
||||
|
||||
fn preview_text(file: &File, core: &WidgetCore, stale: Arc<Mutex<bool>>)
|
||||
-> HResult<WidgetO> {
|
||||
fn preview_text(file: &File,
|
||||
core: &WidgetCore,
|
||||
stale: Stale,
|
||||
animator: Stale)
|
||||
-> HResult<PreviewWidget> {
|
||||
let lines = core.coordinates.ysize() as usize;
|
||||
let mut textview
|
||||
= TextView::new_from_file_limit_lines(&core,
|
||||
|
@ -399,12 +615,15 @@ impl Previewer {
|
|||
|
||||
if is_stale(&stale)? { return Previewer::preview_failed(&file) }
|
||||
|
||||
textview.animate_slide_up()?;
|
||||
Ok(Box::new(textview))
|
||||
textview.animate_slide_up(Some(animator))?;
|
||||
Ok(PreviewWidget::TextView(textview))
|
||||
}
|
||||
|
||||
fn preview_external(file: &File, core: &WidgetCore, stale: Arc<Mutex<bool>>)
|
||||
-> Result<Box<dyn Widget + Send>, HError> {
|
||||
fn preview_external(file: &File,
|
||||
core: &WidgetCore,
|
||||
stale: Stale,
|
||||
animator: Stale)
|
||||
-> HResult<PreviewWidget> {
|
||||
let process =
|
||||
std::process::Command::new("scope.sh")
|
||||
.arg(&file.path)
|
||||
|
@ -433,10 +652,9 @@ impl Previewer {
|
|||
*pid_ = None;
|
||||
}
|
||||
|
||||
let status = output.status.code()
|
||||
.ok_or(HError::preview_failed(file)?);
|
||||
//let status = output.status.code()?;
|
||||
|
||||
if status == Ok(0) || status == Ok(5) && !is_stale(&stale)? {
|
||||
if !is_stale(&stale)? {
|
||||
let output = std::str::from_utf8(&output.stdout)
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
@ -447,8 +665,8 @@ impl Previewer {
|
|||
offset: 0};
|
||||
textview.set_coordinates(&core.coordinates).log();
|
||||
textview.refresh().log();
|
||||
textview.animate_slide_up().log();
|
||||
return Ok(Box::new(textview))
|
||||
textview.animate_slide_up(Some(animator)).log();
|
||||
return Ok(PreviewWidget::TextView(textview))
|
||||
}
|
||||
HError::preview_failed(file)
|
||||
}
|
||||
|
@ -465,6 +683,15 @@ impl Widget for Previewer {
|
|||
Ok(&mut self.core)
|
||||
}
|
||||
|
||||
fn config_loaded(&mut self) -> HResult<()> {
|
||||
let show_hidden = self.config().show_hidden();
|
||||
if let PreviewWidget::FileList(filelist) = self.widget.widget_mut()? {
|
||||
filelist.content.show_hidden = show_hidden;
|
||||
filelist.content.dirty_meta.set_dirty();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_coordinates(&mut self, coordinates: &Coordinates) -> HResult<()> {
|
||||
self.core.coordinates = coordinates.clone();
|
||||
self.widget.set_coordinates(&coordinates)
|
||||
|
@ -478,158 +705,38 @@ impl Widget for Previewer {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// #[derive(PartialEq)]
|
||||
// pub struct AsyncPreviewer {
|
||||
// pub file: Option<File>,
|
||||
// pub buffer: String,
|
||||
// pub coordinates: Coordinates,
|
||||
// pub async_plug: AsyncPlug2<Box<dyn Widget + Send + 'static>>
|
||||
// }
|
||||
|
||||
// impl AsyncPreviewer {
|
||||
// pub fn new() -> AsyncPreviewer {
|
||||
// let closure = Box::new(|| {
|
||||
// Box::new(crate::textview::TextView {
|
||||
// lines: vec![],
|
||||
// buffer: "".to_string(),
|
||||
// coordinates: Coordinates::new()
|
||||
// }) as Box<dyn Widget + Send + 'static>
|
||||
// });
|
||||
|
||||
// AsyncPreviewer {
|
||||
// file: None,
|
||||
// buffer: String::new(),
|
||||
// coordinates: Coordinates::new(),
|
||||
// async_plug: AsyncPlug2::new_from_closure(closure),
|
||||
// }
|
||||
// }
|
||||
// pub fn set_file(&mut self, file: &File) {
|
||||
// let coordinates = self.coordinates.clone();
|
||||
// let file = file.clone();
|
||||
// let redraw = crate::term::reset() + &self.get_redraw_empty_list(0);
|
||||
// //let pids = PIDS.clone();
|
||||
// //kill_procs();
|
||||
|
||||
// self.async_plug.replace_widget(Box::new(move || {
|
||||
// kill_procs();
|
||||
// let mut bufout = std::io::BufWriter::new(std::io::stdout());
|
||||
// match &file.kind {
|
||||
// Kind::Directory => match Files::new_from_path(&file.path) {
|
||||
// Ok(files) => {
|
||||
// //if !is_current(&file) { return }
|
||||
// let len = files.len();
|
||||
// //if len == 0 { return };
|
||||
// let mut file_list = ListView::new(files);
|
||||
// file_list.set_coordinates(&coordinates);
|
||||
// file_list.refresh();
|
||||
// //if !is_current(&file) { return }
|
||||
// file_list.animate_slide_up();
|
||||
// return Box::new(file_list)
|
||||
|
||||
// }
|
||||
// Err(err) => {
|
||||
// write!(bufout, "{}", redraw).unwrap();
|
||||
// let textview = crate::textview::TextView {
|
||||
// lines: vec![],
|
||||
// buffer: "".to_string(),
|
||||
// coordinates: Coordinates::new(),
|
||||
// };
|
||||
// return Box::new(textview)
|
||||
// },
|
||||
// }
|
||||
// _ => {
|
||||
// if file.get_mime() == Some("text".to_string()) {
|
||||
// let lines = coordinates.ysize() as usize;
|
||||
// let mut textview
|
||||
// = TextView::new_from_file_limit_lines(&file,
|
||||
// lines);
|
||||
// //if !is_current(&file) { return }
|
||||
// textview.set_coordinates(&coordinates);
|
||||
// textview.refresh();
|
||||
// //if !is_current(&file) { return }
|
||||
// textview.animate_slide_up();
|
||||
// return Box::new(textview)
|
||||
// } else {
|
||||
// let process =
|
||||
// std::process::Command::new("scope.sh")
|
||||
// .arg(&file.name)
|
||||
// .arg("10".to_string())
|
||||
// .arg("10".to_string())
|
||||
// .arg("".to_string())
|
||||
// .arg("false".to_string())
|
||||
// .stdin(std::process::Stdio::null())
|
||||
// .stdout(std::process::Stdio::piped())
|
||||
// .stderr(std::process::Stdio::null())
|
||||
// .spawn().unwrap();
|
||||
|
||||
// let pid = process.id();
|
||||
// PIDS.lock().unwrap().push(pid);
|
||||
|
||||
// //if !is_current(&file) { return }
|
||||
|
||||
// let output = process.wait_with_output();
|
||||
// match output {
|
||||
// Ok(output) => {
|
||||
// let status = output.status.code();
|
||||
// match status {
|
||||
// Some(status) => {
|
||||
// if status == 0 || status == 5 && is_current(&file) {
|
||||
// let output = std::str::from_utf8(&output.stdout)
|
||||
// .unwrap()
|
||||
// .to_string();
|
||||
// let mut textview = TextView {
|
||||
// lines: output.lines().map(|s| s.to_string()).collect(),
|
||||
// buffer: String::new(),
|
||||
// coordinates: Coordinates::new() };
|
||||
// textview.set_coordinates(&coordinates);
|
||||
// textview.refresh();
|
||||
// textview.animate_slide_up();
|
||||
// return Box::new(textview)
|
||||
// }
|
||||
// }, None => {}
|
||||
// }
|
||||
// }, Err(_) => {}
|
||||
// }
|
||||
|
||||
// write!(bufout, "{}", redraw).unwrap();
|
||||
// //std::io::stdout().flush().unwrap();
|
||||
// let textview = crate::textview::TextView {
|
||||
// lines: vec![],
|
||||
// buffer: "".to_string(),
|
||||
// coordinates: Coordinates::new(),
|
||||
// };
|
||||
// return Box::new(textview)
|
||||
// }
|
||||
// }
|
||||
// }}))
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
impl Widget for PreviewWidget {
|
||||
fn get_core(&self) -> HResult<&WidgetCore> {
|
||||
match self {
|
||||
PreviewWidget::FileList(widget) => widget.get_core(),
|
||||
PreviewWidget::TextView(widget) => widget.get_core()
|
||||
}
|
||||
}
|
||||
fn get_core_mut(&mut self) -> HResult<&mut WidgetCore> {
|
||||
match self {
|
||||
PreviewWidget::FileList(widget) => widget.get_core_mut(),
|
||||
PreviewWidget::TextView(widget) => widget.get_core_mut()
|
||||
}
|
||||
}
|
||||
fn set_coordinates(&mut self, coordinates: &Coordinates) -> HResult<()> {
|
||||
match self {
|
||||
PreviewWidget::FileList(widget) => widget.set_coordinates(coordinates),
|
||||
PreviewWidget::TextView(widget) => widget.set_coordinates(coordinates),
|
||||
}
|
||||
}
|
||||
fn refresh(&mut self) -> HResult<()> {
|
||||
match self {
|
||||
PreviewWidget::FileList(widget) => widget.refresh(),
|
||||
PreviewWidget::TextView(widget) => widget.refresh()
|
||||
}
|
||||
}
|
||||
fn get_drawlist(&self) -> HResult<String> {
|
||||
match self {
|
||||
PreviewWidget::FileList(widget) => widget.get_drawlist(),
|
||||
PreviewWidget::TextView(widget) => widget.get_drawlist()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<T> Widget for Box<T> where T: Widget + ?Sized {
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::process::Child;
|
|||
use std::os::unix::process::{CommandExt, ExitStatusExt};
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::ffi::OsString;
|
||||
use std::os::unix::ffi::{OsStringExt, OsStrExt};
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
|
||||
use termion::event::Key;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
@ -13,9 +13,9 @@ use crate::listview::{Listable, ListView};
|
|||
use crate::textview::TextView;
|
||||
use crate::widget::{Widget, Events, WidgetCore};
|
||||
use crate::coordinates::Coordinates;
|
||||
use crate::preview::{AsyncWidget, Stale};
|
||||
use crate::dirty::Dirtyable;
|
||||
use crate::hbox::HBox;
|
||||
use crate::preview::WillBeWidget;
|
||||
use crate::fail::{HResult, HError, ErrorLog};
|
||||
use crate::term;
|
||||
use crate::files::{File, OsStrTools};
|
||||
|
@ -299,7 +299,7 @@ impl ListView<Vec<Process>> {
|
|||
#[derive(PartialEq)]
|
||||
enum ProcViewWidgets {
|
||||
List(ListView<Vec<Process>>),
|
||||
TextView(WillBeWidget<TextView>),
|
||||
TextView(AsyncWidget<TextView>),
|
||||
}
|
||||
|
||||
impl Widget for ProcViewWidgets {
|
||||
|
@ -332,7 +332,8 @@ impl Widget for ProcViewWidgets {
|
|||
pub struct ProcView {
|
||||
core: WidgetCore,
|
||||
hbox: HBox<ProcViewWidgets>,
|
||||
viewing: Option<usize>
|
||||
viewing: Option<usize>,
|
||||
animator: Stale
|
||||
}
|
||||
|
||||
impl HBox<ProcViewWidgets> {
|
||||
|
@ -348,7 +349,7 @@ impl HBox<ProcViewWidgets> {
|
|||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
fn get_textview(&mut self) -> &mut WillBeWidget<TextView> {
|
||||
fn get_textview(&mut self) -> &mut AsyncWidget<TextView> {
|
||||
match &mut self.widgets[1] {
|
||||
ProcViewWidgets::TextView(textview) => textview,
|
||||
_ => unreachable!()
|
||||
|
@ -360,8 +361,10 @@ impl ProcView {
|
|||
pub fn new(core: &WidgetCore) -> ProcView {
|
||||
let tcore = core.clone();
|
||||
let listview = ListView::new(&core, vec![]);
|
||||
let textview = Box::new(move |_| Ok(TextView::new_blank(&tcore)));
|
||||
let textview = WillBeWidget::new(&core, textview);
|
||||
let textview = AsyncWidget::new(&core, Box::new(move |_| {
|
||||
let textview = TextView::new_blank(&tcore);
|
||||
Ok(textview)
|
||||
}));
|
||||
let mut hbox = HBox::new(&core);
|
||||
hbox.push_widget(ProcViewWidgets::List(listview));
|
||||
hbox.push_widget(ProcViewWidgets::TextView(textview));
|
||||
|
@ -370,7 +373,8 @@ impl ProcView {
|
|||
ProcView {
|
||||
core: core.clone(),
|
||||
hbox: hbox,
|
||||
viewing: None
|
||||
viewing: None,
|
||||
animator: Stale::new()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -382,7 +386,7 @@ impl ProcView {
|
|||
self.hbox.get_listview_mut()
|
||||
}
|
||||
|
||||
fn get_textview(&mut self) -> &mut WillBeWidget<TextView> {
|
||||
fn get_textview(&mut self) -> &mut AsyncWidget<TextView> {
|
||||
self.hbox.get_textview()
|
||||
}
|
||||
|
||||
|
@ -399,12 +403,9 @@ impl ProcView {
|
|||
pub fn remove_proc(&mut self) -> HResult<()> {
|
||||
if self.get_listview_mut().content.len() == 0 { return Ok(()) }
|
||||
self.get_listview_mut().remove_proc()?;
|
||||
self.get_textview().change_to(Box::new(move |_, core| {
|
||||
let mut textview = TextView::new_blank(&core);
|
||||
textview.refresh().log();
|
||||
textview.animate_slide_up().log();
|
||||
Ok(textview)
|
||||
})).log();
|
||||
self.get_textview().clear().log();
|
||||
self.get_textview().widget_mut()?.set_text("").log();
|
||||
self.viewing = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -414,48 +415,52 @@ impl ProcView {
|
|||
}
|
||||
let output = self.get_listview_mut().selected_proc()?.output.lock()?.clone();
|
||||
|
||||
let animator = self.animator.clone();
|
||||
animator.set_fresh().log();
|
||||
|
||||
self.get_textview().change_to(Box::new(move |_, core| {
|
||||
let mut textview = TextView::new_blank(&core);
|
||||
textview.set_text(&output).log();
|
||||
textview.animate_slide_up().log();
|
||||
textview.animate_slide_up(Some(animator)).log();
|
||||
Ok(textview)
|
||||
})).log();
|
||||
|
||||
self.viewing = Some(self.get_listview_mut().get_selection());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn toggle_follow(&mut self) -> HResult<()> {
|
||||
self.get_textview().widget()?.lock()?.as_mut()?.toggle_follow();
|
||||
self.get_textview().widget_mut()?.toggle_follow();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn scroll_up(&mut self) -> HResult<()> {
|
||||
self.get_textview().widget()?.lock()?.as_mut()?.scroll_up();
|
||||
self.get_textview().widget_mut()?.scroll_up();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn scroll_down(&mut self) -> HResult<()> {
|
||||
self.get_textview().widget()?.lock()?.as_mut()?.scroll_down();
|
||||
self.get_textview().widget_mut()?.scroll_down();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn page_up(&mut self) -> HResult<()> {
|
||||
self.get_textview().widget()?.lock()?.as_mut()?.page_up();
|
||||
self.get_textview().widget_mut()?.page_up();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn page_down(&mut self) -> HResult<()> {
|
||||
self.get_textview().widget()?.lock()?.as_mut()?.page_down();
|
||||
self.get_textview().widget_mut()?.page_down();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn scroll_top(&mut self) -> HResult<()> {
|
||||
self.get_textview().widget()?.lock()?.as_mut()?.scroll_top();
|
||||
self.get_textview().widget_mut()?.scroll_top();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn scroll_bottom(&mut self) -> HResult<()> {
|
||||
self.get_textview().widget()?.lock()?.as_mut()?.scroll_bottom();
|
||||
self.get_textview().widget_mut()?.scroll_bottom();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -546,10 +551,13 @@ impl Widget for ProcView {
|
|||
}
|
||||
fn on_key(&mut self, key: Key) -> HResult<()> {
|
||||
match key {
|
||||
Key::Char('w') => { return Err(HError::PopupFinnished) }
|
||||
Key::Char('D') => { self.remove_proc()? }
|
||||
Key::Char('d') => { self.get_listview_mut().kill_proc()? }
|
||||
Key::Up | Key::Char('k') => {
|
||||
Key::Char('w') => {
|
||||
self.animator.set_stale().log();
|
||||
self.clear().log();
|
||||
return Err(HError::PopupFinnished) }
|
||||
Key::Char('d') => { self.remove_proc()? }
|
||||
Key::Char('K') => { self.get_listview_mut().kill_proc()? }
|
||||
Key::Up | Key::Char('p') => {
|
||||
self.get_listview_mut().move_up();
|
||||
}
|
||||
Key::Down | Key::Char('j') => {
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
use systemstat::{System, Platform};
|
||||
use systemstat::data::Filesystem;
|
||||
|
||||
use std::path::{Path, PathBuf, Component};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::fail::{HResult, ErrorLog};
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct FsStat {
|
||||
pub stats: HashMap<PathBuf, Filesystem>
|
||||
}
|
||||
|
||||
impl FsStat {
|
||||
pub fn new() -> HResult<FsStat> {
|
||||
let mut stats = FsStat { stats: HashMap::new() };
|
||||
stats.refresh().log();
|
||||
|
||||
Ok(stats)
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self) -> HResult<()> {
|
||||
let sys = System::new();
|
||||
let mounts = sys.mounts()?;
|
||||
|
||||
let stats = mounts.into_iter()
|
||||
.fold(HashMap::new(), |mut stats, mount: Filesystem| {
|
||||
let path = PathBuf::from(&mount.fs_mounted_on);
|
||||
stats.insert(path, mount);
|
||||
stats
|
||||
});
|
||||
|
||||
self.stats = stats;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn find_fs(&self, path: &Path) -> HResult<&Filesystem> {
|
||||
let candidates = self
|
||||
.stats
|
||||
.keys()
|
||||
.filter(|mount_point| path.starts_with(&mount_point))
|
||||
.collect::<Vec<&PathBuf>>();
|
||||
|
||||
let deepest_match = candidates.iter()
|
||||
.fold(PathBuf::new(), |mut deepest, path| {
|
||||
let curren_path_len = deepest.components().count();
|
||||
let candidate_path_len = path.components().count();
|
||||
|
||||
if candidate_path_len > curren_path_len {
|
||||
deepest = path.to_path_buf();
|
||||
}
|
||||
deepest
|
||||
});
|
||||
let fs = self.stats.get(&deepest_match)?;
|
||||
Ok(fs)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FsExt {
|
||||
fn get_dev(&self) -> String;
|
||||
fn get_total(&self) -> String;
|
||||
fn get_free(&self) -> String;
|
||||
}
|
||||
|
||||
impl FsExt for Filesystem {
|
||||
fn get_dev(&self) -> String {
|
||||
let path = PathBuf::from(&self.fs_mounted_from);
|
||||
let dev = path.components().last().unwrap();
|
||||
let dev = match dev {
|
||||
Component::Normal(dev) => dev.to_string_lossy().to_string(),
|
||||
_ => "wtf".to_string()
|
||||
};
|
||||
dev
|
||||
}
|
||||
|
||||
fn get_total(&self) -> String {
|
||||
self.total.to_string(false)
|
||||
}
|
||||
|
||||
fn get_free(&self) -> String {
|
||||
self.free.to_string(false)
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@ use termion::event::Key;
|
|||
use crate::widget::{Widget, WidgetCore};
|
||||
use crate::fail::{HResult, ErrorLog};
|
||||
use crate::coordinates::Coordinates;
|
||||
use crate::dirty::Dirtyable;
|
||||
|
||||
pub trait Tabbable {
|
||||
fn new_tab(&mut self) -> HResult<()>;
|
||||
|
@ -26,6 +25,10 @@ pub trait Tabbable {
|
|||
_ => self.on_key_sub(key)
|
||||
}
|
||||
}
|
||||
fn on_refresh(&mut self) -> HResult<()> { Ok(()) }
|
||||
fn on_config_loaded(&mut self) -> HResult<()> { Ok(()) }
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -108,6 +111,10 @@ impl<T> Widget for TabView<T> where T: Widget, TabView<T>: Tabbable {
|
|||
Ok(&mut self.core)
|
||||
}
|
||||
|
||||
fn config_loaded(&mut self) -> HResult<()> {
|
||||
self.on_config_loaded()
|
||||
}
|
||||
|
||||
fn set_coordinates(&mut self, coordinates: &Coordinates) -> HResult<()> {
|
||||
self.core.coordinates = coordinates.clone();
|
||||
for widget in &mut self.widgets {
|
||||
|
@ -137,7 +144,8 @@ impl<T> Widget for TabView<T> where T: Widget, TabView<T>: Tabbable {
|
|||
}
|
||||
}).collect::<String>();
|
||||
|
||||
let nums_pos = xsize - nums_length as u16;
|
||||
|
||||
let nums_pos = xsize.saturating_sub(nums_length as u16);
|
||||
|
||||
Ok(format!("{}{}{}{}",
|
||||
header,
|
||||
|
@ -152,6 +160,7 @@ impl<T> Widget for TabView<T> where T: Widget, TabView<T>: Tabbable {
|
|||
}
|
||||
|
||||
fn refresh(&mut self) -> HResult<()> {
|
||||
Tabbable::on_refresh(self).log();
|
||||
self.active_tab_mut().refresh()
|
||||
}
|
||||
|
||||
|
|
95
src/term.rs
95
src/term.rs
|
@ -8,36 +8,39 @@ use termion::raw::{IntoRawMode, RawTerminal};
|
|||
|
||||
use parse_ansi::parse_bytes;
|
||||
|
||||
use crate::fail::HResult;
|
||||
use crate::fail::{HResult, ErrorLog};
|
||||
|
||||
pub type TermMode = AlternateScreen<MouseTerminal<RawTerminal<BufWriter<Stdout>>>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Screen {
|
||||
screen: Arc<Mutex<Option<TermMode>>>,
|
||||
size: Arc<RwLock<Option<(usize, usize)>>>
|
||||
size: Arc<RwLock<Option<(usize, usize)>>>,
|
||||
terminal: String
|
||||
}
|
||||
|
||||
impl Screen {
|
||||
pub fn new() -> HResult<Screen> {
|
||||
let screen = BufWriter::new(std::io::stdout()).into_raw_mode()?;
|
||||
let mut screen = MouseTerminal::from(screen);
|
||||
let screen = MouseTerminal::from(screen);
|
||||
let mut screen = AlternateScreen::from(screen);
|
||||
let terminal = std::env::var("TERM").unwrap_or("xterm".into());
|
||||
|
||||
screen.cursor_hide()?;
|
||||
Ok(Screen {
|
||||
screen: Arc::new(Mutex::new(Some(screen))),
|
||||
size: Arc::new(RwLock::new(None))
|
||||
size: Arc::new(RwLock::new(None)),
|
||||
terminal: terminal
|
||||
})
|
||||
}
|
||||
|
||||
pub fn drop_screen(&mut self) {
|
||||
self.cursor_show();
|
||||
self.to_main_screen();
|
||||
self.cursor_show().log();
|
||||
self.to_main_screen().log();
|
||||
self.screen = Arc::new(Mutex::new(None));
|
||||
|
||||
// Terminal stays fucked without this. Why?
|
||||
std::process::Command::new("reset").arg("-I").spawn();
|
||||
Ok(std::process::Command::new("reset").arg("-I").spawn()).log();
|
||||
}
|
||||
|
||||
pub fn reset_screen(&mut self) -> HResult<()> {
|
||||
|
@ -51,13 +54,32 @@ impl Screen {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_resized(&self) -> HResult<(usize, usize)> {
|
||||
Ok(self.size.read()?.clone()?)
|
||||
pub fn is_resized(&self) -> HResult<bool> {
|
||||
Ok(self.size.read()?.is_some())
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> HResult<(usize, usize)> {
|
||||
match self.size.read()?.clone() {
|
||||
Some((xsize, ysize)) => Ok((xsize, ysize)),
|
||||
None => Ok((self.xsize()?, self.ysize()?))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_size(&self) -> HResult<(usize, usize)> {
|
||||
Ok(self.size.write()?.take()?)
|
||||
}
|
||||
|
||||
pub fn set_title(&mut self, title: &str) -> HResult<()> {
|
||||
if self.terminal.starts_with("xterm") ||
|
||||
self.terminal.starts_with("screen") ||
|
||||
self.terminal.starts_with("tmux"){
|
||||
write!(self, "\x1b]2;hunter: {}\x1b\\", title)?;
|
||||
}
|
||||
if self.terminal.starts_with("tmux") {
|
||||
write!(self, "\x1bkhunter: {}\x1b\\", title)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Screen {
|
||||
|
@ -112,11 +134,6 @@ pub trait ScreenExt: Write {
|
|||
let (_, ysize) = termion::terminal_size()?;
|
||||
Ok((ysize - 1) as usize)
|
||||
}
|
||||
fn set_title(&mut self, title: &str) -> HResult<()> {
|
||||
write!(self, "\x1b]2;hunter: {}", title)?;
|
||||
write!(self, "\x1bkhunter: {}\x1b\\", title)?;
|
||||
Ok(())
|
||||
}
|
||||
fn to_main_screen(&mut self) -> HResult<()> {
|
||||
write!(self, "{}", termion::screen::ToMainScreen)?;
|
||||
self.flush()?;
|
||||
|
@ -132,6 +149,11 @@ pub fn xsize() -> u16 {
|
|||
xsize
|
||||
}
|
||||
|
||||
pub fn xsize_u() -> usize {
|
||||
let (xsize, _) = termion::terminal_size().unwrap();
|
||||
xsize as usize - 1
|
||||
}
|
||||
|
||||
pub fn ysize() -> u16 {
|
||||
let (_, ysize) = termion::terminal_size().unwrap();
|
||||
ysize
|
||||
|
@ -195,6 +217,7 @@ pub fn sized_string_u(string: &str, xsize: usize) -> String {
|
|||
padded
|
||||
}
|
||||
|
||||
|
||||
// Do these as constants
|
||||
|
||||
|
||||
|
@ -228,16 +251,44 @@ pub fn color_green() -> String {
|
|||
format!("{}", termion::color::Fg(termion::color::Green))
|
||||
}
|
||||
|
||||
pub fn color_light_green() -> String {
|
||||
format!("{}", termion::color::Fg(termion::color::LightGreen))
|
||||
}
|
||||
|
||||
pub fn color_cyan() -> String {
|
||||
format!("{}", termion::color::Fg(termion::color::Cyan))
|
||||
}
|
||||
|
||||
pub fn color_light_yellow() -> String {
|
||||
format!("{}", termion::color::Fg(termion::color::LightYellow))
|
||||
}
|
||||
|
||||
pub fn color_orange() -> String {
|
||||
let color = termion::color::Fg(termion::color::AnsiValue::rgb(5 as u8 ,
|
||||
4 as u8,
|
||||
0 as u8));
|
||||
format!("{}", color)
|
||||
}
|
||||
|
||||
|
||||
pub fn from_lscolor(color: &lscolors::Color) -> String {
|
||||
match color {
|
||||
lscolors::Color::Black => format!("{}", termion::color::Fg(termion::color::Black)),
|
||||
lscolors::Color::Red => format!("{}", termion::color::Fg(termion::color::Red)),
|
||||
lscolors::Color::Green => format!("{}", termion::color::Fg(termion::color::Green)),
|
||||
lscolors::Color::Yellow => format!("{}", termion::color::Fg(termion::color::Yellow)),
|
||||
lscolors::Color::Blue => format!("{}", termion::color::Fg(termion::color::Blue)),
|
||||
lscolors::Color::Magenta => format!("{}", termion::color::Fg(termion::color::Magenta)),
|
||||
lscolors::Color::Cyan => format!("{}", termion::color::Fg(termion::color::Cyan)),
|
||||
lscolors::Color::White => format!("{}", termion::color::Fg(termion::color::White)),
|
||||
lscolors::Color::Black
|
||||
=> format!("{}", termion::color::Fg(termion::color::Black)),
|
||||
lscolors::Color::Red
|
||||
=> format!("{}", termion::color::Fg(termion::color::Red)),
|
||||
lscolors::Color::Green
|
||||
=> format!("{}", termion::color::Fg(termion::color::Green)),
|
||||
lscolors::Color::Yellow
|
||||
=> format!("{}", termion::color::Fg(termion::color::Yellow)),
|
||||
lscolors::Color::Blue
|
||||
=> format!("{}", termion::color::Fg(termion::color::Blue)),
|
||||
lscolors::Color::Magenta
|
||||
=> format!("{}", termion::color::Fg(termion::color::Magenta)),
|
||||
lscolors::Color::Cyan
|
||||
=> format!("{}", termion::color::Fg(termion::color::Cyan)),
|
||||
lscolors::Color::White
|
||||
=> format!("{}", termion::color::Fg(termion::color::White)),
|
||||
_ => format!("{}", normal_color()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,9 +134,9 @@ impl Widget for TextView {
|
|||
Ok(&mut self.core)
|
||||
}
|
||||
fn refresh(&mut self) -> HResult<()> {
|
||||
let (xsize, ysize) = self.get_coordinates()?.size().size();
|
||||
let (xpos, ypos) = self.get_coordinates()?.position().position();
|
||||
let len = self.lines.len();
|
||||
// let (xsize, ysize) = self.get_coordinates()?.size().size();
|
||||
// let (xpos, ypos) = self.get_coordinates()?.position().position();
|
||||
// let len = self.lines.len();
|
||||
|
||||
if self.follow {
|
||||
self.scroll_bottom();
|
||||
|
|
121
src/widget.rs
121
src/widget.rs
|
@ -1,6 +1,6 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::sync::mpsc::{Sender, Receiver, channel};
|
||||
use std::io::Write;
|
||||
use std::io::{Write, stdin};
|
||||
|
||||
use termion::event::{Event, Key, MouseEvent};
|
||||
use termion::input::TermRead;
|
||||
|
@ -11,19 +11,23 @@ use crate::minibuffer::MiniBuffer;
|
|||
use crate::term;
|
||||
use crate::term::{Screen, ScreenExt};
|
||||
use crate::dirty::{Dirtyable, DirtyBit};
|
||||
use crate::preview::Stale;
|
||||
use crate::signal_notify::{notify, Signal};
|
||||
use crate::config::Config;
|
||||
use crate::preview::Async;
|
||||
|
||||
|
||||
use std::io::stdin;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Events {
|
||||
InputEvent(Event),
|
||||
WidgetReady,
|
||||
TerminalResized,
|
||||
ExclusiveEvent(Option<Sender<Events>>),
|
||||
ExclusiveEvent(Option<Mutex<Option<Sender<Events>>>>),
|
||||
InputEnabled(bool),
|
||||
RequestInput,
|
||||
Status(String)
|
||||
Status(String),
|
||||
ConfigLoaded,
|
||||
}
|
||||
|
||||
impl PartialEq for WidgetCore {
|
||||
|
@ -51,11 +55,12 @@ pub struct WidgetCore {
|
|||
pub screen: Screen,
|
||||
pub coordinates: Coordinates,
|
||||
pub minibuffer: Arc<Mutex<Option<MiniBuffer>>>,
|
||||
pub event_sender: Sender<Events>,
|
||||
pub event_sender: Arc<Mutex<Sender<Events>>>,
|
||||
event_receiver: Arc<Mutex<Option<Receiver<Events>>>>,
|
||||
pub status_bar_content: Arc<Mutex<Option<String>>>,
|
||||
term_size: (usize, usize),
|
||||
dirty: DirtyBit
|
||||
dirty: DirtyBit,
|
||||
pub config: Arc<RwLock<Async<Config>>>
|
||||
}
|
||||
|
||||
impl WidgetCore {
|
||||
|
@ -69,15 +74,24 @@ impl WidgetCore {
|
|||
let (sender, receiver) = channel();
|
||||
let status_bar_content = Arc::new(Mutex::new(None));
|
||||
|
||||
let mut config = Async::new(Box::new(|_| Config::load()));
|
||||
let confsender = Arc::new(Mutex::new(sender.clone()));
|
||||
config.on_ready(Box::new(move || {
|
||||
confsender.lock()?.send(Events::ConfigLoaded).ok();
|
||||
Ok(())
|
||||
}));
|
||||
config.run().log();
|
||||
|
||||
let core = WidgetCore {
|
||||
screen: screen,
|
||||
coordinates: coords,
|
||||
minibuffer: Arc::new(Mutex::new(None)),
|
||||
event_sender: sender,
|
||||
event_sender: Arc::new(Mutex::new(sender)),
|
||||
event_receiver: Arc::new(Mutex::new(Some(receiver))),
|
||||
status_bar_content: status_bar_content,
|
||||
term_size: (xsize, ysize),
|
||||
dirty: DirtyBit::new() };
|
||||
dirty: DirtyBit::new(),
|
||||
config: Arc::new(RwLock::new(config)) };
|
||||
|
||||
let minibuffer = MiniBuffer::new(&core);
|
||||
*core.minibuffer.lock().unwrap() = Some(minibuffer);
|
||||
|
@ -85,17 +99,19 @@ impl WidgetCore {
|
|||
}
|
||||
|
||||
pub fn get_sender(&self) -> Sender<Events> {
|
||||
self.event_sender.clone()
|
||||
self.event_sender.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Dirtyable for WidgetCore {
|
||||
fn get_bit(&self) -> &DirtyBit {
|
||||
&self.dirty
|
||||
fn is_dirty(&self) -> bool {
|
||||
self.dirty.is_dirty()
|
||||
}
|
||||
|
||||
fn get_bit_mut(&mut self) -> &mut DirtyBit {
|
||||
&mut self.dirty
|
||||
fn set_dirty(&mut self) {
|
||||
self.dirty.set_dirty();
|
||||
}
|
||||
fn set_clean(&mut self) {
|
||||
self.dirty.set_clean();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,6 +143,7 @@ pub trait Widget {
|
|||
fn refresh(&mut self) -> HResult<()>;
|
||||
fn get_drawlist(&self) -> HResult<String>;
|
||||
fn after_draw(&self) -> HResult<()> { Ok(()) }
|
||||
fn config_loaded(&mut self) -> HResult<()> { Ok(()) }
|
||||
|
||||
|
||||
|
||||
|
@ -162,7 +179,7 @@ pub trait Widget {
|
|||
}
|
||||
|
||||
fn bad(&mut self, event: Event) -> HResult<()> {
|
||||
self.show_status(&format!("Stop the nasty stuff!! {:?} does nothing!", event))
|
||||
self.show_status(&format!("Stop it!! {:?} does nothing!", event))
|
||||
}
|
||||
|
||||
fn get_header_drawlist(&mut self) -> HResult<String> {
|
||||
|
@ -238,7 +255,9 @@ pub trait Widget {
|
|||
|
||||
fn run_widget(&mut self) -> HResult<()> {
|
||||
let (tx_event, rx_event) = channel();
|
||||
self.get_core()?.get_sender().send(Events::ExclusiveEvent(Some(tx_event)))?;
|
||||
self.get_core()?
|
||||
.get_sender()
|
||||
.send(Events::ExclusiveEvent(Some(Mutex::new(Some(tx_event)))))?;
|
||||
self.get_core()?.get_sender().send(Events::RequestInput)?;
|
||||
|
||||
self.clear()?;
|
||||
|
@ -252,6 +271,7 @@ pub trait Widget {
|
|||
err @ Err(HError::PopupFinnished) |
|
||||
err @ Err(HError::Quit) |
|
||||
err @ Err(HError::MiniBufferCancelledInput) => err?,
|
||||
err @ Err(HError::WidgetResizedError) => err?,
|
||||
err @ Err(_) => err.log(),
|
||||
Ok(_) => {}
|
||||
}
|
||||
|
@ -259,6 +279,7 @@ pub trait Widget {
|
|||
}
|
||||
Events::WidgetReady => {
|
||||
self.refresh().log();
|
||||
self.draw().log();
|
||||
}
|
||||
Events::Status(status) => {
|
||||
self.show_status(&status).log();
|
||||
|
@ -270,6 +291,9 @@ pub trait Widget {
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
Events::ConfigLoaded => {
|
||||
self.get_core_mut()?.config.write()?.take_async().log();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.refresh().log();
|
||||
|
@ -284,7 +308,19 @@ pub trait Widget {
|
|||
self.write_to_screen(&clearlist)
|
||||
}
|
||||
|
||||
fn animate_slide_up(&mut self) -> HResult<()> {
|
||||
fn config(&self) -> Config {
|
||||
self.get_core()
|
||||
.unwrap()
|
||||
.config.read().unwrap().get()
|
||||
.map(|config| config.clone())
|
||||
.unwrap_or(Config::new())
|
||||
}
|
||||
|
||||
fn animate_slide_up(&mut self, animator: Option<Stale>) -> HResult<()> {
|
||||
if !self.config().animate() { return Ok(()); }
|
||||
|
||||
self.config();
|
||||
|
||||
let coords = self.get_coordinates()?.clone();
|
||||
let xpos = coords.position().x();
|
||||
let ypos = coords.position().y();
|
||||
|
@ -293,20 +329,33 @@ pub trait Widget {
|
|||
let clear = self.get_clearlist()?;
|
||||
let pause = std::time::Duration::from_millis(5);
|
||||
|
||||
if let Some(ref animator) = animator {
|
||||
if animator.is_stale()? {
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
self.write_to_screen(&clear).log();
|
||||
|
||||
for i in (0..10).rev() {
|
||||
let coords = Coordinates { size: Size((xsize,ysize-i)),
|
||||
if let Some(ref animator) = animator {
|
||||
if animator.is_stale()? {
|
||||
self.set_coordinates(&coords).log();
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
let ani_coords = Coordinates { size: Size((xsize,ysize-i)),
|
||||
position: Position
|
||||
((xpos,
|
||||
ypos+i))
|
||||
};
|
||||
self.set_coordinates(&coords).log();
|
||||
self.set_coordinates(&ani_coords).log();
|
||||
let buffer = self.get_drawlist()?;
|
||||
self.write_to_screen(&buffer).log();
|
||||
|
||||
std::thread::sleep(pause);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -341,10 +390,16 @@ pub trait Widget {
|
|||
Events::TerminalResized => {
|
||||
self.screen()?.clear().log();
|
||||
}
|
||||
Events::ConfigLoaded => {
|
||||
self.get_core_mut()?.config.write()?.take_async().log();
|
||||
self.config_loaded().log();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.resize().log();
|
||||
self.screen()?.take_size().ok();
|
||||
if self.screen()?.is_resized()? {
|
||||
self.screen()?.take_size().ok();
|
||||
}
|
||||
self.refresh().ok();
|
||||
self.draw().ok();
|
||||
}
|
||||
|
@ -352,33 +407,29 @@ pub trait Widget {
|
|||
}
|
||||
|
||||
fn draw_status(&self) -> HResult<()> {
|
||||
let xsize = term::xsize() as u16;
|
||||
let xsize = term::xsize_u();
|
||||
let status = match self.get_core()?.status_bar_content.lock()?.as_ref() {
|
||||
Some(status) => status.to_string(),
|
||||
None => "".to_string(),
|
||||
};
|
||||
let sized_status = term::sized_string_u(&status, xsize);
|
||||
|
||||
self.write_to_screen(
|
||||
&format!(
|
||||
"{}{}{:xsize$}{}{}",
|
||||
"{}{}{}",
|
||||
term::move_bottom(),
|
||||
term::status_bg(),
|
||||
" ",
|
||||
term::move_bottom(),
|
||||
status,
|
||||
xsize = xsize as usize
|
||||
sized_status
|
||||
)).log();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn show_status(&self, status: &str) -> HResult<()> {
|
||||
let xsize = self.get_core()?.coordinates.xsize_u();
|
||||
let sized_status = term::sized_string_u(status, xsize);
|
||||
HError::log(status.to_string()).log();
|
||||
HError::log::<()>(status.to_string()).log();
|
||||
{
|
||||
let mut status_content = self.get_core()?.status_bar_content.lock()?;
|
||||
*status_content = Some(sized_status);
|
||||
*status_content = Some(status.to_string());
|
||||
}
|
||||
self.draw_status()?;
|
||||
Ok(())
|
||||
|
@ -408,7 +459,8 @@ pub trait Widget {
|
|||
}
|
||||
|
||||
fn resize(&mut self) -> HResult<()> {
|
||||
if let Ok((xsize, ysize)) = self.screen()?.is_resized() {
|
||||
if let Ok(true) = self.screen()?.is_resized() {
|
||||
let (xsize, ysize) = self.screen()?.get_size()?;
|
||||
let mut coords = self.get_core()?.coordinates.clone();
|
||||
coords.set_size_u(xsize, ysize-2);
|
||||
self.set_coordinates(&coords)?;
|
||||
|
@ -434,7 +486,10 @@ fn dispatch_events(tx_internal: Sender<Events>,
|
|||
for event in rx_event.iter() {
|
||||
match &event {
|
||||
Events::ExclusiveEvent(tx_event) => {
|
||||
tx_exclusive_event = tx_event.clone();
|
||||
tx_exclusive_event = match tx_event {
|
||||
Some(locked_sender) => locked_sender.lock().unwrap().take(),
|
||||
None => None
|
||||
}
|
||||
}
|
||||
Events::InputEnabled(state) => {
|
||||
input_enabled = *state;
|
||||
|
|
Loading…
Reference in New Issue