From 084ecc71d2d5906fd0ea8e7e84c27ac9685ef78f Mon Sep 17 00:00:00 2001 From: djcb Date: Thu, 26 Apr 2012 17:59:34 +0300 Subject: [PATCH] * mu4e(-main|-proc-|utils).el: refactoring, some cleanup/improvements - move all mu4e startup functions to mu4e-utils - add `mu4e' function to mu4e.el that call these mu4e-utils function - now easy to start mu4e without showing ui - mu4e~proc-is-running moved to mu4e-proc - made mu4e-read-option a bit smarter - renamed some more functions from mu4e- => mu4e~ (i.e.., mark them private) --- emacs/mu4e-main.el | 63 ++++--------- emacs/mu4e-proc.el | 16 +++- emacs/mu4e-utils.el | 219 +++++++++++++++++++++++++++----------------- emacs/mu4e.el | 19 +++- 4 files changed, 182 insertions(+), 135 deletions(-) diff --git a/emacs/mu4e-main.el b/emacs/mu4e-main.el index 55cf6361..912fe61e 100644 --- a/emacs/mu4e-main.el +++ b/emacs/mu4e-main.el @@ -26,7 +26,7 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (require 'mu4e-utils) ;; utility functions -(defconst mu4e-main-buffer-name "*mu4e-main*" +(defconst mu4e~main-buffer-name "*mu4e-main*" "*internal* Name of the mu4e main view buffer.") (defvar mu4e-main-mode-map @@ -40,7 +40,7 @@ (define-key map "j" 'mu4e-jump-to-maildir) (define-key map "C" 'mu4e-compose-new) - (define-key map "m" 'mu4e-toggle-mail-sending-mode) + (define-key map "m" 'mu4e~main-toggle-mail-sending-mode) (define-key map "f" 'smtpmail-send-queued-mail) (define-key map "U" 'mu4e-update-mail-show-window) @@ -60,7 +60,7 @@ overwrite-mode 'overwrite-mode-binary)) -(defun mu4e-action-str (str &optional func-or-shortcut) +(defun mu4e~main-action-str (str &optional func-or-shortcut) "Highlight the first occurence of [..] in STR. If FUNC-OR-SHORTCUT is non-nil and if it is a function, call it when STR is clicked (using RET or mouse-2); if FUNC-OR-SHORTCUT is a @@ -84,12 +84,12 @@ clicked." (define-key map (kbd "RET") func) (put-text-property 0 (length newstr) 'keymap map newstr) (put-text-property (string-match "\\w" newstr) - (- (length newstr) 1) 'mouse-face 'highlight newstr) - newstr)) + (- (length newstr) 1) 'mouse-face 'highlight newstr) newstr)) -(defun mu4e-main-view() + +(defun mu4e~main-view () "Show the mu4e main view." - (let ((buf (get-buffer-create mu4e-main-buffer-name)) + (let ((buf (get-buffer-create mu4e~main-buffer-name)) (inhibit-read-only t)) (with-current-buffer buf (erase-buffer) @@ -99,70 +99,46 @@ clicked." (propertize mu4e-mu-version 'face 'mu4e-view-header-key-face) "\n\n" (propertize " Basics\n\n" 'face 'mu4e-title-face) - (mu4e-action-str "\t* [j]ump to some maildir\n" 'mu4e-jump-to-maildir) - (mu4e-action-str "\t* enter a [s]earch query\n" 'mu4e-search) - (mu4e-action-str "\t* [C]ompose a new message\n" 'mu4e-compose-new) + (mu4e~main-action-str "\t* [j]ump to some maildir\n" 'mu4e-jump-to-maildir) + (mu4e~main-action-str "\t* enter a [s]earch query\n" 'mu4e-search) + (mu4e~main-action-str "\t* [C]ompose a new message\n" 'mu4e-compose-new) "\n" (propertize " Bookmarks\n\n" 'face 'mu4e-title-face) ;; TODO: it's a bit uncool to hard-code the "b" shortcut... (mapconcat (lambda (bm) (let* ((query (nth 0 bm)) (title (nth 1 bm)) (key (nth 2 bm))) - (mu4e-action-str + (mu4e~main-action-str (concat "\t* [b" (make-string 1 key) "] " title) (concat "b" (make-string 1 key))))) mu4e-bookmarks "\n") "\n" (propertize " Misc\n\n" 'face 'mu4e-title-face) - (mu4e-action-str "\t* [U]pdate email & database\n" + (mu4e~main-action-str "\t* [U]pdate email & database\n" 'mu4e-update-mail-show-window) ;; show the queue functions if `smtpmail-queue-dir' is defined (if (file-directory-p smtpmail-queue-dir) (concat - (mu4e-action-str "\t* toggle [m]ail sending mode " - 'mu4e-toggle-mail-sending-mode) + (mu4e~main-action-str "\t* toggle [m]ail sending mode " + 'mu4e~main-toggle-mail-sending-mode) "(" (propertize (if smtpmail-queue-mail "queued" "direct") 'face 'mu4e-view-header-key-face) ")\n" - (mu4e-action-str "\t* [f]lush queued mail\n" + (mu4e~main-action-str "\t* [f]lush queued mail\n" 'smtpmail-send-queued-mail)) "") "\n" - (mu4e-action-str "\t* [H]elp\n" 'mu4e-display-manual) - (mu4e-action-str "\t* [q]uit\n" 'mu4e-quit)) + (mu4e~main-action-str "\t* [H]elp\n" 'mu4e-display-manual) + (mu4e~main-action-str "\t* [q]uit\n" 'mu4e-quit)) (mu4e-main-mode) (switch-to-buffer buf) (delete-other-windows)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Interactive functions - -(defconst mu4e-update-buffer-name "*mu4e-update*" - "*internal* Name of the buffer for message retrieval / database - updating.") - -(defun mu4e-update-mail-show-window () - "Try to retrieve mail (using the user-provided shell command), -and update the database afterwards, and show the progress in a -split-window." - (interactive) - (unless mu4e-get-mail-command - (error "`mu4e-get-mail-command' is not defined")) - (let ((buf (get-buffer-create mu4e-update-buffer-name)) - (win - (split-window (selected-window) - (- (window-height (selected-window)) 8)))) - (with-selected-window win - (switch-to-buffer buf) - (set-window-dedicated-p win t) - (erase-buffer) - (insert "\n") ;; FIXME -- needed so output starts - (mu4e-update-mail buf)))) - - -(defun mu4e-toggle-mail-sending-mode () +(defun mu4e~main-toggle-mail-sending-mode () "Toggle sending mail mode, either queued or direct." (interactive) (unless (file-directory-p smtpmail-queue-dir) @@ -171,6 +147,7 @@ split-window." (message (concat "Outgoing mail will now be " (if smtpmail-queue-mail "queued" "sent directly"))) - (mu4e-main-view)) + (mu4e~main-view)) + (provide 'mu4e-main) diff --git a/emacs/mu4e-proc.el b/emacs/mu4e-proc.el index 35882bfc..2da8ca4f 100644 --- a/emacs/mu4e-proc.el +++ b/emacs/mu4e-proc.el @@ -72,6 +72,10 @@ mu4e~proc-process nil mu4e~proc-buf nil)) +(defun mu4e~proc-is-running () + "Whether the mu process is running." + (and mu4e~proc-process (eq (process-status mu4e~proc-process) 'run))) + (defun mu4e~proc-eat-sexp-from-buf () "'Eat' the next s-expression from `mu4e~proc-buf'. `mu4e~proc-buf gets its @@ -102,10 +106,10 @@ updated as well, with all processed sexp data removed." (defun mu4e~proc-filter (proc str) "A process-filter for the 'mu server' output; it accumulates the - strings into valid sexps by checking of the ';;eox' end-of-sexp - marker, and then evaluating them. +strings into valid sexps by checking of the ';;eox' end-of-sexp +marker, and then evaluating them. - The server output is as follows: +The server output is as follows: 1. an error (:error 2 :message \"unknown command\") @@ -344,8 +348,10 @@ will receive (:info add :path :docid )." (defun mu4e~proc-sent (path maildir) "Add the message at PATH to the database, with MAILDIR set to the -maildir this message resides in, e.g. '/drafts'; if this works, we -will receive (:info add :path :docid )." +maildir this message resides in, e.g. '/drafts'. + + if this works, we will receive (:info add :path :docid + :fcc )." (mu4e~proc-send-command "sent path:\"%s\" maildir:\"%s\"" path maildir)) diff --git a/emacs/mu4e-utils.el b/emacs/mu4e-utils.el index b3dff434..3ba266cc 100644 --- a/emacs/mu4e-utils.el +++ b/emacs/mu4e-utils.el @@ -42,35 +42,60 @@ dir already existed, or has been created, nil otherwise." (t nil))) + +(defun mu4e~read-option-normalize-list (options) + "Turn a list OPTIONS into normal-form for `mu4e-read-option'." + ;; transform options into 'normal-form', so that in case an option has 'nil + ;; for CHAR, it's replaced by the first letter of OPTIONSTRING (and that char + ;; is eaten off OPTIONSTR. If RESULT is nil, replace it by CHAR + (map 'list + (lambda (option) + (if (nth 1 option) + (list + (nth 0 option) + (nth 1 option) + (or (nth 2 option) (nth 1 option))) ; + (list + (substring (nth 0 option) 1) ;; chop off first char + (string-to-char (nth 0 option)) ;; first char as shortcut + (or (nth 2 option) (nth 1 option))))) + options)) + + (defun mu4e-read-option (prompt options) "Ask user for an option from a list on the input area. PROMPT describes a multiple-choice question to the user, OPTIONS describe the options, and is a list of cells describing particular options. Cells have the following structure: - (OPTIONSTRING CHAR) where CHAR is a short-cut character for the + + (OPTIONSTRING CHAR [RESULT]) + + where CHAR is a short-cut character for the option, and OPTIONSTRING is a non-empty string describing the option. If CHAR is nil or not-specified, the first character of the optionstring is used. + +If RESULT is provide, this will be returned if the user presses the +corresponding CHAR; otherwise, CHAR is returned. + The options are provided as a list for the user to choose from; -user can then choose by typing CHAR. -Example: +user can then choose by typing CHAR. Example: (mu4e-read-option \"Choose an animal: \" '((\"Monkey\" ?m) (\"Gnu\" ?g) (\"platipus\"))) User now will be presented with a list: - \"Choose an animal: [m]Monkey, [g]Gnu, [p]latipus\" -Function returns the CHAR typed." - (let* ((optionkars) + \"Choose an animal: [m]Monkey, [g]Gnu, [p]latipus\"." + (let* ((options (mu4e~read-option-normalize-list options)) + (chosen) (optionsstr (mapconcat - (lambda (option) - (let* ((descr (car option)) + (lambda (option) + (let* ((descr (car option)) (kar (and (cdr option) (cadr option)))) ;; handle the empty kar case (unless kar (setq ;; eat first kar from descr; use it as kar kar (string-to-char descr) descr (substring descr 1))) - (add-to-list 'optionkars kar) (concat "[" (propertize (make-string 1 kar) 'face 'mu4e-highlight-face) "]" @@ -79,14 +104,16 @@ Function returns the CHAR typed." (inhibit-quit nil) (okchar) (response)) - (while (not okchar) + (while (not chosen) (message nil) ;; we need to clear the echo area first... why?! (setq response (read-char-exclusive (concat prompt optionsstr " [" (propertize "C-g" 'face 'mu4e-highlight-face) " to quit]"))) - (setq okchar (member response optionkars))) - response)) + (setq chosen + (find-if (lambda (option) (eq response (nth 1 option))) options))) + (nth 2 chosen))) + (defun mu4e~get-maildirs-1 (path &optional mdir) "Get maildirs under path, recursively, as a list of relative @@ -328,40 +355,6 @@ function prefers the text part, but this can be changed by setting ;; and finally, remove some crap from the remaining string. (replace-regexp-in-string "[  ]" " " body nil nil nil))) -(defvar mu4e-update-timer nil - "*internal* The mu4e update timer.") - -(defconst mu4e-update-mail-name "*mu4e-update-mail*" - "*internal* Name of the process to update mail") - -(defun mu4e-update-mail (&optional buf) - "Update mail (retrieve using `mu4e-get-mail-command' and update -the database afterwards), with output going to BUF if not nil, or -discarded if nil. After retrieving mail, update the database. Note, -function is asynchronous, returns (almost) immediately, and all the -processing takes part in the background, unless buf is non-nil." - (unless mu4e-get-mail-command - (error "`mu4e-get-mail-command' is not defined")) - (let* ((process-connection-type t) - (proc (start-process-shell-command - mu4e-update-mail-name buf mu4e-get-mail-command))) - (message "Retrieving mail...") - (set-process-sentinel proc - (lambda (proc msg) - (let* ((status (process-status proc)) - (code (process-exit-status proc)) - ;; sadly, fetchmail returns '1' when there is no mail; this is - ;; not really an error of course, but it's hard to distinguish - ;; from a genuine error - (maybe-error (or (not (eq status 'exit)) (/= code 0)))) - (message nil) - ;; there may be an error, give the user up to 5 seconds to check - (when maybe-error - (sit-for 5)) - (mu4e~proc-index mu4e-maildir) - (let ((buf (process-buffer proc))) - (when (buffer-live-p buf) - (kill-buffer buf)))))))) (defun mu4e-display-manual () @@ -515,10 +508,38 @@ process." ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defconst mu4e-update-buffer-name "*mu4e-update*" + "*internal* Name of the buffer for message retrieval / database + updating.") + +(defun mu4e-update-mail-show-window () + "Try to retrieve mail (using the user-provided shell command), +and update the database afterwards, and show the progress in a +split-window." + (interactive) + (unless mu4e-get-mail-command + (error "`mu4e-get-mail-command' is not defined")) + (let ((buf (get-buffer-create mu4e-update-buffer-name)) + (win + (split-window (selected-window) + (- (window-height (selected-window)) 8)))) + (with-selected-window win + (switch-to-buffer buf) + (set-window-dedicated-p win t) + (erase-buffer) + (insert "\n") ;; FIXME -- needed so output starts + (mu4e-update-mail buf)))) + + + + + + -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; start and stopping -(defun mu4e-check-requirements () + +(defun mu4e~check-requirements () "Check for the settings required for running mu4e." (unless (and mu4e-mu-binary (file-executable-p mu4e-mu-binary)) (error "Please set `mu4e-mu-binary' to the full path to the mu @@ -542,49 +563,40 @@ process." (error "%S must start with a '/'" dir)) (unless (mu4e-create-maildir-maybe path) (error "%s (%S) does not exist" path var))))) + -(defun mu4e~proc-is-running () - "Whether the mu process is running." - (and mu4e~proc-process (eq (process-status mu4e~proc-process) 'run))) - - -(defun* mu4e (&key (hide-ui nil)) - "Start mu4e . We do this by sending a 'ping' to the mu server -process, and start the main view if the 'pong' we receive from the -server has the expected values. If keyword argument :hide-ui is -non-nil, don't show the UI." - (interactive) +(defun mu4e~start (&optional func) + "If mu4e is already running, execute function FUNC (if non-nil). Otherwise, +check various requirements, then start mu4e. When succesful, call +FUNC (if non-nil) afterwards." ;; if we're already running, simply go to the main view - (if (mu4e~proc-is-running) - (unless hide-ui - (mu4e-main-view)) - (progn - ;; otherwise, check whether all is okay; - (mu4e-check-requirements) + (if (mu4e~proc-is-running) ;; already running? + (when func + (funcall func))) ;; yup! + (progn ;; nope: check whether all is okay; + (mu4e~check-requirements) ;; explicit version checks are a bit questionable, ;; better to check for specific features - (if (< emacs-major-version 23) - (error "Emacs >= 23.x is required for mu4e") - (progn - ;; define the closure (when we receive the 'pong' - (lexical-let ((hide-ui hide-ui)) - (setq mu4e-pong-func - (lambda (version doccount) - (unless (string= version mu4e-mu-version) - (error "mu server has version %s, but we need %s" - version mu4e-mu-version)) - (unless hide-ui - (mu4e-main-view)) - (when (and mu4e-update-interval (null mu4e-update-timer)) - (setq mu4e-update-timer - (run-at-time - 0 mu4e-update-interval 'mu4e-update-mail))) - (message "Started mu4e with %d message%s in store" - doccount (if (= doccount 1) "" "s"))))) + (unless (>= emacs-major-version 23) + (error "Emacs >= 23.x is required for mu4e")) + ;; set up the 'pong' handler func + (lexical-let ((func func)) + (setq mu4e-pong-func + (lambda (version doccount) + (unless (string= version mu4e-mu-version) + (error "mu server has version %s, but we need %s" + version mu4e-mu-version)) + (when func (funcall func)) + (when (and mu4e-update-interval (null mu4e-update-timer)) + (setq mu4e-update-timer + (run-at-time + 0 mu4e-update-interval 'mu4e-update-mail))) + (message "Started mu4e with %d message%s in store" + doccount (if (= doccount 1) "" "s"))))) ;; send the ping - (mu4e~proc-ping)))))) + (mu4e~proc-ping))) -(defun mu4e-quit() +(defun mu4e~stop () "Quit the mu4e session." (interactive) (when (y-or-n-p "Are you sure you want to quit? ") @@ -594,7 +606,42 @@ non-nil, don't show the UI." (setq mu4e-update-timer nil)) (mu4e~proc-kill) (kill-buffer))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar mu4e-update-timer nil + "*internal* The mu4e update timer.") + +(defconst mu4e-update-mail-name "*mu4e-update-mail*" + "*internal* Name of the process to update mail") + +(defun mu4e-update-mail (&optional buf) + "Update mail (retrieve using `mu4e-get-mail-command' and update +the database afterwards), with output going to BUF if not nil, or +discarded if nil. After retrieving mail, update the database. Note, +function is asynchronous, returns (almost) immediately, and all the +processing takes part in the background, unless buf is non-nil." + (unless mu4e-get-mail-command + (error "`mu4e-get-mail-command' is not defined")) + (let* ((process-connection-type t) + (proc (start-process-shell-command + mu4e-update-mail-name buf mu4e-get-mail-command))) + (message "Retrieving mail...") + (set-process-sentinel proc + (lambda (proc msg) + (let* ((status (process-status proc)) + (code (process-exit-status proc)) + ;; sadly, fetchmail returns '1' when there is no mail; this is + ;; not really an error of course, but it's hard to distinguish + ;; from a genuine error + (maybe-error (or (not (eq status 'exit)) (/= code 0)))) + (message nil) + ;; there may be an error, give the user up to 5 seconds to check + (when maybe-error + (sit-for 5)) + (mu4e~proc-index mu4e-maildir) + (let ((buf (process-buffer proc))) + (when (buffer-live-p buf) + (kill-buffer buf)))))))) + diff --git a/emacs/mu4e.el b/emacs/mu4e.el index ba605689..db8091c1 100644 --- a/emacs/mu4e.el +++ b/emacs/mu4e.el @@ -66,8 +66,25 @@ ;; this one is defined in mu4e-view (setq mu4e-temp-func 'mu4e~view-temp-handler) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun mu4e () + "Start mu4e." + (interactive) + ;; start mu4e, then show the main view + (mu4e~start 'mu4e~main-view)) + +(defun mu4e-quit() + "Quit the mu4e session." + (interactive) + (when (y-or-n-p "Are you sure you want to quit? ") + (mu4e~stop))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (provide 'mu4e)