meli/ui/src/components/mail/view.rs

389 lines
15 KiB
Rust
Raw Normal View History

use super::*;
2018-07-27 18:01:52 +03:00
use linkify::{Link, LinkFinder};
use std::process::{Command, Stdio};
use mime_apps::query_default_app;
#[derive(PartialEq, Debug)]
enum ViewMode {
Normal,
Url,
Attachment(usize),
2018-07-25 22:37:28 +03:00
// Raw,
}
impl ViewMode {
fn is_attachment(&self) -> bool {
match self {
ViewMode::Attachment(_) => true,
_ => false,
}
}
}
2018-07-22 23:11:07 +03:00
/// Contains an Envelope view, with sticky headers, a pager for the body, and subviews for more
/// menus
pub struct MailView {
coordinates: (usize, usize, usize),
pager: Option<Pager>,
subview: Option<Box<MailView>>,
dirty: bool,
mode: ViewMode,
cmd_buf: String,
}
impl MailView {
2018-07-27 18:01:52 +03:00
pub fn new(
coordinates: (usize, usize, usize),
pager: Option<Pager>,
subview: Option<Box<MailView>>,
) -> Self {
MailView {
coordinates: coordinates,
pager: pager,
subview: subview,
dirty: true,
mode: ViewMode::Normal,
cmd_buf: String::with_capacity(4),
}
}
}
impl Component for MailView {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
let (envelope_idx, y): (usize, usize) = {
2018-07-27 18:01:52 +03:00
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];
2018-07-27 18:01:52 +03:00
let (x, y) = write_string_to_grid(
&format!("Date: {}", envelope.date_as_str()),
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);
grid[(x, y)].set_fg(Color::Default);
}
2018-07-27 18:01:52 +03:00
let (x, y) = write_string_to_grid(
&format!("From: {}", envelope.from_to_string()),
grid,
Color::Byte(33),
Color::Default,
(set_y(upper_left, y + 1), bottom_right),
true,
);
for x in x..=get_x(bottom_right) {
grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default);
grid[(x, y)].set_fg(Color::Default);
}
2018-07-27 18:01:52 +03:00
let (x, y) = write_string_to_grid(
&format!("To: {}", envelope.to_to_string()),
grid,
Color::Byte(33),
Color::Default,
(set_y(upper_left, y + 1), bottom_right),
true,
);
for x in x..=get_x(bottom_right) {
grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default);
grid[(x, y)].set_fg(Color::Default);
}
2018-07-27 18:01:52 +03:00
let (x, y) = write_string_to_grid(
&format!("Subject: {}", envelope.subject()),
grid,
Color::Byte(33),
Color::Default,
(set_y(upper_left, y + 1), bottom_right),
true,
);
for x in x..=get_x(bottom_right) {
grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default);
grid[(x, y)].set_fg(Color::Default);
}
2018-07-27 18:01:52 +03:00
let (x, y) = write_string_to_grid(
&format!("Message-ID: {}", envelope.message_id_raw()),
grid,
Color::Byte(33),
Color::Default,
(set_y(upper_left, y + 1), bottom_right),
true,
);
for x in x..=get_x(bottom_right) {
grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default);
grid[(x, y)].set_fg(Color::Default);
}
2018-07-27 18:01:52 +03:00
clear_area(grid, (set_y(upper_left, y + 1), set_y(bottom_right, y + 2)));
context
.dirty_areas
.push_back((upper_left, set_y(bottom_right, y + 1)));
(envelope_idx, y + 1)
};
if self.dirty {
let buf = {
2018-07-27 18:01:52 +03:00
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 finder = LinkFinder::new();
let mut text = match self.mode {
ViewMode::Url => {
2018-07-25 22:37:28 +03:00
let mut t = envelope.body().text().to_string();
for (lidx, l) in finder.links(&envelope.body().text()).enumerate() {
2018-07-27 18:01:52 +03:00
t.insert_str(l.start() + (lidx * 3), &format!("[{}]", lidx));
}
2018-07-25 22:37:28 +03:00
if envelope.body().count_attachments() > 1 {
2018-07-27 18:01:52 +03:00
t = envelope.body().attachments().iter().enumerate().fold(
t,
|mut s, (idx, a)| {
s.push_str(&format!("[{}] {}\n\n", idx, a));
s
},
);
2018-07-25 22:37:28 +03:00
}
t
2018-07-27 18:01:52 +03:00
}
2018-07-25 22:37:28 +03:00
ViewMode::Attachment(aidx) => {
let attachments = envelope.body().attachments();
let mut ret = format!("Viewing attachment. Press `r` to return \n");
ret.push_str(&attachments[aidx].text());
ret
2018-07-27 18:01:52 +03:00
}
2018-07-25 22:37:28 +03:00
_ => {
let mut t = envelope.body().text().to_string();
if envelope.body().count_attachments() > 1 {
2018-07-27 18:01:52 +03:00
t = envelope.body().attachments().iter().enumerate().fold(
t,
|mut s, (idx, a)| {
s.push_str(&format!("[{}] {}\n\n", idx, a));
s
},
);
2018-07-25 22:37:28 +03:00
}
t
2018-07-27 18:01:52 +03:00
}
};
let mut buf = CellBuffer::from(&text);
match self.mode {
ViewMode::Url => {
2018-07-24 11:34:44 +03:00
// URL indexes must be colored (ugh..)
let lines: Vec<&str> = text.split('\n').collect();
let mut shift = 0;
2018-07-24 20:20:32 +03:00
for r in lines.iter() {
for l in finder.links(&r) {
2018-07-24 11:34:44 +03:00
buf[(l.start() + shift - 1, 0)].set_fg(Color::Byte(226));
buf[(l.start() + shift - 2, 0)].set_fg(Color::Byte(226));
buf[(l.start() + shift - 3, 0)].set_fg(Color::Byte(226));
}
2018-07-24 11:34:44 +03:00
// Each Cell represents one char so next line will be:
2018-07-27 18:01:52 +03:00
shift += r.chars().count() + 1;
}
2018-07-27 18:01:52 +03:00
}
_ => {}
}
2018-07-25 22:37:28 +03:00
buf
};
let cursor_pos = if self.mode.is_attachment() {
Some(0)
} else {
self.pager.as_mut().map(|p| p.cursor_pos())
};
2018-07-22 23:11:07 +03:00
// TODO: pass string instead of envelope
2018-07-24 11:34:44 +03:00
self.pager = Some(Pager::from_buf(buf, cursor_pos));
self.dirty = false;
}
2018-07-27 18:01:52 +03:00
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 {
2018-07-24 13:28:15 +03:00
UIEventType::Input(Key::Esc) => {
2018-07-25 22:37:28 +03:00
self.cmd_buf.clear();
2018-07-27 18:01:52 +03:00
}
UIEventType::Input(Key::Char(c)) if c >= '0' && c <= '9' => {
//TODO:this should be an Action
self.cmd_buf.push(c);
2018-07-27 18:01:52 +03:00
}
UIEventType::Input(Key::Char('r')) if self.mode.is_attachment() => {
//TODO:one quit shortcut?
2018-07-25 22:37:28 +03:00
self.mode = ViewMode::Normal;
self.dirty = true;
2018-07-27 18:01:52 +03:00
}
UIEventType::Input(Key::Char('a'))
if self.cmd_buf.len() > 0 && self.mode == ViewMode::Normal =>
{
//TODO:this should be an Action
let lidx = self.cmd_buf.parse::<usize>().unwrap();
self.cmd_buf.clear();
2018-07-24 20:20:32 +03:00
{
2018-07-27 18:01:52 +03:00
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];
if let Some(u) = envelope.body().attachments().get(lidx) {
2018-07-25 22:37:28 +03:00
match u.content_type().0 {
ContentType::Text => {
self.mode = ViewMode::Attachment(lidx);
self.dirty = true;
2018-07-27 18:01:52 +03:00
}
2018-07-25 22:37:28 +03:00
ContentType::Multipart { .. } => {
2018-07-27 18:01:52 +03:00
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::StatusNotification(format!(
"Multipart attachments are not supported yet."
)),
});
return;
2018-07-27 18:01:52 +03:00
}
2018-07-25 22:37:28 +03:00
ContentType::Unsupported { .. } => {
let attachment_type = u.mime_type();
let binary = query_default_app(&attachment_type);
if let Ok(binary) = binary {
let mut p = create_temp_file(&decode(u), None);
Command::new(&binary)
.arg(p.path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect(&format!("Failed to start {}", binary.display()));
} else {
2018-07-27 18:01:52 +03:00
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::StatusNotification(format!(
"Couldn't find a default application for type {}",
attachment_type
)),
});
return;
}
2018-07-27 18:01:52 +03:00
}
2018-07-25 22:37:28 +03:00
}
} else {
2018-07-27 18:01:52 +03:00
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::StatusNotification(format!(
"Attachment `{}` not found.",
lidx
)),
});
return;
}
};
2018-07-27 18:01:52 +03:00
}
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::<usize>().unwrap();
self.cmd_buf.clear();
let url = {
2018-07-27 18:01:52 +03:00
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<Link> = finder.links(&t).collect();
if let Some(u) = links.get(lidx) {
u.as_str().to_string()
} else {
2018-07-27 18:01:52 +03:00
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::StatusNotification(format!(
"Link `{}` not found.",
lidx
)),
});
return;
}
};
2018-07-24 20:20:32 +03:00
Command::new("xdg-open")
.arg(url)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Failed to start xdg_open");
2018-07-27 18:01:52 +03:00
}
UIEventType::Input(Key::Char('u')) => {
//TODO:this should be an Action
match self.mode {
2018-07-27 18:01:52 +03:00
ViewMode::Normal => self.mode = ViewMode::Url,
ViewMode::Url => self.mode = ViewMode::Normal,
_ => {}
}
self.dirty = true;
2018-07-27 18:01:52 +03:00
}
_ => {}
}
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);
}
}
}
fn is_dirty(&self) -> bool {
2018-07-27 18:01:52 +03:00
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)
}
}