diff --git a/emacs/Makefile.am b/emacs/Makefile.am index bcbc709e..e45b7755 100644 --- a/emacs/Makefile.am +++ b/emacs/Makefile.am @@ -27,6 +27,7 @@ dist_lisp_LISP= \ mu4e-compose.el \ mu4e-hdrs.el \ mu4e-main.el \ + mu4e-mark.el \ mu4e-meta.el \ mu4e-proc.el \ mu4e-speedbar.el \ diff --git a/emacs/mu4e-hdrs.el b/emacs/mu4e-hdrs.el index 7007fcf4..e1931ed7 100644 --- a/emacs/mu4e-hdrs.el +++ b/emacs/mu4e-hdrs.el @@ -32,6 +32,7 @@ (require 'mu4e-proc) (require 'mu4e-utils) ;; utility functions (require 'mu4e-vars) +(require 'mu4e-mark) ;;;; internal variables/constants ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defconst mu4e-hdrs-fringe " " "*internal* The space on the left of @@ -43,7 +44,7 @@ message headers to put marks.") (let ((inhibit-read-only t)) (with-current-buffer mu4e-hdrs-buffer (erase-buffer) - (when mu4e-marks-map (clrhash mu4e-marks-map)))))) + (mu4e--mark-clear))))) (defun mu4e-hdrs-search (expr &optional full-search) @@ -90,8 +91,8 @@ headers." (when point ;; is the message present in this list? ;; if it's marked, unmark it now - (when (mu4e-hdrs-docid-is-marked docid) - (mu4e-hdrs-mark 'unmark)) + (when (mu4e-mark-docid-marked-p docid) + (mu4e-mark-set 'unmark)) ;; re-use the thread info from the old one; this is needed because ;; *update* message don't have thread info by themselves (unlike @@ -232,10 +233,19 @@ after the end of the search results." + + ;;; hdrs-mode and mode-map ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defvar mu4e-hdrs-mode-map nil "Keymap for *mu4e-headers* buffers.") (unless mu4e-hdrs-mode-map + ;; add some quick funcs so our key descriptions below are shorter + (defun mu4e--hdrs-mark-trash()(interactive)(mu4e-hdrs-mark-and-next 'trash)) + (defun mu4e--hdrs-mark-delete()(interactive)(mu4e-hdrs-mark-and-next 'delete)) + (defun mu4e--hdrs-mark-unmark()(interactive)(mu4e-hdrs-mark-and-next 'unmark)) + (defun mu4e--hdrs-mark-read()(interactive)(mu4e-hdrs-mark-and-next 'read)) + (defun mu4e--hdrs-mark-unread()(interactive)(mu4e-hdrs-mark-and-next 'unread)) + (setq mu4e-hdrs-mode-map (let ((map (make-sparse-keymap))) @@ -260,24 +270,26 @@ after the end of the search results." ;; switching to view mode (if it's visible) (define-key map "y" 'mu4e-select-other-view) - ;; marking/unmarking/executing - (define-key map (kbd "") 'mu4e-mark-for-trash) - (define-key map "d" 'mu4e-mark-for-trash) + ;; marking/unmarking ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (define-key map (kbd "") 'mu4e--hdrs-mark-trash) + (define-key map (kbd "d") 'mu4e--hdrs-mark-trash) - (define-key map (kbd "") 'mu4e-mark-for-delete) - (define-key map (kbd "") 'mu4e-mark-for-delete) - (define-key map "D" 'mu4e-mark-for-delete) - - (define-key map "o" 'mu4e-mark-as-unread) - (define-key map "r" 'mu4e-mark-as-read) + (define-key map (kbd "") 'mu4e--hdrs-mark-delete) + (define-key map (kbd "") 'mu4e--hdrs-mark-delete) + (define-key map (kbd "D") 'mu4e--hdrs-mark-delete) + + (define-key map (kbd "o") 'mu4e--hdrs-mark-unread) + (define-key map (kbd "r") 'mu4e--hdrs-mark-read) + (define-key map (kbd "u") 'mu4e--hdrs-mark-unmark) + + (define-key map "m" 'mu4e-hdrs-mark-for-move-and-next) + + (define-key map "U" 'mu4e-mark-unmark-all) + (define-key map "x" 'mu4e-mark-execute-all) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (define-key map "j" 'mu4e-jump-to-maildir) - (define-key map "m" 'mu4e-mark-for-move) - - (define-key map "u" 'mu4e-unmark) - (define-key map "U" 'mu4e-unmark-all) - (define-key map "x" 'mu4e-execute-marks) - (define-key map "a" 'mu4e-hdrs-action) ;; message composition @@ -302,19 +314,22 @@ after the end of the search results." (define-key menumap [display-help] '("Help" . mu4e-display-manual)) (define-key menumap [sepa0] '("--")) + + (define-key menumap [execute-marks] '("Execute marks" + . mu4e-mark-execute-all)) + (define-key menumap [unmark-all] '("Unmark all" . mu4e-mark-unmark-all)) + (define-key menumap [unmark] '("Unmark" . mu4e--hdrs-mark-unmark)) - (define-key menumap [execute-marks] '("Execute marks" . mu4e-execute-marks)) - (define-key menumap [unmark-all] '("Unmark all" . mu4e-unmark-all)) - (define-key menumap [unmark] '("Unmark" . mu4e-unmark)) - - (define-key menumap [mark-as-read] '("Mark as read" . mu4e-mark-as-read)) + (define-key menumap [mark-as-read] '("Mark as read" . mu4e--hdrs-mark-read)) (define-key menumap [mark-as-unread] - '("Mark as unread" . mu4e-mark-as-unread)) + '("Mark as unread" . mu4e--hdrs-mark-unread)) (define-key menumap [mark-delete] - '("Mark for deletion" . mu4e-mark-for-delete)) - (define-key menumap [mark-trash] '("Mark for trash" . mu4e-mark-for-trash)) - (define-key menumap [mark-move] '("Mark for move" . mu4e-mark-for-move)) + '("Mark for deletion" . mu4e--hdrs-mark-delete)) + (define-key menumap [mark-trash] + '("Mark for trash" . mu4e--hdrs-mark-trash)) + (define-key menumap [mark-move] + '("Mark for move" . mu4e-hdrs-mark-for-move-and-next)) (define-key menumap [sepa1] '("--")) (define-key menumap [compose-new] '("Compose new" . mu4e-compose-new)) @@ -345,19 +360,17 @@ after the end of the search results." (make-local-variable 'mu4e-last-expr) (make-local-variable 'mu4e-hdrs-proc) - (make-local-variable 'mu4e-marks-map) (make-local-variable 'mu4e--highlighted-docid) (make-local-variable 'global-mode-string) (make-local-variable 'hl-line-face) (setq - mu4e-marks-map (make-hash-table :size 16 :rehash-size 2) truncate-lines t buffer-undo-list t ;; don't record undo information overwrite-mode 'overwrite-mode-binary hl-line-face 'mu4e-header-highlight-face) - + (mu4e--mark-initialize) ;; initialize the marking subsystem (hl-line-mode 1) (setq header-line-format @@ -502,195 +515,6 @@ at (point-max) otherwise. If MSG is not nil, add it as the text-property `msg'." ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;;; marks ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defvar mu4e-marks-map nil - "Map (hash) of docid->markinfo; when a message is marked, the -information is added here. - -markinfo is a list consisting of the following: -\(marker mark target) -where - MARKER is an emacs-textmarker pointing to the beginning of the header line - MARK is the type of mark (move, trash, delete) - TARGET (optional) is the target directory (for 'move')") - -(defun mu4e-hdrs-mark-message (mark &optional target) - "Mark (or unmark) message at point. MARK specifies the - mark-type. For `move'-marks there is also the TARGET argument, - which specifies to which maildir the message is to be moved. - -The following marks are available, and the corresponding props: - - MARK TARGET description - ---------------------------------------------------------- - `move' y move the message to some folder - `trash' n move the message to `mu4e-trash-folder' - `delete' n remove the message - `read' n mark the message as read - `unread' n mark the message as unread - `unmark' n unmark this message" - (let* ((docid (mu4e--docid-at-point)) - (markkar - (case mark ;; the visual mark - ('move "m") - ('trash "d") - ('delete "D") - ('unread "U") - ('read "R") - ('unmark " ") - (t (error "Invalid mark %S" mark))))) - (unless docid (error "No message on this line")) - (save-excursion - (when (mu4e--mark-header docid markkar)) - ;; update the hash -- remove everything current, and if add the new stuff, - ;; unless we're unmarking - (remhash docid mu4e-marks-map) - ;; remove possible overlays - (remove-overlays (line-beginning-position) (line-end-position)) - - ;; now, let's set a mark (unless we were unmarking) - (unless (eql mark 'unmark) - (puthash docid (list (point-marker) mark target) mu4e-marks-map) - ;; when we have a target (ie., when moving), show the target folder in - ;; an overlay - (when target - (let* ((targetstr (propertize (concat "-> " target " ") - 'face 'mu4e-system-face)) - ;; mu4e-goto-docid docid t will take us just after the docid cookie - ;; and then we skip the mu4e-hdrs-fringe - (start (+ (length mu4e-hdrs-fringe) - (mu4e--goto-docid docid t))) - (overlay (make-overlay start (+ start (length targetstr))))) - (overlay-put overlay 'display targetstr))))))) - - -(defun mu4e-hdrs-mark (mark &optional target) - "Mark the header at point, or, if -region is active, mark all headers in the region. Als see -`mu4e-hdrs-mark-message'." - (with-current-buffer mu4e-hdrs-buffer - (if (use-region-p) - ;; mark all messages in the region. - (save-excursion - (let ((b (region-beginning)) (e (region-end))) - (goto-char b) - (while (<= (line-beginning-position) e) - (mu4e-hdrs-mark-message mark target) - (forward-line 1)))) - ;; just a single message - (mu4e-hdrs-mark-message mark target)))) - - -(defun mu4e-hdrs-marks-execute () - "Execute the actions for all marked messages in this -buffer. After the actions have been executed succesfully, the -affected messages are *hidden* from the current header list. Since -the headers are the result of a search, we cannot be certain that -the messages no longer matches the current one - to get that -certainty, we need to rerun the search, but we don't want to do -that automatically, as it may be too slow and/or break the users -flow. Therefore, we hide the message, which in practice seems to -work well." - (if (= 0 (hash-table-count mu4e-marks-map)) - (message "Nothing is marked") - (maphash - (lambda (docid val) - (let ((marker (nth 0 val)) (mark (nth 1 val)) (target (nth 2 val))) - (case mark - (move (mu4e-proc-move docid target)) - (read (mu4e-proc-move docid nil "+S-u-N")) - (unread (mu4e-proc-move docid nil "-S+u")) - (trash - (unless mu4e-trash-folder - (error "`mu4e-trash-folder' not set")) - (mu4e-proc-move docid mu4e-trash-folder "+T")) - (delete (mu4e-proc-remove docid))))) - mu4e-marks-map) - (mu4e-hdrs-unmark-all))) - -(defun mu4e-hdrs-unmark-all () - "Unmark all marked messages." - (unless (/= 0 (hash-table-count mu4e-marks-map)) - (error "Nothing is marked")) - (maphash - (lambda (docid val) - (save-excursion - (goto-char (marker-position (nth 0 val))) - (mu4e-hdrs-mark 'unmark))) - mu4e-marks-map) - ;; in any case, clear the marks map - (clrhash mu4e-marks-map)) - -(defun mu4e-view-message () - "View message at point. If there's an existing window for the -view, re-use that one. If not, create a new one, depending on the -value of `mu4e-split-view': if it's a symbol `horizontal' or -`vertical', split the window accordingly; if it is nil, replace the -current window. " - (interactive) - (with-current-buffer mu4e-hdrs-buffer - (let* ((docid (mu4e--docid-at-point)) - (viewwin (and mu4e-view-buffer - (get-buffer-window mu4e-view-buffer)))) - (unless docid (error "No message at point.")) - ;; is there a window already for the message view? - (unless (window-live-p viewwin) - ;; no view window yet; create one, based on the split settings etc. - ;; emacs' use of the terms "horizontally" and "vertically" - ;; are... suprising. There's a clearer `split-window' in emacs24, but - ;; it's not compatible with emacs 23 - (setq viewwin - (cond ;; is there are live window for the message view? - ((eq mu4e-split-view 'horizontal) ;; split horizontally - (split-window-vertically mu4e-headers-visible-lines)) - ((eq mu4e-split-view 'vertical) ;; split vertically - (split-window-horizontally mu4e-headers-visible-columns)) - (t ;; no splitting; just use the currently selected one - (selected-window))))) - ;; okay, now we should have a window for the message view - ;; we select it, and show the messages there. - (select-window viewwin) - (switch-to-buffer (get-buffer-create mu4e-view-buffer-name)) - (let ((inhibit-read-only t)) - (erase-buffer) - (insert (propertize "Waiting for message..." - 'face 'mu4e-system-face 'intangible t))) - (mu4e-proc-view docid)))) - - -(defun mu4e-hdrs-docid-is-marked (docid) - "Is the given docid marked?" - (when (gethash docid mu4e-marks-map) t)) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defun mu4e-handle-marks () - "If there are any marks in the current buffer, handle those -according to the value of `mu4e-headers-leave-behavior'. This -function is to be called before any further action (like searching, -quiting the buffer) is taken; returning t means 'take the following -action', return nil means 'don't do anything'" - (let ((marknum - (if mu4e-marks-map (hash-table-count mu4e-marks-map) 0)) - (what mu4e-headers-leave-behavior)) - (unless (or (= marknum 0) (eq what 'ignore) (eq what 'apply)) - ;; if `mu4e-headers-leave-behavior' is not apply or ignore, ask the user - (setq what - (let ((kar (mu4e-read-option - "There are existing marks; should we: " - '(("apply marks") ("ignore marks?"))))) - (cond - ((= kar ?a) 'apply) - ((= kar ?i) 'ignore) - (t nil))))) ;; cancel - ;; we determined what to do... now do it - (cond - ((= 0 marknum) t) ;; no marks, just go ahead - ((eq what 'ignore) t) ;; ignore the marks, go ahead - ((eq what 'apply) - (progn (mu4e-execute-marks t) t) t) ;; execute marks, go ahead - (t nil)))) ;; otherwise, don't do anything - ;;; interactive functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -700,7 +524,7 @@ results to `mu4e-search-results-limit', otherwise show all. In other words, use the C-u prefix to get /all/ results, otherwise get up to `mu4e-search-results-limit' much quicker." (interactive "s[mu] search for: ") - (when (mu4e-handle-marks) + (when (mu4e-mark-handle-when-leaving) (mu4e-hdrs-search expr current-prefix-arg))) (defun mu4e-search-bookmark () @@ -708,7 +532,7 @@ up to `mu4e-search-results-limit' much quicker." otherwise, limit to up to `mu4e-search-results-limit'." (interactive) (let ((query (mu4e-ask-bookmark "Bookmark: "))) - (when (and query (mu4e-handle-marks)) + (when (and query (mu4e-mark-handle-when-leaving)) (mu4e-hdrs-search query current-prefix-arg)))) (defun mu4e-search-bookmark-edit-first (expr) @@ -718,13 +542,51 @@ otherwise, limit to up to `mu4e-search-results-limit'." (interactive (list (read-string "[mu] search for: " (concat (or (mu4e-ask-bookmark "Edit bookmark: ") "") " ")))) - (when (and expr (mu4e-handle-marks)) + (when (and expr (mu4e-mark-handle-when-leaving)) (mu4e-hdrs-search expr current-prefix-arg))) + +(defun mu4e-view-message () + "View message at point. If there's an existing window for the +view, re-use that one. If not, create a new one, depending on the +value of `mu4e-split-view': if it's a symbol `horizontal' or +`vertical', split the window accordingly; if it is nil, replace the +current window. " + (interactive) + (unless (eq major-mode 'mu4e-hdrs-mode) + (error "Must be in mu4e-hdrs-mode (%S)" major-mode)) + (let* ((docid (mu4e--docid-at-point)) + (viewwin (and mu4e-view-buffer + (get-buffer-window mu4e-view-buffer)))) + (unless docid (error "No message at point.")) + ;; is there a window already for the message view? + (unless (window-live-p viewwin) + ;; no view window yet; create one, based on the split settings etc. + ;; emacs' use of the terms "horizontally" and "vertically" + ;; are... suprising. There's a clearer `split-window' in emacs24, but + ;; it's not compatible with emacs 23 + (setq viewwin + (cond ;; is there are live window for the message view? + ((eq mu4e-split-view 'horizontal) ;; split horizontally + (split-window-vertically mu4e-headers-visible-lines)) + ((eq mu4e-split-view 'vertical) ;; split vertically + (split-window-horizontally mu4e-headers-visible-columns)) + (t ;; no splitting; just use the currently selected one + (selected-window))))) + ;; okay, now we should have a window for the message view + ;; we select it, and show the messages there. + (select-window viewwin) + (switch-to-buffer (get-buffer-create mu4e-view-buffer-name)) + (let ((inhibit-read-only t)) + (erase-buffer) + (insert (propertize "Waiting for message..." + 'face 'mu4e-system-face 'intangible t))) + (mu4e-proc-view docid))) + (defun mu4e-hdrs-kill-buffer-and-window () "Quit the message view and return to the main view." (interactive) - (when (mu4e-handle-marks) + (when (mu4e-mark-handle-when-leaving) (mu4e-kill-buffer-and-window mu4e-hdrs-buffer) (mu4e-main-view))) @@ -732,7 +594,7 @@ otherwise, limit to up to `mu4e-search-results-limit'." "Rerun the search for the last search expression; if none exists, do a new search." (interactive) - (when (mu4e-handle-marks) + (when (mu4e-mark-handle-when-leaving) (if mu4e-last-expr (mu4e-hdrs-search mu4e-last-expr) (call-interactively 'mu4e-search)))) @@ -741,23 +603,22 @@ do a new search." "Move point LINES lines forward (if LINES is positive) or backward (if LINES is negative). If this succeeds, return the new docid. Otherwise, return nil." - (with-current-buffer mu4e-hdrs-buffer - (unless (buffer-live-p mu4e-hdrs-buffer) - (error "Headers buffer is not alive %S" (current-buffer))) - (let ((succeeded (= 0 (forward-line lines))) - (docid (mu4e--docid-at-point))) - ;; trick to move point, even if this function is called when this window - ;; is not visible - (when docid - (set-window-point (get-buffer-window mu4e-hdrs-buffer) (point)) - ;; attempt to highlight the new line, display the message - (mu4e-hdrs-highlight docid) - ;; if there already is a visible message view, show the message - (when (and (buffer-live-p mu4e-view-buffer) - (window-live-p (get-buffer-window mu4e-view-buffer))) - (mu4e-view-message))) - ;; return the docid only if the move succeeded - (when succeeded docid)))) + (unless (eq major-mode 'mu4e-hdrs-mode) + (error "Must be in mu4e-hdrs-mode (%S)" major-mode)) + (let ((succeeded (= 0 (forward-line lines))) + (docid (mu4e--docid-at-point))) + ;; trick to move point, even if this function is called when this window + ;; is not visible + (when docid + (set-window-point (get-buffer-window mu4e-hdrs-buffer) (point)) + ;; attempt to highlight the new line, display the message + (mu4e-hdrs-highlight docid) + ;; if there already is a visible message view, show the message + (when (and (buffer-live-p mu4e-view-buffer) + (window-live-p (get-buffer-window mu4e-view-buffer))) + (mu4e-view-message))) + ;; return the docid only if the move succeeded + (when succeeded docid))) (defun mu4e-next-header () "Move point to the next message header. If this succeeds, return @@ -778,85 +639,10 @@ maildir). With C-u prefix, show /all/ results, otherwise, limit to up to `mu4e-search-results-limit'." (interactive) (let ((fld (mu4e-ask-maildir "Jump to maildir: "))) - (when (and fld (mu4e-handle-marks)) + (when (and fld (mu4e-mark-handle-when-leaving)) (mu4e-hdrs-search (concat "\"maildir:" fld "\"") current-prefix-arg)))) -(defun mu4e-mark-for-move (&optional target) - "Mark message at point for moving to maildir TARGET. If target is -not provided, function asks for it." - (interactive) - (with-current-buffer mu4e-hdrs-buffer - (unless (mu4e--docid-at-point) - (error "No message at point.")) - (with-current-buffer mu4e-hdrs-buffer - (let* ((target (or target (mu4e-ask-maildir "Move message to: "))) - (target (if (string= (substring target 0 1) "/") - target - (concat "/" target))) - (fulltarget (concat mu4e-maildir target))) - (when (or (file-directory-p fulltarget) - (and (yes-or-no-p - (format "%s does not exist. Create now?" fulltarget)) - (mu4e-proc-mkdir fulltarget))) - (mu4e-hdrs-mark 'move target) - (mu4e-next-header)))))) - - -(defun mu4e-mark (mark) - "Mark message for MARK (trash, delete, read, unread, unmark)." - (with-current-buffer mu4e-hdrs-buffer - (mu4e-hdrs-mark mark) - (mu4e-next-header))) - -(defun mu4e-mark-for-trash () - "Mark message at point for moving to the trash -folder (`mu4e-trash-folder')." - (interactive) - (mu4e-mark 'trash)) - -(defun mu4e-mark-for-delete () - "Mark message at point for direct deletion." - (interactive) - (mu4e-mark 'delete)) - -(defun mu4e-mark-as-read () - "Mark message at point as unread." - (interactive) - (mu4e-mark 'read)) - -(defun mu4e-mark-as-unread () - "Mark message at point as read." - (interactive) - (mu4e-mark 'unread)) - -(defun mu4e-unmark () - "Unmark message at point." - (interactive) - (with-current-buffer mu4e-hdrs-buffer - (mu4e-mark 'unmark))) - -(defun mu4e-unmark-all () - "Unmark all messages." - (interactive) - (with-current-buffer mu4e-hdrs-buffer - (if (= 0 (hash-table-count mu4e-marks-map)) - (message "Nothing is marked") - (mu4e-hdrs-unmark-all)))) - -(defun mu4e-execute-marks (&optional no-confirmation) - "Execute the actions for the marked messages. If optional -parameter NO-CONFIRMATION is is t, don't ask for confirmation." - (interactive) - (with-current-buffer mu4e-hdrs-buffer - (if (= 0 (hash-table-count mu4e-marks-map)) - (message "Nothing is marked") - (when (or no-confirmation - (y-or-n-p (format "Sure you want to execute marks on %d message(s)?" - (hash-table-count mu4e-marks-map)))) - (mu4e-hdrs-marks-execute) - (message nil))))) - (defun mu4e-compose (compose-type) "Start composing a message of COMPOSE-TYPE, where COMPOSE-TYPE is @@ -918,6 +704,21 @@ actions are specified in `mu4e-headers-actions'." (actionfunc (mu4e-choose-action "Action: " mu4e-headers-actions))) (funcall actionfunc msg))) +(defun mu4e-hdrs-mark-and-next (mark) + "Set mark MARK on the message at point or on all messages in the +region if there is a region, then move to the next message." + (interactive) + (mu4e-mark-set mark) + (mu4e-next-header)) + +(defun mu4e-hdrs-mark-for-move-and-next () + "Set mark MARK on the message at point or on all messages in the +region if there is a region, then move to the next message." + (interactive) + (mu4e-mark-for-move-set) + (mu4e-next-header)) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (provide 'mu4e-hdrs) diff --git a/emacs/mu4e-mark.el b/emacs/mu4e-mark.el new file mode 100644 index 00000000..3d2b9d15 --- /dev/null +++ b/emacs/mu4e-mark.el @@ -0,0 +1,231 @@ +;;; mu4e-mark.el -- part of mu4e, the mu mail user agent +;; +;; Copyright (C) 2011-2012 Dirk-Jan C. Binnema + +;; Author: Dirk-Jan C. Binnema +;; Maintainer: Dirk-Jan C. Binnema + +;; This file is not part of GNU Emacs. +;; +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Commentary: + +;; In this file are function related to marking messages; they assume we are +;; currently in the headers buffer. + +;; Code: +(require 'mu4e-proc) +(require 'mu4e-utils) + +;;; marks ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defvar mu4e--mark-map nil + "Map (hash) of docid->markinfo; when a message is marked, the +information is added here. + +markinfo is a list consisting of the following: +\(mark target) +where + MARK is the type of mark (move, trash, delete) + TARGET (optional) is the target directory (for 'move')") + +;; the mark-map is specific for the current header buffer +;; currently, there can't be more than one, but we never know what will +;; happen in the future + + +(defun mu4e--mark-initialize () + "Initialize the marks subsystem." + (make-local-variable 'mu4e--mark-map) + (setq mu4e--mark-map (make-hash-table :size 16 :rehash-size 2))) + +(defun mu4e--mark-clear () + "Clear the marks subsystem." + (clrhash mu4e--mark-map)) + + +(defun mu4e-mark-at-point (mark &optional target) + "Mark (or unmark) message at point. MARK specifies the + mark-type. For `move'-marks there is also the TARGET argument, + which specifies to which maildir the message is to be moved. The + functipn works in both headers buffers and message buffers. + +The following marks are available, and the corresponding props: + + MARK TARGET description + ---------------------------------------------------------- + `move' y move the message to some folder + `trash' n move the message to `mu4e-trash-folder' + `delete' n remove the message + `read' n mark the message as read + `unread' n mark the message as unread + `unmark' n unmark this message" + (interactive) + (let* ((docid (mu4e--docid-at-point)) + (markkar + (case mark ;; the visual mark + ('move "m") + ('trash "d") + ('delete "D") + ('unread "U") + ('read "R") + ('unmark " ") + (t (error "Invalid mark %S" mark))))) + (unless docid (error "No message on this line")) + (save-excursion + (when (mu4e--mark-header docid markkar)) + ;; update the hash -- remove everything current, and if add the new stuff, + ;; unless we're unmarking + (remhash docid mu4e--mark-map) + ;; remove possible overlays + (remove-overlays (line-beginning-position) (line-end-position)) + + ;; now, let's set a mark (unless we were unmarking) + (unless (eql mark 'unmark) + (puthash docid (list mark target) mu4e--mark-map) + ;; when we have a target (ie., when moving), show the target folder in + ;; an overlay + (when target + (let* ((targetstr (propertize (concat "-> " target " ") + 'face 'mu4e-system-face)) + ;; mu4e-goto-docid docid t will take us just after the docid cookie + ;; and then we skip the mu4e-hdrs-fringe + (start (+ (length mu4e-hdrs-fringe) + (mu4e--goto-docid docid t))) + (overlay (make-overlay start (+ start (length targetstr))))) + (overlay-put overlay 'display targetstr) + docid)))))) + + +(defun mu4e-mark-set (mark &optional target) + "Mark the header at point, or, if region is active, mark all +headers in the region." + (interactive) + (if (use-region-p) + ;; mark all messages in the region. + (save-excursion + (let ((b (region-beginning)) (e (region-end))) + (goto-char b) + (while (<= (line-beginning-position) e) + (mu4e-mark-at-point mark target) + (forward-line 1)))) + ;; just a single message + (mu4e-mark-at-point mark target))) + + +(defun mu4e-mark-for-move-set (&optional target) + "Mark message at point or, if region is active, all messages in +the region, for moving to maildir TARGET. If target is not +provided, function asks for it." + (interactive) + (unless (mu4e--docid-at-point) + (error "No message at point.")) + (let* ((target (or target (mu4e-ask-maildir "Move message to: "))) + (target (if (string= (substring target 0 1) "/") + target + (concat "/" target))) + (fulltarget (concat mu4e-maildir target))) + (when (or (file-directory-p fulltarget) + (and (yes-or-no-p + (format "%s does not exist. Create now?" fulltarget)) + (mu4e-proc-mkdir fulltarget))) + (mu4e-mark-set 'move target)))) + + + +(defun mu4e-mark-execute-all (&optional no-confirmation) + "Execute the actions for all marked messages in this +buffer. After the actions have been executed succesfully, the +affected messages are *hidden* from the current header list. Since +the headers are the result of a search, we cannot be certain that +the messages no longer matches the current one - to get that +certainty, we need to rerun the search, but we don't want to do +that automatically, as it may be too slow and/or break the users +flow. Therefore, we hide the message, which in practice seems to +work well. + +If NO-CONFIRMATION is non-nil, do not ask the user for +confirmation." + (interactive) + (if (zerop (hash-table-count mu4e--mark-map)) + (message "Nothing is marked") + (when (or no-confirmation + (y-or-n-p (format "Sure you want to execute marks on %d message(s)?" + (hash-table-count mu4e--mark-map)))) + (maphash + (lambda (docid val) + (let ((mark (nth 0 val)) (target (nth 1 val))) + (case mark + (move (mu4e-proc-move docid target)) + (read (mu4e-proc-move docid nil "+S-u-N")) + (unread (mu4e-proc-move docid nil "-S+u")) + (trash + (unless mu4e-trash-folder + (error "`mu4e-trash-folder' not set")) + (mu4e-proc-move docid mu4e-trash-folder "+T")) + (delete (mu4e-proc-remove docid))))) + mu4e--mark-map) + (mu4e-mark-unmark-all) + (message nil)))) + +(defun mu4e-mark-unmark-all () + "Unmark all marked messages." + (interactive) + (when (zerop (hash-table-count mu4e--mark-map)) + (error "Nothing is marked")) + (maphash + (lambda (docid val) + (save-excursion + (when (mu4e--goto-docid docid) + (mu4e-mark-set 'unmark)))) + mu4e--mark-map) + ;; in any case, clear the marks map + (mu4e--mark-clear)) + + +(defun mu4e-mark-docid-marked-p (docid) + "Is the given docid marked?" + (when (gethash docid mu4e--mark-map) t)) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + +(defun mu4e-mark-handle-when-leaving () + "If there are any marks in the current buffer, handle those +according to the value of `mu4e-headers-leave-behavior'. This +function is to be called before any further action (like searching, +quiting the buffer) is taken; returning t means 'take the following +action', return nil means 'don't do anything'" + (let ((marknum + (if mu4e--mark-map (hash-table-count mu4e--mark-map) 0)) + (what mu4e-headers-leave-behavior)) + (unless (or (= marknum 0) (eq what 'ignore) (eq what 'apply)) + ;; if `mu4e-headers-leave-behavior' is not apply or ignore, ask the user + (setq what + (let ((kar (mu4e-read-option + "There are existing marks; should we: " + '(("apply marks") ("ignore marks?"))))) + (cond + ((= kar ?a) 'apply) + ((= kar ?i) 'ignore) + (t nil))))) ;; cancel + ;; we determined what to do... now do it + (cond + ((= 0 marknum) t) ;; no marks, just go ahead + ((eq what 'ignore) t) ;; ignore the marks, go ahead + ((eq what 'apply) + (progn (mu4e-mark-execute-all t) t) t) ;; execute marks, go ahead + (t nil)))) ;; otherwise, don't do anything + + +(provide 'mu4e-mark) diff --git a/emacs/mu4e-view.el b/emacs/mu4e-view.el index 8ce32b65..ade0f040 100644 --- a/emacs/mu4e-view.el +++ b/emacs/mu4e-view.el @@ -28,18 +28,16 @@ ;;; Code: (require 'mu4e-utils) ;; utility functions (require 'mu4e-vars) +(require 'mu4e-mark) ;; we prefer the improved fill-region (require 'filladapt nil 'noerror) (require 'comint) ;; some buffer-local variables -(defvar mu4e-hdrs-buffer nil +(defvar mu4e--view-hdrs-buffer nil "*internal* Headers buffer connected to this view.") -(defconst mu4e-view-raw-buffer-name "*mu4e-raw-view*" - "*internal* Name for the raw message view buffer") - (defun mu4e-view-message-with-msgid (msgid) "View message with MSGID. This is meant for external programs wanting to show specific messages - for example, `mu4e-org'." @@ -114,7 +112,7 @@ marking if it still had that." (setq ;; these are buffer-local buffer-read-only t mu4e-current-msg msg - mu4e-hdrs-buffer hdrsbuf + mu4e--view-hdrs-buffer hdrsbuf mu4e-link-map (make-hash-table :size 32 :rehash-size 2 :weakness nil)) (switch-to-buffer buf) @@ -225,6 +223,7 @@ is nil, and otherwise open it." (defvar mu4e-view-mode-map nil "Keymap for \"*mu4e-view*\" buffers.") (unless mu4e-view-mode-map + (setq mu4e-view-mode-map (let ((map (make-sparse-keymap))) @@ -264,11 +263,11 @@ is nil, and otherwise open it." #'(lambda () (interactive) (scroll-up -1))) ;; navigation between messages - (define-key map "p" 'mu4e-view-prev-header) - (define-key map "n" 'mu4e-view-next-header) + (define-key map "p" 'mu4e--view-prev-header) + (define-key map "n" 'mu4e--view-next-header) ;; the same - (define-key map (kbd "") 'mu4e-view-next-header) - (define-key map (kbd "") 'mu4e-view-prev-header) + (define-key map (kbd "") 'mu4e--view-next-header) + (define-key map (kbd "") 'mu4e--view-prev-header) ;; switching to view mode (if it's visible) (define-key map "y" 'mu4e-select-other-view) @@ -348,8 +347,8 @@ is nil, and otherwise open it." (define-key menumap [jump] '("Jump to maildir" . mu4e-jump-to-maildir)) (define-key menumap [sepa4] '("--")) - (define-key menumap [next] '("Next" . mu4e-view-next-header)) - (define-key menumap [previous] '("Previous" . mu4e-view-prev-header))) + (define-key menumap [next] '("Next" . mu4e--view-next-header)) + (define-key menumap [previous] '("Previous" . mu4e--view-prev-header))) map))) (fset 'mu4e-view-mode-map mu4e-view-mode-map) @@ -363,7 +362,7 @@ is nil, and otherwise open it." \\{mu4e-view-mode-map}." (use-local-map mu4e-view-mode-map) - (make-local-variable 'mu4e-hdrs-buffer) + (make-local-variable 'mu4e--view-hdrs-buffer) (make-local-variable 'mu4e-current-msg) (make-local-variable 'mu4e-link-map) @@ -545,7 +544,7 @@ See the `org-contacts' documentation for more details." "Redisplay the current message, without wrapped lines or hidden citations." (interactive) - (mu4e-view mu4e-current-msg mu4e-hdrs-buffer t) + (mu4e-view mu4e-current-msg mu4e--view-hdrs-buffer t) (setq mu4e-lines-wrapped nil mu4e-cited-hidden nil)) @@ -559,35 +558,16 @@ citations." (kill-buffer-and-window) (kill-buffer))))) -(defun mu4e-view-next-header () - "View the next header." - (interactive) - (when (mu4e-next-header) - (mu4e-view-message))) +(defun mu4e--view-hdrs-move (lines) + "Move point LINES lines forward (if LINES is positive) or +backward (if LINES is negative). If this succeeds, return the new +docid. Otherwise, return nil." + (when (buffer-live-p mu4e--view-hdrs-buffer) + (with-current-buffer mu4e--view-hdrs-buffer + (mu4e--hdrs-move lines)))) -(defun mu4e-view-prev-header () - "View the previous header." - (interactive) - (when (mu4e-prev-header) - (mu4e-view-message))) - -(defun mu4e-view-mark-for-move () - "Mark the current message for moving." - (interactive) - (when (mu4e-mark-for-move) - (mu4e-view-message))) - -(defun mu4e-view-mark-for-trash () - "Mark the current message for moving to the trash folder." - (interactive) - (when (mu4e-mark-for-trash) - (mu4e-view-message))) - -(defun mu4e-view-mark-for-delete () - "Mark the current message for deletion." - (interactive) - (when (mu4e-mark-for-delete) - (mu4e-view-message))) +(defun mu4e--view-next-header()(interactive)(mu4e--view-hdrs-move 1)) +(defun mu4e--view-prev-header()(interactive)(mu4e--view-hdrs-move -1)) (defun mu4e-view-action (&optional msg) @@ -684,6 +664,7 @@ PIPECMD is nil, ask user for it." (index (plist-get att :index))) (mu4e--temp-action (plist-get msg :docid) index "emacs"))) + (defun mu4e-view-attachment-action (&optional msg) "Ask user what to do with attachments in MSG (or nil to use message-at-point, then do it. The actions are specified in @@ -697,6 +678,7 @@ message-at-point, then do it. The actions are specified in (when (and actionfunc attnum) (funcall actionfunc msg attnum)))) + ;; handler-function to handle the response we get from the server when we ;; want to do something with one of the attachments. (defun mu4e-view-temp-handler (path what param) @@ -718,7 +700,22 @@ attachments) in response to a (mu4e-proc-extract 'temp ... )." (t (error "Unsupported action %S" what)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defun mu4e--in-split-view () +;;; marking +(defun mu4e--view-mark-set (mark) + "Set mark on the current messages." + (unless (buffer-live-p mu4e--view-hdrs-buffer) + (error "No headers buffer available")) + (let ((docid (mu4e-msg-field mu4e-current-msg :docid))) + (with-current-buffer mu4e--view-hdrs-buffer + (if (eq mark 'move) + (mu4e-mark-for-move-set) + (mu4e-mark-at-point mark))))) + ;; (when (not (eq mark 'unmark)) + ;; (mu4e--view-next-header) + ;; (mu4e-view-message))))) + + +(defun mu4e--split-view-p () "Return t if we're in split-view, nil otherwise." (member mu4e-split-view '(horizontal vertical))) @@ -726,24 +723,43 @@ attachments) in response to a (mu4e-proc-extract 'temp ... )." "If we're in split-view, unmark all messages. Otherwise, warn user that unmarking only works in the header list." (interactive) - (if (mu4e--in-split-view) - (mu4e-unmark-all) + (if (mu4e--split-view-p) + (mu4e-mark-unmark-all) (message "Unmarking needs to be done in the header list view"))) (defun mu4e-view-unmark () "If we're in split-view, unmark message at point. Otherwise, warn user that unmarking only works in the header list." (interactive) - (if (mu4e--in-split-view) - (mu4e-unmark) + (if (mu4e--split-view-p) + (mu4e--view-mark-set 'unmark) (message "Unmarking needs to be done in the header list view"))) +(defun mu4e-view-mark-for-move () + "Mark the current message for moving." + (interactive) + (mu4e--view-mark-set 'move) + (mu4e--view-next-header)) + +(defun mu4e-view-mark-for-trash () + "Mark the current message for moving to the trash folder." + (interactive) + (mu4e--view-mark-set 'trash) + (mu4e--view-next-header)) + +(defun mu4e-view-mark-for-delete () + "Mark the current message for deletion." + (interactive) + (mu4e--view-mark-set 'delete) + (mu4e--view-next-header)) + (defun mu4e-view-marked-execute () "If we're in split-view, execute the marks. Otherwise, warn user that execution can only take place in n the header list." (interactive) - (if (mu4e--in-split-view) - (mu4e-execute-marks) + (if (mu4e--split-view-p) + (with-current-buffer mu4e--view-hdrs-buffer + (mu4e-mark-execute-all)) (message "Execution needs to be done in the header list view"))) (defun mu4e-view-go-to-url (num) @@ -753,6 +769,9 @@ that execution can only take place in n the header list." (unless url (error "Invalid number for URL")) (browse-url url))) +(defconst mu4e-view-raw-buffer-name "*mu4e-raw-view*" + "*internal* Name for the raw message view buffer") + (defun mu4e-view-raw-message () "Display the raw contents of message at point in a new buffer." (interactive)