compose: add `add-attachment-file-picker` command
parent
a4b78532b7
commit
406af1848f
|
@ -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
|
||||
|
||||
|
|
10
docs/meli.1
10
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -79,6 +79,7 @@ pub enum ViewAction {
|
|||
#[derive(Debug)]
|
||||
pub enum ComposeAction {
|
||||
AddAttachment(String),
|
||||
AddAttachmentFilePicker(Option<String>),
|
||||
AddAttachmentPipe(String),
|
||||
RemoveAttachment(usize),
|
||||
SaveDraft,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue