From 99446027a16ae77ac22745767637ac28ba659bde Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sat, 23 Jan 2021 18:04:59 +0100 Subject: Complete restructuring Signed-off-by: Matthias Beyer --- Cargo.toml | 10 +-- src/bindings.rs | 16 ++--- src/configuration.rs | 1 + src/main.rs | 11 +++- src/runtime.rs | 24 ++++++++ src/tabs.rs | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/util.rs | 2 +- src/views/mail.rs | 25 ++++---- src/views/maillist.rs | 139 +++++++++++++++--------------------------- src/views/main.rs | 146 ++++++++++++++++---------------------------- 10 files changed, 329 insertions(+), 211 deletions(-) create mode 100644 src/runtime.rs create mode 100644 src/tabs.rs diff --git a/Cargo.toml b/Cargo.toml index c56a614..aeaf644 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,14 +8,15 @@ version = "0.1.0" anyhow = "1" chrono = "0.4" config = "0.10" -cursive = "0.15" +cursive = "0.16" cursive-async-view = "0.4" cursive-flexi-logger-view = { git = "https://github.com/deinstapel/cursive-flexi-logger-view/" } -cursive-multiplex = "0.4" -cursive-tabs = "0.5" +cursive-multiplex = { git = "https://github.com/matthiasbeyer/cursive-multiplex", branch = "getters" } +cursive-tabs = { git = "https://github.com/matthiasbeyer/cursive-tabs", branch = "getters" } cursive_table_view.git = "https://git.sr.ht/~matthiasbeyer/cursive_table_view" env_logger = "0.7" -flexi_logger = "0.15" +flexi_logger = "0.17" +failure = "0.1" getset = "0.1" handlebars = "3" indoc = "1" @@ -26,6 +27,7 @@ notmuch = "0.6" result-inspect = "0.1" serde = { version = "1.0", features = ["derive"] } walkdir = "2" +parse-display = "0.4" [dependencies.cursive_tree_view] git = "https://github.com/matthiasbeyer/cursive_tree_view" diff --git a/src/bindings.rs b/src/bindings.rs index 0e42cf2..e21243f 100644 --- a/src/bindings.rs +++ b/src/bindings.rs @@ -1,19 +1,11 @@ -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::views::main::MainView; @@ -48,10 +40,10 @@ pub fn get_bindings() -> Bindings { }) }, - Binding { - chars: ["open", "o"].iter().map(ToString::to_string).collect(), - callback: Callback::from_fn(MainView::add_notmuch_query_layer), - } + // Binding { + // chars: ["open", "o"].iter().map(ToString::to_string).collect(), + // callback: Callback::from_fn(MainView::add_notmuch_query_layer), + // } ]) } diff --git a/src/configuration.rs b/src/configuration.rs index 165b503..c28ad14 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -1,4 +1,5 @@ use std::path::PathBuf; + use serde::Deserialize; #[derive(Debug, Deserialize)] diff --git a/src/main.rs b/src/main.rs index 2bf7c50..2f9cfae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ #[macro_use] extern crate anyhow; #[macro_use] extern crate log; +use std::rc::Rc; + use anyhow::Result; use cursive::Cursive; use cursive::CursiveExt; @@ -10,10 +12,13 @@ use flexi_logger::{Logger, LogTarget}; mod bindings; mod configuration; +mod runtime; +mod tabs; mod util; mod views; -use configuration::Configuration; +use crate::configuration::Configuration; +use crate::runtime::Runtime; fn main() -> Result<()> { let mut siv = cursive::Cursive::default(); @@ -39,8 +44,10 @@ fn main() -> Result<()> { let trigger: EventTrigger = Event::Char('q').into(); siv.set_on_post_event(trigger, |s| s.quit()); + let runtime = Runtime::new(config)?; + debug!("Adding mainview"); - siv.add_fullscreen_layer(crate::views::main::MainView::new(config)?); + siv.add_fullscreen_layer(crate::views::main::MainView::new(Rc::new(runtime))?); siv.add_global_callback('~', |siv: &mut Cursive| siv.add_layer(FlexiLoggerView::scrollable())); debug!("Starting cursive"); diff --git a/src/runtime.rs b/src/runtime.rs new file mode 100644 index 0000000..e6f0819 --- /dev/null +++ b/src/runtime.rs @@ -0,0 +1,24 @@ +use anyhow::Result; + +use crate::configuration::Configuration; + +#[derive(getset::Getters, Debug)] +pub struct Runtime { + #[getset(get = "pub")] + config: Configuration, + + #[getset(get = "pub")] + database: notmuch::Database, +} + +impl Runtime { + pub fn new(config: Configuration) -> Result { + Ok({ + Runtime { + database: notmuch::Database::open(config.notmuch_database_path(), notmuch::DatabaseMode::ReadOnly)?, + config + } + }) + } +} + diff --git a/src/tabs.rs b/src/tabs.rs new file mode 100644 index 0000000..e0963fe --- /dev/null +++ b/src/tabs.rs @@ -0,0 +1,166 @@ +use std::ops::Deref; +use std::ops::DerefMut; +use std::rc::Rc; + +use anyhow::Result; +use cursive::Printer; +use cursive::Rect; +use cursive::View; +use cursive::XY; +use cursive::direction::Direction; +use cursive::event::Event; +use cursive::event::EventResult; +use cursive::view::Selector; +use cursive::view::ViewNotFound; + +use crate::runtime::Runtime; +use crate::views::maillist::MaillistView; + +#[derive(Hash, Eq, PartialEq, Clone, parse_display::Display)] +pub enum TabPanelName { + #[display("Query: {}")] + NotmuchQuery(String), + + #[display("Other")] + Other, +} + +pub struct Tabs(cursive_tabs::TabPanel); + +impl Deref for Tabs { + type Target = cursive_tabs::TabPanel; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Tabs { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Tabs { + pub fn new(query: &str, rt: Rc) -> Result { + let tab_ident = TabPanelName::NotmuchQuery(query.to_string()); + let tp = cursive_tabs::TabPanel::new() + .with_bar_alignment(cursive_tabs::Align::Start) + .with_bar_placement(cursive_tabs::Placement::VerticalLeft) + .with_tab(tab_ident, Tab::for_query(rt, query)?); + + Ok(Tabs(tp)) + } +} + +impl View for Tabs { + fn draw(&self, printer: &Printer) { + self.0.draw(printer); + } + + fn layout(&mut self, xy: XY) { + self.0.layout(xy) + } + + fn needs_relayout(&self) -> bool { + self.0.needs_relayout() + } + + fn required_size(&mut self, constraint: XY) -> XY { + self.0.required_size(constraint) + } + + fn on_event(&mut self, e: Event) -> EventResult { + self.0.on_event(e) + } + + fn call_on_any<'a>(&mut self, s: &Selector, tpl: &'a mut (dyn FnMut(&mut (dyn View + 'static)) + 'a)) { + self.0.call_on_any(s, tpl); + } + + fn focus_view(&mut self, s: &Selector) -> Result<(), ViewNotFound> { + self.0.focus_view(s) + } + + fn take_focus(&mut self, source: Direction) -> bool { + self.0.take_focus(source) + } + + fn important_area(&self, view_size: XY) -> Rect { + self.0.important_area(view_size) + } + + fn type_name(&self) -> &'static str { + self.0.type_name() + } + +} + +pub struct Tab { + mux: cursive_multiplex::Mux, + nodes: Vec, + rt: Rc, +} + +impl Tab { + fn new(rt: Rc) -> Self { + Tab { + mux: cursive_multiplex::Mux::new(), + nodes: vec![], + rt, + } + } + + fn for_query(rt: Rc, query: &str) -> Result { + use failure::Fail; + + let mut this = Self::new(rt.clone()); + let root = this.mux.root().build().unwrap(); + let list = MaillistView::for_query(rt, query)?; + this.nodes.push(this.mux.add_right_of(list, root).map_err(|e| e.compat())?); + Ok(this) + } +} + +impl View for Tab { + fn draw(&self, printer: &Printer) { + self.mux.draw(printer); + } + + fn layout(&mut self, xy: XY) { + self.mux.layout(xy) + } + + fn needs_relayout(&self) -> bool { + self.mux.needs_relayout() + } + + fn required_size(&mut self, constraint: XY) -> XY { + self.mux.required_size(constraint) + } + + fn on_event(&mut self, e: Event) -> EventResult { + self.mux.on_event(e) + } + + fn call_on_any<'a>(&mut self, s: &Selector, tpl: &'a mut (dyn FnMut(&mut (dyn View + 'static)) + 'a)) { + self.mux.call_on_any(s, tpl); + } + + fn focus_view(&mut self, s: &Selector) -> Result<(), ViewNotFound> { + self.mux.focus_view(s) + } + + fn take_focus(&mut self, source: Direction) -> bool { + self.mux.take_focus(source) + } + + fn important_area(&self, view_size: XY) -> Rect { + self.mux.important_area(view_size) + } + + fn type_name(&self) -> &'static str { + self.mux.type_name() + } +} + diff --git a/src/util.rs b/src/util.rs index 3490e30..3d99b39 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,6 +1,6 @@ +use anyhow::Error; use cursive::views::Dialog; use cursive::views::TextView; -use anyhow::Error; pub fn dialog_for(e: S) -> Dialog { Dialog::around({ diff --git a/src/views/mail.rs b/src/views/mail.rs index 93c781a..fe3a2f3 100644 --- a/src/views/mail.rs +++ b/src/views/mail.rs @@ -1,7 +1,8 @@ use std::path::PathBuf; -use anyhow::Result; -use anyhow::Error; +use std::rc::Rc; +use anyhow::Error; +use anyhow::Result; use cursive::Printer; use cursive::Rect; use cursive::View; @@ -9,25 +10,27 @@ use cursive::XY; use cursive::direction::Direction; use cursive::event::Event; use cursive::event::EventResult; -use cursive::view::Nameable; -use cursive::view::Selector; use cursive::view::Scrollable; -use cursive::views::NamedView; -use cursive::views::TextView; +use cursive::view::Selector; +use cursive::view::ViewNotFound; use cursive::views::LinearLayout; use cursive::views::ScrollView; -use result_inspect::ResultInspect; +use cursive::views::TextView; use mailparse::ParsedMail; +use result_inspect::ResultInspect; + +use crate::runtime::Runtime; pub struct MailView { + rt: Rc, view: ScrollView, } impl MailView { - pub fn create_for(database_path: PathBuf, id: String, mailfile: PathBuf, name: String) -> Result> { + pub fn create_for(rt: Rc, id: String, mailfile: PathBuf) -> Result { let query = format!("id:{}", id); - let view = notmuch::Database::open(&database_path, notmuch::DatabaseMode::ReadOnly)? + let view = rt.database() .create_query(&query)? .search_messages()? .map(|msg| { @@ -58,7 +61,7 @@ impl MailView { view }; - Ok(MailView { view: view.scrollable() }.with_name(name)) + Ok(MailView { rt, view: view.scrollable() }) } fn parsed_mail_to_list_of_textviews<'a>(pm: &'a ParsedMail) -> Result> { @@ -103,7 +106,7 @@ impl View for MailView { self.view.call_on_any(s, tpl); } - fn focus_view(&mut self, s: &Selector) -> Result<(), ()> { + fn focus_view(&mut self, s: &Selector) -> Result<(), ViewNotFound> { self.view.focus_view(s) } diff --git a/src/views/maillist.rs b/src/views/maillist.rs index dc13bf3..4f68790 100644 --- a/src/views/maillist.rs +++ b/src/views/maillist.rs @@ -1,45 +1,29 @@ use std::path::PathBuf; -use std::ops::Deref; +use std::rc::Rc; -use anyhow::Result; use anyhow::Context; +use anyhow::Result; +use chrono::naive::NaiveDateTime; use cursive::Cursive; -use cursive::Printer; -use cursive::Rect; -use cursive::View; -use cursive::XY; -use cursive::direction::Direction; -use cursive::view::Nameable; -use cursive::view::Selector; -use cursive::event::Event; -use cursive::event::EventResult; -use cursive::views::NamedView; +use cursive::view::SizeConstraint; +use cursive::views::ResizedView; use cursive_table_view::TableView; use cursive_table_view::TableViewItem; -use chrono::naive::NaiveDateTime; +use getset::Getters; use notmuch::Message; use notmuch::MessageOwner; -use getset::Getters; -use cursive::views::ResizedView; -use cursive::view::SizeConstraint; -use crate::views::main::MainView; use crate::views::mail::MailView; +use crate::runtime::Runtime; pub struct MaillistView { + rt: Rc, view: ResizedView>, } -impl Deref for MaillistView { - type Target = TableView; - fn deref(&self) -> &Self::Target { - self.view.get_inner() - } -} - impl MaillistView { - pub fn create_for(database_path: PathBuf, query: &str, name: String) -> Result> { - debug!("Getting '{}' from '{}'", query, database_path.display()); + pub fn for_query(rt: Rc, query: &str) -> Result { + debug!("Getting '{}' from '{}'", query, rt.config().notmuch_database_path().display()); fn get_header_field_save<'o, O: MessageOwner + 'o>(msg: &Message<'o, O>, field: &str) -> String { match msg.header(field) { @@ -53,8 +37,7 @@ impl MaillistView { } } - let items = notmuch::Database::open(&database_path, notmuch::DatabaseMode::ReadOnly) - .context(format!("Opening database {}", database_path.display()))? + let items = rt.database() .create_query(query) .context("Creating the search query")? .search_messages() @@ -86,12 +69,11 @@ impl MaillistView { }) }) .collect::>>() - .context(format!("Creating MaillinglistView for '{}' on {}", query, database_path.display()))?; + .context(format!("Creating MaillinglistView for '{}' on {}", query, rt.config().notmuch_database_path().display()))?; debug!("Found {} entries", items.len()); - let n = name.clone(); - let db_path = database_path.clone(); - let view = TableView::::new() + let mailviewrt = rt.clone(); + let view = TableView::::new() .column(MailListingColumn::Date, "Date", |c| c.width(20)) .column(MailListingColumn::Tags, "Tags", |c| c.width(20)) .column(MailListingColumn::From, "From", |c| c) @@ -101,82 +83,61 @@ impl MaillistView { .items(items) .selected_item(0) .on_submit(move |siv: &mut Cursive, row: usize, _: usize| { - let (mail_id, filename) = siv.call_on_name(&n, move |table: &mut MaillistView| { - table.view - .get_inner_mut() - .borrow_item(row) - .map(|data| { - debug!("Opening: {:?}", data); - (data.mail_id.clone(), data.filename.clone()) + let (mail_id, filename) = siv.call_on_name(crate::views::main::MAIN_VIEW_NAME, move |main: &mut crate::views::main::MainView| { + main.get_current_mux() + .map(|mux| { + if let Some(table) = mux.downcast_ref::>() { + table + .borrow_item(row) + .map(|data| { + debug!("Opening: {:?}", data); + (data.mail_id.clone(), data.filename.clone()) + }) + } else { + unimplemented!() + } }) }) .unwrap() + .unwrap() .unwrap(); debug!("Showing mail {}", mail_id); + let mv = MailView::create_for(mailviewrt.clone(), mail_id, filename).unwrap(); - // Why do I have to do this? This is UGLY! - let n = n.clone(); - let db_path = db_path.clone(); - let name = format!("{}-{}", n, mail_id); - let mv = MailView::create_for(db_path, mail_id, filename, name).unwrap(); - - siv.call_on_name(crate::views::main::MAIN_MUX_NAME, move |mux: &mut cursive_multiplex::Mux| { - mux.add_right_of(mv, mux.root().build().unwrap()); + siv.call_on_name(crate::views::main::MAIN_VIEW_NAME , move |main: &mut crate::views::main::MainView| { + main.get_current_tab_mut() + .map(|mux: &mut cursive_multiplex::Mux| { + mux.add_right_of(mv, mux.focus()); + }) }); // use the mail ID to get the whole thread and open it as a table item }); - Ok(MaillistView { view: ResizedView::new(SizeConstraint::Full, SizeConstraint::Full, view )}.with_name(name)) - } - - pub fn borrow_item(&mut self, idx: usize) -> Option<&MailListingData> { - self.view.get_inner_mut().borrow_item(idx) + Ok({ + MaillistView { + rt, + view: ResizedView::new(SizeConstraint::Full, SizeConstraint::Full, view) + } + }) } } -impl View for MaillistView { - fn draw(&self, printer: &Printer) { - self.view.draw(printer) - } - - fn layout(&mut self, xy: XY) { - self.view.layout(xy) - } - - fn needs_relayout(&self) -> bool { - self.view.needs_relayout() - } - - fn required_size(&mut self, constraint: XY) -> XY { - self.view.required_size(constraint) - } - - fn on_event(&mut self, e: Event) -> EventResult { - self.view.on_event(e) - } +impl cursive::view::ViewWrapper for MaillistView { + type V = ResizedView>; - fn call_on_any<'a>(&mut self, s: &Selector, tpl: &'a mut (dyn FnMut(&mut (dyn View + 'static)) + 'a)) { - self.view.call_on_any(s, tpl); - } - - fn focus_view(&mut self, s: &Selector) -> Result<(), ()> { - self.view.focus_view(s) - } - - fn take_focus(&mut self, source: Direction) -> bool { - self.view.take_focus(source) - } - - fn important_area(&self, view_size: XY) -> Rect { - self.view.important_area(view_size) + fn with_view(&self, f: F) -> Option + where F: FnOnce(&Self::V) -> R + { + Some(f(&self.view)) } - fn type_name(&self) -> &'static str { - self.view.type_name() + fn with_view_mut(&mut self, f: F) -> Option + where F: FnOnce(&mut Self::V) -> R + { + Some(f(&mut self.view)) } - } #[derive(Copy, Clone, PartialEq, Eq, Hash)] diff --git a/src/views/main.rs b/src/views/main.rs index af9dcea..2cf54c3 100644 --- a/src/views/main.rs +++ b/src/views/main.rs @@ -1,48 +1,84 @@ +use std::rc::Rc; + 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::traits::Resizable; use cursive::view::Nameable; use cursive::view::Selector; -use cursive::views::Dialog; -use cursive::views::EditView; +use cursive::view::SizeConstraint; +use cursive::view::ViewNotFound; 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::bindings::BindingCaller; use crate::bindings::Bindings; -use crate::configuration::Configuration; -use crate::views::mail::MailView; -use crate::views::maillist::MailListingData; -use crate::views::maillist::MaillistView; +use crate::tabs::*; +use crate::runtime::Runtime; 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, + rt: Rc, #[getset(get = "pub", get_mut = "pub")] - tabs: cursive_tabs::TabPanel, + tabs: crate::tabs::Tabs, bindings: Bindings, bindings_caller: Option>>, } +impl MainView { + pub fn new(rt: Rc) -> Result> { + let default_query = rt.config().notmuch_default_query(); + let tabs = Tabs::new(default_query, rt.clone())?; + let bindings = crate::bindings::get_bindings(); + + Ok(MainView { rt, tabs, bindings, bindings_caller: None }.with_name(MAIN_VIEW_NAME)) + } + + pub fn get_current_tab(&self) -> Option<&cursive_multiplex::Mux> { + if let Some(mux) = self.tabs.get_active_tab() { + mux.downcast_ref::() + } else { + None + } + } + + pub fn get_current_mux(&self) -> Option<&Box> { + if let Some(mux) = self.get_current_tab() { + return mux.get_current_view() + } + + None + } + + pub fn get_current_tab_mut(&mut self) -> Option<&mut cursive_multiplex::Mux> { + if let Some(mux) = self.tabs.get_active_tab_mut() { + mux.downcast_mut::() + } else { + None + } + } + + pub fn get_current_mux_mut(&mut self) -> Option<&mut Box> { + if let Some(mut mux) = self.get_current_tab_mut() { + return mux.get_current_view_mut() + } + + None + } + +} + impl View for MainView { fn draw(&self, printer: &Printer) { self.tabs.draw(printer); @@ -99,7 +135,7 @@ impl View for MainView { self.tabs.call_on_any(s, tpl); } - fn focus_view(&mut self, s: &Selector) -> Result<(), ()> { + fn focus_view(&mut self, s: &Selector) -> Result<(), ViewNotFound> { self.tabs.focus_view(s) } @@ -116,77 +152,3 @@ impl View for MainView { } } - -impl MainView { - pub fn new(config: Configuration) -> Result> { - let mut tab = cursive_multiplex::Mux::new(); - let muxroot = tab.root().build().unwrap(); - - { - let dbpath = config.notmuch_database_path(); - let dbquery = config.notmuch_default_query(); - let mlname = MAIN_MAIL_LIST_NAME.to_string(); - let view = MaillistView::create_for(dbpath.to_path_buf(), dbquery, mlname)?; - - let _ = tab.add_right_of(view, muxroot); - } - - let tabs = cursive_tabs::TabPanel::default() - .with_bar_alignment(cursive_tabs::Align::Start) - .with_bar_placement(cursive_tabs::Placement::HorizontalTop) - .with_tab(config.notmuch_default_query().clone(), tab.with_name(MAIN_MUX_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) { - self.tabs.add_tab(id, view) - } - - pub fn config(&self) -> &Configuration { - &self.config - } - - pub fn add_notmuch_query_layer(siv: &mut Cursive) { - use crate::util::dialog_for; - use crate::util::error_dialog_for; - - let edit_view = EditView::new() - .on_submit(move |siv: &mut Cursive, query: &str| { - siv.call_on_name(MAIN_VIEW_NAME, move |main_view: &mut MainView| { - main_view.config().notmuch_database_path().clone() - }) - .map(|dbpath| { - let t = MaillistView::create_for(dbpath.to_path_buf(), query, query.to_string())? - .full_screen() - .with_name(format!("{}-view", query)); - - siv.call_on_name(MAIN_VIEW_NAME, move |main_view: &mut MainView| { - main_view.add_tab(query.to_string(), t); - }); - - siv.pop_layer(); - Ok(()) - }) - .unwrap_or_else(|| { - siv.pop_layer(); - siv.add_layer(dialog_for("Failed to get database connection set up")); - Ok(()) - }) - .unwrap_or_else(|e: anyhow::Error| { - siv.pop_layer(); - siv.add_layer(error_dialog_for(e)) - }); - }) - .with_name("query"); - - siv.add_layer({ - Dialog::around(edit_view) - .title("Query") - .min_width(80) - }) - } - -} -- cgit v1.2.3