diff --git a/ui/Cargo.toml b/ui/Cargo.toml index d9c862fa..72ec77e1 100644 --- a/ui/Cargo.toml +++ b/ui/Cargo.toml @@ -12,3 +12,5 @@ notify = "4.0.1" notify-rust = "^3" nom = "3.2.0" chan-signal = "0.3.1" + +linkify = "0.3.1" diff --git a/ui/src/components/mail/view.rs b/ui/src/components/mail/view.rs index 746fb8d3..b8a43ffa 100644 --- a/ui/src/components/mail/view.rs +++ b/ui/src/components/mail/view.rs @@ -1,6 +1,16 @@ use super::*; +use linkify::{LinkFinder, Link}; +use std::process::{Command, Stdio}; +#[derive(PartialEq, Debug)] +enum ViewMode { + Normal, + Url, + Attachment, + Raw, +} + /// Contains an Envelope view, with sticky headers, a pager for the body, and subviews for more /// menus pub struct MailView { @@ -8,6 +18,9 @@ pub struct MailView { pager: Option, subview: Option>, dirty: bool, + mode: ViewMode, + + cmd_buf: String, } impl MailView { @@ -18,6 +31,9 @@ impl MailView { pager: pager, subview: subview, dirty: true, + mode: ViewMode::Normal, + + cmd_buf: String::with_capacity(4), } } } @@ -40,11 +56,11 @@ impl Component for MailView { let envelope: &Envelope = &mailbox.collection[envelope_idx]; let (x,y) = write_string_to_grid(&format!("Date: {}", envelope.date_as_str()), - grid, - Color::Byte(33), - Color::Default, - area, - true); + grid, + Color::Byte(33), + Color::Default, + area, + true); for x in x..=get_x(bottom_right) { grid[(x, y)].set_ch(' '); grid[(x, y)].set_bg(Color::Default); @@ -99,18 +115,88 @@ impl Component for MailView { context.dirty_areas.push_back((upper_left, set_y(bottom_right, y+1))); (envelope_idx, y + 1) }; + if self.dirty { + let text = { + let mailbox_idx = self.coordinates; // coordinates are mailbox idxs + let mailbox = &mut context.accounts[mailbox_idx.0][mailbox_idx.1].as_ref().unwrap().as_ref().unwrap(); + let envelope : &Envelope = &mailbox.collection[envelope_idx]; + let mut text = match self.mode { + ViewMode::Url => { + let finder = LinkFinder::new(); + let mut t = envelope.body().text().to_string(); + for (lidx, l) in finder.links(&envelope.body().text()).enumerate() { + t.insert_str(l.start()+(lidx*3), &format!("[{}]", lidx)); + } + t + }, + _ => envelope.body().text().to_string(), + }; + if envelope.body().count_attachments() > 1 { + text = envelope.body().attachments().iter().enumerate().fold(text, |mut s, (idx, a)| { s.push_str(&format!("[{}] {}\n\n", idx, a)); s }); + } + text + }; + let cursor_pos = self.pager.as_mut().map(|p| p.cursor_pos()); // TODO: pass string instead of envelope - self.pager = Some(Pager::from_envelope((self.coordinates.0, self.coordinates.1), envelope_idx, context)); + self.pager = Some(Pager::from_string(text, context, cursor_pos)); self.dirty = false; } self.pager.as_mut().map(|p| p.draw(grid, (set_y(upper_left, y + 1),bottom_right), context)); } fn process_event(&mut self, event: &UIEvent, context: &mut Context) { + match event.event_type { + UIEventType::Input(Key::Char(c)) if c >= '0' && c <= '9' => { //TODO:this should be an Action + match self.mode { + ViewMode::Url => { self.cmd_buf.push(c); + eprintln!("buf is {}", self.cmd_buf); + return; }, + _ => {}, + } + }, + UIEventType::Input(Key::Char('g')) if self.cmd_buf.len() > 0 && self.mode == ViewMode::Url => { //TODO:this should be an Action + + let lidx = self.cmd_buf.parse::().unwrap(); + self.cmd_buf.clear(); + let url = { + let threaded = context.accounts[self.coordinates.0].runtime_settings.threaded; + let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1].as_ref().unwrap().as_ref().unwrap(); + let envelope_idx: usize = if threaded { + mailbox.threaded_mail(self.coordinates.2) + } else { + self.coordinates.2 + }; + + let envelope: &Envelope = &mailbox.collection[envelope_idx]; + let finder = LinkFinder::new(); + let mut t = envelope.body().text().to_string(); + let links: Vec = finder.links(&t).collect(); + links[lidx].as_str().to_string() + }; + + + let open_url = Command::new("xdg-open") + .arg(url) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("Failed to start xdg_open"); + + }, + UIEventType::Input(Key::Char('u')) => { //TODO:this should be an Action + match self.mode { + ViewMode::Normal => { self.mode = ViewMode::Url }, + ViewMode::Url => { self.mode = ViewMode::Normal }, + _ => {}, + } + self.dirty = true; + }, + _ => {}, + } if let Some(ref mut sub) = self.subview { sub.process_event(event, context); - + } else { if let Some(ref mut p) = self.pager { p.process_event(event, context); @@ -119,6 +205,6 @@ impl Component for MailView { } fn is_dirty(&self) -> bool { self.dirty || self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false) || - self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false) + self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false) } } diff --git a/ui/src/components/utilities.rs b/ui/src/components/utilities.rs index 71965425..2d14adfc 100644 --- a/ui/src/components/utilities.rs +++ b/ui/src/components/utilities.rs @@ -47,7 +47,6 @@ impl Component for HSplit { let _ = self.bottom.component.draw(grid, ((get_x(upper_left), get_y(upper_left) + mid), bottom_right), context); - grid[bottom_right].set_ch('b'); } fn process_event(&mut self, event: &UIEvent, context: &mut Context) { self.top.rcv_event(event, context); @@ -143,14 +142,10 @@ pub struct Pager { content: CellBuffer, } -// TODO: Make a `new` method that is content agnostic. impl Pager { - pub fn from_envelope(mailbox_idx: (usize, usize), envelope_idx: usize, context: &mut Context) -> Self { - let mailbox = &mut context.accounts[mailbox_idx.0][mailbox_idx.1].as_ref().unwrap().as_ref().unwrap(); - let envelope : &Envelope = &mailbox.collection[envelope_idx]; + pub fn from_string(mut text: String, context: &mut Context, cursor_pos: Option) -> Self { let pager_filter: Option<&String> = context.settings.pager.filter.as_ref(); //let format_flowed: bool = context.settings.pager.format_flowed; - let mut text = envelope.body().text(); if let Some(bin) = pager_filter { use std::io::Write; use std::process::{Command, Stdio}; @@ -168,10 +163,7 @@ impl Pager { text = String::from_utf8_lossy(&filter_child.wait_with_output().expect("Failed to wait on filter").stdout).to_string(); } - let mut text = text.to_string(); - if envelope.body().count_attachments() > 1 { - text = envelope.body().attachments().iter().enumerate().fold(text, |mut s, (idx, a)| { s.push_str(&format!("[{}] {}\n\n", idx, a)); s }); - } + let lines: Vec<&str> = text.trim().split('\n').collect(); let height = lines.len() + 1; let width = lines.iter().map(|l| l.len()).max().unwrap_or(0); @@ -179,7 +171,7 @@ impl Pager { //interpret_format_flowed(&text); Pager::print_string(&mut content, &text); Pager { - cursor_pos: 0, + cursor_pos: cursor_pos.unwrap_or(0), height: height, width: width, dirty: true, @@ -214,6 +206,9 @@ impl Pager { } } } + pub fn cursor_pos(&self) -> usize { + self.cursor_pos + } } impl Component for Pager { diff --git a/ui/src/lib.rs b/ui/src/lib.rs index 79ad174f..93a25bee 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -45,6 +45,7 @@ extern crate notify_rust; #[macro_use] extern crate chan; extern crate chan_signal; +extern crate linkify; use melib::*; use std::io::{Write, };