|
|
1.1 ! root 1: ;;; mh-e.el (Version: 3.6 for GNU Emacs Version 18 and MH.5 and MH.6) ! 2: ! 3: (defvar mh-e-RCS-id) ! 4: (setq mh-e-RCS-id "$Header: mh-e.el,v 2.24 88/08/29 12:07:53 larus Exp $") ! 5: (provide 'mh-e) ! 6: ! 7: ;;; Copyright (C) 1985, 1986, 1987, 1988 Free Software Foundation, Inc. ! 8: ;;; Author: James Larus ([email protected] or ucbvax!larus) ! 9: ;;; Please send suggestions and corrections to the above address. ! 10: ;;; ! 11: ;;; This file contains mh-e, a GNU Emacs front end to the MH mail system. ! 12: ! 13: ! 14: ;; GNU Emacs is distributed in the hope that it will be useful, ! 15: ;; but without any warranty. No author or distributor ! 16: ;; accepts responsibility to anyone for the consequences of using it ! 17: ;; or for whether it serves any particular purpose or works at all, ! 18: ;; unless he says so in writing. ! 19: ! 20: ;; Everyone is granted permission to copy, modify and redistribute ! 21: ;; GNU Emacs, but only under the conditions described in the ! 22: ;; document "GNU Emacs copying permission notice". An exact copy ! 23: ;; of the document is supposed to have been given to you along with ! 24: ;; GNU Emacs so that you can know how you may redistribute it all. ! 25: ;; It should be in a file named COPYING. Among other things, the ! 26: ;; copyright notice and this notice must be preserved on all copies. ! 27: ! 28: ! 29: ;;; Original version for Gosling emacs by Brian Reid, Stanford, 1982. ! 30: ;;; Modified by James Larus, BBN, July 1984 and UCB, 1984 & 1985. ! 31: ;;; Rewritten for GNU Emacs, James Larus 1985. [email protected] ! 32: ;;; Modified by Stephen Gildea 1988. [email protected] ! 33: ! 34: ! 35: ;;; NB. MH must have been compiled with the MHE compiler flag or several ! 36: ;;; features necessary mh-e will be missing from MH commands, specifically ! 37: ;;; the -build switch to repl and forw. ! 38: ! 39: ! 40: ! 41: ;;; Constants: ! 42: ! 43: ;;; Set for local environment: ! 44: ;;;* These are now in paths.el. ! 45: ;;;(defvar mh-progs "/usr/new/mh/" "Directory containing MH commands") ! 46: ;;;(defvar mh-lib "/usr/new/lib/mh/" "Directory of MH library") ! 47: ! 48: (defvar mh-redist-full-contents t ! 49: "Non-nil if the `dist' command needs whole letter for redistribution (i.e., ! 50: when `send' is compiled with the BERK option). Nil otherwise.") ! 51: ! 52: ! 53: ;;; Mode hooks: ! 54: ! 55: (defvar mh-folder-mode-hook nil ! 56: "*Invoked in mh-folder-mode on a new folder.") ! 57: ! 58: (defvar mh-letter-mode-hook nil ! 59: "*Invoked in mh-letter-mode on a new letter.") ! 60: ! 61: (defvar mh-compose-letter-hook nil ! 62: "*Invoked in mh-compose-and-send-mail on an outgoing letter. It is passed ! 63: three arguments: TO recipients, SUBJECT, and CC recipients.") ! 64: ! 65: (defvar mh-inc-folder-hook nil ! 66: "*Invoked after incorporating new mail into a folder.") ! 67: ! 68: ! 69: ! 70: ;;; Personal preferences: ! 71: ! 72: (defvar mh-clean-message-header nil ! 73: "*Non-nil means remove invisible header lines or only show visible header ! 74: lines in messages.") ! 75: ! 76: (defvar mh-visible-headers nil ! 77: "*If non-nil, it contains a regexp specifying the headers that are shown in ! 78: a message if mh-clean-message-header is non-nil. Setting this variable ! 79: overrides mh-invisible-headers.") ! 80: ! 81: (defvar mhl-formfile nil ! 82: "*Name of format file to be used by mhl to show messages. ! 83: A value of T means use the default format file. ! 84: Nil means don't use mhl to format messages.") ! 85: ! 86: (defvar mh-lpr-command-format "lpr -p -J '%s'" ! 87: "*Format for Unix command line to print a message. The format should be ! 88: a unix command line, with the string \"%s\" where the folder and message ! 89: number should appear.") ! 90: ! 91: (defvar mh-print-background nil ! 92: "*Print messages in the background if non-nil. WARNING: do not delete ! 93: the messages until printing is finished; otherwise, your output may be ! 94: truncated.") ! 95: ! 96: (defvar mh-summary-height 4 ! 97: "*Number of lines in summary window.") ! 98: ! 99: (defvar mh-recenter-summary-p nil ! 100: "*Recenter summary window when the show window is toggled off if ! 101: this is non-nil.") ! 102: ! 103: (defvar mh-ins-buf-prefix ">> " ! 104: "*String to put before each non-blank line of the the current message ! 105: as it is inserted in an outgoing letter.") ! 106: ! 107: (defvar mh-do-not-confirm nil ! 108: "*Non-nil means do not prompt for confirmation before executing some ! 109: innocuous commands.") ! 110: ! 111: (defvar mh-bury-show-buffer t ! 112: "*Non-nil means that the displayed show buffer for a folder is buried.") ! 113: ! 114: (defvar mh-delete-yanked-msg-window nil ! 115: "*If non-nil, yanking the current message into a letter being composed, ! 116: with \\[mh-yank-cur-msg], deletes any windows displaying the message.") ! 117: ! 118: (defvar mh-yank-from-start-of-msg t ! 119: "*If non-nil, \\[mh-yank-cur-msg] will include the entire message. If ! 120: `body' then the message minus the header will be yanked. If nil, only the ! 121: portion of the message following the point will be yanked. If there is a ! 122: region in the show buffer, this variable is ignored.") ! 123: ! 124: (defvar mh-reply-default-reply-to nil ! 125: "*If non-nil, then \\[mh-reply] will use this as the person or persons to ! 126: which the reply will be sent. The value should be one of \"from\", \"to\", or ! 127: \"cc\".") ! 128: ! 129: (defvar mh-recursive-folders nil ! 130: "*If non-nil, then commands which operate on folders do so recursively.") ! 131: ! 132: ! 133: ;;; Parameterize mh-e to work with different scan formats. The defaults work ! 134: ;;; the standard MH scan listings. ! 135: ! 136: (defvar mh-cmd-note 4 ! 137: "Offset to insert notation") ! 138: ! 139: (defvar mh-good-msg-regexp "^....[^D^]" ! 140: "Regexp specifiying the scan lines that are 'good' messages.") ! 141: ! 142: (defvar mh-deleted-msg-regexp "^....D" ! 143: "Regexp matching scan lines of deleted messages.") ! 144: ! 145: (defvar mh-refiled-msg-regexp "^....\\^" ! 146: "Regexp matching scan lines of refiled messages.") ! 147: ! 148: (defvar mh-valid-scan-line "^[ ]*[0-9]" ! 149: "Regexp matching scan lines for messages (not error messages).") ! 150: ! 151: (defvar mh-msg-number-regexp "^[ ]*\\([0-9]+\\)" ! 152: "Regexp matching the number of a message in a scan line. It must surround ! 153: the number with \\( \\)") ! 154: ! 155: (defvar mh-msg-search-regexp "^[^0-9]*%d[^0-9]" ! 156: "String for format that will return a regexp matching the scan listing for ! 157: a given message number.") ! 158: ! 159: (defvar mh-flagged-scan-msg-regexp "^....\\D\\|^....\\^\\|^....\\+\\|^.....%" ! 160: "Regexp matching scan lines marked as deleted, refiled, in a sequence, or ! 161: the cur message.") ! 162: ! 163: (defvar mh-cur-scan-msg-regexp "^....\\+" ! 164: "regexp matching scan line for the cur message.") ! 165: ! 166: ! 167: ;;; Real constants: ! 168: ! 169: (defvar mh-invisible-headers ! 170: "^Received: \\|^Message-Id: \\|^Remailed-\\|^Via: \\|^Mail-from: \\|^Return-Path: \\|^In-Reply-To: \\|^Resent-" ! 171: "Regexp specifying headers that are not to be shown.") ! 172: ! 173: (defvar mh-rejected-letter-start "^ ----- Unsent message follows -----$" ! 174: "Regexp specifying the beginning of the wrapper around a letter returned ! 175: by the mail system.") ! 176: ! 177: (defvar mh-to-field-choices '((?t . "To:") (?s . "Subject:") (?c . "Cc:") ! 178: (?b . "Bcc:") (?f . "Fcc:")) ! 179: "A-list of (character . field name) strings for mh-to-field.") ! 180: ! 181: ! 182: ;;; Global variables: ! 183: ! 184: (defvar mh-user-path "" ! 185: "User's mail folder.") ! 186: ! 187: (defvar mh-last-destination nil ! 188: "Destination of last `refile' command.") ! 189: ! 190: (defvar mh-folder-mode-map (make-keymap) ! 191: "Keymap for MH folders.") ! 192: ! 193: (defvar mh-letter-mode-map (make-sparse-keymap) ! 194: "Keymap for composing mail.") ! 195: ! 196: (defvar mh-pick-mode-map (make-sparse-keymap) ! 197: "Keymap for searching folder.") ! 198: ! 199: (defvar mh-letter-mode-syntax-table nil ! 200: "Syntax table used while in mh-e letter mode.") ! 201: ! 202: (if mh-letter-mode-syntax-table ! 203: () ! 204: (setq mh-letter-mode-syntax-table ! 205: (make-syntax-table text-mode-syntax-table)) ! 206: (set-syntax-table mh-letter-mode-syntax-table) ! 207: (modify-syntax-entry ?% "." mh-letter-mode-syntax-table)) ! 208: ! 209: (defvar mh-folder-list nil ! 210: "List of folder names for completion.") ! 211: ! 212: (defvar mh-draft-folder nil ! 213: "Name of folder containing draft messages. ! 214: NIL means do not use draft folder.") ! 215: ! 216: (defvar mh-unseen-seq nil ! 217: "Name of the unseen sequence.") ! 218: ! 219: ! 220: ;;; Macros and generic functions: ! 221: ! 222: (defmacro mh-push (v l) ! 223: (list 'setq l (list 'cons v l))) ! 224: ! 225: (defmacro when (pred &rest body) ! 226: (list 'cond (cons pred body))) ! 227: ! 228: (defun mapc (func list) ! 229: (while list ! 230: (funcall func (car list)) ! 231: (setq list (cdr list)))) ! 232: ! 233: ! 234: (defun mh-list* (&rest args) (mh-make-list* args)) ! 235: ! 236: (defun mh-make-list* (arglist) ! 237: (cond ((null arglist) ()) ! 238: ((null (cdr arglist)) (car arglist)) ! 239: (t (cons (car arglist) (mh-make-list* (cdr arglist)))))) ! 240: ! 241: ! 242: ! 243: ;;; Entry points: ! 244: ! 245: (defun mh-rmail (&optional arg) ! 246: "Inc(orporate) new mail (no arg) or scan a MH mail box (arg given). ! 247: This front end uses the MH mail system, which uses different conventions ! 248: from the usual mail system." ! 249: (interactive "P") ! 250: (mh-find-path) ! 251: (if arg ! 252: (call-interactively 'mh-visit-folder) ! 253: (mh-inc-folder))) ! 254: ! 255: ! 256: (defun mh-smail () ! 257: "Send mail using the MH mail system." ! 258: (interactive) ! 259: (mh-find-path) ! 260: (call-interactively 'mh-send)) ! 261: ! 262: ! 263: (defun mh-smail-other-window () ! 264: "Send mail in other window using the MH mail system." ! 265: (interactive) ! 266: (mh-find-path) ! 267: (call-interactively 'mh-send-other-window)) ! 268: ! 269: ! 270: ! 271: ;;; User executable mh-e commands: ! 272: ! 273: (defun mh-burst-digest () ! 274: "Burst apart the current message, which should be a digest. Message is ! 275: replaced by its table of contents and the letters from the digest are inserted ! 276: into the folder after that message." ! 277: (interactive) ! 278: (let ((digest (mh-get-msg-num t))) ! 279: (mh-process-or-undo-commands mh-current-folder) ! 280: (message "Bursting digest...") ! 281: (mh-exec-cmd "burst" mh-current-folder digest "-inplace") ! 282: (mh-scan-folder mh-current-folder (format "%d-last" mh-first-msg-num)) ! 283: (message "Bursting digest...done"))) ! 284: ! 285: ! 286: (defun mh-copy-msg (prefix-provided msg-or-seq dest) ! 287: "Copy specified MESSAGE(s) (default: displayed message) to another ! 288: FOLDER without deleting them. ! 289: If (optional) prefix argument provided, then prompt for the message sequence." ! 290: (interactive (list current-prefix-arg ! 291: (if current-prefix-arg ! 292: (mh-read-seq "Copy" t mh-narrowed-to-seq) ! 293: (mh-get-msg-num t)) ! 294: (mh-prompt-for-folder "Copy to" "" t))) ! 295: (mh-exec-cmd "refile" msg-or-seq "-link" "-src" mh-current-folder dest) ! 296: (if prefix-provided ! 297: (mh-notate-seq msg-or-seq ?C mh-cmd-note) ! 298: (mh-notate msg-or-seq ?C mh-cmd-note))) ! 299: ! 300: ! 301: (defun mh-delete-msg (prefix-provided msg-or-seq) ! 302: "Mark the specified MESSAGE(s) (default: displayed message) for later ! 303: deletion. ! 304: If (optional) prefix argument provided, then prompt for the message sequence." ! 305: (interactive (list current-prefix-arg ! 306: (if current-prefix-arg ! 307: (mh-read-seq "Delete" t mh-narrowed-to-seq) ! 308: (mh-get-msg-num t)))) ! 309: (if prefix-provided ! 310: (mh-map-to-seq-msgs 'mh-delete-a-msg msg-or-seq) ! 311: (mh-delete-a-msg msg-or-seq)) ! 312: (mh-next-msg)) ! 313: ! 314: ! 315: (defun mh-delete-msg-from-seq (prefix-provided msg-or-seq &optional from-seq) ! 316: "Delete MESSAGE (default: displayed message) from SEQUENCE. ! 317: If (optional) prefix argument provided, then delete all messages from a ! 318: sequence." ! 319: (interactive (let ((argp current-prefix-arg)) ! 320: (list argp ! 321: (if argp ! 322: (mh-read-seq "Delete" t mh-narrowed-to-seq) ! 323: (mh-get-msg-num t)) ! 324: (if (not argp) ! 325: (mh-read-seq "Delete from" t mh-narrowed-to-seq))))) ! 326: (if prefix-provided ! 327: (mh-remove-seq msg-or-seq) ! 328: (mh-remove-msg-from-seq msg-or-seq from-seq))) ! 329: ! 330: ! 331: (defun mh-edit-again (msg) ! 332: "Clean-up a draft or a message previously sent and make it resendable." ! 333: (interactive (list (mh-get-msg-num t))) ! 334: (let* ((from-folder mh-current-folder) ! 335: (config (current-window-configuration)) ! 336: (draft ! 337: (cond ((and mh-draft-folder (equal from-folder mh-draft-folder)) ! 338: (find-file (mh-msg-filename msg)) ! 339: (rename-buffer (format "draft-%d" msg)) ! 340: (buffer-name)) ! 341: (t ! 342: (mh-read-draft "clean-up" (mh-msg-filename msg) nil))))) ! 343: (mh-clean-msg-header (point-min) ! 344: "^Date:\\|^Received:\\|^Message-Id:\\|^From:" ! 345: nil) ! 346: (goto-char (point-min)) ! 347: (set-buffer-modified-p nil) ! 348: (mh-compose-and-send-mail draft "" from-folder nil nil nil nil nil nil ! 349: config))) ! 350: ! 351: ! 352: (defun mh-execute-commands () ! 353: "Process outstanding delete and refile requests." ! 354: (interactive) ! 355: (if mh-narrowed-to-seq (mh-widen)) ! 356: (save-excursion ! 357: (mh-process-commands mh-current-folder)) ! 358: (mh-goto-cur-msg) ! 359: (mh-set-scan-mode) ! 360: (mh-make-folder-mode-line)) ! 361: ! 362: ! 363: (defun mh-extract-rejected-mail (msg) ! 364: "Extract a letter returned by the mail system (default: displayed message) ! 365: and make it resendable." ! 366: (interactive (list (mh-get-msg-num t))) ! 367: (let ((from-folder mh-current-folder) ! 368: (config (current-window-configuration)) ! 369: (draft (mh-read-draft "extraction" (mh-msg-filename msg) nil))) ! 370: (goto-char (point-min)) ! 371: (cond ((re-search-forward mh-rejected-letter-start nil t) ! 372: (forward-char 1) ! 373: (delete-region (point-min) (point)) ! 374: (mh-clean-msg-header (point-min) ! 375: "^Date:\\|^Received:\\|^Message-Id:\\|^From:\\|^Sender:" ! 376: nil)) ! 377: (t ! 378: (message "Does not appear to be a rejected letter."))) ! 379: (goto-char (point-min)) ! 380: (set-buffer-modified-p nil) ! 381: (mh-compose-and-send-mail draft "" from-folder msg (mh-get-field "To") ! 382: (mh-get-field "From") (mh-get-field "cc") ! 383: nil nil config))) ! 384: ! 385: ! 386: (defun mh-forward (prefix-provided msg-or-seq to cc) ! 387: "Forward MESSAGE(s) (default: displayed message). ! 388: If (optional) prefix argument provided, then prompt for the message sequence." ! 389: (interactive (list current-prefix-arg ! 390: (if current-prefix-arg ! 391: (mh-read-seq "Forward" t mh-narrowed-to-seq) ! 392: (mh-get-msg-num t)) ! 393: (read-string "To: ") ! 394: (read-string "Cc: "))) ! 395: (let* ((folder mh-current-folder) ! 396: (config (current-window-configuration)) ! 397: ;; forw always leaves file in "draft" since it doesn't have -draft ! 398: (draft-name (mh-expand-file-name "draft" mh-user-path)) ! 399: (draft (cond ((or (not (file-exists-p draft-name)) ! 400: (y-or-n-p "The file 'draft' exists. Discard it? ")) ! 401: (mh-exec-cmd "forw" "-build" ! 402: mh-current-folder msg-or-seq) ! 403: (prog1 ! 404: (mh-read-draft "" draft-name t) ! 405: (mh-insert-fields "To:" to "Cc:" cc) ! 406: (set-buffer-modified-p nil))) ! 407: (t ! 408: (mh-read-draft "" draft-name nil))))) ! 409: (goto-char (point-min)) ! 410: (re-search-forward "^------- Forwarded Message") ! 411: (previous-line 1) ! 412: (narrow-to-region (point) (point-max)) ! 413: (let* ((subject (save-excursion (mh-get-field "From:"))) ! 414: (trim (string-match "<" subject)) ! 415: (forw-subject (save-excursion (mh-get-field "Subject:")))) ! 416: (if trim ! 417: (setq subject (substring subject 0 (- trim 1)))) ! 418: (widen) ! 419: (save-excursion ! 420: (mh-insert-fields "Subject:" (format "[%s: %s]" subject forw-subject))) ! 421: (delete-other-windows) ! 422: (if prefix-provided ! 423: (mh-add-msgs-to-seq (mh-seq-to-msgs msg-or-seq) 'forwarded t) ! 424: (mh-add-msgs-to-seq msg-or-seq 'forwarded t)) ! 425: (mh-compose-and-send-mail draft "" folder msg-or-seq ! 426: to subject cc ! 427: "F" "Forwarded:" ! 428: config)))) ! 429: ! 430: ! 431: (defun mh-goto-msg (number &optional no-error-if-no-message dont-show) ! 432: "Position the cursor at message NUMBER. ! 433: Non-nil second argument means do not signal an error if message does not exist. ! 434: Non-nil third argument means not to show the message. ! 435: Return non-nil if cursor is at message." ! 436: (interactive "NMessage number? ") ! 437: (let ((cur-msg (mh-get-msg-num nil)) ! 438: (starting-place (point)) ! 439: (msg-pattern (mh-msg-search-pat number))) ! 440: (cond ((cond ((and cur-msg (= cur-msg number)) t) ! 441: ((and cur-msg ! 442: (< cur-msg number) ! 443: (re-search-forward msg-pattern nil t)) t) ! 444: ((and cur-msg ! 445: (> cur-msg number) ! 446: (re-search-backward msg-pattern nil t)) t) ! 447: (t ; Do thorough search of buffer ! 448: (goto-char (point-min)) ! 449: (re-search-forward msg-pattern nil t))) ! 450: (beginning-of-line) ! 451: (if (not dont-show) (mh-maybe-show number)) ! 452: t) ! 453: (t ! 454: (goto-char starting-place) ! 455: (if (not no-error-if-no-message) ! 456: (error "No message %d " number)) ! 457: nil)))) ! 458: ! 459: ! 460: (defun mh-inc-folder (&optional maildrop-name) ! 461: "Inc(orporate) new mail into +inbox. ! 462: Optional prefix argument specifies an alternate maildrop from the default. ! 463: If this is given, mail is incorporated into the current folder, rather ! 464: than +inbox." ! 465: (interactive (list (if current-prefix-arg ! 466: (expand-file-name ! 467: (read-file-name "inc mail from file: " ! 468: mh-user-path))))) ! 469: (let ((config (current-window-configuration))) ! 470: (if (not maildrop-name) ! 471: (cond ((not (get-buffer "+inbox")) ! 472: (mh-make-folder "+inbox") ! 473: (setq mh-previous-window-config config)) ! 474: ((not (eq (current-buffer) (get-buffer "+inbox"))) ! 475: (switch-to-buffer "+inbox") ! 476: (setq mh-previous-window-config config))))) ! 477: (mh-get-new-mail maildrop-name) ! 478: (run-hooks 'mh-inc-folder-hook)) ! 479: ! 480: ! 481: (defun mh-kill-folder () ! 482: "Remove the current folder." ! 483: (interactive) ! 484: (if (or mh-do-not-confirm ! 485: (yes-or-no-p (format "Remove folder %s? " mh-current-folder))) ! 486: (let ((folder mh-current-folder)) ! 487: (mh-exec-cmd-demon "rmf" folder) ! 488: (mh-remove-folder-from-folder-list folder) ! 489: (message "Folder removed") ! 490: (kill-buffer folder)) ! 491: (message "Folder not removed"))) ! 492: ! 493: ! 494: (defun mh-list-folders () ! 495: "List mail folders." ! 496: (interactive) ! 497: (with-output-to-temp-buffer " *mh-temp*" ! 498: (save-excursion ! 499: (switch-to-buffer " *mh-temp*") ! 500: (erase-buffer) ! 501: (message "listing folders...") ! 502: (mh-exec-cmd-output "folders" t) ! 503: (goto-char (point-min)) ! 504: (message "listing folders...done")))) ! 505: ! 506: ! 507: (defun mh-msg-is-in-seq (msg) ! 508: "Display the sequences that contain MESSAGE (default: displayed message)." ! 509: (interactive (list (mh-get-msg-num t))) ! 510: (message "Message %d is in sequences: %s" ! 511: msg ! 512: (mapconcat 'concat ! 513: (mh-list-to-string (mh-seq-containing-msg msg)) ! 514: " "))) ! 515: ! 516: ! 517: (defun mh-narrow-to-seq (seq) ! 518: "Restrict display of this folder to just messages in a sequence. ! 519: Reads which sequence. Use \\[mh-widen] to undo this command." ! 520: (interactive (list (mh-read-seq "Narrow to" t))) ! 521: (let ((eob (point-max)) ! 522: (buffer-read-only nil)) ! 523: (cond ((mh-seq-to-msgs seq) ! 524: (mh-copy-seq-to-point seq eob) ! 525: (narrow-to-region eob (point-max)) ! 526: (mh-make-folder-mode-line (symbol-name seq)) ! 527: (recenter) ! 528: (setq mh-narrowed-to-seq seq)) ! 529: (t ! 530: (error "No messages in sequence `%s'" (symbol-name seq)))))) ! 531: ! 532: ! 533: (defun mh-next-undeleted-msg (&optional arg) ! 534: "Move to next undeleted message in window." ! 535: (interactive "p") ! 536: (forward-line (if arg arg 1)) ! 537: (setq mh-next-direction 'forward) ! 538: (cond ((re-search-forward mh-good-msg-regexp nil 0 arg) ! 539: (beginning-of-line) ! 540: (mh-maybe-show (mh-get-msg-num t))) ! 541: (t ! 542: (forward-line -1) ! 543: (if (get-buffer mh-show-buffer) ! 544: (delete-windows-on mh-show-buffer))))) ! 545: ! 546: ! 547: (defun mh-pack-folder () ! 548: "Execute any outstanding commands for the current folder, then renumber the ! 549: remaining messages to be 1..N." ! 550: (interactive) ! 551: (message "packing buffer...") ! 552: (mh-pack-folder-1) ! 553: (mh-goto-cur-msg) ! 554: (message "packing buffer...done")) ! 555: ! 556: ! 557: (defun mh-refile-msg (prefix-provided msg-or-seq dest) ! 558: "Refile MESSAGE(s) (default: displayed message) in FOLDER. ! 559: If (optional) prefix argument provided, then prompt for message sequence." ! 560: (interactive ! 561: (list current-prefix-arg ! 562: (if current-prefix-arg ! 563: (mh-read-seq "Refile" t mh-narrowed-to-seq) ! 564: (mh-get-msg-num t)) ! 565: (intern ! 566: (mh-prompt-for-folder "Destination" ! 567: (if (eq 'refile (car mh-last-destination)) ! 568: (symbol-name (cdr mh-last-destination)) ! 569: "") ! 570: t)))) ! 571: (setq mh-last-destination (cons 'refile dest)) ! 572: (if prefix-provided ! 573: (mh-map-to-seq-msgs 'mh-refile-a-msg msg-or-seq dest) ! 574: (mh-refile-a-msg msg-or-seq dest)) ! 575: (mh-next-msg)) ! 576: ! 577: ! 578: (defun mh-refile-or-write-again (msg) ! 579: "Re-execution the last refile or write command on the given MESSAGE (default: ! 580: displayed message). ! 581: Use the same folder or file as the previous refile or write command." ! 582: (interactive (list (mh-get-msg-num t))) ! 583: (if (null mh-last-destination) ! 584: (error "No previous refile")) ! 585: (cond ((eq (car mh-last-destination) 'refile) ! 586: (mh-refile-a-msg msg (cdr mh-last-destination)) ! 587: (message "Destination folder: %s" (cdr mh-last-destination))) ! 588: (t ! 589: (mh-write-msg-to-file msg (cdr mh-last-destination)) ! 590: (message "Destination: %s" (cdr mh-last-destination)))) ! 591: (mh-next-msg)) ! 592: ! 593: ! 594: (defun mh-reply (prefix-provided msg) ! 595: "Reply to a MESSAGE (default: displayed message). ! 596: If (optional) prefix argument provided, then include the message in the reply." ! 597: (interactive (list current-prefix-arg (mh-get-msg-num t))) ! 598: (let ((minibuffer-help-form ! 599: "from => Sender only\nto => Sender and primary recipients\ncc or all => Sender and all recipients")) ! 600: (let ((reply-to (or mh-reply-default-reply-to ! 601: (completing-read "Reply to whom: " ! 602: '(("from") ("to") ("cc") ("all")) ! 603: nil ! 604: t))) ! 605: (msg-filename (mh-msg-filename msg)) ! 606: (folder mh-current-folder) ! 607: (show-buffer mh-show-buffer) ! 608: (config (current-window-configuration))) ! 609: (message "Composing a reply...") ! 610: (cond ((or (equal reply-to "from") (equal reply-to "")) ! 611: (apply 'mh-exec-cmd ! 612: (mh-list* "repl" "-build" ! 613: "-nodraftfolder" mh-current-folder ! 614: msg ! 615: "-nocc" "all" ! 616: (if prefix-provided ! 617: (list "-filter" "mhl.reply"))))) ! 618: ((equal reply-to "to") ! 619: (apply 'mh-exec-cmd ! 620: (mh-list* "repl" "-build" ! 621: "-nodraftfolder" mh-current-folder ! 622: msg ! 623: "-cc" "to" ! 624: (if prefix-provided ! 625: (list "-filter" "mhl.reply"))))) ! 626: ((or (equal reply-to "cc") (equal reply-to "all")) ! 627: (apply 'mh-exec-cmd ! 628: (mh-list* "repl" "-build" ! 629: "-nodraftfolder" mh-current-folder ! 630: msg ! 631: "-cc" "all" "-nocc" "me" ! 632: (if prefix-provided ! 633: (list "-filter" "mhl.reply")))))) ! 634: ! 635: (let ((draft (mh-read-draft "reply" ! 636: (mh-expand-file-name "reply" mh-user-path) ! 637: t))) ! 638: (delete-other-windows) ! 639: (set-buffer-modified-p nil) ! 640: ! 641: (let ((to (mh-get-field "To:")) ! 642: (subject (mh-get-field "Subject:")) ! 643: (cc (mh-get-field "Cc:"))) ! 644: (goto-char (point-min)) ! 645: (mh-goto-header-end 1) ! 646: (if (not prefix-provided) ! 647: (mh-display-msg msg msg-filename show-buffer)) ! 648: (mh-add-msgs-to-seq msg 'answered t) ! 649: (message "Composing a reply...done") ! 650: (mh-compose-and-send-mail draft "" folder msg to subject cc ! 651: "-" "Replied:" config)))))) ! 652: ! 653: ! 654: (defun mh-restore-window-config () ! 655: "Restore the previous window configuration, if one exists." ! 656: (interactive) ! 657: (if mh-previous-window-config ! 658: (set-window-configuration mh-previous-window-config))) ! 659: ! 660: ! 661: (defun mh-page-digest () ! 662: "Advance displayed message to next digested message." ! 663: (interactive) ! 664: (save-excursion ! 665: (mh-show-message-in-other-window) ! 666: ;; Go to top of screen (in case user moved point). ! 667: (move-to-window-line 0) ! 668: (let ((case-fold-search nil)) ! 669: ;; Search for blank line and then for From: ! 670: (when (not (and (search-forward "\n\n" nil t) ! 671: (search-forward "From:" nil t))) ! 672: (other-window -1) ! 673: (error "No more messages."))) ! 674: ;; Go back to previous blank line, then forward to the first non-blank. ! 675: (search-backward "\n\n" nil t) ! 676: (forward-line 2) ! 677: (recenter 0) ! 678: (other-window -1))) ! 679: ! 680: ! 681: (defun mh-page-digest-backwards () ! 682: "Back up displayed message to previous digested message." ! 683: (interactive) ! 684: (save-excursion ! 685: (mh-show-message-in-other-window) ! 686: ;; Go to top of screen (in case user moved point). ! 687: (move-to-window-line 0) ! 688: (let ((case-fold-search nil)) ! 689: (beginning-of-line) ! 690: (when (not (and (search-backward "\n\n" nil t) ! 691: (search-backward "From:" nil t))) ! 692: (other-window -1) ! 693: (error "No more messages."))) ! 694: ;; Go back to previous blank line, then forward to the first non-blank. ! 695: (search-backward "\n\n" nil t) ! 696: (forward-line 2) ! 697: (recenter 0) ! 698: (other-window -1))) ! 699: ! 700: ! 701: (defun mh-page-msg (&optional arg) ! 702: "Page the displayed message forwards ARG lines or a full screen if no ! 703: argument is supplied." ! 704: (interactive "P") ! 705: (scroll-other-window arg)) ! 706: ! 707: ! 708: (defun mh-previous-page (&optional arg) ! 709: "Page the displayed message backwards ARG lines or a full screen if no ! 710: argument is supplied." ! 711: (interactive "P") ! 712: (save-excursion ! 713: (mh-show-message-in-other-window) ! 714: (unwind-protect ! 715: (scroll-down arg) ! 716: (other-window -1)))) ! 717: ! 718: ! 719: (defun mh-previous-undeleted-msg (&optional arg) ! 720: "Move to previous undeleted message in window." ! 721: (interactive "p") ! 722: (setq mh-next-direction 'backward) ! 723: (beginning-of-line 1) ! 724: (cond ((re-search-backward mh-good-msg-regexp nil 0 arg) ! 725: (mh-maybe-show (mh-get-msg-num t))) ! 726: (t ! 727: (if (get-buffer mh-show-buffer) ! 728: (delete-windows-on mh-show-buffer))))) ! 729: ! 730: ! 731: (defun mh-print-msg (prefix-provided msg-or-seq) ! 732: "Print MESSAGE(s) (default: displayed message) on a line printer. ! 733: If (optional) prefix argument provided, then prompt for the message sequence." ! 734: (interactive (list current-prefix-arg ! 735: (if current-prefix-arg ! 736: (reverse (mh-seq-to-msgs ! 737: (mh-read-seq "Print" t mh-narrowed-to-seq))) ! 738: (list (mh-get-msg-num t))))) ! 739: (if prefix-provided ! 740: (message "printing sequence...") ! 741: (message "printing message...")) ! 742: (let ((command ! 743: (if prefix-provided ! 744: (format "(scan -clear %s ; %s -nobell -clear %s %s) | %s" ! 745: (mapconcat (function (lambda (msg) msg)) msg-or-seq " ") ! 746: (mh-expand-file-name "mhl" mh-lib) ! 747: (if (stringp mhl-formfile) ! 748: (format "-form %s" mhl-formfile) ! 749: "") ! 750: (mh-msg-filenames msg-or-seq mh-folder-filename) ! 751: (format mh-lpr-command-format ! 752: (if prefix-provided ! 753: (format "Sequence from %s" mh-current-folder) ! 754: (format "%s/%d" mh-current-folder ! 755: (car msg-or-seq))))) ! 756: (format "%s -nobell -clear %s %s | %s" ! 757: (mh-expand-file-name "mhl" mh-lib) ! 758: (mh-msg-filenames msg-or-seq mh-folder-filename) ! 759: (if (stringp mhl-formfile) ! 760: (format "-form %s" mhl-formfile) ! 761: "") ! 762: (format mh-lpr-command-format ! 763: (if prefix-provided ! 764: (format "Sequence from %s" mh-current-folder) ! 765: (format "%s/%d" mh-current-folder ! 766: (car msg-or-seq)))))))) ! 767: (if mh-print-background ! 768: (mh-exec-cmd-demon shell-file-name "-c" command) ! 769: (call-process shell-file-name nil nil nil "-c" command)) ! 770: (if prefix-provided ! 771: (mh-notate-seq msg-or-seq ?P mh-cmd-note) ! 772: (mh-notate (car msg-or-seq) ?P mh-cmd-note)) ! 773: (mh-add-msgs-to-seq msg-or-seq 'printed t) ! 774: (if prefix-provided ! 775: (message "printing sequence...done") ! 776: (message "printing message...done")))) ! 777: ! 778: ! 779: (defun mh-put-msg-in-seq (prefix-provided from to) ! 780: "Add MESSAGE(s) (default: displayed message) to SEQUENCE. ! 781: If (optional) prefix argument provided, then prompt for the message sequence." ! 782: (interactive (list current-prefix-arg ! 783: (if current-prefix-arg ! 784: (mh-seq-to-msgs ! 785: (mh-read-seq "Add messages from" t ! 786: mh-narrowed-to-seq)) ! 787: (mh-get-msg-num t)) ! 788: (mh-read-seq "Add to" nil mh-narrowed-to-seq))) ! 789: (mh-add-msgs-to-seq from to)) ! 790: ! 791: ! 792: (defun mh-rescan-folder (range) ! 793: "Rescan a folder after optionally processing the outstanding commands. ! 794: If (optional) prefix argument provided, prompt for the range of messages to ! 795: display. Otherwise show the entire folder." ! 796: (interactive (list (if current-prefix-arg ! 797: (read-string "Range [all]? ") ! 798: "all"))) ! 799: (setq mh-next-direction 'forward) ! 800: (mh-scan-folder mh-current-folder range)) ! 801: ! 802: ! 803: (defun mh-redistribute (to cc msg) ! 804: "Redistribute a letter." ! 805: (interactive (list (read-string "Redist-To: ") ! 806: (read-string "Redist-Cc: ") ! 807: (mh-get-msg-num t))) ! 808: (save-window-excursion ! 809: (let ((msg-filename (mh-msg-filename msg)) ! 810: (folder mh-current-folder) ! 811: (draft (mh-read-draft "redistribution" ! 812: (if mh-redist-full-contents ! 813: (mh-msg-filename msg) ! 814: nil) ! 815: nil))) ! 816: (mh-goto-header-end 0) ! 817: (insert "Resent-To: " to "\n") ! 818: (if (not (equal cc "")) (insert "Resent-cc: " cc "\n")) ! 819: (mh-clean-msg-header (point-min) ! 820: "^Message-Id:\\|^Received:\\|^Return-Path:\\|^Sender:\\|^Date:\\|^From:" ! 821: nil) ! 822: (save-buffer) ! 823: (message "Redistributing...") ! 824: (if mh-redist-full-contents ! 825: (call-process "/bin/sh" nil 0 nil "-c" ! 826: (format "mhdist=1 mhaltmsg=%s %s -push %s" ! 827: (buffer-file-name) ! 828: (mh-expand-file-name "send" mh-progs) ! 829: (buffer-file-name))) ! 830: (call-process "/bin/sh" nil 0 nil "-c" ! 831: (format "mhdist=1 mhaltmsg=%s mhannotate=1 %s -push %s" ! 832: msg-filename ! 833: (mh-expand-file-name "send" mh-progs) ! 834: (buffer-file-name)))) ! 835: (mh-annotate-msg msg folder "R" ! 836: "-component" "Resent:" ! 837: "-text" (format "\"%s %s\"" to cc)) ! 838: (kill-buffer draft) ! 839: (message "Redistributing...done")))) ! 840: ! 841: ! 842: (defun mh-write-msg-to-file (msg file) ! 843: "Append MESSAGE to the end of a FILE." ! 844: (interactive (list (mh-get-msg-num t) ! 845: (expand-file-name ! 846: (read-file-name "Save message in file: " ! 847: (if (eq 'write (car mh-last-destination)) ! 848: (cdr mh-last-destination) ! 849: ""))))) ! 850: (setq mh-last-destination (cons 'write file)) ! 851: (let ((file-name (mh-msg-filename msg))) ! 852: (save-excursion ! 853: (set-buffer (get-buffer-create " *mh-temp*")) ! 854: (erase-buffer) ! 855: (insert-file-contents file-name) ! 856: (append-to-file (point-min) (point-max) file)))) ! 857: ! 858: ! 859: (defun mh-search-folder (folder) ! 860: "Search FOLDER for messages matching a pattern." ! 861: (interactive (list (mh-prompt-for-folder "Search" ! 862: mh-current-folder ! 863: t))) ! 864: (switch-to-buffer-other-window "pick-pattern") ! 865: (if (or (zerop (buffer-size)) ! 866: (not (y-or-n-p "Reuse pattern? "))) ! 867: (mh-make-pick-template) ! 868: (message "")) ! 869: (setq mh-searching-folder folder)) ! 870: ! 871: ! 872: (defun mh-send (to cc subject) ! 873: "Compose and send a letter." ! 874: (interactive "sTo: \nsCc: \nsSubject: ") ! 875: (let ((config (current-window-configuration))) ! 876: (delete-other-windows) ! 877: (mh-send-sub to cc subject config))) ! 878: ! 879: ! 880: (defun mh-send-other-window (to cc subject) ! 881: "Compose and send a letter in another window.." ! 882: (interactive "sTo: \nsCc: \nsSubject: ") ! 883: (let ((pop-up-windows t)) ! 884: (mh-send-sub to cc subject (current-window-configuration)))) ! 885: ! 886: ! 887: (defun mh-send-sub (to cc subject config) ! 888: "Do the real work of composing and sending a letter. ! 889: Expects the TO, CC, and SUBJECT fields as arguments. ! 890: CONFIG is the window configuration before sending mail." ! 891: (let ((folder (if (boundp 'mh-current-folder) mh-current-folder)) ! 892: (msg-num (mh-get-msg-num nil))) ! 893: (message "Composing a message...") ! 894: (let ((draft (mh-read-draft ! 895: "message" ! 896: (if (file-exists-p (mh-expand-file-name "components" ! 897: mh-user-path)) ! 898: (mh-expand-file-name "components" mh-user-path) ! 899: (if (file-exists-p (mh-expand-file-name "components" ! 900: mh-lib)) ! 901: (mh-expand-file-name "components" mh-lib) ! 902: (error "Can't find components file"))) ! 903: nil))) ! 904: (mh-insert-fields "To:" to "Subject:" subject "Cc:" cc) ! 905: (set-buffer-modified-p nil) ! 906: (goto-char (point-max)) ! 907: (message "Composing a message...done") ! 908: (mh-compose-and-send-mail draft "" folder msg-num ! 909: to subject cc ! 910: nil nil config)))) ! 911: ! 912: ! 913: (defun mh-show (msg) ! 914: "Show MESSAGE (default: displayed message)." ! 915: (interactive (list (mh-get-msg-num t))) ! 916: (setq mh-summarize nil) ! 917: (mh-set-mode-name "mh-e show") ! 918: (let ((folder mh-current-folder)) ! 919: (mh-display-msg msg (mh-msg-filename msg) mh-show-buffer) ! 920: ! 921: ;; These contortions are to force the summary line to be the top window. ! 922: (switch-to-buffer-other-window folder) ! 923: (delete-other-windows) ! 924: (mh-show-message-in-other-window) ! 925: (switch-to-buffer-other-window folder) ! 926: (shrink-window (- (window-height) mh-summary-height)) ! 927: (recenter '(4)) ;center this line ! 928: (if mh-bury-show-buffer (bury-buffer mh-show-buffer)) ! 929: (if (not (memq msg mh-seen-list)) (mh-push msg mh-seen-list)))) ! 930: ! 931: ! 932: (defun mh-sort-folder () ! 933: "Sort the messages in the current folder by date." ! 934: (interactive "") ! 935: (mh-process-or-undo-commands mh-current-folder) ! 936: (setq mh-next-direction 'forward) ! 937: (message "sorting folder...") ! 938: (mh-exec-cmd "sortm" mh-current-folder) ! 939: (message "sorting folder...done") ! 940: (mh-scan-folder mh-current-folder "all")) ! 941: ! 942: ! 943: (defun mh-toggle-summarize () ! 944: "Turn the summary mode of displaying messages on or off." ! 945: (interactive) ! 946: (if mh-summarize ! 947: (mh-show (mh-get-msg-num t)) ! 948: (mh-set-scan-mode))) ! 949: ! 950: ! 951: (defun mh-undo (prefix-provided msg-or-seq) ! 952: "Undo the deletion or refile of the specified MESSAGE(s) ! 953: \(default: displayed message). ! 954: If (optional) prefix argument provided, then prompt for the message sequence." ! 955: (interactive (list current-prefix-arg ! 956: (if current-prefix-arg ! 957: (mh-read-seq "Undo" t mh-narrowed-to-seq) ! 958: (mh-get-msg-num t)))) ! 959: (beginning-of-line) ! 960: (cond ((looking-at mh-deleted-msg-regexp) ! 961: (cond (prefix-provided ! 962: (mapc (function (lambda (msg) ! 963: (setq mh-delete-list ! 964: (delq msg mh-delete-list)) ! 965: (mh-remove-msg-from-seq msg 'deleted t))) ! 966: (mh-seq-to-msgs msg-or-seq)) ! 967: (mh-notate-seq msg-or-seq ? mh-cmd-note)) ! 968: (t ! 969: (setq mh-delete-list (delq msg-or-seq mh-delete-list)) ! 970: (mh-remove-msg-from-seq msg-or-seq 'deleted t) ! 971: (mh-notate msg-or-seq ? mh-cmd-note)))) ! 972: ! 973: ((looking-at mh-refiled-msg-regexp) ! 974: (cond (prefix-provided ! 975: (mapc (function (lambda (msg) ! 976: (mapc (function ! 977: (lambda (dest) ! 978: (mh-remove-msg-from-seq msg dest t))) ! 979: mh-refile-list))) ! 980: (mh-seq-to-msgs msg-or-seq)) ! 981: (mh-notate-seq msg-or-seq ? mh-cmd-note)) ! 982: (t ! 983: (mapc (function (lambda (dest) ! 984: (mh-remove-msg-from-seq msg-or-seq dest t))) ! 985: mh-refile-list) ! 986: (mh-notate msg-or-seq ? mh-cmd-note)))) ! 987: ! 988: (t nil)) ! 989: (if (mh-outstanding-commands-p) ! 990: (mh-set-folder-modified-p nil))) ! 991: ! 992: ! 993: (defun mh-undo-folder () ! 994: "Undo all commands in current folder." ! 995: (interactive "") ! 996: (cond ((or mh-do-not-confirm ! 997: (yes-or-no-p "Undo all commands in folder? ")) ! 998: (setq mh-delete-list nil ! 999: mh-refile-list nil ! 1000: mh-seq-list nil ! 1001: mh-next-direction 'forward) ! 1002: (mh-unmark-all-headers t) ! 1003: (mh-set-folder-modified-p nil)) ! 1004: (t ! 1005: (message "Commands not undone.") ! 1006: (sit-for 2)))) ! 1007: ! 1008: ! 1009: (defun mh-visit-folder (folder range config) ! 1010: "Visit FOLDER and display RANGE of messages." ! 1011: (interactive (list (mh-prompt-for-folder "Visit" "+inbox" t) ! 1012: (read-string "Range [all]? ") ! 1013: (current-window-configuration))) ! 1014: (mh-scan-folder folder (if (equal range "") "all" range)) ! 1015: (setq mh-previous-window-config config)) ! 1016: ! 1017: ! 1018: (defun mh-widen () ! 1019: "Remove restrictions from the current folder, thereby showing all messages." ! 1020: (interactive "") ! 1021: (let ((buffer-read-only nil)) ! 1022: (delete-region (point-min) (point-max)) ! 1023: (widen) ! 1024: (mh-make-folder-mode-line)) ! 1025: (setq mh-narrowed-to-seq nil)) ! 1026: ! 1027: ! 1028: ! 1029: ;;; Support routines. ! 1030: ! 1031: (defun mh-delete-a-msg (msg) ! 1032: ;; Delete the MESSAGE. ! 1033: (save-excursion ! 1034: (mh-goto-msg msg nil nil) ! 1035: (if (looking-at mh-refiled-msg-regexp) ! 1036: (error "Message %d is refiled. Undo refile before deleting." msg)) ! 1037: (mh-push msg mh-delete-list) ! 1038: (mh-add-msgs-to-seq msg 'deleted t) ! 1039: (mh-notate msg ?D mh-cmd-note) ! 1040: (mh-set-folder-modified-p t))) ! 1041: ! 1042: ! 1043: (defun mh-refile-a-msg (msg destination) ! 1044: ;; Refile the MESSAGE in the FOLDER. ! 1045: (save-excursion ! 1046: (mh-goto-msg msg nil nil) ! 1047: (cond ((looking-at mh-deleted-msg-regexp) ! 1048: (error "Message %d is deleted. Undo delete before moving." msg)) ! 1049: (t ! 1050: (if (not (memq destination mh-refile-list)) ! 1051: (mh-push destination mh-refile-list)) ! 1052: (mh-add-msgs-to-seq msg destination t) ! 1053: (mh-notate msg ?^ mh-cmd-note) ! 1054: (mh-set-folder-modified-p t))))) ! 1055: ! 1056: ! 1057: (defun mh-display-msg (msg-num msg-filename show-buffer) ! 1058: ;; Display the message NUMBER and PATHNAME in BUFFER. ! 1059: (if (not (file-exists-p msg-filename)) ! 1060: (error "Message %d does not exist." msg-num)) ! 1061: ;; Bind these variables in case they are local to folder buffer. ! 1062: (let ((formfile mhl-formfile) ! 1063: (clean-message-header mh-clean-message-header) ! 1064: (invisible-headers mh-invisible-headers) ! 1065: (visible-headers mh-visible-headers)) ! 1066: (switch-to-buffer show-buffer) ! 1067: (if mh-bury-show-buffer (bury-buffer (current-buffer))) ! 1068: (when (not (equal msg-filename buffer-file-name)) ! 1069: ;; Buffer does not yet contain message. ! 1070: (clear-visited-file-modtime) ! 1071: (unlock-buffer) ! 1072: (erase-buffer) ! 1073: (if formfile ! 1074: (if (stringp formfile) ! 1075: (mh-exec-lib-cmd-output "mhl" "-nobell" "-noclear" ! 1076: "-form" formfile msg-filename) ! 1077: (mh-exec-lib-cmd-output "mhl" "-nobell" "-noclear" ! 1078: msg-filename)) ! 1079: (insert-file-contents msg-filename t)) ! 1080: (goto-char (point-min)) ! 1081: (cond (clean-message-header ! 1082: (mh-clean-msg-header (point-min) ! 1083: invisible-headers ! 1084: visible-headers) ! 1085: (goto-char (point-min))) ! 1086: (t ! 1087: (let ((case-fold-search t)) ! 1088: (re-search-forward ! 1089: "^To:\\|^From:\\|^Subject:\\|^Date:" nil t) ! 1090: (beginning-of-line) ! 1091: (recenter 0)))) ! 1092: (set-buffer-modified-p nil) ! 1093: (setq buffer-file-name msg-filename) ! 1094: (set-mark nil) ! 1095: (setq mode-line-buffer-identification ! 1096: (list "{%b} " (format "%s" folder) "/" (format "%d" msg-num)))))) ! 1097: ! 1098: ! 1099: (defun mh-show-message-in-other-window () ! 1100: (let ((buffer mh-show-buffer)) ! 1101: (switch-to-buffer-other-window buffer) ! 1102: (if mh-bury-show-buffer (bury-buffer (current-buffer))))) ! 1103: ! 1104: ! 1105: (defun mh-clean-msg-header (start invisible-headers visible-headers) ! 1106: ;; Flush extraneous lines in a message header, from the given POINT to the ! 1107: ;; end of the message header. If VISIBLE-HEADERS is non-nil, it contains a ! 1108: ;; regular expression specifying the lines to display, otherwise ! 1109: ;; INVISIBLE-HEADERS contains a regular expression specifying lines to ! 1110: ;; delete from the header. ! 1111: (let ((case-fold-search t)) ! 1112: (save-restriction ! 1113: (goto-char start) ! 1114: (if (search-forward "\n\n" nil t) ! 1115: (backward-char 2)) ! 1116: (narrow-to-region start (point)) ! 1117: (goto-char (point-min)) ! 1118: (if visible-headers ! 1119: (while (< (point) (point-max)) ! 1120: (beginning-of-line) ! 1121: (cond ((looking-at visible-headers) ! 1122: (forward-line 1) ! 1123: (while (looking-at "^[ \t]+") (forward-line 1))) ! 1124: (t ! 1125: (mh-delete-line 1) ! 1126: (while (looking-at "^[ \t]+") ! 1127: (beginning-of-line) ! 1128: (mh-delete-line 1))))) ! 1129: (while (re-search-forward invisible-headers nil t) ! 1130: (beginning-of-line) ! 1131: (mh-delete-line 1) ! 1132: (while (looking-at "^[ \t]+") ! 1133: (beginning-of-line) ! 1134: (mh-delete-line 1)))) ! 1135: (unlock-buffer)))) ! 1136: ! 1137: ! 1138: (defun mh-delete-line (lines) ! 1139: ;; Delete version of kill-line. ! 1140: (delete-region (point) (save-excursion (forward-line lines) (point)))) ! 1141: ! 1142: ! 1143: (defun mh-read-draft (use initial-contents delete-contents-file) ! 1144: ;; Read draft file into a draft buffer and make that buffer the current one. ! 1145: ;; USE is a message used for prompting about the intended use of the message. ! 1146: ;; INITIAL-CONTENTS is filename that is read into an empty buffer, or NIL ! 1147: ;; if buffer should not be modified. Delete the initial-contents file if ! 1148: ;; DELETE-CONTENTS-FILE flag is set. ! 1149: ;; Returns the draft folder's name. ! 1150: ;; If the draft folder facility is enabled in ~/.mh_profile, a new buffer is ! 1151: ;; used each time and saved in the draft folder. The draft file can then be ! 1152: ;; reused. ! 1153: (cond (mh-draft-folder ! 1154: (pop-to-buffer (find-file-noselect (mh-new-draft-name) t)) ! 1155: (rename-buffer (format "draft-%s" (buffer-name)))) ! 1156: (t ! 1157: (let ((draft-name (mh-expand-file-name "draft" mh-user-path))) ! 1158: (pop-to-buffer "draft") ; Create if necessary ! 1159: (if (buffer-modified-p) ! 1160: (if (y-or-n-p "Draft has been modified; kill anyway? ") ! 1161: (set-buffer-modified-p nil) ! 1162: (error "Draft preserved."))) ! 1163: (setq buffer-file-name draft-name) ! 1164: (clear-visited-file-modtime) ! 1165: (unlock-buffer) ! 1166: (when (and (file-exists-p draft-name) ! 1167: (not (equal draft-name initial-contents))) ! 1168: (insert-file-contents draft-name) ! 1169: (delete-file draft-name))))) ! 1170: (when (and initial-contents ! 1171: (or (zerop (buffer-size)) ! 1172: (not (y-or-n-p ! 1173: (format "A draft exists. Use for %s? " use))))) ! 1174: (erase-buffer) ! 1175: (insert-file-contents initial-contents) ! 1176: (if delete-contents-file (delete-file initial-contents))) ! 1177: (auto-save-mode 1) ! 1178: (if mh-draft-folder ! 1179: (save-buffer)) ; Do not reuse draft name ! 1180: (buffer-name)) ! 1181: ! 1182: ! 1183: (defun mh-new-draft-name () ! 1184: ;; Returns the pathname of folder for draft messages. ! 1185: (save-excursion ! 1186: (set-buffer (get-buffer-create " *mh-temp*")) ! 1187: (erase-buffer) ! 1188: (mh-exec-cmd-output "mhpath" nil mh-draft-folder "new") ! 1189: (buffer-substring (point) (- (mark) 1)))) ! 1190: ! 1191: ! 1192: (defun mh-next-msg () ! 1193: ;; Move backward or forward to the next undeleted message in the buffer. ! 1194: (if (eq mh-next-direction 'forward) ! 1195: (mh-next-undeleted-msg 1) ! 1196: (mh-previous-undeleted-msg 1))) ! 1197: ! 1198: ! 1199: (defun mh-set-scan-mode () ! 1200: ;; Display the scan listing buffer, but do not show a message. ! 1201: (if (get-buffer mh-show-buffer) ! 1202: (delete-windows-on mh-show-buffer)) ! 1203: (mh-set-mode-name "mh-e scan") ! 1204: (setq mh-summarize t) ! 1205: (if mh-recenter-summary-p ! 1206: (recenter (/ (window-height) 2)))) ! 1207: ! 1208: ! 1209: (defun mh-maybe-show (msg) ! 1210: ;; If the scan listing is not summarized, then display the message pointed ! 1211: ;; to by the cursor is the scan listing. ! 1212: (if (not mh-summarize) (mh-show msg))) ! 1213: ! 1214: ! 1215: (defun mh-set-mode-name (mode-name-string) ! 1216: ;; Set the mode-name and ensure that the mode line is updated. ! 1217: (setq mode-name mode-name-string) ! 1218: ;; Force redisplay of all buffers' mode lines to be considered. ! 1219: (save-excursion (set-buffer (other-buffer))) ! 1220: (set-buffer-modified-p (buffer-modified-p))) ! 1221: ! 1222: ! 1223: ! 1224: ;;; The folder data abstraction. ! 1225: ! 1226: (defvar mh-current-folder nil "Name of current folder") ! 1227: (defvar mh-show-buffer nil "Buffer that displays mesage for this folder") ! 1228: (defvar mh-folder-filename nil "Full path of directory for this folder") ! 1229: (defvar mh-summarize nil "If non-nil, show scan list only") ! 1230: (defvar mh-next-seq-num nil "Index of free sequence id") ! 1231: (defvar mh-delete-list nil "list of msg numbers to delete") ! 1232: (defvar mh-refile-list nil "list of folder names in mh-seq-list") ! 1233: (defvar mh-seq-list nil "alist of (seq .msgs ) numbers") ! 1234: (defvar mh-seen-list nil "list of displayed messages") ! 1235: (defvar mh-next-direction 'forward "direction to move to next message") ! 1236: (defvar mh-narrowed-to-seq nil "sequence display is narrowed to") ! 1237: (defvar mh-first-msg-num nil "number of first msg in buffer") ! 1238: (defvar mh-last-msg-num nil "number of last msg in buffer") ! 1239: ! 1240: (defun mh-make-folder (name) ! 1241: ;; Create and initialize a new mail folder called NAME and make it the ! 1242: ;; current folder. ! 1243: (switch-to-buffer name) ! 1244: (kill-all-local-variables) ! 1245: (setq buffer-read-only nil) ! 1246: (erase-buffer) ! 1247: (make-local-vars ! 1248: 'mh-current-folder name ; Name of folder ! 1249: 'mh-show-buffer (format "show-%s" name) ; Buffer that displays messages ! 1250: 'mh-folder-filename ; e.g. /usr/foobar/Mail/inbox/ ! 1251: (file-name-as-directory (mh-expand-file-name name)) ! 1252: 'mh-summarize t ; Show scan list only? ! 1253: 'mh-next-seq-num 0 ; Index of free sequence id ! 1254: 'mh-delete-list nil ; List of msgs nums to delete ! 1255: 'mh-refile-list nil ; List of folder names in mh-seq-list ! 1256: 'mh-seq-list nil ; Alist of (seq . msgs) nums ! 1257: 'mh-seen-list nil ; List of displayed messages ! 1258: 'mh-next-direction 'forward ; Direction to move to next message ! 1259: 'mh-narrowed-to-seq nil ; Sequence display is narrowed to ! 1260: 'mh-first-msg-num nil ; Number of first msg in buffer ! 1261: 'mh-last-msg-num nil ; Number of last msg in buffer ! 1262: 'mh-previous-window-config nil) ; Previous window configuration ! 1263: (mh-folder-mode) ! 1264: (setq buffer-read-only t) ! 1265: (mh-set-folder-modified-p nil) ! 1266: (auto-save-mode -1) ! 1267: (setq buffer-offer-save t) ! 1268: (mh-set-mode-name "mh-e scan")) ! 1269: ! 1270: ! 1271: ! 1272: (defun make-local-vars (&rest pairs) ! 1273: ;; Take VARIABLE-VALUE pairs and makes local variables initialized to the ! 1274: ;; value. ! 1275: (while pairs ! 1276: (make-variable-buffer-local (car pairs)) ! 1277: (set (car pairs) (car (cdr pairs))) ! 1278: (setq pairs (cdr (cdr pairs))))) ! 1279: ! 1280: ! 1281: (defun mh-folder-mode () ! 1282: "Major mode for \"editing\" an MH folder scan listing. ! 1283: Messages can be marked for refiling and deletion. However, both actions ! 1284: are deferred until you request execution with \\[mh-execute-commands]. ! 1285: \\{mh-folder-mode-map} ! 1286: A prefix argument (\\[universal-argument]) to delete, refile, list, or undo applies the action to a message sequence. ! 1287: ! 1288: Variables controlling mh-e operation are (defaults in parentheses): ! 1289: ! 1290: mh-bury-show-buffer (t) ! 1291: Non-nil means that the buffer used to display message is buried. ! 1292: It will never be offered as the default other buffer. ! 1293: ! 1294: mh-clean-message-header (nil) ! 1295: Non-nil means remove header lines matching the regular expression ! 1296: specified in mh-invisible-headers from messages. ! 1297: ! 1298: mh-visible-headers (nil) ! 1299: If non-nil, it contains a regexp specifying the headers that are shown in ! 1300: a message if mh-clean-message-header is non-nil. Setting this variable ! 1301: overrides mh-invisible-headers. ! 1302: ! 1303: mh-do-not-confirm (nil) ! 1304: Non-nil means do not prompt for confirmation before executing some ! 1305: non-recoverable commands such as mh-kill-folder and mh-undo-folder. ! 1306: ! 1307: mhl-formfile (nil) ! 1308: Name of format file to be used by mhl to show messages. ! 1309: A value of T means use the default format file. ! 1310: Nil means don't use mhl to format messages. ! 1311: ! 1312: mh-lpr-command-format (\"lpr -p -J '%s'\") ! 1313: Format for command used to print a message on a system printer. ! 1314: ! 1315: mh-recenter-summary-p (nil) ! 1316: If non-nil, then the scan listing is recentered when the window displaying ! 1317: a messages is toggled off. ! 1318: ! 1319: mh-summary-height (4) ! 1320: Number of lines in the summary window. ! 1321: ! 1322: mh-ins-buf-prefix (\">> \") ! 1323: String to insert before each non-blank line of a message as it is ! 1324: inserted in a letter being composed." ! 1325: ! 1326: (use-local-map mh-folder-mode-map) ! 1327: (setq major-mode 'mh-folder-mode) ! 1328: (mh-set-mode-name "mh-e folder") ! 1329: (run-hooks 'mh-folder-mode-hook)) ! 1330: ! 1331: ! 1332: (defun mh-scan-folder (folder range) ! 1333: ;; Scan the FOLDER over the RANGE. Return in the folder's buffer. ! 1334: (cond ((null (get-buffer folder)) ! 1335: (mh-make-folder folder)) ! 1336: (t ! 1337: (mh-process-or-undo-commands folder) ! 1338: (switch-to-buffer folder))) ! 1339: (mh-regenerate-headers range) ! 1340: (when (= (count-lines (point-min) (point-max)) 0) ! 1341: (if (equal range "all") ! 1342: (message "Folder %s is empty" folder) ! 1343: (message "No messages in %s, range %s" folder range)) ! 1344: (sit-for 5)) ! 1345: (mh-goto-cur-msg)) ! 1346: ! 1347: ! 1348: (defun mh-regenerate-headers (range) ! 1349: ;; Replace buffer with scan of its contents over range RANGE. ! 1350: (let ((buffer-read-only nil) ! 1351: (folder (buffer-name))) ! 1352: (message (format "scanning %s..." folder)) ! 1353: (erase-buffer) ! 1354: (mh-exec-cmd-output "scan" nil ! 1355: "-noclear" "-noheader" ! 1356: "-width" (window-width) ! 1357: folder range) ! 1358: (goto-char (point-min)) ! 1359: (cond ((looking-at "scan: no messages in") ! 1360: (keep-lines mh-valid-scan-line)) ; Flush random scan lines ! 1361: ((looking-at "scan: ")) ; Keep error messages ! 1362: (t ! 1363: (keep-lines mh-valid-scan-line))) ; Flush random scan lines ! 1364: (mh-delete-seq-locally 'cur) ; To pick up new one ! 1365: (setq mh-seq-list (mh-read-folder-sequences folder t)) ! 1366: (mh-notate-user-sequences) ! 1367: (mh-make-folder-mode-line) ! 1368: (mh-set-folder-modified-p nil) ! 1369: (message (format "scanning %s...done" folder)))) ! 1370: ! 1371: ! 1372: (defun mh-get-new-mail (maildrop-name) ! 1373: ;; Read new mail from a maildrop into the current buffer. ! 1374: ;; Return T if there was new mail, NIL otherwise. Return in the current ! 1375: ;; buffer. ! 1376: (let ((buffer-read-only nil) ! 1377: (point-before-inc (point)) ! 1378: (folder (buffer-name)) ! 1379: (folder-modified-flag (buffer-modified-p))) ! 1380: (message (if maildrop-name ! 1381: (format "inc %s -file %s..." folder maildrop-name) ! 1382: (format "inc %s..." folder))) ! 1383: (mh-unmark-all-headers nil) ! 1384: (setq mh-next-direction 'forward) ! 1385: (keep-lines mh-valid-scan-line) ; Kill old error messages ! 1386: (goto-char (point-max)) ! 1387: (let ((start-of-inc (point))) ! 1388: (if maildrop-name ! 1389: (mh-exec-cmd-output "inc" nil folder ! 1390: "-file" (expand-file-name maildrop-name) ! 1391: "-width" (window-width) ! 1392: "-truncate") ! 1393: (mh-exec-cmd-output "inc" nil ! 1394: "-width" (window-width))) ! 1395: (message ! 1396: (if maildrop-name ! 1397: (format "inc %s -file %s...done" folder maildrop-name) ! 1398: (format "inc %s...done" folder))) ! 1399: (mh-delete-seq-locally 'cur) ; To pick up new one ! 1400: (setq mh-seq-list (mh-read-folder-sequences folder t)) ! 1401: (mh-notate-user-sequences) ! 1402: (goto-char start-of-inc) ! 1403: (cond ((looking-at "inc: no mail") ! 1404: (keep-lines mh-valid-scan-line) ; Flush random scan lines ! 1405: (mh-make-folder-mode-line) ! 1406: (goto-char point-before-inc) ! 1407: (message "No new mail%s%s." (if maildrop-name " in " "") ! 1408: (if maildrop-name maildrop-name "")) ! 1409: nil) ! 1410: ((looking-at "inc:") ; Error messages ! 1411: (mh-make-folder-mode-line) ! 1412: (goto-char point-before-inc) ! 1413: (message "inc error") ! 1414: nil) ! 1415: (t ! 1416: (keep-lines mh-valid-scan-line) ! 1417: (mh-make-folder-mode-line) ! 1418: (mh-goto-cur-msg) ! 1419: t))) ! 1420: (mh-set-folder-modified-p folder-modified-flag))) ! 1421: ! 1422: ! 1423: (defun mh-make-folder-mode-line (&optional annotation) ! 1424: ;; Set the fields of the mode line for a folder buffer. ! 1425: ;; The optional ANNOTATION string is displayed after the folder's name. ! 1426: (save-excursion ! 1427: (goto-char (point-min)) ! 1428: (setq mh-first-msg-num (mh-get-msg-num nil)) ! 1429: (let* ((lines (count-lines (point-min) (point-max))) ! 1430: (case-fold-search nil)) ! 1431: (goto-char (point-max)) ! 1432: (previous-line 1) ! 1433: (setq mh-last-msg-num (mh-get-msg-num nil)) ! 1434: (setq mode-line-buffer-identification ! 1435: (list (format "{%%b%s} %d msg%s" ! 1436: (if annotation (format "/%s" annotation) "") ! 1437: lines ! 1438: (if (= lines 0) ! 1439: "s" ! 1440: (if (> lines 1) ! 1441: (format "s (%d-%d)" mh-first-msg-num ! 1442: mh-last-msg-num) ! 1443: (format " (%d)" mh-first-msg-num))))))))) ! 1444: ! 1445: ! 1446: (defun mh-unmark-all-headers (remove-all-flags) ! 1447: ;; Remove all '+' flags from the headers, and if called with a non-nil ! 1448: ;; argument, remove all 'D', '^' and '%' flags too. ! 1449: (save-excursion ! 1450: (let ((buffer-read-only nil) ! 1451: (case-fold-search nil)) ! 1452: (goto-char (point-min)) ! 1453: (while (if remove-all-flags ! 1454: (re-search-forward mh-flagged-scan-msg-regexp nil t) ! 1455: (re-search-forward mh-cur-scan-msg-regexp nil t)) ! 1456: (delete-backward-char 1) ! 1457: (insert " ") ! 1458: (beginning-of-line))))) ; Check line again ! 1459: ! 1460: ! 1461: (defun mh-goto-cur-msg () ! 1462: ;; Position the cursor at the current message. ! 1463: (let ((cur-msg (car (mh-seq-to-msgs 'cur)))) ! 1464: (cond ((or (null cur-msg) (not (mh-goto-msg cur-msg t nil))) ! 1465: (goto-char (point-max)) ! 1466: (forward-line -1) ! 1467: (message "No current message")) ! 1468: (t ! 1469: (mh-notate cur-msg ?+ mh-cmd-note) ! 1470: (recenter 0) ! 1471: (mh-maybe-show cur-msg))))) ! 1472: ! 1473: ! 1474: (defun mh-pack-folder-1 () ! 1475: ;; Close and pack the current folder. ! 1476: (let ((buffer-read-only nil)) ! 1477: (message "closing folder...") ! 1478: (mh-process-or-undo-commands mh-current-folder) ! 1479: (message "packing folder...") ! 1480: (save-excursion ! 1481: (mh-exec-cmd-quiet " *mh-temp*" "folder" mh-current-folder "-pack")) ! 1482: (mh-regenerate-headers "all") ! 1483: (message "packing done"))) ! 1484: ! 1485: ! 1486: (defun mh-process-or-undo-commands (folder) ! 1487: ;; If FOLDER has outstanding commands, then either process or discard them. ! 1488: (set-buffer folder) ! 1489: (if (mh-outstanding-commands-p) ! 1490: (if (or mh-do-not-confirm ! 1491: (y-or-n-p ! 1492: "Process outstanding deletes and refiles (or lose them)? ")) ! 1493: (mh-process-commands folder) ! 1494: (mh-undo-folder)) ! 1495: (mh-invalidate-show-cache))) ! 1496: ! 1497: ! 1498: (defun mh-process-commands (folder) ! 1499: ;; Process outstanding commands for the folder FOLDER. ! 1500: (message "Processing deletes and refiles...") ! 1501: (set-buffer folder) ! 1502: (let ((buffer-read-only nil)) ! 1503: ;; Update the unseen sequence if it exists ! 1504: (if (and mh-seen-list (mh-seq-to-msgs mh-unseen-seq)) ! 1505: (mh-undefine-sequence mh-unseen-seq mh-seen-list)) ! 1506: ! 1507: ;; Then refile messages ! 1508: (mapc (function ! 1509: (lambda (dest) ! 1510: (let ((msgs (mh-seq-to-msgs dest))) ! 1511: (when msgs ! 1512: (mh-delete-scan-msgs msgs) ! 1513: (apply 'mh-exec-cmd ! 1514: (nconc (cons "refile" msgs) ! 1515: (list "-src" folder (symbol-name dest)))))))) ! 1516: mh-refile-list) ! 1517: ! 1518: ;; Now delete messages ! 1519: (when mh-delete-list ! 1520: (apply 'mh-exec-cmd (mh-list* "rmm" (format "%s" folder) mh-delete-list)) ! 1521: (mh-delete-scan-msgs mh-delete-list)) ! 1522: ! 1523: ;; Don't need to remove sequences since delete and refile do so. ! 1524: ! 1525: ;; Mark cur message ! 1526: (mh-define-sequence 'cur (list (or (mh-get-msg-num nil) "last"))) ! 1527: ! 1528: (mh-invalidate-show-cache) ! 1529: ! 1530: (setq mh-delete-list nil ! 1531: mh-refile-list nil ! 1532: mh-seq-list (mh-read-folder-sequences mh-current-folder nil) ! 1533: mh-seen-list nil) ! 1534: (mh-unmark-all-headers t) ! 1535: (mh-notate-user-sequences) ! 1536: (mh-set-folder-modified-p nil) ! 1537: (message "Processing deletes and refiles...done"))) ! 1538: ! 1539: ! 1540: (defun mh-invalidate-show-cache () ! 1541: ;; Invalidate show buffer file cache. ! 1542: (if (get-buffer mh-show-buffer) ! 1543: (save-excursion ! 1544: (set-buffer mh-show-buffer) ! 1545: (setq buffer-file-name nil)))) ! 1546: ! 1547: ! 1548: (defun mh-delete-scan-msgs (msgs) ! 1549: ;; Delete the scan listing lines for each of the msgs in the LIST. ! 1550: (save-excursion ! 1551: (goto-char (point-min)) ! 1552: (flush-lines (mapconcat 'mh-msg-search-pat msgs "\\|")))) ! 1553: ! 1554: ! 1555: (defun mh-set-folder-modified-p (flag) ! 1556: "Mark current folder as modified or unmodified according to FLAG." ! 1557: (set-buffer-modified-p flag)) ! 1558: ! 1559: ! 1560: (defun mh-outstanding-commands-p () ! 1561: ;; Returns non-nil if there are outstanding deletes or refiles. ! 1562: (or mh-delete-list mh-refile-list)) ! 1563: ! 1564: ! 1565: ! 1566: ;;; Mode for composing and sending a message. ! 1567: ! 1568: (defun mh-letter-mode () ! 1569: "Mode for composing letters in mh-e. ! 1570: When you have finished composing, type \\[mh-send-letter] to send the letter. ! 1571: ! 1572: Variables controlling this mode (defaults in parentheses): ! 1573: ! 1574: mh-delete-yanked-msg-window (nil) ! 1575: If non-nil, \\[mh-yank-cur-msg] will delete any windows displaying ! 1576: the yanked message. ! 1577: ! 1578: mh-yank-from-start-of-msg (t) ! 1579: If non-nil, \\[mh-yank-cur-msg] will include the entire message. ! 1580: If `body', just yank the body (no header). ! 1581: If nil, only the portion of the message following the point will be yanked. ! 1582: If there is a region, this variable is ignored. ! 1583: ! 1584: Upon invoking mh-letter-mode, text-mode-hook and mh-letter-mode-hook are ! 1585: invoked with no args, if those values are non-nil. ! 1586: ! 1587: \\{mh-letter-mode-map}" ! 1588: (interactive) ! 1589: (kill-all-local-variables) ! 1590: (make-local-variable 'paragraph-start) ! 1591: (setq paragraph-start (concat "^[ \t]*[-_][-_][-_]+$\\|" paragraph-start)) ! 1592: (make-local-variable 'paragraph-separate) ! 1593: (setq paragraph-separate ! 1594: (concat "^[ \t]*[-_][-_][-_]+$\\|" paragraph-separate)) ! 1595: (make-local-variable 'mh-send-args) ! 1596: (make-local-variable 'mh-annotate-char) ! 1597: (make-local-variable 'mh-sent-from-folder) ! 1598: (make-local-variable 'mh-sent-from-msg) ! 1599: (use-local-map mh-letter-mode-map) ! 1600: (setq major-mode 'mh-letter-mode) ! 1601: (mh-set-mode-name "mh-e letter") ! 1602: (set-syntax-table mh-letter-mode-syntax-table) ! 1603: (run-hooks 'text-mode-hook 'mh-letter-mode-hook)) ! 1604: ! 1605: ! 1606: (defun mh-to-field () ! 1607: "Move point to the end of the header field indicated by the previous ! 1608: keystroke. Create the field if it does not exist. Set the mark to the ! 1609: point before moving." ! 1610: (interactive "") ! 1611: (expand-abbrev) ! 1612: (let ((target (cdr (assoc (logior last-input-char ?`) mh-to-field-choices))) ! 1613: (case-fold-search t)) ! 1614: (cond ((mh-position-on-field target t) ! 1615: (if (not (looking-at "[ \t]")) (insert " "))) ! 1616: (t ! 1617: (goto-char (dot-min)) ! 1618: (re-search-forward "^To:") ! 1619: (forward-line 1) ! 1620: (while (looking-at "^[ \t]") (forward-line 1)) ! 1621: (insert (format "%s \n" target)) ! 1622: (backward-char 1))))) ! 1623: ! 1624: ! 1625: (defun mh-to-fcc () ! 1626: "Insert a Fcc: field in the current message, prompting for the field ! 1627: name with a completion list of the current folders." ! 1628: (interactive) ! 1629: (expand-abbrev) ! 1630: (save-excursion ! 1631: (mh-insert-fields "Fcc:" ! 1632: (substring (mh-prompt-for-folder "Fcc" "" t) 1 nil)))) ! 1633: ! 1634: ! 1635: (defun mh-insert-signature () ! 1636: "Insert the file ~/.signature at the current point." ! 1637: (interactive "") ! 1638: (insert-file-contents "~/.signature")) ! 1639: ! 1640: ! 1641: (defun mh-check-whom () ! 1642: "Verify recipients of the current letter." ! 1643: (interactive) ! 1644: (let ((file-name (buffer-file-name))) ! 1645: (set-buffer-modified-p t) ; Force writing of contents ! 1646: (save-buffer) ! 1647: (message "Checking recipients...") ! 1648: (switch-to-buffer-other-window "*Mail Recipients*") ! 1649: (bury-buffer (current-buffer)) ! 1650: (erase-buffer) ! 1651: (mh-exec-cmd-output "whom" t file-name) ! 1652: (other-window -1) ! 1653: (message "Checking recipients...done"))) ! 1654: ! 1655: ! 1656: ! 1657: ;;; Routines to make a search pattern and search for a message. ! 1658: ! 1659: (defun mh-make-pick-template () ! 1660: ;; Initialize the current buffer with a template for a pick pattern. ! 1661: (erase-buffer) ! 1662: (kill-all-local-variables) ! 1663: (make-local-variable 'mh-searching-folder) ! 1664: (insert "From: \n" ! 1665: "To: \n" ! 1666: "Cc: \n" ! 1667: "Date: \n" ! 1668: "Subject: \n" ! 1669: "---------\n") ! 1670: (mh-letter-mode) ! 1671: (use-local-map mh-pick-mode-map) ! 1672: (goto-char (point-min)) ! 1673: (end-of-line)) ! 1674: ! 1675: ! 1676: (defun mh-do-pick-search () ! 1677: "Find messages in the folder named in mh-searching-folder that match the ! 1678: qualifications in current buffer. Put messages found in a sequence ! 1679: named `search'." ! 1680: (interactive) ! 1681: (let ((pattern-buffer (buffer-name)) ! 1682: (searching-buffer mh-searching-folder) ! 1683: (range) ! 1684: (pattern nil) ! 1685: (new-buffer nil)) ! 1686: (save-excursion ! 1687: (cond ((get-buffer searching-buffer) ! 1688: (set-buffer searching-buffer) ! 1689: (setq range (format "%d-%d" mh-first-msg-num mh-last-msg-num))) ! 1690: (t ! 1691: (mh-make-folder searching-buffer) ! 1692: (setq range "all") ! 1693: (setq new-buffer t)))) ! 1694: (message "Searching...") ! 1695: (goto-char (point-min)) ! 1696: (while (setq pattern (mh-next-pick-field pattern-buffer)) ! 1697: (setq msgs (mh-seq-from-command searching-buffer ! 1698: 'search ! 1699: (nconc (cons "pick" pattern) ! 1700: (list searching-buffer ! 1701: range ! 1702: "-sequence" "search" ! 1703: "-list")))) ! 1704: (setq range "search")) ! 1705: (message "Searching...done") ! 1706: (if new-buffer ! 1707: (mh-scan-folder searching-buffer msgs) ! 1708: (switch-to-buffer searching-buffer)) ! 1709: (delete-other-windows) ! 1710: (mh-notate-seq 'search ?% (+ mh-cmd-note 1)))) ! 1711: ! 1712: ! 1713: (defun mh-next-pick-field (buffer) ! 1714: ;; Return the next piece of a pick argument that can be extracted from the ! 1715: ;; BUFFER. Returns nil if no pieces remain. ! 1716: (set-buffer buffer) ! 1717: (let ((case-fold-search t)) ! 1718: (cond ((eobp) ! 1719: nil) ! 1720: ((re-search-forward "^\\([a-z].*\\):[ \t]*\\([a-z0-9].*\\)$" nil t) ! 1721: (let* ((component ! 1722: (format "-%s" ! 1723: (downcase (buffer-substring (match-beginning 1) ! 1724: (match-end 1))))) ! 1725: (pat (buffer-substring (match-beginning 2) (match-end 2)))) ! 1726: (forward-line 1) ! 1727: (list component pat))) ! 1728: ((re-search-forward "^-*$" nil t) ! 1729: (forward-char 1) ! 1730: (let ((body (buffer-substring (point) (point-max)))) ! 1731: (if (and (> (length body) 0) (not (equal body "\n"))) ! 1732: (list "-search" body) ! 1733: nil))) ! 1734: (t ! 1735: nil)))) ! 1736: ! 1737: ! 1738: ! 1739: ;;; Routines to compose and send a letter. ! 1740: ! 1741: (defun mh-compose-and-send-mail (draft send-args ! 1742: sent-from-folder sent-from-msg ! 1743: to subject cc ! 1744: annotate-char annotate-field ! 1745: config) ! 1746: ;; Edit and compose a draft message in buffer DRAFT and send or save it. ! 1747: ;; SENT-FROM-FOLDER is buffer containing scan listing of current folder, or ! 1748: ;; nil if none exists. ! 1749: ;; SENT-FROM-MSG is the message number or sequence name or nil. ! 1750: ;; SEND-ARGS is an optional argument passed to the send command. ! 1751: ;; nThe TO, SUBJECT, and CC fields are passed to the mh-compose-letter-hook. ! 1752: ;; If ANNOTATE-CHAR is non-null, it is used to notate the scan listing of the ! 1753: ;; message. In that case, the ANNOTATE-FIELD is used to build a string ! 1754: ;; for mh-annotate-msg. ! 1755: ;; CONFIG is the window configuration to restore after sending the letter. ! 1756: (pop-to-buffer draft) ! 1757: (mh-letter-mode) ! 1758: (make-local-vars ! 1759: 'mh-annotate-field annotate-field ! 1760: 'mh-previous-window-config config) ! 1761: (setq mh-sent-from-folder sent-from-folder) ! 1762: (setq mh-sent-from-msg sent-from-msg) ! 1763: (setq mh-send-args send-args) ! 1764: (setq mh-annotate-char annotate-char) ! 1765: (setq mode-line-buffer-identification (list "{%b}")) ! 1766: (if (and (boundp 'mh-compose-letter-hook) ! 1767: (symbol-value 'mh-compose-letter-hook)) ! 1768: ;; run-hooks will not pass arguments. ! 1769: (let ((value (symbol-value 'mh-compose-letter-hook))) ! 1770: (if (and (listp value) (not (eq (car value) 'lambda))) ! 1771: (while value ! 1772: (funcall (car value) to subject cc) ! 1773: (setq value (cdr value))) ! 1774: (funcall mh-compose-letter-hook to subject cc))))) ! 1775: ! 1776: ! 1777: (defun mh-send-letter (&optional arg) ! 1778: "Send the draft letter in the current buffer. ! 1779: If (optional) prefix argument provided, monitor delivery." ! 1780: (interactive "P") ! 1781: (set-buffer-modified-p t) ; Make sure buffer is written ! 1782: (save-buffer) ! 1783: (message "Sending...") ! 1784: (let ((buffer-name (buffer-name)) ! 1785: (file-name (buffer-file-name)) ! 1786: (config mh-previous-window-config)) ! 1787: (cond (arg ! 1788: (pop-to-buffer "MH mail delivery") ! 1789: (erase-buffer) ! 1790: (if mh-send-args ! 1791: (mh-exec-cmd-output "send" t "-watch" "-nopush" ! 1792: "-nodraftfolder" mh-send-args file-name) ! 1793: (mh-exec-cmd-output "send" t "-watch" "-nopush" ! 1794: "-nodraftfolder" file-name))) ! 1795: ! 1796: (mh-send-args ! 1797: (mh-exec-cmd-demon "send" "-nodraftfolder" "-noverbose" ! 1798: mh-send-args file-name)) ! 1799: (t ! 1800: (mh-exec-cmd-demon "send" "-nodraftfolder" "-noverbose" ! 1801: file-name))) ! 1802: ! 1803: (if mh-annotate-char ! 1804: (mh-annotate-msg mh-sent-from-msg ! 1805: mh-sent-from-folder ! 1806: mh-annotate-char ! 1807: "-component" mh-annotate-field ! 1808: "-text" (format "\"%s %s\"" ! 1809: (mh-get-field "To:") ! 1810: (mh-get-field "Cc:")))) ! 1811: ! 1812: (when (or (not arg) ! 1813: (y-or-n-p "Kill draft buffer? ")) ! 1814: (kill-buffer buffer-name) ! 1815: (if config ! 1816: (set-window-configuration config))) ! 1817: (message "Sending...done"))) ! 1818: ! 1819: ! 1820: ! 1821: (defun mh-insert-letter (prefix-provided folder msg) ! 1822: "Insert a message from any folder into the current letter. ! 1823: Removes the message's headers using mh-invisible-headers. ! 1824: Prefixes each non-blank line with mh-ins-buf-prefix (default \">> \"). ! 1825: If (optional) prefix argument provided, do not indent and do not delete ! 1826: headers. ! 1827: Leaves the mark before the letter and point after it." ! 1828: (interactive ! 1829: (list current-prefix-arg ! 1830: (mh-prompt-for-folder "Message from" mh-sent-from-folder nil) ! 1831: (read-input (format "Message number%s: " ! 1832: (if mh-sent-from-msg ! 1833: (format " [%d]" mh-sent-from-msg) ! 1834: ""))))) ! 1835: (save-restriction ! 1836: (narrow-to-region (point) (point)) ! 1837: (let ((start (point-min))) ! 1838: (if (equal msg "") (setq msg (format "%d" mh-sent-from-msg))) ! 1839: (mh-exec-lib-cmd-output "mhl" "-nobell" "-noclear" ! 1840: (mh-expand-file-name msg ! 1841: (mh-expand-file-name ! 1842: folder))) ! 1843: (when (not prefix-provided) ! 1844: (mh-clean-msg-header start mh-invisible-headers mh-visible-headers) ! 1845: (set-mark start) ; since mh-clean-msg-header moves it ! 1846: (mh-insert-prefix-string mh-ins-buf-prefix))))) ! 1847: ! 1848: ! 1849: (defun mh-yank-cur-msg () ! 1850: "Insert the currently displayed message into the draft buffer. Prefix each ! 1851: non-blank line in the message with the string in mh-ins-buf-prefix. If a ! 1852: region is set in the message's buffer, then only the region will be inserted. ! 1853: Otherwise, the entire message will be inserted if mh-yank-from-start-of-msg is ! 1854: non-nil. If this variable is nil, the portion of the message following the ! 1855: point will be yanked. If mh-delete-yanked-msg-window is non-nil, any window ! 1856: displaying the yanked message will be deleted." ! 1857: (interactive) ! 1858: (if (and (boundp 'mh-sent-from-folder) mh-sent-from-folder mh-sent-from-msg) ! 1859: (let ((to-point (point)) ! 1860: (to-buffer (current-buffer))) ! 1861: (set-buffer mh-sent-from-folder) ! 1862: (if mh-delete-yanked-msg-window ! 1863: (delete-windows-on mh-show-buffer)) ! 1864: (set-buffer mh-show-buffer) ; Find displayed message ! 1865: (let ((mh-ins-str (cond ((mark) ! 1866: (buffer-substring (point) (mark))) ! 1867: ((eq 'body mh-yank-from-start-of-msg) ! 1868: (buffer-substring ! 1869: (save-excursion ! 1870: (mh-goto-header-end 1) ! 1871: (point)) ! 1872: (point-max))) ! 1873: (mh-yank-from-start-of-msg ! 1874: (buffer-substring (point-min) (point-max))) ! 1875: (t ! 1876: (buffer-substring (point) (point-max)))))) ! 1877: (set-buffer to-buffer) ! 1878: (narrow-to-region to-point to-point) ! 1879: (insert mh-ins-str) ! 1880: (mh-insert-prefix-string mh-ins-buf-prefix) ! 1881: (insert "\n") ! 1882: (widen))) ! 1883: (error "There is no current message."))) ! 1884: ! 1885: (defun mh-insert-prefix-string (ins-string) ! 1886: ;; Preface each line in the current buffer with STRING. ! 1887: (goto-char (point-min)) ! 1888: (while (not (eobp)) ! 1889: (insert ins-string) ! 1890: (forward-line 1))) ! 1891: ! 1892: ! 1893: (defun mh-fully-kill-draft () ! 1894: "Kill the draft message file and the draft message buffer. ! 1895: Use \\[kill-buffer] if you don't want to delete the draft message file." ! 1896: (interactive "") ! 1897: (if (y-or-n-p "Kill draft message? ") ! 1898: (let ((config mh-previous-window-config)) ! 1899: (if (file-exists-p (buffer-file-name)) ! 1900: (delete-file (buffer-file-name))) ! 1901: (set-buffer-modified-p nil) ! 1902: (kill-buffer (buffer-name)) ! 1903: (if config ! 1904: (set-window-configuration config))) ! 1905: (error "Message not killed"))) ! 1906: ! 1907: ! 1908: ! 1909: ;;; Commands to manipulate sequences. Sequences are stored in an alist ! 1910: ;;; of the form: ! 1911: ;;; ((seq-name msgs ...) (seq-name msgs ...) ...) ! 1912: ! 1913: (defun mh-make-seq (name msgs) (cons name msgs)) ! 1914: ! 1915: (defmacro mh-seq-name (pair) (list 'car pair)) ! 1916: ! 1917: (defmacro mh-seq-msgs (pair) (list 'cdr pair)) ! 1918: ! 1919: (defun mh-find-seq (name) (assoc name mh-seq-list)) ! 1920: ! 1921: ! 1922: (defun mh-seq-to-msgs (seq) ! 1923: "Return a list of the messages in SEQUENCE." ! 1924: (mh-seq-msgs (mh-find-seq seq))) ! 1925: ! 1926: ! 1927: (defun mh-seq-containing-msg (msg) ! 1928: ;; Return a list of the sequences containing MESSAGE. ! 1929: (let ((l mh-seq-list) ! 1930: (seqs ())) ! 1931: (while l ! 1932: (if (memq msg (mh-seq-msgs (car l))) ! 1933: (mh-push (mh-seq-name (car l)) seqs)) ! 1934: (setq l (cdr l))) ! 1935: seqs)) ! 1936: ! 1937: ! 1938: (defun mh-msg-to-seq (msg) ! 1939: ;; Given a MESSAGE number, return the first sequence in which it occurs. ! 1940: (car (mh-seq-containing-msg msg))) ! 1941: ! 1942: ! 1943: (defun mh-read-seq (prompt not-empty &optional default) ! 1944: ;; Read and return a sequence name. Prompt with PROMPT, raise an error ! 1945: ;; if the sequence is empty and the NOT-EMPTY flag is non-nil, and supply ! 1946: ;; an optional DEFAULT sequence. ! 1947: ;; A reply of '%' defaults to the first sequence containing the current ! 1948: ;; message. ! 1949: (let* ((input (completing-read (format "%s %s %s" prompt "sequence:" ! 1950: (if default ! 1951: (format "[%s] " default) ! 1952: "")) ! 1953: (mh-seq-names mh-seq-list))) ! 1954: (seq (cond ((equal input "%") (mh-msg-to-seq (mh-get-msg-num t))) ! 1955: ((equal input "") default) ! 1956: (t (intern input)))) ! 1957: (msgs (mh-seq-to-msgs seq))) ! 1958: (if (and (null msgs) not-empty) ! 1959: (error (format "No messages in sequence `%s'" seq))) ! 1960: seq)) ! 1961: ! 1962: ! 1963: (defun mh-read-folder-sequences (folder define-sequences) ! 1964: ;; Read and return the predefined sequences for a FOLDER. If ! 1965: ;; DEFINE-SEQUENCES is non-nil, then define mh-e's sequences before ! 1966: ;; reading MH's sequences. ! 1967: (let ((seqs ())) ! 1968: (when define-sequences ! 1969: (mh-define-sequences mh-seq-list) ! 1970: (mapc (function (lambda (seq) ; Save the internal sequences ! 1971: (if (mh-folder-name (mh-seq-name seq)) ! 1972: (mh-push seq seqs)))) ! 1973: mh-seq-list)) ! 1974: (save-excursion ! 1975: (mh-exec-cmd-quiet " *mh-temp*" "mark" folder "-list") ! 1976: (goto-char (point-min)) ! 1977: (while (re-search-forward "\\(^[a-zA-Z][a-zA-Z]*\\)" nil t) ! 1978: (mh-push (mh-make-seq (intern (buffer-substring (match-beginning 1) ! 1979: (match-end 1))) ! 1980: (mh-read-msg-list)) ! 1981: seqs))) ! 1982: seqs)) ! 1983: ! 1984: ! 1985: (defun mh-seq-names (seq-list) ! 1986: ;; Return an alist containing the names of the SEQUENCES. ! 1987: (mapcar (function (lambda (entry) (list (symbol-name (mh-seq-name entry))))) ! 1988: seq-list)) ! 1989: ! 1990: ! 1991: (defun mh-seq-from-command (folder seq command) ! 1992: ;; In FOLDER, make a sequence named SEQ by executing COMMAND. ! 1993: (let ((msg) ! 1994: (msgs ()) ! 1995: (case-fold-search t)) ! 1996: (save-excursion ! 1997: (save-window-excursion ! 1998: (apply 'mh-exec-cmd-quiet (cons " *mh-temp*" command)) ! 1999: (goto-char (point-min)) ! 2000: (while (setq msg (car (mh-read-msg-list))) ! 2001: (mh-push msg msgs) ! 2002: (forward-line 1))) ! 2003: (set-buffer folder) ! 2004: (setq msgs (nreverse msgs)) ; Put in ascending order ! 2005: (mh-push (mh-make-seq seq msgs) mh-seq-list) ! 2006: msgs))) ! 2007: ! 2008: ! 2009: (defun mh-read-msg-list () ! 2010: ;; Return a list of message numbers from the current point to the end of ! 2011: ;; the line. ! 2012: (let ((msgs ()) ! 2013: (end-of-line (save-excursion (end-of-line) (point)))) ! 2014: (while (re-search-forward "\\([0-9]+\\)" end-of-line t) ! 2015: (let ((num (string-to-int (buffer-substring (match-beginning 1) ! 2016: (match-end 1))))) ! 2017: (cond ((looking-at "-") ; Message range ! 2018: (forward-char 1) ! 2019: (re-search-forward "\\([0-9]+\\)" end-of-line t) ! 2020: (let ((num2 (string-to-int (buffer-substring (match-beginning 1) ! 2021: (match-end 1))))) ! 2022: (if (< num2 num) ! 2023: (error "Bad message range: %d-%d" num num2)) ! 2024: (while (<= num num2) ! 2025: (mh-push num msgs) ! 2026: (setq num (+ num 1))))) ! 2027: ((not (zerop num)) (mh-push num msgs))))) ! 2028: msgs)) ! 2029: ! 2030: ! 2031: (defun mh-remove-seq (seq) ! 2032: ;; Delete the SEQUENCE. ! 2033: (mh-map-to-seq-msgs 'mh-notate-if-in-one-seq seq ? (+ mh-cmd-note 1) seq) ! 2034: (mh-undefine-sequence seq (list "all")) ! 2035: (mh-delete-seq-locally seq)) ! 2036: ! 2037: ! 2038: (defun mh-delete-seq-locally (seq) ! 2039: ;; Remove mh-e's record of SEQUENCE. ! 2040: (let ((entry (mh-find-seq seq))) ! 2041: (setq mh-seq-list (delq entry mh-seq-list)))) ! 2042: ! 2043: ! 2044: (defun mh-remove-msg-from-seq (msg seq &optional internal-flag) ! 2045: ;; Remove MESSAGE from the SEQUENCE. If optional FLAG is non-nil, do not ! 2046: ;; inform MH of the change. ! 2047: (let ((entry (mh-find-seq seq))) ! 2048: (when entry ! 2049: (mh-notate-if-in-one-seq msg ? (+ mh-cmd-note 1) (mh-seq-name entry)) ! 2050: (if (not internal-flag) ! 2051: (mh-undefine-sequence seq (list msg))) ! 2052: (setcdr entry (delq msg (mh-seq-msgs entry)))))) ! 2053: ! 2054: ! 2055: (defun mh-add-msgs-to-seq (msgs seq &optional internal-flag) ! 2056: ;; Add MESSAGE(s) to the SEQUENCE. If optional FLAG is non-nil, do not mark ! 2057: ;; the message in the scan listing or inform MH of the addition. ! 2058: (let ((entry (mh-find-seq seq))) ! 2059: (if (and msgs (atom msgs)) (setq msgs (list msgs))) ! 2060: (if (null entry) ! 2061: (mh-push (mh-make-seq seq msgs) mh-seq-list) ! 2062: (if msgs (setcdr entry (append msgs (cdr entry))))) ! 2063: (when (not internal-flag) ! 2064: (mh-add-to-sequence seq msgs) ! 2065: (mh-notate-seq seq ?% (+ mh-cmd-note 1))))) ! 2066: ! 2067: ! 2068: (defun mh-rename-seq (seq new-name) ! 2069: "Rename a SEQUENCE to have a new NAME." ! 2070: (interactive "SOld sequence name: \nSNew name: ") ! 2071: (let ((old-seq (mh-find-seq seq))) ! 2072: (if old-seq ! 2073: (rplaca old-seq new-name) ! 2074: (error "Sequence %s does not exists" seq)) ! 2075: (mh-undefine-sequence seq (mh-seq-msgs old-seq)) ! 2076: (mh-define-sequence new-name (mh-seq-msgs old-seq)))) ! 2077: ! 2078: ! 2079: (defun mh-notate-user-sequences () ! 2080: ;; Mark the scan listing of all messages in user-defined sequences. ! 2081: (let ((seqs mh-seq-list)) ! 2082: (while seqs ! 2083: (let ((name (mh-seq-name (car seqs)))) ! 2084: (if (not (mh-internal-seq name)) ! 2085: (mh-notate-seq name ?% (+ mh-cmd-note 1))) ! 2086: (setq seqs (cdr seqs)))))) ! 2087: ! 2088: ! 2089: (defun mh-internal-seq (name) ! 2090: ;; Return non-NIL if NAME is the name of an internal mh-e sequence. ! 2091: (or (memq name '(answered cur deleted forwarded printed)) ! 2092: (eq name mh-unseen-seq) ! 2093: (mh-folder-name name))) ! 2094: ! 2095: ! 2096: (defun mh-folder-name (name) ! 2097: ;; Return non-NIL if NAME is the possible name of a folder (i.e., begins ! 2098: ;; with "+"). ! 2099: (if (symbolp name) ! 2100: (mh-folder-name (symbol-name name)) ! 2101: (equal (substring name 0 1) "+"))) ! 2102: ! 2103: ! 2104: (defun mh-notate-seq (seq notation offset) ! 2105: ;; Mark the scan listing of all messages in the SEQUENCE with the CHARACTER ! 2106: ;; at the given OFFSET from the beginning of the listing line. ! 2107: (mh-map-to-seq-msgs 'mh-notate seq notation offset)) ! 2108: ! 2109: ! 2110: (defun mh-notate-if-in-one-seq (msg notation offset seq) ! 2111: ;; If the MESSAGE is in only the SEQUENCE, then mark the scan listing of the ! 2112: ;; message with the CHARACTER at the given OFFSET from the beginning of the ! 2113: ;; listing line. ! 2114: (let ((in-seqs (mh-seq-containing-msg msg))) ! 2115: (if (and (eq seq (car in-seqs)) (null (cdr in-seqs))) ! 2116: (mh-notate msg notation offset)))) ! 2117: ! 2118: ! 2119: (defun mh-map-to-seq-msgs (func seq &rest args) ! 2120: ;; Invoke the FUNCTION at each message in the SEQUENCE, passing the ! 2121: ;; remaining ARGS as arguments. ! 2122: (save-excursion ! 2123: (let ((msgs (mh-seq-to-msgs seq))) ! 2124: (while msgs ! 2125: (if (mh-goto-msg (car msgs) t t) ! 2126: (apply func (cons (car msgs) args))) ! 2127: (setq msgs (cdr msgs)))))) ! 2128: ! 2129: ! 2130: (defun mh-map-over-seqs (func seq-list) ! 2131: ;; Apply the FUNCTION to each element in the list of SEQUENCES, ! 2132: ;; passing the sequence name and the list of messages as arguments. ! 2133: (while seq-list ! 2134: (funcall func (mh-seq-name (car seq-list)) (mh-seq-msgs (car seq-list))) ! 2135: (setq seq-list (cdr seq-list)))) ! 2136: ! 2137: ! 2138: (defun mh-define-sequences (seq-list) ! 2139: ;; Define the sequences in SEQ-LIST. ! 2140: (mh-map-over-seqs 'mh-define-sequence seq-list)) ! 2141: ! 2142: ! 2143: (defun mh-add-to-sequence (seq msgs) ! 2144: ;; Add to a SEQUENCE each message the list of MSGS. ! 2145: (if (not (equal (substring (symbol-name seq) 0 1) "+")) ! 2146: (if msgs ! 2147: (apply 'mh-exec-cmd (mh-list* "mark" mh-current-folder ! 2148: "-sequence" (format "%s" seq) ! 2149: "-add" msgs))))) ! 2150: ! 2151: (defun mh-define-sequence (seq msgs) ! 2152: ;; Define the SEQUENCE to contain the list of MSGS. Do not mark ! 2153: ;; pseudo-sequences or empty sequences. ! 2154: (if (and msgs ! 2155: (not (equal (substring (symbol-name seq) 0 1) "+"))) ! 2156: (save-excursion ! 2157: (apply 'mh-exec-cmd-quiet (mh-list* " *mh-temp*" ! 2158: "mark" mh-current-folder ! 2159: "-sequence" (format "%s" seq) ! 2160: "-add" "-zero" msgs))))) ! 2161: ! 2162: ! 2163: (defun mh-undefine-sequence (seq msgs) ! 2164: ;; Remove from the SEQUENCE the list of MSGS. ! 2165: (apply 'mh-exec-cmd (mh-list* "mark" mh-current-folder ! 2166: "-sequence" (format "%s" seq) ! 2167: "-delete" msgs))) ! 2168: ! 2169: ! 2170: (defun mh-copy-seq-to-point (seq location) ! 2171: ;; Copy the scan listing of the messages in SEQUENCE to after the point ! 2172: ;; LOCATION in the current buffer. ! 2173: (mh-map-to-seq-msgs 'mh-copy-line-to-point seq location)) ! 2174: ! 2175: ! 2176: (defun mh-copy-line-to-point (msg location) ! 2177: ;; Copy the current line to the LOCATION in the current buffer. ! 2178: (beginning-of-line) ! 2179: (let ((beginning-of-line (point))) ! 2180: (forward-line 1) ! 2181: (copy-region-as-kill beginning-of-line (point)) ! 2182: (goto-char location) ! 2183: (yank) ! 2184: (goto-char beginning-of-line))) ! 2185: ! 2186: ! 2187: ! 2188: ;;; Issue commands to MH. ! 2189: ! 2190: (defun mh-exec-cmd (command &rest args) ! 2191: ;; Execute MH command COMMAND with ARGS. Any output is shown to the user. ! 2192: (save-window-excursion ! 2193: (switch-to-buffer-other-window " *mh-temp*") ! 2194: (erase-buffer) ! 2195: (apply 'call-process ! 2196: (mh-list* (mh-expand-file-name command mh-progs) nil t nil ! 2197: (mh-list-to-string args))) ! 2198: (if (> (buffer-size) 0) ! 2199: (sit-for 5)))) ! 2200: ! 2201: ! 2202: (defun mh-exec-cmd-quiet (buffer command &rest args) ! 2203: ;; In BUFFER, execute MH command COMMAND with ARGS. Return in buffer, if ! 2204: ;; one exists. ! 2205: (when (stringp buffer) ! 2206: (switch-to-buffer buffer) ! 2207: (erase-buffer)) ! 2208: (apply 'call-process ! 2209: (mh-list* (mh-expand-file-name command mh-progs) nil buffer nil ! 2210: (mh-list-to-string args)))) ! 2211: ! 2212: ! 2213: (defun mh-exec-cmd-output (command display &rest args) ! 2214: ;; Execute MH command COMMAND with DISPLAY flag and ARGS putting the output ! 2215: ;; into buffer after point. Set mark after inserted text. ! 2216: (push-mark (point) t) ! 2217: (apply 'call-process ! 2218: (mh-list* (mh-expand-file-name command mh-progs) nil t display ! 2219: (mh-list-to-string args))) ! 2220: (exchange-point-and-mark)) ! 2221: ! 2222: ! 2223: (defun mh-exec-cmd-demon (command &rest args) ! 2224: ;; Execute MH command COMMAND with ARGS. Any output from command is ! 2225: ;; displayed in an asynchronous pop-up window. ! 2226: (save-excursion ! 2227: (switch-to-buffer " *mh-temp*") ! 2228: (erase-buffer)) ! 2229: (let ((process (apply 'start-process ! 2230: (mh-list* "mh-output" nil ! 2231: (expand-file-name command mh-progs) ! 2232: (mh-list-to-string args))))) ! 2233: (set-process-filter process 'mh-process-demon))) ! 2234: ! 2235: ! 2236: (defun mh-process-demon (process output) ! 2237: ;; Process demon that puts output into a temporary buffer. ! 2238: (pop-to-buffer " *mh-temp*") ! 2239: (insert output) ! 2240: (other-window 1)) ! 2241: ! 2242: ! 2243: (defun mh-exec-lib-cmd-output (command &rest args) ! 2244: ;; Execute MH library command COMMAND with ARGS. Put the output into ! 2245: ;; buffer after point. Set mark after inserted text. ! 2246: (push-mark (point) t) ! 2247: (apply 'call-process ! 2248: (mh-list* (mh-expand-file-name command mh-lib) nil t nil ! 2249: (mh-list-to-string args))) ! 2250: (exchange-point-and-mark)) ! 2251: ! 2252: ! 2253: (defun mh-list-to-string (l) ! 2254: ;; Flattens the list L and makes every element of the new list into a string. ! 2255: (let ((new-list nil)) ! 2256: (while l ! 2257: (cond ((null (car l))) ! 2258: ((symbolp (car l)) (mh-push (symbol-name (car l)) new-list)) ! 2259: ((numberp (car l)) (mh-push (int-to-string (car l)) new-list)) ! 2260: ((equal (car l) "")) ! 2261: ((stringp (car l)) (mh-push (car l) new-list)) ! 2262: ((listp (car l)) ! 2263: (setq new-list (nconc (nreverse (mh-list-to-string (car l))) ! 2264: new-list))) ! 2265: (t (error "Bad argument %s" (car l)))) ! 2266: (setq l (cdr l))) ! 2267: (nreverse new-list))) ! 2268: ! 2269: ! 2270: ! 2271: ;;; Commands to annotate a message. ! 2272: ! 2273: (defun mh-annotate-msg (msg buffer note &rest args) ! 2274: ;; Mark the MESSAGE in BUFFER listing with the character NOTE and annotate ! 2275: ;; the saved message with ARGS. ! 2276: (apply 'mh-exec-cmd (mh-list* "anno" buffer msg args)) ! 2277: (save-excursion ! 2278: (set-buffer buffer) ! 2279: (if (symbolp msg) ! 2280: (mh-notate-seq msg note (+ mh-cmd-note 1)) ! 2281: (mh-notate msg note (+ mh-cmd-note 1))))) ! 2282: ! 2283: ! 2284: (defun mh-notate (msg notation offset) ! 2285: ;; Marks MESSAGE with the character NOTATION at position OFFSET. ! 2286: (save-excursion ! 2287: (if (mh-goto-msg msg t t) ! 2288: (let ((buffer-read-only nil) ! 2289: (folder-modified-flag (buffer-modified-p))) ! 2290: (beginning-of-line) ! 2291: (goto-char (+ (point) offset)) ! 2292: (delete-char 1) ! 2293: (insert notation) ! 2294: (mh-set-folder-modified-p folder-modified-flag))))) ! 2295: ! 2296: ! 2297: ! 2298: ;;; User prompting commands. ! 2299: ! 2300: (defun mh-prompt-for-folder (prompt default can-create) ! 2301: ;; Prompt for a folder name with PROMPT. Returns the folder's name. ! 2302: ;; DEFAULT is used if the folder exists and the user types return. ! 2303: ;; If the CAN-CREATE flag is t, then a non-existant folder is made. ! 2304: (let* ((prompt (format "%s folder%s" prompt ! 2305: (if (equal "" default) ! 2306: "? " ! 2307: (format " [%s]? " default)))) ! 2308: name) ! 2309: (if (null mh-folder-list) ! 2310: (setq mh-folder-list (mh-make-folder-list))) ! 2311: (while (and (setq name (completing-read prompt mh-folder-list ! 2312: nil nil "+")) ! 2313: (equal name "") ! 2314: (equal default ""))) ! 2315: (cond ((or (equal name "") (equal name "+")) ! 2316: (setq name default)) ! 2317: ((not (equal (substring name 0 1) "+")) ! 2318: (setq name (format "+%s" name)))) ! 2319: (let ((new-file-p (not (file-exists-p (mh-expand-file-name name))))) ! 2320: (cond ((and new-file-p ! 2321: (y-or-n-p ! 2322: (format "Folder %s does not exist. Create it? " name))) ! 2323: (message "Creating %s" name) ! 2324: (call-process "mkdir" nil nil nil (mh-expand-file-name name)) ! 2325: (message "Creating %s...done" name) ! 2326: (mh-push (list name) mh-folder-list) ! 2327: (mh-push (list (substring name 1 nil)) mh-folder-list)) ! 2328: (new-file-p ! 2329: (error "")) ! 2330: (t ! 2331: (when (null (assoc name mh-folder-list)) ! 2332: (mh-push (list name) mh-folder-list) ! 2333: (mh-push (list (substring name 1 nil)) mh-folder-list))))) ! 2334: name)) ! 2335: ! 2336: ! 2337: (defun mh-make-folder-list () ! 2338: "Return a list of the user's folders. ! 2339: Result is in a form suitable for completing read." ! 2340: (interactive) ! 2341: (message "Collecting folder names...") ! 2342: (save-window-excursion ! 2343: (mh-exec-cmd-quiet " *mh-temp*" "folders" "-fast" ! 2344: (if mh-recursive-folders ! 2345: "-recurse" ! 2346: "-norecurse")) ! 2347: (goto-char (point-min)) ! 2348: (let ((list nil)) ! 2349: (while (not (eobp)) ! 2350: (let ((start (point))) ! 2351: (search-forward "\n" nil t) ! 2352: (let ((folder (buffer-substring start (- (point) 1)))) ! 2353: (mh-push (list (format "+%s" folder)) list)))) ! 2354: (message "Collecting folder names...done") ! 2355: list))) ! 2356: ! 2357: ! 2358: (defun mh-remove-folder-from-folder-list (folder) ! 2359: ;; Remove FOLDER from the list of folders. ! 2360: (setq mh-folder-list ! 2361: (delq (assoc (substring folder 1 nil) mh-folder-list) ! 2362: mh-folder-list))) ! 2363: ! 2364: ! 2365: ! 2366: ;;; Misc. functions. ! 2367: ! 2368: (defun mh-get-msg-num (error-if-no-message) ! 2369: ;; Return the message number of the displayed message. If the argument ! 2370: ;; ERROR-IF-NO-MESSAGE is non-nil, then complain if the cursor is not ! 2371: ;; pointing to a message. ! 2372: (save-excursion ! 2373: (beginning-of-line) ! 2374: (cond ((looking-at mh-msg-number-regexp) ! 2375: (string-to-int (buffer-substring (match-beginning 1) ! 2376: (match-end 1)))) ! 2377: (error-if-no-message ! 2378: (error "Cursor not pointing to message")) ! 2379: (t nil)))) ! 2380: ! 2381: ! 2382: (defun mh-msg-search-pat (n) ! 2383: ;; Return a search pattern for message N in the scan listing. ! 2384: (format mh-msg-search-regexp n)) ! 2385: ! 2386: ! 2387: (defun mh-msg-filename (msg) ! 2388: ;; Returns a string containing the file name of the MESSAGE. ! 2389: (mh-expand-file-name (int-to-string msg) mh-folder-filename)) ! 2390: ! 2391: ! 2392: (defun mh-msg-filenames (msgs folder) ! 2393: ;; Return a string of filenames for MSGS in FOLDER. ! 2394: (let ((mh-folder-filename folder)) ! 2395: (mapconcat (function (lambda (msg) (mh-msg-filename msg))) msgs " "))) ! 2396: ! 2397: ! 2398: (defun mh-find-path () ! 2399: ;; Set mh-user-path, mh-draft-folder, and mh-unseen-seq from ~/.mh_profile. ! 2400: (save-window-excursion ! 2401: (let ((profile (or (getenv "MH") "~/.mh_profile"))) ! 2402: (if (not (file-exists-p profile)) ! 2403: (error "Cannot find ~/.mh_profile")) ! 2404: (switch-to-buffer " *mh-temp*") ! 2405: (erase-buffer) ! 2406: (insert-file-contents profile) ! 2407: (setq mh-draft-folder (mh-get-field "Draft-Folder:" )) ! 2408: (cond ((equal mh-draft-folder "") ! 2409: (setq mh-draft-folder nil)) ! 2410: ((not (equal (substring mh-draft-folder 0 1) "+")) ! 2411: (setq mh-draft-folder (format "+%s" mh-draft-folder)))) ! 2412: (setq mh-user-path (mh-get-field "Path:")) ! 2413: (if (equal mh-user-path "") ! 2414: (setq mh-user-path "Mail")) ! 2415: (setq mh-user-path ! 2416: (file-name-as-directory ! 2417: (expand-file-name mh-user-path (expand-file-name "~")))) ! 2418: (if (and mh-draft-folder ! 2419: (not (file-exists-p (mh-expand-file-name mh-draft-folder)))) ! 2420: (error "Draft folder does not exist. Create it and try again.")) ! 2421: (setq mh-unseen-seq (mh-get-field "Unseen-Sequence:")) ! 2422: (if (equal mh-unseen-seq "") ! 2423: (setq mh-unseen-seq 'unseen) ! 2424: (setq mh-unseen-seq (intern mh-unseen-seq)))))) ! 2425: ! 2426: ! 2427: (defun mh-expand-file-name (filename &optional default) ! 2428: "Just like expand-file-name, but also handles MH folder names. ! 2429: Assumes that any filename that starts with '+' is a folder name." ! 2430: (if (string-equal (substring filename 0 1) "+") ! 2431: (expand-file-name (substring filename 1) mh-user-path) ! 2432: (expand-file-name filename default))) ! 2433: ! 2434: ! 2435: (defun mh-get-field (field) ! 2436: ;; Find and return the value of field FIELD in the current buffer. ! 2437: ;; Returns the empty string if the field is not in the message. ! 2438: (let ((case-fold-search t)) ! 2439: (goto-char (point-min)) ! 2440: (cond ((not (search-forward field nil t)) "") ! 2441: ((looking-at "[\t ]*$") "") ! 2442: (t ! 2443: (re-search-forward "[\t ]*\\([^\t \n].*\\)$" nil t) ! 2444: (let ((field (buffer-substring (match-beginning 1) ! 2445: (match-end 1))) ! 2446: (end-of-match (point))) ! 2447: (forward-line) ! 2448: (while (looking-at "[ \t]") (forward-line 1)) ! 2449: (backward-char 1) ! 2450: (if (<= (point) end-of-match) ! 2451: field ! 2452: (format "%s%s" ! 2453: field ! 2454: (buffer-substring end-of-match (point))))))))) ! 2455: ! 2456: ! 2457: (defun mh-insert-fields (&rest name-values) ! 2458: ;; Insert the NAME-VALUE pairs in the current buffer. ! 2459: ;; Do not insert any pairs whose value is the empty string. ! 2460: (let ((case-fold-search t)) ! 2461: (while name-values ! 2462: (let ((field-name (car name-values)) ! 2463: (value (car (cdr name-values)))) ! 2464: (when (not (equal value "")) ! 2465: (goto-char (point-min)) ! 2466: (cond ((not (re-search-forward (format "^%s" field-name) nil t)) ! 2467: (mh-goto-header-end 0) ! 2468: (insert field-name " " value "\n")) ! 2469: (t ! 2470: (end-of-line) ! 2471: (insert " " value)))) ! 2472: (setq name-values (cdr (cdr name-values))))))) ! 2473: ! 2474: ! 2475: (defun mh-position-on-field (field set-mark) ! 2476: ;; Set point to the end of the line beginning with FIELD. ! 2477: ;; Set the mark to the old value of point, if SET-MARK is non-nil. ! 2478: (let ((case-fold-search t)) ! 2479: (if set-mark (push-mark)) ! 2480: (goto-char (point-min)) ! 2481: (mh-goto-header-end 0) ! 2482: (if (re-search-backward (format "^%s" field) nil t) ! 2483: (progn (end-of-line) t) ! 2484: nil))) ! 2485: ! 2486: ! 2487: (defun mh-goto-header-end (arg) ! 2488: ;; Find the end of the message header in the current buffer and position ! 2489: ;; the cursor at the ARG'th newline after the header. ! 2490: (if (re-search-forward "^$\\|^-+$" nil nil) ! 2491: (forward-line arg))) ! 2492: ! 2493: ! 2494: ! 2495: ;;; Build the folder-mode keymap: ! 2496: ! 2497: (suppress-keymap mh-folder-mode-map) ! 2498: (define-key mh-folder-mode-map "q" 'mh-restore-window-config) ! 2499: (define-key mh-folder-mode-map "b" 'mh-restore-window-config) ! 2500: (define-key mh-folder-mode-map "?" 'mh-msg-is-in-seq) ! 2501: (define-key mh-folder-mode-map "%" 'mh-put-msg-in-seq) ! 2502: (define-key mh-folder-mode-map "\ea" 'mh-edit-again) ! 2503: (define-key mh-folder-mode-map "\e%" 'mh-delete-msg-from-seq) ! 2504: (define-key mh-folder-mode-map "\C-Xn" 'mh-narrow-to-seq) ! 2505: (define-key mh-folder-mode-map "\C-Xw" 'mh-widen) ! 2506: (define-key mh-folder-mode-map "\eb" 'mh-burst-digest) ! 2507: (define-key mh-folder-mode-map "\eu" 'mh-undo-folder) ! 2508: (define-key mh-folder-mode-map "\e " 'mh-page-digest) ! 2509: (define-key mh-folder-mode-map "\e\177" 'mh-page-digest-backwards) ! 2510: (define-key mh-folder-mode-map "\ee" 'mh-extract-rejected-mail) ! 2511: (define-key mh-folder-mode-map "\ef" 'mh-visit-folder) ! 2512: (define-key mh-folder-mode-map "\ek" 'mh-kill-folder) ! 2513: (define-key mh-folder-mode-map "\el" 'mh-list-folders) ! 2514: (define-key mh-folder-mode-map "\ep" 'mh-pack-folder) ! 2515: (define-key mh-folder-mode-map "\es" 'mh-search-folder) ! 2516: (define-key mh-folder-mode-map "\er" 'mh-rescan-folder) ! 2517: (define-key mh-folder-mode-map "l" 'mh-print-msg) ! 2518: (define-key mh-folder-mode-map "t" 'mh-toggle-summarize) ! 2519: (define-key mh-folder-mode-map "c" 'mh-copy-msg) ! 2520: (define-key mh-folder-mode-map ">" 'mh-write-msg-to-file) ! 2521: (define-key mh-folder-mode-map "i" 'mh-inc-folder) ! 2522: (define-key mh-folder-mode-map "x" 'mh-execute-commands) ! 2523: (define-key mh-folder-mode-map "e" 'mh-execute-commands) ! 2524: (define-key mh-folder-mode-map "r" 'mh-redistribute) ! 2525: (define-key mh-folder-mode-map "f" 'mh-forward) ! 2526: (define-key mh-folder-mode-map "s" 'mh-send) ! 2527: (define-key mh-folder-mode-map "m" 'mh-send) ! 2528: (define-key mh-folder-mode-map "a" 'mh-reply) ! 2529: (define-key mh-folder-mode-map "j" 'mh-goto-msg) ! 2530: (define-key mh-folder-mode-map "g" 'mh-goto-msg) ! 2531: (define-key mh-folder-mode-map "\177" 'mh-previous-page) ! 2532: (define-key mh-folder-mode-map " " 'mh-page-msg) ! 2533: (define-key mh-folder-mode-map "." 'mh-show) ! 2534: (define-key mh-folder-mode-map "u" 'mh-undo) ! 2535: (define-key mh-folder-mode-map "!" 'mh-refile-or-write-again) ! 2536: (define-key mh-folder-mode-map "^" 'mh-refile-msg) ! 2537: (define-key mh-folder-mode-map "d" 'mh-delete-msg) ! 2538: (define-key mh-folder-mode-map "p" 'mh-previous-undeleted-msg) ! 2539: (define-key mh-folder-mode-map "n" 'mh-next-undeleted-msg) ! 2540: ! 2541: ! 2542: ;;; Build the letter-mode keymap: ! 2543: ! 2544: (define-key mh-letter-mode-map "\C-c\C-f\C-b" 'mh-to-field) ! 2545: (define-key mh-letter-mode-map "\C-c\C-f\C-c" 'mh-to-field) ! 2546: (define-key mh-letter-mode-map "\C-c\C-f\C-f" 'mh-to-field) ! 2547: (define-key mh-letter-mode-map "\C-c\C-f\C-s" 'mh-to-field) ! 2548: (define-key mh-letter-mode-map "\C-c\C-f\C-t" 'mh-to-field) ! 2549: (define-key mh-letter-mode-map "\C-c\C-fb" 'mh-to-field) ! 2550: (define-key mh-letter-mode-map "\C-c\C-fc" 'mh-to-field) ! 2551: (define-key mh-letter-mode-map "\C-c\C-ff" 'mh-to-field) ! 2552: (define-key mh-letter-mode-map "\C-c\C-fs" 'mh-to-field) ! 2553: (define-key mh-letter-mode-map "\C-c\C-ft" 'mh-to-field) ! 2554: (define-key mh-letter-mode-map "\C-c\C-q" 'mh-fully-kill-draft) ! 2555: (define-key mh-letter-mode-map "\C-c\C-w" 'mh-check-whom) ! 2556: (define-key mh-letter-mode-map "\C-c\C-i" 'mh-insert-letter) ! 2557: (define-key mh-letter-mode-map "\C-c\C-y" 'mh-yank-cur-msg) ! 2558: (define-key mh-letter-mode-map "\C-c\C-s" 'mh-insert-signature) ! 2559: (define-key mh-letter-mode-map "\C-c\C-c" 'mh-send-letter) ! 2560: ! 2561: ! 2562: ;;; Build the pick-mode keymap: ! 2563: ! 2564: (define-key mh-pick-mode-map "\C-c\C-c" 'mh-do-pick-search) ! 2565: (define-key mh-pick-mode-map "\C-c\C-f\C-b" 'mh-to-field) ! 2566: (define-key mh-pick-mode-map "\C-c\C-f\C-c" 'mh-to-field) ! 2567: (define-key mh-pick-mode-map "\C-c\C-f\C-f" 'mh-to-field) ! 2568: (define-key mh-pick-mode-map "\C-c\C-f\C-s" 'mh-to-field) ! 2569: (define-key mh-pick-mode-map "\C-c\C-f\C-t" 'mh-to-field) ! 2570: (define-key mh-pick-mode-map "\C-c\C-fb" 'mh-to-field) ! 2571: (define-key mh-pick-mode-map "\C-c\C-fc" 'mh-to-field) ! 2572: (define-key mh-pick-mode-map "\C-c\C-ff" 'mh-to-field) ! 2573: (define-key mh-pick-mode-map "\C-c\C-fs" 'mh-to-field) ! 2574: (define-key mh-pick-mode-map "\C-c\C-ft" 'mh-to-field) ! 2575: (define-key mh-pick-mode-map "\C-c\C-w" 'mh-check-whom) ! 2576:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.