diff --git a/lib/mu-server.cc b/lib/mu-server.cc index be1c6aa1..87757e64 100644 --- a/lib/mu-server.cc +++ b/lib/mu-server.cc @@ -449,7 +449,8 @@ Server::Private::invoke(const std::string& expr) noexcept throw res.error(); } catch (const Mu::Error& me) { - output_sexp(make_error(me.code(), mu_format("{}", me.what()))); + output_sexp(make_error(me.code(), mu_format("{}", + me.what()))); keep_going_ = true; } catch (const Xapian::Error& xerr) { output_sexp(make_error(Error::Code::Internal, @@ -458,7 +459,17 @@ Server::Private::invoke(const std::string& expr) noexcept keep_going_ = false; } catch (const std::runtime_error& re) { output_sexp(make_error(Error::Code::Internal, - mu_format("caught exception: {}", re.what()))); + mu_format("caught runtime exception: {}", + re.what()))); + keep_going_ = false; + } catch (const std::out_of_range& oore) { + output_sexp(make_error(Error::Code::Internal, + mu_format("caught out-of-range exception: {}", + oore.what()))); + keep_going_ = false; + } catch (const std::exception& e) { + output_sexp(make_error(Error::Code::Internal, + mu_format(" exception: {}", e.what()))); keep_going_ = false; } catch (...) { output_sexp(make_error(Error::Code::Internal, @@ -872,10 +883,14 @@ Server::Private::move_docid(Store::Id docid, } /* - * 'move' moves a message to a different maildir and/or changes its - * flags. parameters are *either* a 'docid:' or 'msgid:' pointing to - * the message, a 'maildir:' for the target maildir, and a 'flags:' - * parameter for the new flags. + * 'move' moves a message to a different maildir and/or changes its flags. + * parameters are *either* a 'docid:' or 'msgid:' pointing to the message, a + * 'maildir:' for the target maildir, and a 'flags:' parameter for the new + * flags. + * + * With :msgid, this is "opportunistic": it's not an error when the given + * message-id does not exist. This is e.g. for the case when tagging possible + * related messages. */ void Server::Private::move_handler(const Command& cmd) @@ -886,7 +901,19 @@ Server::Private::move_handler(const Command& cmd) const auto no_view{cmd.boolean_arg(":noupdate")}; const auto docids{determine_docids(store_, cmd)}; - if (docids.size() > 1) { + if (docids.empty()) { + if (!!cmd.string_arg(":msgid")) { + // msgid not found: no problem. + mu_debug("no move: '{}' not found", + *cmd.string_arg(":msgid")); + return; + } + // however, if we wanted to be move by msgid, it's worth raising + // an error. + throw Mu::Error{Error::Code::Store, + "message not found in store (docid={})", + cmd.number_arg(":docid").value_or(0)}; + } else if (docids.size() > 1) { if (!maildir.empty()) // ie. duplicate message-ids. throw Mu::Error{Error::Code::Store, "cannot move multiple messages at the same time"}; @@ -894,21 +921,22 @@ Server::Private::move_handler(const Command& cmd) for (auto&& docid : docids) move_docid(docid, flagopt, rename, no_view); return; + } else { + const auto docid{docids.at(0)}; + auto msg = store().find_message(docid) + .or_else([&]{throw Error{Error::Code::InvalidArgument, + "cannot find message {}", docid};}).value(); + + /* if maildir was not specified, take the current one */ + if (maildir.empty()) + maildir = msg.maildir(); + + /* determine the real target flags, which come from the flags-parameter + * we received (ie., flagstr), if any, plus the existing message + * flags. */ + const auto flags = calculate_message_flags(msg, flagopt); + perform_move(docid, msg, maildir, flags, rename, no_view); } - const auto docid{docids.at(0)}; - auto msg = store().find_message(docid) - .or_else([&]{throw Error{Error::Code::InvalidArgument, - "cannot find message {}", docid};}).value(); - - /* if maildir was not specified, take the current one */ - if (maildir.empty()) - maildir = msg.maildir(); - - /* determine the real target flags, which come from the flags-parameter - * we received (ie., flagstr), if any, plus the existing message - * flags. */ - const auto flags = calculate_message_flags(msg, flagopt); - perform_move(docid, msg, maildir, flags, rename, no_view); } void diff --git a/mu4e/meson.build b/mu4e/meson.build index 079e7486..a2a22bbd 100644 --- a/mu4e/meson.build +++ b/mu4e/meson.build @@ -43,6 +43,7 @@ mu4e_srcs=[ 'mu4e-contacts.el', 'mu4e-context.el', 'mu4e-contrib.el', + 'mu4e-draft.el', 'mu4e-folders.el', 'mu4e.el', 'mu4e-headers.el', diff --git a/mu4e/mu4e-compose.el b/mu4e/mu4e-compose.el index 701ed93d..142b74e5 100644 --- a/mu4e/mu4e-compose.el +++ b/mu4e/mu4e-compose.el @@ -22,10 +22,9 @@ ;;; Commentary: -;; Implements mu4e-compose-mode, which is a `message-mode' derivative. This is a -;; *fairly* thin wrapper around the gnus functions for message composition, -;; integrated with mu4e. Still, quite a bit of code to make it work nicely in -;; the mu4e context. +;; Implements mu4e-compose-mode, which is a `message-mode' derivative. There's +;; quite a bit of trickery involved to make the message-mode functions work in +;; this context; see mu4e-draft for details. ;;; Code: @@ -40,124 +39,14 @@ (require 'mu4e-context) (require 'mu4e-folders) +(require 'mu4e-draft) + ;;; User configuration for compose-mode (defgroup mu4e-compose nil "Customization for composing/sending messages." :group 'mu4e) -(defcustom mu4e-sent-messages-behavior 'sent - "Determines what mu4e does with sent messages. - -This is one of the symbols: -* `sent' move the sent message to the Sent-folder (`mu4e-sent-folder') -* `trash' move the sent message to the Trash-folder (`mu4e-trash-folder') -* `delete' delete the sent message. - -Note, when using GMail/IMAP, you should set this to either -`trash' or `delete', since GMail already takes care of keeping -copies in the sent folder. - -Alternatively, `mu4e-sent-messages-behavior' can be a function -which takes no arguments, and which should return one of the mentioned -symbols, for example: - - (setq mu4e-sent-messages-behavior (lambda () - (if (string= (message-sendmail-envelope-from) \"foo@example.com\") - \\='delete \\='sent))) - -The various `message-' functions from `message-mode' are available -for querying the message information." - :type '(choice (const :tag "move message to mu4e-sent-folder" sent) - (const :tag "move message to mu4e-trash-folder" trash) - (const :tag "delete message" delete)) - :group 'mu4e-compose) - -(defcustom mu4e-compose-switch nil - "Where to display the new message? -A symbol: -- nil : default (new buffer) -- window : compose in new window -- frame or t : compose in new frame -- display-buffer: use `display-buffer' / `display-buffer-alist' - (for fine-tuning). - -For backward compatibility with `mu4e-compose-in-new-frame', t is -treated as =\\'frame." - :type 'symbol - :group 'mu4e-compose) - -(defcustom mu4e-compose-context-policy 'ask - "Policy for determining the context when composing a new message. - -If the value is `always-ask', ask the user unconditionally. - -In all other cases, if any context matches (using its match -function), this context is used. Otherwise, if none of the -contexts match, we have the following choices: - -- `pick-first': pick the first of the contexts available (ie. the default) -- `ask': ask the user -- `ask-if-none': ask if there is no context yet, otherwise leave it as it is -- nil: return nil; leaves the current context as is. - -Also see `mu4e-context-policy'." - :type '(choice - (const :tag "Always ask what context to use" always-ask) - (const :tag "Ask if none of the contexts match" ask) - (const :tag "Ask when there's no context yet" ask-if-none) - (const :tag "Pick the first context if none match" pick-first) - (const :tag "Don't change the context when none match" nil)) - :safe 'symbolp - :group 'mu4e-compose) - -(defcustom mu4e-compose-crypto-policy - '(encrypt-encrypted-replies sign-encrypted-replies) - "Policy to control when messages will be signed/encrypted. - -The value is a list which influence the way draft messages are -created. Specifically, it might contain: - -- `sign-all-messages': Always add a signature. -- `sign-new-messages': Add a signature to new message, ie. - messages that aren't responses to another message. -- `sign-forwarded-messages': Add a signature when forwarding - a message -- `sign-edited-messages': Add a signature to drafts -- `sign-all-replies': Add a signature when responding to - another message. -- `sign-plain-replies': Add a signature when responding to - non-encrypted messages. -- `sign-encrypted-replies': Add a signature when responding - to encrypted messages. - -It should be noted that certain symbols have priorities over one -another. So `sign-all-messages' implies `sign-all-replies', which -in turn implies `sign-plain-replies'. Adding both to the set, is -not a contradiction, but a redundant configuration. - -All `sign-*' options have a `encrypt-*' analogue." - :type '(set :greedy t - (const :tag "Sign all messages" sign-all-messages) - (const :tag "Encrypt all messages" encrypt-all-messages) - (const :tag "Sign new messages" sign-new-messages) - (const :tag "Encrypt new messages" encrypt-new-messages) - (const :tag "Sign forwarded messages" sign-forwarded-messages) - (const :tag "Encrypt forwarded messages" - encrypt-forwarded-messages) - (const :tag "Sign edited messages" sign-edited-messages) - (const :tag "Encrypt edited messages" edited-forwarded-messages) - (const :tag "Sign all replies" sign-all-replies) - (const :tag "Encrypt all replies" encrypt-all-replies) - (const :tag "Sign replies to plain messages" sign-plain-replies) - (const :tag "Encrypt replies to plain messages" - encrypt-plain-replies) - (const :tag "Sign replies to encrypted messages" - sign-encrypted-replies) - (const :tag "Encrypt replies to encrypted messages" - encrypt-encrypted-replies)) - :group 'mu4e-compose) - (defcustom mu4e-compose-format-flowed nil "Whether to compose messages to be sent as format=flowed. \(Or with long lines if variable `use-hard-newlines' is set to @@ -183,16 +72,26 @@ the place to do that." :type 'hook :group 'mu4e-compose) - -;;; Runtime variables; useful for user-hooks etc. -(defvar-local mu4e-compose-parent-message nil - "The parent message plist. -This is the message being replied to, forwarded or edited; used -in `mu4e-compose-pre-hook'. For new (non-reply, forward etc.) -messages, it is nil.") +(defcustom mu4e-compose-post-hook + (list + ;; kill compose frames + #'mu4e-compose-post-kill-frame + ;; attempt to restore the old configuration. + #'mu4e-compose-post-restore-window-configuration) + "Hook run *after* message composition is over. -(defvar-local mu4e-compose-type nil - "The compose-type for the current message.") +This is hook is run when composition buffer, +either by sending, postponing, exiting or killing it. + +This multiplexes the `message-mode' hooks `message-send-actions', +`message-postpone-actions', `message-exit-actions' and +`message-kill-actions', and the hook is run with a variable +`mu4e-compose-post-trigger' set correspondingly to a symbol, +`send', `postpone', `exit' or `kill'." + :type 'hook + :group 'mu4e-compose) + + (defvar mu4e-captured-message) (defun mu4e-compose-attach-captured-message () @@ -285,61 +184,6 @@ the file under our feet, which is a bit fragile." (when message-signature (save-excursion (message-insert-signature)))))))) - -;;; Filenames -(defun mu4e--message-basename() - "Construct a randomized filename for a message with flags FLAGSTR. -It looks something like -