parent
92f42ad8fa
commit
cfb7dcec64
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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..)
|
||||
|
|
|
@ -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 }
|
||||
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 }
|
||||
} 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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue