Add arch taglines
[bpt/emacs.git] / lisp / mh-e / mh-utils.el
CommitLineData
bdcfe844 1;;; mh-utils.el --- MH-E code needed for both sending and reading
c26cf6c8 2
924df208
BW
3;; Copyright (C) 1993, 95, 1997,
4;; 2000, 01, 02, 2003 Free Software Foundation, Inc.
a1b4049d
BW
5
6;; Author: Bill Wohler <wohler@newt.com>
7;; Maintainer: Bill Wohler <wohler@newt.com>
8;; Keywords: mail
9;; See: mh-e.el
c26cf6c8 10
60370d40 11;; This file is part of GNU Emacs.
c26cf6c8 12
9b7bc076 13;; GNU Emacs is free software; you can redistribute it and/or modify
c26cf6c8
RS
14;; it under the terms of the GNU General Public License as published by
15;; the Free Software Foundation; either version 2, or (at your option)
16;; any later version.
17
9b7bc076 18;; GNU Emacs is distributed in the hope that it will be useful,
c26cf6c8
RS
19;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21;; GNU General Public License for more details.
22
23;; You should have received a copy of the GNU General Public License
b578f267
EN
24;; along with GNU Emacs; see the file COPYING. If not, write to the
25;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
26;; Boston, MA 02111-1307, USA.
c26cf6c8
RS
27
28;;; Commentary:
29
bdcfe844 30;; Internal support for MH-E package.
c26cf6c8 31
a1b4049d
BW
32;;; Change Log:
33
c26cf6c8
RS
34;;; Code:
35
c3d9274a
BW
36;; Is this XEmacs-land? Located here since needed by mh-customize.el.
37(defvar mh-xemacs-flag (featurep 'xemacs)
38 "Non-nil means the current Emacs is XEmacs.")
39
bdcfe844
BW
40(require 'cl)
41(require 'gnus-util)
c3d9274a
BW
42(require 'font-lock)
43(require 'mh-loaddefs)
44(require 'mh-customize)
45
46(load "mm-decode" t t) ; Non-fatal dependency
47(load "mm-view" t t) ; Non-fatal dependency
48(load "executable" t t) ; Non-fatal dependency on
49 ; executable-find
bdcfe844
BW
50
51;; Shush the byte-compiler
52(defvar font-lock-auto-fontify)
53(defvar font-lock-defaults)
54(defvar mark-active)
55(defvar tool-bar-mode)
56
c3d9274a 57;;; Autoloads
bdcfe844 58(autoload 'gnus-article-highlight-citation "gnus-cite")
924df208 59(require 'sendmail)
bdcfe844 60(autoload 'Info-goto-node "info")
bdcfe844
BW
61(unless (fboundp 'make-hash-table)
62 (autoload 'make-hash-table "cl"))
63
c919c21a
RS
64;;; Set for local environment:
65;;; mh-progs and mh-lib used to be set in paths.el, which tried to
66;;; figure out at build time which of several possible directories MH
67;;; was installed into. But if you installed MH after building Emacs,
68;;; this would almost certainly be wrong, so now we do it at run time.
c26cf6c8 69
c919c21a
RS
70(defvar mh-progs nil
71 "Directory containing MH commands, such as inc, repl, and rmm.")
c26cf6c8 72
c919c21a
RS
73(defvar mh-lib nil
74 "Directory containing the MH library.
bdcfe844 75This directory contains, among other things, the components file.")
ae3864d7
KH
76
77(defvar mh-lib-progs nil
78 "Directory containing MH helper programs.
bdcfe844 79This directory contains, among other things, the mhl program.")
ae3864d7 80
bdcfe844
BW
81(defvar mh-nmh-flag nil
82 "Non-nil means nmh is installed on this system instead of MH.")
c26cf6c8 83
b6d4ab05
KH
84;;;###autoload
85(put 'mh-progs 'risky-local-variable t)
86;;;###autoload
87(put 'mh-lib 'risky-local-variable t)
ae3864d7
KH
88;;;###autoload
89(put 'mh-lib-progs 'risky-local-variable t)
90;;;###autoload
bdcfe844
BW
91(put 'mh-nmh-flag 'risky-local-variable t)
92
c3d9274a
BW
93;;; CL Replacements
94(defun mh-search-from-end (char string)
95 "Return the position of last occurrence of CHAR in STRING.
96If CHAR is not present in STRING then return nil. The function is used in lieu
97of `search' in the CL package."
98 (loop for index from (1- (length string)) downto 0
99 when (equal (aref string index) char) return index
100 finally return nil))
101
924df208
BW
102;;; Macros to generate correct code for different emacs variants
103
104(defmacro mh-do-in-gnu-emacs (&rest body)
105 "Execute BODY if in GNU Emacs."
106 (unless mh-xemacs-flag `(progn ,@body)))
107(put 'mh-do-in-gnu-emacs 'lisp-indent-hook 'defun)
108
109(defmacro mh-do-in-xemacs (&rest body)
110 "Execute BODY if in GNU Emacs."
111 (when mh-xemacs-flag `(progn ,@body)))
112(put 'mh-do-in-xemacs 'lisp-indent-hook 'defun)
113
114(defmacro mh-funcall-if-exists (function &rest args)
115 "Call FUNCTION with ARGS as parameters if it exists."
116 (if (fboundp function)
117 `(funcall ',function ,@args)))
118
119(defmacro mh-make-local-hook (hook)
120 "Make HOOK local if needed.
121XEmacs and versions of GNU Emacs before 21.1 require `make-local-hook' to be
122called."
123 (when (and (fboundp 'make-local-hook)
124 (not (get 'make-local-hook 'byte-obsolete-info)))
125 `(make-local-hook ,hook)))
bdcfe844
BW
126
127(defmacro mh-mark-active-p (check-transient-mark-mode-flag)
128 "A macro that expands into appropriate code in XEmacs and nil in GNU Emacs.
129In GNU Emacs if CHECK-TRANSIENT-MARK-MODE-FLAG is non-nil then check if
130variable `transient-mark-mode' is active."
c3d9274a 131 (cond (mh-xemacs-flag ;XEmacs
bdcfe844
BW
132 `(and (boundp 'zmacs-regions) zmacs-regions (region-active-p)))
133 ((not check-transient-mark-mode-flag) ;GNU Emacs
134 `(and (boundp 'mark-active) mark-active))
c3d9274a 135 (t ;GNU Emacs
bdcfe844
BW
136 `(and (boundp 'transient-mark-mode) transient-mark-mode
137 (boundp 'mark-active) mark-active))))
b6d4ab05 138
a1b4049d
BW
139;;; Additional header fields that might someday be added:
140;;; "Sender: " "Reply-to: "
141
a1b4049d 142(defvar mh-scan-msg-number-regexp "^ *\\([0-9]+\\)"
c26cf6c8
RS
143 "Regexp to find the number of a message in a scan line.
144The message's number must be surrounded with \\( \\)")
145
3d7ca223 146(defvar mh-scan-msg-overflow-regexp "^[?0-9][0-9]"
bdcfe844
BW
147 "Regexp to find a scan line in which the message number overflowed.
148The message's number is left truncated in this case.")
149
150(defvar mh-scan-msg-format-regexp "%\\([0-9]*\\)(msg)"
151 "Regexp to find message number width in an scan format.
152The message number width must be surrounded with \\( \\).")
153
154(defvar mh-scan-msg-format-string "%d"
155 "Format string for width of the message number in a scan format.
156Use `0%d' for zero-filled message numbers.")
157
a1b4049d 158(defvar mh-scan-msg-search-regexp "^[^0-9]*%d[^0-9]"
c26cf6c8
RS
159 "Format string containing a regexp matching the scan listing for a message.
160The desired message's number will be an argument to format.")
161
b6d4ab05
KH
162(defvar mh-default-folder-for-message-function nil
163 "Function to select a default folder for refiling or Fcc.
164If set to a function, that function is called with no arguments by
165`\\[mh-refile-msg]' and `\\[mh-to-fcc]' to get a default when
166prompting the user for a folder. The function is called from within a
a1b4049d 167`save-excursion', with point at the start of the message. It should
b6d4ab05
KH
168return the folder to offer as the refile or Fcc folder, as a string
169with a leading `+' sign. It can also return an empty string to use no
bdcfe844 170default, or nil to calculate the default the usual way.
b6d4ab05
KH
171NOTE: This variable is not an ordinary hook;
172It may not be a list of functions.")
173
3d7ca223 174(defvar mh-show-buffer-mode-line-buffer-id " {show-%s} %d"
b6d4ab05
KH
175 "Format string to produce `mode-line-buffer-identification' for show buffers.
176First argument is folder name. Second is message number.")
c26cf6c8
RS
177
178(defvar mh-cmd-note 4
bdcfe844
BW
179 "Column to insert notation.
180Use `mh-set-cmd-note' to modify it.
181This value may be dynamically updated if `mh-adaptive-cmd-note-flag' is
182non-nil and `mh-scan-format-file' is t.
183Note that the first column is column number 0.")
184(make-variable-buffer-local 'mh-cmd-note)
c26cf6c8 185
b6d4ab05
KH
186(defvar mh-note-seq "%"
187 "String whose first character is used to notate messages in a sequence.")
188
a1b4049d
BW
189(defvar mh-mail-header-separator "--------"
190 "*Line used by MH to separate headers from text in messages being composed.
191This variable should not be used directly in programs. Programs should use
192`mail-header-separator' instead. `mail-header-separator' is initialized to
193`mh-mail-header-separator' in `mh-letter-mode'; in other contexts, you may
194have to perform this initialization yourself.
195
196Do not make this a regexp as it may be the argument to `insert' and it is
197passed through `regexp-quote' before being used by functions like
198`re-search-forward'.")
199
c3d9274a 200;; Variables for MIME display
bdcfe844 201
c3d9274a
BW
202;; Structure to keep track of MIME handles on a per buffer basis.
203(defstruct (mh-buffer-data (:conc-name mh-mime-)
204 (:constructor mh-make-buffer-data))
205 (handles ()) ; List of MIME handles
206 (handles-cache (make-hash-table)) ; Cache to avoid multiple decodes of
207 ; nested messages
208 (parts-count 0) ; The button number is generated from
209 ; this number
210 (part-index-hash (make-hash-table))) ; Avoid incrementing the part number
211 ; for nested messages
212;;; This has to be a macro, since we do: (setf (mh-buffer-data) ...)
213(defmacro mh-buffer-data ()
214 "Convenience macro to get the MIME data structures of the current buffer."
215 `(gethash (current-buffer) mh-globals-hash))
bdcfe844 216
bdcfe844
BW
217(defvar mh-globals-hash (make-hash-table)
218 "Keeps track of MIME data on a per buffer basis.")
219
220(defvar mh-gnus-pgp-support-flag (not (not (locate-library "mml2015")))
221 "Non-nil means installed Gnus has PGP support.")
222
223(defvar mh-mm-inline-media-tests
224 `(("image/jpeg"
225 mm-inline-image
226 (lambda (handle)
227 (mm-valid-and-fit-image-p 'jpeg handle)))
228 ("image/png"
229 mm-inline-image
230 (lambda (handle)
231 (mm-valid-and-fit-image-p 'png handle)))
232 ("image/gif"
233 mm-inline-image
234 (lambda (handle)
235 (mm-valid-and-fit-image-p 'gif handle)))
236 ("image/tiff"
237 mm-inline-image
238 (lambda (handle)
239 (mm-valid-and-fit-image-p 'tiff handle)) )
240 ("image/xbm"
241 mm-inline-image
242 (lambda (handle)
243 (mm-valid-and-fit-image-p 'xbm handle)))
244 ("image/x-xbitmap"
245 mm-inline-image
246 (lambda (handle)
247 (mm-valid-and-fit-image-p 'xbm handle)))
248 ("image/xpm"
249 mm-inline-image
250 (lambda (handle)
251 (mm-valid-and-fit-image-p 'xpm handle)))
252 ("image/x-pixmap"
253 mm-inline-image
254 (lambda (handle)
255 (mm-valid-and-fit-image-p 'xpm handle)))
256 ("image/bmp"
257 mm-inline-image
258 (lambda (handle)
259 (mm-valid-and-fit-image-p 'bmp handle)))
260 ("image/x-portable-bitmap"
261 mm-inline-image
262 (lambda (handle)
263 (mm-valid-and-fit-image-p 'pbm handle)))
264 ("text/plain" mm-inline-text identity)
265 ("text/enriched" mm-inline-text identity)
266 ("text/richtext" mm-inline-text identity)
267 ("text/x-patch" mm-display-patch-inline
268 (lambda (handle)
269 (locate-library "diff-mode")))
270 ("application/emacs-lisp" mm-display-elisp-inline identity)
271 ("application/x-emacs-lisp" mm-display-elisp-inline identity)
272 ("text/html"
273 ,(if (fboundp 'mm-inline-text-html) 'mm-inline-text-html 'mm-inline-text)
274 (lambda (handle)
275 (or (and (boundp 'mm-inline-text-html-renderer)
276 mm-inline-text-html-renderer)
277 (and (boundp 'mm-text-html-renderer) mm-text-html-renderer))))
278 ("text/x-vcard"
279 mm-inline-text-vcard
280 (lambda (handle)
281 (or (featurep 'vcard)
282 (locate-library "vcard"))))
283 ("message/delivery-status" mm-inline-text identity)
284 ("message/rfc822" mh-mm-inline-message identity)
c3d9274a
BW
285 ;;("message/partial" mm-inline-partial identity)
286 ;;("message/external-body" mm-inline-external-body identity)
bdcfe844
BW
287 ("text/.*" mm-inline-text identity)
288 ("audio/wav" mm-inline-audio
289 (lambda (handle)
290 (and (or (featurep 'nas-sound) (featurep 'native-sound))
291 (device-sound-enabled-p))))
292 ("audio/au"
293 mm-inline-audio
294 (lambda (handle)
295 (and (or (featurep 'nas-sound) (featurep 'native-sound))
296 (device-sound-enabled-p))))
297 ("application/pgp-signature" ignore identity)
298 ("application/x-pkcs7-signature" ignore identity)
299 ("application/pkcs7-signature" ignore identity)
300 ("application/x-pkcs7-mime" ignore identity)
301 ("application/pkcs7-mime" ignore identity)
302 ("multipart/alternative" ignore identity)
303 ("multipart/mixed" ignore identity)
304 ("multipart/related" ignore identity)
305 ;; Disable audio and image
306 ("audio/.*" ignore ignore)
307 ("image/.*" ignore ignore)
308 ;; Default to displaying as text
309 (".*" mm-inline-text mm-readable-p))
310 "Alist of media types/tests saying whether types can be displayed inline.")
311
c3d9274a
BW
312;; Copy of `goto-address-mail-regexp'
313(defvar mh-address-mail-regexp
314 "[-a-zA-Z0-9._]+@[-a-zA-z0-9_]+\\.+[a-zA-Z0-9]+"
315 "A regular expression probably matching an e-mail address.")
316
317;; From goto-addr.el, which we don't want to force-load on users.
318;;;###mh-autoload
319(defun mh-goto-address-find-address-at-point ()
320 "Find e-mail address around or before point.
321Then search backwards to beginning of line for the start of an e-mail
322address. If no e-mail address found, return nil."
323 (re-search-backward "[^-_A-z0-9.@]" (line-beginning-position) 'lim)
324 (if (or (looking-at mh-address-mail-regexp) ; already at start
325 (and (re-search-forward mh-address-mail-regexp
326 (line-end-position) 'lim)
327 (goto-char (match-beginning 0))))
328 (match-string-no-properties 0)))
329
924df208
BW
330(defun mh-mail-header-end ()
331 "Substitute for `mail-header-end' that doesn't widen the buffer.
332In MH-E we frequently need to find the end of headers in nested messages, where
333the buffer has been narrowed. This function works in this situation."
334 (save-excursion
335 (rfc822-goto-eoh)
336 (point)))
337
a1b4049d 338(defun mh-in-header-p ()
bdcfe844 339 "Return non-nil if the point is in the header of a draft message."
924df208 340 (< (point) (mh-mail-header-end)))
a1b4049d 341
c3d9274a
BW
342(defun mh-header-field-beginning ()
343 "Move to the beginning of the current header field.
344Handles RFC 822 continuation lines."
345 (beginning-of-line)
346 (while (looking-at "^[ \t]")
347 (forward-line -1)))
348
a1b4049d 349(defun mh-header-field-end ()
bdcfe844
BW
350 "Move to the end of the current header field.
351Handles RFC 822 continuation lines."
a1b4049d
BW
352 (forward-line 1)
353 (while (looking-at "^[ \t]")
354 (forward-line 1))
c3d9274a 355 (backward-char 1)) ;to end of previous line
a1b4049d
BW
356
357(defun mh-letter-header-font-lock (limit)
358 "Return the entire mail header to font-lock.
359Argument LIMIT limits search."
360 (if (= (point) limit)
361 nil
924df208 362 (let* ((mail-header-end (save-match-data (mh-mail-header-end)))
a1b4049d
BW
363 (lesser-limit (if (< mail-header-end limit) mail-header-end limit)))
364 (when (mh-in-header-p)
365 (set-match-data (list 1 lesser-limit))
366 (goto-char lesser-limit)
367 t))))
368
369(defun mh-header-field-font-lock (field limit)
370 "Return the value of a header field FIELD to font-lock.
371Argument LIMIT limits search."
372 (if (= (point) limit)
373 nil
924df208 374 (let* ((mail-header-end (mh-mail-header-end))
a1b4049d
BW
375 (lesser-limit (if (< mail-header-end limit) mail-header-end limit))
376 (case-fold-search t))
c3d9274a 377 (when (and (< (point) mail-header-end) ;Only within header
a1b4049d
BW
378 (re-search-forward (format "^%s" field) lesser-limit t))
379 (let ((match-one-b (match-beginning 0))
380 (match-one-e (match-end 0)))
381 (mh-header-field-end)
c3d9274a 382 (if (> (point) limit) ;Don't search for end beyond limit
a1b4049d
BW
383 (goto-char limit))
384 (set-match-data (list match-one-b match-one-e
385 (1+ match-one-e) (point)))
386 t)))))
387
388(defun mh-header-to-font-lock (limit)
bdcfe844
BW
389 "Return the value of a header field To to font-lock.
390Argument LIMIT limits search."
a1b4049d
BW
391 (mh-header-field-font-lock "To:" limit))
392
393(defun mh-header-cc-font-lock (limit)
bdcfe844
BW
394 "Return the value of a header field cc to font-lock.
395Argument LIMIT limits search."
a1b4049d
BW
396 (mh-header-field-font-lock "cc:" limit))
397
398(defun mh-header-subject-font-lock (limit)
bdcfe844
BW
399 "Return the value of a header field Subject to font-lock.
400Argument LIMIT limits search."
a1b4049d
BW
401 (mh-header-field-font-lock "Subject:" limit))
402
bdcfe844
BW
403(eval-and-compile
404 ;; Otherwise byte-compilation fails on `mh-show-font-lock-keywords-with-cite'
405 (defvar mh-show-font-lock-keywords
406 '(("^\\(From:\\|Sender:\\)\\(.*\\)" (1 'default) (2 mh-show-from-face))
407 (mh-header-to-font-lock (0 'default) (1 mh-show-to-face))
408 (mh-header-cc-font-lock (0 'default) (1 mh-show-cc-face))
409 ("^\\(Reply-To:\\|Return-Path:\\)\\(.*\\)$"
c3d9274a 410 (1 'default) (2 mh-show-from-face))
bdcfe844
BW
411 (mh-header-subject-font-lock (0 'default) (1 mh-show-subject-face))
412 ("^\\(Apparently-To:\\|Newsgroups:\\)\\(.*\\)"
c3d9274a 413 (1 'default) (2 mh-show-cc-face))
bdcfe844 414 ("^\\(In-reply-to\\|Date\\):\\(.*\\)$"
c3d9274a 415 (1 'default) (2 mh-show-date-face))
bdcfe844
BW
416 (mh-letter-header-font-lock (0 mh-show-header-face append t)))
417 "Additional expressions to highlight in MH-show mode."))
418
419(defvar mh-show-font-lock-keywords-with-cite
420 (eval-when-compile
421 (let* ((cite-chars "[>|}]")
422 (cite-prefix "A-Za-z")
423 (cite-suffix (concat cite-prefix "0-9_.@-`'\"")))
424 (append
425 mh-show-font-lock-keywords
426 (list
427 ;; Use MATCH-ANCHORED to effectively anchor the regexp left side.
428 `(,cite-chars
429 (,(concat "\\=[ \t]*"
430 "\\(\\([" cite-prefix "]+[" cite-suffix "]*\\)?"
431 "\\(" cite-chars "[ \t]*\\)\\)+"
432 "\\(.*\\)")
433 (beginning-of-line) (end-of-line)
434 (2 font-lock-constant-face nil t)
435 (4 font-lock-comment-face nil t)))))))
436 "Additional expressions to highlight in MH-show mode.")
437
438(defun mh-show-font-lock-fontify-region (beg end loudly)
439 "Limit font-lock in `mh-show-mode' to the header.
440Used when `mh-highlight-citation-p' is set to gnus, leaving the body to be
441dealt with by gnus highlighting. The region between BEG and END is
442given over to be fontified and LOUDLY controls if a user sees a
443message about the fontification operation."
924df208 444 (let ((header-end (mh-mail-header-end)))
bdcfe844
BW
445 (cond
446 ((and (< beg header-end)(< end header-end))
447 (font-lock-default-fontify-region beg end loudly))
448 ((and (< beg header-end)(>= end header-end))
449 (font-lock-default-fontify-region beg header-end loudly))
450 (t
451 nil))))
452
453;; Needed to help shush the byte-compiler.
454(if mh-xemacs-flag
455 (progn
456 (eval-and-compile
c3d9274a
BW
457 (require 'gnus)
458 (require 'gnus-art)
459 (require 'gnus-cite))))
a1b4049d
BW
460
461(defun mh-gnus-article-highlight-citation ()
462 "Highlight cited text in current buffer using gnus."
463 (interactive)
bdcfe844
BW
464 ;; Requiring gnus-cite should have been sufficient. However for Emacs21.1,
465 ;; recursive-load-depth-limit is only 10, so an error occurs. Also it may be
466 ;; better to have an autoload at top-level (though that won't work because
467 ;; of recursive-load-depth-limit). That gets rid of a compiler warning as
468 ;; well.
469 (unless mh-xemacs-flag
470 (require 'gnus-art)
471 (require 'gnus-cite))
472 ;; Don't allow Gnus to create buttons while highlighting, maybe this is bad
473 ;; style?
474 (flet ((gnus-article-add-button (&rest args) nil))
475 (let* ((modified (buffer-modified-p))
c3d9274a
BW
476 (gnus-article-buffer (buffer-name))
477 (gnus-cite-face-list `(,@(cdr gnus-cite-face-list)
478 ,(car gnus-cite-face-list))))
bdcfe844
BW
479 (gnus-article-highlight-citation t)
480 (set-buffer-modified-p modified))))
a1b4049d 481
b6d4ab05
KH
482;;; Internal bookkeeping variables:
483
b6d4ab05 484;; Cached value of the `Path:' component in the user's MH profile.
a1b4049d
BW
485;; User's mail folder directory.
486(defvar mh-user-path nil)
c26cf6c8 487
bdcfe844 488;; An mh-draft-folder of nil means do not use a draft folder.
b6d4ab05 489;; Cached value of the `Draft-Folder:' component in the user's MH profile.
a1b4049d
BW
490;; Name of folder containing draft messages.
491(defvar mh-draft-folder nil)
c26cf6c8 492
b6d4ab05 493;; Cached value of the `Unseen-Sequence:' component in the user's MH profile.
a1b4049d
BW
494;; Name of the Unseen sequence.
495(defvar mh-unseen-seq nil)
c26cf6c8 496
a1b4049d
BW
497;; Cached value of the `Previous-Sequence:' component in the user's MH
498;; profile.
499;; Name of the Previous sequence.
500(defvar mh-previous-seq nil)
c26cf6c8 501
b6d4ab05
KH
502;; Cached value of the `Inbox:' component in the user's MH profile,
503;; or "+inbox" if no such component.
a1b4049d
BW
504;; Name of the Inbox folder.
505(defvar mh-inbox nil)
c26cf6c8 506
3d7ca223
BW
507;; The names of ephemeral buffers have a " *mh-" prefix (so that they are
508;; hidden and can be programmatically removed in mh-quit), and the variable
509;; names have the form mh-temp-.*-buffer.
510(defconst mh-temp-buffer " *mh-temp*") ;scratch
511
512;; The names of MH-E buffers that are not ephemeral and can be used by the
513;; user (and deleted by the user when no longer needed) have a "*MH-E " prefix
514;; (so they can be programmatically removed in mh-quit), and the variable
515;; names have the form mh-.*-buffer.
516(defconst mh-folders-buffer "*MH-E Folders*") ;folder list
517(defconst mh-info-buffer "*MH-E Info*") ;version information buffer
518(defconst mh-log-buffer "*MH-E Log*") ;output of MH commands and so on
519(defconst mh-recipients-buffer "*MH-E Recipients*") ;killed when draft sent
520(defconst mh-sequences-buffer "*MH-E Sequences*") ;sequences list
924df208
BW
521(defconst mh-mail-delivery-buffer "*MH-E Mail Delivery*") ;mail delivery log
522
523;; Number of lines to keep in mh-log-buffer.
524(defvar mh-log-buffer-lines 100)
a1b4049d 525
bdcfe844 526;; Window configuration before MH-E command.
a1b4049d
BW
527(defvar mh-previous-window-config nil)
528
529;;Non-nil means next SPC or whatever goes to next undeleted message.
bdcfe844 530(defvar mh-page-to-next-msg-flag nil)
c26cf6c8 531
b6d4ab05 532;;; Internal variables local to a folder.
c26cf6c8 533
a1b4049d
BW
534;; Name of current folder, a string.
535(defvar mh-current-folder nil)
c26cf6c8 536
a1b4049d
BW
537;; Buffer that displays message for this folder.
538(defvar mh-show-buffer nil)
c26cf6c8 539
a1b4049d
BW
540;; Full path of directory for this folder.
541(defvar mh-folder-filename nil)
a1506d29 542
a1b4049d
BW
543;;Number of msgs in buffer.
544(defvar mh-msg-count nil)
c26cf6c8 545
a1b4049d
BW
546;; If non-nil, show the message in a separate window.
547(defvar mh-showing-mode nil)
c26cf6c8 548
bdcfe844
BW
549(defvar mh-show-mode-map (make-sparse-keymap)
550 "Keymap used by the show buffer.")
551
552(defvar mh-show-folder-buffer nil
553 "Keeps track of folder whose message is being displayed.")
554
3d7ca223
BW
555(defvar mh-logo-cache nil)
556
557(defun mh-logo-display ()
558 "Modify mode line to display MH-E logo."
924df208
BW
559 (mh-do-in-gnu-emacs
560 (add-text-properties
561 0 2
562 `(display ,(or mh-logo-cache
563 (setq mh-logo-cache
564 (mh-funcall-if-exists
565 find-image '((:type xpm :ascent center
566 :file "mh-logo.xpm"))))))
567 (car mode-line-buffer-identification)))
568 (mh-do-in-xemacs
569 (setq modeline-buffer-identification
570 (list
571 (if mh-modeline-glyph
572 (cons modeline-buffer-id-left-extent mh-modeline-glyph)
573 (cons modeline-buffer-id-left-extent "XEmacs%N:"))
574 (cons modeline-buffer-id-right-extent " %17b")))))
575
3d7ca223 576
b6d4ab05 577;;; This holds a documentation string used by describe-mode.
a1b4049d
BW
578(defun mh-showing-mode (&optional arg)
579 "Change whether messages should be displayed.
580With arg, display messages iff ARG is positive."
581 (setq mh-showing-mode
c3d9274a
BW
582 (if (null arg)
583 (not mh-showing-mode)
584 (> (prefix-numeric-value arg) 0))))
b6d4ab05 585
a1b4049d
BW
586;; The sequences of this folder. An alist of (seq . msgs).
587(defvar mh-seq-list nil)
b6d4ab05 588
a1b4049d
BW
589;; List of displayed messages to be removed from the Unseen sequence.
590(defvar mh-seen-list nil)
b6d4ab05
KH
591
592;; If non-nil, show buffer contains message with all headers.
593;; If nil, show buffer contains message processed normally.
a1b4049d
BW
594;; Showing message with headers or normally.
595(defvar mh-showing-with-headers nil)
c26cf6c8
RS
596
597
bdcfe844 598;;; MH-E macros
c919c21a 599
bdcfe844
BW
600(defmacro with-mh-folder-updating (save-modification-flag &rest body)
601 "Format is (with-mh-folder-updating (SAVE-MODIFICATION-FLAG) &body BODY).
602Execute BODY, which can modify the folder buffer without having to
603worry about file locking or the read-only flag, and return its result.
604If SAVE-MODIFICATION-FLAG is non-nil, the buffer's modification
605flag is unchanged, otherwise it is cleared."
606 (setq save-modification-flag (car save-modification-flag)) ; CL style
b787fc05
GM
607 `(prog1
608 (let ((mh-folder-updating-mod-flag (buffer-modified-p))
c3d9274a
BW
609 (buffer-read-only nil)
610 (buffer-file-name nil)) ;don't let the buffer get locked
611 (prog1
612 (progn
613 ,@body)
614 (mh-set-folder-modified-p mh-folder-updating-mod-flag)))
bdcfe844 615 ,@(if (not save-modification-flag)
c3d9274a 616 '((mh-set-folder-modified-p nil)))))
c919c21a 617
924df208 618(put 'with-mh-folder-updating 'lisp-indent-hook 'defun)
c919c21a
RS
619
620(defmacro mh-in-show-buffer (show-buffer &rest body)
bdcfe844
BW
621 "Format is (mh-in-show-buffer (SHOW-BUFFER) &body BODY).
622Display buffer SHOW-BUFFER in other window and execute BODY in it.
623Stronger than `save-excursion', weaker than `save-window-excursion'."
c3d9274a 624 (setq show-buffer (car show-buffer)) ; CL style
b787fc05
GM
625 `(let ((mh-in-show-buffer-saved-window (selected-window)))
626 (switch-to-buffer-other-window ,show-buffer)
bdcfe844 627 (if mh-bury-show-buffer-flag (bury-buffer (current-buffer)))
b787fc05 628 (unwind-protect
c3d9274a 629 (progn
b787fc05
GM
630 ,@body)
631 (select-window mh-in-show-buffer-saved-window))))
c919c21a 632
924df208 633(put 'mh-in-show-buffer 'lisp-indent-hook 'defun)
c919c21a 634
bdcfe844
BW
635(defmacro mh-make-seq (name msgs)
636 "Create sequence NAME with the given MSGS."
637 (list 'cons name msgs))
638
639(defmacro mh-seq-name (sequence)
640 "Extract sequence name from the given SEQUENCE."
641 (list 'car sequence))
642
643(defmacro mh-seq-msgs (sequence)
644 "Extract messages from the given SEQUENCE."
645 (list 'cdr sequence))
646
647(defun mh-recenter (arg)
648 "Like recenter but with three improvements:
649- At the end of the buffer it tries to show fewer empty lines.
650- operates only if the current buffer is in the selected window.
651 (Commands like `save-some-buffers' can make this false.)
652- nil ARG means recenter as if prefix argument had been given."
653 (cond ((not (eq (get-buffer-window (current-buffer)) (selected-window)))
654 nil)
655 ((= (point-max) (save-excursion
656 (forward-line (- (/ (window-height) 2) 2))
657 (point)))
658 (let ((lines-from-end 2))
659 (save-excursion
660 (while (> (point-max) (progn (forward-line) (point)))
661 (incf lines-from-end)))
662 (recenter (- lines-from-end))))
663 ;; '(4) is the same as C-u prefix argument.
664 (t (recenter (or arg '(4))))))
665
666(defun mh-start-of-uncleaned-message ()
667 "Position uninteresting headers off the top of the window."
668 (let ((case-fold-search t))
669 (re-search-forward
670 "^To:\\|^Cc:\\|^From:\\|^Subject:\\|^Date:" nil t)
671 (beginning-of-line)
672 (mh-recenter 0)))
673
674(defun mh-invalidate-show-buffer ()
675 "Invalidate the show buffer so we must update it to use it."
676 (if (get-buffer mh-show-buffer)
677 (save-excursion
c3d9274a
BW
678 (set-buffer mh-show-buffer)
679 (mh-unvisit-file))))
bdcfe844
BW
680
681(defun mh-unvisit-file ()
682 "Separate current buffer from the message file it was visiting."
683 (or (not (buffer-modified-p))
c3d9274a 684 (null buffer-file-name) ;we've been here before
bdcfe844 685 (yes-or-no-p (format "Message %s modified; flush changes? "
c3d9274a 686 (file-name-nondirectory buffer-file-name)))
bdcfe844
BW
687 (error "Flushing changes not confirmed"))
688 (clear-visited-file-modtime)
689 (unlock-buffer)
690 (setq buffer-file-name nil))
a1506d29 691
c3d9274a 692;;;###mh-autoload
bdcfe844
BW
693(defun mh-get-msg-num (error-if-no-message)
694 "Return the message number of the displayed message.
695If the argument ERROR-IF-NO-MESSAGE is non-nil, then complain if the cursor is
696not pointing to a message."
697 (save-excursion
698 (beginning-of-line)
699 (cond ((looking-at mh-scan-msg-number-regexp)
c3d9274a
BW
700 (string-to-int (buffer-substring (match-beginning 1)
701 (match-end 1))))
702 (error-if-no-message
703 (error "Cursor not pointing to message"))
704 (t nil))))
bdcfe844
BW
705
706(defun mh-folder-name-p (name)
707 "Return non-nil if NAME is the name of a folder.
708A name (a string or symbol) can be a folder name if it begins with \"+\"."
709 (if (symbolp name)
710 (eq (aref (symbol-name name) 0) ?+)
711 (and (> (length name) 0)
c3d9274a 712 (eq (aref name 0) ?+))))
bdcfe844
BW
713
714
715(defun mh-expand-file-name (filename &optional default)
716 "Expand FILENAME like `expand-file-name', but also handle MH folder names.
717Any filename that starts with '+' is treated as a folder name.
718See `expand-file-name' for description of DEFAULT."
c3d9274a
BW
719 (if (mh-folder-name-p filename)
720 (expand-file-name (substring filename 1) mh-user-path)
721 (expand-file-name filename default)))
b6d4ab05 722
c919c21a 723
bdcfe844
BW
724(defun mh-msg-filename (msg &optional folder)
725 "Return the file name of MSG in FOLDER (default current folder)."
726 (expand-file-name (int-to-string msg)
c3d9274a
BW
727 (if folder
728 (mh-expand-file-name folder)
729 mh-folder-filename)))
c919c21a 730
bdcfe844
BW
731;;; Infrastructure to generate show-buffer functions from folder functions
732;;; XEmacs does not have deactivate-mark? What is the equivalent of
733;;; transient-mark-mode for XEmacs? Should we be restoring the mark in the
734;;; folder buffer after the operation has been carried out.
735(defmacro mh-defun-show-buffer (function original-function
c3d9274a 736 &optional dont-return)
bdcfe844
BW
737 "Define FUNCTION to run ORIGINAL-FUNCTION in folder buffer.
738If the buffer we start in is still visible and DONT-RETURN is nil then switch
739to it after that."
740 `(defun ,function ()
741 ,(format "Calls %s from the message's folder.\n%s\nSee `%s' for more info.\n"
742 original-function
743 (if dont-return ""
744 "When function completes, returns to the show buffer if it is
745still visible.\n")
746 original-function)
747 (interactive)
748 (when (buffer-live-p (get-buffer mh-show-folder-buffer))
749 (let ((config (current-window-configuration))
750 (folder-buffer mh-show-folder-buffer)
751 (normal-exit nil)
752 ,@(if dont-return () '((cur-buffer-name (buffer-name)))))
753 (pop-to-buffer mh-show-folder-buffer nil)
754 (unless (equal (buffer-name
755 (window-buffer (frame-first-window (selected-frame))))
756 folder-buffer)
757 (delete-other-windows))
758 (mh-goto-cur-msg t)
924df208 759 (mh-funcall-if-exists deactivate-mark)
bdcfe844
BW
760 (unwind-protect
761 (prog1 (call-interactively (function ,original-function))
762 (setq normal-exit t))
924df208 763 (mh-funcall-if-exists deactivate-mark)
bdcfe844
BW
764 (cond ((not normal-exit)
765 (set-window-configuration config))
766 ,(if dont-return
767 `(t (setq mh-previous-window-config config))
768 `((and (get-buffer cur-buffer-name)
769 (window-live-p (get-buffer-window
770 (get-buffer cur-buffer-name))))
771 (pop-to-buffer (get-buffer cur-buffer-name) nil)))))))))
772
773;;; Generate interactive functions for the show buffer from the corresponding
774;;; folder functions.
775(mh-defun-show-buffer mh-show-previous-undeleted-msg
c3d9274a 776 mh-previous-undeleted-msg)
bdcfe844 777(mh-defun-show-buffer mh-show-next-undeleted-msg
c3d9274a 778 mh-next-undeleted-msg)
bdcfe844
BW
779(mh-defun-show-buffer mh-show-quit mh-quit)
780(mh-defun-show-buffer mh-show-delete-msg mh-delete-msg)
781(mh-defun-show-buffer mh-show-refile-msg mh-refile-msg)
782(mh-defun-show-buffer mh-show-undo mh-undo)
783(mh-defun-show-buffer mh-show-execute-commands mh-execute-commands)
784(mh-defun-show-buffer mh-show-reply mh-reply t)
785(mh-defun-show-buffer mh-show-redistribute mh-redistribute)
786(mh-defun-show-buffer mh-show-forward mh-forward t)
787(mh-defun-show-buffer mh-show-header-display mh-header-display)
788(mh-defun-show-buffer mh-show-refile-or-write-again
c3d9274a 789 mh-refile-or-write-again)
bdcfe844
BW
790(mh-defun-show-buffer mh-show-show mh-show)
791(mh-defun-show-buffer mh-show-write-message-to-file
c3d9274a 792 mh-write-msg-to-file)
bdcfe844 793(mh-defun-show-buffer mh-show-extract-rejected-mail
c3d9274a 794 mh-extract-rejected-mail t)
bdcfe844 795(mh-defun-show-buffer mh-show-delete-msg-no-motion
c3d9274a 796 mh-delete-msg-no-motion)
bdcfe844
BW
797(mh-defun-show-buffer mh-show-first-msg mh-first-msg)
798(mh-defun-show-buffer mh-show-last-msg mh-last-msg)
799(mh-defun-show-buffer mh-show-copy-msg mh-copy-msg)
800(mh-defun-show-buffer mh-show-edit-again mh-edit-again t)
801(mh-defun-show-buffer mh-show-goto-msg mh-goto-msg)
802(mh-defun-show-buffer mh-show-inc-folder mh-inc-folder)
c3d9274a
BW
803(mh-defun-show-buffer mh-show-delete-subject-or-thread
804 mh-delete-subject-or-thread)
805(mh-defun-show-buffer mh-show-delete-subject mh-delete-subject)
bdcfe844
BW
806(mh-defun-show-buffer mh-show-print-msg mh-print-msg)
807(mh-defun-show-buffer mh-show-send mh-send t)
808(mh-defun-show-buffer mh-show-toggle-showing mh-toggle-showing t)
809(mh-defun-show-buffer mh-show-pipe-msg mh-pipe-msg t)
810(mh-defun-show-buffer mh-show-sort-folder mh-sort-folder)
811(mh-defun-show-buffer mh-show-visit-folder mh-visit-folder t)
812(mh-defun-show-buffer mh-show-rescan-folder mh-rescan-folder)
813(mh-defun-show-buffer mh-show-pack-folder mh-pack-folder)
814(mh-defun-show-buffer mh-show-kill-folder mh-kill-folder t)
815(mh-defun-show-buffer mh-show-list-folders mh-list-folders t)
816(mh-defun-show-buffer mh-show-search-folder mh-search-folder t)
817(mh-defun-show-buffer mh-show-undo-folder mh-undo-folder)
818(mh-defun-show-buffer mh-show-delete-msg-from-seq
c3d9274a 819 mh-delete-msg-from-seq)
bdcfe844
BW
820(mh-defun-show-buffer mh-show-delete-seq mh-delete-seq)
821(mh-defun-show-buffer mh-show-list-sequences mh-list-sequences)
822(mh-defun-show-buffer mh-show-narrow-to-seq mh-narrow-to-seq)
823(mh-defun-show-buffer mh-show-put-msg-in-seq mh-put-msg-in-seq)
824(mh-defun-show-buffer mh-show-msg-is-in-seq mh-msg-is-in-seq)
825(mh-defun-show-buffer mh-show-widen mh-widen)
826(mh-defun-show-buffer mh-show-narrow-to-subject
c3d9274a 827 mh-narrow-to-subject)
bdcfe844
BW
828(mh-defun-show-buffer mh-show-store-msg mh-store-msg)
829(mh-defun-show-buffer mh-show-page-digest mh-page-digest)
830(mh-defun-show-buffer mh-show-page-digest-backwards
c3d9274a 831 mh-page-digest-backwards)
bdcfe844
BW
832(mh-defun-show-buffer mh-show-burst-digest mh-burst-digest)
833(mh-defun-show-buffer mh-show-page-msg mh-page-msg)
834(mh-defun-show-buffer mh-show-previous-page mh-previous-page)
835(mh-defun-show-buffer mh-show-modify mh-modify t)
836(mh-defun-show-buffer mh-show-next-button mh-next-button)
837(mh-defun-show-buffer mh-show-prev-button mh-prev-button)
838(mh-defun-show-buffer mh-show-toggle-mime-part mh-folder-toggle-mime-part)
839(mh-defun-show-buffer mh-show-save-mime-part mh-folder-save-mime-part)
840(mh-defun-show-buffer mh-show-inline-mime-part mh-folder-inline-mime-part)
841(mh-defun-show-buffer mh-show-toggle-threads mh-toggle-threads)
c3d9274a
BW
842(mh-defun-show-buffer mh-show-thread-delete mh-thread-delete)
843(mh-defun-show-buffer mh-show-thread-refile mh-thread-refile)
bdcfe844 844(mh-defun-show-buffer mh-show-update-sequences mh-update-sequences)
c3d9274a
BW
845(mh-defun-show-buffer mh-show-next-unread-msg mh-next-unread-msg)
846(mh-defun-show-buffer mh-show-previous-unread-msg mh-previous-unread-msg)
847(mh-defun-show-buffer mh-show-thread-ancestor mh-thread-ancestor)
848(mh-defun-show-buffer mh-show-thread-next-sibling mh-thread-next-sibling)
849(mh-defun-show-buffer mh-show-thread-previous-sibling
850 mh-thread-previous-sibling)
851(mh-defun-show-buffer mh-show-index-visit-folder mh-index-visit-folder t)
924df208
BW
852(mh-defun-show-buffer mh-show-toggle-tick mh-toggle-tick)
853(mh-defun-show-buffer mh-show-narrow-to-tick mh-narrow-to-tick)
854(mh-defun-show-buffer mh-show-junk-blacklist mh-junk-blacklist)
855(mh-defun-show-buffer mh-show-junk-whitelist mh-junk-whitelist)
856(mh-defun-show-buffer mh-show-index-new-messages mh-index-new-messages)
bdcfe844
BW
857
858;;; Populate mh-show-mode-map
859(gnus-define-keys mh-show-mode-map
860 " " mh-show-page-msg
861 "!" mh-show-refile-or-write-again
924df208 862 "'" mh-show-toggle-tick
bdcfe844
BW
863 "," mh-show-header-display
864 "." mh-show-show
865 ">" mh-show-write-message-to-file
866 "?" mh-help
867 "E" mh-show-extract-rejected-mail
868 "M" mh-show-modify
869 "\177" mh-show-previous-page
870 "\C-d" mh-show-delete-msg-no-motion
871 "\t" mh-show-next-button
872 [backtab] mh-show-prev-button
873 "\M-\t" mh-show-prev-button
874 "\ed" mh-show-redistribute
875 "^" mh-show-refile-msg
876 "c" mh-show-copy-msg
877 "d" mh-show-delete-msg
878 "e" mh-show-edit-again
879 "f" mh-show-forward
880 "g" mh-show-goto-msg
881 "i" mh-show-inc-folder
c3d9274a 882 "k" mh-show-delete-subject-or-thread
bdcfe844
BW
883 "l" mh-show-print-msg
884 "m" mh-show-send
885 "n" mh-show-next-undeleted-msg
c3d9274a 886 "\M-n" mh-show-next-unread-msg
bdcfe844
BW
887 "o" mh-show-refile-msg
888 "p" mh-show-previous-undeleted-msg
c3d9274a 889 "\M-p" mh-show-previous-unread-msg
bdcfe844
BW
890 "q" mh-show-quit
891 "r" mh-show-reply
892 "s" mh-show-send
893 "t" mh-show-toggle-showing
894 "u" mh-show-undo
895 "x" mh-show-execute-commands
c3d9274a 896 "v" mh-show-index-visit-folder
bdcfe844
BW
897 "|" mh-show-pipe-msg)
898
899(gnus-define-keys (mh-show-folder-map "F" mh-show-mode-map)
900 "?" mh-prefix-help
901 "S" mh-show-sort-folder
902 "f" mh-show-visit-folder
903 "i" mh-index-search
904 "k" mh-show-kill-folder
905 "l" mh-show-list-folders
924df208 906 "n" mh-index-new-messages
bdcfe844
BW
907 "o" mh-show-visit-folder
908 "r" mh-show-rescan-folder
909 "s" mh-show-search-folder
910 "t" mh-show-toggle-threads
911 "u" mh-show-undo-folder
912 "v" mh-show-visit-folder)
913
914(gnus-define-keys (mh-show-sequence-map "S" mh-show-mode-map)
915 "?" mh-prefix-help
916 "d" mh-show-delete-msg-from-seq
917 "k" mh-show-delete-seq
918 "l" mh-show-list-sequences
919 "n" mh-show-narrow-to-seq
920 "p" mh-show-put-msg-in-seq
921 "s" mh-show-msg-is-in-seq
922 "w" mh-show-widen)
923
924df208
BW
924(define-key mh-show-mode-map "I" mh-inc-spool-map)
925
926(gnus-define-keys (mh-show-junk-map "J" mh-show-mode-map)
927 "?" mh-prefix-help
928 "b" mh-show-junk-blacklist
929 "w" mh-show-junk-whitelist)
930
bdcfe844
BW
931(gnus-define-keys (mh-show-thread-map "T" mh-show-mode-map)
932 "?" mh-prefix-help
c3d9274a
BW
933 "u" mh-show-thread-ancestor
934 "p" mh-show-thread-previous-sibling
935 "n" mh-show-thread-next-sibling
936 "t" mh-show-toggle-threads
937 "d" mh-show-thread-delete
938 "o" mh-show-thread-refile)
bdcfe844
BW
939
940(gnus-define-keys (mh-show-limit-map "/" mh-show-mode-map)
924df208 941 "'" mh-show-narrow-to-tick
bdcfe844
BW
942 "?" mh-prefix-help
943 "s" mh-show-narrow-to-subject
944 "w" mh-show-widen)
945
946(gnus-define-keys (mh-show-extract-map "X" mh-show-mode-map)
947 "?" mh-prefix-help
948 "s" mh-show-store-msg
949 "u" mh-show-store-msg)
950
951;; Untested...
952(gnus-define-keys (mh-show-digest-map "D" mh-show-mode-map)
953 "?" mh-prefix-help
c3d9274a 954 " " mh-show-page-digest
bdcfe844 955 "\177" mh-show-page-digest-backwards
c3d9274a 956 "b" mh-show-burst-digest)
bdcfe844
BW
957
958(gnus-define-keys (mh-show-mime-map "K" mh-show-mode-map)
959 "?" mh-prefix-help
c3d9274a 960 "a" mh-mime-save-parts
bdcfe844
BW
961 "v" mh-show-toggle-mime-part
962 "o" mh-show-save-mime-part
963 "i" mh-show-inline-mime-part
964 "\t" mh-show-next-button
965 [backtab] mh-show-prev-button
966 "\M-\t" mh-show-prev-button)
967
968(easy-menu-define
969 mh-show-sequence-menu mh-show-mode-map "Menu for MH-E folder-sequence."
970 '("Sequence"
971 ["Add Message to Sequence..." mh-show-put-msg-in-seq t]
972 ["List Sequences for Message" mh-show-msg-is-in-seq t]
973 ["Delete Message from Sequence..." mh-show-delete-msg-from-seq t]
974 ["List Sequences in Folder..." mh-show-list-sequences t]
975 ["Delete Sequence..." mh-show-delete-seq t]
976 ["Narrow to Sequence..." mh-show-narrow-to-seq t]
977 ["Widen from Sequence" mh-show-widen t]
978 "--"
979 ["Narrow to Subject Sequence" mh-show-narrow-to-subject t]
924df208
BW
980 ["Narrow to Tick Sequence" mh-show-narrow-to-tick
981 (save-excursion
982 (set-buffer mh-show-folder-buffer)
983 (and mh-tick-seq (mh-seq-msgs (mh-find-seq mh-tick-seq))))]
bdcfe844 984 ["Delete Rest of Same Subject" mh-show-delete-subject t]
924df208 985 ["Toggle Tick Mark" mh-show-toggle-tick t]
bdcfe844
BW
986 "--"
987 ["Push State Out to MH" mh-show-update-sequences t]))
988
989(easy-menu-define
990 mh-show-message-menu mh-show-mode-map "Menu for MH-E folder-message."
991 '("Message"
992 ["Show Message" mh-show-show t]
993 ["Show Message with Header" mh-show-header-display t]
994 ["Next Message" mh-show-next-undeleted-msg t]
995 ["Previous Message" mh-show-previous-undeleted-msg t]
996 ["Go to First Message" mh-show-first-msg t]
997 ["Go to Last Message" mh-show-last-msg t]
998 ["Go to Message by Number..." mh-show-goto-msg t]
999 ["Modify Message" mh-show-modify t]
1000 ["Delete Message" mh-show-delete-msg t]
1001 ["Refile Message" mh-show-refile-msg t]
1002 ["Undo Delete/Refile" mh-show-undo t]
1003 ["Process Delete/Refile" mh-show-execute-commands t]
1004 "--"
1005 ["Compose a New Message" mh-send t]
1006 ["Reply to Message..." mh-show-reply t]
1007 ["Forward Message..." mh-show-forward t]
1008 ["Redistribute Message..." mh-show-redistribute t]
1009 ["Edit Message Again" mh-show-edit-again t]
1010 ["Re-edit a Bounced Message" mh-show-extract-rejected-mail t]
1011 "--"
1012 ["Copy Message to Folder..." mh-show-copy-msg t]
1013 ["Print Message" mh-show-print-msg t]
1014 ["Write Message to File..." mh-show-write-msg-to-file t]
1015 ["Pipe Message to Command..." mh-show-pipe-msg t]
1016 ["Unpack Uuencoded Message..." mh-show-store-msg t]
1017 ["Burst Digest Message" mh-show-burst-digest t]))
1018
1019(easy-menu-define
1020 mh-show-folder-menu mh-show-mode-map "Menu for MH-E folder."
1021 '("Folder"
1022 ["Incorporate New Mail" mh-show-inc-folder t]
1023 ["Toggle Show/Folder" mh-show-toggle-showing t]
1024 ["Execute Delete/Refile" mh-show-execute-commands t]
1025 ["Rescan Folder" mh-show-rescan-folder t]
1026 ["Thread Folder" mh-show-toggle-threads t]
1027 ["Pack Folder" mh-show-pack-folder t]
1028 ["Sort Folder" mh-show-sort-folder t]
1029 "--"
1030 ["List Folders" mh-show-list-folders t]
1031 ["Visit a Folder..." mh-show-visit-folder t]
924df208 1032 ["View New Messages" mh-show-index-new-messages t]
bdcfe844
BW
1033 ["Search a Folder..." mh-show-search-folder t]
1034 ["Indexed Search..." mh-index-search t]
1035 "--"
1036 ["Quit MH-E" mh-quit t]))
1037
c919c21a 1038
c26cf6c8
RS
1039;;; Ensure new buffers won't get this mode if default-major-mode is nil.
1040(put 'mh-show-mode 'mode-class 'special)
1041
924df208
BW
1042;; Avoid compiler warning
1043(defvar tool-bar-map)
1044
a1b4049d 1045(define-derived-mode mh-show-mode text-mode "MH-Show"
bdcfe844
BW
1046 "Major mode for showing messages in MH-E.\\<mh-show-mode-map>
1047The value of `mh-show-mode-hook' is a list of functions to
1048be called, with no arguments, upon entry to this mode."
a1b4049d 1049 (set (make-local-variable 'mail-header-separator) mh-mail-header-separator)
bdcfe844 1050 (setq paragraph-start (default-value 'paragraph-start))
a1b4049d 1051 (mh-show-unquote-From)
bdcfe844
BW
1052 (mh-show-xface)
1053 (mh-show-addr)
a1b4049d 1054 (make-local-variable 'font-lock-defaults)
c3d9274a 1055 ;;(set (make-local-variable 'font-lock-support-mode) nil)
a1b4049d
BW
1056 (cond
1057 ((equal mh-highlight-citation-p 'font-lock)
1058 (setq font-lock-defaults '(mh-show-font-lock-keywords-with-cite t)))
1059 ((equal mh-highlight-citation-p 'gnus)
bdcfe844
BW
1060 (setq font-lock-defaults '((mh-show-font-lock-keywords)
1061 t nil nil nil
1062 (font-lock-fontify-region-function
1063 . mh-show-font-lock-fontify-region)))
a1b4049d
BW
1064 (mh-gnus-article-highlight-citation))
1065 (t
bdcfe844
BW
1066 (setq font-lock-defaults '(mh-show-font-lock-keywords t))))
1067 (if (and mh-xemacs-flag
c3d9274a 1068 font-lock-auto-fontify)
bdcfe844
BW
1069 (turn-on-font-lock))
1070 (if (and (boundp 'tool-bar-mode) tool-bar-mode)
1071 (set (make-local-variable 'tool-bar-map) mh-show-tool-bar-map))
924df208 1072 (mh-funcall-if-exists mh-toolbar-init :show)
bdcfe844 1073 (when mh-decode-mime-flag
924df208 1074 (mh-make-local-hook 'kill-buffer-hook)
bdcfe844
BW
1075 (add-hook 'kill-buffer-hook 'mh-mime-cleanup nil t))
1076 (easy-menu-add mh-show-sequence-menu)
1077 (easy-menu-add mh-show-message-menu)
1078 (easy-menu-add mh-show-folder-menu)
1079 (make-local-variable 'mh-show-folder-buffer)
1080 (buffer-disable-undo)
1081 (setq buffer-read-only t)
1082 (use-local-map mh-show-mode-map)
1083 (run-hooks 'mh-show-mode-hook))
1084
1085(defun mh-show-addr ()
1086 "Use `goto-address'."
1087 (when mh-show-use-goto-addr-flag
1088 (if (not (featurep 'goto-addr))
1089 (load "goto-addr" t t))
1090 (if (fboundp 'goto-address)
1091 (goto-address))))
1092
924df208
BW
1093\f
1094
1095;; X-Face and Face display
bdcfe844 1096(defvar mh-show-xface-function
924df208 1097 (cond ((and mh-xemacs-flag (locate-library "x-face") (not (featurep 'xface)))
bdcfe844 1098 (load "x-face" t t)
924df208
BW
1099 #'mh-face-display-function)
1100 ((>= emacs-major-version 21)
1101 #'mh-face-display-function)
bdcfe844
BW
1102 (t #'ignore))
1103 "Determine at run time what function should be called to display X-Face.")
1104
924df208
BW
1105(defvar mh-uncompface-executable
1106 (and (fboundp 'executable-find) (executable-find "uncompface")))
1107
1108(defun mh-face-to-png (data)
1109 "Convert base64 encoded DATA to png image."
1110 (with-temp-buffer
1111 (insert data)
1112 (ignore-errors (base64-decode-region (point-min) (point-max)))
1113 (buffer-string)))
1114
1115(defun mh-uncompface (data)
1116 "Run DATA through `uncompface' to generate bitmap."
1117 (with-temp-buffer
1118 (insert data)
1119 (when (and mh-uncompface-executable
1120 (equal (call-process-region (point-min) (point-max)
1121 mh-uncompface-executable t '(t nil))
1122 0))
1123 (mh-icontopbm)
1124 (buffer-string))))
1125
1126(defun mh-icontopbm ()
1127 "Elisp substitute for `icontopbm'."
1128 (goto-char (point-min))
1129 (let ((end (point-max)))
1130 (while (re-search-forward "0x\\(..\\)\\(..\\)," nil t)
1131 (save-excursion
1132 (goto-char (point-max))
1133 (insert (string-to-number (match-string 1) 16))
1134 (insert (string-to-number (match-string 2) 16))))
1135 (delete-region (point-min) end)
1136 (goto-char (point-min))
1137 (insert "P4\n48 48\n")))
1138
1139(mh-do-in-xemacs (defvar default-enable-multibyte-characters))
1140
1141(defun mh-face-display-function ()
1142 "Display a Face or X-Face header field.
1143Display Face if both are present."
1144 (save-restriction
1145 (goto-char (point-min))
1146 (re-search-forward "\n\n" (point-max) t)
1147 (narrow-to-region (point-min) (point))
1148 (let* ((case-fold-search t)
1149 (default-enable-multibyte-characters nil)
1150 (face (message-fetch-field "face" t))
1151 (x-face (message-fetch-field "x-face" t))
1152 (url (message-fetch-field "x-image-url" t))
1153 raw type)
1154 (cond (face (setq raw (mh-face-to-png face)
1155 type 'png))
1156 (x-face (setq raw (mh-uncompface x-face)
1157 type 'pbm))
1158 (url (setq type 'url)))
1159 (when type
1160 (goto-char (point-min))
1161 (when (re-search-forward "^from:" (point-max) t)
1162 ;; GNU Emacs
1163 (mh-do-in-gnu-emacs
1164 (if (eq type 'url)
1165 (mh-x-image-url-display url)
1166 (mh-funcall-if-exists
1167 insert-image (create-image
1168 raw type t
1169 :foreground (face-foreground 'mh-show-xface-face)
1170 :background (face-background 'mh-show-xface-face))
1171 " ")))
1172 ;; XEmacs
1173 (mh-do-in-xemacs
1174 (cond
1175 ((eq type 'url)
1176 (mh-x-image-url-display url))
1177 ((eq type 'png)
1178 (when (featurep 'png)
1179 (set-extent-begin-glyph
1180 (make-extent (point) (point))
1181 (make-glyph (vector 'png ':data (mh-face-to-png face))))))
1182 ;; Try internal xface support if available...
1183 ((and (eq type 'pbm) (featurep 'xface))
1184 (set-glyph-face
1185 (set-extent-begin-glyph
1186 (make-extent (point) (point))
1187 (make-glyph (vector 'xface ':data (concat "X-Face: " x-face))))
1188 'mh-show-xface-face))
1189 ;; Otherwise try external support with x-face...
1190 ((and (eq type 'pbm)
1191 (fboundp 'x-face-xmas-wl-display-x-face)
1192 (fboundp 'executable-find) (executable-find "uncompface"))
1193 (mh-funcall-if-exists x-face-xmas-wl-display-x-face)))
1194 (when raw (insert " "))))))))
1195
1196
bdcfe844
BW
1197(defun mh-show-xface ()
1198 "Display X-Face."
924df208 1199 (when (and window-system mh-show-use-xface-flag
c3d9274a
BW
1200 (or mh-decode-mime-flag mhl-formfile
1201 mh-clean-message-header-flag))
bdcfe844 1202 (funcall mh-show-xface-function)))
c26cf6c8 1203
924df208
BW
1204\f
1205
1206;; X-Image-URL display
1207
1208(defvar mh-x-image-cache-directory nil
1209 "Directory where X-Image-URL images are cached.")
1210
1211(defvar mh-convert-executable (executable-find "convert"))
1212(defvar mh-wget-executable (executable-find "wget"))
1213(defvar mh-x-image-temp-file nil)
1214(defvar mh-x-image-url nil)
1215(defvar mh-x-image-marker nil)
1216(defvar mh-x-image-url-cache-file nil)
1217
1218(defun mh-x-image-url-cache-canonicalize (url)
1219 "Canonicalize URL.
1220Replace the ?/ character with a ?! character."
1221 (with-temp-buffer
1222 (insert url)
1223 (goto-char (point-min))
1224 (while (search-forward "/" nil t) (replace-match "!"))
1225 (format "%s/%s.png" mh-x-image-cache-directory (buffer-string))))
1226
1227(defun mh-x-image-url-fetch-image (url cache-file marker sentinel)
1228 "Fetch and display the image specified by URL.
1229After the image is fetched, it is stored in CACHE-FILE. It will be displayed
1230in a buffer and position specified by MARKER. The actual display is carried
1231out by the SENTINEL function."
1232 (if (and mh-wget-executable
1233 mh-fetch-x-image-url
1234 (or (eq mh-fetch-x-image-url t)
1235 (y-or-n-p (format "Fetch %s? " url))))
1236 (let ((buffer (get-buffer-create (generate-new-buffer-name " *mh-url*")))
1237 (filename (make-temp-name "/tmp/mhe-wget")))
1238 (save-excursion
1239 (set-buffer buffer)
1240 (set (make-local-variable 'mh-x-image-url-cache-file) cache-file)
1241 (set (make-local-variable 'mh-x-image-marker) marker)
1242 (set (make-local-variable 'mh-x-image-temp-file) filename))
1243 (set-process-sentinel
1244 (start-process "*wget*" buffer mh-wget-executable "-O" filename url)
1245 sentinel))
1246 ;; Make sure we don't ask about this image again
1247 (when (and mh-wget-executable (eq mh-fetch-x-image-url 'ask))
1248 (make-symbolic-link mh-x-image-cache-directory cache-file t))))
1249
1250(defun mh-x-image-display (image marker)
1251 "Display IMAGE at MARKER."
1252 (save-excursion
1253 (set-buffer (marker-buffer marker))
1254 (let ((buffer-read-only nil)
1255 (default-enable-multibyte-characters nil)
1256 (buffer-modified-flag (buffer-modified-p)))
1257 (unwind-protect
1258 (when (and (file-readable-p image) (not (file-symlink-p image)))
1259 (goto-char marker)
1260 (mh-do-in-gnu-emacs
1261 (mh-funcall-if-exists insert-image (create-image image 'png)))
1262 (mh-do-in-xemacs
1263 (when (featurep 'png)
1264 (set-extent-begin-glyph
1265 (make-extent (point) (point))
1266 (make-glyph
1267 (vector 'png ':data (with-temp-buffer
1268 (insert-file-contents-literally image)
1269 (buffer-string))))))))
1270 (set-buffer-modified-p buffer-modified-flag)))))
1271
1272(defun mh-x-image-scale-and-display (process change)
1273 "When the wget PROCESS terminates scale and display image.
1274The argument CHANGE is ignored."
1275 (when (eq (process-status process) 'exit)
1276 (let (marker temp-file cache-filename wget-buffer)
1277 (save-excursion
1278 (set-buffer (setq wget-buffer (process-buffer process)))
1279 (setq marker mh-x-image-marker
1280 cache-filename mh-x-image-url-cache-file
1281 temp-file mh-x-image-temp-file))
1282 (when mh-convert-executable
1283 (call-process mh-convert-executable nil nil nil "-resize" "96x48"
1284 temp-file cache-filename))
1285 (if (file-exists-p cache-filename)
1286 (mh-x-image-display cache-filename marker)
1287 (make-symbolic-link mh-x-image-cache-directory cache-filename t))
1288 (ignore-errors
1289 (set-marker marker nil)
1290 (delete-process process)
1291 (kill-buffer wget-buffer)
1292 (delete-file temp-file)))))
1293
1294(defun mh-x-image-url-display (url)
1295 "Display image from location URL.
1296If the URL isn't present in the cache then it is fetched with wget."
1297 (let ((cache-filename (mh-x-image-url-cache-canonicalize url))
1298 (marker (set-marker (make-marker) (point))))
1299 (cond ((file-exists-p cache-filename)
1300 (mh-x-image-display cache-filename marker))
1301 ((not mh-fetch-x-image-url)
1302 (set-marker marker nil))
1303 ((and (not (file-exists-p mh-x-image-cache-directory))
1304 (call-process "mkdir" nil nil nil mh-x-image-cache-directory)
1305 nil))
1306 ((and (file-exists-p mh-x-image-cache-directory)
1307 (file-directory-p mh-x-image-cache-directory))
1308 (mh-x-image-url-fetch-image url cache-filename marker
1309 'mh-x-image-scale-and-display)))))
1310
1311\f
1312
c26cf6c8 1313(defun mh-maybe-show (&optional msg)
bdcfe844
BW
1314 "Display message at cursor, but only if in show mode.
1315If optional arg MSG is non-nil, display that message instead."
a1b4049d 1316 (if mh-showing-mode (mh-show msg)))
c26cf6c8 1317
b6d4ab05 1318(defun mh-show (&optional message)
bdcfe844
BW
1319 "Show message at cursor.
1320If optional argument MESSAGE is non-nil, display that message instead.
c919c21a 1321Force a two-window display with the folder window on top (size
a1b4049d 1322`mh-summary-height') and the show buffer below it.
c919c21a
RS
1323If the message is already visible, display the start of the message.
1324
1325Display of the message is controlled by setting the variables
bdcfe844 1326`mh-clean-message-header-flag' and `mhl-formfile'. The default behavior is
c919c21a
RS
1327to scroll uninteresting headers off the top of the window.
1328Type \"\\[mh-header-display]\" to see the message with all its headers."
c26cf6c8
RS
1329 (interactive)
1330 (and mh-showing-with-headers
bdcfe844 1331 (or mhl-formfile mh-clean-message-header-flag)
c26cf6c8 1332 (mh-invalidate-show-buffer))
b6d4ab05 1333 (mh-show-msg message))
c26cf6c8 1334
a1b4049d
BW
1335(defun mh-show-mouse (EVENT)
1336 "Move point to mouse EVENT and show message."
1337 (interactive "e")
1338 (mouse-set-point EVENT)
1339 (mh-show))
c26cf6c8
RS
1340
1341(defun mh-show-msg (msg)
bdcfe844
BW
1342 "Show MSG.
1343The value of `mh-show-hook' is a list of functions to be called, with no
1344arguments, after the message has been displayed."
c26cf6c8
RS
1345 (if (not msg)
1346 (setq msg (mh-get-msg-num t)))
a1b4049d 1347 (mh-showing-mode t)
bdcfe844 1348 (setq mh-page-to-next-msg-flag nil)
c26cf6c8 1349 (let ((folder mh-current-folder)
c3d9274a
BW
1350 (clean-message-header mh-clean-message-header-flag)
1351 (show-window (get-buffer-window mh-show-buffer)))
056e1e3f 1352 (if (not (eq (next-window (minibuffer-window)) (selected-window)))
c3d9274a 1353 (delete-other-windows)) ; force ourself to the top window
c26cf6c8
RS
1354 (mh-in-show-buffer (mh-show-buffer)
1355 (if (and show-window
c3d9274a
BW
1356 (equal (mh-msg-filename msg folder) buffer-file-name))
1357 (progn ;just back up to start
1358 (goto-char (point-min))
1359 (if (not clean-message-header)
1360 (mh-start-of-uncleaned-message)))
1361 (mh-display-msg msg folder))))
ec5b8815 1362 (if (not (= (1+ (window-height)) (frame-height))) ;not horizontally split
c26cf6c8
RS
1363 (shrink-window (- (window-height) mh-summary-height)))
1364 (mh-recenter nil)
c3d9274a
BW
1365 (if (not (memq msg mh-seen-list))
1366 (setq mh-seen-list (cons msg mh-seen-list)))
bdcfe844 1367 (when mh-update-sequences-after-mh-show-flag
924df208 1368 (if mh-index-data (mh-index-update-unseen msg))
a1b4049d 1369 (mh-update-sequences))
c26cf6c8
RS
1370 (run-hooks 'mh-show-hook))
1371
bdcfe844
BW
1372(defun mh-modify (&optional message)
1373 "Edit message at cursor.
1374If optional argument MESSAGE is non-nil, edit that message instead.
1375Force a two-window display with the folder window on top (size
1376`mh-summary-height') and the message editing buffer below it.
1377
1378The message is displayed in raw form."
1379 (interactive)
1380 (let* ((message (or message (mh-get-msg-num t)))
1381 (msg-filename (mh-msg-filename message))
1382 edit-buffer)
1383 (when (not (file-exists-p msg-filename))
1384 (error "Message %d does not exist" message))
1385
1386 ;; Invalidate the show buffer if it is showing the same message that is
1387 ;; to be edited.
1388 (when (and (buffer-live-p (get-buffer mh-show-buffer))
1389 (equal (save-excursion (set-buffer mh-show-buffer)
1390 buffer-file-name)
1391 msg-filename))
1392 (mh-invalidate-show-buffer))
1393
1394 ;; Edit message
1395 (find-file msg-filename)
1396 (setq edit-buffer (current-buffer))
1397
1398 ;; Set buffer properties
1399 (mh-letter-mode)
1400 (use-local-map text-mode-map)
1401
1402 ;; Just show the edit buffer...
1403 (delete-other-windows)
1404 (switch-to-buffer edit-buffer)))
c26cf6c8 1405
a1b4049d 1406(defun mh-show-unquote-From ()
bdcfe844 1407 "Decode >From at beginning of lines for `mh-show-mode'."
a1b4049d
BW
1408 (save-excursion
1409 (let ((modified (buffer-modified-p))
1410 (case-fold-search nil))
924df208 1411 (goto-char (mh-mail-header-end))
a1b4049d
BW
1412 (while (re-search-forward "^>From" nil t)
1413 (replace-match "From"))
1414 (set-buffer-modified-p modified))))
1415
bdcfe844
BW
1416(defun mh-msg-folder (folder-name)
1417 "Return the name of the buffer for FOLDER-NAME."
1418 folder-name)
1419
1420(defun mh-display-msg (msg-num folder-name)
1421 "Display MSG-NUM of FOLDER-NAME.
1422Sets the current buffer to the show buffer."
1423 (let ((folder (mh-msg-folder folder-name)))
1424 (set-buffer folder)
1425 ;; When Gnus uses external displayers it has to keep handles longer. So
1426 ;; we will delete these handles when mh-quit is called on the folder. It
1427 ;; would be nicer if there are weak pointers in emacs lisp, then we could
1428 ;; get the garbage collector to do this for us.
1429 (unless (mh-buffer-data)
1430 (setf (mh-buffer-data) (mh-make-buffer-data)))
1431 ;; Bind variables in folder buffer in case they are local
1432 (let ((formfile mhl-formfile)
1433 (clean-message-header mh-clean-message-header-flag)
1434 (invisible-headers mh-invisible-headers)
1435 (visible-headers mh-visible-headers)
1436 (msg-filename (mh-msg-filename msg-num folder-name))
1437 (show-buffer mh-show-buffer)
1438 (mm-inline-media-tests mh-mm-inline-media-tests))
1439 (if (not (file-exists-p msg-filename))
c3d9274a 1440 (error "Message %d does not exist" msg-num))
bdcfe844 1441 (if (and (> mh-show-maximum-size 0)
c3d9274a
BW
1442 (> (elt (file-attributes msg-filename) 7)
1443 mh-show-maximum-size)
1444 (not (y-or-n-p
1445 (format
1446 "Message %d (%d bytes) exceeds %d bytes. Display it? "
1447 msg-num (elt (file-attributes msg-filename) 7)
1448 mh-show-maximum-size))))
1449 (error "Message %d not displayed" msg-num))
bdcfe844
BW
1450 (set-buffer show-buffer)
1451 (cond ((not (equal msg-filename buffer-file-name))
1452 (mh-unvisit-file)
1453 (setq buffer-read-only nil)
1454 (erase-buffer)
1455 ;; Changing contents, so this hook needs to be reinitialized.
1456 ;; pgp.el uses this.
1457 (if (boundp 'write-contents-hooks) ;Emacs 19
c3d9274a 1458 (kill-local-variable 'write-contents-hooks))
bdcfe844 1459 (if formfile
c3d9274a 1460 (mh-exec-lib-cmd-output "mhl" "-nobell" "-noclear"
bdcfe844 1461 (if (stringp formfile)
c3d9274a 1462 (list "-form" formfile))
bdcfe844 1463 msg-filename)
3d7ca223 1464 (insert-file-contents-literally msg-filename))
bdcfe844
BW
1465 ;; Cleanup old mime handles
1466 (mh-mime-cleanup)
1467 ;; Use mm to display buffer
1468 (when (and mh-decode-mime-flag (not formfile))
1469 (mh-add-missing-mime-version-header)
1470 (setf (mh-buffer-data) (mh-make-buffer-data))
1471 (mh-mime-display))
924df208 1472 (mh-show-mode)
bdcfe844
BW
1473 ;; Header cleanup
1474 (goto-char (point-min))
1475 (cond (clean-message-header
1476 (mh-clean-msg-header (point-min)
1477 invisible-headers
1478 visible-headers)
1479 (goto-char (point-min)))
1480 (t
1481 (mh-start-of-uncleaned-message)))
924df208 1482 (mh-decode-message-header)
bdcfe844
BW
1483 ;; the parts of visiting we want to do (no locking)
1484 (or (eq buffer-undo-list t) ;don't save undo info for prev msgs
1485 (setq buffer-undo-list nil))
1486 (set-buffer-auto-saved)
1487 ;; the parts of set-visited-file-name we want to do (no locking)
1488 (setq buffer-file-name msg-filename)
1489 (setq buffer-backed-up nil)
1490 (auto-save-mode 1)
1491 (set-mark nil)
bdcfe844
BW
1492 (unwind-protect
1493 (when (and mh-decode-mime-flag (not formfile))
1494 (setq buffer-read-only nil)
1495 (mh-display-smileys)
1496 (mh-display-emphasis))
1497 (setq buffer-read-only t))
1498 (set-buffer-modified-p nil)
1499 (setq mh-show-folder-buffer folder)
1500 (setq mode-line-buffer-identification
1501 (list (format mh-show-buffer-mode-line-buffer-id
1502 folder-name msg-num)))
3d7ca223 1503 (mh-logo-display)
bdcfe844
BW
1504 (set-buffer folder)
1505 (setq mh-showing-with-headers nil))))))
c26cf6c8
RS
1506
1507(defun mh-clean-msg-header (start invisible-headers visible-headers)
bdcfe844
BW
1508 "Flush extraneous lines in message header.
1509Header is cleaned from START to the end of the message header.
1510INVISIBLE-HEADERS contains a regular expression specifying lines to delete
1511from the header. VISIBLE-HEADERS contains a regular expression specifying the
1512lines to display. INVISIBLE-HEADERS is ignored if VISIBLE-HEADERS is non-nil."
a1b4049d 1513 (let ((case-fold-search t)
924df208 1514 (buffer-read-only nil)
a1b4049d 1515 (after-change-functions nil)) ;Work around emacs-20 font-lock bug
c3d9274a 1516 ;causing an endless loop.
c26cf6c8
RS
1517 (save-restriction
1518 (goto-char start)
1519 (if (search-forward "\n\n" nil 'move)
c3d9274a 1520 (backward-char 1))
c26cf6c8
RS
1521 (narrow-to-region start (point))
1522 (goto-char (point-min))
1523 (if visible-headers
c3d9274a
BW
1524 (while (< (point) (point-max))
1525 (cond ((looking-at visible-headers)
1526 (forward-line 1)
1527 (while (looking-at "[ \t]") (forward-line 1)))
1528 (t
1529 (mh-delete-line 1)
1530 (while (looking-at "[ \t]")
1531 (mh-delete-line 1)))))
1532 (while (re-search-forward invisible-headers nil t)
1533 (beginning-of-line)
1534 (mh-delete-line 1)
1535 (while (looking-at "[ \t]")
1536 (mh-delete-line 1))))
c26cf6c8
RS
1537 (unlock-buffer))))
1538
c26cf6c8 1539(defun mh-delete-line (lines)
bdcfe844 1540 "Delete the next LINES lines."
b3470e4c 1541 (delete-region (point) (progn (forward-line lines) (point))))
c26cf6c8 1542
c26cf6c8 1543(defun mh-notate (msg notation offset)
bdcfe844 1544 "Mark MSG with the character NOTATION at position OFFSET.
924df208
BW
1545Null MSG means the message at cursor.
1546If NOTATION is nil then no change in the buffer occurs."
c26cf6c8
RS
1547 (save-excursion
1548 (if (or (null msg)
c3d9274a
BW
1549 (mh-goto-msg msg t t))
1550 (with-mh-folder-updating (t)
1551 (beginning-of-line)
1552 (forward-char offset)
924df208
BW
1553 (let ((notation (or notation (char-after))))
1554 (delete-char 1)
1555 (insert notation))))))
c26cf6c8 1556
b3470e4c 1557(defun mh-find-msg-get-num (step)
bdcfe844
BW
1558 "Return the message number of the message nearest the cursor.
1559Jumps over non-message lines, such as inc errors.
1560If we have to search, STEP tells whether to search forward or backward."
b3470e4c
KH
1561 (or (mh-get-msg-num nil)
1562 (let ((msg-num nil)
c3d9274a
BW
1563 (nreverses 0))
1564 (while (and (not msg-num)
1565 (< nreverses 2))
1566 (cond ((eobp)
1567 (setq step -1)
1568 (setq nreverses (1+ nreverses)))
1569 ((bobp)
1570 (setq step 1)
1571 (setq nreverses (1+ nreverses))))
1572 (forward-line step)
1573 (setq msg-num (mh-get-msg-num nil)))
1574 msg-num)))
b3470e4c 1575
c26cf6c8
RS
1576(defun mh-goto-msg (number &optional no-error-if-no-message dont-show)
1577 "Position the cursor at message NUMBER.
a1b4049d
BW
1578Optional non-nil second argument NO-ERROR-IF-NO-MESSAGE means return nil
1579instead of signaling an error if message does not exist; in this case, the
1580cursor is positioned near where the message would have been.
1581Non-nil third argument DONT-SHOW means not to show the message."
b6d4ab05 1582 (interactive "NGo to message: ")
bdcfe844
BW
1583 (setq number (prefix-numeric-value number))
1584 (let ((point (point))
1585 (return-value t))
1586 (goto-char (point-min))
1587 (unless (re-search-forward (format "^[ ]*%s[^0-9]+" number) nil t)
1588 (goto-char point)
1589 (unless no-error-if-no-message
1590 (error "No message %d" number))
1591 (setq return-value nil))
1592 (beginning-of-line)
1593 (or dont-show (not return-value) (mh-maybe-show number))
1594 return-value))
c26cf6c8
RS
1595
1596(defun mh-msg-search-pat (n)
bdcfe844 1597 "Return a search pattern for message N in the scan listing."
a1b4049d 1598 (format mh-scan-msg-search-regexp n))
c26cf6c8 1599
b6d4ab05 1600(defun mh-get-profile-field (field)
bdcfe844
BW
1601 "Find and return the value of FIELD in the current buffer.
1602Returns nil if the field is not in the buffer."
b6d4ab05
KH
1603 (let ((case-fold-search t))
1604 (goto-char (point-min))
1605 (cond ((not (re-search-forward (format "^%s" field) nil t)) nil)
c3d9274a
BW
1606 ((looking-at "[\t ]*$") nil)
1607 (t
1608 (re-search-forward "[\t ]*\\([^\t \n].*\\)$" nil t)
1609 (let ((start (match-beginning 1)))
1610 (end-of-line)
1611 (buffer-substring start (point)))))))
b6d4ab05 1612
7c3b9c62 1613(defvar mail-user-agent)
0c28d842 1614(defvar read-mail-command)
7c3b9c62
RS
1615
1616(defvar mh-find-path-run nil
1617 "Non-nil if `mh-find-path' has been run already.")
b6d4ab05 1618
c26cf6c8 1619(defun mh-find-path ()
bdcfe844
BW
1620 "Set `mh-progs', `mh-lib', and `mh-lib-progs' variables.
1621Set `mh-user-path', `mh-draft-folder', `mh-unseen-seq', `mh-previous-seq',
1622`mh-inbox' from user's MH profile.
1623The value of `mh-find-path-hook' is a list of functions to be called, with no
1624arguments, after these variable have been set."
c26cf6c8 1625 (mh-find-progs)
7c3b9c62
RS
1626 (unless mh-find-path-run
1627 (setq mh-find-path-run t)
0c28d842 1628 (setq read-mail-command 'mh-rmail)
7c3b9c62 1629 (setq mail-user-agent 'mh-e-user-agent))
c26cf6c8
RS
1630 (save-excursion
1631 ;; Be sure profile is fully expanded before switching buffers
1632 (let ((profile (expand-file-name (or (getenv "MH") "~/.mh_profile"))))
b6d4ab05 1633 (set-buffer (get-buffer-create mh-temp-buffer))
c3d9274a 1634 (setq buffer-offer-save nil) ;for people who set default to t
c26cf6c8
RS
1635 (erase-buffer)
1636 (condition-case err
c3d9274a
BW
1637 (insert-file-contents profile)
1638 (file-error
1639 (mh-install profile err)))
b6d4ab05
KH
1640 (setq mh-user-path (mh-get-profile-field "Path:"))
1641 (if (not mh-user-path)
c3d9274a 1642 (setq mh-user-path "Mail"))
c26cf6c8 1643 (setq mh-user-path
c3d9274a
BW
1644 (file-name-as-directory
1645 (expand-file-name mh-user-path (expand-file-name "~"))))
924df208
BW
1646 (unless mh-x-image-cache-directory
1647 (setq mh-x-image-cache-directory
1648 (expand-file-name ".mhe-x-image-cache" mh-user-path)))
b6d4ab05
KH
1649 (setq mh-draft-folder (mh-get-profile-field "Draft-Folder:"))
1650 (if mh-draft-folder
c3d9274a
BW
1651 (progn
1652 (if (not (mh-folder-name-p mh-draft-folder))
1653 (setq mh-draft-folder (format "+%s" mh-draft-folder)))
1654 (if (not (file-exists-p (mh-expand-file-name mh-draft-folder)))
1655 (error "Draft folder \"%s\" not found. Create it and try again"
1656 (mh-expand-file-name mh-draft-folder)))))
b6d4ab05
KH
1657 (setq mh-inbox (mh-get-profile-field "Inbox:"))
1658 (cond ((not mh-inbox)
c3d9274a
BW
1659 (setq mh-inbox "+inbox"))
1660 ((not (mh-folder-name-p mh-inbox))
1661 (setq mh-inbox (format "+%s" mh-inbox))))
b6d4ab05
KH
1662 (setq mh-unseen-seq (mh-get-profile-field "Unseen-Sequence:"))
1663 (if mh-unseen-seq
c3d9274a
BW
1664 (setq mh-unseen-seq (intern mh-unseen-seq))
1665 (setq mh-unseen-seq 'unseen)) ;old MH default?
b6d4ab05
KH
1666 (setq mh-previous-seq (mh-get-profile-field "Previous-Sequence:"))
1667 (if mh-previous-seq
c3d9274a 1668 (setq mh-previous-seq (intern mh-previous-seq)))
3d7ca223 1669 (run-hooks 'mh-find-path-hook))))
c26cf6c8 1670
f209429d
RS
1671(defun mh-file-command-p (file)
1672 "Return t if file FILE is the name of a executable regular file."
1673 (and (file-regular-p file) (file-executable-p file)))
1674
c26cf6c8 1675(defun mh-find-progs ()
a1b4049d 1676 "Find the directories for the installed MH/nmh binaries and config files.
ae3864d7 1677Set the `mh-progs' and `mh-lib', and `mh-lib-progs' variables to the
bdcfe844
BW
1678directory names and set `mh-nmh-flag' if we detect nmh instead of MH."
1679 (unless (and mh-progs mh-lib mh-lib-progs)
1680 (let ((path (or (mh-path-search exec-path "mhparam")
1681 (mh-path-search '("/usr/local/nmh/bin" ; nmh default
1682 "/usr/local/bin/mh/"
1683 "/usr/local/mh/"
1684 "/usr/bin/mh/" ;Ultrix 4.2, Linux
1685 "/usr/new/mh/" ;Ultrix <4.2
1686 "/usr/contrib/mh/bin/" ;BSDI
c3d9274a 1687 "/usr/pkg/bin/" ; NetBSD
bdcfe844
BW
1688 "/usr/local/bin/"
1689 )
1690 "mhparam"))))
1691 (if (not path)
1692 (error "Unable to find the `mhparam' command"))
1693 (save-excursion
1694 (let ((tmp-buffer (get-buffer-create mh-temp-buffer)))
1695 (set-buffer tmp-buffer)
1696 (unwind-protect
1697 (progn
1698 (call-process (expand-file-name "mhparam" path)
1699 nil '(t nil) nil "libdir" "etcdir")
1700 (goto-char (point-min))
1701 (if (search-forward-regexp "^libdir:\\s-\\(\\S-+\\)\\s-*$"
1702 nil t)
1703 (setq mh-lib-progs (match-string 1)
1704 mh-lib mh-lib-progs
1705 mh-progs path))
1706 (goto-char (point-min))
1707 (if (search-forward-regexp "^etcdir:\\s-\\(\\S-+\\)\\s-*$"
1708 nil t)
1709 (setq mh-lib (match-string 1)
1710 mh-nmh-flag t)))
1711 (kill-buffer tmp-buffer))))
1712 (unless (and mh-progs mh-lib mh-lib-progs)
c3d9274a 1713 (error "Unable to determine paths from `mhparam' command")))))
bdcfe844
BW
1714
1715(defun mh-path-search (path file)
1716 "Search PATH, a list of directory names, for FILE.
1717Returns the element of PATH that contains FILE, or nil if not found."
c26cf6c8 1718 (while (and path
c3d9274a
BW
1719 (not (funcall 'mh-file-command-p
1720 (expand-file-name file (car path)))))
c26cf6c8
RS
1721 (setq path (cdr path)))
1722 (car path))
1723
c3d9274a 1724(defvar mh-no-install nil) ;do not run install-mh
b3470e4c 1725
c26cf6c8 1726(defun mh-install (profile error-val)
bdcfe844
BW
1727 "Initialize the MH environment.
1728This is called if we fail to read the PROFILE file. ERROR-VAL is the error
1729that made this call necessary."
c26cf6c8 1730 (if (or (getenv "MH")
c3d9274a
BW
1731 (file-exists-p profile)
1732 mh-no-install)
b3470e4c 1733 (signal (car error-val)
c3d9274a
BW
1734 (list (format "Cannot read MH profile \"%s\"" profile)
1735 (car (cdr (cdr error-val))))))
c26cf6c8
RS
1736 ;; The "install-mh" command will output a short note which
1737 ;; mh-exec-cmd will display to the user.
b6d4ab05
KH
1738 ;; The MH 5 version of install-mh might try prompt the user
1739 ;; for information, which would fail here.
ae3864d7 1740 (mh-exec-cmd (expand-file-name "install-mh" mh-lib-progs) "-auto")
c26cf6c8
RS
1741 ;; now try again to read the profile file
1742 (erase-buffer)
1743 (condition-case err
1744 (insert-file-contents profile)
1745 (file-error
c3d9274a
BW
1746 (signal (car err) ;re-signal with more specific msg
1747 (list (format "Cannot read MH profile \"%s\"" profile)
1748 (car (cdr (cdr err))))))))
c26cf6c8 1749
c26cf6c8 1750(defun mh-set-folder-modified-p (flag)
bdcfe844 1751 "Mark current folder as modified or unmodified according to FLAG."
c26cf6c8
RS
1752 (set-buffer-modified-p flag))
1753
bdcfe844
BW
1754(defun mh-find-seq (name)
1755 "Return sequence NAME."
1756 (assoc name mh-seq-list))
c26cf6c8 1757
c26cf6c8 1758(defun mh-seq-to-msgs (seq)
bdcfe844 1759 "Return a list of the messages in SEQ."
c26cf6c8
RS
1760 (mh-seq-msgs (mh-find-seq seq)))
1761
bdcfe844
BW
1762(defun mh-update-scan-format (fmt width)
1763 "Return a scan format with the (msg) width in the FMT replaced with WIDTH.
1764
1765The message number width portion of the format is discovered using
1766`mh-scan-msg-format-regexp'. Its replacement is controlled with
1767`mh-scan-msg-format-string'."
1768 (or (and
1769 (string-match mh-scan-msg-format-regexp fmt)
1770 (let ((begin (match-beginning 1))
1771 (end (match-end 1)))
1772 (concat (substring fmt 0 begin)
1773 (format mh-scan-msg-format-string width)
1774 (substring fmt end))))
1775 fmt))
a1506d29 1776
bdcfe844
BW
1777(defun mh-message-number-width (folder)
1778 "Return the widest message number in this FOLDER."
1779 (or mh-progs (mh-find-path))
1780 (let ((tmp-buffer (get-buffer-create mh-temp-buffer))
c3d9274a 1781 (width 0))
bdcfe844
BW
1782 (save-excursion
1783 (set-buffer tmp-buffer)
1784 (erase-buffer)
1785 (apply 'call-process
924df208 1786 (expand-file-name mh-scan-prog mh-progs) nil '(t nil) nil
c3d9274a 1787 (list folder "last" "-format" "%(msg)"))
bdcfe844
BW
1788 (goto-char (point-min))
1789 (if (re-search-forward mh-scan-msg-number-regexp nil 0 1)
c3d9274a
BW
1790 (setq width (length (buffer-substring
1791 (match-beginning 1) (match-end 1))))))
bdcfe844 1792 width))
c26cf6c8 1793
3d7ca223 1794(defun mh-add-msgs-to-seq (msgs seq &optional internal-flag dont-annotate-flag)
bdcfe844
BW
1795 "Add MSGS to SEQ.
1796Remove duplicates and keep sequence sorted. If optional INTERNAL-FLAG is
1797non-nil, do not mark the message in the scan listing or inform MH of the
3d7ca223
BW
1798addition.
1799
1800If DONT-ANNOTATE-FLAG is non-nil then the annotations in the folder buffer are
1801not updated."
c26cf6c8
RS
1802 (let ((entry (mh-find-seq seq)))
1803 (if (and msgs (atom msgs)) (setq msgs (list msgs)))
1804 (if (null entry)
c3d9274a 1805 (setq mh-seq-list
bdcfe844
BW
1806 (cons (mh-make-seq seq (mh-canonicalize-sequence msgs))
1807 mh-seq-list))
1808 (if msgs (setcdr entry (mh-canonicalize-sequence
1809 (append msgs (mh-seq-msgs entry))))))
c26cf6c8 1810 (cond ((not internal-flag)
c3d9274a 1811 (mh-add-to-sequence seq msgs)
3d7ca223
BW
1812 (unless dont-annotate-flag
1813 (mh-notate-seq seq mh-note-seq (1+ mh-cmd-note)))))))
c26cf6c8 1814
bdcfe844
BW
1815(defun mh-canonicalize-sequence (msgs)
1816 "Sort MSGS in decreasing order and remove duplicates."
1817 (let* ((sorted-msgs (sort (copy-sequence msgs) '>))
1818 (head sorted-msgs))
1819 (while (cdr head)
1820 (if (= (car head) (cadr head))
1821 (setcdr head (cddr head))
1822 (setq head (cdr head))))
1823 sorted-msgs))
c26cf6c8 1824
3d7ca223 1825(defvar mh-sub-folders-cache (make-hash-table :test #'equal))
924df208 1826(defvar mh-current-folder-name nil)
3d7ca223
BW
1827
1828(defun mh-normalize-folder-name (folder &optional empty-string-okay
1829 dont-remove-trailing-slash)
1830 "Normalizes FOLDER name.
1831Makes sure that two '/' characters never occur next to each other. Also all
1832occurrences of \"..\" and \".\" are suitably processed. So \"+inbox/../news\"
1833will be normalized to \"+news\".
1834
1835If optional argument EMPTY-STRING-OKAY is nil then a '+' is added at the
1836front if FOLDER lacks one. If non-nil and FOLDER is the empty string then
1837nothing is added.
1838
1839If optional argument DONT-REMOVE-TRAILING-SLASH is non-nil then a trailing '/'
1840if present is retained (if present), otherwise it is removed."
1841 (when (stringp folder)
1842 ;; Replace two or more consecutive '/' characters with a single '/'
1843 (while (string-match "//" folder)
1844 (setq folder (replace-match "/" nil t folder)))
1845 (let* ((length (length folder))
1846 (trailing-slash-present (and (> length 0)
924df208
BW
1847 (equal (aref folder (1- length)) ?/)))
1848 (leading-slash-present (and (> length 0)
1849 (equal (aref folder 0) ?/))))
1850 (when (and (> length 0) (equal (aref folder 0) ?@)
1851 (stringp mh-current-folder-name))
1852 (setq folder (format "%s/%s/" mh-current-folder-name
1853 (substring folder 1))))
1854 ;; XXX: Purge empty strings from the list that split-string returns. In
1855 ;; XEmacs, (split-string "+foo/" "/") returns ("+foo" "") while in GNU
1856 ;; Emacs it returns ("+foo"). In the code it is assumed that the
1857 ;; components list has no empty strings.
1858 (let ((components (delete "" (split-string folder "/")))
3d7ca223
BW
1859 (result ()))
1860 ;; Remove .. and . from the pathname.
1861 (dolist (component components)
1862 (cond ((and (equal component "..") result)
1863 (pop result))
1864 ((equal component ".."))
1865 ((equal component "."))
1866 (t (push component result))))
1867 (setq folder "")
1868 (dolist (component result)
1869 (setq folder (concat component "/" folder)))
1870 ;; Remove trailing '/' if needed.
1871 (unless (and trailing-slash-present dont-remove-trailing-slash)
1872 (when (not (equal folder ""))
924df208
BW
1873 (setq folder (substring folder 0 (1- (length folder))))))
1874 (when leading-slash-present
1875 (setq folder (concat "/" folder)))))
3d7ca223
BW
1876 (cond ((and empty-string-okay (equal folder "")))
1877 ((equal folder "") (setq folder "+"))
1878 ((not (equal (aref folder 0) ?+)) (setq folder (concat "+" folder)))))
1879 folder)
1880
1881(defun mh-sub-folders (folder &optional add-trailing-slash-flag)
1882 "Find the subfolders of FOLDER.
1883The function avoids running folders unnecessarily by caching the results of
1884the actual folders call.
1885
1886If optional argument ADD-TRAILING-SLASH-FLAG is non-nil then a slash is added
1887to each of the sub-folder names that may have nested folders within them."
1888 (let* ((folder (mh-normalize-folder-name folder))
1889 (match (gethash folder mh-sub-folders-cache 'no-result))
1890 (sub-folders (cond ((eq match 'no-result)
1891 (setf (gethash folder mh-sub-folders-cache)
1892 (mh-sub-folders-actual folder)))
1893 (t match))))
1894 (if add-trailing-slash-flag
1895 (mapcar #'(lambda (x)
1896 (if (cdr x) (cons (concat (car x) "/") (cdr x)) x))
1897 sub-folders)
1898 sub-folders)))
1899
1900(defun mh-sub-folders-actual (folder)
1901 "Execute the command folders to return the sub-folders of FOLDER.
1902Filters out the folder names that start with \".\" so that directories that
1903aren't usually mail folders are hidden."
1904 (let ((arg-list `(,(expand-file-name "folders" mh-progs)
1905 nil (t nil) nil "-noheader" "-norecurse" "-nototal"
1906 ,@(if (stringp folder) (list folder) ())))
1907 (results ())
1908 (current-folder (concat
1909 (with-temp-buffer
1910 (call-process (expand-file-name "folder" mh-progs)
1911 nil '(t nil) nil "-fast")
1912 (buffer-substring (point-min) (1- (point-max))))
1913 "+")))
1914 (with-temp-buffer
1915 (apply #'call-process arg-list)
1916 (goto-char (point-min))
1917 (while (not (and (eolp) (bolp)))
1918 (goto-char (line-end-position))
1919 (let ((has-pos (search-backward " has " (line-beginning-position) t)))
1920 (when (integerp has-pos)
1921 (while (equal (char-after has-pos) ? )
1922 (decf has-pos))
1923 (incf has-pos)
1924 (let* ((name (buffer-substring (line-beginning-position) has-pos))
1925 (first-char (aref name 0))
1926 (last-char (aref name (1- (length name)))))
1927 (unless (member first-char '(?. ?# ?,))
1928 (when (and (equal last-char ?+) (equal name current-folder))
1929 (setq name (substring name 0 (1- (length name)))))
1930 (push
1931 (cons name
1932 (search-forward "(others)" (line-end-position) t))
1933 results))))
1934 (forward-line 1))))
1935 (setq results (nreverse results))
1936 (when (stringp folder)
1937 (setq results (cdr results))
1938 (let ((folder-name-len (length (format "%s/" (substring folder 1)))))
1939 (setq results (mapcar (lambda (f)
1940 (cons (substring (car f) folder-name-len)
1941 (cdr f)))
1942 results))))
1943 results))
1944
1945(defun mh-remove-from-sub-folders-cache (folder)
1946 "Remove FOLDER and its parent from `mh-sub-folders-cache'.
1947FOLDER should be unconditionally removed from the cache. Also the last ancestor
1948of FOLDER present in the cache must be removed as well.
1949
1950To see why this is needed assume we have a folder +foo which has a single
1951sub-folder qux. Now we create the folder +foo/bar/baz. Here we will need to
1952invalidate the cached sub-folders of +foo, otherwise completion on +foo won't
1953tell us about the option +foo/bar!"
1954 (remhash folder mh-sub-folders-cache)
1955 (block ancestor-found
1956 (let ((parent folder)
1957 (one-ancestor-found nil)
1958 last-slash)
1959 (while (setq last-slash (mh-search-from-end ?/ parent))
1960 (setq parent (substring parent 0 last-slash))
1961 (unless (eq (gethash parent mh-sub-folders-cache 'none) 'none)
1962 (remhash parent mh-sub-folders-cache)
1963 (if one-ancestor-found
1964 (return-from ancestor-found)
1965 (setq one-ancestor-found t))))
1966 (remhash nil mh-sub-folders-cache))))
1967
bdcfe844
BW
1968(defvar mh-folder-hist nil)
1969(defvar mh-speed-folder-map)
924df208
BW
1970(defvar mh-speed-flists-cache)
1971
1972(defvar mh-allow-root-folder-flag nil
1973 "Non-nil means \"+\" is an acceptable folder name.
1974This variable is used to communicate with `mh-folder-completion-function'. That
1975function can have exactly three arguments so we bind this variable to t or nil.
1976
1977This variable should never be set.")
1978
3d7ca223
BW
1979(defvar mh-folder-completion-map (copy-keymap minibuffer-local-completion-map))
1980(define-key mh-folder-completion-map " " 'minibuffer-complete)
1981
924df208
BW
1982(defun mh-speed-flists-active-p ()
1983 "Check if speedbar is running with message counts enabled."
1984 (and (featurep 'mh-speed)
1985 (> (hash-table-count mh-speed-flists-cache) 0)))
1986
3d7ca223
BW
1987(defun mh-folder-completion-function (name predicate flag)
1988 "Programmable completion for folder names.
1989NAME is the partial folder name that has been input. PREDICATE if non-nil is a
1990function that is used to filter the possible choices and FLAG determines
1991whether the completion is over."
1992 (let* ((orig-name name)
1993 (name (mh-normalize-folder-name name nil t))
1994 (last-slash (mh-search-from-end ?/ name))
1995 (last-complete (if last-slash (substring name 0 last-slash) nil))
1996 (remainder (cond (last-complete (substring name (1+ last-slash)))
1997 ((and (> (length name) 0) (equal (aref name 0) ?+))
1998 (substring name 1))
1999 (t ""))))
2000 (cond ((eq flag nil)
2001 (let ((try-res (try-completion
2002 name
2003 (mapcar (lambda (x)
2004 (cons (if (not last-complete)
2005 (concat "+" (car x))
2006 (concat last-complete "/" (car x)))
2007 (cdr x)))
2008 (mh-sub-folders last-complete t))
2009 predicate)))
2010 (cond ((eq try-res nil) nil)
2011 ((and (eq try-res t) (equal name orig-name)) t)
2012 ((eq try-res t) name)
2013 (t try-res))))
2014 ((eq flag t)
2015 (all-completions
2016 remainder (mh-sub-folders last-complete t) predicate))
2017 ((eq flag 'lambda)
924df208
BW
2018 (let ((path (concat mh-user-path
2019 (substring (mh-normalize-folder-name name) 1))))
2020 (cond (mh-allow-root-folder-flag (file-exists-p path))
2021 ((equal path mh-user-path) nil)
2022 (t (file-exists-p path))))))))
2023
2024(defun mh-folder-completing-read (prompt default allow-root-folder-flag)
2025 "Read folder name with PROMPT and default result DEFAULT.
2026If ALLOW-ROOT-FOLDER-FLAG is non-nil then \"+\" is allowed to be a folder name
2027corresponding to `mh-user-path'."
3d7ca223 2028 (mh-normalize-folder-name
924df208
BW
2029 (let ((minibuffer-local-completion-map mh-folder-completion-map)
2030 (mh-allow-root-folder-flag allow-root-folder-flag))
3d7ca223
BW
2031 (completing-read prompt 'mh-folder-completion-function nil nil nil
2032 'mh-folder-hist default))
2033 t))
bdcfe844
BW
2034
2035(defun mh-prompt-for-folder (prompt default can-create
3d7ca223 2036 &optional default-string allow-root-folder-flag)
bdcfe844
BW
2037 "Prompt for a folder name with PROMPT.
2038Returns the folder's name as a string. DEFAULT is used if the folder exists
2039and the user types return. If the CAN-CREATE flag is t, then a folder is
2040created if it doesn't already exist. If optional argument DEFAULT-STRING is
3d7ca223
BW
2041non-nil, use it in the prompt instead of DEFAULT. If ALLOW-ROOT-FOLDER-FLAG is
2042non-nil then the function will accept the folder +, which means all folders
2043when used in searching."
c26cf6c8
RS
2044 (if (null default)
2045 (setq default ""))
bdcfe844
BW
2046 (let* ((default-string (cond (default-string (format " [%s]? "
2047 default-string))
2048 ((equal "" default) "? ")
2049 (t (format " [%s]? " default))))
2050 (prompt (format "%s folder%s" prompt default-string))
924df208 2051 (mh-current-folder-name mh-current-folder)
c3d9274a 2052 read-name folder-name)
924df208
BW
2053 (while (and (setq read-name (mh-folder-completing-read
2054 prompt default allow-root-folder-flag))
c3d9274a
BW
2055 (equal read-name "")
2056 (equal default "")))
3d7ca223
BW
2057 (cond ((or (equal read-name "")
2058 (and (equal read-name "+") (not allow-root-folder-flag)))
c3d9274a
BW
2059 (setq read-name default))
2060 ((not (mh-folder-name-p read-name))
2061 (setq read-name (format "+%s" read-name))))
a1b4049d
BW
2062 (if (or (not read-name) (equal "" read-name))
2063 (error "No folder specified"))
c26cf6c8
RS
2064 (setq folder-name read-name)
2065 (cond ((and (> (length folder-name) 0)
c3d9274a
BW
2066 (eq (aref folder-name (1- (length folder-name))) ?/))
2067 (setq folder-name (substring folder-name 0 -1))))
924df208
BW
2068 (let* ((last-slash (mh-search-from-end ?/ folder-name))
2069 (parent (and last-slash (substring folder-name 0 last-slash)))
2070 (child (if last-slash
2071 (substring folder-name (1+ last-slash))
2072 (substring folder-name 1))))
2073 (unless (member child
2074 (mapcar #'car (gethash parent mh-sub-folders-cache)))
2075 (mh-remove-from-sub-folders-cache folder-name)))
bdcfe844 2076 (let ((new-file-flag
c3d9274a 2077 (not (file-exists-p (mh-expand-file-name folder-name)))))
bdcfe844 2078 (cond ((and new-file-flag
c3d9274a
BW
2079 (y-or-n-p
2080 (format "Folder %s does not exist. Create it? "
2081 folder-name)))
2082 (message "Creating %s" folder-name)
bdcfe844 2083 (mh-exec-cmd-error nil "folder" folder-name)
3d7ca223 2084 (mh-remove-from-sub-folders-cache folder-name)
bdcfe844
BW
2085 (when (boundp 'mh-speed-folder-map)
2086 (mh-speed-add-folder folder-name))
3d7ca223 2087 (message "Creating %s...done" folder-name))
c3d9274a
BW
2088 (new-file-flag
2089 (error "Folder %s is not created" folder-name))
2090 ((not (file-directory-p (mh-expand-file-name folder-name)))
2091 (error "\"%s\" is not a directory"
3d7ca223 2092 (mh-expand-file-name folder-name)))))
c26cf6c8
RS
2093 folder-name))
2094
924df208
BW
2095(defun mh-truncate-log-buffer ()
2096 "If `mh-log-buffer' is too big then truncate it.
2097If the number of lines in `mh-log-buffer' exceeds `mh-log-buffer-lines' then
2098keep only the last `mh-log-buffer-lines'. As a side effect the point is set to
2099the end of the log buffer.
2100
2101The function returns the size of the final size of the log buffer."
2102 (with-current-buffer (get-buffer-create mh-log-buffer)
2103 (goto-char (point-max))
2104 (save-excursion
2105 (when (equal (forward-line (- mh-log-buffer-lines)) 0)
2106 (delete-region (point-min) (point))))
2107 (unless (or (bobp)
2108 (save-excursion
2109 (and (equal (forward-line -1) 0) (equal (char-after) ?\f))))
2110 (insert "\n\f\n"))
2111 (buffer-size)))
2112
c26cf6c8
RS
2113;;; Issue commands to MH.
2114
c26cf6c8 2115(defun mh-exec-cmd (command &rest args)
bdcfe844
BW
2116 "Execute mh-command COMMAND with ARGS.
2117The side effects are what is desired.
2118Any output is assumed to be an error and is shown to the user.
2119The output is not read or parsed by MH-E."
c26cf6c8 2120 (save-excursion
3d7ca223 2121 (set-buffer (get-buffer-create mh-log-buffer))
924df208
BW
2122 (let ((initial-size (mh-truncate-log-buffer)))
2123 (apply 'call-process
2124 (expand-file-name command mh-progs) nil t nil
2125 (mh-list-to-string args))
2126 (if (> (buffer-size) initial-size)
2127 (save-window-excursion
2128 (switch-to-buffer-other-window mh-log-buffer)
2129 (sit-for 5))))))
c26cf6c8 2130
c26cf6c8 2131(defun mh-exec-cmd-error (env command &rest args)
bdcfe844
BW
2132 "In environment ENV, execute mh-command COMMAND with ARGS.
2133ENV is nil or a string of space-separated \"var=value\" elements.
2134Signals an error if process does not complete successfully."
c26cf6c8 2135 (save-excursion
b6d4ab05 2136 (set-buffer (get-buffer-create mh-temp-buffer))
c26cf6c8 2137 (erase-buffer)
924df208
BW
2138 (let ((process-environment process-environment))
2139 ;; XXX: We should purge the list that split-string returns of empty
2140 ;; strings. This can happen in XEmacs if leading or trailing spaces
2141 ;; are present.
2142 (dolist (elem (if (stringp env) (split-string env " ") ()))
2143 (push elem process-environment))
2144 (mh-handle-process-error
2145 command (apply #'call-process (expand-file-name command mh-progs)
2146 nil t nil (mh-list-to-string args))))))
c26cf6c8 2147
3d7ca223
BW
2148(defun mh-exec-cmd-daemon (command filter &rest args)
2149 "Execute MH command COMMAND in the background.
2150
2151If FILTER is non-nil then it is used to process the output otherwise the
2152default filter `mh-process-daemon' is used. See `set-process-filter' for more
2153details of FILTER.
2154
2155ARGS are passed to COMMAND as command line arguments."
c26cf6c8 2156 (save-excursion
3d7ca223 2157 (set-buffer (get-buffer-create mh-log-buffer))
924df208 2158 (mh-truncate-log-buffer))
c26cf6c8 2159 (let* ((process-connection-type nil)
c3d9274a
BW
2160 (process (apply 'start-process
2161 command nil
2162 (expand-file-name command mh-progs)
2163 (mh-list-to-string args))))
3d7ca223 2164 (set-process-filter process (or filter 'mh-process-daemon))))
c26cf6c8 2165
924df208
BW
2166(defun mh-exec-cmd-env-daemon (env command filter &rest args)
2167 "In ennvironment ENV, execute mh-command COMMAND in the background.
2168
2169ENV is nil or a string of space-separated \"var=value\" elements.
2170Signals an error if process does not complete successfully.
2171
2172If FILTER is non-nil then it is used to process the output otherwise the
2173default filter `mh-process-daemon' is used. See `set-process-filter' for more
2174details of FILTER.
2175
2176ARGS are passed to COMMAND as command line arguments."
2177 (let ((process-environment process-environment))
2178 (dolist (elem (if (stringp env) (split-string env " ") ()))
2179 (push elem process-environment))
2180 (apply #'mh-exec-cmd-daemon command filter args)))
2181
c26cf6c8 2182(defun mh-process-daemon (process output)
3d7ca223
BW
2183 "PROCESS daemon that puts OUTPUT into a temporary buffer.
2184Any output from the process is displayed in an asynchronous pop-up window."
2185 (set-buffer (get-buffer-create mh-log-buffer))
c26cf6c8 2186 (insert-before-markers output)
3d7ca223 2187 (display-buffer mh-log-buffer))
c26cf6c8 2188
c26cf6c8 2189(defun mh-exec-cmd-quiet (raise-error command &rest args)
bdcfe844
BW
2190 "Signal RAISE-ERROR if COMMAND with ARGS fails.
2191Execute MH command COMMAND with ARGS. ARGS is a list of strings.
2192Return at start of mh-temp buffer, where output can be parsed and used.
2193Returns value of `call-process', which is 0 for success, unless RAISE-ERROR is
2194non-nil, in which case an error is signaled if `call-process' returns non-0."
b6d4ab05 2195 (set-buffer (get-buffer-create mh-temp-buffer))
c26cf6c8
RS
2196 (erase-buffer)
2197 (let ((value
c3d9274a
BW
2198 (apply 'call-process
2199 (expand-file-name command mh-progs) nil t nil
2200 args)))
c26cf6c8
RS
2201 (goto-char (point-min))
2202 (if raise-error
c3d9274a 2203 (mh-handle-process-error command value)
c26cf6c8
RS
2204 value)))
2205
c3d9274a
BW
2206(defun mh-profile-component (component)
2207 "Return COMPONENT value from mhparam, or nil if unset."
2208 (save-excursion
2209 (mh-exec-cmd-quiet nil "mhparam" "-components" component)
2210 (mh-get-profile-field (concat component ":"))))
2211
bdcfe844
BW
2212(defun mh-exchange-point-and-mark-preserving-active-mark ()
2213 "Put the mark where point is now, and point where the mark is now.
2214This command works even when the mark is not active, and preserves whether the
2215mark is active or not."
2216 (interactive nil)
2217 (let ((is-active (and (boundp 'mark-active) mark-active)))
2218 (let ((omark (mark t)))
2219 (if (null omark)
2220 (error "No mark set in this buffer"))
2221 (set-mark (point))
2222 (goto-char omark)
2223 (if (boundp 'mark-active)
2224 (setq mark-active is-active))
2225 nil)))
c26cf6c8
RS
2226
2227(defun mh-exec-cmd-output (command display &rest args)
bdcfe844
BW
2228 "Execute MH command COMMAND with DISPLAY flag and ARGS.
2229Put the output into buffer after point. Set mark after inserted text.
2230Output is expected to be shown to user, not parsed by MH-E."
c26cf6c8
RS
2231 (push-mark (point) t)
2232 (apply 'call-process
c3d9274a
BW
2233 (expand-file-name command mh-progs) nil t display
2234 (mh-list-to-string args))
c26cf6c8 2235
bdcfe844
BW
2236 ;; The following is used instead of 'exchange-point-and-mark because the
2237 ;; latter activates the current region (between point and mark), which
2238 ;; turns on highlighting. So prior to this bug fix, doing "inc" would
2239 ;; highlight a region containing the new messages, which is undesirable.
2240 ;; The bug wasn't seen in emacs21 but still occurred in XEmacs21.4.
2241 (mh-exchange-point-and-mark-preserving-active-mark))
c26cf6c8
RS
2242
2243(defun mh-exec-lib-cmd-output (command &rest args)
bdcfe844
BW
2244 "Execute MH library command COMMAND with ARGS.
2245Put the output into buffer after point. Set mark after inserted text."
ae3864d7 2246 (apply 'mh-exec-cmd-output (expand-file-name command mh-lib-progs) nil args))
c26cf6c8 2247
c26cf6c8 2248(defun mh-handle-process-error (command status)
924df208
BW
2249 "Raise error if COMMAND returned non-zero STATUS, otherwise return STATUS."
2250 (if (equal status 0)
2251 status
2252 (goto-char (point-min))
2253 (insert (if (integerp status)
2254 (format "%s: exit code %d\n" command status)
2255 (format "%s: %s\n" command status)))
2256 (save-excursion
2257 (let ((error-message (buffer-substring (point-min) (point-max))))
2258 (set-buffer (get-buffer-create mh-log-buffer))
2259 (mh-truncate-log-buffer)
2260 (insert error-message)))
2261 (error "%s failed, check %s buffer for error message"
2262 command mh-log-buffer)))
c26cf6c8 2263
c26cf6c8 2264(defun mh-list-to-string (l)
bdcfe844 2265 "Flatten the list L and make every element of the new list into a string."
c26cf6c8
RS
2266 (nreverse (mh-list-to-string-1 l)))
2267
2268(defun mh-list-to-string-1 (l)
bdcfe844 2269 "Flatten the list L and make every element of the new list into a string."
c26cf6c8
RS
2270 (let ((new-list nil))
2271 (while l
2272 (cond ((null (car l)))
c3d9274a
BW
2273 ((symbolp (car l))
2274 (setq new-list (cons (symbol-name (car l)) new-list)))
2275 ((numberp (car l))
2276 (setq new-list (cons (int-to-string (car l)) new-list)))
2277 ((equal (car l) ""))
2278 ((stringp (car l)) (setq new-list (cons (car l) new-list)))
2279 ((listp (car l))
2280 (setq new-list (nconc (mh-list-to-string-1 (car l))
2281 new-list)))
2282 (t (error "Bad element in mh-list-to-string: %s" (car l))))
c26cf6c8
RS
2283 (setq l (cdr l)))
2284 new-list))
2285
2286(provide 'mh-utils)
2287
bdcfe844 2288;;; Local Variables:
c3d9274a 2289;;; indent-tabs-mode: nil
bdcfe844
BW
2290;;; sentence-end-double-space: nil
2291;;; End:
2292
ab5796a9 2293;;; arch-tag: 1af39fdf-f66f-4b06-9b48-18a7656c8e36
c26cf6c8 2294;;; mh-utils.el ends here