Mercurial > emacs
diff lisp/mh-e/mh-utils.el @ 88155:d7ddb3e565de
sync with trunk
author | Henrik Enberg <henrik.enberg@telia.com> |
---|---|
date | Mon, 16 Jan 2006 00:03:54 +0000 |
parents | 0d8b17d428b5 |
children |
line wrap: on
line diff
--- a/lisp/mh-e/mh-utils.el Sun Jan 15 23:02:10 2006 +0000 +++ b/lisp/mh-e/mh-utils.el Mon Jan 16 00:03:54 2006 +0000 @@ -1,6 +1,7 @@ ;;; mh-utils.el --- MH-E code needed for both sending and reading -;; Copyright (C) 1993, 1995, 1997, 2000, 2001, 2002 Free Software Foundation, Inc. +;; Copyright (C) 1993, 1995, 1997, +;; 2000, 2001, 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. ;; Author: Bill Wohler <wohler@newt.com> ;; Maintainer: Bill Wohler <wohler@newt.com> @@ -21,8 +22,8 @@ ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs; see the file COPYING. If not, write to the -;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, -;; Boston, MA 02111-1307, USA. +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. ;;; Commentary: @@ -30,156 +31,158 @@ ;;; Change Log: -;; $Id: mh-utils.el,v 1.2 2003/02/03 20:55:30 wohler Exp $ - ;;; Code: -;; Is this XEmacs-land? Located here since needed by mh-customize.el. -(defvar mh-xemacs-flag (featurep 'xemacs) - "Non-nil means the current Emacs is XEmacs.") +;;(message "> mh-utils") +(eval-and-compile + (defvar recursive-load-depth-limit) + (if (and (boundp 'recursive-load-depth-limit) + (integerp recursive-load-depth-limit) + (< recursive-load-depth-limit 50)) + (setq recursive-load-depth-limit 50))) -(require 'cl) -(require 'gnus-util) -(require 'font-lock) -(require 'mh-loaddefs) -(require 'mh-customize) +(eval-when-compile (require 'mh-acros)) +(mh-require-cl) -(load "mm-decode" t t) ; Non-fatal dependency -(load "mm-view" t t) ; Non-fatal dependency -(load "executable" t t) ; Non-fatal dependency on - ; executable-find +(require 'font-lock) +(require 'gnus-util) +(require 'mh-buffers) +(require 'mh-customize) +(require 'mh-inc) +(require 'mouse) +(require 'sendmail) +;;(message "< mh-utils") -;; Shush the byte-compiler -(defvar font-lock-auto-fontify) -(defvar font-lock-defaults) -(defvar mark-active) -(defvar tool-bar-mode) +;; Non-fatal dependencies +(load "hl-line" t t) +(load "mm-decode" t t) +(load "mm-view" t t) +(load "tool-bar" t t) +(load "vcard" t t) + + ;;; Autoloads + (autoload 'gnus-article-highlight-citation "gnus-cite") -(autoload 'mail-header-end "sendmail") -(autoload 'Info-goto-node "info") +(autoload 'message-fetch-field "message") +(autoload 'message-tokenize-header "message") (unless (fboundp 'make-hash-table) (autoload 'make-hash-table "cl")) -;;; Set for local environment: -;;; mh-progs and mh-lib used to be set in paths.el, which tried to -;;; figure out at build time which of several possible directories MH -;;; was installed into. But if you installed MH after building Emacs, -;;; this would almost certainly be wrong, so now we do it at run time. - -(defvar mh-progs nil - "Directory containing MH commands, such as inc, repl, and rmm.") - -(defvar mh-lib nil - "Directory containing the MH library. -This directory contains, among other things, the components file.") - -(defvar mh-lib-progs nil - "Directory containing MH helper programs. -This directory contains, among other things, the mhl program.") - -(defvar mh-nmh-flag nil - "Non-nil means nmh is installed on this system instead of MH.") - -;;;###autoload -(put 'mh-progs 'risky-local-variable t) -;;;###autoload -(put 'mh-lib 'risky-local-variable t) -;;;###autoload -(put 'mh-lib-progs 'risky-local-variable t) -;;;###autoload -(put 'mh-nmh-flag 'risky-local-variable t) + ;;; CL Replacements + (defun mh-search-from-end (char string) "Return the position of last occurrence of CHAR in STRING. -If CHAR is not present in STRING then return nil. The function is used in lieu -of `search' in the CL package." +If CHAR is not present in STRING then return nil. The function is +used in lieu of `search' in the CL package." (loop for index from (1- (length string)) downto 0 when (equal (aref string index) char) return index finally return nil)) -;;; Macro to generate correct code for different emacs variants +;; Additional header fields that might someday be added: +;; "Sender: " "Reply-to: " -(defmacro mh-mark-active-p (check-transient-mark-mode-flag) - "A macro that expands into appropriate code in XEmacs and nil in GNU Emacs. -In GNU Emacs if CHECK-TRANSIENT-MARK-MODE-FLAG is non-nil then check if -variable `transient-mark-mode' is active." - (cond (mh-xemacs-flag ;XEmacs - `(and (boundp 'zmacs-regions) zmacs-regions (region-active-p))) - ((not check-transient-mark-mode-flag) ;GNU Emacs - `(and (boundp 'mark-active) mark-active)) - (t ;GNU Emacs - `(and (boundp 'transient-mark-mode) transient-mark-mode - (boundp 'mark-active) mark-active)))) + -;;; Additional header fields that might someday be added: -;;; "Sender: " "Reply-to: " +;;; Scan Line Formats (defvar mh-scan-msg-number-regexp "^ *\\([0-9]+\\)" - "Regexp to find the number of a message in a scan line. -The message's number must be surrounded with \\( \\)") + "This regular expression extracts the message number. + +It must match from the beginning of the line. Note that the +message number must be placed in a parenthesized expression as in +the default of \"^ *\\\\([0-9]+\\\\)\".") (defvar mh-scan-msg-overflow-regexp "^[?0-9][0-9]" - "Regexp to find a scan line in which the message number overflowed. -The message's number is left truncated in this case.") + "This regular expression matches overflowed message numbers.") (defvar mh-scan-msg-format-regexp "%\\([0-9]*\\)(msg)" - "Regexp to find message number width in an scan format. -The message number width must be surrounded with \\( \\).") + "This regular expression finds the message number width in a scan format. + +Note that the message number must be placed in a parenthesized +expression as in the default of \"%\\\\([0-9]*\\\\)(msg)\". This +variable is only consulted if `mh-scan-format-file' is set to +\"Use MH-E scan Format\".") (defvar mh-scan-msg-format-string "%d" - "Format string for width of the message number in a scan format. -Use `0%d' for zero-filled message numbers.") + "This is a format string for width of the message number in a scan format. + +Use \"0%d\" for zero-filled message numbers. This variable is only +consulted if `mh-scan-format-file' is set to \"Use MH-E scan +Format\".") (defvar mh-scan-msg-search-regexp "^[^0-9]*%d[^0-9]" - "Format string containing a regexp matching the scan listing for a message. -The desired message's number will be an argument to format.") + "This regular expression matches a particular message. + +It is a format string; use \"%d\" to represent the location of the +message number within the expression as in the default of +\"^[^0-9]*%d[^0-9]\".") + +(defvar mh-cmd-note 4 + "Column for notations. -(defvar mh-default-folder-for-message-function nil - "Function to select a default folder for refiling or Fcc. -If set to a function, that function is called with no arguments by -`\\[mh-refile-msg]' and `\\[mh-to-fcc]' to get a default when -prompting the user for a folder. The function is called from within a -`save-excursion', with point at the start of the message. It should -return the folder to offer as the refile or Fcc folder, as a string -with a leading `+' sign. It can also return an empty string to use no -default, or nil to calculate the default the usual way. -NOTE: This variable is not an ordinary hook; -It may not be a list of functions.") +This variable should be set with the function `mh-set-cmd-note'. +This variable may be updated dynamically if +`mh-adaptive-cmd-note-flag' is on. + +Note that columns in Emacs start with 0.") +(make-variable-buffer-local 'mh-cmd-note) + +(defvar mh-note-seq ?% + "Messages in a user-defined sequence are marked by this character. + +Messages in the \"search\" sequence are marked by this character as +well.") + + (defvar mh-show-buffer-mode-line-buffer-id " {show-%s} %d" "Format string to produce `mode-line-buffer-identification' for show buffers. -First argument is folder name. Second is message number.") -(defvar mh-cmd-note 4 - "Column to insert notation. -Use `mh-set-cmd-note' to modify it. -This value may be dynamically updated if `mh-adaptive-cmd-note-flag' is -non-nil and `mh-scan-format-file' is t. -Note that the first column is column number 0.") -(make-variable-buffer-local 'mh-cmd-note) +First argument is folder name. Second is message number.") -(defvar mh-note-seq "%" - "String whose first character is used to notate messages in a sequence.") + (defvar mh-mail-header-separator "--------" "*Line used by MH to separate headers from text in messages being composed. -This variable should not be used directly in programs. Programs should use -`mail-header-separator' instead. `mail-header-separator' is initialized to -`mh-mail-header-separator' in `mh-letter-mode'; in other contexts, you may -have to perform this initialization yourself. + +This variable should not be used directly in programs. Programs +should use `mail-header-separator' instead. +`mail-header-separator' is initialized to +`mh-mail-header-separator' in `mh-letter-mode'; in other +contexts, you may have to perform this initialization yourself. + +Do not make this a regular expression as it may be the argument +to `insert' and it is passed through `regexp-quote' before being +used by functions like `re-search-forward'.") + +(defvar mh-signature-separator-regexp "^-- $" + "This regular expression matches the signature separator. +See `mh-signature-separator'.") -Do not make this a regexp as it may be the argument to `insert' and it is -passed through `regexp-quote' before being used by functions like -`re-search-forward'.") +(defvar mh-signature-separator "-- \n" + "Text of a signature separator. + +A signature separator is used to separate the body of a message +from the signature. This can be used by user agents such as MH-E +to render the signature differently or to suppress the inclusion +of the signature in a reply. Use `mh-signature-separator-regexp' +when searching for a separator.") + +(defun mh-signature-separator-p () + "Return non-nil if buffer includes \"^-- $\"." + (save-excursion + (goto-char (point-min)) + (re-search-forward mh-signature-separator-regexp nil t))) ;; Variables for MIME display ;; Structure to keep track of MIME handles on a per buffer basis. -(defstruct (mh-buffer-data (:conc-name mh-mime-) - (:constructor mh-make-buffer-data)) +(mh-defstruct (mh-buffer-data (:conc-name mh-mime-) + (:constructor mh-make-buffer-data)) (handles ()) ; List of MIME handles (handles-cache (make-hash-table)) ; Cache to avoid multiple decodes of ; nested messages @@ -187,7 +190,8 @@ ; this number (part-index-hash (make-hash-table))) ; Avoid incrementing the part number ; for nested messages -;;; This has to be a macro, since we do: (setf (mh-buffer-data) ...) + +;; This has to be a macro, since we do: (setf (mh-buffer-data) ...) (defmacro mh-buffer-data () "Convenience macro to get the MIME data structures of the current buffer." `(gethash (current-buffer) mh-globals-hash)) @@ -195,9 +199,6 @@ (defvar mh-globals-hash (make-hash-table) "Keeps track of MIME data on a per buffer basis.") -(defvar mh-gnus-pgp-support-flag (not (not (locate-library "mml2015"))) - "Non-nil means installed Gnus has PGP support.") - (defvar mh-mm-inline-media-tests `(("image/jpeg" mm-inline-image @@ -287,30 +288,18 @@ (".*" mm-inline-text mm-readable-p)) "Alist of media types/tests saying whether types can be displayed inline.") -;; Needed by mh-comp.el and mh-mime.el -(defvar mh-mhn-compose-insert-flag nil - "Non-nil means MIME insertion was done. -Triggers an automatic call to `mh-edit-mhn' in `mh-send-letter'. -This variable is buffer-local.") -(make-variable-buffer-local 'mh-mhn-compose-insert-flag) - -(defvar mh-mml-compose-insert-flag nil - "Non-nil means that a MIME insertion was done. -This buffer-local variable is used to remember if a MIME insertion was done. -Triggers an automatic call to `mh-mml-to-mime' in `mh-send-letter'.") -(make-variable-buffer-local 'mh-mml-compose-insert-flag) - ;; Copy of `goto-address-mail-regexp' (defvar mh-address-mail-regexp - "[-a-zA-Z0-9._]+@[-a-zA-z0-9_]+\\.+[a-zA-Z0-9]+" + "[-a-zA-Z0-9._]+@\\([-a-zA-z0-9_]+\\.\\)+[a-zA-Z0-9]+" "A regular expression probably matching an e-mail address.") ;; From goto-addr.el, which we don't want to force-load on users. -;;;###mh-autoload + (defun mh-goto-address-find-address-at-point () "Find e-mail address around or before point. -Then search backwards to beginning of line for the start of an e-mail -address. If no e-mail address found, return nil." + +Then search backwards to beginning of line for the start of an +e-mail address. If no e-mail address found, return nil." (re-search-backward "[^-_A-z0-9.@]" (line-beginning-position) 'lim) (if (or (looking-at mh-address-mail-regexp) ; already at start (and (re-search-forward mh-address-mail-regexp @@ -318,9 +307,30 @@ (goto-char (match-beginning 0)))) (match-string-no-properties 0))) +(defun mh-mail-header-end () + "Substitute for `mail-header-end' that doesn't widen the buffer. + +In MH-E we frequently need to find the end of headers in nested +messages, where the buffer has been narrowed. This function works +in this situation." + (save-excursion + ;; XXX: The following replaces a call to rfc822-goto-eoh. Occasionally, + ;; mail headers that MH-E has to read contains lines of the form: + ;; From xxx@yyy Mon May 10 11:48:07 2004 + ;; In this situation, rfc822-goto-eoh doesn't go to the end of the + ;; header. The replacement allows From_ lines in the mail header. + (goto-char (point-min)) + (loop for p = (re-search-forward + "^\\([:\n]\\|[^: \t\n]+[ \t\n]\\)" nil 'move) + do (cond ((null p) (return)) + (t (goto-char (match-beginning 0)) + (unless (looking-at "From ") (return)) + (goto-char p)))) + (point))) + (defun mh-in-header-p () "Return non-nil if the point is in the header of a draft message." - (< (point) (mail-header-end))) + (< (point) (mh-mail-header-end))) (defun mh-header-field-beginning () "Move to the beginning of the current header field. @@ -342,7 +352,7 @@ Argument LIMIT limits search." (if (= (point) limit) nil - (let* ((mail-header-end (save-match-data (mail-header-end))) + (let* ((mail-header-end (save-match-data (mh-mail-header-end))) (lesser-limit (if (< mail-header-end limit) mail-header-end limit))) (when (mh-in-header-p) (set-match-data (list 1 lesser-limit)) @@ -354,7 +364,7 @@ Argument LIMIT limits search." (if (= (point) limit) nil - (let* ((mail-header-end (mail-header-end)) + (let* ((mail-header-end (mh-mail-header-end)) (lesser-limit (if (< mail-header-end limit) mail-header-end limit)) (case-fold-search t)) (when (and (< (point) mail-header-end) ;Only within header @@ -386,18 +396,30 @@ (eval-and-compile ;; Otherwise byte-compilation fails on `mh-show-font-lock-keywords-with-cite' (defvar mh-show-font-lock-keywords - '(("^\\(From:\\|Sender:\\)\\(.*\\)" (1 'default) (2 mh-show-from-face)) - (mh-header-to-font-lock (0 'default) (1 mh-show-to-face)) - (mh-header-cc-font-lock (0 'default) (1 mh-show-cc-face)) + '(("^\\(From:\\|Sender:\\)\\(.*\\)" + (1 'default) + (2 'mh-show-from)) + (mh-header-to-font-lock + (0 'default) + (1 'mh-show-to)) + (mh-header-cc-font-lock + (0 'default) + (1 'mh-show-cc)) ("^\\(Reply-To:\\|Return-Path:\\)\\(.*\\)$" - (1 'default) (2 mh-show-from-face)) - (mh-header-subject-font-lock (0 'default) (1 mh-show-subject-face)) + (1 'default) + (2 'mh-show-from)) + (mh-header-subject-font-lock + (0 'default) + (1 'mh-show-subject)) ("^\\(Apparently-To:\\|Newsgroups:\\)\\(.*\\)" - (1 'default) (2 mh-show-cc-face)) + (1 'default) + (2 'mh-show-cc)) ("^\\(In-reply-to\\|Date\\):\\(.*\\)$" - (1 'default) (2 mh-show-date-face)) - (mh-letter-header-font-lock (0 mh-show-header-face append t))) - "Additional expressions to highlight in MH-show mode.")) + (1 'default) + (2 'mh-show-date)) + (mh-letter-header-font-lock + (0 'mh-show-header append t))) + "Additional expressions to highlight in MH-Show buffers.")) (defvar mh-show-font-lock-keywords-with-cite (eval-when-compile @@ -416,15 +438,23 @@ (beginning-of-line) (end-of-line) (2 font-lock-constant-face nil t) (4 font-lock-comment-face nil t))))))) - "Additional expressions to highlight in MH-show mode.") + "Additional expressions to highlight in MH-Show buffers.") + +(defvar mh-letter-font-lock-keywords + `(,@mh-show-font-lock-keywords-with-cite + (mh-font-lock-field-data + (1 'mh-letter-header-field prepend t))) + "Additional expressions to highlight in MH-Letter buffers.") (defun mh-show-font-lock-fontify-region (beg end loudly) "Limit font-lock in `mh-show-mode' to the header. -Used when `mh-highlight-citation-p' is set to gnus, leaving the body to be -dealt with by gnus highlighting. The region between BEG and END is -given over to be fontified and LOUDLY controls if a user sees a -message about the fontification operation." - (let ((header-end (mail-header-end))) + +Used when the option `mh-highlight-citation-style' is set to +\"Gnus\", leaving the body to be dealt with by Gnus highlighting. +The region between BEG and END is given over to be fontified and +LOUDLY controls if a user sees a message about the fontification +operation." + (let ((header-end (mh-mail-header-end))) (cond ((and (< beg header-end)(< end header-end)) (font-lock-default-fontify-region beg end loudly)) @@ -433,16 +463,15 @@ (t nil)))) -;; Needed to help shush the byte-compiler. +;; Shush compiler. (if mh-xemacs-flag - (progn - (eval-and-compile - (require 'gnus) - (require 'gnus-art) - (require 'gnus-cite)))) + (eval-and-compile + (require 'gnus) + (require 'gnus-art) + (require 'gnus-cite))) (defun mh-gnus-article-highlight-citation () - "Highlight cited text in current buffer using gnus." + "Highlight cited text in current buffer using Gnus." (interactive) ;; Requiring gnus-cite should have been sufficient. However for Emacs21.1, ;; recursive-load-depth-limit is only 10, so an error occurs. Also it may be @@ -462,68 +491,56 @@ (gnus-article-highlight-citation t) (set-buffer-modified-p modified)))) + + ;;; Internal bookkeeping variables: -;; Cached value of the `Path:' component in the user's MH profile. -;; User's mail folder directory. -(defvar mh-user-path nil) - -;; An mh-draft-folder of nil means do not use a draft folder. -;; Cached value of the `Draft-Folder:' component in the user's MH profile. -;; Name of folder containing draft messages. -(defvar mh-draft-folder nil) +(defvar mh-user-path nil + "Cached value of the \"Path:\" MH profile component. +User's mail folder directory.") -;; Cached value of the `Unseen-Sequence:' component in the user's MH profile. -;; Name of the Unseen sequence. -(defvar mh-unseen-seq nil) +(defvar mh-draft-folder nil + "Cached value of the \"Draft-Folder:\" MH profile component. +Name of folder containing draft messages. +Nil means do not use a draft folder.") -;; Cached value of the `Previous-Sequence:' component in the user's MH -;; profile. -;; Name of the Previous sequence. -(defvar mh-previous-seq nil) +(defvar mh-unseen-seq nil + "Cached value of the \"Unseen-Sequence:\" MH profile component. +Name of the Unseen sequence.") -;; Cached value of the `Inbox:' component in the user's MH profile, -;; or "+inbox" if no such component. -;; Name of the Inbox folder. -(defvar mh-inbox nil) - -;; The names of ephemeral buffers have a " *mh-" prefix (so that they are -;; hidden and can be programmatically removed in mh-quit), and the variable -;; names have the form mh-temp-.*-buffer. -(defconst mh-temp-buffer " *mh-temp*") ;scratch +(defvar mh-previous-seq nil + "Cached value of the \"Previous-Sequence:\" MH profile component. +Name of the Previous sequence.") -;; The names of MH-E buffers that are not ephemeral and can be used by the -;; user (and deleted by the user when no longer needed) have a "*MH-E " prefix -;; (so they can be programmatically removed in mh-quit), and the variable -;; names have the form mh-.*-buffer. -(defconst mh-folders-buffer "*MH-E Folders*") ;folder list -(defconst mh-info-buffer "*MH-E Info*") ;version information buffer -(defconst mh-log-buffer "*MH-E Log*") ;output of MH commands and so on -(defconst mh-recipients-buffer "*MH-E Recipients*") ;killed when draft sent -(defconst mh-sequences-buffer "*MH-E Sequences*") ;sequences list +(defvar mh-inbox nil + "Cached value of the \"Inbox:\" MH profile component. +Set to \"+inbox\" if no such component. +Name of the Inbox folder.") -;; Window configuration before MH-E command. -(defvar mh-previous-window-config nil) +(defvar mh-previous-window-config nil + "Window configuration before MH-E command.") -;;Non-nil means next SPC or whatever goes to next undeleted message. -(defvar mh-page-to-next-msg-flag nil) +(defvar mh-page-to-next-msg-flag nil + "Non-nil means next SPC or whatever goes to next undeleted message.") + + ;;; Internal variables local to a folder. -;; Name of current folder, a string. -(defvar mh-current-folder nil) +(defvar mh-current-folder nil + "Name of current folder, a string.") -;; Buffer that displays message for this folder. -(defvar mh-show-buffer nil) +(defvar mh-show-buffer nil + "Buffer that displays message for this folder.") -;; Full path of directory for this folder. -(defvar mh-folder-filename nil) +(defvar mh-folder-filename nil + "Full path of directory for this folder.") -;;Number of msgs in buffer. -(defvar mh-msg-count nil) +(defvar mh-msg-count nil + "Number of msgs in buffer.") -;; If non-nil, show the message in a separate window. -(defvar mh-showing-mode nil) +(defvar mh-showing-mode nil + "If non-nil, show the message in a separate window.") (defvar mh-show-mode-map (make-sparse-keymap) "Keymap used by the show buffer.") @@ -535,35 +552,44 @@ (defun mh-logo-display () "Modify mode line to display MH-E logo." - (when (fboundp 'find-image) - (add-text-properties - 0 2 - `(display ,(or mh-logo-cache - (setq mh-logo-cache - (find-image '((:type xpm :ascent center - :file "mh-logo.xpm")))))) - (car mode-line-buffer-identification)))) + (mh-do-in-gnu-emacs + (add-text-properties + 0 2 + `(display ,(or mh-logo-cache + (setq mh-logo-cache + (mh-funcall-if-exists + find-image '((:type xpm :ascent center + :file "mh-logo.xpm")))))) + (car mode-line-buffer-identification))) + (mh-do-in-xemacs + (setq modeline-buffer-identification + (list + (if mh-modeline-glyph + (cons modeline-buffer-id-left-extent mh-modeline-glyph) + (cons modeline-buffer-id-left-extent "XEmacs%N:")) + (cons modeline-buffer-id-right-extent " %17b"))))) -;;; This holds a documentation string used by describe-mode. (defun mh-showing-mode (&optional arg) "Change whether messages should be displayed. -With arg, display messages iff ARG is positive." + +With ARG, display messages iff ARG is positive." (setq mh-showing-mode (if (null arg) (not mh-showing-mode) (> (prefix-numeric-value arg) 0)))) -;; The sequences of this folder. An alist of (seq . msgs). -(defvar mh-seq-list nil) - -;; List of displayed messages to be removed from the Unseen sequence. -(defvar mh-seen-list nil) +(defvar mh-seq-list nil + "Alist of this folder's sequences. +Elements have the form (SEQUENCE . MESSAGES).") -;; If non-nil, show buffer contains message with all headers. -;; If nil, show buffer contains message processed normally. -;; Showing message with headers or normally. -(defvar mh-showing-with-headers nil) +(defvar mh-seen-list nil + "List of displayed messages to be removed from the \"Unseen\" sequence.") +(defvar mh-showing-with-headers nil + "If non-nil, MH-Show buffer contains message with all header fields. +If nil, MH-Show buffer contains message processed normally.") + + ;;; MH-E macros @@ -571,8 +597,8 @@ "Format is (with-mh-folder-updating (SAVE-MODIFICATION-FLAG) &body BODY). Execute BODY, which can modify the folder buffer without having to worry about file locking or the read-only flag, and return its result. -If SAVE-MODIFICATION-FLAG is non-nil, the buffer's modification -flag is unchanged, otherwise it is cleared." +If SAVE-MODIFICATION-FLAG is non-nil, the buffer's modification flag +is unchanged, otherwise it is cleared." (setq save-modification-flag (car save-modification-flag)) ; CL style `(prog1 (let ((mh-folder-updating-mod-flag (buffer-modified-p)) @@ -585,7 +611,7 @@ ,@(if (not save-modification-flag) '((mh-set-folder-modified-p nil))))) -(put 'with-mh-folder-updating 'lisp-indent-hook 1) +(put 'with-mh-folder-updating 'lisp-indent-hook 'defun) (defmacro mh-in-show-buffer (show-buffer &rest body) "Format is (mh-in-show-buffer (SHOW-BUFFER) &body BODY). @@ -600,7 +626,41 @@ ,@body) (select-window mh-in-show-buffer-saved-window)))) -(put 'mh-in-show-buffer 'lisp-indent-hook 1) +(put 'mh-in-show-buffer 'lisp-indent-hook 'defun) + +(defmacro mh-do-at-event-location (event &rest body) + "Switch to the location of EVENT and execute BODY. +After BODY has been executed return to original window. The +modification flag of the buffer in the event window is +preserved." + (let ((event-window (make-symbol "event-window")) + (event-position (make-symbol "event-position")) + (original-window (make-symbol "original-window")) + (original-position (make-symbol "original-position")) + (modified-flag (make-symbol "modified-flag"))) + `(save-excursion + (let* ((,event-window + (or (mh-funcall-if-exists posn-window (event-start ,event)) + (mh-funcall-if-exists event-window ,event))) + (,event-position + (or (mh-funcall-if-exists posn-point (event-start ,event)) + (mh-funcall-if-exists event-closest-point ,event))) + (,original-window (selected-window)) + (,original-position (progn + (set-buffer (window-buffer ,event-window)) + (set-marker (make-marker) (point)))) + (,modified-flag (buffer-modified-p)) + (buffer-read-only nil)) + (unwind-protect (progn + (select-window ,event-window) + (goto-char ,event-position) + ,@body) + (set-buffer-modified-p ,modified-flag) + (goto-char ,original-position) + (set-marker ,original-position nil) + (select-window ,original-window)))))) + +(put 'mh-do-at-event-location 'lisp-indent-hook 'defun) (defmacro mh-make-seq (name msgs) "Create sequence NAME with the given MSGS." @@ -616,9 +676,12 @@ (defun mh-recenter (arg) "Like recenter but with three improvements: + - At the end of the buffer it tries to show fewer empty lines. + - operates only if the current buffer is in the selected window. (Commands like `save-some-buffers' can make this false.) + - nil ARG means recenter as if prefix argument had been given." (cond ((not (eq (get-buffer-window (current-buffer)) (selected-window))) nil) @@ -659,29 +722,28 @@ (unlock-buffer) (setq buffer-file-name nil)) -;;;###mh-autoload (defun mh-get-msg-num (error-if-no-message) "Return the message number of the displayed message. -If the argument ERROR-IF-NO-MESSAGE is non-nil, then complain if the cursor is -not pointing to a message." +If the argument ERROR-IF-NO-MESSAGE is non-nil, then complain if +the cursor is not pointing to a message." (save-excursion (beginning-of-line) (cond ((looking-at mh-scan-msg-number-regexp) - (string-to-int (buffer-substring (match-beginning 1) - (match-end 1)))) + (string-to-number (buffer-substring (match-beginning 1) + (match-end 1)))) (error-if-no-message (error "Cursor not pointing to message")) (t nil)))) (defun mh-folder-name-p (name) "Return non-nil if NAME is the name of a folder. -A name (a string or symbol) can be a folder name if it begins with \"+\"." +A name (a string or symbol) can be a folder name if it begins +with \"+\"." (if (symbolp name) (eq (aref (symbol-name name) 0) ?+) (and (> (length name) 0) (eq (aref name 0) ?+)))) - (defun mh-expand-file-name (filename &optional default) "Expand FILENAME like `expand-file-name', but also handle MH folder names. Any filename that starts with '+' is treated as a folder name. @@ -690,7 +752,6 @@ (expand-file-name (substring filename 1) mh-user-path) (expand-file-name filename default))) - (defun mh-msg-filename (msg &optional folder) "Return the file name of MSG in FOLDER (default current folder)." (expand-file-name (int-to-string msg) @@ -698,17 +759,17 @@ (mh-expand-file-name folder) mh-folder-filename))) -;;; Infrastructure to generate show-buffer functions from folder functions -;;; XEmacs does not have deactivate-mark? What is the equivalent of -;;; transient-mark-mode for XEmacs? Should we be restoring the mark in the -;;; folder buffer after the operation has been carried out. +;; Infrastructure to generate show-buffer functions from folder functions +;; XEmacs does not have deactivate-mark? What is the equivalent of +;; transient-mark-mode for XEmacs? Should we be restoring the mark in the +;; folder buffer after the operation has been carried out. (defmacro mh-defun-show-buffer (function original-function &optional dont-return) "Define FUNCTION to run ORIGINAL-FUNCTION in folder buffer. -If the buffer we start in is still visible and DONT-RETURN is nil then switch -to it after that." +If the buffer we start in is still visible and DONT-RETURN is nil +then switch to it after that." `(defun ,function () - ,(format "Calls %s from the message's folder.\n%s\nSee `%s' for more info.\n" + ,(format "Calls %s from the message's folder.\n%s\nSee \"%s\" for more info.\n" original-function (if dont-return "" "When function completes, returns to the show buffer if it is @@ -726,11 +787,13 @@ folder-buffer) (delete-other-windows)) (mh-goto-cur-msg t) - (and (fboundp 'deactivate-mark) (deactivate-mark)) + (mh-funcall-if-exists deactivate-mark) (unwind-protect (prog1 (call-interactively (function ,original-function)) (setq normal-exit t)) - (and (fboundp 'deactivate-mark) (deactivate-mark)) + (mh-funcall-if-exists deactivate-mark) + (when (eq major-mode 'mh-folder-mode) + (mh-funcall-if-exists hl-line-highlight)) (cond ((not normal-exit) (set-window-configuration config)) ,(if dont-return @@ -740,8 +803,8 @@ (get-buffer cur-buffer-name)))) (pop-to-buffer (get-buffer cur-buffer-name) nil))))))))) -;;; Generate interactive functions for the show buffer from the corresponding -;;; folder functions. +;; Generate interactive functions for the show buffer from the corresponding +;; folder functions. (mh-defun-show-buffer mh-show-previous-undeleted-msg mh-previous-undeleted-msg) (mh-defun-show-buffer mh-show-next-undeleted-msg @@ -783,7 +846,6 @@ (mh-defun-show-buffer mh-show-pack-folder mh-pack-folder) (mh-defun-show-buffer mh-show-kill-folder mh-kill-folder t) (mh-defun-show-buffer mh-show-list-folders mh-list-folders t) -(mh-defun-show-buffer mh-show-search-folder mh-search-folder t) (mh-defun-show-buffer mh-show-undo-folder mh-undo-folder) (mh-defun-show-buffer mh-show-delete-msg-from-seq mh-delete-msg-from-seq) @@ -793,8 +855,11 @@ (mh-defun-show-buffer mh-show-put-msg-in-seq mh-put-msg-in-seq) (mh-defun-show-buffer mh-show-msg-is-in-seq mh-msg-is-in-seq) (mh-defun-show-buffer mh-show-widen mh-widen) -(mh-defun-show-buffer mh-show-narrow-to-subject - mh-narrow-to-subject) +(mh-defun-show-buffer mh-show-narrow-to-subject mh-narrow-to-subject) +(mh-defun-show-buffer mh-show-narrow-to-from mh-narrow-to-from) +(mh-defun-show-buffer mh-show-narrow-to-cc mh-narrow-to-cc) +(mh-defun-show-buffer mh-show-narrow-to-range mh-narrow-to-range) +(mh-defun-show-buffer mh-show-narrow-to-to mh-narrow-to-to) (mh-defun-show-buffer mh-show-store-msg mh-store-msg) (mh-defun-show-buffer mh-show-page-digest mh-page-digest) (mh-defun-show-buffer mh-show-page-digest-backwards @@ -819,11 +884,31 @@ (mh-defun-show-buffer mh-show-thread-previous-sibling mh-thread-previous-sibling) (mh-defun-show-buffer mh-show-index-visit-folder mh-index-visit-folder t) +(mh-defun-show-buffer mh-show-toggle-tick mh-toggle-tick) +(mh-defun-show-buffer mh-show-narrow-to-tick mh-narrow-to-tick) +(mh-defun-show-buffer mh-show-junk-blacklist mh-junk-blacklist) +(mh-defun-show-buffer mh-show-junk-whitelist mh-junk-whitelist) +(mh-defun-show-buffer mh-show-index-new-messages mh-index-new-messages) +(mh-defun-show-buffer mh-show-index-ticked-messages mh-index-ticked-messages) +(mh-defun-show-buffer mh-show-index-sequenced-messages + mh-index-sequenced-messages) +(mh-defun-show-buffer mh-show-catchup mh-catchup) +(mh-defun-show-buffer mh-show-ps-print-toggle-color mh-ps-print-toggle-color) +(mh-defun-show-buffer mh-show-ps-print-toggle-faces mh-ps-print-toggle-faces) +(mh-defun-show-buffer mh-show-ps-print-msg-file mh-ps-print-msg-file) +(mh-defun-show-buffer mh-show-ps-print-msg mh-ps-print-msg) +(mh-defun-show-buffer mh-show-toggle-mime-buttons mh-toggle-mime-buttons) +(mh-defun-show-buffer mh-show-display-with-external-viewer + mh-display-with-external-viewer) -;;; Populate mh-show-mode-map + + +;;; Build mh-show-mode keymaps + (gnus-define-keys mh-show-mode-map " " mh-show-page-msg "!" mh-show-refile-or-write-again + "'" mh-show-toggle-tick "," mh-show-header-display "." mh-show-show ">" mh-show-write-message-to-file @@ -844,7 +929,6 @@ "g" mh-show-goto-msg "i" mh-show-inc-folder "k" mh-show-delete-subject-or-thread - "l" mh-show-print-msg "m" mh-show-send "n" mh-show-next-undeleted-msg "\M-n" mh-show-next-unread-msg @@ -862,19 +946,23 @@ (gnus-define-keys (mh-show-folder-map "F" mh-show-mode-map) "?" mh-prefix-help + "'" mh-index-ticked-messages "S" mh-show-sort-folder + "c" mh-show-catchup "f" mh-show-visit-folder - "i" mh-index-search "k" mh-show-kill-folder "l" mh-show-list-folders + "n" mh-index-new-messages "o" mh-show-visit-folder + "q" mh-show-index-sequenced-messages "r" mh-show-rescan-folder - "s" mh-show-search-folder + "s" mh-search "t" mh-show-toggle-threads "u" mh-show-undo-folder "v" mh-show-visit-folder) (gnus-define-keys (mh-show-sequence-map "S" mh-show-mode-map) + "'" mh-show-narrow-to-tick "?" mh-prefix-help "d" mh-show-delete-msg-from-seq "k" mh-show-delete-seq @@ -884,6 +972,21 @@ "s" mh-show-msg-is-in-seq "w" mh-show-widen) +(define-key mh-show-mode-map "I" mh-inc-spool-map) + +(gnus-define-keys (mh-show-junk-map "J" mh-show-mode-map) + "?" mh-prefix-help + "b" mh-show-junk-blacklist + "w" mh-show-junk-whitelist) + +(gnus-define-keys (mh-show-ps-print-map "P" mh-show-mode-map) + "?" mh-prefix-help + "C" mh-show-ps-print-toggle-color + "F" mh-show-ps-print-toggle-faces + "f" mh-show-ps-print-msg-file + "l" mh-show-print-msg + "p" mh-show-ps-print-msg) + (gnus-define-keys (mh-show-thread-map "T" mh-show-mode-map) "?" mh-prefix-help "u" mh-show-thread-ancestor @@ -894,8 +997,13 @@ "o" mh-show-thread-refile) (gnus-define-keys (mh-show-limit-map "/" mh-show-mode-map) + "'" mh-show-narrow-to-tick "?" mh-prefix-help + "c" mh-show-narrow-to-cc + "f" mh-show-narrow-to-from + "r" mh-show-narrow-to-range "s" mh-show-narrow-to-subject + "t" mh-show-narrow-to-to "w" mh-show-widen) (gnus-define-keys (mh-show-extract-map "X" mh-show-mode-map) @@ -913,9 +1021,11 @@ (gnus-define-keys (mh-show-mime-map "K" mh-show-mode-map) "?" mh-prefix-help "a" mh-mime-save-parts + "e" mh-show-display-with-external-viewer "v" mh-show-toggle-mime-part "o" mh-show-save-mime-part "i" mh-show-inline-mime-part + "t" mh-show-toggle-mime-buttons "\t" mh-show-next-button [backtab] mh-show-prev-button "\M-\t" mh-show-prev-button) @@ -932,7 +1042,12 @@ ["Widen from Sequence" mh-show-widen t] "--" ["Narrow to Subject Sequence" mh-show-narrow-to-subject t] + ["Narrow to Tick Sequence" mh-show-narrow-to-tick + (save-excursion + (set-buffer mh-show-folder-buffer) + (and mh-tick-seq (mh-seq-msgs (mh-find-seq mh-tick-seq))))] ["Delete Rest of Same Subject" mh-show-delete-subject t] + ["Toggle Tick Mark" mh-show-toggle-tick t] "--" ["Push State Out to MH" mh-show-update-sequences t])) @@ -979,30 +1094,38 @@ "--" ["List Folders" mh-show-list-folders t] ["Visit a Folder..." mh-show-visit-folder t] - ["Search a Folder..." mh-show-search-folder t] - ["Indexed Search..." mh-index-search t] + ["View New Messages" mh-show-index-new-messages t] + ["Search..." mh-search t] "--" ["Quit MH-E" mh-quit t])) +;; Ensure new buffers won't get this mode if default-major-mode is nil. +(put 'mh-show-mode 'mode-class 'special) -;;; Ensure new buffers won't get this mode if default-major-mode is nil. -(put 'mh-show-mode 'mode-class 'special) +;; Shush compiler. +(eval-when-compile (defvar font-lock-auto-fontify)) (define-derived-mode mh-show-mode text-mode "MH-Show" "Major mode for showing messages in MH-E.\\<mh-show-mode-map> -The value of `mh-show-mode-hook' is a list of functions to -be called, with no arguments, upon entry to this mode." + +The hook `mh-show-mode-hook' is called upon entry to this mode. + +See also `mh-folder-mode'. + +\\{mh-show-mode-map}" (set (make-local-variable 'mail-header-separator) mh-mail-header-separator) (setq paragraph-start (default-value 'paragraph-start)) (mh-show-unquote-From) (mh-show-xface) (mh-show-addr) + (setq buffer-invisibility-spec '((vanish . t) t)) + (set (make-local-variable 'line-move-ignore-invisible) t) (make-local-variable 'font-lock-defaults) ;;(set (make-local-variable 'font-lock-support-mode) nil) (cond - ((equal mh-highlight-citation-p 'font-lock) + ((equal mh-highlight-citation-style 'font-lock) (setq font-lock-defaults '(mh-show-font-lock-keywords-with-cite t))) - ((equal mh-highlight-citation-p 'gnus) + ((equal mh-highlight-citation-style 'gnus) (setq font-lock-defaults '((mh-show-font-lock-keywords) t nil nil nil (font-lock-fontify-region-function @@ -1013,9 +1136,10 @@ (if (and mh-xemacs-flag font-lock-auto-fontify) (turn-on-font-lock)) - (if (and (boundp 'tool-bar-mode) tool-bar-mode) - (set (make-local-variable 'tool-bar-map) mh-show-tool-bar-map)) + (set (make-local-variable 'tool-bar-map) mh-show-tool-bar-map) + (mh-funcall-if-exists mh-tool-bar-init :show) (when mh-decode-mime-flag + (mh-make-local-hook 'kill-buffer-hook) (add-hook 'kill-buffer-hook 'mh-mime-cleanup nil t)) (easy-menu-add mh-show-sequence-menu) (easy-menu-add mh-show-message-menu) @@ -1023,8 +1147,7 @@ (make-local-variable 'mh-show-folder-buffer) (buffer-disable-undo) (setq buffer-read-only t) - (use-local-map mh-show-mode-map) - (run-hooks 'mh-show-mode-hook)) + (use-local-map mh-show-mode-map)) (defun mh-show-addr () "Use `goto-address'." @@ -1034,92 +1157,576 @@ (if (fboundp 'goto-address) (goto-address)))) + + +;; X-Face and Face display (defvar mh-show-xface-function - (cond ((and mh-xemacs-flag (locate-library "x-face")) + (cond ((and mh-xemacs-flag (locate-library "x-face") (not (featurep 'xface))) (load "x-face" t t) - (if (fboundp 'x-face-xmas-wl-display-x-face) - #'x-face-xmas-wl-display-x-face - #'ignore)) - ((and (not mh-xemacs-flag) (>= emacs-major-version 21)) - (load "x-face-e21" t t) - (if (fboundp 'x-face-decode-message-header) - #'x-face-decode-message-header - #'ignore)) + #'mh-face-display-function) + ((>= emacs-major-version 21) + #'mh-face-display-function) (t #'ignore)) "Determine at run time what function should be called to display X-Face.") +(defvar mh-uncompface-executable + (and (fboundp 'executable-find) (executable-find "uncompface"))) + +(defun mh-face-to-png (data) + "Convert base64 encoded DATA to png image." + (with-temp-buffer + (insert data) + (ignore-errors (base64-decode-region (point-min) (point-max))) + (buffer-string))) + +(defun mh-uncompface (data) + "Run DATA through `uncompface' to generate bitmap." + (with-temp-buffer + (insert data) + (when (and mh-uncompface-executable + (equal (call-process-region (point-min) (point-max) + mh-uncompface-executable t '(t nil)) + 0)) + (mh-icontopbm) + (buffer-string)))) + +(defun mh-icontopbm () + "Elisp substitute for `icontopbm'." + (goto-char (point-min)) + (let ((end (point-max))) + (while (re-search-forward "0x\\(..\\)\\(..\\)," nil t) + (save-excursion + (goto-char (point-max)) + (insert (string-to-number (match-string 1) 16)) + (insert (string-to-number (match-string 2) 16)))) + (delete-region (point-min) end) + (goto-char (point-min)) + (insert "P4\n48 48\n"))) + +(mh-do-in-xemacs (defvar default-enable-multibyte-characters)) + +(defmacro mh-face-foreground-compat (face &optional frame inherit) + "Return the foreground color name of FACE, or nil if unspecified. +See documentation for `face-foreground' for a description of the +arguments FACE, FRAME, and INHERIT. + +Calls `face-foreground' correctly in older environments. Versions +of Emacs prior to version 22 lacked an INHERIT argument which +when t tells `face-foreground' to consider an inherited value for +the foreground if the face does not define one itself." + (if (>= emacs-major-version 22) + `(face-foreground ,face ,frame ,inherit) + `(face-foreground ,face ,frame))) + +(defmacro mh-face-background-compat (face &optional frame inherit) + "Return the background color name of face, or nil if unspecified. +See documentation for `back-foreground' for a description of the +arguments FACE, FRAME, and INHERIT. + +Calls `face-background' correctly in older environments. Versions +of Emacs prior to version 22 lacked an INHERIT argument which +when t tells `face-background' to consider an inherited value for +the background if the face does not define one itself." + (if (>= emacs-major-version 22) + `(face-background ,face ,frame ,inherit) + `(face-background ,face ,frame))) + +(defun mh-face-display-function () + "Display a Face, X-Face, or X-Image-URL header field. +If more than one of these are present, then the first one found +in this order is used." + (save-restriction + (goto-char (point-min)) + (re-search-forward "\n\n" (point-max) t) + (narrow-to-region (point-min) (point)) + (let* ((case-fold-search t) + (default-enable-multibyte-characters nil) + (face (message-fetch-field "face" t)) + (x-face (message-fetch-field "x-face" t)) + (url (message-fetch-field "x-image-url" t)) + raw type) + (cond (face (setq raw (mh-face-to-png face) + type 'png)) + (x-face (setq raw (mh-uncompface x-face) + type 'pbm)) + (url (setq type 'url)) + (t (multiple-value-setq (type raw) (mh-picon-get-image)))) + (when type + (goto-char (point-min)) + (when (re-search-forward "^from:" (point-max) t) + ;; GNU Emacs + (mh-do-in-gnu-emacs + (if (eq type 'url) + (mh-x-image-url-display url) + (mh-funcall-if-exists + insert-image (create-image + raw type t + :foreground + (mh-face-foreground-compat 'mh-show-xface nil t) + :background + (mh-face-background-compat 'mh-show-xface nil t)) + " "))) + ;; XEmacs + (mh-do-in-xemacs + (cond + ((eq type 'url) + (mh-x-image-url-display url)) + ((eq type 'png) + (when (featurep 'png) + (set-extent-begin-glyph + (make-extent (point) (point)) + (make-glyph (vector 'png ':data (mh-face-to-png face)))))) + ;; Try internal xface support if available... + ((and (eq type 'pbm) (featurep 'xface)) + (set-glyph-face + (set-extent-begin-glyph + (make-extent (point) (point)) + (make-glyph (vector 'xface ':data (concat "X-Face: " x-face)))) + 'mh-show-xface)) + ;; Otherwise try external support with x-face... + ((and (eq type 'pbm) + (fboundp 'x-face-xmas-wl-display-x-face) + (fboundp 'executable-find) (executable-find "uncompface")) + (mh-funcall-if-exists x-face-xmas-wl-display-x-face)) + ;; Picon display + ((and raw (member type '(xpm xbm gif))) + (when (featurep type) + (set-extent-begin-glyph + (make-extent (point) (point)) + (make-glyph (vector type ':data raw)))))) + (when raw (insert " ")))))))) + (defun mh-show-xface () "Display X-Face." - (when (and mh-show-use-xface-flag - (or mh-decode-mime-flag mhl-formfile + (when (and window-system mh-show-use-xface-flag + (or mh-decode-mime-flag mh-mhl-format-file mh-clean-message-header-flag)) (funcall mh-show-xface-function))) + + +;;; Picon display + +;; XXX: This should be customizable. As a side-effect of setting this +;; variable, arrange to reset mh-picon-existing-directory-list to 'unset. +(defvar mh-picon-directory-list + '("~/.picons" "~/.picons/users" "~/.picons/usenix" "~/.picons/news" + "~/.picons/domains" "~/.picons/misc" + "/usr/share/picons/" "/usr/share/picons/users" "/usr/share/picons/usenix" + "/usr/share/picons/news" "/usr/share/picons/domains" + "/usr/share/picons/misc") + "List of directories where picons reside. +The directories are searched for in the order they appear in the list.") + +(defvar mh-picon-existing-directory-list 'unset + "List of directories to search in.") + +(defvar mh-picon-cache (make-hash-table :test #'equal)) + +(defvar mh-picon-image-types + (loop for type in '(xpm xbm gif) + when (or (mh-do-in-gnu-emacs + (ignore-errors + (mh-funcall-if-exists image-type-available-p type))) + (mh-do-in-xemacs (featurep type))) + collect type)) + +(defun mh-picon-set-directory-list () + "Update `mh-picon-existing-directory-list' if needed." + (when (eq mh-picon-existing-directory-list 'unset) + (setq mh-picon-existing-directory-list + (loop for x in mh-picon-directory-list + when (file-directory-p x) collect x)))) + +(defun* mh-picon-get-image () + "Find the best possible match and return contents." + (mh-picon-set-directory-list) + (save-restriction + (let* ((from-field (ignore-errors (car (message-tokenize-header + (mh-get-header-field "from:"))))) + (from (car (ignore-errors + (mh-funcall-if-exists ietf-drums-parse-address + from-field)))) + (host (and from + (string-match "\\([^+]*\\)\\(+.*\\)?@\\(.*\\)" from) + (downcase (match-string 3 from)))) + (user (and host (downcase (match-string 1 from)))) + (canonical-address (format "%s@%s" user host)) + (cached-value (gethash canonical-address mh-picon-cache)) + (host-list (and host (delete "" (split-string host "\\.")))) + (match nil)) + (cond (cached-value (return-from mh-picon-get-image cached-value)) + ((not host-list) (return-from mh-picon-get-image nil))) + (setq match + (block 'loop + ;; u@h search + (loop for dir in mh-picon-existing-directory-list + do (loop for type in mh-picon-image-types + ;; [path]user@host + for file1 = (format "%s/%s.%s" + dir canonical-address type) + when (file-exists-p file1) + do (return-from 'loop file1) + ;; [path]user + for file2 = (format "%s/%s.%s" dir user type) + when (file-exists-p file2) + do (return-from 'loop file2) + ;; [path]host + for file3 = (format "%s/%s.%s" dir host type) + when (file-exists-p file3) + do (return-from 'loop file3))) + ;; facedb search + ;; Search order for user@foo.net: + ;; [path]net/foo/user + ;; [path]net/foo/user/face + ;; [path]net/user + ;; [path]net/user/face + ;; [path]net/foo/unknown + ;; [path]net/foo/unknown/face + ;; [path]net/unknown + ;; [path]net/unknown/face + (loop for u in (list user "unknown") + do (loop for dir in mh-picon-existing-directory-list + do (loop for x on host-list by #'cdr + for y = (mh-picon-generate-path x u dir) + do (loop for type in mh-picon-image-types + for z1 = (format "%s.%s" y type) + when (file-exists-p z1) + do (return-from 'loop z1) + for z2 = (format "%s/face.%s" + y type) + when (file-exists-p z2) + do (return-from 'loop z2))))))) + (setf (gethash canonical-address mh-picon-cache) + (mh-picon-file-contents match))))) + +(defun mh-picon-file-contents (file) + "Return details about FILE. +A list of consisting of a symbol for the type of the file and the +file contents as a string is returned. If FILE is nil, then both +elements of the list are nil." + (if (stringp file) + (with-temp-buffer + (let ((type (and (string-match ".*\\.\\(...\\)$" file) + (intern (match-string 1 file))))) + (insert-file-contents-literally file) + (values type (buffer-string)))) + (values nil nil))) + +(defun mh-picon-generate-path (host-list user directory) + "Generate the image file path. +HOST-LIST is the parsed host address of the email address, USER +the username and DIRECTORY is the directory relative to which the +path is generated." + (loop with acc = "" + for elem in host-list + do (setq acc (format "%s/%s" elem acc)) + finally return (format "%s/%s%s" directory acc user))) + + + +;; X-Image-URL display + +(defvar mh-x-image-cache-directory nil + "Directory where X-Image-URL images are cached.") +(defvar mh-x-image-scaling-function + (cond ((executable-find "convert") + 'mh-x-image-scale-with-convert) + ((and (executable-find "anytopnm") (executable-find "pnmscale") + (executable-find "pnmtopng")) + 'mh-x-image-scale-with-pnm) + (t 'ignore)) + "Function to use to scale image to proper size.") +(defvar mh-wget-executable nil) +(defvar mh-wget-choice + (or (and (setq mh-wget-executable (executable-find "wget")) 'wget) + (and (setq mh-wget-executable (executable-find "fetch")) 'fetch) + (and (setq mh-wget-executable (executable-find "curl")) 'curl))) +(defvar mh-wget-option + (cdr (assoc mh-wget-choice '((curl . "-o") (fetch . "-o") (wget . "-O"))))) +(defvar mh-x-image-temp-file nil) +(defvar mh-x-image-url nil) +(defvar mh-x-image-marker nil) +(defvar mh-x-image-url-cache-file nil) + +;; Functions to scale image to proper size +(defun mh-x-image-scale-with-pnm (input output) + "Scale image in INPUT file and write to OUTPUT file using pnm tools." + (let ((res (shell-command-to-string + (format "anytopnm < %s | pnmscale -xysize 96 48 | pnmtopng > %s" + input output)))) + (unless (equal res "") + (delete-file output)))) + +(defun mh-x-image-scale-with-convert (input output) + "Scale image in INPUT file and write to OUTPUT file using ImageMagick." + (call-process "convert" nil nil nil "-geometry" "96x48" input output)) + +;; Copy of constant from url-util.el in Emacs 22; needed by Emacs 21. +(if (not (boundp 'url-unreserved-chars)) + (defconst url-unreserved-chars + '( + ?a ?b ?c ?d ?e ?f ?g ?h ?i ?j ?k ?l ?m ?n ?o ?p ?q ?r ?s ?t ?u ?v ?w ?x ?y ?z + ?A ?B ?C ?D ?E ?F ?G ?H ?I ?J ?K ?L ?M ?N ?O ?P ?Q ?R ?S ?T ?U ?V ?W ?X ?Y ?Z + ?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 + ?- ?_ ?. ?! ?~ ?* ?' ?\( ?\)) + "A list of characters that are _NOT_ reserved in the URL spec. +This is taken from RFC 2396.")) + +;; Copy of function from url-util.el in Emacs 22; needed by Emacs 21. +(mh-defun-compat url-hexify-string (str) + "Escape characters in a string." + (mapconcat + (lambda (char) + ;; Fixme: use a char table instead. + (if (not (memq char url-unreserved-chars)) + (if (> char 255) + (error "Hexifying multibyte character %s" str) + (format "%%%02X" char)) + (char-to-string char))) + str "")) + +(defun mh-x-image-url-cache-canonicalize (url) + "Canonicalize URL. +Replace the ?/ character with a ?! character and append .png. +Also replaces special characters with `url-hexify-string' since +not all characters, such as :, are legal within Windows +filenames. See URL `http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp'." + (format "%s/%s.png" mh-x-image-cache-directory + (url-hexify-string + (with-temp-buffer + (insert url) + (mh-replace-string "/" "!") + (buffer-string))))) + +(defun mh-x-image-set-download-state (file data) + "Setup a symbolic link from FILE to DATA." + (if data + (make-symbolic-link (symbol-name data) file t) + (delete-file file))) + +(defun mh-x-image-get-download-state (file) + "Check the state of FILE by following any symbolic links." + (unless (file-exists-p mh-x-image-cache-directory) + (call-process "mkdir" nil nil nil mh-x-image-cache-directory)) + (cond ((file-symlink-p file) + (intern (file-name-nondirectory (file-chase-links file)))) + ((not (file-exists-p file)) nil) + (t 'ok))) + +(defun mh-x-image-url-fetch-image (url cache-file marker sentinel) + "Fetch and display the image specified by URL. +After the image is fetched, it is stored in CACHE-FILE. It will +be displayed in a buffer and position specified by MARKER. The +actual display is carried out by the SENTINEL function." + (if mh-wget-executable + (let ((buffer (get-buffer-create (generate-new-buffer-name + mh-temp-fetch-buffer))) + (filename (or (mh-funcall-if-exists make-temp-file "mhe-fetch") + (expand-file-name (make-temp-name "~/mhe-fetch"))))) + (save-excursion + (set-buffer buffer) + (set (make-local-variable 'mh-x-image-url-cache-file) cache-file) + (set (make-local-variable 'mh-x-image-marker) marker) + (set (make-local-variable 'mh-x-image-temp-file) filename)) + (set-process-sentinel + (start-process "*mh-x-image-url-fetch*" buffer + mh-wget-executable mh-wget-option filename url) + sentinel)) + ;; Temporary failure + (mh-x-image-set-download-state cache-file 'try-again))) + +(defun mh-x-image-display (image marker) + "Display IMAGE at MARKER." + (save-excursion + (set-buffer (marker-buffer marker)) + (let ((buffer-read-only nil) + (default-enable-multibyte-characters nil) + (buffer-modified-flag (buffer-modified-p))) + (unwind-protect + (when (and (file-readable-p image) (not (file-symlink-p image)) + (eq marker mh-x-image-marker)) + (goto-char marker) + (mh-do-in-gnu-emacs + (mh-funcall-if-exists insert-image (create-image image 'png))) + (mh-do-in-xemacs + (when (featurep 'png) + (set-extent-begin-glyph + (make-extent (point) (point)) + (make-glyph + (vector 'png ':data (with-temp-buffer + (insert-file-contents-literally image) + (buffer-string)))))))) + (set-buffer-modified-p buffer-modified-flag))))) + +(defun mh-x-image-scale-and-display (process change) + "When the wget PROCESS terminates scale and display image. +The argument CHANGE is ignored." + (when (eq (process-status process) 'exit) + (let (marker temp-file cache-filename wget-buffer) + (save-excursion + (set-buffer (setq wget-buffer (process-buffer process))) + (setq marker mh-x-image-marker + cache-filename mh-x-image-url-cache-file + temp-file mh-x-image-temp-file)) + (cond + ;; Check if we have `convert' + ((eq mh-x-image-scaling-function 'ignore) + (message "The \"convert\" program is needed to display X-Image-URL") + (mh-x-image-set-download-state cache-filename 'try-again)) + ;; Scale fetched image + ((and (funcall mh-x-image-scaling-function temp-file cache-filename) + nil)) + ;; Attempt to display image if we have it + ((file-exists-p cache-filename) + (mh-x-image-display cache-filename marker)) + ;; We didn't find the image. Should we try to display it the next time? + (t (mh-x-image-set-download-state cache-filename 'try-again))) + (ignore-errors + (set-marker marker nil) + (delete-process process) + (kill-buffer wget-buffer) + (delete-file temp-file))))) + +(defun mh-x-image-url-sane-p (url) + "Check if URL is something sensible." + (let ((len (length url))) + (cond ((< len 5) nil) + ((not (equal (substring url 0 5) "http:")) nil) + ((> len 100) nil) + (t t)))) + +(defun mh-x-image-url-display (url) + "Display image from location URL. +If the URL isn't present in the cache then it is fetched with wget." + (let* ((cache-filename (mh-x-image-url-cache-canonicalize url)) + (state (mh-x-image-get-download-state cache-filename)) + (marker (set-marker (make-marker) (point)))) + (set (make-local-variable 'mh-x-image-marker) marker) + (cond ((not (mh-x-image-url-sane-p url))) + ((eq state 'ok) + (mh-x-image-display cache-filename marker)) + ((or (not mh-wget-executable) + (eq mh-x-image-scaling-function 'ignore))) + ((eq state 'never)) + ((not mh-fetch-x-image-url) + (set-marker marker nil)) + ((eq state 'try-again) + (mh-x-image-set-download-state cache-filename nil) + (mh-x-image-url-fetch-image url cache-filename marker + 'mh-x-image-scale-and-display)) + ((and (eq mh-fetch-x-image-url 'ask) + (not (y-or-n-p (format "Fetch %s? " url)))) + (mh-x-image-set-download-state cache-filename 'never)) + ((eq state nil) + (mh-x-image-url-fetch-image url cache-filename marker + 'mh-x-image-scale-and-display))))) + + + (defun mh-maybe-show (&optional msg) "Display message at cursor, but only if in show mode. If optional arg MSG is non-nil, display that message instead." (if mh-showing-mode (mh-show msg))) -(defun mh-show (&optional message) - "Show message at cursor. -If optional argument MESSAGE is non-nil, display that message instead. -Force a two-window display with the folder window on top (size -`mh-summary-height') and the show buffer below it. -If the message is already visible, display the start of the message. +(defun mh-show (&optional message redisplay-flag) + "Display message\\<mh-folder-mode-map>. + +If the message under the cursor is already displayed, this command +scrolls to the beginning of the message. MH-E normally hides a lot of +the superfluous header fields that mailers add to a message, but if +you wish to see all of them, use the command \\[mh-header-display]. -Display of the message is controlled by setting the variables -`mh-clean-message-header-flag' and `mhl-formfile'. The default behavior is -to scroll uninteresting headers off the top of the window. -Type \"\\[mh-header-display]\" to see the message with all its headers." - (interactive) - (and mh-showing-with-headers - (or mhl-formfile mh-clean-message-header-flag) - (mh-invalidate-show-buffer)) +Two hooks can be used to control how messages are displayed. The +first hook, `mh-show-mode-hook', is called early on in the +process of the message display. It is usually used to perform +some action on the message's content. The second hook, +`mh-show-hook', is the last thing called after messages are +displayed. It's used to affect the behavior of MH-E in general or +when `mh-show-mode-hook' is too early. + +From a program, optional argument MESSAGE can be used to display an +alternative message. The optional argument REDISPLAY-FLAG forces the +redisplay of the message even if the show buffer was already +displaying the correct message. + +See the \"mh-show\" customization group for a litany of options that +control what displayed messages look like." + (interactive (list nil t)) + (when (or redisplay-flag + (and mh-showing-with-headers + (or mh-mhl-format-file mh-clean-message-header-flag))) + (mh-invalidate-show-buffer)) (mh-show-msg message)) -(defun mh-show-mouse (EVENT) +(defun mh-show-mouse (event) "Move point to mouse EVENT and show message." (interactive "e") - (mouse-set-point EVENT) + (mouse-set-point event) (mh-show)) +(defun mh-summary-height () + "Return ideal value for the variable `mh-summary-height'. +The current frame height is taken into consideration." + (or (and (fboundp 'frame-height) + (> (frame-height) 24) + (min 10 (/ (frame-height) 6))) + 4)) + (defun mh-show-msg (msg) "Show MSG. -The value of `mh-show-hook' is a list of functions to be called, with no -arguments, after the message has been displayed." + +The hook `mh-show-hook' is called after the message has been +displayed." (if (not msg) (setq msg (mh-get-msg-num t))) (mh-showing-mode t) (setq mh-page-to-next-msg-flag nil) (let ((folder mh-current-folder) + (folders (list mh-current-folder)) (clean-message-header mh-clean-message-header-flag) - (show-window (get-buffer-window mh-show-buffer))) + (show-window (get-buffer-window mh-show-buffer)) + (display-mime-buttons-flag mh-display-buttons-for-inline-parts-flag)) (if (not (eq (next-window (minibuffer-window)) (selected-window))) (delete-other-windows)) ; force ourself to the top window (mh-in-show-buffer (mh-show-buffer) + (setq mh-display-buttons-for-inline-parts-flag display-mime-buttons-flag) (if (and show-window (equal (mh-msg-filename msg folder) buffer-file-name)) (progn ;just back up to start (goto-char (point-min)) (if (not clean-message-header) (mh-start-of-uncleaned-message))) - (mh-display-msg msg folder)))) - (if (not (= (1+ (window-height)) (frame-height))) ;not horizontally split - (shrink-window (- (window-height) mh-summary-height))) - (mh-recenter nil) - (if (not (memq msg mh-seen-list)) - (setq mh-seen-list (cons msg mh-seen-list))) - (when mh-update-sequences-after-mh-show-flag - (mh-update-sequences)) - (run-hooks 'mh-show-hook)) + (mh-display-msg msg folder))) + (if (not (= (1+ (window-height)) (frame-height))) ;not horizontally split + (shrink-window (- (window-height) (or mh-summary-height + (mh-summary-height))))) + (mh-recenter nil) + ;; The following line is a nop which forces update of the scan line so + ;; that font-lock will update it (if needed)... + (mh-notate nil nil mh-cmd-note) + (if (not (memq msg mh-seen-list)) + (setq mh-seen-list (cons msg mh-seen-list))) + (when mh-update-sequences-after-mh-show-flag + (mh-update-sequences) + (when mh-index-data + (setq folders + (append (mh-index-delete-from-sequence mh-unseen-seq (list msg)) + folders))) + (when (mh-speed-flists-active-p) + (apply #'mh-speed-flists t folders))) + (run-hooks 'mh-show-hook))) (defun mh-modify (&optional message) - "Edit message at cursor. -If optional argument MESSAGE is non-nil, edit that message instead. -Force a two-window display with the folder window on top (size -`mh-summary-height') and the message editing buffer below it. + "Edit message. -The message is displayed in raw form." +There are times when you need to edit a message. For example, you +may need to fix a broken Content-Type header field. You can do +this with this command. It displays the raw message in an +editable buffer. When you are done editing, save and kill the +buffer as you would any other. + +From a program, edit MESSAGE; nil means edit current message." (interactive) (let* ((message (or message (mh-get-msg-num t))) (msg-filename (mh-msg-filename message)) @@ -1147,32 +1754,13 @@ (delete-other-windows) (switch-to-buffer edit-buffer))) -(defun mh-decode-content-transfer-encoded-message () - "Run mimencode on message body, if needed." - (let ((case-fold-search t) - (header-end (mail-header-end))) - (goto-char (point-min)) - (when (re-search-forward "^content-transfer-encoding: " header-end t) - (let ((enc (buffer-substring-no-properties (point) (line-end-position))) - cmdline) - (setq cmdline - (cond ((string-match "base64" enc) (list "-u" "-b" "-p")) - ((string-match "quoted-printable" enc) (list "-u" "-q")) - (t nil))) - (when cmdline - (beginning-of-line) - (insert "Removed-") - (setq header-end (mail-header-end)) - (goto-char (1+ header-end)) - (apply #'call-process-region (1+ header-end) (point-max) "mimencode" - t t nil cmdline)))))) - (defun mh-show-unquote-From () "Decode >From at beginning of lines for `mh-show-mode'." (save-excursion (let ((modified (buffer-modified-p)) - (case-fold-search nil)) - (goto-char (mail-header-end)) + (case-fold-search nil) + (buffer-read-only nil)) + (goto-char (mh-mail-header-end)) (while (re-search-forward "^>From" nil t) (replace-match "From")) (set-buffer-modified-p modified)))) @@ -1193,10 +1781,10 @@ (unless (mh-buffer-data) (setf (mh-buffer-data) (mh-make-buffer-data))) ;; Bind variables in folder buffer in case they are local - (let ((formfile mhl-formfile) + (let ((formfile mh-mhl-format-file) (clean-message-header mh-clean-message-header-flag) - (invisible-headers mh-invisible-headers) - (visible-headers mh-visible-headers) + (invisible-headers mh-invisible-header-fields-compiled) + (visible-headers nil) (msg-filename (mh-msg-filename msg-num folder-name)) (show-buffer mh-show-buffer) (mm-inline-media-tests mh-mm-inline-media-tests)) @@ -1215,6 +1803,8 @@ (cond ((not (equal msg-filename buffer-file-name)) (mh-unvisit-file) (setq buffer-read-only nil) + ;; Cleanup old mime handles + (mh-mime-cleanup) (erase-buffer) ;; Changing contents, so this hook needs to be reinitialized. ;; pgp.el uses this. @@ -1226,15 +1816,12 @@ (list "-form" formfile)) msg-filename) (insert-file-contents-literally msg-filename)) - (if mh-decode-content-transfer-encoded-message-flag - (mh-decode-content-transfer-encoded-message)) - ;; Cleanup old mime handles - (mh-mime-cleanup) ;; Use mm to display buffer (when (and mh-decode-mime-flag (not formfile)) (mh-add-missing-mime-version-header) (setf (mh-buffer-data) (mh-make-buffer-data)) (mh-mime-display)) + (mh-show-mode) ;; Header cleanup (goto-char (point-min)) (cond (clean-message-header @@ -1244,6 +1831,7 @@ (goto-char (point-min))) (t (mh-start-of-uncleaned-message))) + (mh-decode-message-header) ;; the parts of visiting we want to do (no locking) (or (eq buffer-undo-list t) ;don't save undo info for prev msgs (setq buffer-undo-list nil)) @@ -1253,7 +1841,6 @@ (setq buffer-backed-up nil) (auto-save-mode 1) (set-mark nil) - (mh-show-mode) (unwind-protect (when (and mh-decode-mime-flag (not formfile)) (setq buffer-read-only nil) @@ -1271,13 +1858,16 @@ (defun mh-clean-msg-header (start invisible-headers visible-headers) "Flush extraneous lines in message header. + Header is cleaned from START to the end of the message header. -INVISIBLE-HEADERS contains a regular expression specifying lines to delete -from the header. VISIBLE-HEADERS contains a regular expression specifying the -lines to display. INVISIBLE-HEADERS is ignored if VISIBLE-HEADERS is non-nil." +INVISIBLE-HEADERS contains a regular expression specifying lines +to delete from the header. VISIBLE-HEADERS contains a regular +expression specifying the lines to display. INVISIBLE-HEADERS is +ignored if VISIBLE-HEADERS is non-nil." + ;; XXX Note that MH-E no longer supports the `mh-visible-headers' + ;; variable, so this function could be trimmed of this feature too." (let ((case-fold-search t) - (after-change-functions nil)) ;Work around emacs-20 font-lock bug - ;causing an endless loop. + (buffer-read-only nil)) (save-restriction (goto-char start) (if (search-forward "\n\n" nil 'move) @@ -1297,8 +1887,10 @@ (beginning-of-line) (mh-delete-line 1) (while (looking-at "[ \t]") - (mh-delete-line 1)))) - (unlock-buffer)))) + (mh-delete-line 1))))) + (let ((mh-compose-skipped-header-fields ())) + (mh-letter-hide-all-skipped-fields)) + (unlock-buffer))) (defun mh-delete-line (lines) "Delete the next LINES lines." @@ -1306,47 +1898,53 @@ (defun mh-notate (msg notation offset) "Mark MSG with the character NOTATION at position OFFSET. -Null MSG means the message at cursor." +Null MSG means the message at cursor. +If NOTATION is nil then no change in the buffer occurs." (save-excursion (if (or (null msg) (mh-goto-msg msg t t)) (with-mh-folder-updating (t) (beginning-of-line) (forward-char offset) - (delete-char 1) - (insert notation))))) - -(defun mh-find-msg-get-num (step) - "Return the message number of the message nearest the cursor. -Jumps over non-message lines, such as inc errors. -If we have to search, STEP tells whether to search forward or backward." - (or (mh-get-msg-num nil) - (let ((msg-num nil) - (nreverses 0)) - (while (and (not msg-num) - (< nreverses 2)) - (cond ((eobp) - (setq step -1) - (setq nreverses (1+ nreverses))) - ((bobp) - (setq step 1) - (setq nreverses (1+ nreverses)))) - (forward-line step) - (setq msg-num (mh-get-msg-num nil))) - msg-num))) + (let* ((change-stack-flag + (and (equal offset + (+ mh-cmd-note mh-scan-field-destination-offset)) + (not (eq notation mh-note-seq)))) + (msg (and change-stack-flag (or msg (mh-get-msg-num nil)))) + (stack (and msg (gethash msg mh-sequence-notation-history))) + (notation (or notation (char-after)))) + (if stack + ;; The presence of the stack tells us that we don't need to + ;; notate the message, since the notation would be replaced + ;; by a sequence notation. So we will just put the notation + ;; at the bottom of the stack. If the sequence is deleted, + ;; the correct notation will be shown. + (setf (gethash msg mh-sequence-notation-history) + (reverse (cons notation (cdr (reverse stack))))) + ;; Since we don't have any sequence notations in the way, just + ;; notate the scan line. + (delete-char 1) + (insert notation)) + (when change-stack-flag + (mh-thread-update-scan-line-map msg notation offset))))))) (defun mh-goto-msg (number &optional no-error-if-no-message dont-show) - "Position the cursor at message NUMBER. -Optional non-nil second argument NO-ERROR-IF-NO-MESSAGE means return nil -instead of signaling an error if message does not exist; in this case, the -cursor is positioned near where the message would have been. -Non-nil third argument DONT-SHOW means not to show the message." + "Go to a message\\<mh-folder-mode-map>. + +You can enter the message NUMBER either before or after typing +\\[mh-goto-msg]. In the latter case, Emacs prompts you. + +In a program, optional non-nil second argument NO-ERROR-IF-NO-MESSAGE +means return nil instead of signaling an error if message does not +exist\; in this case, the cursor is positioned near where the message +would have been. Non-nil third argument DONT-SHOW means not to show +the message." (interactive "NGo to message: ") (setq number (prefix-numeric-value number)) (let ((point (point)) (return-value t)) (goto-char (point-min)) - (unless (re-search-forward (format "^[ ]*%s[^0-9]+" number) nil t) + (unless (re-search-forward (format mh-scan-msg-search-regexp number) nil t) (goto-char point) (unless no-error-if-no-message (error "No message %d" number)) @@ -1355,157 +1953,6 @@ (or dont-show (not return-value) (mh-maybe-show number)) return-value)) -(defun mh-msg-search-pat (n) - "Return a search pattern for message N in the scan listing." - (format mh-scan-msg-search-regexp n)) - -(defun mh-get-profile-field (field) - "Find and return the value of FIELD in the current buffer. -Returns nil if the field is not in the buffer." - (let ((case-fold-search t)) - (goto-char (point-min)) - (cond ((not (re-search-forward (format "^%s" field) nil t)) nil) - ((looking-at "[\t ]*$") nil) - (t - (re-search-forward "[\t ]*\\([^\t \n].*\\)$" nil t) - (let ((start (match-beginning 1))) - (end-of-line) - (buffer-substring start (point))))))) - -(defvar mail-user-agent) -(defvar read-mail-command) - -(defvar mh-find-path-run nil - "Non-nil if `mh-find-path' has been run already.") - -(defun mh-find-path () - "Set `mh-progs', `mh-lib', and `mh-lib-progs' variables. -Set `mh-user-path', `mh-draft-folder', `mh-unseen-seq', `mh-previous-seq', -`mh-inbox' from user's MH profile. -The value of `mh-find-path-hook' is a list of functions to be called, with no -arguments, after these variable have been set." - (mh-find-progs) - (unless mh-find-path-run - (setq mh-find-path-run t) - (setq read-mail-command 'mh-rmail) - (setq mail-user-agent 'mh-e-user-agent)) - (save-excursion - ;; Be sure profile is fully expanded before switching buffers - (let ((profile (expand-file-name (or (getenv "MH") "~/.mh_profile")))) - (set-buffer (get-buffer-create mh-temp-buffer)) - (setq buffer-offer-save nil) ;for people who set default to t - (erase-buffer) - (condition-case err - (insert-file-contents profile) - (file-error - (mh-install profile err))) - (setq mh-user-path (mh-get-profile-field "Path:")) - (if (not mh-user-path) - (setq mh-user-path "Mail")) - (setq mh-user-path - (file-name-as-directory - (expand-file-name mh-user-path (expand-file-name "~")))) - (setq mh-draft-folder (mh-get-profile-field "Draft-Folder:")) - (if mh-draft-folder - (progn - (if (not (mh-folder-name-p mh-draft-folder)) - (setq mh-draft-folder (format "+%s" mh-draft-folder))) - (if (not (file-exists-p (mh-expand-file-name mh-draft-folder))) - (error "Draft folder \"%s\" not found. Create it and try again" - (mh-expand-file-name mh-draft-folder))))) - (setq mh-inbox (mh-get-profile-field "Inbox:")) - (cond ((not mh-inbox) - (setq mh-inbox "+inbox")) - ((not (mh-folder-name-p mh-inbox)) - (setq mh-inbox (format "+%s" mh-inbox)))) - (setq mh-unseen-seq (mh-get-profile-field "Unseen-Sequence:")) - (if mh-unseen-seq - (setq mh-unseen-seq (intern mh-unseen-seq)) - (setq mh-unseen-seq 'unseen)) ;old MH default? - (setq mh-previous-seq (mh-get-profile-field "Previous-Sequence:")) - (if mh-previous-seq - (setq mh-previous-seq (intern mh-previous-seq))) - (run-hooks 'mh-find-path-hook)))) - -(defun mh-file-command-p (file) - "Return t if file FILE is the name of a executable regular file." - (and (file-regular-p file) (file-executable-p file))) - -(defun mh-find-progs () - "Find the directories for the installed MH/nmh binaries and config files. -Set the `mh-progs' and `mh-lib', and `mh-lib-progs' variables to the -directory names and set `mh-nmh-flag' if we detect nmh instead of MH." - (unless (and mh-progs mh-lib mh-lib-progs) - (let ((path (or (mh-path-search exec-path "mhparam") - (mh-path-search '("/usr/local/nmh/bin" ; nmh default - "/usr/local/bin/mh/" - "/usr/local/mh/" - "/usr/bin/mh/" ;Ultrix 4.2, Linux - "/usr/new/mh/" ;Ultrix <4.2 - "/usr/contrib/mh/bin/" ;BSDI - "/usr/pkg/bin/" ; NetBSD - "/usr/local/bin/" - ) - "mhparam")))) - (if (not path) - (error "Unable to find the `mhparam' command")) - (save-excursion - (let ((tmp-buffer (get-buffer-create mh-temp-buffer))) - (set-buffer tmp-buffer) - (unwind-protect - (progn - (call-process (expand-file-name "mhparam" path) - nil '(t nil) nil "libdir" "etcdir") - (goto-char (point-min)) - (if (search-forward-regexp "^libdir:\\s-\\(\\S-+\\)\\s-*$" - nil t) - (setq mh-lib-progs (match-string 1) - mh-lib mh-lib-progs - mh-progs path)) - (goto-char (point-min)) - (if (search-forward-regexp "^etcdir:\\s-\\(\\S-+\\)\\s-*$" - nil t) - (setq mh-lib (match-string 1) - mh-nmh-flag t))) - (kill-buffer tmp-buffer)))) - (unless (and mh-progs mh-lib mh-lib-progs) - (error "Unable to determine paths from `mhparam' command"))))) - -(defun mh-path-search (path file) - "Search PATH, a list of directory names, for FILE. -Returns the element of PATH that contains FILE, or nil if not found." - (while (and path - (not (funcall 'mh-file-command-p - (expand-file-name file (car path))))) - (setq path (cdr path))) - (car path)) - -(defvar mh-no-install nil) ;do not run install-mh - -(defun mh-install (profile error-val) - "Initialize the MH environment. -This is called if we fail to read the PROFILE file. ERROR-VAL is the error -that made this call necessary." - (if (or (getenv "MH") - (file-exists-p profile) - mh-no-install) - (signal (car error-val) - (list (format "Cannot read MH profile \"%s\"" profile) - (car (cdr (cdr error-val)))))) - ;; The "install-mh" command will output a short note which - ;; mh-exec-cmd will display to the user. - ;; The MH 5 version of install-mh might try prompt the user - ;; for information, which would fail here. - (mh-exec-cmd (expand-file-name "install-mh" mh-lib-progs) "-auto") - ;; now try again to read the profile file - (erase-buffer) - (condition-case err - (insert-file-contents profile) - (file-error - (signal (car err) ;re-signal with more specific msg - (list (format "Cannot read MH profile \"%s\"" profile) - (car (cdr (cdr err)))))))) - (defun mh-set-folder-modified-p (flag) "Mark current folder as modified or unmodified according to FLAG." (set-buffer-modified-p flag)) @@ -1521,9 +1968,9 @@ (defun mh-update-scan-format (fmt width) "Return a scan format with the (msg) width in the FMT replaced with WIDTH. -The message number width portion of the format is discovered using -`mh-scan-msg-format-regexp'. Its replacement is controlled with -`mh-scan-msg-format-string'." +The message number width portion of the format is discovered +using `mh-scan-msg-format-regexp'. Its replacement is controlled +with `mh-scan-msg-format-string'." (or (and (string-match mh-scan-msg-format-regexp fmt) (let ((begin (match-beginning 1)) @@ -1533,8 +1980,8 @@ (substring fmt end)))) fmt)) -(defun mh-message-number-width (folder) - "Return the widest message number in this FOLDER." +(defun mh-msg-num-width (folder) + "Return the width of the largest message number in this FOLDER." (or mh-progs (mh-find-path)) (let ((tmp-buffer (get-buffer-create mh-temp-buffer)) (width 0)) @@ -1542,7 +1989,7 @@ (set-buffer tmp-buffer) (erase-buffer) (apply 'call-process - (expand-file-name "scan" mh-progs) nil '(t nil) nil + (expand-file-name mh-scan-prog mh-progs) nil '(t nil) nil (list folder "last" "-format" "%(msg)")) (goto-char (point-min)) (if (re-search-forward mh-scan-msg-number-regexp nil 0 1) @@ -1552,13 +1999,15 @@ (defun mh-add-msgs-to-seq (msgs seq &optional internal-flag dont-annotate-flag) "Add MSGS to SEQ. -Remove duplicates and keep sequence sorted. If optional INTERNAL-FLAG is -non-nil, do not mark the message in the scan listing or inform MH of the -addition. + +Remove duplicates and keep sequence sorted. If optional +INTERNAL-FLAG is non-nil, do not mark the message in the scan +listing or inform MH of the addition. -If DONT-ANNOTATE-FLAG is non-nil then the annotations in the folder buffer are -not updated." - (let ((entry (mh-find-seq seq))) +If DONT-ANNOTATE-FLAG is non-nil then the annotations in the +folder buffer are not updated." + (let ((entry (mh-find-seq seq)) + (internal-seq-flag (mh-internal-seq seq))) (if (and msgs (atom msgs)) (setq msgs (list msgs))) (if (null entry) (setq mh-seq-list @@ -1566,10 +2015,12 @@ mh-seq-list)) (if msgs (setcdr entry (mh-canonicalize-sequence (append msgs (mh-seq-msgs entry)))))) - (cond ((not internal-flag) - (mh-add-to-sequence seq msgs) - (unless dont-annotate-flag - (mh-notate-seq seq mh-note-seq (1+ mh-cmd-note))))))) + (unless internal-flag + (mh-add-to-sequence seq msgs) + (when (not dont-annotate-flag) + (mh-iterate-on-range msg msgs + (unless (memq msg (cdr entry)) + (mh-add-sequence-notation msg internal-seq-flag))))))) (defun mh-canonicalize-sequence (msgs) "Sort MSGS in decreasing order and remove duplicates." @@ -1582,28 +2033,89 @@ sorted-msgs)) (defvar mh-sub-folders-cache (make-hash-table :test #'equal)) +(defvar mh-current-folder-name nil) +(defvar mh-flists-partial-line "") +(defvar mh-flists-process nil) + +;; Initialize mh-sub-folders-cache... +(defun mh-collect-folder-names () + "Collect folder names by running \"folders\"." + (unless mh-flists-process + (setq mh-flists-process + (mh-exec-cmd-daemon "folders" 'mh-collect-folder-names-filter + "-recurse" "-fast")))) + +(defun mh-collect-folder-names-filter (process output) + "Read folder names. +PROCESS is the flists process that was run to collect folder +names and the function is called when OUTPUT is available." + (let ((position 0) + (prevailing-match-data (match-data)) + line-end folder) + (unwind-protect + (while (setq line-end (string-match "\n" output position)) + (setq folder (format "+%s%s" + mh-flists-partial-line + (substring output position line-end))) + (setq mh-flists-partial-line "") + (unless (equal (aref folder 1) ?.) + (mh-populate-sub-folders-cache folder)) + (setq position (1+ line-end))) + (set-match-data prevailing-match-data)) + (setq mh-flists-partial-line (substring output position)))) + +(defun mh-populate-sub-folders-cache (folder) + "Tell `mh-sub-folders-cache' about FOLDER." + (let* ((last-slash (mh-search-from-end ?/ folder)) + (child1 (substring folder (1+ (or last-slash 0)))) + (parent (and last-slash (substring folder 0 last-slash))) + (parent-slash (and parent (mh-search-from-end ?/ parent))) + (child2 (and parent (substring parent (1+ (or parent-slash 0))))) + (grand-parent (and parent-slash (substring parent 0 parent-slash))) + (cache-entry (gethash parent mh-sub-folders-cache))) + (unless (loop for x in cache-entry when (equal (car x) child1) return t + finally return nil) + (push (list child1) cache-entry) + (setf (gethash parent mh-sub-folders-cache) + (sort cache-entry (lambda (x y) (string< (car x) (car y))))) + (when parent + (loop for x in (gethash grand-parent mh-sub-folders-cache) + when (equal (car x) child2) + do (progn (setf (cdr x) t) (return))))))) (defun mh-normalize-folder-name (folder &optional empty-string-okay dont-remove-trailing-slash) "Normalizes FOLDER name. -Makes sure that two '/' characters never occur next to each other. Also all -occurrences of \"..\" and \".\" are suitably processed. So \"+inbox/../news\" -will be normalized to \"+news\". + +Makes sure that two '/' characters never occur next to each +other. Also all occurrences of \"..\" and \".\" are suitably +processed. So \"+inbox/../news\" will be normalized to \"+news\". -If optional argument EMPTY-STRING-OKAY is nil then a '+' is added at the -front if FOLDER lacks one. If non-nil and FOLDER is the empty string then -nothing is added. +If optional argument EMPTY-STRING-OKAY is nil then a '+' is added +at the front if FOLDER lacks one. If non-nil and FOLDER is the +empty string then nothing is added. -If optional argument DONT-REMOVE-TRAILING-SLASH is non-nil then a trailing '/' -if present is retained (if present), otherwise it is removed." +If optional argument DONT-REMOVE-TRAILING-SLASH is non-nil then a +trailing '/' if present is retained (if present), otherwise it is +removed." (when (stringp folder) ;; Replace two or more consecutive '/' characters with a single '/' (while (string-match "//" folder) (setq folder (replace-match "/" nil t folder))) (let* ((length (length folder)) (trailing-slash-present (and (> length 0) - (equal (aref folder (1- length)) ?/)))) - (let ((components (split-string folder "/")) + (equal (aref folder (1- length)) ?/))) + (leading-slash-present (and (> length 0) + (equal (aref folder 0) ?/)))) + (when (and (> length 0) (equal (aref folder 0) ?@) + (stringp mh-current-folder-name)) + (setq folder (format "%s/%s/" mh-current-folder-name + (substring folder 1)))) + ;; XXX: Purge empty strings from the list that split-string returns. In + ;; XEmacs, (split-string "+foo/" "/") returns ("+foo" "") while in GNU + ;; Emacs it returns ("+foo"). In the code it is assumed that the + ;; components list has no empty strings. + (let ((components (delete "" (split-string folder "/"))) (result ())) ;; Remove .. and . from the pathname. (dolist (component components) @@ -1618,19 +2130,60 @@ ;; Remove trailing '/' if needed. (unless (and trailing-slash-present dont-remove-trailing-slash) (when (not (equal folder "")) - (setq folder (substring folder 0 (1- (length folder)))))))) + (setq folder (substring folder 0 (1- (length folder)))))) + (when leading-slash-present + (setq folder (concat "/" folder))))) (cond ((and empty-string-okay (equal folder ""))) ((equal folder "") (setq folder "+")) ((not (equal (aref folder 0) ?+)) (setq folder (concat "+" folder))))) folder) +(defmacro mh-children-p (folder) + "Return t if FOLDER from sub-folders cache has children. +The car of folder is the name, and the cdr is either t or some +sort of count that I do not understand. It's too small to be the +number of messages in the sub-folders and too large to be the +number of sub-folders. XXX" + `(if (cdr ,folder) + t + nil)) + +(defun mh-folder-list (folder) + "Return FOLDER and its descendents. +Returns a list of strings. For example, + + '(\"inbox\" \"lists\" \"lists/mh-e\"). + +If folder is nil, then all folders are considered. Respects the +value of `mh-recursive-folders-flag'. If this flag is nil, and +the sub-folders have not been explicitly viewed, then they will +not be returned." + (let ((folder-list)) + ;; Normalize folder. Strip leading +. Add trailing slash. If no + ;; folder is specified, ensure it is nil to ensure we get the + ;; top-level folders; otherwise mh-sub-folders returns all the + ;; files in / if given an empty string or +. + (when folder + (setq folder (replace-regexp-in-string "^\+" "" folder)) + (setq folder (replace-regexp-in-string "/*$" "/" folder)) + (if (equal folder "") + (setq folder nil))) + (loop for f in (mh-sub-folders folder) do + (setq folder-list (append folder-list (list (concat folder (car f))))) + (if (mh-children-p f) + (setq folder-list + (append folder-list + (mh-folder-list (concat folder (car f))))))) + folder-list)) + (defun mh-sub-folders (folder &optional add-trailing-slash-flag) "Find the subfolders of FOLDER. -The function avoids running folders unnecessarily by caching the results of -the actual folders call. +The function avoids running folders unnecessarily by caching the +results of the actual folders call. -If optional argument ADD-TRAILING-SLASH-FLAG is non-nil then a slash is added -to each of the sub-folder names that may have nested folders within them." +If optional argument ADD-TRAILING-SLASH-FLAG is non-nil then a +slash is added to each of the sub-folder names that may have +nested folders within them." (let* ((folder (mh-normalize-folder-name folder)) (match (gethash folder mh-sub-folders-cache 'no-result)) (sub-folders (cond ((eq match 'no-result) @@ -1645,8 +2198,8 @@ (defun mh-sub-folders-actual (folder) "Execute the command folders to return the sub-folders of FOLDER. -Filters out the folder names that start with \".\" so that directories that -aren't usually mail folders are hidden." +Filters out the folder names that start with \".\" so that +directories that aren't usually mail folders are hidden." (let ((arg-list `(,(expand-file-name "folders" mh-progs) nil (t nil) nil "-noheader" "-norecurse" "-nototal" ,@(if (stringp folder) (list folder) ()))) @@ -1662,12 +2215,15 @@ (goto-char (point-min)) (while (not (and (eolp) (bolp))) (goto-char (line-end-position)) - (let ((has-pos (search-backward " has " (line-beginning-position) t))) + (let ((start-pos (line-beginning-position)) + (has-pos (search-backward " has " (line-beginning-position) t))) (when (integerp has-pos) (while (equal (char-after has-pos) ? ) (decf has-pos)) (incf has-pos) - (let* ((name (buffer-substring (line-beginning-position) has-pos)) + (while (equal (char-after start-pos) ? ) + (incf start-pos)) + (let* ((name (buffer-substring start-pos has-pos)) (first-char (aref name 0)) (last-char (aref name (1- (length name))))) (unless (member first-char '(?. ?# ?,)) @@ -1690,13 +2246,15 @@ (defun mh-remove-from-sub-folders-cache (folder) "Remove FOLDER and its parent from `mh-sub-folders-cache'. -FOLDER should be unconditionally removed from the cache. Also the last ancestor -of FOLDER present in the cache must be removed as well. +FOLDER should be unconditionally removed from the cache. Also the +last ancestor of FOLDER present in the cache must be removed as +well. -To see why this is needed assume we have a folder +foo which has a single -sub-folder qux. Now we create the folder +foo/bar/baz. Here we will need to -invalidate the cached sub-folders of +foo, otherwise completion on +foo won't -tell us about the option +foo/bar!" +To see why this is needed assume we have a folder +foo which has +a single sub-folder qux. Now we create the folder +foo/bar/baz. +Here we will need to invalidate the cached sub-folders of +foo, +otherwise completion on +foo won't tell us about the option ++foo/bar!" (remhash folder mh-sub-folders-cache) (block ancestor-found (let ((parent folder) @@ -1712,15 +2270,36 @@ (remhash nil mh-sub-folders-cache)))) (defvar mh-folder-hist nil) -(defvar mh-speed-folder-map) + +;; Shush compiler. +(eval-when-compile + (defvar mh-speed-folder-map) + (defvar mh-speed-flists-cache)) + +(defvar mh-allow-root-folder-flag nil + "Non-nil means \"+\" is an acceptable folder name. +This variable is used to communicate with +`mh-folder-completion-function'. That function can have exactly +three arguments so we bind this variable to t or nil. + +This variable should never be set.") + (defvar mh-folder-completion-map (copy-keymap minibuffer-local-completion-map)) -(define-key mh-folder-completion-map " " 'minibuffer-complete) +(define-key mh-folder-completion-map " " 'minibuffer-complete) ;Why??? + +(defvar mh-speed-flists-inhibit-flag nil) + +(defun mh-speed-flists-active-p () + "Check if speedbar is running with message counts enabled." + (and (featurep 'mh-speed) + (not mh-speed-flists-inhibit-flag) + (> (hash-table-count mh-speed-flists-cache) 0))) (defun mh-folder-completion-function (name predicate flag) "Programmable completion for folder names. -NAME is the partial folder name that has been input. PREDICATE if non-nil is a -function that is used to filter the possible choices and FLAG determines -whether the completion is over." +NAME is the partial folder name that has been input. PREDICATE if +non-nil is a function that is used to filter the possible choices +and FLAG determines whether the completion is over." (let* ((orig-name name) (name (mh-normalize-folder-name name nil t)) (last-slash (mh-search-from-end ?/ name)) @@ -1747,14 +2326,21 @@ (all-completions remainder (mh-sub-folders last-complete t) predicate)) ((eq flag 'lambda) - (file-exists-p - (concat mh-user-path - (substring (mh-normalize-folder-name name) 1))))))) + (let ((path (concat mh-user-path + (substring (mh-normalize-folder-name name) 1)))) + (cond (mh-allow-root-folder-flag (file-exists-p path)) + ((equal path mh-user-path) nil) + (t (file-exists-p path)))))))) -(defun mh-folder-completing-read (prompt default) - "Read folder name with PROMPT and default result DEFAULT." +(defun mh-folder-completing-read (prompt default allow-root-folder-flag) + "Read folder name with PROMPT and default result DEFAULT. +If ALLOW-ROOT-FOLDER-FLAG is non-nil then \"+\" is allowed to be +a folder name corresponding to `mh-user-path'." (mh-normalize-folder-name - (let ((minibuffer-local-completion-map mh-folder-completion-map)) + (let ((minibuffer-completing-file-name t) + (completion-root-regexp "^[+/]") + (minibuffer-local-completion-map mh-folder-completion-map) + (mh-allow-root-folder-flag allow-root-folder-flag)) (completing-read prompt 'mh-folder-completion-function nil nil nil 'mh-folder-hist default)) t)) @@ -1762,21 +2348,23 @@ (defun mh-prompt-for-folder (prompt default can-create &optional default-string allow-root-folder-flag) "Prompt for a folder name with PROMPT. -Returns the folder's name as a string. DEFAULT is used if the folder exists -and the user types return. If the CAN-CREATE flag is t, then a folder is -created if it doesn't already exist. If optional argument DEFAULT-STRING is -non-nil, use it in the prompt instead of DEFAULT. If ALLOW-ROOT-FOLDER-FLAG is -non-nil then the function will accept the folder +, which means all folders -when used in searching." +Returns the folder's name as a string. DEFAULT is used if the +folder exists and the user types return. If the CAN-CREATE flag +is t, then a folder is created if it doesn't already exist. If +optional argument DEFAULT-STRING is non-nil, use it in the prompt +instead of DEFAULT. If ALLOW-ROOT-FOLDER-FLAG is non-nil then the +function will accept the folder +, which means all folders when +used in searching." (if (null default) (setq default "")) - (let* ((default-string (cond (default-string (format " [%s]? " - default-string)) - ((equal "" default) "? ") - (t (format " [%s]? " default)))) - (prompt (format "%s folder%s" prompt default-string)) + (let* ((default-string (cond (default-string (format " (default %s)" default-string)) + ((equal "" default) "") + (t (format " (default %s)" default)))) + (prompt (format "%s folder%s: " prompt default-string)) + (mh-current-folder-name mh-current-folder) read-name folder-name) - (while (and (setq read-name (mh-folder-completing-read prompt default)) + (while (and (setq read-name (mh-folder-completing-read + prompt default allow-root-folder-flag)) (equal read-name "") (equal default ""))) (cond ((or (equal read-name "") @@ -1790,9 +2378,18 @@ (cond ((and (> (length folder-name) 0) (eq (aref folder-name (1- (length folder-name))) ?/)) (setq folder-name (substring folder-name 0 -1)))) + (let* ((last-slash (mh-search-from-end ?/ folder-name)) + (parent (and last-slash (substring folder-name 0 last-slash))) + (child (if last-slash + (substring folder-name (1+ last-slash)) + (substring folder-name 1)))) + (unless (member child + (mapcar #'car (gethash parent mh-sub-folders-cache))) + (mh-remove-from-sub-folders-cache folder-name))) (let ((new-file-flag (not (file-exists-p (mh-expand-file-name folder-name))))) (cond ((and new-file-flag + can-create (y-or-n-p (format "Folder %s does not exist. Create it? " folder-name))) @@ -1803,160 +2400,15 @@ (mh-speed-add-folder folder-name)) (message "Creating %s...done" folder-name)) (new-file-flag - (error "Folder %s is not created" folder-name)) + (error "Folder %s does not exist" folder-name)) ((not (file-directory-p (mh-expand-file-name folder-name))) - (error "\"%s\" is not a directory" + (error "%s is not a directory" (mh-expand-file-name folder-name))))) folder-name)) -;;; Issue commands to MH. - -(defun mh-exec-cmd (command &rest args) - "Execute mh-command COMMAND with ARGS. -The side effects are what is desired. -Any output is assumed to be an error and is shown to the user. -The output is not read or parsed by MH-E." - (save-excursion - (set-buffer (get-buffer-create mh-log-buffer)) - (erase-buffer) - (apply 'call-process - (expand-file-name command mh-progs) nil t nil - (mh-list-to-string args)) - (if (> (buffer-size) 0) - (save-window-excursion - (switch-to-buffer-other-window mh-log-buffer) - (sit-for 5))))) - -(defun mh-exec-cmd-error (env command &rest args) - "In environment ENV, execute mh-command COMMAND with ARGS. -ENV is nil or a string of space-separated \"var=value\" elements. -Signals an error if process does not complete successfully." - (save-excursion - (set-buffer (get-buffer-create mh-temp-buffer)) - (erase-buffer) - (let ((status - (if env - ;; the shell hacks necessary here shows just how broken Unix is - (apply 'call-process "/bin/sh" nil t nil "-c" - (format "%s %s ${1+\"$@\"}" - env - (expand-file-name command mh-progs)) - command - (mh-list-to-string args)) - (apply 'call-process - (expand-file-name command mh-progs) nil t nil - (mh-list-to-string args))))) - (mh-handle-process-error command status)))) - -(defun mh-exec-cmd-daemon (command filter &rest args) - "Execute MH command COMMAND in the background. - -If FILTER is non-nil then it is used to process the output otherwise the -default filter `mh-process-daemon' is used. See `set-process-filter' for more -details of FILTER. - -ARGS are passed to COMMAND as command line arguments." - (save-excursion - (set-buffer (get-buffer-create mh-log-buffer)) - (erase-buffer)) - (let* ((process-connection-type nil) - (process (apply 'start-process - command nil - (expand-file-name command mh-progs) - (mh-list-to-string args)))) - (set-process-filter process (or filter 'mh-process-daemon)))) - -(defun mh-process-daemon (process output) - "PROCESS daemon that puts OUTPUT into a temporary buffer. -Any output from the process is displayed in an asynchronous pop-up window." - (set-buffer (get-buffer-create mh-log-buffer)) - (insert-before-markers output) - (display-buffer mh-log-buffer)) + -(defun mh-exec-cmd-quiet (raise-error command &rest args) - "Signal RAISE-ERROR if COMMAND with ARGS fails. -Execute MH command COMMAND with ARGS. ARGS is a list of strings. -Return at start of mh-temp buffer, where output can be parsed and used. -Returns value of `call-process', which is 0 for success, unless RAISE-ERROR is -non-nil, in which case an error is signaled if `call-process' returns non-0." - (set-buffer (get-buffer-create mh-temp-buffer)) - (erase-buffer) - (let ((value - (apply 'call-process - (expand-file-name command mh-progs) nil t nil - args))) - (goto-char (point-min)) - (if raise-error - (mh-handle-process-error command value) - value))) - -(defun mh-profile-component (component) - "Return COMPONENT value from mhparam, or nil if unset." - (save-excursion - (mh-exec-cmd-quiet nil "mhparam" "-components" component) - (mh-get-profile-field (concat component ":")))) - -(defun mh-exchange-point-and-mark-preserving-active-mark () - "Put the mark where point is now, and point where the mark is now. -This command works even when the mark is not active, and preserves whether the -mark is active or not." - (interactive nil) - (let ((is-active (and (boundp 'mark-active) mark-active))) - (let ((omark (mark t))) - (if (null omark) - (error "No mark set in this buffer")) - (set-mark (point)) - (goto-char omark) - (if (boundp 'mark-active) - (setq mark-active is-active)) - nil))) - -(defun mh-exec-cmd-output (command display &rest args) - "Execute MH command COMMAND with DISPLAY flag and ARGS. -Put the output into buffer after point. Set mark after inserted text. -Output is expected to be shown to user, not parsed by MH-E." - (push-mark (point) t) - (apply 'call-process - (expand-file-name command mh-progs) nil t display - (mh-list-to-string args)) - - ;; The following is used instead of 'exchange-point-and-mark because the - ;; latter activates the current region (between point and mark), which - ;; turns on highlighting. So prior to this bug fix, doing "inc" would - ;; highlight a region containing the new messages, which is undesirable. - ;; The bug wasn't seen in emacs21 but still occurred in XEmacs21.4. - (mh-exchange-point-and-mark-preserving-active-mark)) - -(defun mh-exec-lib-cmd-output (command &rest args) - "Execute MH library command COMMAND with ARGS. -Put the output into buffer after point. Set mark after inserted text." - (apply 'mh-exec-cmd-output (expand-file-name command mh-lib-progs) nil args)) - -(defun mh-handle-process-error (command status) - "Raise error if COMMAND returned non-zero STATUS, otherwise return STATUS. -STATUS is return value from `call-process'. -Program output is in current buffer. -If output is too long to include in error message, display the buffer." - (cond ((eq status 0) ;success - status) - ((stringp status) ;kill string - (error "%s: %s" command status)) - (t ;exit code - (cond - ((= (buffer-size) 0) ;program produced no error message - (error "%s: exit code %d" command status)) - (t - ;; will error message fit on one line? - (goto-line 2) - (if (and (< (buffer-size) (frame-width)) - (eobp)) - (error "%s" - (buffer-substring 1 (progn (goto-char 1) - (end-of-line) - (point)))) - (display-buffer (current-buffer)) - (error "%s failed with status %d. See error message in other window" - command status))))))) +;;; List and string manipulation (defun mh-list-to-string (l) "Flatten the list L and make every element of the new list into a string." @@ -1976,15 +2428,24 @@ ((listp (car l)) (setq new-list (nconc (mh-list-to-string-1 (car l)) new-list))) - (t (error "Bad element in mh-list-to-string: %s" (car l)))) + (t (error "Bad element in `mh-list-to-string': %s" (car l)))) (setq l (cdr l))) new-list)) +(defun mh-replace-string (old new) + "Replace all occurrences of OLD with NEW in the current buffer. +Ignores case when searching for OLD." + (goto-char (point-min)) + (let ((case-fold-search t)) + (while (search-forward old nil t) + (replace-match new t t)))) + (provide 'mh-utils) -;;; Local Variables: -;;; indent-tabs-mode: nil -;;; sentence-end-double-space: nil -;;; End: +;; Local Variables: +;; indent-tabs-mode: nil +;; sentence-end-double-space: nil +;; End: +;; arch-tag: 1af39fdf-f66f-4b06-9b48-18a7656c8e36 ;;; mh-utils.el ends here