From 8c98d3a5a03bb0b67112d47f8e1b9e08b25a156d Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Fri, 20 Jul 2018 12:15:06 +0300 Subject: [PATCH] Move ui and melib to different crates closes #9 --- Cargo.toml | 22 +- melib/Cargo.toml | 22 ++ {src => melib/src}/conf/mod.rs | 0 {src => melib/src}/conf/pager.rs | 0 {src => melib/src}/error.rs | 0 {src => melib/src}/lib.rs | 0 {src => melib/src}/mailbox/accounts.rs | 0 {src => melib/src}/mailbox/backends/imap.rs | 0 .../src}/mailbox/backends/maildir.rs | 0 {src => melib/src}/mailbox/backends/mbox.rs | 0 {src => melib/src}/mailbox/backends/mod.rs | 0 .../src}/mailbox/email/attachments.rs | 99 +++-- {src => melib/src}/mailbox/email/mod.rs | 0 {src => melib/src}/mailbox/email/parser.rs | 0 {src => melib/src}/mailbox/mod.rs | 0 {src => melib/src}/mailbox/thread.rs | 0 src/bin.rs | 6 +- src/python/mod.rs | 22 ++ ui/Cargo.toml | 13 + {src/ui => ui/src}/cells.rs | 0 .../src/components/mail/listing.rs | 355 ++++-------------- ui/src/components/mail/mod.rs | 189 ++++++++++ {src/ui => ui/src}/components/mod.rs | 37 +- .../ui => ui/src}/components/notifications.rs | 4 +- {src/ui => ui/src}/components/utilities.rs | 46 ++- {src/ui => ui/src}/execute/mod.rs | 0 src/ui/mod.rs => ui/src/lib.rs | 9 +- {src/ui => ui/src}/position.rs | 0 28 files changed, 461 insertions(+), 363 deletions(-) create mode 100644 melib/Cargo.toml rename {src => melib/src}/conf/mod.rs (100%) rename {src => melib/src}/conf/pager.rs (100%) rename {src => melib/src}/error.rs (100%) rename {src => melib/src}/lib.rs (100%) rename {src => melib/src}/mailbox/accounts.rs (100%) rename {src => melib/src}/mailbox/backends/imap.rs (100%) rename {src => melib/src}/mailbox/backends/maildir.rs (100%) rename {src => melib/src}/mailbox/backends/mbox.rs (100%) rename {src => melib/src}/mailbox/backends/mod.rs (100%) rename {src => melib/src}/mailbox/email/attachments.rs (91%) rename {src => melib/src}/mailbox/email/mod.rs (100%) rename {src => melib/src}/mailbox/email/parser.rs (100%) rename {src => melib/src}/mailbox/mod.rs (100%) rename {src => melib/src}/mailbox/thread.rs (100%) create mode 100644 src/python/mod.rs create mode 100644 ui/Cargo.toml rename {src/ui => ui/src}/cells.rs (100%) rename src/ui/components/mail.rs => ui/src/components/mail/listing.rs (62%) create mode 100644 ui/src/components/mail/mod.rs rename {src/ui => ui/src}/components/mod.rs (87%) rename {src/ui => ui/src}/components/notifications.rs (94%) rename {src/ui => ui/src}/components/utilities.rs (93%) rename {src/ui => ui/src}/execute/mod.rs (100%) rename src/ui/mod.rs => ui/src/lib.rs (99%) rename {src/ui => ui/src}/position.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index cd25a38a3..8c5814d2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,32 +3,16 @@ name = "meli" version = "0.1.0" authors = ["Manos Pitsidianakis "] -[lib] -name = "melib" -path = "src/lib.rs" - [[bin]] name = "meli" path = "src/bin.rs" [dependencies] -chrono = "0.4" -xdg = "2.1.0" -config = "0.6" -serde_derive = "^1.0.8" -serde = "^1.0.8" -nom = "3.2.0" -memmap = "0.5.2" -base64 = "*" -crossbeam = "^0.3.0" -fnv = "1.0.3" -encoding = "0.2.33" -bitflags = "1.0" -notify = "4.0.1" -termion = "1.5.1" chan = "0.1.21" chan-signal = "0.3.1" -notify-rust = "^3" +melib = { path = "melib", version = "*" } + +ui = { path = "ui", version = "*" } [profile.release] #lto = true diff --git a/melib/Cargo.toml b/melib/Cargo.toml new file mode 100644 index 000000000..76f75ad8e --- /dev/null +++ b/melib/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "melib" +version = "0.0.1" #:version +authors = [] +workspace = ".." + +[dependencies] +notify = "4.0.1" +notify-rust = "^3" +crossbeam = "^0.3.0" +fnv = "1.0.3" +config = "0.6" +xdg = "2.1.0" +chrono = "0.4" +serde_derive = "^1.0.8" +serde = "^1.0.8" +nom = "3.2.0" +memmap = "0.5.2" +base64 = "*" +encoding = "0.2.33" +bitflags = "1.0" +termion = "1.5.1" diff --git a/src/conf/mod.rs b/melib/src/conf/mod.rs similarity index 100% rename from src/conf/mod.rs rename to melib/src/conf/mod.rs diff --git a/src/conf/pager.rs b/melib/src/conf/pager.rs similarity index 100% rename from src/conf/pager.rs rename to melib/src/conf/pager.rs diff --git a/src/error.rs b/melib/src/error.rs similarity index 100% rename from src/error.rs rename to melib/src/error.rs diff --git a/src/lib.rs b/melib/src/lib.rs similarity index 100% rename from src/lib.rs rename to melib/src/lib.rs diff --git a/src/mailbox/accounts.rs b/melib/src/mailbox/accounts.rs similarity index 100% rename from src/mailbox/accounts.rs rename to melib/src/mailbox/accounts.rs diff --git a/src/mailbox/backends/imap.rs b/melib/src/mailbox/backends/imap.rs similarity index 100% rename from src/mailbox/backends/imap.rs rename to melib/src/mailbox/backends/imap.rs diff --git a/src/mailbox/backends/maildir.rs b/melib/src/mailbox/backends/maildir.rs similarity index 100% rename from src/mailbox/backends/maildir.rs rename to melib/src/mailbox/backends/maildir.rs diff --git a/src/mailbox/backends/mbox.rs b/melib/src/mailbox/backends/mbox.rs similarity index 100% rename from src/mailbox/backends/mbox.rs rename to melib/src/mailbox/backends/mbox.rs diff --git a/src/mailbox/backends/mod.rs b/melib/src/mailbox/backends/mod.rs similarity index 100% rename from src/mailbox/backends/mod.rs rename to melib/src/mailbox/backends/mod.rs diff --git a/src/mailbox/email/attachments.rs b/melib/src/mailbox/email/attachments.rs similarity index 91% rename from src/mailbox/email/attachments.rs rename to melib/src/mailbox/email/attachments.rs index f41dd0c22..f34ea5e5e 100644 --- a/src/mailbox/email/attachments.rs +++ b/melib/src/mailbox/email/attachments.rs @@ -204,7 +204,7 @@ impl AttachmentBuilder { }; AttachmentType::Multipart { of_type: multipart_type, - subattachments: Attachment::subattachments(&self.raw, b), + subattachments: Self::subattachments(&self.raw, b), } } ContentType::Unsupported { ref tag } => AttachmentType::Data { tag: tag.clone() }, @@ -216,6 +216,48 @@ impl AttachmentBuilder { attachment_type: attachment_type, } } + + pub fn subattachments(raw: &[u8], boundary: &str) -> Vec { + let boundary_length = boundary.len(); + match parser::attachments(raw, &boundary[0..boundary_length - 2], boundary).to_full_result() + { + Ok(attachments) => { + let mut vec = Vec::with_capacity(attachments.len()); + for a in attachments { + let (headers, body) = match parser::attachment(a).to_full_result() { + Ok(v) => v, + Err(_) => { + eprintln!("error in parsing attachment"); + eprintln!("\n-------------------------------"); + eprintln!("{}\n", ::std::string::String::from_utf8_lossy(a)); + eprintln!("-------------------------------\n"); + + continue; + } + }; + let mut builder = AttachmentBuilder::new(body); + for (name, value) in headers { + if name.eq_ignore_ascii_case("content-type") { + builder.content_type(value); + } else if name.eq_ignore_ascii_case("content-transfer-encoding") { + builder.content_transfer_encoding(value); + } + } + vec.push(builder.build()); + } + vec + } + a => { + eprintln!( + "error {:?}\n\traw: {:?}\n\tboundary: {:?}", + a, + ::std::str::from_utf8(raw).unwrap(), + boundary + ); + Vec::new() + } + } + } } @@ -267,45 +309,28 @@ impl Attachment { pub fn get_tag(&self) -> String { format!("{}/{}", self.content_type.0, self.content_type.1).to_string() } - pub fn subattachments(raw: &[u8], boundary: &str) -> Vec { - let boundary_length = boundary.len(); - match parser::attachments(raw, &boundary[0..boundary_length - 2], boundary).to_full_result() - { - Ok(attachments) => { - let mut vec = Vec::with_capacity(attachments.len()); - for a in attachments { - let (headers, body) = match parser::attachment(a).to_full_result() { - Ok(v) => v, - Err(_) => { - eprintln!("error in parsing attachment"); - eprintln!("\n-------------------------------"); - eprintln!("{}\n", ::std::string::String::from_utf8_lossy(a)); - eprintln!("-------------------------------\n"); + pub fn count_attachments(&mut self) -> usize { + let mut counter = 0; - continue; - } - }; - let mut builder = AttachmentBuilder::new(body); - for (name, value) in headers { - if name.eq_ignore_ascii_case("content-type") { - builder.content_type(value); - } else if name.eq_ignore_ascii_case("content-transfer-encoding") { - builder.content_transfer_encoding(value); - } - } - vec.push(builder.build()); + fn count_recursive(att: &Attachment, counter: &mut usize) { + match att.attachment_type { + AttachmentType::Data { .. } => { + *counter += 1; } - vec - } - a => { - eprintln!( - "error in 469 {:?}\n\traw: {:?}\n\tboundary: {:?}", - a, - ::std::str::from_utf8(raw).unwrap(), - boundary - ); - Vec::new() + AttachmentType::Text { .. } => { + } + AttachmentType::Multipart { + of_type: ref multipart_type, + subattachments: ref sub_att_vec, + } => if *multipart_type != MultipartType::Alternative { + for a in sub_att_vec { + count_recursive(a, counter); + } + }, } } + + count_recursive(&self, &mut counter); + counter } } diff --git a/src/mailbox/email/mod.rs b/melib/src/mailbox/email/mod.rs similarity index 100% rename from src/mailbox/email/mod.rs rename to melib/src/mailbox/email/mod.rs diff --git a/src/mailbox/email/parser.rs b/melib/src/mailbox/email/parser.rs similarity index 100% rename from src/mailbox/email/parser.rs rename to melib/src/mailbox/email/parser.rs diff --git a/src/mailbox/mod.rs b/melib/src/mailbox/mod.rs similarity index 100% rename from src/mailbox/mod.rs rename to melib/src/mailbox/mod.rs diff --git a/src/mailbox/thread.rs b/melib/src/mailbox/thread.rs similarity index 100% rename from src/mailbox/thread.rs rename to melib/src/mailbox/thread.rs diff --git a/src/bin.rs b/src/bin.rs index c2e8c578b..c5f2f12f5 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -25,12 +25,8 @@ The mail handling stuff is done in the `melib` crate which includes all backend */ extern crate melib; -#[macro_use] -extern crate nom; -extern crate termion; -extern crate notify_rust; +extern crate ui; -pub mod ui; use ui::*; pub use melib::*; diff --git a/src/python/mod.rs b/src/python/mod.rs new file mode 100644 index 000000000..f4ededd37 --- /dev/null +++ b/src/python/mod.rs @@ -0,0 +1,22 @@ +#![cfg(feature = "python")] +use pyo3::prelude::*; + + +#[pymodinit(pythonmeli)] +fn pythonmeli(py: Python, m: &PyModule) -> PyResult<()> { + // pyo3 aware function. All of our python interface could be declared in a separate module. + // Note that the `#[pyfn()]` annotation automatically converts the arguments from + // Python objects to Rust values; and the Rust return value back into a Python object. + #[pyfn(m, "sum_as_string")] + fn sum_as_string_py(_py: Python, a:i64, b:i64) -> PyResult { + let out = sum_as_string(a, b); + Ok(out) + } + + Ok(()) +} + +// logic implemented as a normal rust function +fn sum_as_string(a:i64, b:i64) -> String { + format!("{}", a + b).to_string() +} diff --git a/ui/Cargo.toml b/ui/Cargo.toml new file mode 100644 index 000000000..13bd15666 --- /dev/null +++ b/ui/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ui" +version = "0.0.1" #:version +authors = [] +workspace = ".." + +[dependencies] +melib = { path = "../melib", version = "*" } +termion = "1.5.1" +chan = "0.1.21" +notify = "4.0.1" +notify-rust = "^3" +nom = "3.2.0" diff --git a/src/ui/cells.rs b/ui/src/cells.rs similarity index 100% rename from src/ui/cells.rs rename to ui/src/cells.rs diff --git a/src/ui/components/mail.rs b/ui/src/components/mail/listing.rs similarity index 62% rename from src/ui/components/mail.rs rename to ui/src/components/mail/listing.rs index 054c85408..c8a8a9af3 100644 --- a/src/ui/components/mail.rs +++ b/ui/src/components/mail/listing.rs @@ -1,9 +1,4 @@ -/*! Entities that handle Mail specific functions. - */ -use ui::components::*; -use ui::cells::*; - - +use super::*; const MAX_COLS: usize = 500; /// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a @@ -30,9 +25,6 @@ impl MailListing { format!("{} {} {:.85}",idx,&e.get_datetime().format("%Y-%m-%d %H:%M:%S").to_string(),e.get_subject()) } - - - pub fn new() -> Self { let content = CellBuffer::new(0, 0, Cell::with_char(' ')); MailListing { @@ -60,7 +52,12 @@ impl MailListing { // Inform State that we changed the current folder view. context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::RefreshMailbox((self.cursor_pos.0, self.cursor_pos.1)) }); - self.length = mailbox.len(); + self.length = if threaded { + mailbox.threaded_collection.len() + + } else { + mailbox.len() + }; let mut content = CellBuffer::new(MAX_COLS, self.length+1, Cell::with_char(' ')); if self.length == 0 { write_string_to_grid(&format!("Folder `{}` is empty.", @@ -68,7 +65,8 @@ impl MailListing { &mut content, Color::Default, Color::Default, - ((0, 0), (MAX_COLS-1, 0))); + ((0, 0), (MAX_COLS-1, 0)), + true); self.content = content; return; } @@ -118,11 +116,12 @@ impl MailListing { } else { Color::Default }; - let x = write_string_to_grid(&MailListing::make_thread_entry(envelope, idx, indentation, container, &indentations), + let (x, y) = write_string_to_grid(&MailListing::make_thread_entry(envelope, idx, indentation, container, &indentations), &mut content, fg_color, bg_color, - ((0, idx) , (MAX_COLS-1, idx))); + ((0, idx) , (MAX_COLS-1, idx)), + false); for x in x..MAX_COLS { content[(x,idx)].set_ch(' '); content[(x,idx)].set_bg(bg_color); @@ -171,11 +170,12 @@ impl MailListing { } else { Color::Default }; - let x = write_string_to_grid(&MailListing::make_entry_string(envelope, idx), + let (x, y)= write_string_to_grid(&MailListing::make_entry_string(envelope, idx), &mut content, fg_color, bg_color, - ((0, y) , (MAX_COLS-1, y))); + ((0, y) , (MAX_COLS-1, y)), + false); for x in x..MAX_COLS { content[(x,y)].set_ch(' '); @@ -189,9 +189,15 @@ impl MailListing { self.content = content; } - fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize, context: &mut Context) { - let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1].as_ref().unwrap().as_ref().unwrap(); - let envelope: &Envelope = &mailbox.collection[idx]; + fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) { + let threaded = context.accounts[self.cursor_pos.0].settings.threaded; + let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1].as_ref().unwrap().as_ref().unwrap(); + let envelope: &Envelope = if threaded { + let i = mailbox.get_threaded_mail(idx); + &mailbox.collection[i] + } else { + &mailbox.collection[idx] + }; let fg_color = if !envelope.is_seen() { Color::Byte(0) @@ -202,7 +208,7 @@ impl MailListing { Color::Byte(246) } else { if !envelope.is_seen() { - Color::Byte(252) + Color::Byte(251) } else if idx % 2 == 0 { Color::Byte(236) } else { @@ -256,40 +262,6 @@ impl MailListing { context.dirty_areas.push_back(area); } - /// Create a pager for the `Envelope` currently under the cursor. - fn draw_header_view(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - { - let threaded = context.accounts[self.cursor_pos.0].settings.threaded; - let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1].as_ref().unwrap().as_ref().unwrap(); - let envelope: &Envelope = if threaded { - let i = mailbox.get_threaded_mail(self.cursor_pos.2); - &mailbox.collection[i] - } else { - &mailbox.collection[self.cursor_pos.2] - }; - - let pager_filter = context.settings.pager.filter.clone(); - self.pager = Some(Pager::new(&envelope, pager_filter)); - } - self.pager.as_mut().map(|p| p.draw(grid, area, context)); - } - /// Create a pager for the `Envelope` currently under the cursor. - fn draw_attachment_view(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - { - let threaded = context.accounts[self.cursor_pos.0].settings.threaded; - let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1].as_ref().unwrap().as_ref().unwrap(); - let envelope: &Envelope = if threaded { - let i = mailbox.get_threaded_mail(self.cursor_pos.2); - &mailbox.collection[i] - } else { - &mailbox.collection[self.cursor_pos.2] - }; - - let pager_filter = context.settings.pager.filter.clone(); - self.pager = Some(Pager::new(&envelope, pager_filter)); - } - self.pager.as_mut().map(|p| p.draw(grid, area, context)); - } /// Create a pager for the `Envelope` currently under the cursor. fn draw_mail_view(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { { @@ -335,6 +307,8 @@ impl MailListing { } s.push('─'); s.push('>'); } + s.push_str(&format!(" {}∞ ", envelope.get_body().count_attachments())); + if show_subject { s.push_str(&format!("{:.85}", envelope.get_subject())); } @@ -360,37 +334,23 @@ impl Component for MailListing { clear_area(grid, area); context.dirty_areas.push_back(area); } - // TODO: Make this configurable. User should be able to choose what headers to display, - // and toggle between full header view and custom header view like in mutt. - let headers_rows: usize = 6; /* Render the mail body in a pager, basically copy what HSplit does */ let total_rows = get_y(bottom_right) - get_y(upper_left); let pager_ratio = context.settings.pager.pager_ratio; - let mut bottom_entity_rows = (pager_ratio*total_rows )/100; - if bottom_entity_rows < headers_rows + 2 { - bottom_entity_rows = headers_rows + 2; - } + let bottom_entity_rows = (pager_ratio*total_rows )/100; + if bottom_entity_rows > total_rows { clear_area(grid, area); context.dirty_areas.push_back(area); return; } let mid = get_y(upper_left) + total_rows - bottom_entity_rows; - - if !self.dirty { - if let Some(ref mut p) = self.pager { - p.draw(grid, - ((get_x(upper_left), get_y(upper_left) + mid + headers_rows + 1), bottom_right), - context); - } - return; - } - self.dirty = false; self.draw_list(grid, (upper_left, (get_x(bottom_right), get_y(upper_left)+ mid-1)), context); if self.length == 0 { + self.dirty = false; return; } { @@ -405,8 +365,10 @@ impl Component for MailListing { grid[(i, mid)].set_ch('─'); } } + // TODO: Make headers view configurable /* Draw header */ + let y = { let threaded = context.accounts[self.cursor_pos.0].settings.threaded; let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1].as_ref().unwrap().as_ref().unwrap(); @@ -417,66 +379,82 @@ impl Component for MailListing { &mailbox.collection[self.cursor_pos.2] }; - let x = write_string_to_grid(&format!("Date: {}", envelope.get_date_as_str()), + let (x,y) = write_string_to_grid(&format!("Date: {}", envelope.get_date_as_str()), grid, Color::Byte(33), Color::Default, - (set_y(upper_left, mid+1), set_y(bottom_right, mid+1))); + (set_y(upper_left, mid+1), bottom_right), + true); for x in x..=get_x(bottom_right) { - grid[(x, mid+1)].set_ch(' '); - grid[(x, mid+1)].set_bg(Color::Default); - grid[(x, mid+1)].set_fg(Color::Default); + grid[(x, y)].set_ch(' '); + grid[(x, y)].set_bg(Color::Default); + grid[(x, y)].set_fg(Color::Default); } - let x = write_string_to_grid(&format!("From: {}", envelope.get_from()), + let (x,y) = write_string_to_grid(&format!("From: {}", envelope.get_from()), grid, Color::Byte(33), Color::Default, - (set_y(upper_left, mid+2), set_y(bottom_right, mid+2))); + (set_y(upper_left, y+1), bottom_right), + true); for x in x..=get_x(bottom_right) { - grid[(x, mid+2)].set_ch(' '); - grid[(x, mid+2)].set_bg(Color::Default); - grid[(x, mid+2)].set_fg(Color::Default); + grid[(x, y)].set_ch(' '); + grid[(x, y)].set_bg(Color::Default); + grid[(x, y)].set_fg(Color::Default); } - let x = write_string_to_grid(&format!("To: {}", envelope.get_to()), + let (x,y) = write_string_to_grid(&format!("To: {}", envelope.get_to()), grid, Color::Byte(33), Color::Default, - (set_y(upper_left, mid+3), set_y(bottom_right, mid+3))); + (set_y(upper_left, y+1), bottom_right), + true); for x in x..=get_x(bottom_right) { - grid[(x, mid+3)].set_ch(' '); - grid[(x, mid+3)].set_bg(Color::Default); - grid[(x, mid+3)].set_fg(Color::Default); + grid[(x, y)].set_ch(' '); + grid[(x, y)].set_bg(Color::Default); + grid[(x, y)].set_fg(Color::Default); } - let x = write_string_to_grid(&format!("Subject: {}", envelope.get_subject()), + let (x,y) = write_string_to_grid(&format!("Subject: {}", envelope.get_subject()), grid, Color::Byte(33), Color::Default, - (set_y(upper_left, mid+4), set_y(bottom_right, mid+4))); + (set_y(upper_left, y+1), bottom_right), + true); for x in x..=get_x(bottom_right) { - grid[(x, mid+4)].set_ch(' '); - grid[(x, mid+4)].set_bg(Color::Default); - grid[(x, mid+4)].set_fg(Color::Default); + grid[(x, y)].set_ch(' '); + grid[(x, y)].set_bg(Color::Default); + grid[(x, y)].set_fg(Color::Default); } - let x = write_string_to_grid(&format!("Message-ID: {}", envelope.get_message_id_raw()), + let (x, y) = write_string_to_grid(&format!("Message-ID: {}", envelope.get_message_id_raw()), grid, Color::Byte(33), Color::Default, - (set_y(upper_left, mid+5), set_y(bottom_right, mid+5))); + (set_y(upper_left, y+1), bottom_right), + true); for x in x..=get_x(bottom_right) { - grid[(x, mid+5)].set_ch(' '); - grid[(x, mid+5)].set_bg(Color::Default); - grid[(x, mid+5)].set_fg(Color::Default); + grid[(x, y)].set_ch(' '); + grid[(x, y)].set_bg(Color::Default); + grid[(x, y)].set_fg(Color::Default); } + clear_area(grid, + (set_y(upper_left, y+1), set_y(bottom_right, y+2))); + context.dirty_areas.push_back((set_y(upper_left, mid), set_y(bottom_right, y+1))); + y + 2 + }; + + + if !self.dirty { + if let Some(ref mut p) = self.pager { + p.draw(grid, + ((get_x(upper_left), y), bottom_right), + context); + } + return; } - clear_area(grid, - (set_y(upper_left, mid+headers_rows), set_y(bottom_right, mid+headers_rows))); - - context.dirty_areas.push_back((set_y(upper_left, mid), set_y(bottom_right, mid+headers_rows))); + self.dirty = false; /* Draw body */ self.draw_mail_view(grid, - ((get_x(upper_left), get_y(upper_left) + mid + headers_rows + 1), bottom_right), + ((get_x(upper_left), y), bottom_right), context); } @@ -563,180 +541,3 @@ impl Component for MailListing { self.dirty || self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false) } } - -#[derive(Debug)] -struct AccountMenuEntry { - name: String, - index: usize, - entries: Vec<(usize, Folder)>, -} - - -#[derive(Debug)] -pub struct AccountMenu { - accounts: Vec, - dirty: bool, - cursor: Option<(usize, usize)>, -} - -impl AccountMenu { - pub fn new(accounts: &Vec) -> Self { - let accounts = accounts.iter().enumerate().map(|(i, a)| { - AccountMenuEntry { - name: a.get_name().to_string(), - index: i, - entries: { - let mut entries = Vec::with_capacity(a.len()); - for (idx, acc) in a.list_folders().iter().enumerate() { - entries.push((idx, acc.clone())); - } - entries} - } - }).collect(); - AccountMenu { - accounts: accounts, - dirty: true, - cursor: None, - } - } - fn print_account(&self, grid: &mut CellBuffer, area: Area, a: &AccountMenuEntry) -> usize { - if !is_valid_area!(area) { - eprintln!("BUG: invalid area in print_account"); - } - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); - - - let highlight = self.cursor.map(|(x,_)| x == a.index).unwrap_or(false); - - let mut parents: Vec> = vec!(None; a.entries.len()); - - for (idx, e) in a.entries.iter().enumerate() { - for c in e.1.get_children() { - parents[*c] = Some(idx); - } - } - let mut roots = Vec::new(); - for (idx, c) in parents.iter().enumerate() { - if c.is_none() { - roots.push(idx); - } - } - - let mut inc = 0; - let mut depth = String::from(""); - let mut s = String::from(format!("{}\n", a.name)); - fn pop(depth: &mut String) { - depth.pop(); - depth.pop(); - } - - fn push(depth: &mut String, c: char) { - depth.push(c); - } - - fn print(root: usize, parents: &Vec>, depth: &mut String, entries: &Vec<(usize, Folder)>, s: &mut String, inc: &mut usize) -> () { - let len = s.len(); - s.insert_str(len, &format!("{} {}\n ", *inc, &entries[root].1.get_name())); - *inc += 1; - let children_no = entries[root].1.get_children().len(); - for (idx, child) in entries[root].1.get_children().iter().enumerate() { - let len = s.len(); - s.insert_str(len, &format!("{}├─", depth)); - push(depth, if idx == children_no - 1 {'│'} else { ' ' }); - print(*child, parents, depth, entries, s, inc); - pop(depth); - } - } - for r in roots { - print(r, &parents, &mut depth, &a.entries, &mut s, &mut inc); - } - - let lines: Vec<&str> = s.lines().collect(); - let lines_len = lines.len(); - let mut idx = 0; - for y in get_y(upper_left)..get_y(bottom_right) { - if idx == lines_len { - break; - } - let s = if idx == lines_len - 2 { - format!("{}", lines[idx].replace("├", "└")) - } else { - format!("{}", lines[idx]) - }; - let color_fg = if highlight { - if idx > 1 && self.cursor.unwrap().1 == idx - 2 { - Color::Byte(233) - } else { - Color::Byte(15) - } - } else { - Color::Default - }; - - let color_bg = if highlight { - if idx > 1 && self.cursor.unwrap().1 == idx - 2 { - Color::Byte(15) - } else { - Color::Byte(233) - } - } else { - Color::Default - }; - - let x = write_string_to_grid(&s, - grid, - color_fg, - color_bg, - (set_y(upper_left, y), bottom_right)); - - if highlight && idx > 1 && self.cursor.unwrap().1 == idx - 2 { - change_colors(grid, ((x, y),(get_x(bottom_right)+1, y)), color_fg , color_bg); - } else { - change_colors(grid, ((x, y),set_y(bottom_right, y)), color_fg , color_bg); - } - idx += 1; - } - idx - 1 - - } -} - -impl Component for AccountMenu { - fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - if !self.is_dirty() { - return; - } - clear_area(grid, area); - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); - self.dirty = false; - let mut y = get_y(upper_left) + 1; - for a in &self.accounts { - y += self.print_account(grid, - (set_y(upper_left, y), bottom_right), - &a); - } - - context.dirty_areas.push_back(area); - } - fn process_event(&mut self, event: &UIEvent, _context: &mut Context) { - match event.event_type { - UIEventType::RefreshMailbox(c) => { - self.cursor = Some(c); - self.dirty = true; - }, - UIEventType::ChangeMode(UIMode::Normal) => { - self.dirty = true; - }, - UIEventType::Resize => { - self.dirty = true; - }, - _ => { - }, - } - } - fn is_dirty(&self) -> bool { - self.dirty - } -} diff --git a/ui/src/components/mail/mod.rs b/ui/src/components/mail/mod.rs new file mode 100644 index 000000000..83c8ef130 --- /dev/null +++ b/ui/src/components/mail/mod.rs @@ -0,0 +1,189 @@ +/*! Entities that handle Mail specific functions. + */ +use super::*; + +pub mod listing; +pub use listing::*; + + +#[derive(Debug)] +struct AccountMenuEntry { + name: String, + index: usize, + entries: Vec<(usize, Folder)>, +} + + +#[derive(Debug)] +pub struct AccountMenu { + accounts: Vec, + dirty: bool, + cursor: Option<(usize, usize)>, +} + +impl AccountMenu { + pub fn new(accounts: &Vec) -> Self { + let accounts = accounts.iter().enumerate().map(|(i, a)| { + AccountMenuEntry { + name: a.get_name().to_string(), + index: i, + entries: { + let mut entries = Vec::with_capacity(a.len()); + for (idx, acc) in a.list_folders().iter().enumerate() { + entries.push((idx, acc.clone())); + } + entries} + } + }).collect(); + AccountMenu { + accounts: accounts, + dirty: true, + cursor: None, + } + } + fn print_account(&self, grid: &mut CellBuffer, area: Area, a: &AccountMenuEntry) -> usize { + if !is_valid_area!(area) { + eprintln!("BUG: invalid area in print_account"); + } + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + + + let highlight = self.cursor.map(|(x,_)| x == a.index).unwrap_or(false); + + let mut parents: Vec> = vec!(None; a.entries.len()); + + for (idx, e) in a.entries.iter().enumerate() { + for c in e.1.get_children() { + parents[*c] = Some(idx); + } + } + let mut roots = Vec::new(); + for (idx, c) in parents.iter().enumerate() { + if c.is_none() { + roots.push(idx); + } + } + + let mut inc = 0; + let mut depth = String::from(""); + let mut s = String::from(format!("{}\n", a.name)); + fn pop(depth: &mut String) { + depth.pop(); + depth.pop(); + } + + fn push(depth: &mut String, c: char) { + depth.push(c); + } + + fn print(root: usize, parents: &Vec>, depth: &mut String, entries: &Vec<(usize, Folder)>, s: &mut String, inc: &mut usize) -> () { + let len = s.len(); + s.insert_str(len, &format!("{} {}\n ", *inc, &entries[root].1.get_name())); + *inc += 1; + let children_no = entries[root].1.get_children().len(); + for (idx, child) in entries[root].1.get_children().iter().enumerate() { + let len = s.len(); + s.insert_str(len, &format!("{}├─", depth)); + push(depth, if idx == children_no - 1 {'│'} else { ' ' }); + print(*child, parents, depth, entries, s, inc); + pop(depth); + } + } + for r in roots { + print(r, &parents, &mut depth, &a.entries, &mut s, &mut inc); + } + + let lines: Vec<&str> = s.lines().collect(); + let lines_len = lines.len(); + let mut idx = 0; + for y in get_y(upper_left)..get_y(bottom_right) { + if idx == lines_len { + break; + } + let s = if idx == lines_len - 2 { + format!("{}", lines[idx].replace("├", "└")) + } else { + format!("{}", lines[idx]) + }; + let color_fg = if highlight { + if idx > 1 && self.cursor.unwrap().1 == idx - 2 { + Color::Byte(233) + } else { + Color::Byte(15) + } + } else { + Color::Default + }; + + let color_bg = if highlight { + if idx > 1 && self.cursor.unwrap().1 == idx - 2 { + Color::Byte(15) + } else { + Color::Byte(233) + } + } else { + Color::Default + }; + + let (x, _) = write_string_to_grid(&s, + grid, + color_fg, + color_bg, + (set_y(upper_left, y), bottom_right), + false); + + if highlight && idx > 1 && self.cursor.unwrap().1 == idx - 2 { + change_colors(grid, ((x, y),(get_x(bottom_right)+1, y)), color_fg , color_bg); + } else { + change_colors(grid, ((x, y),set_y(bottom_right, y)), color_fg , color_bg); + } + idx += 1; + } + if idx == 0 { + 0 + } else { + idx - 1 + } + + } +} + +impl Component for AccountMenu { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + if !self.is_dirty() { + return; + } + clear_area(grid, area); + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + self.dirty = false; + let mut y = get_y(upper_left) + 1; + for a in &self.accounts { + y += self.print_account(grid, + (set_y(upper_left, y), bottom_right), + &a); + } + + context.dirty_areas.push_back(area); + } + fn process_event(&mut self, event: &UIEvent, _context: &mut Context) { + match event.event_type { + UIEventType::RefreshMailbox(c) => { + self.cursor = Some(c); + self.dirty = true; + }, + UIEventType::ChangeMode(UIMode::Normal) => { + self.dirty = true; + }, + UIEventType::Resize => { + self.dirty = true; + }, + _ => { + }, + } + } + fn is_dirty(&self) -> bool { + self.dirty + } +} diff --git a/src/ui/components/mod.rs b/ui/src/components/mod.rs similarity index 87% rename from src/ui/components/mod.rs rename to ui/src/components/mod.rs index 3f85cc0e0..a4df67686 100644 --- a/src/ui/components/mod.rs +++ b/ui/src/components/mod.rs @@ -25,11 +25,11 @@ See the `Component` Trait for more details. */ +use super::*; pub mod utilities; pub mod mail; pub mod notifications; -use super::*; pub use utilities::*; pub use mail::*; @@ -126,34 +126,43 @@ pub fn change_colors(grid: &mut CellBuffer, area: Area, fg_color: Color, bg_colo /// Write an `&str` to a `CellBuffer` in a specified `Area` with the passed colors. -fn write_string_to_grid(s: &str, grid: &mut CellBuffer, fg_color: Color, bg_color: Color, area: Area) -> usize { +fn write_string_to_grid(s: &str, grid: &mut CellBuffer, fg_color: Color, bg_color: Color, area: Area, line_break: bool) -> Pos { let bounds = grid.size(); let upper_left = upper_left!(area); let bottom_right = bottom_right!(area); let (mut x, mut y) = upper_left; if y > (get_y(bottom_right)) || x > get_x(bottom_right) || y > get_y(bounds) || x > get_x(bounds) { - return 0; + eprintln!(" Invalid area with string {} and area {:?}", s, area); + return (x, y); } - for c in s.chars() { - grid[(x,y)].set_ch(c); - grid[(x,y)].set_fg(fg_color); - grid[(x,y)].set_bg(bg_color); - x += 1; + for l in s.lines() { + 'char: for c in l.chars() { + grid[(x,y)].set_ch(c); + grid[(x,y)].set_fg(fg_color); + grid[(x,y)].set_bg(bg_color); + x += 1; - if x == (get_x(bottom_right))+1 || x > get_x(bounds) { - x = get_x(upper_left); - y += 1; - if y == (get_y(bottom_right))+1 || y > get_y(bounds) { - return x; + if x == (get_x(bottom_right))+1 || x > get_x(bounds) { + x = get_x(upper_left); + y += 1; + if y >= (get_y(bottom_right)) || y > get_y(bounds) { + return (x, y - 1); + } + if !line_break { + break 'char; + } } } } - x + (x, y) } /// Completely clear an `Area` with an empty char and the terminal's default colors. fn clear_area(grid: &mut CellBuffer, area: Area) { + if !is_valid_area!(area) { + return; + } let upper_left = upper_left!(area); let bottom_right = bottom_right!(area); for y in get_y(upper_left)..=get_y(bottom_right) { diff --git a/src/ui/components/notifications.rs b/ui/src/components/notifications.rs similarity index 94% rename from src/ui/components/notifications.rs rename to ui/src/components/notifications.rs index 31497f08f..1687fa17b 100644 --- a/src/ui/components/notifications.rs +++ b/ui/src/components/notifications.rs @@ -3,8 +3,8 @@ */ use notify_rust::Notification as notify_Notification; -use ui::*; -use ui::components::*; +use super::*; +use super::components::*; /// Passes notifications to the OS using the XDG specifications. pub struct XDGNotifications {} diff --git a/src/ui/components/utilities.rs b/ui/src/components/utilities.rs similarity index 93% rename from src/ui/components/utilities.rs rename to ui/src/components/utilities.rs index 3324761c6..61ab2e70c 100644 --- a/src/ui/components/utilities.rs +++ b/ui/src/components/utilities.rs @@ -1,8 +1,6 @@ /*! Various useful components that can be used in a generic fashion. */ - -use ui::components::*; -use ui::cells::*; +use super::*; /// A horizontally split in half container. pub struct HSplit { @@ -24,6 +22,9 @@ impl HSplit { impl Component for HSplit { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + if !is_valid_area!(area) { + return; + } let upper_left = upper_left!(area); let bottom_right = bottom_right!(area); let total_rows = get_y(bottom_right) - get_y(upper_left); @@ -71,6 +72,9 @@ impl VSplit { impl Component for VSplit { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + if !is_valid_area!(area) { + return; + } let upper_left = upper_left!(area); let bottom_right = bottom_right!(area); let total_cols = get_x(bottom_right) - get_x(upper_left); @@ -186,7 +190,8 @@ impl Pager { content, Color::Default, Color::Default, - ((0, i), (width -1, i))); + ((0, i), (width -1, i)), + true); } } } @@ -194,6 +199,9 @@ impl Pager { impl Component for Pager { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + if !is_valid_area!(area) { + return; + } if !self.is_dirty() { return; } @@ -272,7 +280,7 @@ impl StatusBar { grid, Color::Byte(123), Color::Byte(26), - area); + area, false); change_colors(grid, area, Color::Byte(123), Color::Byte(26)); context.dirty_areas.push_back(area); } @@ -282,7 +290,7 @@ impl StatusBar { grid, Color::Byte(219), Color::Byte(88), - area); + area, false); change_colors(grid, area, Color::Byte(219), Color::Byte(88)); context.dirty_areas.push_back(area); } @@ -291,6 +299,9 @@ impl StatusBar { impl Component for StatusBar { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + if !is_valid_area!(area) { + return; + } let upper_left = upper_left!(area); let bottom_right = bottom_right!(area); @@ -359,3 +370,26 @@ impl Component for StatusBar { self.dirty || self.container.component.is_dirty() } } + + +// A box with a text content. +pub struct TextBox { + content: String, +} + +impl TextBox { + pub fn new(s: String) -> Self { + TextBox { + content: s, + } + } +} + +impl Component for TextBox { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + + } + fn process_event(&mut self, event: &UIEvent, context: &mut Context) { + return; + } +} diff --git a/src/ui/execute/mod.rs b/ui/src/execute/mod.rs similarity index 100% rename from src/ui/execute/mod.rs rename to ui/src/execute/mod.rs diff --git a/src/ui/mod.rs b/ui/src/lib.rs similarity index 99% rename from src/ui/mod.rs rename to ui/src/lib.rs index e08573627..6fa4f4a17 100644 --- a/src/ui/mod.rs +++ b/ui/src/lib.rs @@ -30,20 +30,20 @@ #[macro_use] mod position; -pub mod components; mod cells; +pub mod components; #[macro_use] mod execute; -use self::execute::goto; +use execute::goto; pub use self::position::*; use self::cells::*; pub use self::components::*; extern crate melib; +extern crate notify_rust; use melib::*; -use std; use std::io::{Write, }; use std::collections::VecDeque; use std::fmt; @@ -53,6 +53,9 @@ use termion::raw::IntoRawMode; use termion::event::{Key as TermionKey, }; use termion::input::TermRead; +extern crate chan; +#[macro_use] +extern crate nom; use chan::Sender; /// `ThreadEvent` encapsulates all of the possible values we need to transfer between our threads diff --git a/src/ui/position.rs b/ui/src/position.rs similarity index 100% rename from src/ui/position.rs rename to ui/src/position.rs