From e33a9a72e9ef9ce688407e7284fc60330bd88621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Sat, 5 Feb 2022 00:29:57 +0100 Subject: [PATCH] release v0.5.4 (#285) * replace bsd3 license by bsd4 * add attachments with save and send commands (#284) * set up tpl save and send commands * improve msg save and send handlers * add vim msg#add_attachment fn * improve vim logs * update changelog * add attachment keybind vim doc * reverse range order fetch envelopes (#276) * bump version v0.5.4 --- CHANGELOG.md | 13 +++- Cargo.lock | 2 +- Cargo.toml | 2 +- LICENSE | 44 ++++++------ src/domain/imap/imap_service.rs | 4 +- src/domain/msg/msg_arg.rs | 10 +-- src/domain/msg/msg_handler.rs | 43 +++++++---- src/domain/msg/tpl_arg.rs | 108 +++++++++++++++++----------- src/domain/msg/tpl_handler.rs | 63 ++++++++++++++++ src/main.rs | 6 ++ vim/README.md | 10 +++ vim/autoload/himalaya/msg.vim | 25 +++++-- vim/ftplugin/himalaya-msg-write.vim | 4 ++ 13 files changed, 243 insertions(+), 91 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5126c06..37bc20d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.5.4] - 2022-02-05 + +### Fixed + +- Add attachments with save and send commands [#47] [#259] +- Invalid sequence set [#276] + ## [0.5.3] - 2022-02-03 ### Added @@ -273,7 +280,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Password from command [#22] - Set up README [#20] -[unreleased]: https://github.com/soywod/himalaya/compare/v0.5.3...HEAD +[unreleased]: https://github.com/soywod/himalaya/compare/v0.5.4...HEAD +[0.5.4]: https://github.com/soywod/himalaya/compare/v0.5.3...v0.5.4 [0.5.3]: https://github.com/soywod/himalaya/compare/v0.5.2...v0.5.3 [0.5.2]: https://github.com/soywod/himalaya/compare/v0.5.1...v0.5.2 [0.5.1]: https://github.com/soywod/himalaya/compare/v0.5.0...v0.5.1 @@ -325,6 +333,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#39]: https://github.com/soywod/himalaya/issues/39 [#40]: https://github.com/soywod/himalaya/issues/40 [#41]: https://github.com/soywod/himalaya/issues/41 +[#47]: https://github.com/soywod/himalaya/issues/47 [#48]: https://github.com/soywod/himalaya/issues/48 [#50]: https://github.com/soywod/himalaya/issues/50 [#58]: https://github.com/soywod/himalaya/issues/58 @@ -383,9 +392,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#228]: https://github.com/soywod/himalaya/issues/228 [#229]: https://github.com/soywod/himalaya/issues/229 [#249]: https://github.com/soywod/himalaya/issues/249 +[#259]: https://github.com/soywod/himalaya/issues/259 [#268]: https://github.com/soywod/himalaya/issues/268 [#272]: https://github.com/soywod/himalaya/issues/272 [#273]: https://github.com/soywod/himalaya/issues/273 [#276]: https://github.com/soywod/himalaya/issues/276 [#271]: https://github.com/soywod/himalaya/issues/271 +[#276]: https://github.com/soywod/himalaya/issues/276 [#280]: https://github.com/soywod/himalaya/issues/280 diff --git a/Cargo.lock b/Cargo.lock index 0999ac6..89d3fe9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -361,7 +361,7 @@ dependencies = [ [[package]] name = "himalaya" -version = "0.5.3" +version = "0.5.4" dependencies = [ "ammonia", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index a942c80..00d25e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "himalaya" description = "Command-line interface for email management" -version = "0.5.3" +version = "0.5.4" authors = ["soywod "] edition = "2018" license-file = "LICENSE" diff --git a/LICENSE b/LICENSE index 8cc414e..e32e403 100644 --- a/LICENSE +++ b/LICENSE @@ -1,30 +1,32 @@ -Copyright © 2020,2021 soywod +Copyright (c) 2020-2021, soywod (Clément DOUIN) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. - * Neither the name of Author name here nor the names of other - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. +3. All advertising materials mentioning features or use of this software must + display the following acknowledgement: + This product includes software developed by Clément DOUIN. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +4. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/domain/imap/imap_service.rs b/src/domain/imap/imap_service.rs index ba38d7e..7b5cfea 100644 --- a/src/domain/imap/imap_service.rs +++ b/src/domain/imap/imap_service.rs @@ -144,11 +144,11 @@ impl<'a> ImapServiceInterface<'a> for ImapService<'a> { let cursor = (page * page_size) as i64; let begin = 1.max(last_seq - cursor); let end = begin - begin.min(*page_size as i64) + 1; - format!("{}:{}", begin, end) + format!("{}:{}", end, begin) } else { String::from("1:*") }; - debug!("range: {:?}", range); + debug!("range: {}", range); let fetches = self .sess()? diff --git a/src/domain/msg/msg_arg.rs b/src/domain/msg/msg_arg.rs index 4f91eac..a708086 100644 --- a/src/domain/msg/msg_arg.rs +++ b/src/domain/msg/msg_arg.rs @@ -23,7 +23,7 @@ type Raw = bool; type All = bool; type RawMsg<'a> = &'a str; type Query = String; -type AttachmentsPaths<'a> = Vec<&'a str>; +type AttachmentPaths<'a> = Vec<&'a str>; type MaxTableWidth = Option; /// Message commands. @@ -31,15 +31,15 @@ pub enum Command<'a> { Attachments(Seq<'a>), Copy(Seq<'a>, Mbox<'a>), Delete(Seq<'a>), - Forward(Seq<'a>, AttachmentsPaths<'a>), + Forward(Seq<'a>, AttachmentPaths<'a>), List(MaxTableWidth, Option, Page), Move(Seq<'a>, Mbox<'a>), Read(Seq<'a>, TextMime<'a>, Raw), - Reply(Seq<'a>, All, AttachmentsPaths<'a>), + Reply(Seq<'a>, All, AttachmentPaths<'a>), Save(RawMsg<'a>), Search(Query, MaxTableWidth, Option, Page), Send(RawMsg<'a>), - Write(AttachmentsPaths<'a>), + Write(AttachmentPaths<'a>), Flag(Option>), Tpl(Option>), @@ -256,7 +256,7 @@ fn page_arg<'a>() -> Arg<'a, 'a> { } /// Message attachment argument. -fn attachment_arg<'a>() -> Arg<'a, 'a> { +pub fn attachment_arg<'a>() -> Arg<'a, 'a> { Arg::with_name("attachments") .help("Adds attachment to the message") .short("a") diff --git a/src/domain/msg/msg_handler.rs b/src/domain/msg/msg_handler.rs index c93a5e4..b1b7fd4 100644 --- a/src/domain/msg/msg_handler.rs +++ b/src/domain/msg/msg_handler.rs @@ -5,7 +5,7 @@ use anyhow::{Context, Result}; use atty::Stream; use imap::types::Flag; -use log::{debug, trace}; +use log::{debug, info, trace}; use std::{ borrow::Cow, convert::{TryFrom, TryInto}, @@ -244,14 +244,25 @@ pub fn reply< imap.add_flags(seq, &flags) } -/// Save a raw message to the targetted mailbox. +/// Saves a raw message to the targetted mailbox. pub fn save<'a, Printer: PrinterService, ImapService: ImapServiceInterface<'a>>( mbox: &Mbox, raw_msg: &str, printer: &mut Printer, imap: &mut ImapService, ) -> Result<()> { - let raw_msg = if atty::is(Stream::Stdin) || printer.is_json() { + info!("entering save message handler"); + + debug!("mailbox: {}", mbox); + let flags = Flags::try_from(vec![Flag::Seen])?; + debug!("flags: {}", flags); + + let is_tty = atty::is(Stream::Stdin); + debug!("is tty: {}", is_tty); + let is_json = printer.is_json(); + debug!("is json: {}", is_json); + + let raw_msg = if is_tty || is_json { raw_msg.replace("\r", "").replace("\n", "\r\n") } else { io::stdin() @@ -261,8 +272,6 @@ pub fn save<'a, Printer: PrinterService, ImapService: ImapServiceInterface<'a>>( .collect::>() .join("\r\n") }; - - let flags = Flags::try_from(vec![Flag::Seen])?; imap.append_raw_msg_with_flags(mbox, raw_msg.as_bytes(), flags) } @@ -297,7 +306,19 @@ pub fn send< imap: &mut ImapService, smtp: &mut SmtpService, ) -> Result<()> { - let raw_msg = if atty::is(Stream::Stdin) || printer.is_json() { + info!("entering send message handler"); + + let mbox = Mbox::new(&account.sent_folder); + debug!("mailbox: {}", mbox); + let flags = Flags::try_from(vec![Flag::Seen])?; + debug!("flags: {}", flags); + + let is_tty = atty::is(Stream::Stdin); + debug!("is tty: {}", is_tty); + let is_json = printer.is_json(); + debug!("is json: {}", is_json); + + let raw_msg = if is_tty || is_json { raw_msg.replace("\r", "").replace("\n", "\r\n") } else { io::stdin() @@ -307,15 +328,11 @@ pub fn send< .collect::>() .join("\r\n") }; + trace!("raw message: {:?}", raw_msg); + let envelope: lettre::address::Envelope = Msg::from_tpl(&raw_msg)?.try_into()?; + trace!("envelope: {:?}", envelope); - let msg = Msg::from_tpl(&raw_msg)?; - let envelope: lettre::address::Envelope = msg.try_into()?; smtp.send_raw_msg(&envelope, raw_msg.as_bytes())?; - debug!("message sent!"); - - // Save message to sent folder - let mbox = Mbox::new(&account.sent_folder); - let flags = Flags::try_from(vec![Flag::Seen])?; imap.append_raw_msg_with_flags(&mbox, raw_msg.as_bytes(), flags) } diff --git a/src/domain/msg/tpl_arg.rs b/src/domain/msg/tpl_arg.rs index 432ce19..ccc4e86 100644 --- a/src/domain/msg/tpl_arg.rs +++ b/src/domain/msg/tpl_arg.rs @@ -4,12 +4,14 @@ use anyhow::Result; use clap::{self, App, AppSettings, Arg, ArgMatches, SubCommand}; -use log::{debug, trace}; +use log::{debug, info, trace}; use crate::domain::msg::msg_arg; type Seq<'a> = &'a str; -type All = bool; +type ReplyAll = bool; +type AttachmentPaths<'a> = Vec<&'a str>; +type Tpl<'a> = &'a str; #[derive(Debug, Default)] pub struct TplOverride<'a> { @@ -23,69 +25,77 @@ pub struct TplOverride<'a> { pub sig: Option<&'a str>, } +impl<'a> From<&'a ArgMatches<'a>> for TplOverride<'a> { + fn from(matches: &'a ArgMatches<'a>) -> Self { + Self { + subject: matches.value_of("subject"), + from: matches.values_of("from").map(|v| v.collect()), + to: matches.values_of("to").map(|v| v.collect()), + cc: matches.values_of("cc").map(|v| v.collect()), + bcc: matches.values_of("bcc").map(|v| v.collect()), + headers: matches.values_of("headers").map(|v| v.collect()), + body: matches.value_of("body"), + sig: matches.value_of("signature"), + } + } +} + /// Message template commands. pub enum Command<'a> { New(TplOverride<'a>), - Reply(Seq<'a>, All, TplOverride<'a>), + Reply(Seq<'a>, ReplyAll, TplOverride<'a>), Forward(Seq<'a>, TplOverride<'a>), + Save(AttachmentPaths<'a>, Tpl<'a>), + Send(AttachmentPaths<'a>, Tpl<'a>), } /// Message template command matcher. pub fn matches<'a>(m: &'a ArgMatches) -> Result>> { if let Some(m) = m.subcommand_matches("new") { - debug!("new command matched"); - let tpl = TplOverride { - subject: m.value_of("subject"), - from: m.values_of("from").map(|v| v.collect()), - to: m.values_of("to").map(|v| v.collect()), - cc: m.values_of("cc").map(|v| v.collect()), - bcc: m.values_of("bcc").map(|v| v.collect()), - headers: m.values_of("headers").map(|v| v.collect()), - body: m.value_of("body"), - sig: m.value_of("signature"), - }; - trace!(r#"template args: "{:?}""#, tpl); + info!("new command matched"); + let tpl = TplOverride::from(m); + trace!("template override: {:?}", tpl); return Ok(Some(Command::New(tpl))); } if let Some(m) = m.subcommand_matches("reply") { - debug!("reply command matched"); + info!("reply command matched"); let seq = m.value_of("seq").unwrap(); - trace!(r#"seq: "{}""#, seq); + debug!("sequence: {}", seq); let all = m.is_present("reply-all"); - trace!("reply all: {}", all); - let tpl = TplOverride { - subject: m.value_of("subject"), - from: m.values_of("from").map(|v| v.collect()), - to: m.values_of("to").map(|v| v.collect()), - cc: m.values_of("cc").map(|v| v.collect()), - bcc: m.values_of("bcc").map(|v| v.collect()), - headers: m.values_of("headers").map(|v| v.collect()), - body: m.value_of("body"), - sig: m.value_of("signature"), - }; - trace!(r#"template args: "{:?}""#, tpl); + debug!("reply all: {}", all); + let tpl = TplOverride::from(m); + trace!("template override: {:?}", tpl); return Ok(Some(Command::Reply(seq, all, tpl))); } if let Some(m) = m.subcommand_matches("forward") { - debug!("forward command matched"); + info!("forward command matched"); let seq = m.value_of("seq").unwrap(); - trace!(r#"seq: "{}""#, seq); - let tpl = TplOverride { - subject: m.value_of("subject"), - from: m.values_of("from").map(|v| v.collect()), - to: m.values_of("to").map(|v| v.collect()), - cc: m.values_of("cc").map(|v| v.collect()), - bcc: m.values_of("bcc").map(|v| v.collect()), - headers: m.values_of("headers").map(|v| v.collect()), - body: m.value_of("body"), - sig: m.value_of("signature"), - }; - trace!(r#"template args: "{:?}""#, tpl); + debug!("sequence: {}", seq); + let tpl = TplOverride::from(m); + trace!("template args: {:?}", tpl); return Ok(Some(Command::Forward(seq, tpl))); } + if let Some(m) = m.subcommand_matches("save") { + info!("save command matched"); + let attachment_paths: Vec<&str> = m.values_of("attachments").unwrap_or_default().collect(); + trace!("attachments paths: {:?}", attachment_paths); + let tpl = m.value_of("template").unwrap_or_default(); + trace!("template: {}", tpl); + return Ok(Some(Command::Save(attachment_paths, tpl))); + } + + if let Some(m) = m.subcommand_matches("send") { + info!("send command matched"); + let attachment_paths: Vec<&str> = m.values_of("attachments").unwrap_or_default().collect(); + trace!("attachments paths: {:?}", attachment_paths); + let tpl = m.value_of("template").unwrap_or_default(); + trace!("template: {}", tpl); + return Ok(Some(Command::Send(attachment_paths, tpl))); + } + Ok(None) } @@ -154,7 +164,7 @@ pub fn subcmds<'a>() -> Vec> { ) .subcommand( SubCommand::with_name("reply") - .aliases(&["rep", "r"]) + .aliases(&["rep", "re", "r"]) .about("Generates a reply message template") .arg(msg_arg::seq_arg()) .arg(msg_arg::reply_all_arg()) @@ -166,5 +176,17 @@ pub fn subcmds<'a>() -> Vec> { .about("Generates a forward message template") .arg(msg_arg::seq_arg()) .args(&tpl_args()), + ) + .subcommand( + SubCommand::with_name("save") + .about("Saves a message based on the given template") + .arg(&msg_arg::attachment_arg()) + .arg(Arg::with_name("template").raw(true)), + ) + .subcommand( + SubCommand::with_name("send") + .about("Sends a message based on the given template") + .arg(&msg_arg::attachment_arg()) + .arg(Arg::with_name("template").raw(true)), )] } diff --git a/src/domain/msg/tpl_handler.rs b/src/domain/msg/tpl_handler.rs index d700bbe..fbfe5e2 100644 --- a/src/domain/msg/tpl_handler.rs +++ b/src/domain/msg/tpl_handler.rs @@ -3,12 +3,19 @@ //! This module gathers all message template commands. use anyhow::Result; +use atty::Stream; +use imap::types::Flag; +use std::{ + convert::{TryFrom, TryInto}, + io::{self, BufRead}, +}; use crate::{ config::Account, domain::{ imap::ImapServiceInterface, msg::{Msg, TplOverride}, + Flags, Mbox, SmtpServiceInterface, }, output::PrinterService, }; @@ -53,3 +60,59 @@ pub fn forward<'a, Printer: PrinterService, ImapService: ImapServiceInterface<'a .to_tpl(opts, account); printer.print(tpl) } + +/// Saves a message based on a template. +pub fn save<'a, Printer: PrinterService, ImapService: ImapServiceInterface<'a>>( + mbox: &Mbox, + attachments_paths: Vec<&str>, + tpl: &str, + printer: &mut Printer, + imap: &mut ImapService, +) -> Result<()> { + let tpl = if atty::is(Stream::Stdin) || printer.is_json() { + tpl.replace("\r", "") + } else { + io::stdin() + .lock() + .lines() + .filter_map(Result::ok) + .collect::>() + .join("\n") + }; + let msg = Msg::from_tpl(&tpl)?.add_attachments(attachments_paths)?; + let raw_msg: Vec = TryInto::try_into(&msg)?; + let flags = Flags::try_from(vec![Flag::Seen])?; + imap.append_raw_msg_with_flags(mbox, &raw_msg, flags)?; + printer.print("Template successfully saved") +} + +/// Sends a message based on a template. +pub fn send< + 'a, + Printer: PrinterService, + ImapService: ImapServiceInterface<'a>, + SmtpService: SmtpServiceInterface, +>( + mbox: &Mbox, + attachments_paths: Vec<&str>, + tpl: &str, + printer: &mut Printer, + imap: &mut ImapService, + smtp: &mut SmtpService, +) -> Result<()> { + let tpl = if atty::is(Stream::Stdin) || printer.is_json() { + tpl.replace("\r", "") + } else { + io::stdin() + .lock() + .lines() + .filter_map(Result::ok) + .collect::>() + .join("\n") + }; + let msg = Msg::from_tpl(&tpl)?.add_attachments(attachments_paths)?; + let sent_msg = smtp.send_msg(&msg)?; + let flags = Flags::try_from(vec![Flag::Seen])?; + imap.append_raw_msg_with_flags(mbox, &sent_msg.formatted(), flags)?; + printer.print("Template successfully sent") +} diff --git a/src/main.rs b/src/main.rs index 78738a5..76863ee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -176,6 +176,12 @@ fn main() -> Result<()> { Some(tpl_arg::Command::Forward(seq, tpl)) => { return tpl_handler::forward(seq, tpl, &account, &mut printer, &mut imap); } + Some(tpl_arg::Command::Save(atts, tpl)) => { + return tpl_handler::save(&mbox, atts, tpl, &mut printer, &mut imap); + } + Some(tpl_arg::Command::Send(atts, tpl)) => { + return tpl_handler::send(&mbox, atts, tpl, &mut printer, &mut imap, &mut smtp); + } _ => (), }, _ => (), diff --git a/vim/README.md b/vim/README.md index 8dd2abe..00af70f 100644 --- a/vim/README.md +++ b/vim/README.md @@ -155,6 +155,16 @@ nmap gD (himalaya-msg-delete) ![gif](https://user-images.githubusercontent.com/10437171/110708795-84387900-81fb-11eb-8f8a-f7e7862e816d.gif) +| Function | Default binding | +| --- | --- | +| Add attachment | `ga` | + +They can be customized: + +```vim +nmap ga (himalaya-msg-add-attachment) +``` + When you exit this special buffer, you will be prompted 4 choices: - `Send`: sends the message diff --git a/vim/autoload/himalaya/msg.vim b/vim/autoload/himalaya/msg.vim index 32ba5bc..9384c9d 100644 --- a/vim/autoload/himalaya/msg.vim +++ b/vim/autoload/himalaya/msg.vim @@ -5,6 +5,7 @@ let s:plain_req = function("himalaya#request#plain") let s:msg_id = 0 let s:draft = "" +let s:attachment_paths = [] function! himalaya#msg#list_with(account, mbox, page, should_throw) let pos = getpos(".") @@ -254,6 +255,7 @@ endfunction function! himalaya#msg#draft_handle() try let account = himalaya#account#curr() + let attachments = join(map(s:attachment_paths, "'--attachment '.v:val"), " ") while 1 let choice = input("(s)end, (d)raft, (q)uit or (c)ancel? ") let choice = tolower(choice)[0] @@ -261,15 +263,15 @@ function! himalaya#msg#draft_handle() if choice == "s" return s:cli( - \"--account %s send -- %s", - \[shellescape(account), shellescape(s:draft)], + \"--account %s template send %s -- %s", + \[shellescape(account), attachments, shellescape(s:draft)], \"Sending message", \0, \) elseif choice == "d" return s:cli( - \"--account %s --mailbox Drafts save -- %s", - \[shellescape(account), shellescape(s:draft)], + \"--account %s --mailbox Drafts template save %s -- %s", + \[shellescape(account), attachments, shellescape(s:draft)], \"Saving draft", \0, \) @@ -336,6 +338,21 @@ function! himalaya#msg#complete_contact(findstart, base) endtry endfunction +function! himalaya#msg#add_attachment() + try + let attachment_path = input("Attachment path: ", "", "file") + if empty(expand(glob(attachment_path))) + throw "The file does not exist" + endif + call add(s:attachment_paths, attachment_path) + redraw | call himalaya#shared#log#info("Attachment added!") + catch + if !empty(v:exception) + redraw | call himalaya#shared#log#err(v:exception) + endif + endtry +endfunction + " Utils " https://newbedev.com/get-usable-window-width-in-vim-script diff --git a/vim/ftplugin/himalaya-msg-write.vim b/vim/ftplugin/himalaya-msg-write.vim index d9dc050..ccbfd2d 100644 --- a/vim/ftplugin/himalaya-msg-write.vim +++ b/vim/ftplugin/himalaya-msg-write.vim @@ -7,6 +7,10 @@ if exists("g:himalaya_complete_contact_cmd") setlocal completefunc=himalaya#msg#complete_contact endif +call himalaya#shared#bindings#define([ + \["n", "ga", "msg#add_attachment"], +\]) + augroup himalaya_write autocmd! * autocmd BufWriteCmd call himalaya#msg#draft_save()