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