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