From b47bcc4967097535b4b740c2e00c752498c3e39f Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sat, 16 Jan 2021 11:40:36 +0100 Subject: Implementation of a vim-like bar with bindings processor --- src/bindings.rs | 153 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + src/main_view.rs | 78 ++++++++++++++++------------ 3 files changed, 200 insertions(+), 32 deletions(-) create mode 100644 src/bindings.rs diff --git a/src/bindings.rs b/src/bindings.rs new file mode 100644 index 0000000..3b4587c --- /dev/null +++ b/src/bindings.rs @@ -0,0 +1,153 @@ +use anyhow::Result; +use cursive::Cursive; +use cursive::Printer; +use cursive::Rect; +use cursive::View; +use cursive::XY; +use cursive::direction::Direction; +use cursive::event::Callback; +use cursive::event::Event; +use cursive::event::EventResult; +use cursive::event::Key; +use cursive::view::Nameable; +use cursive::view::Selector; +use cursive::view::SizeConstraint; +use cursive::views::NamedView; +use cursive::views::ResizedView; + +use crate::main_view::MainView; + +pub fn get_bindings() -> Bindings { + Bindings(vec![ + Binding { + chars: vec!['q'], + callback: Callback::from_fn(|siv: &mut Cursive| { + trace!("Callback called: q"); + let continue_running = siv.call_on_name(crate::main_view::MAIN_VIEW_NAME, |mv: &mut MainView| { + if mv.tabs().tab_order().len() == 1 { + false + } else { + if let Some(key) = mv.tabs().active_tab().cloned() { + debug!("Removing tab: {}", key); + if let Err(e) = mv.tabs_mut().remove_tab(&key) { + error!("{:?}", e); // TODO do more than just logging + } + debug!("Remove tab"); + } else { + debug!("No tab to remove found."); + } + true + } + }) + .unwrap_or(true); + + if !continue_running { + debug!("Byebye"); + siv.quit(); + } + }) + }, + + Binding { + chars: vec!['o'], + callback: Callback::from_fn(MainView::add_notmuch_query_layer), + } + ]) +} + +pub const BINDINGS_CALLER: &str = "BINDINGS_CALLER"; + +#[derive(Clone)] +pub struct Bindings(Vec); + +impl Bindings { + pub fn new(bs: Vec) -> Bindings { + Bindings(bs) + } + + pub fn caller(&self) -> BindingCaller { + BindingCaller::new(self.clone()) + } +} + +#[derive(Clone)] +pub struct Binding { + chars: Vec, + callback: Callback, +} + +impl Binding { + pub fn for_events(chars: Vec, callback: Callback) -> Binding { + Binding { chars, callback } + } +} + +pub struct BindingCaller { + configuration: Bindings, + state: Vec +} + +impl BindingCaller { + pub fn new(configuration: Bindings) -> Self { + BindingCaller { configuration, state: Vec::new() } + } + + pub fn process(&mut self, chr: char) { + debug!("Char = {}", chr); + self.state.push(chr); + } + + pub fn finalize(&self) -> Option { + self.configuration + .0 + .iter() + .find(|binding| { + trace!("chars {:?} == state {:?}", binding.chars, self.state); + binding.chars == self.state + }) + .map(|binding| { + trace!("Binding found"); + binding.callback.clone() + }) + } + +} + +impl View for BindingCaller { + fn draw(&self, printer: &Printer) { + trace!("Drawing with offset = {:?}", printer.offset); + trace!("Drawing with output_size = {:?}", printer.output_size); + trace!("Drawing with size = {:?}", printer.size); + + { + let line = "-".repeat(printer.output_size.x); + let position = XY { + x: printer.offset.x, + y: printer.output_size.y - 3, + }; + printer.print(position, &line) + } + + { + let line = format!(":{}", self.state.iter().collect::()); + let position = XY { + x: printer.offset.x, + y: printer.output_size.y - 2, + }; + printer.print(position, &line) + } + } + + fn on_event(&mut self, e: Event) -> EventResult { + match e { + Event::Key(Key::Enter) => EventResult::Consumed(self.finalize()), + Event::Char(chr) => { + self.process(chr); + EventResult::Consumed(None) + }, + _ => unimplemented!() + } + } + +} + diff --git a/src/main.rs b/src/main.rs index 596f2ae..906da9e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ use cursive::event::{Event, EventTrigger}; use cursive_flexi_logger_view::FlexiLoggerView; use flexi_logger::{Logger, LogTarget}; +mod bindings; mod main_view; mod mail_view; mod maillist_view; diff --git a/src/main_view.rs b/src/main_view.rs index 8425872..c0dcd08 100644 --- a/src/main_view.rs +++ b/src/main_view.rs @@ -16,24 +16,38 @@ use cursive::views::NamedView; use cursive::views::ResizedView; use cursive::traits::Resizable; use cursive_multiplex::Mux; +use cursive::view::SizeConstraint; +use cursive::event::Key; +use getset::{Getters, MutGetters}; use crate::configuration::Configuration; use crate::maillist_view::MaillistView; use crate::maillist_view::MailListingData; +use crate::bindings::Bindings; +use crate::bindings::BindingCaller; pub const MAIN_VIEW_NAME: &'static str = "main_view"; pub const MAIN_MUX_NAME: &'static str = "main_mux"; pub const MAIN_MAIL_LIST_NAME: &'static str = "main_mail_list"; +#[derive(Getters, MutGetters)] pub struct MainView { config: Configuration, muxroot: cursive_multiplex::Id, + + #[getset(get = "pub", get_mut = "pub")] tabs: cursive_tabs::TabPanel, + + bindings: Bindings, + bindings_caller: Option>>, } impl View for MainView { fn draw(&self, printer: &Printer) { - self.tabs.draw(printer) + self.tabs.draw(printer); + if let Some(caller) = self.bindings_caller.as_ref() { + caller.draw(printer); + } } fn layout(&mut self, xy: XY) { @@ -49,35 +63,34 @@ impl View for MainView { } fn on_event(&mut self, e: Event) -> EventResult { - match e { - Event::Char('q') => { - if self.tabs.tab_order().len() == 1 { - EventResult::Ignored - } else { - if let Some(key) = self.tabs.active_tab().cloned() { - debug!("Removing tab: {}", key); - if let Err(e) = self.tabs.remove_tab(&key) { - error!("{:?}", e); // TODO do more than just logging - } - debug!("Remove tab"); - EventResult::Consumed(None) - } else { - debug!("No tab to remove found."); - EventResult::Ignored - } - } - }, - - Event::Key(cursive::event::Key::Enter) => { - let muxroot = self.muxroot.clone(); - EventResult::Consumed(Some(Callback::from_fn(move |siv| Self::add_mailview(siv, muxroot)))) - }, - - Event::Char('o') => { - EventResult::Consumed(Some(Callback::from_fn(MainView::add_notmuch_query_layer))) + debug!("Received event: {:?}", e); + match self.bindings_caller.as_mut() { + Some(caller) => match e { + Event::Key(Key::Esc) => { + self.bindings_caller = None; + debug!("Escape. Resetting bindings caller."); + EventResult::Consumed(None) + }, + other => { + debug!("Forwarding event to bindings caller"); + caller.on_event(other) + }, }, - - other => self.tabs.on_event(other), + None => match e { + Event::Char(':') => { + debug!(": -> Constructing bindings caller."); + self.bindings_caller = Some({ + self.bindings.caller() + .with_name(crate::bindings::BINDINGS_CALLER) + .resized(SizeConstraint::Full, SizeConstraint::AtLeast(5)) + }); + EventResult::Consumed(None) + }, + other => { + debug!("Forwarding event to tabs"); + self.tabs.on_event(other) + }, + } } } @@ -105,7 +118,6 @@ impl View for MainView { impl MainView { pub fn new(config: Configuration) -> Result> { - use cursive::view::SizeConstraint; let mut tab = cursive_multiplex::Mux::new(); let muxroot = tab.root().build().unwrap(); @@ -123,7 +135,9 @@ impl MainView { .with_bar_placement(cursive_tabs::Placement::HorizontalTop) .with_tab(config.notmuch_default_query().clone(), tab.with_name(MAIN_MUX_NAME)); - Ok(MainView { config, muxroot, tabs }.with_name(MAIN_VIEW_NAME)) + let bindings = crate::bindings::get_bindings(); + + Ok(MainView { config, muxroot, tabs, bindings, bindings_caller: None }.with_name(MAIN_VIEW_NAME)) } pub fn add_tab(&mut self, id: String, view: T) { @@ -134,7 +148,7 @@ impl MainView { &self.config } - fn add_notmuch_query_layer(siv: &mut Cursive) { + pub fn add_notmuch_query_layer(siv: &mut Cursive) { use crate::util::dialog_for; use crate::util::error_dialog_for; -- cgit v1.2.3 From 50c4ab8defaac96174e9d5782b032091c6f37a36 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 17 Jan 2021 17:05:29 +0100 Subject: Make bindings able to handle multiple caller strings Signed-off-by: Matthias Beyer --- src/bindings.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/bindings.rs b/src/bindings.rs index 3b4587c..9907bdc 100644 --- a/src/bindings.rs +++ b/src/bindings.rs @@ -20,7 +20,7 @@ use crate::main_view::MainView; pub fn get_bindings() -> Bindings { Bindings(vec![ Binding { - chars: vec!['q'], + chars: ["quit", "q"].iter().map(ToString::to_string).collect(), callback: Callback::from_fn(|siv: &mut Cursive| { trace!("Callback called: q"); let continue_running = siv.call_on_name(crate::main_view::MAIN_VIEW_NAME, |mv: &mut MainView| { @@ -49,7 +49,7 @@ pub fn get_bindings() -> Bindings { }, Binding { - chars: vec!['o'], + chars: ["open", "o"].iter().map(ToString::to_string).collect(), callback: Callback::from_fn(MainView::add_notmuch_query_layer), } ]) @@ -72,12 +72,12 @@ impl Bindings { #[derive(Clone)] pub struct Binding { - chars: Vec, + chars: Vec, callback: Callback, } impl Binding { - pub fn for_events(chars: Vec, callback: Callback) -> Binding { + pub fn for_events(chars: Vec, callback: Callback) -> Binding { Binding { chars, callback } } } @@ -103,7 +103,8 @@ impl BindingCaller { .iter() .find(|binding| { trace!("chars {:?} == state {:?}", binding.chars, self.state); - binding.chars == self.state + let state_str = self.state.iter().collect::(); + binding.chars.iter().any(|state| *state == state_str) }) .map(|binding| { trace!("Binding found"); -- cgit v1.2.3