summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--melib/src/mailbox/email/attachments.rs14
-rw-r--r--ui/src/components/mail/view.rs106
-rw-r--r--ui/src/components/mail/view/envelope.rs77
-rw-r--r--ui/src/components/mail/view/html.rs107
-rw-r--r--ui/src/conf.rs57
-rw-r--r--ui/src/conf/pager.rs20
6 files changed, 272 insertions, 109 deletions
diff --git a/melib/src/mailbox/email/attachments.rs b/melib/src/mailbox/email/attachments.rs
index ec126ca2..ed0a60ab 100644
--- a/melib/src/mailbox/email/attachments.rs
+++ b/melib/src/mailbox/email/attachments.rs
@@ -412,9 +412,9 @@ fn decode_rfc822(_raw: &[u8]) -> Attachment {
*/
}
-type Filter = Box<Fn(&Attachment, &mut Vec<u8>) -> ()>;
+type Filter<'a> = Box<FnMut(&'a Attachment, &mut Vec<u8>) -> () + 'a>;
-fn decode_rec_helper(a: &Attachment, filter: &Option<Filter>) -> Vec<u8> {
+fn decode_rec_helper<'a>(a: &'a Attachment, filter: &mut Option<Filter<'a>>) -> Vec<u8> {
let mut ret = match a.content_type {
ContentType::Unsupported { .. } => Vec::new(),
ContentType::Text { .. } => decode_helper(a, filter),
@@ -449,11 +449,11 @@ fn decode_rec_helper(a: &Attachment, filter: &Option<Filter>) -> Vec<u8> {
ret
}
-pub fn decode_rec(a: &Attachment, filter: Option<Filter>) -> Vec<u8> {
- decode_rec_helper(a, &filter)
+pub fn decode_rec<'a>(a: &'a Attachment, mut filter: Option<Filter<'a>>) -> Vec<u8> {
+ decode_rec_helper(a, &mut filter)
}
-fn decode_helper(a: &Attachment, filter: &Option<Filter>) -> Vec<u8> {
+fn decode_helper<'a>(a: &'a Attachment, filter: &mut Option<Filter<'a>>) -> Vec<u8> {
let charset = match a.content_type {
ContentType::Text { charset: c, .. } => c,
_ => Default::default(),
@@ -488,6 +488,6 @@ fn decode_helper(a: &Attachment, filter: &Option<Filter>) -> Vec<u8> {
ret
}
-pub fn decode(a: &Attachment, filter: Option<Filter>) -> Vec<u8> {
- decode_helper(a, &filter)
+pub fn decode<'a>(a: &'a Attachment, mut filter: Option<Filter<'a>>) -> Vec<u8> {
+ decode_helper(a, &mut filter)
}
diff --git a/ui/src/components/mail/view.rs b/ui/src/components/mail/view.rs
index d2549330..b73056fb 100644
--- a/ui/src/components/mail/view.rs
+++ b/ui/src/components/mail/view.rs
@@ -116,31 +116,52 @@ impl MailView {
}
/// Returns the string to be displayed in the Viewer
- fn attachment_to_text(&self, body: &Attachment) -> String {
+ fn attachment_to_text<'closure, 's: 'closure, 'context: 's>(
+ &'s self,
+ body: &'context Attachment,
+ context: &'context mut Context,
+ ) -> String {
let finder = LinkFinder::new();
let body_text = String::from_utf8_lossy(&decode_rec(
- &body,
- Some(Box::new(|a: &Attachment, v: &mut Vec<u8>| {
+ body,
+ Some(Box::new(move |a: &'closure Attachment, v: &mut Vec<u8>| {
if a.content_type().is_text_html() {
use std::io::Write;
use std::process::{Command, Stdio};
+ let settings = context.accounts[self.coordinates.0].runtime_settings.conf();
+ if let Some(filter_invocation) = settings.html_filter() {
+ let parts = split_command!(filter_invocation);
+ let (cmd, args) = (parts[0], &parts[1..]);
+ let command_obj = Command::new(cmd)
+ .args(args)
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .spawn();
+ if command_obj.is_err() {
+ context.replies.push_back(UIEvent {
+ id: 0,
+ event_type: UIEventType::Notification(
+ Some(format!(
+ "Failed to start html filter process: {}",
+ filter_invocation,
+ )),
+ String::new(),
+ ),
+ });
+ return;
+ }
- let mut html_filter = Command::new("w3m")
- .args(&["-I", "utf-8", "-T", "text/html"])
- .stdin(Stdio::piped())
- .stdout(Stdio::piped())
- .spawn()
- .expect("Failed to start html filter process");
-
- html_filter
- .stdin
- .as_mut()
- .unwrap()
- .write_all(&v)
- .expect("Failed to write to w3m stdin");
- *v = b"Text piped through `w3m`. Press `v` to open in web browser. \n\n"
- .to_vec();
- v.extend(html_filter.wait_with_output().unwrap().stdout);
+ let mut html_filter = command_obj.unwrap();
+ html_filter
+ .stdin
+ .as_mut()
+ .unwrap()
+ .write_all(&v)
+ .expect("Failed to write to stdin");
+ *v = format!("Text piped through `{}`. Press `v` to open in web browser. \n\n",
+ filter_invocation).into_bytes();
+ v.extend(html_filter.wait_with_output().unwrap().stdout);
+ }
}
})),
))
@@ -326,35 +347,42 @@ impl Component for MailView {
};
if self.dirty {
- let mailbox_idx = self.coordinates; // coordinates are mailbox idxs
- let mailbox = &context.accounts[mailbox_idx.0][mailbox_idx.1]
- .as_ref()
- .unwrap();
- let envelope: &Envelope = &mailbox.collection[&mailbox_idx.2];
- let op = context.accounts[mailbox_idx.0]
- .backend
- .operation(envelope.hash(), mailbox.folder.hash());
- let body = envelope.body(op);
+ let body = {
+ let mailbox_idx = self.coordinates; // coordinates are mailbox idxs
+ let mailbox = &context.accounts[mailbox_idx.0][mailbox_idx.1]
+ .as_ref()
+ .unwrap();
+ let envelope: &Envelope = &mailbox.collection[&mailbox_idx.2];
+ let op = context.accounts[mailbox_idx.0]
+ .backend
+ .operation(envelope.hash(), mailbox.folder.hash());
+ envelope.body(op)
+ };
match self.mode {
ViewMode::Attachment(aidx) if body.attachments()[aidx].is_html() => {
self.pager = None;
- self.subview = Some(Box::new(HtmlView::new(decode(
- &body.attachments()[aidx],
- None,
- ))));
+ let attachment = &body.attachments()[aidx];
+ self.subview = Some(Box::new(HtmlView::new(
+ decode(&attachment, None),
+ context,
+ self.coordinates.0,
+ )));
self.mode = ViewMode::Subview;
}
ViewMode::Normal if body.is_html() => {
- self.subview = Some(Box::new(HtmlView::new(decode(&body, None))));
+ self.subview = Some(Box::new(HtmlView::new(
+ decode(&body, None),
+ context,
+ self.coordinates.0,
+ )));
self.pager = None;
self.mode = ViewMode::Subview;
}
ViewMode::Subview | ViewMode::ContactSelector(_) => {}
_ => {
let text = {
- self.attachment_to_text(&body)
+ self.attachment_to_text(&body, context)
/*
- let text = self.attachment_to_text(&body);
// URL indexes must be colored (ugh..)
MailView::plain_text_to_buf(&text, self.mode == ViewMode::Url)
*/
@@ -530,8 +558,12 @@ impl Component for MailView {
self.mode = ViewMode::Subview;
match EnvelopeWrapper::new(u.bytes().to_vec()) {
Ok(wrapper) => {
- self.subview =
- Some(Box::new(EnvelopeView::new(wrapper, None, None)));
+ self.subview = Some(Box::new(EnvelopeView::new(
+ wrapper,
+ None,
+ None,
+ self.coordinates.0,
+ )));
}
Err(e) => {
context.replies.push_back(UIEvent {
diff --git a/ui/src/components/mail/view/envelope.rs b/ui/src/components/mail/view/envelope.rs
index 9f028195..5c490579 100644
--- a/ui/src/components/mail/view/envelope.rs
+++ b/ui/src/components/mail/view/envelope.rs
@@ -53,6 +53,7 @@ pub struct EnvelopeView {
mode: ViewMode,
wrapper: EnvelopeWrapper,
+ account_pos: usize,
cmd_buf: String,
}
@@ -68,6 +69,7 @@ impl EnvelopeView {
wrapper: EnvelopeWrapper,
pager: Option<Pager>,
subview: Option<Box<Component>>,
+ account_pos: usize,
) -> Self {
EnvelopeView {
pager,
@@ -75,13 +77,17 @@ impl EnvelopeView {
dirty: true,
mode: ViewMode::Normal,
wrapper,
-
+ account_pos,
cmd_buf: String::with_capacity(4),
}
}
/// Returns the string to be displayed in the Viewer
- fn attachment_to_text(&self, body: &Attachment) -> String {
+ fn attachment_to_text<'closure, 's: 'closure, 'context: 's>(
+ &'s self,
+ body: &'context Attachment,
+ context: &'context mut Context,
+ ) -> String {
let finder = LinkFinder::new();
let body_text = String::from_utf8_lossy(&decode_rec(
&body,
@@ -89,23 +95,40 @@ impl EnvelopeView {
if a.content_type().is_text_html() {
use std::io::Write;
use std::process::{Command, Stdio};
+ let settings = context.accounts[self.account_pos].runtime_settings.conf();
+ if let Some(filter_invocation) = settings.html_filter() {
+ let parts = split_command!(filter_invocation);
+ let (cmd, args) = (parts[0], &parts[1..]);
+ let command_obj = Command::new(cmd)
+ .args(args)
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .spawn();
+ if command_obj.is_err() {
+ context.replies.push_back(UIEvent {
+ id: 0,
+ event_type: UIEventType::Notification(
+ Some(format!(
+ "Failed to start html filter process: {}",
+ filter_invocation,
+ )),
+ String::new(),
+ ),
+ });
+ return;
+ }
- let mut html_filter = Command::new("w3m")
- .args(&["-I", "utf-8", "-T", "text/html"])
- .stdin(Stdio::piped())
- .stdout(Stdio::piped())
- .spawn()
- .expect("Failed to start html filter process");
-
- html_filter
- .stdin
- .as_mut()
- .unwrap()
- .write_all(&v)
- .expect("Failed to write to w3m stdin");
- *v = b"Text piped through `w3m`. Press `v` to open in web browser. \n\n"
- .to_vec();
- v.extend(html_filter.wait_with_output().unwrap().stdout);
+ let mut html_filter = command_obj.unwrap();
+ html_filter
+ .stdin
+ .as_mut()
+ .unwrap()
+ .write_all(&v)
+ .expect("Failed to write to stdin");
+ *v = format!("Text piped through `{}`. Press `v` to open in web browser. \n\n",
+ filter_invocation).into_bytes();
+ v.extend(html_filter.wait_with_output().unwrap().stdout);
+ }
}
})),
))
@@ -288,18 +311,24 @@ impl Component for EnvelopeView {
let body = self.wrapper.body_bytes(self.wrapper.buffer());
match self.mode {
ViewMode::Attachment(aidx) if body.attachments()[aidx].is_html() => {
- self.subview = Some(Box::new(HtmlView::new(decode(
- &body.attachments()[aidx],
- None,
- ))));
+ let attachment = &body.attachments()[aidx];
+ self.subview = Some(Box::new(HtmlView::new(
+ decode(&attachment, None),
+ context,
+ self.account_pos,
+ )));
}
ViewMode::Normal if body.is_html() => {
- self.subview = Some(Box::new(HtmlView::new(decode(&body, None))));
+ self.subview = Some(Box::new(HtmlView::new(
+ decode(&body, None),
+ context,
+ self.account_pos,
+ )));
self.mode = ViewMode::Subview;
}
_ => {
let text = {
- self.attachment_to_text(&body)
+ self.attachment_to_text(&body, context)
/*
let text = self.attachment_to_text(&body);
// URL indexes must be colored (ugh..)
diff --git a/ui/src/components/mail/view/html.rs b/ui/src/components/mail/view/html.rs
index 4eb4f2bc..b4ca9f55 100644
--- a/ui/src/components/mail/view/html.rs
+++ b/ui/src/components/mail/view/html.rs
@@ -30,27 +30,94 @@ pub struct HtmlView {
}
impl HtmlView {
- pub fn new(bytes: Vec<u8>) -> Self {
- let mut html_filter = Command::new("w3m")
- .args(&["-I", "utf-8", "-T", "text/html"])
- .stdin(Stdio::piped())
- .stdout(Stdio::piped())
- .spawn()
- .expect("Failed to start html filter process");
- html_filter
- .stdin
- .as_mut()
- .unwrap()
- .write_all(&bytes)
- .expect("Failed to write to w3m stdin");
- let mut display_text =
- String::from("Text piped through `w3m`. Press `v` to open in web browser. \n\n");
- display_text.push_str(&String::from_utf8_lossy(
- &html_filter.wait_with_output().unwrap().stdout,
- ));
+ pub fn new(bytes: Vec<u8>, context: &mut Context, account_pos: usize) -> Self {
+ let settings = context.accounts[account_pos].runtime_settings.conf();
+ if let Some(filter_invocation) = settings.html_filter() {
+ let parts = split_command!(filter_invocation);
+ let (cmd, args) = (parts[0], &parts[1..]);
+ let command_obj = Command::new(cmd)
+ .args(args)
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .spawn();
+ if command_obj.is_err() {
+ context.replies.push_back(UIEvent {
+ id: 0,
+ event_type: UIEventType::Notification(
+ Some(format!(
+ "Failed to start html filter process: {}",
+ filter_invocation
+ )),
+ String::new(),
+ ),
+ });
+ let pager = Pager::from_string(
+ String::from_utf8_lossy(&bytes).to_string(),
+ None,
+ None,
+ None,
+ );
+ HtmlView { pager, bytes }
+ } else {
+ let mut html_filter = command_obj.unwrap();
+ html_filter
+ .stdin
+ .as_mut()
+ .unwrap()
+ .write_all(&bytes)
+ .expect("Failed to write to html filter stdin");
+ let mut display_text = format!(
+ "Text piped through `{}`. Press `v` to open in web browser. \n\n",
+ filter_invocation
+ );
+ display_text.push_str(&String::from_utf8_lossy(
+ &html_filter.wait_with_output().unwrap().stdout,
+ ));
+
+ let pager = Pager::from_string(display_text, None, None, None);
+ HtmlView { pager, bytes }
+ }
+ } else {
+ if let Ok(mut html_filter) = Command::new("w3m")
+ .args(&["-I", "utf-8", "-T", "text/html"])
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .spawn()
+ {
+ html_filter
+ .stdin
+ .as_mut()
+ .unwrap()
+ .write_all(&bytes)
+ .expect("Failed to write to html filter stdin");
+ let mut display_text = String::from(
+ "Text piped through `w3m`. Press `v` to open in web browser. \n\n",
+ );
+ display_text.push_str(&String::from_utf8_lossy(
+ &html_filter.wait_with_output().unwrap().stdout,
+ ));
- let pager = Pager::from_string(display_text, None, None, None);
- HtmlView { pager, bytes }
+ let pager = Pager::from_string(display_text, None, None, None);
+ HtmlView { pager, bytes }
+ } else {
+ context.replies.push_back(UIEvent {
+ id: 0,
+ event_type: UIEventType::Notification(
+ Some(format!(
+ "Failed to find any application to use as html filter"
+ )),
+ String::new(),
+ ),
+ });
+ let pager = Pager::from_string(
+ String::from_utf8_lossy(&bytes).to_string(),
+ None,
+ None,
+ None,
+ );
+ HtmlView { pager, bytes }
+ }
+ }
}
}
diff --git a/ui/src/conf.rs b/ui/src/conf.rs
index 564540bf..2a5c96e1 100644
--- a/ui/src/conf.rs
+++ b/ui/src/conf.rs
@@ -33,6 +33,7 @@ pub use self::accounts::Account;
use self::config::{Config, File, FileFormat};
pub use self::shortcuts::*;
+use self::default_vals::*;
use self::notifications::NotificationsSettings;
use melib::conf::AccountSettings;
use melib::error::*;
@@ -43,8 +44,11 @@ use std::collections::HashMap;
use std::env;
use std::path::PathBuf;
-fn true_val() -> bool {
- true
+#[macro_export]
+macro_rules! split_command {
+ ($cmd:expr) => {{
+ $cmd.split_whitespace().collect::<Vec<&str>>()
+ }};
}
#[derive(Debug, Clone, Default, Deserialize)]
@@ -68,9 +72,17 @@ pub struct FileAccount {
sent_folder: String,
draft_folder: String,
identity: String,
+
+ #[serde(default = "none")]
display_name: Option<String>,
#[serde(deserialize_with = "index_from_str")]
index: IndexStyle,
+
+ /// A command to pipe html output before displaying it in a pager
+ /// Default: None
+ #[serde(default = "none", deserialize_with = "non_empty_string")]
+ html_filter: Option<String>,
+
folders: Option<HashMap<String, FolderConf>>,
}
@@ -108,6 +120,9 @@ impl FileAccount {
pub fn index(&self) -> IndexStyle {
self.index
}
+ pub fn html_filter(&self) -> Option<&str> {
+ self.html_filter.as_ref().map(|f| f.as_str())
+ }
}
#[derive(Debug, Clone, Default, Deserialize)]
@@ -218,3 +233,41 @@ where
_ => Err(de::Error::custom("invalid `index` value")),
}
}
+
+fn non_empty_string<'de, D>(deserializer: D) -> std::result::Result<Option<String>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ let s = <String>::deserialize(deserializer)?;
+ if s.is_empty() {
+ Ok(None)
+ } else {
+ Ok(Some(s))
+ }
+}
+
+/*
+ * Deserialize default functions
+ */
+
+mod default_vals {
+ pub(in conf) fn false_val() -> bool {
+ true
+ }
+
+ pub(in conf) fn true_val() -> bool {
+ true
+ }
+
+ pub(in conf) fn zero_val() -> usize {
+ 0
+ }
+
+ pub(in conf) fn eighty_percent() -> usize {
+ 80
+ }
+
+ pub(in conf) fn none() -> Option<String> {
+ None
+ }
+}
diff --git a/ui/src/conf/pager.rs b/ui/src/conf/pager.rs
index 0575d98f..59eaa2fc 100644
--- a/ui/src/conf/pager.rs
+++ b/ui/src/conf/pager.rs
@@ -19,25 +19,7 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
-fn false_val() -> bool {
- true
-}
-
-fn true_val() -> bool {
- true
-}
-
-fn zero_val() -> usize {
- 0
-}
-fn eighty_percent() -> usize {
- 80
-}
-
-fn none() -> Option<String> {
- None
-}
-
+use super::default_vals::*;
/// Settings for the pager function.
#[derive(Debug, Deserialize, Clone, Default)]
pub struct PagerSettings {