* 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)
This commit is contained in:
djcb 2012-04-26 17:59:34 +03:00
parent 5ea06f1469
commit 084ecc71d2
4 changed files with 182 additions and 135 deletions

View File

@ -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)

View File

@ -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 <path> :docid <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 <path> :docid <docid>)."
maildir this message resides in, e.g. '/drafts'.
if this works, we will receive (:info add :path <path> :docid
<docid> :fcc <path>)."
(mu4e~proc-send-command "sent path:\"%s\" maildir:\"%s\""
path maildir))

View File

@ -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))))))))

View File

@ -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)