Trailing whitepace deleted.
[bpt/emacs.git] / lisp / mh-e / mh-funcs.el
CommitLineData
bdcfe844 1;;; mh-funcs.el --- MH-E functions not everyone will use right away
c26cf6c8 2
a1b4049d
BW
3;; Copyright (C) 1993, 1995, 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
BW
29;; Internal support for MH-E package.
30;; Putting these functions in a separate file lets MH-E start up faster,
b578f267 31;; since less Lisp code needs to be loaded all at once.
c26cf6c8 32
847b8219
KH
33;;; Change Log:
34
a1506d29 35;; $Id: mh-funcs.el,v 1.2 2003/02/03 20:55:30 wohler Exp $
847b8219 36
c26cf6c8
RS
37;;; Code:
38
c26cf6c8
RS
39(require 'mh-e)
40
c3d9274a 41;;; Customization
847b8219 42
c26cf6c8
RS
43(defvar mh-sortm-args nil
44 "Extra arguments to have \\[mh-sort-folder] pass to the \"sortm\" command.
847b8219
KH
45The arguments are passed to sortm if \\[mh-sort-folder] is given a
46prefix argument. Normally default arguments to sortm are specified in the
47MH profile.
c26cf6c8
RS
48For example, '(\"-nolimit\" \"-textfield\" \"subject\") is a useful setting.")
49
847b8219
KH
50(defvar mh-note-copied "C"
51 "String whose first character is used to notate copied messages.")
52
53(defvar mh-note-printed "P"
54 "String whose first character is used to notate printed messages.")
55
bdcfe844 56;;; Functions
847b8219 57
c3d9274a 58;;;###mh-autoload
c26cf6c8
RS
59(defun mh-burst-digest ()
60 "Burst apart the current message, which should be a digest.
847b8219 61The message is replaced by its table of contents and the messages from the
c26cf6c8
RS
62digest are inserted into the folder after that message."
63 (interactive)
64 (let ((digest (mh-get-msg-num t)))
65 (mh-process-or-undo-commands mh-current-folder)
c3d9274a 66 (mh-set-folder-modified-p t) ; lock folder while bursting
c26cf6c8
RS
67 (message "Bursting digest...")
68 (mh-exec-cmd "burst" mh-current-folder digest "-inplace")
847b8219
KH
69 (with-mh-folder-updating (t)
70 (beginning-of-line)
71 (delete-region (point) (point-max)))
72 (mh-regenerate-headers (format "%d-last" digest) t)
73 (mh-goto-cur-msg)
c26cf6c8
RS
74 (message "Bursting digest...done")))
75
c3d9274a 76;;;###mh-autoload
847b8219 77(defun mh-copy-msg (msg-or-seq folder)
a1b4049d 78 "Copy the specified MSG-OR-SEQ to another FOLDER without deleting them.
bdcfe844
BW
79Default is the displayed message. If optional prefix argument is provided,
80then prompt for the message sequence."
c3d9274a
BW
81 (interactive (list (cond
82 ((mh-mark-active-p t)
3d7ca223 83 (cons (region-beginning) (region-end)))
c3d9274a
BW
84 (current-prefix-arg
85 (mh-read-seq-default "Copy" t))
86 (t
3d7ca223 87 (cons (line-beginning-position) (line-end-position))))
c3d9274a 88 (mh-prompt-for-folder "Copy to" "" t)))
3d7ca223
BW
89 (let ((msg-list (cond ((numberp msg-or-seq) (list msg-or-seq))
90 ((symbolp msg-or-seq) (mh-seq-to-msgs msg-or-seq))
91 ((and (consp msg-or-seq) (numberp (car msg-or-seq))
92 (numberp (cdr msg-or-seq)))
93 (let ((result ()))
94 (mh-iterate-on-messages-in-region msg
95 (car msg-or-seq) (cdr msg-or-seq)
96 (mh-notate nil mh-note-copied mh-cmd-note)
97 (push msg result))
98 result))
99 (t msg-or-seq))))
100 (mh-exec-cmd "refile" (mh-coalesce-msg-list msg-list)
101 "-link" "-src" mh-current-folder folder)
102 (cond ((numberp msg-or-seq)
103 (mh-notate msg-or-seq mh-note-copied mh-cmd-note))
104 ((symbolp msg-or-seq)
105 (mh-notate-seq msg-or-seq mh-note-copied mh-cmd-note)))))
c26cf6c8 106
c3d9274a 107;;;###mh-autoload
c26cf6c8 108(defun mh-kill-folder ()
a1b4049d
BW
109 "Remove the current folder and all included messages.
110Removes all of the messages (files) within the specified current folder,
3d7ca223 111and then removes the folder (directory) itself."
c26cf6c8 112 (interactive)
3d7ca223
BW
113 (if (or mh-index-data
114 (yes-or-no-p (format "Remove folder %s (and all included messages)?"
115 mh-current-folder)))
116 (let ((folder mh-current-folder)
117 (window-config mh-previous-window-config))
c3d9274a 118 (mh-set-folder-modified-p t) ; lock folder to kill it
3d7ca223 119 (mh-exec-cmd-daemon "rmf" 'mh-rmf-daemon folder)
bdcfe844
BW
120 (when (boundp 'mh-speed-folder-map)
121 (mh-speed-invalidate-map folder))
3d7ca223 122 (mh-remove-from-sub-folders-cache folder)
c3d9274a 123 (mh-set-folder-modified-p nil) ; so kill-buffer doesn't complain
3d7ca223 124 (if (and mh-show-buffer (get-buffer mh-show-buffer))
c3d9274a
BW
125 (kill-buffer mh-show-buffer))
126 (if (get-buffer folder)
3d7ca223
BW
127 (kill-buffer folder))
128 (when window-config
129 (set-window-configuration window-config))
130 (message "Folder %s removed" folder))
c3d9274a 131 (message "Folder not removed")))
c26cf6c8 132
3d7ca223
BW
133(defun mh-rmf-daemon (process output)
134 "The rmf PROCESS puts OUTPUT in temporary buffer.
135Display the results only if something went wrong."
136 (set-buffer (get-buffer-create mh-temp-buffer))
137 (insert-before-markers output)
138 (when (save-excursion
139 (beginning-of-buffer)
140 (re-search-forward "^rmf: " (point-max) t))
141 (display-buffer mh-temp-buffer)))
142
bdcfe844
BW
143;; Avoid compiler warning...
144(defvar view-exit-action)
c26cf6c8 145
c3d9274a 146;;;###mh-autoload
c26cf6c8
RS
147(defun mh-list-folders ()
148 "List mail folders."
149 (interactive)
3d7ca223 150 (let ((temp-buffer mh-folders-buffer))
a1b4049d
BW
151 (with-output-to-temp-buffer temp-buffer
152 (save-excursion
c3d9274a
BW
153 (set-buffer temp-buffer)
154 (erase-buffer)
155 (message "Listing folders...")
156 (mh-exec-cmd-output "folders" t (if mh-recursive-folders-flag
157 "-recurse"
158 "-norecurse"))
159 (goto-char (point-min))
160 (view-mode 1)
161 (setq view-exit-action 'kill-buffer)
162 (message "Listing folders...done")))))
163
164;;;###mh-autoload
c26cf6c8
RS
165(defun mh-pack-folder (range)
166 "Renumber the messages of a folder to be 1..n.
bdcfe844
BW
167First, offer to execute any outstanding commands for the current folder. If
168optional prefix argument provided, prompt for the RANGE of messages to display
169after packing. Otherwise, show the entire folder."
c26cf6c8 170 (interactive (list (if current-prefix-arg
c3d9274a
BW
171 (mh-read-msg-range mh-current-folder t)
172 '("all"))))
173 (let ((threaded-flag (memq 'unthread mh-view-ops)))
174 (mh-pack-folder-1 range)
175 (mh-goto-cur-msg)
176 (when mh-index-data
177 (mh-index-update-maps mh-current-folder))
178 (cond (threaded-flag (mh-toggle-threads))
179 (mh-index-data (mh-index-insert-folder-headers))))
c26cf6c8
RS
180 (message "Packing folder...done"))
181
c26cf6c8 182(defun mh-pack-folder-1 (range)
bdcfe844
BW
183 "Close and pack the current folder.
184Display the given RANGE of messages after packing. If RANGE is nil, show the
185entire folder."
c26cf6c8
RS
186 (mh-process-or-undo-commands mh-current-folder)
187 (message "Packing folder...")
c3d9274a 188 (mh-set-folder-modified-p t) ; lock folder while packing
c26cf6c8 189 (save-excursion
847b8219 190 (mh-exec-cmd-quiet t "folder" mh-current-folder "-pack"
c3d9274a 191 "-norecurse" "-fast"))
bdcfe844 192 (mh-reset-threads-and-narrowing)
c26cf6c8
RS
193 (mh-regenerate-headers range))
194
c3d9274a 195;;;###mh-autoload
c26cf6c8
RS
196(defun mh-pipe-msg (command include-headers)
197 "Pipe the current message through the given shell COMMAND.
198If INCLUDE-HEADERS (prefix argument) is provided, send the entire message.
199Otherwise just send the message's body without the headers."
200 (interactive
201 (list (read-string "Shell command on message: ") current-prefix-arg))
847b8219 202 (let ((msg-file-to-pipe (mh-msg-filename (mh-get-msg-num t)))
c3d9274a 203 (message-directory default-directory))
c26cf6c8 204 (save-excursion
847b8219 205 (set-buffer (get-buffer-create mh-temp-buffer))
c26cf6c8 206 (erase-buffer)
847b8219 207 (insert-file-contents msg-file-to-pipe)
c26cf6c8
RS
208 (goto-char (point-min))
209 (if (not include-headers) (search-forward "\n\n"))
847b8219 210 (let ((default-directory message-directory))
c3d9274a 211 (shell-command-on-region (point) (point-max) command nil)))))
c26cf6c8 212
c3d9274a 213;;;###mh-autoload
c26cf6c8
RS
214(defun mh-page-digest ()
215 "Advance displayed message to next digested message."
216 (interactive)
217 (mh-in-show-buffer (mh-show-buffer)
218 ;; Go to top of screen (in case user moved point).
219 (move-to-window-line 0)
220 (let ((case-fold-search nil))
221 ;; Search for blank line and then for From:
222 (or (and (search-forward "\n\n" nil t)
c3d9274a
BW
223 (re-search-forward "^From:" nil t))
224 (error "No more messages in digest")))
c26cf6c8
RS
225 ;; Go back to previous blank line, then forward to the first non-blank.
226 (search-backward "\n\n" nil t)
227 (forward-line 2)
228 (mh-recenter 0)))
229
c3d9274a 230;;;###mh-autoload
c26cf6c8
RS
231(defun mh-page-digest-backwards ()
232 "Back up displayed message to previous digested message."
233 (interactive)
234 (mh-in-show-buffer (mh-show-buffer)
235 ;; Go to top of screen (in case user moved point).
236 (move-to-window-line 0)
237 (let ((case-fold-search nil))
238 (beginning-of-line)
239 (or (and (search-backward "\n\n" nil t)
c3d9274a
BW
240 (re-search-backward "^From:" nil t))
241 (error "No previous message in digest")))
c26cf6c8
RS
242 ;; Go back to previous blank line, then forward to the first non-blank.
243 (if (search-backward "\n\n" nil t)
c3d9274a 244 (forward-line 2))
c26cf6c8
RS
245 (mh-recenter 0)))
246
c3d9274a 247;;;###mh-autoload
c26cf6c8 248(defun mh-print-msg (msg-or-seq)
a1b4049d 249 "Print MSG-OR-SEQ (default: displayed message) on printer.
c26cf6c8 250If optional prefix argument provided, then prompt for the message sequence.
a1b4049d 251The variable `mh-lpr-command-format' is used to generate the print command.
bdcfe844 252The messages are formatted by mhl. See the variable `mhl-formfile'."
c26cf6c8 253 (interactive (list (if current-prefix-arg
c3d9274a
BW
254 (reverse (mh-seq-to-msgs
255 (mh-read-seq-default "Print" t)))
256 (mh-get-msg-num t))))
c26cf6c8
RS
257 (if (numberp msg-or-seq)
258 (message "Printing message...")
c3d9274a 259 (message "Printing sequence..."))
c26cf6c8 260 (let ((print-command
c3d9274a
BW
261 (if (numberp msg-or-seq)
262 (format "%s -nobell -clear %s %s | %s"
263 (expand-file-name "mhl" mh-lib-progs)
264 (mh-msg-filename msg-or-seq)
265 (if (stringp mhl-formfile)
266 (format "-form %s" mhl-formfile)
267 "")
268 (format mh-lpr-command-format
269 (if (numberp msg-or-seq)
270 (format "%s/%d" mh-current-folder
271 msg-or-seq)
272 (format "Sequence from %s" mh-current-folder))))
273 (format "(scan -clear %s ; %s -nobell -clear %s %s) | %s"
274 (mapconcat (function (lambda (msg) msg)) msg-or-seq " ")
275 (expand-file-name "mhl" mh-lib-progs)
276 (if (stringp mhl-formfile)
277 (format "-form %s" mhl-formfile)
278 "")
279 (mh-msg-filenames msg-or-seq)
280 (format mh-lpr-command-format
281 (if (numberp msg-or-seq)
282 (format "%s/%d" mh-current-folder
283 msg-or-seq)
284 (format "Sequence from %s"
285 mh-current-folder)))))))
bdcfe844 286 (if mh-print-background-flag
3d7ca223 287 (mh-exec-cmd-daemon shell-file-name nil "-c" print-command)
c26cf6c8
RS
288 (call-process shell-file-name nil nil nil "-c" print-command))
289 (if (numberp msg-or-seq)
c3d9274a
BW
290 (mh-notate msg-or-seq mh-note-printed mh-cmd-note)
291 (mh-notate-seq msg-or-seq mh-note-printed mh-cmd-note))
c26cf6c8
RS
292 (mh-add-msgs-to-seq msg-or-seq 'printed t)
293 (if (numberp msg-or-seq)
c3d9274a
BW
294 (message "Printing message...done")
295 (message "Printing sequence...done"))))
c26cf6c8 296
c26cf6c8 297(defun mh-msg-filenames (msgs &optional folder)
bdcfe844 298 "Return a list of file names for MSGS in FOLDER (default current folder)."
c26cf6c8
RS
299 (mapconcat (function (lambda (msg) (mh-msg-filename msg folder))) msgs " "))
300
c3d9274a 301;;;###mh-autoload
847b8219 302(defun mh-sort-folder (&optional extra-args)
c26cf6c8
RS
303 "Sort the messages in the current folder by date.
304Calls the MH program sortm to do the work.
bdcfe844
BW
305The arguments in the list `mh-sortm-args' are passed to sortm if the optional
306argument EXTRA-ARGS is given."
c26cf6c8
RS
307 (interactive "P")
308 (mh-process-or-undo-commands mh-current-folder)
309 (setq mh-next-direction 'forward)
c3d9274a 310 (mh-set-folder-modified-p t) ; lock folder while sorting
c26cf6c8 311 (message "Sorting folder...")
c3d9274a
BW
312 (let ((threaded-flag (memq 'unthread mh-view-ops)))
313 (mh-exec-cmd "sortm" mh-current-folder (if extra-args mh-sortm-args))
314 (when mh-index-data
315 (mh-index-update-maps mh-current-folder))
316 (message "Sorting folder...done")
317 (mh-reset-threads-and-narrowing)
318 (mh-scan-folder mh-current-folder "all")
319 (cond (threaded-flag (mh-toggle-threads))
320 (mh-index-data (mh-index-insert-folder-headers)))))
321
322;;;###mh-autoload
c26cf6c8 323(defun mh-undo-folder (&rest ignore)
a1b4049d
BW
324 "Undo all pending deletes and refiles in current folder.
325Argument IGNORE is deprecated."
c26cf6c8 326 (interactive)
bdcfe844 327 (cond ((or mh-do-not-confirm-flag
c3d9274a
BW
328 (yes-or-no-p "Undo all commands in folder? "))
329 (setq mh-delete-list nil
330 mh-refile-list nil
331 mh-seq-list nil
332 mh-next-direction 'forward)
333 (with-mh-folder-updating (nil)
334 (mh-unmark-all-headers t)))
335 (t
336 (message "Commands not undone.")
337 (sit-for 2))))
338
339;;;###mh-autoload
847b8219 340(defun mh-store-msg (directory)
c26cf6c8
RS
341 "Store the file(s) contained in the current message into DIRECTORY.
342The message can contain a shar file or uuencoded file.
343Default directory is the last directory used, or initially the value of
a1b4049d 344`mh-store-default-directory' or the current directory."
c3d9274a
BW
345 (interactive (list (let ((udir (or mh-store-default-directory
346 default-directory)))
347 (read-file-name "Store message in directory: "
348 udir udir nil))))
847b8219 349 (let ((msg-file-to-store (mh-msg-filename (mh-get-msg-num t))))
c26cf6c8 350 (save-excursion
847b8219 351 (set-buffer (get-buffer-create mh-temp-buffer))
c26cf6c8 352 (erase-buffer)
847b8219
KH
353 (insert-file-contents msg-file-to-store)
354 (mh-store-buffer directory))))
c26cf6c8 355
c3d9274a 356;;;###mh-autoload
847b8219 357(defun mh-store-buffer (directory)
c26cf6c8
RS
358 "Store the file(s) contained in the current buffer into DIRECTORY.
359The buffer can contain a shar file or uuencoded file.
360Default directory is the last directory used, or initially the value of
361`mh-store-default-directory' or the current directory."
bdcfe844 362 (interactive (list (let ((udir (or mh-store-default-directory
c3d9274a
BW
363 default-directory)))
364 (read-file-name "Store buffer in directory: "
365 udir udir nil))))
847b8219 366 (let ((store-directory (expand-file-name directory))
c3d9274a
BW
367 (sh-start (save-excursion
368 (goto-char (point-min))
369 (if (re-search-forward
370 "^#![ \t]*/bin/sh\\|^#\\|^: " nil t)
371 (progn
372 ;; The "cut here" pattern was removed from above
373 ;; because it seemed to hurt more than help.
374 ;; But keep this to make it easier to put it back.
375 (if (looking-at "^[^a-z0-9\"]*cut here\\b")
376 (forward-line 1))
377 (beginning-of-line)
378 (if (looking-at "^[#:]....+\n\\( ?\n\\)?end$")
379 nil ;most likely end of a uuencode
380 (point))))))
381 (log-buffer (get-buffer-create "*Store Output*"))
382 (command "sh")
383 (uudecode-filename "(unknown filename)"))
847b8219 384 (if (not sh-start)
c3d9274a
BW
385 (save-excursion
386 (goto-char (point-min))
387 (if (re-search-forward "^begin [0-7]+ " nil t)
388 (setq uudecode-filename
389 (buffer-substring (point)
390 (progn (end-of-line) (point)))))))
c26cf6c8
RS
391 (save-excursion
392 (set-buffer log-buffer)
393 (erase-buffer)
394 (if (not (file-directory-p store-directory))
c3d9274a
BW
395 (progn
396 (insert "mkdir " directory "\n")
397 (call-process "mkdir" nil log-buffer t store-directory)))
847b8219
KH
398 (insert "cd " directory "\n")
399 (setq mh-store-default-directory directory)
400 (if (not sh-start)
c3d9274a
BW
401 (progn
402 (setq command "uudecode")
403 (insert uudecode-filename " being uudecoded...\n"))))
c26cf6c8 404 (set-window-start (display-buffer log-buffer) 0) ;watch progress
847b8219
KH
405 (let (value)
406 (let ((default-directory (file-name-as-directory store-directory)))
c3d9274a
BW
407 (setq value (call-process-region sh-start (point-max) command
408 nil log-buffer t)))
847b8219
KH
409 (set-buffer log-buffer)
410 (mh-handle-process-error command value))
c26cf6c8 411 (insert "\n(mh-store finished)\n")))
bdcfe844
BW
412
413\f
414
415;;; Help Functions
416
417(defun mh-ephem-message (string)
418 "Display STRING in the minibuffer momentarily."
419 (message "%s" string)
420 (sit-for 5)
421 (message ""))
422
c3d9274a 423;;;###mh-autoload
bdcfe844
BW
424(defun mh-help ()
425 "Display cheat sheet for the MH-Folder commands in minibuffer."
426 (interactive)
427 (mh-ephem-message
428 (substitute-command-keys
429 (mapconcat 'identity (cdr (assoc nil mh-help-messages)) ""))))
a1506d29 430
c3d9274a 431;;;###mh-autoload
bdcfe844
BW
432(defun mh-prefix-help ()
433 "Display cheat sheet for the commands of the current prefix in minibuffer."
434 (interactive)
435 ;; We got here because the user pressed a `?', but he pressed a prefix key
436 ;; before that. Since the the key vector starts at index 0, the index of the
437 ;; last keystroke is length-1 and thus the second to last keystroke is at
438 ;; length-2. We use that information to obtain a suitable prefix character
439 ;; from the recent keys.
440 (let* ((keys (recent-keys))
c3d9274a 441 (prefix-char (elt keys (- (length keys) 2))))
bdcfe844
BW
442 (mh-ephem-message
443 (substitute-command-keys
444 (mapconcat 'identity (cdr (assoc prefix-char mh-help-messages)) "")))))
445
446(provide 'mh-funcs)
447
448;;; Local Variables:
c3d9274a 449;;; indent-tabs-mode: nil
bdcfe844
BW
450;;; sentence-end-double-space: nil
451;;; End:
452
60370d40 453;;; mh-funcs.el ends here