add custom keybindings

This commit is contained in:
rabite 2019-07-25 21:35:33 +02:00
parent c106fded2e
commit 889abf77ad
17 changed files with 1821 additions and 330 deletions

42
Cargo.lock generated
View File

@ -515,6 +515,14 @@ dependencies = [
"pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "heck"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "hunter"
version = "1.3.4"
@ -543,10 +551,13 @@ dependencies = [
"parse-ansi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"pathbuftools 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"signal-notify 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"sixel 0.3.1 (git+https://github.com/rabite0/sixel-rs?tag=v0.3.1)",
"sixel-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strum 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
"strum_macros 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
"systemstat 0.1.4 (git+https://github.com/myfreeweb/systemstat?tag=v0.1.4)",
"termion 1.5.3 (git+https://github.com/redox-os/termion)",
"tree_magic 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1206,6 +1217,11 @@ dependencies = [
"ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rust-ini"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rustc-demangle"
version = "0.1.15"
@ -1311,6 +1327,22 @@ name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "strum"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "strum_macros"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.39 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "0.15.39"
@ -1455,6 +1487,11 @@ dependencies = [
"version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-segmentation"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-width"
version = "0.1.5"
@ -1606,6 +1643,7 @@ dependencies = [
"checksum gstreamer-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfc2f6cc9b6a1f5159bfd500310fe431cfb0b74b3af17ce3fdf8353cf586975"
"checksum gstreamer-video 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44868f3390bec7cad142f5fc3e39bb1782d1453d07889efd2ac69a50b0656d1f"
"checksum gstreamer-video-sys 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6b8fcb1e577de93d1ad1e5117234ce64d40f215143d752140719923651608983"
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
"checksum image 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)" = "99198e595d012efccf12abf4abc08da2d97be0b0355a2b08d101347527476ba4"
"checksum inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff"
"checksum inotify 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40b54539f3910d6f84fbf9a643efd6e3aa6e4f001426c0329576128255994718"
@ -1681,6 +1719,7 @@ dependencies = [
"checksum regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0b2f0808e7d7e4fb1cb07feb6ff2f4bc827938f24f8c2e6a3beb7370af544bdd"
"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
"checksum regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d76410686f9e3a17f06128962e0ecc5755870bb890c34820c7af7f1db2e1d48"
"checksum rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2"
"checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267"
@ -1697,6 +1736,8 @@ dependencies = [
"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7"
"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum strum 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e5d1c33039533f051704951680f1adfd468fd37ac46816ded0d9ee068e60f05f"
"checksum strum_macros 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "47cd23f5c7dee395a00fa20135e2ec0fffcdfa151c56182966d7a3261343432e"
"checksum syn 0.15.39 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d960b829a55e56db167e861ddb43602c003c7be0bee1d345021703fac2fb7c"
"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f"
"checksum systemstat 0.1.4 (git+https://github.com/myfreeweb/systemstat?tag=v0.1.4)" = "<none>"
@ -1712,6 +1753,7 @@ dependencies = [
"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86"
"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33"
"checksum unicase 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a84e5511b2a947f3ae965dcb29b13b7b1691b6e7332cf5dbc1744138d5acb7f6"
"checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9"
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum users 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7fed7d0912567d35f88010c23dbaf865e9da8b5227295e8dc0f2fdd109155ab7"

View File

@ -1,5 +1,3 @@
cargo-features = ["default-run"]
[package]
name = "hunter"
version = "1.3.4"
@ -41,7 +39,10 @@ pathbuftools = "0.1"
clap = "2.33"
mime = "0.3.13"
base64 = "0.10.1"
#glib = "*"
strum = "0.15"
strum_macros = "0.15"
rust-ini = "0.13"
image = { version = "0.21.1", optional = true }
gstreamer = { version = "0.14", optional = true }

184
README.md
View File

@ -5,11 +5,11 @@ hunter
NEW
- [Custom Keybindings] Customize keys to your liking
- [Graphics] High quality support for graphics using SIXEL/kitty protocols
- [QuickActions] Added quick action creator/customizer
- [Previews] New and improved preview customization
- [**[IRC channel](https://webchat.freenode.net/?channels=hunter)**] Problems? Bugs? Praise? Chat with us: [#hunter @ Freenode](https://webchat.freenode.net/?channels=hunter)!
- [Quick Actions] Run specific actions based on file type
@ -130,6 +130,10 @@ media_previewer=hunter-media
graphics_mode=auto
```
## Keys
Keys can be configured in ```~/.config/hunter/keys```. Some actions can be further customized with arguments. For example, you can specify ```GotoTab(n)```, where n is a positive number to move up n times. Some keys like F1-F12 are represented as an enum like this: ```F(u8)```. You can take that u8 and stick it into the ```GotoTab``` action by using a placeholder binding like this: ```GotoTab(_)=F_```. This also works for key combinations, so you can specify ```C-_``` to bind all Ctrl-<key> combinations to some action like Delete(_) on bookmarks. To bind ```_``` itself escape like this: ```\_```. See the default configuration for more examples.
## Previews
Defining previews is easy. You just need a shell script that takes a path as first parameter and prints out what you want to see in the preview column. Put that shell script in
@ -200,96 +204,110 @@ To change the directory of your shell when quitting hunter with Q you need to so
Keybindings:
============
## holy mode
By default hunter uses vi-style keybindings. If you use a QWERTY-like keyboard layout this is probably what you want. Most people will want this, so I made it the default. If you have a different keyboard layout this might not be the best choice. The holy-branch changes the movement keys to the emacs keybindings, which is more ergonomic on e.g. Colemak.
Note: ```_``` means any key.
## Main view:
## Movement:
Up(1)=k, Up
Down(1)=j, Down
Left=b, Left
Right=f, Right
Top=<, Home
Bottom=>, End
Up(10)=K
Down(10)=J
PageUp=C-v, PageUp
PageDown=M-v, PageDown
| Key | Action |
| ------------------- | :--------------------------------- |
| j/k (holy: n/p) | move down/up |
| J/K (holy: N/P) | 5x move down/5x move up |
| ]/[ | move down/up on left column |
| < | move to top |
| > | move to bottom |
| l/h (holy: f/b) | open/go back |
| S | search file |
| Alt(s) | search next |
| Alt(S) | search prev |
| Ctrl(f) | filter |
| space | multi select file |
| Alt(space) | select with external program |
| v | invert selections |
| V | clear all selections |
| Alt(v) | only show selected files |
| 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 |
| ~ | go to $HOME |
| / | turbo cd |
| Alt(/) | enter dir with external program |
| Q | quit with dir/selections |
| L | run in background |
| ~ | goto prev cwd |
| ` | goto bookmark |
| m | add bookmark |
| w | show processes |
| g holy(l) | show log |
| a | show quick actions |
| z | open subshell in cwd |
| c | toggle columns |
| F(n) | switch to tab |
| Alt(m) | toggle media pause and autoplay |
| Alt(M) | toggle media mute |
| Alt(>) | seek media +5s |
| Alt(<) | seek media -5s |
## File Browser (global effects):
Quit=q
QuitWithDir=Q
LeftColumnDown=]
LeftColumnUp=[
GotoHome=~
TurboCd=/
SelectExternal=M-Space
EnterDirExternal=M-/
RunInBackground=F
GotoPrevCwd=-
ShowBookmarks=`
AddBookmark=b
ShowProcesses=w
ShowLog=g
ShowQuickActions=a
RunSubshell=z
ToggleColumns=c
ExecCmd=!
## File List (affects current directory):
Search=S
SearchNext=M-s
SearchPrev=M-S
Filter=C-f
Select=Space
InvertSelection=v
ClearSelection=V
FilterSelection=M-V
ToggleTag=t
ToggleHidden=h
ReverseSort=r
CycleSort=s
ToNextMtime=K
ToPrevMtime=k
ToggleDirsFirst=d
## Tabs
NewTab=C-t
CloseTab=C-w
NextTab=Tab
PrevTab=BackTab
GotoTab(_)=F_
## Keybindings in bookmark popup:
## Media
TogglePause=M-m
ToggleMute=M-M
SeekForward=M->
SeekBackward=M-<
| Key | Action |
| ------------------- |:---------------------------------|
|(key) |open bookmark |
|` |goto last cwd |
|Ctrl(c) |cancel |
|Alt(key) |delete bookmark |
## Bookmarks
GotoLastCwd=`
Goto(_)=_
Delete(_)=M-_
## Keybindings in process viewer:
## Processes
Close=w, Esc
Remove=d
Kill=k
FollowOutput=f
ScrollOutputUp=C-p
ScrollOutputDown=C-n
ScrollOutputPageUp=C-V
ScrollOutputPageDown=C-v
ScrollOutputTop=C-<
ScrollOutputBottom=>
| Key | Action |
| ------------------- |:---------------------------------|
|w |close process viewer |
|d |remove process |
|k |kill process |
|k holy(p) |move up |
|j holy(n) |move down |
|f |toggle follow output |
|Ctrl(j) holy(Ctrl(n) |scroll output down |
|Ctrl(k) holy(Ctrl(p) |scroll output up |
|Ctrl(v) |page down |
|Alt(v) |page up |
|< |scroll to bottom |
|> |scroll to top |
## MiniBuffer
InsertChar(_)=_
InsertTab(_)=F_
Cancel=C-c, Esc
Finish=Enter
Complete=Tab
DeleteChar=C-d, Delete
BackwardDeleteChar=Backspace
CursorLeft=C-b, Left
CursorRight=C-f, Right
HistoryUp=C-p, M-p, Up
HistoryDown=C-n, M-n, Down
ClearLine=C-u
DeleteWord=C-h
CursorToStart=C-a, Home
CursorToEnd=C-e, End
## Folds
ToggleFold=t,Tab
## Keybindings in minibuffer:
## Log
Close=g,Esc
| Key | Action |
| ------------------- |:---------------------------------|
|Esc/Ctrl(c) |cancel input |
|Tab |complete |
|F(n) |insert tab substitution |
|Ctrl(d) |delete char |
|Ctrl(b) |move 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 |
## QuickActions
Close=a, Esc, C-a
SelectOrRun(_)=_

Binary file not shown.

View File

@ -6,6 +6,7 @@ use std::sync::RwLock;
use crate::paths;
use crate::fail::{HError, HResult, ErrorLog};
use crate::keybind::KeyBinds;
#[derive(Clone)]
@ -92,6 +93,7 @@ pub struct Config {
pub media_previewer: String,
pub ratios: Vec::<usize>,
pub graphics: String,
pub keybinds: KeyBinds,
}
@ -115,6 +117,7 @@ impl Config {
media_previewer: "hunter-media".to_string(),
ratios: vec![20,30,49],
graphics: detect_g_mode(),
keybinds: KeyBinds::default(),
}
}
@ -182,7 +185,12 @@ impl Config {
config
});
let config = infuse_argv_config(config);
let mut config = infuse_argv_config(config);
//use std::iter::Extend;
KeyBinds::load()
.map(|kb| config.keybinds = kb)
.log();
Ok(config)
}

View File

@ -1,7 +1,7 @@
use failure;
use failure::Fail;
//use failure::Backtrace;
use async_value::AError;
//use async_value::AError;
use termion::event::Key;
@ -102,10 +102,16 @@ pub enum HError {
UTF8ParseError(std::str::Utf8Error),
#[fail(display = "Failed to parse integer!")]
ParseIntError(std::num::ParseIntError),
#[fail(display = "Failed to parse char!")]
ParseCharError(std::char::ParseCharError),
#[fail(display = "{}", _0)]
Media(MediaError),
#[fail(display = "{}", _0)]
Mime(MimeError),
#[fail(display = "{}", _0)]
KeyBind(KeyBindError),
#[fail(display = "FileBrowser needs to know about all tab's files to run exec!")]
FileBrowserNeedTabFiles
}
impl HError {
@ -203,33 +209,64 @@ pub trait ErrorLog where Self: Sized {
fn log_and(self) -> Self;
}
impl<T> ErrorLog for HResult<T> {
// impl<T> ErrorLog for HResult<T> {
// fn log(self) {
// if let Err(err) = self {
// put_log(&err).ok();
// }
// }
// fn log_and(self) -> Self {
// if let Err(err) = &self {
// put_log(err).ok();
// }
// self
// }
// }
// impl<T> ErrorLog for Result<T, AError> {
// fn log(self) {
// if let Err(err) = self {
// put_log(&err.into()).ok();
// }
// }
// fn log_and(self) -> Self {
// if let Err(err) = &self {
// put_log(&err.clone().into()).ok();
// }
// self
// }
// }
impl<T, E> ErrorLog for Result<T, E>
where E: Into<HError> + Clone {
fn log(self) {
if let Err(err) = self {
let err: HError = err.into();
put_log(&err).ok();
}
}
fn log_and(self) -> Self {
if let Err(err) = &self {
put_log(err).ok();
if let Err(ref err) = self {
let err: HError = err.clone().into();
put_log(&err).ok();
}
self
}
}
impl<T> ErrorLog for Result<T, AError> {
impl<E> ErrorLog for E
where E: Into<HError> + Clone {
fn log(self) {
if let Err(err) = self {
put_log(&err.into()).ok();
}
}
let err: HError = self.into();
put_log(&err).ok();
}
fn log_and(self) -> Self {
if let Err(err) = &self {
put_log(&err.clone().into()).ok();
}
let err: HError = self.clone().into();
put_log(&err).ok();
self
}
}
@ -334,6 +371,13 @@ impl From<std::num::ParseIntError> for HError {
}
}
impl From<std::char::ParseCharError> for HError {
fn from(error: std::char::ParseCharError) -> Self {
let err = HError::ParseCharError(error);
err
}
}
// MIME Errors
@ -352,3 +396,36 @@ impl From<MimeError> for HError {
HError::Mime(e)
}
}
impl From<KeyBindError> for HError {
fn from(e: KeyBindError) -> Self {
HError::KeyBind(e)
}
}
#[derive(Fail, Debug, Clone)]
pub enum KeyBindError {
#[fail(display = "Movement has not been defined for this widget")]
MovementUndefined,
#[fail(display = "Keybind defined with wrong key: {} -> {}", _0, _1)]
WrongKey(String, String),
#[fail(display = "Defined keybind for non-existing action: {}", _0)]
WrongAction(String),
#[fail(display = "Failed to parse keybind: {}", _0)]
ParseKeyError(String),
#[fail(display = "Trouble with ini file! Error: {}", _0)]
IniError(Arc<ini::ini::Error>),
#[fail(display = "Couldn't parse as either char or u8: {}", _0)]
CharOrNumParseError(String),
#[fail(display = "Wanted {}, but got {}!", _0, _1)]
CharOrNumWrongType(String, String)
}
impl From<ini::ini::Error> for KeyBindError {
fn from(err: ini::ini::Error) -> Self {
KeyBindError::IniError(Arc::new(err))
}
}

View File

@ -1,4 +1,4 @@
use termion::event::{Event, Key};
use termion::event::Key;
use pathbuftools::PathBufTools;
use osstrtools::OsStrTools;
use async_value::Stale;
@ -126,6 +126,11 @@ impl Tabbable for TabView<FileBrowser> {
Ok(())
}
fn prev_tab(&mut self) -> HResult<()> {
self.prev_tab_();
Ok(())
}
fn goto_tab(&mut self, index: usize) -> HResult<()> {
self.goto_tab_(index)
}
@ -152,10 +157,11 @@ impl Tabbable for TabView<FileBrowser> {
}
fn on_key_sub(&mut self, key: Key) -> HResult<()> {
match key {
Key::Char('!') => {
match self.active_tab_mut().on_key(key) {
// returned by specific tab when called with ExecCmd action
Err(HError::FileBrowserNeedTabFiles) => {
let tab_dirs = self.widgets.iter().map(|w| w.cwd.clone())
.collect::<Vec<_>>();
.collect::<Vec<_>>();
let selected_files = self
.widgets
.iter()
@ -165,7 +171,7 @@ impl Tabbable for TabView<FileBrowser> {
self.widgets[self.active].exec_cmd(tab_dirs, selected_files)
}
_ => { self.active_tab_mut().on_key(key) }
result @ _ => result
}
}
@ -246,6 +252,7 @@ impl Tabbable for TabView<FileBrowser> {
impl FileBrowser {
pub fn new(core: &WidgetCore, cache: Option<FsCache>) -> HResult<FileBrowser> {
let fs_cache = cache.unwrap_or_else(|| FsCache::new(core.get_sender()));
@ -1077,7 +1084,6 @@ impl FileBrowser {
fn exec_cmd(&mut self,
tab_dirs: Vec<File>,
tab_files: Vec<Vec<File>>) -> HResult<()> {
let cwd = self.cwd()?.clone();
let selected_file = self.selected_file().ok();
let selected_files = self.selected_files().ok();
@ -1295,44 +1301,69 @@ impl Widget for FileBrowser {
}
fn on_key(&mut self, key: Key) -> HResult<()> {
match key {
Key::Char('a') => self.quick_action()?,
Key::Char(']') => self.move_down_left_widget()?,
Key::Char('[') => self.move_up_left_widget()?,
Key::Alt(' ') => self.external_select()?,
Key::Alt('/') => self.external_cd()?,
Key::Char('/') => { self.turbo_cd()?; },
Key::Char('~') => { self.go_home()?; },
Key::Char('q') => HError::quit()?,
Key::Char('Q') => { self.quit_with_dir()?; },
Key::Right | Key::Char('l') => { self.enter_dir()?; },
Key::Char('L') => { self.open_bg()?; },
Key::Left | Key::Char('h') => { self.go_back()?; },
Key::Char('-') => { self.goto_prev_cwd()?; },
Key::Char('`') => { self.goto_bookmark()?; },
Key::Char('m') => { self.add_bookmark()?; },
Key::Char('w') => { self.show_procview()?; },
Key::Char('g') => self.show_log()?,
Key::Char('z') => self.run_subshell()?,
Key::Char('c') => self.toggle_colums(),
_ => {
let main_widget_result = self.main_widget_mut()?.on_key(key);
if let Err(HError::WidgetUndefinedKeyError{..}) = main_widget_result {
match self.preview_widget_mut()?.on_key(key) {
Ok(()) => {}
Err(HError::WidgetUndefinedKeyError{key}) => {
self.bad(Event::Key(key))?;
}
err @ Err(_) => { err?; }
match self.do_key(key) {
Err(HError::WidgetUndefinedKeyError{..}) => {
match self.main_widget_mut()?.on_key(key) {
Err(HError::WidgetUndefinedKeyError{..}) => {
self.preview_widget_mut()?.on_key(key)?
}
e @ _ => e?
}
},
}
}
e @ _ => e?
};
if !self.columns.zoom_active { self.update_preview().log(); }
Ok(())
}
}
use crate::keybind::{Acting, Bindings, FileBrowserAction, Movement};
impl Acting for FileBrowser {
type Action=FileBrowserAction;
fn search_in(&self) -> Bindings<Self::Action> {
self.core.config().keybinds.filebrowser
}
fn movement(&mut self, movement: &Movement) -> HResult<()> {
use Movement::*;
match movement {
Left => self.go_back(),
Right => self.enter_dir(),
_ => self.main_widget_mut()?.movement(movement)
}
}
fn do_action(&mut self, action: &Self::Action) -> HResult<()> {
use FileBrowserAction::*;
match action {
Quit => HError::quit()?,
QuitWithDir => self.quit_with_dir()?,
LeftColumnDown => self.move_down_left_widget()?,
LeftColumnUp => self.move_up_left_widget()?,
GotoHome => self.go_home()?,
TurboCd => self.turbo_cd()?,
SelectExternal => self.external_select()?,
EnterDirExternal => self.external_cd()?,
RunInBackground => self.open_bg()?,
GotoPrevCwd => self.goto_prev_cwd()?,
ShowBookmarks => self.goto_bookmark()?,
AddBookmark => self.add_bookmark()?,
ShowProcesses => self.show_procview()?,
ShowLog => self.show_log()?,
ShowQuickActions => self.quick_action()?,
RunSubshell => self.run_subshell()?,
ToggleColumns => self.toggle_colums(),
// Tab implementation needs to call exec_cmd because ALL files are needed
ExecCmd => Err(HError::FileBrowserNeedTabFiles)?
}
Ok(())
}
}
impl PartialEq for FileBrowser {
fn eq(&self, other: &FileBrowser) -> bool {
if self.columns == other.columns && self.cwd == other.cwd {

View File

@ -5,8 +5,9 @@ use chrono::{DateTime, Local};
use crate::term;
use crate::widget::Widget;
use crate::listview::{ListView, Listable};
use crate::fail::{HResult, HError};
use crate::fail::{HResult, HError, KeyBindError};
use crate::dirty::Dirtyable;
use crate::keybind::{Acting, AnyKey, Bindings, BindingSection, Movement, FoldAction, LogAction};
pub type LogView = ListView<Vec<LogEntry>>;
@ -81,9 +82,73 @@ impl From<&HError> for LogEntry {
}
}
pub trait ActingExt
where
Self::Action: BindingSection + std::fmt::Debug,
Bindings<Self::Action>: Default,
Self: Widget
{
type Action;
fn search_in(&self) -> Bindings<Self::Action>;
fn movement(&mut self, _movement: &Movement) -> HResult<()> {
Err(KeyBindError::MovementUndefined)?
}
fn do_key_ext(&mut self, key: Key) -> HResult<()> {
let gkey = AnyKey::from(key);
pub trait FoldableWidgetExt {
// Moving takes priority
if let Some(movement) = self.get_core()?
.config()
.keybinds
.movement
.get(gkey) {
match self.movement(movement) {
Ok(()) => return Ok(()),
Err(HError::KeyBind(KeyBindError::MovementUndefined)) => {}
Err(e) => Err(e)?
}
}
self.search_in();
let bindings = self.search_in();
if let Some(action) = bindings.get(key) {
return self.do_action(action)
} else if let Some(any_key) = gkey.any() {
if let Some(action) = bindings.get(any_key) {
let action = action.insert_key_param(key);
return self.do_action(&action);
}
}
HError::undefined_key(key)
}
fn do_action(&mut self, _action: &Self::Action) -> HResult<()> {
Err(KeyBindError::MovementUndefined)?
}
}
impl ActingExt for ListView<Vec<LogEntry>> {
type Action = LogAction;
fn search_in(&self) -> Bindings<Self::Action> {
self.core.config().keybinds.log
}
fn do_action(&mut self, action: &Self::Action) -> HResult<()> {
match action {
LogAction::Close => self.popup_finnished()
}
}
}
pub trait FoldableWidgetExt
where
Self: ActingExt,
Bindings<<Self as ActingExt>::Action>: Default
{
fn on_refresh(&mut self) -> HResult<()> { Ok(()) }
fn render_header(&self) -> HResult<String> { Ok("".to_string()) }
fn render_footer(&self) -> HResult<String> { Ok("".to_string()) }
@ -204,7 +269,8 @@ pub trait Foldable {
impl<F: Foldable> ListView<Vec<F>>
where
ListView<Vec<F>>: FoldableWidgetExt {
ListView<Vec<F>>: FoldableWidgetExt,
Bindings<<ListView<Vec<F>> as ActingExt>::Action>: Default {
pub fn toggle_fold(&mut self) -> HResult<()> {
let fold = self.current_fold()?;
@ -257,7 +323,9 @@ where
impl<F: Foldable> Listable for ListView<Vec<F>>
where
ListView<Vec<F>>: FoldableWidgetExt {
ListView<Vec<F>>: FoldableWidgetExt,
Bindings<<ListView<Vec<F>> as ActingExt>::Action>: Default
{
fn len(&self) -> usize {
self.content.iter().map(|f| f.lines()).sum()
@ -294,21 +362,42 @@ where
}
fn on_key(&mut self, key: Key) -> HResult<()> {
// this on_key() could have been implmented by some type
let result = FoldableWidgetExt::on_key(self, key);
if let Err(HError::WidgetUndefinedKeyError{key}) = result {
match key {
Key::Up | Key::Char('k') => self.move_up(),
Key::Char('K') => for _ in 0..10 { self.move_up() },
Key::Char('J') => for _ in 0..10 { self.move_down() },
Key::Down | Key::Char('j') => self.move_down(),
Key::Char('t') => self.toggle_fold()?,
Key::Char('g') | Key::Esc => self.popup_finnished()?,
_ => { HError::undefined_key(key)?; },
}
// Key was defined, or _ match would have returned undefined key
return Ok(());
match ActingExt::do_key_ext(self, key) {
Err(HError::PopupFinnished) => Err(HError::PopupFinnished),
_ => self.do_key(key)
}
}
}
impl<F: Foldable> Acting for ListView<Vec<F>>
where
ListView<Vec<F>>: FoldableWidgetExt,
Bindings<<ListView<Vec<F>> as ActingExt>::Action>: Default
{
type Action = FoldAction;
fn search_in(&self) -> Bindings<Self::Action> {
self.core.config().keybinds.fold
}
fn movement(&mut self, movement: &Movement) -> HResult<()> {
use Movement::*;
match movement {
Up(n) => for _ in 0..*n { self.move_up() },
Down(n) => for _ in 0..*n { self.move_down() },
_ => { Err(KeyBindError::MovementUndefined)? },
}
Ok(())
}
fn do_action(&mut self, action: &FoldAction) -> HResult<()> {
use FoldAction::*;
match action {
ToggleFold => self.toggle_fold()
}
result
}
}

1094
src/keybind.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,56 @@ pub trait Listable {
fn on_key(&mut self, _key: Key) -> HResult<()> { Ok(()) }
}
use crate::keybind::{Acting, Bindings, FileListAction, Movement};
impl Acting for ListView<Files> {
type Action=FileListAction;
fn search_in(&self) -> Bindings<Self::Action> {
self.core.config().keybinds.filelist
}
fn movement(&mut self, movement: &Movement) -> HResult<()> {
use Movement::*;
match movement {
Up(n) => { for _ in 0..*n { self.move_up(); }; self.refresh()?; }
Down(n) => { for _ in 0..*n { self.move_down(); }; self.refresh()?; }
PageUp => self.page_up(),
PageDown => self.page_down(),
Top => self.move_top(),
Bottom => self.move_bottom(),
Left | Right => {}
}
Ok(())
}
fn do_action(&mut self, action: &Self::Action) -> HResult<()> {
use FileListAction::*;
match action {
Search => self.search_file()?,
SearchNext => self.search_next()?,
SearchPrev => self.search_prev()?,
Filter => self.filter()?,
Select => self.multi_select_file(),
InvertSelection => self.invert_selection(),
ClearSelection => self.clear_selections(),
FilterSelection => self.toggle_filter_selected(),
ToggleTag => self.toggle_tag()?,
ToggleHidden => self.toggle_hidden(),
ReverseSort => self.reverse_sort(),
CycleSort => self.cycle_sort(),
ToNextMtime => self.select_next_mtime(),
ToPrevMtime => self.select_prev_mtime(),
ToggleDirsFirst => self.toggle_dirs_first(),
}
Ok(())
}
}
impl Listable for ListView<Files> {
fn len(&self) -> usize {
self.content.len()
@ -60,41 +110,7 @@ impl Listable for ListView<Files> {
}
fn on_key(&mut self, key: Key) -> HResult<()> {
match key {
Key::Up | Key::Char('k') => {
self.move_up();
self.refresh()?;
}
Key::Char('K') => { for _ in 0..10 { self.move_up() } self.refresh()?; }
Key::Char('J') => { for _ in 0..10 { self.move_down() } self.refresh()?; }
Key::Down | Key::Char('j') => {
self.move_down();
self.refresh()?;
},
Key::PageUp => self.page_up(),
Key::PageDown => self.page_down(),
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::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('V') => self.clear_selections(),
Key::Alt('v') => self.toggle_filter_selected(),
Key::Char('t') => self.toggle_tag()?,
Key::Char('H') => self.toggle_hidden(),
Key::Char('r') => self.reverse_sort(),
Key::Char('s') => self.cycle_sort(),
Key::Char('N') => self.select_next_mtime(),
Key::Char('n') => self.select_prev_mtime(),
Key::Char('d') => self.toggle_dirs_first(),
_ => { HError::undefined_key(key)? }
}
Ok(())
self.do_key(key)
}
}

View File

@ -24,6 +24,9 @@ extern crate systemstat;
extern crate mime_guess;
extern crate mime;
extern crate clap;
extern crate strum;
#[macro_use]
extern crate strum_macros;
extern crate osstrtools;
extern crate pathbuftools;
@ -61,7 +64,7 @@ mod trait_ext;
mod config_installer;
mod imgview;
mod mediaview;
mod keybind;

View File

@ -527,13 +527,7 @@ impl Widget for MediaView {
}
fn on_key(&mut self, key: Key) -> HResult<()> {
match key {
Key::Alt('>') => self.seek_forward(),
Key::Alt('<') => self.seek_backward(),
Key::Alt('m') => self.toggle_pause(),
Key::Alt('M') => Ok(self.toggle_mute()),
_ => HError::undefined_key(key)
}
self.do_key(key)
}
}
@ -545,3 +539,27 @@ impl Drop for MediaView {
self.core.clear().log();
}
}
use crate::keybind::*;
impl Acting for MediaView {
type Action = MediaAction;
fn search_in(&self) -> Bindings<Self::Action> {
self.core.config().keybinds.media
}
fn do_action(&mut self, action: &MediaAction) -> HResult<()> {
use MediaAction::*;
match action {
SeekForward => self.seek_forward()?,
SeekBackward => self.seek_backward()?,
TogglePause => self.toggle_pause()?,
ToggleMute => self.toggle_mute(),
}
Ok(())
}
}

View File

@ -433,62 +433,7 @@ impl Widget for MiniBuffer {
fn on_key(&mut self, key: Key) -> HResult<()> {
let prev_input = self.input.clone();
match key {
Key::Esc | Key::Ctrl('c') => {
self.clear();
self.input_cancelled()?;
},
Key::Char('\n') => {
if self.input != "" {
self.history.add(&self.query, &self.input);
}
self.input_finnished()?;
}
Key::Char('\t') => {
self.complete()?;
}
Key::F(n) => {
let fnstr = format!("${}", n-1);
self.input.insert_str(self.position, &fnstr);
self.position += 2;
}
Key::Backspace => {
if self.position != 0 {
self.input.remove(self.position - 1);
self.position -= 1;
}
}
Key::Delete | Key::Ctrl('d') => {
if self.position != self.input.len() {
self.input.remove(self.position);
}
}
Key::Left | Key::Ctrl('b') => {
if self.position != 0 {
self.position -= 1;
}
}
Key::Right | Key::Ctrl('f') => {
if self.position != self.input.len() {
self.position += 1;
}
}
Key::Up | Key::Ctrl('p') | Key::Alt('p') => {
self.history_up()?;
}
Key::Down | Key::Ctrl('n') | Key::Alt('n') => {
self.history_down()?;
}
Key::Ctrl('u') => { self.clear_line()?; },
Key::Ctrl('h') => { self.delete_word()?; },
Key::Ctrl('a') => { self.position = 0 },
Key::Ctrl('e') => { self.position = self.input.len(); },
Key::Char(key) => {
self.input.insert(self.position, key);
self.position += 1;
}
_ => { }
}
self.do_key(key)?;
if self.continuous && prev_input != self.input {
self.input_updated()?;
@ -511,3 +456,65 @@ impl Widget for MiniBuffer {
Ok(())
}
}
use crate::keybind::*;
impl Acting for MiniBuffer {
type Action = MiniBufferAction;
fn search_in(&self) -> Bindings<Self::Action> {
self.core.config().keybinds.minibuffer
}
fn do_action(&mut self, action: &Self::Action) -> HResult<()> {
use MiniBufferAction::*;
match action {
InsertChar(ch) => {
self.input.insert(self.position, *ch);
self.position += 1;
}
InsertTab(n) => {
let fnstr = format!("${}", n-1);
self.input.insert_str(self.position, &fnstr);
self.position += 2;
}
Cancel => { self.clear(); self.input_cancelled()? }
Finish => {
if self.input != "" {
self.history.add(&self.query, &self.input);
}
self.input_finnished()?
},
Complete => self.complete()?,
DeleteChar => {
if self.position != self.input.len() {
self.input.remove(self.position);
}
},
BackwardDeleteChar => {
if self.position != 0 {
self.input.remove(self.position - 1);
self.position -= 1;
}
}
CursorLeft => {
if self.position != 0 {
self.position -= 1;
}
},
CursorRight => {
if self.position != self.input.len() {
self.position += 1;
}
},
HistoryUp => self.history_up()?,
HistoryDown => self.history_down()?,
ClearLine => self.clear_line()?,
DeleteWord => self.delete_word()?,
CursorToStart => self.position = 0,
CursorToEnd => self.position = self.input.len(),
}
Ok(())
}
}

View File

@ -36,6 +36,12 @@ pub fn config_path() -> HResult<PathBuf> {
Ok(config_path)
}
pub fn bindings_path() -> HResult<PathBuf> {
let mut config_path = hunter_path()?;
config_path.push("keys");
Ok(config_path)
}
pub fn bookmark_path() -> HResult<PathBuf> {
let mut bookmark_path = hunter_path()?;
bookmark_path.push("bookmarks");

View File

@ -656,30 +656,10 @@ impl Widget for ProcView {
self.hbox.get_drawlist()
}
fn on_key(&mut self, key: Key) -> HResult<()> {
match key {
Key::Char('w') | Key::Esc => {
self.animator.set_stale().log();
self.get_core()?.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('k') => {
self.get_listview_mut().move_up();
}
Key::Down | Key::Char('j') => {
self.get_listview_mut().move_down();
}
Key::Char('f') => { self.toggle_follow().log(); }
Key::Ctrl('j') => { self.scroll_down().log(); },
Key::Ctrl('k') => { self.scroll_up().log(); },
Key::Ctrl('v') => { self.page_down().log(); },
Key::Alt('v') => { self.page_up().log(); },
Key::Char('>') => { self.scroll_bottom().log(); },
Key::Char('<') => { self.scroll_top().log(); }
_ => {}
}
self.do_key(key)?;
self.refresh().log();
self.draw().log();
Ok(())
}
}
@ -730,3 +710,67 @@ impl ConcatOsString for Vec<OsString> {
})
}
}
use crate::keybind::*;
impl Acting for ProcView {
type Action = ProcessAction;
fn search_in(&self) -> Bindings<Self::Action> {
self.core.config().keybinds.process
}
fn movement(&mut self, movement: &Movement) -> HResult<()> {
self.get_listview_mut().movement(movement)
}
fn do_action(&mut self, action: &Self::Action) -> HResult<()> {
use ProcessAction::*;
match action {
Close => { self.animator.set_stale().log();
self.core.clear().log();
Err(HError::PopupFinnished)? }
Remove => self.remove_proc()?,
Kill => self.get_listview_mut().kill_proc()?,
FollowOutput => self.toggle_follow()?,
ScrollOutputDown => self.scroll_down()?,
ScrollOutputUp => self.scroll_up()?,
ScrollOutputPageDown => self.page_down()?,
ScrollOutputPageUp => self.page_up()?,
ScrollOutputBottom => self.scroll_bottom()?,
ScrollOutputTop => self.scroll_top()?
}
Ok(())
}
}
impl Acting for ListView<Vec<Process>> {
type Action=ProcessAction;
fn search_in(&self) -> Bindings<Self::Action> {
self.core.config().keybinds.process
}
fn movement(&mut self, movement: &Movement) -> HResult<()> {
use Movement::*;
match movement {
Up(n) => { for _ in 0..*n { self.move_up(); }; self.refresh()?; }
Down(n) => { for _ in 0..*n { self.move_down(); }; self.refresh()?; }
PageUp => self.page_up(),
PageDown => self.page_down(),
Top => self.move_top(),
Bottom => self.move_bottom(),
Left | Right => {}
}
Ok(())
}
fn do_action(&mut self, _action: &Self::Action) -> HResult<()> {
Ok(())
}
}

View File

@ -12,15 +12,16 @@ use std::ffi::OsString;
use std::str::FromStr;
use crate::fail::{HResult, HError};
use crate::fail::{HResult, HError, KeyBindError};
use crate::widget::{Widget, WidgetCore, Events};
use crate::foldview::{Foldable, FoldableWidgetExt};
use crate::foldview::{Foldable, FoldableWidgetExt, ActingExt};
use crate::listview::ListView;
use crate::proclist::ProcView;
use crate::files::File;
use crate::paths;
use crate::term;
use crate::term::ScreenExt;
use crate::keybind::{Bindings, Movement, QuickActionAction};
pub type QuickActionView = ListView<Vec<QuickActions>>;
@ -66,47 +67,7 @@ impl FoldableWidgetExt for ListView<Vec<QuickActions>> {
}
fn on_key(&mut self, key: Key) -> HResult<()> {
match key {
Key::Char('a') |
Key::Char('h') |
Key::Ctrl('c') |
Key::Esc => HError::popup_finnished()?,
// undefined key causes parent to handle move up/down
Key::Char('j') => HError::undefined_key(key)?,
Key::Char('k') => HError::undefined_key(key)?,
Key::Char('l') => self.run_action(None),
key @ Key::Char(_) => {
let chr = match key {
Key::Char(key) => key,
// some other key that becomes None with letter_to_num()
_ => 'x'
};
let num = self.letter_to_num(chr);
if let Some(num) = num {
// only select the action at first, to prevent accidents
if self.get_selection() != num {
self.set_selection(num);
return Ok(());
// activate the action the second time the key is pressed
} else {
if self.is_description_selected() {
self.toggle_fold()?;
} else {
self.run_action(Some(num))?;
HError::popup_finnished()?
}
}
}
// Was a valid key, but not used, don't handle at parent
return Ok(());
}
_ => HError::undefined_key(key)?
}?;
HError::popup_finnished()?
ActingExt::do_key_ext(self,key)
}
fn render(&self) -> Vec<String> {
@ -131,6 +92,50 @@ impl FoldableWidgetExt for ListView<Vec<QuickActions>> {
}
}
impl ActingExt for QuickActionView {
type Action = QuickActionAction;
fn search_in(&self) -> Bindings<Self::Action> {
self.core.config().keybinds.quickaction
}
fn movement(&mut self, movement: &Movement) -> HResult<()> {
match movement {
Movement::Right => self.run_action(None),
_ => Err(KeyBindError::MovementUndefined)?
}
}
fn do_action(&mut self, action: &Self::Action) -> HResult<()> {
use crate::keybind::QuickActionAction::*;
match action {
Close => self.popup_finnished(),
SelectOrRun(chr) => {
let num = self.letter_to_num(*chr);
if let Some(num) = num {
// only select the action at first, to prevent accidents
if self.get_selection() != num {
self.set_selection(num);
return Ok(());
// activate the action the second time the key is pressed
} else {
if self.is_description_selected() {
self.toggle_fold()?;
} else {
self.run_action(Some(num))?;
HError::popup_finnished()?
}
}
}
Ok(())
}
}
}
}
impl ListView<Vec<QuickActions>> {
fn render(&self) -> Vec<String> {

View File

@ -1,13 +1,14 @@
use termion::event::Key;
use crate::widget::{Widget, WidgetCore};
use crate::fail::{HResult, ErrorLog};
use crate::fail::{HResult, HError, ErrorLog};
use crate::coordinates::Coordinates;
pub trait Tabbable {
fn new_tab(&mut self) -> HResult<()>;
fn close_tab(&mut self) -> HResult<()>;
fn next_tab(&mut self) -> HResult<()>;
fn prev_tab(&mut self) -> HResult<()>;
fn goto_tab(&mut self, index: usize) -> HResult<()>;
fn on_tab_switch(&mut self) -> HResult<()> {
Ok(())
@ -17,13 +18,7 @@ pub trait Tabbable {
fn active_tab_mut(&mut self) -> &mut dyn Widget;
fn on_key_sub(&mut self, key: Key) -> HResult<()>;
fn on_key(&mut self, key: Key) -> HResult<()> {
match key {
Key::F(n) => self.goto_tab(n as usize -1),
Key::Ctrl('t') => self.new_tab(),
Key::Ctrl('w') => self.close_tab(),
Key::Char('\t') => self.next_tab(),
_ => self.on_key_sub(key)
}
self.on_key_sub(key)
}
fn on_refresh(&mut self) -> HResult<()> { Ok(()) }
fn on_config_loaded(&mut self) -> HResult<()> { Ok(()) }
@ -105,6 +100,15 @@ impl<T> TabView<T> where T: Widget, TabView<T>: Tabbable {
}
self.on_tab_switch().log();
}
pub fn prev_tab_(&mut self) {
if self.active == 0 {
self.active = self.widgets.len() - 1;
} else {
self.active -= 1;
}
self.on_tab_switch().log();
}
}
impl<T> Widget for TabView<T> where T: Widget, TabView<T>: Tabbable {
@ -173,7 +177,35 @@ impl<T> Widget for TabView<T> where T: Widget, TabView<T>: Tabbable {
}
fn on_key(&mut self, key: Key) -> HResult<()> {
Tabbable::on_key(self, key)?;
match self.do_key(key) {
Err(HError::WidgetUndefinedKeyError{..}) => Tabbable::on_key(self, key)?,
e @ _ => e?
}
Ok(())
}
}
use crate::keybind::*;
impl<T: Widget> Acting for TabView<T> where TabView<T>: Tabbable {
type Action = TabAction;
fn search_in(&self) -> Bindings<Self::Action> {
self.core.config().keybinds.tab
}
fn do_action(&mut self, action: &Self::Action) -> HResult<()> {
use TabAction::*;
match action {
GotoTab(n) => self.goto_tab(*n)?,
NewTab => self.new_tab()?,
CloseTab => self.close_tab()?,
NextTab => self.next_tab()?,
PrevTab => self.prev_tab()?,
}
Ok(())
}
}