Skip to content

Commit

Permalink
ui: add mailcap support
Browse files Browse the repository at this point in the history
  • Loading branch information
epilys committed Nov 11, 2019
1 parent 9cd00cf commit dce1c39
Show file tree
Hide file tree
Showing 5 changed files with 428 additions and 28 deletions.
77 changes: 63 additions & 14 deletions meli.1
Expand Up @@ -24,7 +24,7 @@
.Nm meli
.Nd Meli Mail User Agent. meli is the Greek word for honey.
.Sh SYNOPSIS
.Nm meli
.Nm
.Op Fl -help | h
.Op Fl -version | v
.Op Fl -create-config Op Ar path
Expand All @@ -45,7 +45,9 @@ if given, or at
Start meli with given configuration file.
.El
.Sh STARTING WITH meli
When launched for the first time, meli will search for its configuration directory,
When launched for the first time,
.Nm
will search for its configuration directory,
.Pa $XDG_CONFIG_HOME/meli/ Ns
\&. If it doesn't exist, you will be asked if you want to create one along with a sample configuration. The sample configuration
.Pa $XDG_CONFIG_HOME/meli/config
Expand Down Expand Up @@ -83,6 +85,21 @@ section of your configuration.
^^ (`-=-=-=-=-`)
`-=-=-=-=-` ^^
.Ed
.Sh VIEWING MAIL
Open attachments by typing their index in the attachments list and then
.Cm a Ns
\&.
.Ns
.Nm
will attempt to open text inside its pager and other content via
.Cm xdg-open Ns
\&. Press
.Cm m
instead to use the mailcap entry for the MIME type of the attachment, if any. See
.Sx FILES
for the location of the mailcap files and
.Xr mailcap 5
for their syntax.
.Sh COMPOSING
To send mail, press
.Cm m
Expand Down Expand Up @@ -112,15 +129,18 @@ will send your message by piping it into a binary of your choosing (see
.Cm close
and select 'save as draft'.
.Pp
If there is no Draft or Sent folder, meli tries first saving mail in your INBOX and then at any other folder. On complete failure to save your draft or sent message it will be saved in your
If there is no Draft or Sent folder,
.Nm
tries first saving mail in your INBOX and then at any other folder. On complete failure to save your draft or sent message it will be saved in your
.Em tmp
directory instead and you will be notified of its location.
.Pp
To open a draft for editing later, select your draft in the mail listing and press
.Cm e Ns
\&.
.Sh SEARCH
meli, if built with sqlite3, includes the ability to perform full text search on the following fields: From, To, Cc, Bcc, In-Reply-To, References, Subject and Date. The message body (in plain text human readable form) and the flags can also be queried. To create the sqlite3 index issue command
.Nm Ns
, if built with sqlite3, includes the ability to perform full text search on the following fields: From, To, Cc, Bcc, In-Reply-To, References, Subject and Date. The message body (in plain text human readable form) and the flags can also be queried. To create the sqlite3 index issue command
.Ic index Ar ACCOUNT_NAME Ns \&.

To search in the message body type your keywords without any special formatting.
Expand Down Expand Up @@ -150,7 +170,9 @@ To prevent downloading all your messages from your IMAP server, don't set
.Em cache_type
to
.Em sqlite3 Ns
\&. meli will relay your queries to the IMAP server. Expect a delay between query and response. Sqlite3 on the contrary at reasonable mailbox sizes should have a non noticable delay.
\&.
.Nm
will relay your queries to the IMAP server. Expect a delay between query and response. Sqlite3 on the contrary at reasonable mailbox sizes should have a non noticable delay.
.Sh EXECUTE mode
Commands are issued in EXECUTE mode, by default started with the space character and exited with Escape key.
.Pp
Expand Down Expand Up @@ -198,7 +220,7 @@ delete folder
.El
.Pp
envelope view commands:
.Bl -tag -width "rename-folder ACCOUNT FOLDER_PATH_SRC FOLDER_PATH_DEST" -offset indent
.Bl -tag -width "rename-folder ACCOUNT FOLDER_PATH_SRC FOLDER_PATH_DEST"
.It Cm pipe Ar EXECUTABLE Ar ARGS
pipe pager contents to binary
.It Cm list-post
Expand All @@ -211,7 +233,7 @@ open list archive with
.El
.Pp
composing mail commands:
.Bl -tag -width "rename-folder ACCOUNT FOLDER_PATH_SRC FOLDER_PATH_DEST" -offset indent
.Bl -tag -width "rename-folder ACCOUNT FOLDER_PATH_SRC FOLDER_PATH_DEST"
.It Ic add-attachment Ar PATH
in composer, add
.Ar PATH
Expand All @@ -223,7 +245,7 @@ toggle between signing and not signing this message. If the gpg invocation fails
.El
.Pp
generic commands:
.Bl -tag -width "rename-folder ACCOUNT FOLDER_PATH_SRC FOLDER_PATH_DEST" -offset indent
.Bl -tag -width "rename-folder ACCOUNT FOLDER_PATH_SRC FOLDER_PATH_DEST"
.It Cm open-in-tab
opens envelope view in new tab
.It Ic close
Expand All @@ -239,7 +261,7 @@ print environment variable
.El
.Sh SHORTCUTS
Non-complete list of shortcuts and their default values.
.Bl -tag -width "rename-folder ACCOUNT FOLDER_PATH_SRC FOLDER_PATH_DEST" -offset indent
.Bl -tag -width "rename-folder ACCOUNT FOLDER_PATH_SRC FOLDER_PATH_DEST"
.It Cm open_thread
\&'\\n'
.It Cm exit_thread
Expand Down Expand Up @@ -275,7 +297,7 @@ PageDown
.It Cm select
\&'v'
.El
.Bl -tag -width "rename-folder ACCOUNT FOLDER_PATH_SRC FOLDER_PATH_DEST" -offset indent
.Bl -tag -width "rename-folder ACCOUNT FOLDER_PATH_SRC FOLDER_PATH_DEST"
.It Cm `
toggles hiding of sidebar in mail listings
.It Cm \&?
Expand All @@ -296,6 +318,11 @@ opens the
.Ar n Ns
th
attachment.
.It Ar n Ns Cm m
opens the
.Ar n Ns
th
attachment according to its mailcap entry.
.It Cm v
(un)selects mail entries in mail listings
.El
Expand All @@ -314,7 +341,8 @@ Specifies the editor to use
Override the configuration file
.El
.Sh FILES
meli uses the following parts of the XDG standard:
.Nm
uses the following parts of the XDG standard:
.Bl -tag -width "$XDG_CONFIG_HOME/meli/plugins/*" -offset indent
.It Ev XDG_CONFIG_HOME
defaults to
Expand Down Expand Up @@ -343,16 +371,37 @@ Internal data used by meli.
.It Pa $XDG_DATA_HOME/meli/meli.log
Operation log.
.It Pa /tmp/meli/*
Temporary files generated by meli.
Temporary files generated by
.Nm Ns
\&.
.El
.Pp
Mailcap entries are searched for in the following files, in this order:
.Pp
.Bl -enum -compact -offset indent
.It
.Pa $XDG_CONFIG_HOME/meli/mailcap
.It
.Pa $XDG_CONFIG_HOME/.mailcap
.It
.Pa $HOME/.mailcap
.It
.Pa /etc/mailcap
.It
.Pa /usr/etc/mailcap
.It
.Pa /usr/local/etc/mailcap
.El
.Sh SEE ALSO
.Xr meli.conf 5 ,
.Xr xdg-open 1 ,
.Xr meli.conf 5
.Xr mailcap 5
.Sh CONFORMING TO
XDG Standard
.Aq https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html Ns
, maildir
.Aq https://cr.yp.to/proto/maildir.html
.Aq https://cr.yp.to/proto/maildir.html Ns
, IMAPv4rev1 RFC3501.
.Sh AUTHORS
Copyright 2017-2019
.An Manos Pitsidianakis Aq epilys@nessuent.xyz
Expand Down
21 changes: 21 additions & 0 deletions melib/src/email/attachments.rs
Expand Up @@ -642,6 +642,27 @@ impl Attachment {
into_raw_helper(self, &mut ret);
ret
}

pub fn parameters(&self) -> Vec<(&[u8], &[u8])> {
let mut ret = Vec::new();
let (headers, _) = match parser::attachment(&self.raw).to_full_result() {
Ok(v) => v,
Err(_) => return ret,
};
for (name, value) in headers {
if name.eq_ignore_ascii_case(b"content-type") {
match parser::content_type(value).to_full_result() {
Ok((_, _, params)) => {
ret = params;
}
_ => {}
}
break;
}
}

ret
}
}

pub fn interpret_format_flowed(_t: &str) -> String {
Expand Down
107 changes: 93 additions & 14 deletions ui/src/components/mail/view.rs
Expand Up @@ -701,8 +701,12 @@ impl Component for MailView {
}
}

let shortcuts = &self.get_shortcuts(context)[MailView::DESCRIPTION];
match *event {
UIEvent::Input(Key::Char('c')) if !self.mode.is_contact_selector() => {
UIEvent::Input(ref key)
if !self.mode.is_contact_selector()
&& *key == shortcuts["add_addresses_to_contacts"] =>
{
let account = &mut context.accounts[self.coordinates.0];
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);

Expand Down Expand Up @@ -746,25 +750,87 @@ impl Component for MailView {
)));
return true;
}
UIEvent::Input(Key::Alt('r'))
if self.mode == ViewMode::Normal || self.mode == ViewMode::Subview =>
UIEvent::Input(ref key)
if (self.mode == ViewMode::Normal || self.mode == ViewMode::Subview)
&& *key == shortcuts["view_raw_source"] =>
{
self.mode = ViewMode::Raw;
self.set_dirty();
return true;
}
UIEvent::Input(Key::Char('r'))
if self.mode.is_attachment()
UIEvent::Input(ref key)
if (self.mode.is_attachment()
|| self.mode == ViewMode::Subview
|| self.mode == ViewMode::Url
|| self.mode == ViewMode::Raw =>
|| self.mode == ViewMode::Raw)
&& *key == shortcuts["return_to_normal_view"] =>
{
self.mode = ViewMode::Normal;
self.set_dirty();
return true;
}
UIEvent::Input(Key::Char('a'))
if !self.cmd_buf.is_empty()
UIEvent::Input(ref key)
if (self.mode == ViewMode::Normal || self.mode == ViewMode::Subview)
&& !self.cmd_buf.is_empty()
&& *key == shortcuts["open_mailcap"] =>
{
let lidx = self.cmd_buf.parse::<usize>().unwrap();
self.cmd_buf.clear();
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));

{
let account = &mut context.accounts[self.coordinates.0];
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
let op = account.operation(envelope.hash());

let attachments = match envelope.body(op) {
Ok(body) => body.attachments(),
Err(e) => {
context.replies.push_back(UIEvent::Notification(
Some("Failed to open e-mail".to_string()),
e.to_string(),
Some(NotificationType::ERROR),
));
log(
format!(
"Failed to open envelope {}: {}",
envelope.message_id_display(),
e.to_string()
),
ERROR,
);
return true;
}
};
drop(envelope);
drop(account);
if let Some(u) = attachments.get(lidx) {
if let Ok(()) = crate::mailcap::MailcapEntry::execute(u, context) {
self.set_dirty();
} else {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
"no mailcap entry found for {}",
u.content_type()
)),
));
}
} else {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
"Attachment `{}` not found.",
lidx
)),
));
}
return true;
}
}
UIEvent::Input(ref key)
if *key == shortcuts["open_attachment"]
&& !self.cmd_buf.is_empty()
&& (self.mode == ViewMode::Normal || self.mode == ViewMode::Subview) =>
{
let lidx = self.cmd_buf.parse::<usize>().unwrap();
Expand Down Expand Up @@ -895,13 +961,15 @@ impl Component for MailView {
}
};
}
UIEvent::Input(Key::Char('h')) => {
UIEvent::Input(ref key) if *key == shortcuts["toggle_expand_headers"] => {
self.expand_headers = !self.expand_headers;
self.dirty = true;
return true;
}
UIEvent::Input(Key::Char('g'))
if !self.cmd_buf.is_empty() && self.mode == ViewMode::Url =>
UIEvent::Input(ref key)
if !self.cmd_buf.is_empty()
&& self.mode == ViewMode::Url
&& *key == shortcuts["go_to_url"] =>
{
let lidx = self.cmd_buf.parse::<usize>().unwrap();
self.cmd_buf.clear();
Expand Down Expand Up @@ -943,15 +1011,24 @@ impl Component for MailView {
}
};

Command::new("xdg-open")
if let Err(e) = Command::new("xdg-open")
.arg(url)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Failed to start xdg_open");
{
context.replies.push_back(UIEvent::Notification(
Some("Failed to launch xdg-open".to_string()),
e.to_string(),
Some(NotificationType::ERROR),
));
}
return true;
}
UIEvent::Input(Key::Char('u')) => {
UIEvent::Input(ref key)
if (self.mode == ViewMode::Normal || self.mode == ViewMode::Url)
&& *key == shortcuts["toggle_url_mode"] =>
{
match self.mode {
ViewMode::Normal => self.mode = ViewMode::Url,
ViewMode::Url => self.mode = ViewMode::Normal,
Expand Down Expand Up @@ -1220,12 +1297,14 @@ impl Component for MailView {
our_map.insert("return_to_normal_view", Key::Char('r'));
}
our_map.insert("open_attachment", Key::Char('a'));
our_map.insert("open_mailcap", Key::Char('m'));
if self.mode == ViewMode::Url {
our_map.insert("go_to_url", Key::Char('g'));
}
if self.mode == ViewMode::Normal || self.mode == ViewMode::Url {
our_map.insert("toggle_url_mode", Key::Char('u'));
}
our_map.insert("toggle_expand_headers", Key::Char('h'));
map.insert(MailView::DESCRIPTION.to_string(), our_map);

map
Expand Down
2 changes: 2 additions & 0 deletions ui/src/lib.rs
Expand Up @@ -74,6 +74,8 @@ pub mod sqlite3;

pub mod cache;

pub mod mailcap;

pub use crate::username::*;
pub mod username {
use libc;
Expand Down

0 comments on commit dce1c39

Please sign in to comment.