summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2020-10-09 21:21:15 +0300
committerManos Pitsidianakis <el13635@mail.ntua.gr>2020-10-11 16:53:04 +0300
commit406af1848f01c2e5fe4ab3748ce934199190b827 (patch)
tree6d4cb7185342d69fa6b77bf31c96026791c8ca22
parenta4b78532b7d2347f9df7e87163c12880cb46cdd9 (diff)
compose: add `add-attachment-file-picker` command
-rw-r--r--CHANGELOG.md2
-rw-r--r--docs/meli.110
-rw-r--r--docs/meli.conf.58
-rw-r--r--src/command.rs19
-rw-r--r--src/command/actions.rs1
-rw-r--r--src/components/mail/compose.rs96
-rw-r--r--src/conf/terminal.rs4
7 files changed, 128 insertions, 12 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 441a39e1..8142effd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Add import command to import email from files into accounts
+- Add add-attachment-file-picker command and `file_picker_command` setting to
+ use external commands to choose files when composing new mail
## [alpha-0.6.2] - 2020-09-24
diff --git a/docs/meli.1 b/docs/meli.1
index 4eefb695..d0789022 100644
--- a/docs/meli.1
+++ b/docs/meli.1
@@ -451,6 +451,16 @@ as an attachment
in composer, pipe
.Ar CMD Ar ARGS
output into an attachment
+.It Cm add-attachment-file-picker
+Launch command defined in the configuration value
+.Ic file_picker_command
+in
+.Xr meli.conf 5 TERMINAL
+.It Cm add-attachment-file-picker < Ar CMD Ar ARGS
+Launch command
+.Ar CMD Ar ARGS Ns
+\&.
+The command should print file paths in stderr, separated by NULL bytes.
.It Cm remove-attachment Ar INDEX
remove attachment with given index
.It Cm toggle sign
diff --git a/docs/meli.conf.5 b/docs/meli.conf.5
index a183251e..174bec26 100644
--- a/docs/meli.conf.5
+++ b/docs/meli.conf.5
@@ -920,6 +920,14 @@ If false, no ANSI colors are used.
Set window title in xterm compatible terminals An empty string means no window title is set.
.\" default value
.Pq Em "meli"
+.It Ic file_picker_command Ar String
+.Pq Em optional
+Set command that prints file paths in stderr, separated by NULL bytes.
+Used with
+.Ic add-attachment-file-picker
+when composing new mail.
+.\" default value
+.Pq Em None
.It Ic themes Ar hash table String[String[Attribute]]
Define UI themes.
See
diff --git a/src/command.rs b/src/command.rs
index 370ff833..6c6f56b4 100644
--- a/src/command.rs
+++ b/src/command.rs
@@ -57,7 +57,7 @@ macro_rules! to_stream {
};
($($tokens:expr),*) => {
TokenStream {
- tokens: &[$($token),*],
+ tokens: &[$($tokens),*],
}
};
}
@@ -495,9 +495,10 @@ define_commands!([
}
)
},
- { tags: ["add-attachment "],
+ { tags: ["add-attachment ", "add-attachment-file-picker "],
desc: "add-attachment PATH",
- tokens: &[One(Literal("add-attachment")), One(Filepath)],
+ tokens: &[One(
+Alternatives(&[to_stream!(One(Literal("add-attachment")), One(Filepath)), to_stream!(One(Literal("add-attachment-file-picker")))]))],
parser:(
fn add_attachment<'a>(input: &'a [u8]) -> IResult<&'a [u8], Action> {
alt((
@@ -515,6 +516,18 @@ define_commands!([
let (input, path) = quoted_argument(input)?;
let (input, _) = eof(input)?;
Ok((input, Compose(AddAttachment(path.to_string()))))
+ }, |input: &'a [u8]| -> IResult<&'a [u8], Action> {
+ let (input, _) = tag("add-attachment-file-picker")(input.trim())?;
+ let (input, _) = eof(input)?;
+ Ok((input, Compose(AddAttachmentFilePicker(None))))
+ }, |input: &'a [u8]| -> IResult<&'a [u8], Action> {
+ let (input, _) = tag("add-attachment-file-picker")(input.trim())?;
+ let (input, _) = is_a(" ")(input)?;
+ let (input, _) = tag("<")(input.trim())?;
+ let (input, _) = is_a(" ")(input)?;
+ let (input, shell) = map_res(not_line_ending, std::str::from_utf8)(input)?;
+ let (input, _) = eof(input)?;
+ Ok((input, Compose(AddAttachmentFilePicker(Some(shell.to_string())))))
}
))(input)
}
diff --git a/src/command/actions.rs b/src/command/actions.rs
index d4144599..0dd97e68 100644
--- a/src/command/actions.rs
+++ b/src/command/actions.rs
@@ -79,6 +79,7 @@ pub enum ViewAction {
#[derive(Debug)]
pub enum ComposeAction {
AddAttachment(String),
+ AddAttachmentFilePicker(Option<String>),
AddAttachmentPipe(String),
RemoveAttachment(usize),
SaveDraft,
diff --git a/src/components/mail/compose.rs b/src/components/mail/compose.rs
index 8fb0e4a5..98d844d3 100644
--- a/src/components/mail/compose.rs
+++ b/src/components/mail/compose.rs
@@ -31,6 +31,7 @@ use indexmap::IndexSet;
use nix::sys::wait::WaitStatus;
use std::convert::TryInto;
use std::future::Future;
+use std::process::{Command, Stdio};
use std::str::FromStr;
use std::sync::{Arc, Mutex};
@@ -1262,7 +1263,6 @@ impl Component for Composer {
self.mode = ViewMode::Embed;
return true;
}
- use std::process::{Command, Stdio};
/* Kill input thread so that spawned command can be sole receiver of stdin */
{
context.input_kill();
@@ -1334,17 +1334,22 @@ impl Component for Composer {
return false;
}
let f = create_temp_file(&[], None, None, true);
- match std::process::Command::new("sh")
+ match Command::new("sh")
.args(&["-c", command])
- .stdin(std::process::Stdio::null())
- .stdout(std::process::Stdio::from(f.file()))
+ .stdin(Stdio::null())
+ .stdout(Stdio::from(f.file()))
.spawn()
+ .and_then(|child| Ok(child.wait_with_output()?.stderr))
{
- Ok(child) => {
- let _ = child
- .wait_with_output()
- .expect("failed to launch command")
- .stdout;
+ Ok(stderr) => {
+ if !stderr.is_empty() {
+ context.replies.push_back(UIEvent::StatusEvent(
+ StatusEvent::DisplayMessage(format!(
+ "Command stderr output: `{}`.",
+ String::from_utf8_lossy(&stderr)
+ )),
+ ));
+ }
let attachment =
match melib::email::compose::attachment_from_file(f.path()) {
Ok(a) => a,
@@ -1361,6 +1366,7 @@ impl Component for Composer {
}
};
self.draft.attachments_mut().push(attachment);
+ self.has_changes = true;
self.dirty = true;
return true;
}
@@ -1388,6 +1394,78 @@ impl Component for Composer {
}
};
self.draft.attachments_mut().push(attachment);
+ self.has_changes = true;
+ self.dirty = true;
+ return true;
+ }
+ Action::Compose(ComposeAction::AddAttachmentFilePicker(ref command)) => {
+ let command = if let Some(ref cmd) = command
+ .as_ref()
+ .or_else(|| context.settings.terminal.file_picker_command.as_ref())
+ {
+ cmd.as_str()
+ } else {
+ context.replies.push_back(UIEvent::Notification(
+ None,
+ "You haven't defined any command to launch.".into(),
+ Some(NotificationType::Error(melib::error::ErrorKind::None)),
+ ));
+ return true;
+ };
+ /* Kill input thread so that spawned command can be sole receiver of stdin */
+ {
+ context.input_kill();
+ }
+
+ log(
+ format!("Executing: sh -c \"{}\"", command.replace("\"", "\\\"")),
+ DEBUG,
+ );
+ match Command::new("sh")
+ .args(&["-c", command])
+ .stdin(Stdio::inherit())
+ .stdout(Stdio::inherit())
+ .stderr(Stdio::piped())
+ .spawn()
+ .and_then(|child| Ok(child.wait_with_output()?.stderr))
+ {
+ Ok(stderr) => {
+ debug!(&String::from_utf8_lossy(&stderr));
+ for path in stderr.split(|c| [b'\0', b'\t', b'\n'].contains(c)) {
+ match melib::email::compose::attachment_from_file(
+ &String::from_utf8_lossy(&path).as_ref(),
+ ) {
+ Ok(a) => {
+ self.draft.attachments_mut().push(a);
+ self.has_changes = true;
+ }
+ Err(err) => {
+ context.replies.push_back(UIEvent::Notification(
+ Some(format!(
+ "could not add attachment: {}",
+ String::from_utf8_lossy(&path)
+ )),
+ err.to_string(),
+ Some(NotificationType::Error(
+ melib::error::ErrorKind::None,
+ )),
+ ));
+ }
+ };
+ }
+ }
+ Err(err) => {
+ let command = command.to_string();
+ context.replies.push_back(UIEvent::Notification(
+ Some(format!("Failed to execute {}: {}", command, err)),
+ err.to_string(),
+ Some(NotificationType::Error(melib::error::ErrorKind::External)),
+ ));
+ context.restore_input();
+ return true;
+ }
+ }
+ context.replies.push_back(UIEvent::Fork(ForkType::Finished));
self.dirty = true;
return true;
}
diff --git a/src/conf/terminal.rs b/src/conf/terminal.rs
index f872e105..2b6c94e5 100644
--- a/src/conf/terminal.rs
+++ b/src/conf/terminal.rs
@@ -37,6 +37,8 @@ pub struct TerminalSettings {
pub use_color: ToggleFlag,
#[serde(deserialize_with = "non_empty_string")]
pub window_title: Option<String>,
+ #[serde(deserialize_with = "non_empty_string")]
+ pub file_picker_command: Option<String>,
}
impl Default for TerminalSettings {
@@ -47,6 +49,7 @@ impl Default for TerminalSettings {
ascii_drawing: false,
use_color: ToggleFlag::InternalVal(true),
window_title: Some("meli".to_string()),
+ file_picker_command: None,
}
}
}
@@ -74,6 +77,7 @@ impl DotAddressable for TerminalSettings {
"ascii_drawing" => self.ascii_drawing.lookup(field, tail),
"use_color" => self.use_color.lookup(field, tail),
"window_title" => self.window_title.lookup(field, tail),
+ "file_picker_command" => self.file_picker_command.lookup(field, tail),
other => Err(MeliError::new(format!(
"{} has no field named {}",
parent_field, other