summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSam Tay <sam.chong.tay@gmail.com>2020-06-16 12:41:09 -0700
committerSam Tay <sam.chong.tay@gmail.com>2020-06-16 12:41:09 -0700
commitbd3e479aaf76ea6ec94a680123b393eca971387f (patch)
tree1ef5a36601b40bb6ecdd46f6cd3d34f4336cb523
parentf1be144cb9a598b50bb25bf76e57375035e29ae3 (diff)
Add vim bindings everywhere
-rw-r--r--TODO.md5
-rw-r--r--src/tui/app.rs21
-rw-r--r--src/tui/views.rs135
3 files changed, 127 insertions, 34 deletions
diff --git a/TODO.md b/TODO.md
index ee4c148..bd5924d 100644
--- a/TODO.md
+++ b/TODO.md
@@ -11,9 +11,12 @@ changing [soon](https://meta.stackexchange.com/q/348746).
#### Cursive interface for viewing questions and answers
2. Handle focus with h,l
5. Init with smaller layout if terminal size smaller?
-3. maybe cli --auto-resize option
+3. maybe cli `--auto-resize` option
3. make the default colors.toml file have a banging RGB > Hex > Defaults fallback
6. Small text at bottom with '?' to bring up key mapping dialog
+7. Clean up! remove dupe between ListView and MdView by making a common trait
+8. Possibly make `--arrow_keys_focus` option to toggle the arrow key consumption
+9. Maybe **ESC** cycles layout in the opposite direction?
#### other
1. Use [par_iter](https://github.com/rayon-rs/rayon) for text preprocess & parsing?
diff --git a/src/tui/app.rs b/src/tui/app.rs
index 3d00ddc..7280cac 100644
--- a/src/tui/app.rs
+++ b/src/tui/app.rs
@@ -9,8 +9,8 @@ use std::sync::Arc;
use super::markdown;
use super::views::{
- LayoutView, ListView, MdView, Name, NAME_ANSWER_LIST, NAME_ANSWER_VIEW, NAME_QUESTION_LIST,
- NAME_QUESTION_VIEW,
+ LayoutView, ListView, MdView, Name, Vimable, NAME_ANSWER_LIST, NAME_ANSWER_VIEW,
+ NAME_QUESTION_LIST, NAME_QUESTION_VIEW,
};
use crate::config;
use crate::error::Result;
@@ -83,13 +83,16 @@ pub fn run(qs: Vec<Question>) -> Result<()> {
s.call_on_name(NAME_ANSWER_VIEW, |v: &mut MdView| v.set_content(&a.body));
});
- siv.add_layer(LayoutView::new(
- 1,
- question_list_view,
- question_view,
- answer_list_view,
- answer_view,
- ));
+ siv.add_layer(
+ LayoutView::new(
+ 1,
+ question_list_view,
+ question_view,
+ answer_list_view,
+ answer_view,
+ )
+ .add_vim_bindings(),
+ );
let cb = siv.call_on_name(NAME_QUESTION_LIST, |v: &mut ListView| v.select(0));
if let Some(cb) = cb {
diff --git a/src/tui/views.rs b/src/tui/views.rs
index a093eda..6558d25 100644
--- a/src/tui/views.rs
+++ b/src/tui/views.rs
@@ -1,15 +1,16 @@
-use cursive::event::{Callback, Event, EventResult};
+use cursive::event::{Callback, Event, EventResult, Key};
use cursive::traits::{Finder, Nameable, Resizable, Scrollable};
use cursive::utils::markup::StyledString;
use cursive::view::{Margins, SizeConstraint, View, ViewWrapper};
use cursive::views::{
- HideableView, LinearLayout, NamedView, OnEventView, PaddedView, Panel, ResizedView, ScrollView,
- SelectView, TextView,
+ HideableView, LinearLayout, NamedView, PaddedView, Panel, ResizedView, ScrollView, SelectView,
+ TextView,
};
use cursive::{Cursive, Vec2, XY};
use std::fmt;
use std::fmt::Display;
use std::rc::Rc;
+use std::time;
use super::markdown;
use crate::error::Result;
@@ -19,6 +20,8 @@ pub const NAME_ANSWER_LIST: &str = "answer_list";
pub const NAME_QUESTION_VIEW: &str = "question_view";
pub const NAME_ANSWER_VIEW: &str = "answer_view";
pub const NAME_FULL_LAYOUT: &str = "full_layout";
+// This is in std lib but currently unstable
+const SECOND: time::Duration = time::Duration::from_secs(1);
// TODO might need resizable wrappers in types
@@ -70,11 +73,9 @@ trait Hide {
}
}
-// TODO maybe I should use cursive's ListView over SelectView ?
-// TODO copy one of them to allow overriding selected style => reverse video
-pub type ListView = ListViewT<
- HideableView<ResizedView<Panel<ScrollView<OnEventView<NamedView<SelectView<u32>>>>>>>,
->;
+// TODO Copy select_view to to allow overriding selected style => reverse video
+pub type ListView =
+ ListViewT<HideableView<ResizedView<Panel<ScrollView<NamedView<SelectView<u32>>>>>>>;
pub struct ListViewT<T: View> {
inner_name: String,
@@ -85,9 +86,26 @@ pub struct ListViewT<T: View> {
impl<T: View> ViewWrapper for ListViewT<T> {
cursive::wrap_impl!(self.view: T);
+ // In full screen mode we always take focus, even though currently hidden
fn wrap_take_focus(&mut self, source: cursive::direction::Direction) -> bool {
self.force_take_focus || self.view.take_focus(source)
}
+
+ // Always take arrow keys, its jarring to have them move pane focus
+ fn wrap_on_event(&mut self, event: Event) -> EventResult {
+ let should_consume = match event {
+ Event::Key(Key::Right)
+ | Event::Key(Key::Left)
+ | Event::Key(Key::Down)
+ | Event::Key(Key::Up) => true,
+ _ => false,
+ };
+
+ match self.view.on_event(event) {
+ EventResult::Ignored if should_consume => EventResult::Consumed(None),
+ event_result => event_result,
+ }
+ }
}
impl ListView {
@@ -119,7 +137,6 @@ impl ListView {
view.add_all(items);
}
let view = view.with_name(&inner_name);
- let view = add_vim_bindings(view);
let view = view.scrollable();
let view = Panel::new(view).title(format!("{}", name));
let view = view.resized(SizeConstraint::Free, SizeConstraint::Free);
@@ -193,6 +210,22 @@ impl<T: View> ViewWrapper for MdViewT<T> {
fn wrap_take_focus(&mut self, source: cursive::direction::Direction) -> bool {
self.force_take_focus || self.view.take_focus(source)
}
+
+ // Always take arrow keys, its jarring to have them move pane focus
+ fn wrap_on_event(&mut self, event: Event) -> EventResult {
+ let should_consume = match event {
+ Event::Key(Key::Right)
+ | Event::Key(Key::Left)
+ | Event::Key(Key::Down)
+ | Event::Key(Key::Up) => true,
+ _ => false,
+ };
+
+ match self.view.on_event(event) {
+ EventResult::Ignored if should_consume => EventResult::Consumed(None),
+ event_result => event_result,
+ }
+ }
}
impl MdView {
@@ -277,9 +310,6 @@ pub enum Layout {
impl ViewWrapper for LayoutView {
cursive::wrap_impl!(self.view: PaddedView<LinearLayout>);
- // TODO what the actual fuck is wrong with this lifetime?
- // cursive does this shit all over the place...
- // For now just issue a call_on_name like an asshat
fn wrap_on_event(&mut self, event: Event) -> EventResult {
match event {
Event::WindowResize => {
@@ -482,16 +512,73 @@ impl LayoutView {
}
}
-fn add_vim_bindings<T: 'static>(
- view: NamedView<SelectView<T>>,
-) -> OnEventView<NamedView<SelectView<T>>>
-where
-{
- OnEventView::new(view)
- .on_pre_event_inner('k', |s, _| {
- Some(EventResult::Consumed(Some(s.get_mut().select_up(1))))
- })
- .on_pre_event_inner('j', |s, _| {
- Some(EventResult::Consumed(Some(s.get_mut().select_down(1))))
- })
+/// Note that as it stands, this is very intrusive, and disallows the idea of
+/// having an inner EditView. If more nuance is needed later, then it will keep
+/// track of a `Mode = Insert | Command`
+pub struct VimBindingsView<T: View> {
+ last_event: Option<(Event, time::Instant)>, // TODO add time and check elapsed
+ view: T,
}
+
+impl<T: View> ViewWrapper for VimBindingsView<T> {
+ cursive::wrap_impl!(self.view: T);
+
+ fn wrap_on_event(&mut self, event: Event) -> EventResult {
+ // TODO add more
+ match event {
+ Event::Char('g') => {
+ let now = time::Instant::now();
+ match self.last_event {
+ Some((Event::Char('g'), then)) if now.duration_since(then) < SECOND => {
+ self.last_event = None;
+ return self.view.on_event(Event::Key(Key::Home));
+ }
+ _ => self.last_event = Some((Event::Char('g'), now)),
+ }
+ }
+ _ => self.last_event = None,
+ }
+ match event {
+ Event::Char('h') => {
+ return self.view.on_event(Event::Key(Key::Left));
+ }
+ Event::Char('j') => {
+ return self.view.on_event(Event::Key(Key::Down));
+ }
+ Event::Char('k') => {
+ return self.view.on_event(Event::Key(Key::Up));
+ }
+ Event::Char('l') => {
+ return self.view.on_event(Event::Key(Key::Right));
+ }
+ Event::CtrlChar('d') | Event::CtrlChar('f') => {
+ return self.view.on_event(Event::Key(Key::PageDown));
+ }
+ Event::CtrlChar('u') | Event::CtrlChar('b') => {
+ return self.view.on_event(Event::Key(Key::PageUp));
+ }
+ Event::Char('G') => {
+ return self.view.on_event(Event::Key(Key::End));
+ }
+ _ => (),
+ }
+ self.view.on_event(event)
+ }
+}
+
+impl<T: View> VimBindingsView<T> {
+ fn new(view: T) -> Self {
+ VimBindingsView {
+ view,
+ last_event: None,
+ }
+ }
+}
+
+pub trait Vimable: View + Sized {
+ fn add_vim_bindings(self) -> VimBindingsView<Self> {
+ VimBindingsView::new(self)
+ }
+}
+
+impl<T: View> Vimable for T {}