|
|
1.1 ! root 1: ;;; mh-e.el (Version: 3.3e for GNU Emacs Version 17 and MH.5 and MH.6) ! 2: ! 3: ;;; Copyright (C) James Larus ([email protected], ucbvax!larus), 1985 ! 4: ;;; Please send suggestions and corrections to the above address. ! 5: ;;; ! 6: ;;; This file contains mh-e, a GNU Emacs front end to the MH mail system. ! 7: ! 8: ! 9: ;; GNU Emacs is distributed in the hope that it will be useful, ! 10: ;; but without any warranty. No author or distributor ! 11: ;; accepts responsibility to anyone for the consequences of using it ! 12: ;; or for whether it serves any particular purpose or works at all, ! 13: ;; unless he says so in writing. ! 14: ! 15: ;; Everyone is granted permission to copy, modify and redistribute ! 16: ;; GNU Emacs, but only under the conditions described in the ! 17: ;; document "GNU Emacs copying permission notice". An exact copy ! 18: ;; of the document is supposed to have been given to you along with ! 19: ;; GNU Emacs so that you can know how you may redistribute it all. ! 20: ;; It should be in a file named COPYING. Among other things, the ! 21: ;; copyright notice and this notice must be preserved on all copies. ! 22: ! 23: ! 24: ;;; Original version for Gosling emacs by Brian Reid, Stanford, 1982. ! 25: ;;; Modified by James Larus, BBN, July 1984 and UCB, 1984 & 1985. ! 26: ;;; Rewritten for GNU Emacs, James Larus 1985. ! 27: ! 28: ! 29: ;;; NB MH must have been compiled with the MHE compiler flag or several ! 30: ;;; features necessary to this program will be missing. ! 31: ! 32: ! 33: ! 34: ;;; Constants: ! 35: ! 36: ;;; Set for local environment: ! 37: ;;;* These are now in paths.el. ! 38: ;;;(defvar mh-progs "/usr/new/mh/" "Directory containing MH commands") ! 39: ;;;(defvar mh-lib "/usr/new/lib/mh/" "Directory of MH library") ! 40: ! 41: ! 42: ;;; Mode hooks: ! 43: ! 44: (defvar mh-folder-mode-hook nil ! 45: "*Invoked in mh-folder-mode on a new folder.") ! 46: (defvar mh-letter-mode-hook nil ! 47: "*Invoked in mh-letter-mode on a new letter.") ! 48: (defvar mh-compose-letter-hook nil ! 49: "*Invoked in mh-compose-and-send-mail on an outgoing letter. It is passed ! 50: three arguments: TO recipients, SUBJECT, and CC recipients.") ! 51: ! 52: ! 53: ;;; Personal preferences: ! 54: ! 55: (defvar mh-clean-message-header nil ! 56: "*Non-nil means remove invisible header lines in messages.") ! 57: (defvar mh-use-mhl nil ! 58: "*Non-nil means use mhl to format messages.") ! 59: (defvar mh-lpr-command-format "lpr -p -J '%s'" ! 60: "*Format for Unix command line to print a message. The format should be ! 61: a unix command line, with the string \"%s\" where the folder and message ! 62: number should appear.") ! 63: (defvar mh-summary-height 4 ! 64: "*Number of lines in summary window.") ! 65: (defvar mh-ins-buf-prefix ">> " ! 66: "*String to put before each non-blank line of the the current message ! 67: as it is inserted in an outgoing letter.") ! 68: ! 69: ! 70: ;;; Real constants: ! 71: ! 72: (defvar mh-cmd-note 4 "Offset to insert notation") ! 73: (defvar mh-invisible-headers ! 74: "^Received: \\|^Message-Id: \\|^Remailed-\\|^Via: \\|^Mail-from: \\|\^Return-Path: \\|^In-Reply-To: \\|^Resent-" ! 75: "Regexp specifying headers that are not to be shown.") ! 76: (defvar mh-rejected-letter-start "^ ----- Unsent message follows -----$" ! 77: "Regexp specifying the beginning of the wrapper around a letter returned ! 78: by the mail system.") ! 79: (defvar mh-good-msg-regexp "^....[^D^]" ! 80: "Regexp specifiying the scan lines that are 'good' messages") ! 81: ! 82: ;;; Global variables: ! 83: ! 84: (defvar mh-user-path "" "User's mail folder.") ! 85: (defvar mh-last-destination nil "Destination of last "move" command.") ! 86: (defvar mh-folder-mode-map (make-sparse-keymap) "Keymap for MH folders.") ! 87: (defvar mh-letter-mode-map (make-sparse-keymap) "Keymap for composing mail.") ! 88: (defvar mh-pick-mode-map (make-sparse-keymap) "Keymap for searching folder.") ! 89: (defvar mh-folder-list nil "List of folder names for completion.") ! 90: ! 91: ;;; Macros and generic functions: ! 92: ! 93: (defmacro push (v l) ! 94: (list 'setq l (list 'cons v l))) ! 95: ! 96: (defmacro caar (l) ! 97: (list 'car (list 'car l))) ! 98: ! 99: (defmacro cadr (l) ! 100: (list 'car (list 'cdr l))) ! 101: ! 102: (defmacro cdar (l) ! 103: (list 'cdr (list 'car l))) ! 104: ! 105: (defmacro cddr (l) ! 106: (list 'cdr (list 'cdr l))) ! 107: ! 108: (defmacro when (pred &rest body) ! 109: (list 'cond (cons pred body))) ! 110: ! 111: (defun mapc (func list) ! 112: (while list ! 113: (funcall func (car list)) ! 114: (setq list (cdr list)))) ! 115: ! 116: ! 117: ! 118: ;;; Entry points: ! 119: ! 120: (defun mh-rmail (&optional arg) ! 121: "Inc(orporate) new mail if optional ARG omitted, or scan a MH mail box ! 122: if arg is present. This front end uses the MH mail system, which uses ! 123: different conventions from the usual mail system." ! 124: (interactive "P") ! 125: (mh-find-path) ! 126: (if (null mh-folder-list) ! 127: (setq mh-folder-list (mh-make-folder-list))) ! 128: (cond (arg ! 129: (let ((folder (mh-prompt-for-folder "mh" "+inbox" t)) ! 130: (range (read-string "Range [all]? "))) ! 131: (mh-scan-folder folder (if (equal range "") "all" range)) ! 132: (delete-other-windows))) ! 133: (t ! 134: (mh-inc-folder)))) ! 135: ! 136: ! 137: (defun mh-smail () ! 138: "Send mail using the MH mail system." ! 139: (interactive) ! 140: (mh-find-path) ! 141: (call-interactively 'mh-send)) ! 142: ! 143: ! 144: ! 145: ;;; User executable mh-e commands: ! 146: ! 147: ! 148: (defun mh-answer (&optional arg) ! 149: "Answer a letter. If given a non-nil ARGUMENT, then include the current ! 150: message in the reply." ! 151: (interactive "P") ! 152: (let ((msg-filename (mh-msg-filename)) ! 153: (msg (mh-get-msg-num t)) ! 154: (minibuffer-help-form ! 155: "from => Sender\n to => Sender and primary recipients\n cc => Sender and all recipients") ! 156: (folder mh-current-folder) ! 157: (show-buffer mh-show-buffer)) ! 158: (let ((reply-to (completing-read ! 159: "Reply to whom: " '(("from") ("to") ("cc")) nil t))) ! 160: (message "Composing a reply...") ! 161: (cond ((or (equal reply-to "from") (equal reply-to "")) ! 162: (apply 'mh-exec-cmd ! 163: (nconc ! 164: (list "repl" "-build" mh-current-folder msg "-nocc" "all") ! 165: (if arg (list "-filter" "mhl.reply"))))) ! 166: ((equal reply-to "to") ! 167: (apply 'mh-exec-cmd ! 168: (nconc ! 169: (list "repl" "-build" mh-current-folder msg "-cc" "to") ! 170: (if arg (list "-filter" "mhl.reply"))))) ! 171: ((equal reply-to "cc") ! 172: (apply 'mh-exec-cmd ! 173: (nconc ! 174: (list "repl" "-build" mh-current-folder msg "-cc" "all") ! 175: (if arg (list "-filter" "mhl.reply")))))) ! 176: ! 177: (mh-read-draft) ! 178: (delete-other-windows) ! 179: (when (or (zerop (buffer-size)) ! 180: (not (y-or-n-p "The file 'draft' exists. Use for reply? "))) ! 181: (erase-buffer) ! 182: (insert-file-contents (format "%sreply" mh-user-path)) ! 183: (delete-file (format "%sreply" mh-user-path))) ! 184: (set-buffer-modified-p nil) ! 185: ! 186: (let ((to (mh-get-field "To:")) ! 187: (subject (mh-get-field "Subject:")) ! 188: (cc (mh-get-field "Cc:"))) ! 189: (goto-char (point-min)) ! 190: (re-search-forward "^$" (point-max) nil) ! 191: (if (not arg) ! 192: (mh-display-msg msg msg-filename show-buffer)) ! 193: (mh-add-msg-to-seq msg "answered" t) ! 194: (message "Composing a reply...done") ! 195: (mh-compose-and-send-mail "" folder to subject cc "-" "Replied:"))))) ! 196: ! 197: ! 198: (defun my-apply-command-to-seq (command) ! 199: "Applies the next command to all messages in a sequence to be prompted for." ! 200: (interactive "k") ! 201: (funcall (key-binding command) (mh-read-seq "Apply to" mh-narrowed-to-seq))) ! 202: ! 203: ! 204: (defun mh-copy-msg (&optional seq) ! 205: "Copy specified message(s) to another folder without deleting them. ! 206: Optional argument is a SEQUENCE name to copy." ! 207: (interactive "P") ! 208: (let ((msgs (if seq seq (mh-get-msg-num t)))) ! 209: (mh-exec-cmd "refile" msgs "-link" "-src" mh-current-folder ! 210: (mh-prompt-for-folder "Copy to" "" t)) ! 211: (if seq ! 212: (mh-notate-seq msgs ?C mh-cmd-note) ! 213: (mh-notate ?C mh-cmd-note)))) ! 214: ! 215: ! 216: (defun mh-delete-msg (&optional seq) ! 217: "Marks the specified message(s) for later deletion. Optional argument is a ! 218: SEQUENCE name to delete." ! 219: (interactive "P") ! 220: (if seq ! 221: (mh-map-to-seq-msgs 'mh-delete-a-msg seq) ! 222: (mh-delete-a-msg)) ! 223: (mh-next-msg)) ! 224: ! 225: ! 226: (defun mh-delete-msg-from-seq (&optional arg) ! 227: "Deletes a message from a sequence or, if optional ARG is non-nil, deletes ! 228: the sequence." ! 229: (interactive "P") ! 230: (if arg ! 231: (mh-remove-seq (mh-read-seq "Delete")) ! 232: (mh-remove-msg-from-seq (mh-get-msg-num t) (mh-read-seq "Delete from"))) ! 233: (mh-next-msg)) ! 234: ! 235: ! 236: (defun mh-execute-commands () ! 237: "Process outstanding delete and move commands." ! 238: (interactive) ! 239: (if mh-narrowed-to-seq (mh-widen)) ! 240: (save-excursion ! 241: (mh-process-commands mh-current-folder)) ! 242: (delete-other-windows) ! 243: (setq mh-summarize t) ! 244: (setq mode-name "Mh-Summary") ! 245: (setq mode-line-format (mh-make-mode-line))) ! 246: ! 247: ! 248: (defun mh-extract-rejected-mail () ! 249: "Extract a letter returned by the mail system and make it resendable." ! 250: (interactive "") ! 251: (let ((msg-filename (format "%s%d" mh-folder-filename (mh-get-msg-num t)))) ! 252: (mh-read-draft) ! 253: (when (or (zerop (buffer-size)) ! 254: (not (y-or-n-p "The file 'draft' exists. Use it? "))) ! 255: (erase-buffer) ! 256: (insert-file-contents msg-filename)) ! 257: (goto-char (point-min)) ! 258: (re-search-forward mh-rejected-letter-start) ! 259: (forward-char 1) ! 260: (kill-region (point-min) (point)) ! 261: (let ((mh-invisible-headers "^Date:\\|^Received:\\|^Message-Id:\\|^From:")) ! 262: (mh-clean-msg-header (point-min))) ! 263: (goto-char (point-min)) ! 264: (switch-to-buffer mh-current-folder) ! 265: (mh-compose-and-send-mail "" mh-current-folder (mh-get-field "To") ! 266: (mh-get-field "From") (mh-get-field "cc")))) ! 267: ! 268: ! 269: (defun mh-forward (&optional seq) ! 270: "Forward a letter. Optional argument is a SEQUENCE of messages to forward." ! 271: (interactive "P") ! 272: (let ((to (read-string "To: ")) ! 273: (cc (read-string "Cc: ")) ! 274: (msg-filename (mh-msg-filename)) ! 275: (msg (if seq seq (mh-get-msg-num t))) ! 276: (folder mh-current-folder)) ! 277: (cond ((or (not (file-exists-p (format "%sdraft" mh-user-path))) ! 278: (y-or-n-p "The file 'draft' exists. Discard it? ")) ! 279: (mh-exec-cmd "forw" "-build" mh-current-folder msg) ! 280: (mh-read-draft) ! 281: (mh-insert-fields "To:" to "Cc:" cc) ! 282: (set-buffer-modified-p nil)) ! 283: (t ! 284: (mh-read-draft))) ! 285: (goto-char (point-min)) ! 286: (re-search-forward "^------- Forwarded Message") ! 287: (previous-line 1) ! 288: (narrow-to-region (point) (point-max)) ! 289: (let* ((subject (save-excursion (mh-get-field "From:"))) ! 290: (trim (string-match "<" subject)) ! 291: (forw-subject (save-excursion (mh-get-field "Subject:")))) ! 292: (if trim ! 293: (setq subject (substring subject 0 (1- trim)))) ! 294: (widen) ! 295: (save-excursion ! 296: (mh-insert-fields "Subject:" (format "[%s: %s]" subject forw-subject))) ! 297: (delete-other-windows) ! 298: (if seq ! 299: (mh-add-msg-list-to-seq (mh-seq-to-msgs seq) "forwarded" t) ! 300: (mh-add-msg-to-seq msg "forwarded" t)) ! 301: (mh-compose-and-send-mail "" folder to subject cc "F" "Forwarded:")))) ! 302: ! 303: ! 304: (defun mh-goto-msg (number &optional no-error-if-no-message) ! 305: "Position the cursor at message NUMBER. Do not signal an error if optional ! 306: ARG is t. Return non-nil if cursor is at message." ! 307: (interactive "nMessage number? ") ! 308: (let ((starting-place (point))) ! 309: (goto-char (point-min)) ! 310: (cond ((not (re-search-forward (mh-msg-search-pat number) nil t)) ! 311: (goto-char starting-place) ! 312: (if (not no-error-if-no-message) (error "No message %d " number)) ! 313: nil) ! 314: (t ! 315: (beginning-of-line) ! 316: (mh-maybe-show) ! 317: t)))) ! 318: ! 319: ! 320: (defun mh-inc-folder () ! 321: "inc(orporate) new mail into inbox." ! 322: (interactive) ! 323: (switch-to-buffer "+inbox") ! 324: (if (or (not (boundp 'mh-current-folder)) (null mh-current-folder)) ! 325: (mh-make-folder "+inbox")) ! 326: (mh-get-new-mail)) ! 327: ! 328: ! 329: (defun mh-kill-folder () ! 330: "Removes the current folder." ! 331: (interactive) ! 332: (if (yes-or-no-p (format "Remove folder %s? " mh-current-folder)) ! 333: (let ((folder mh-current-folder)) ! 334: (switch-to-buffer-other-window " *mh-temp*") ! 335: (mh-exec-cmd "rmf" "-nointeractive" folder) ! 336: (mh-remove-folder-from-folder-list folder) ! 337: (message "Folder removed") ! 338: (kill-buffer folder)) ! 339: (message "Folder not removed"))) ! 340: ! 341: ! 342: (defun mh-list-folders () ! 343: "List mail folders." ! 344: (interactive) ! 345: (message "listing folders...") ! 346: (switch-to-buffer-other-window " *mh-temp*") ! 347: (erase-buffer) ! 348: (mh-exec-cmd-output "folders") ! 349: (goto-char (point-min)) ! 350: (message "listing folders...done")) ! 351: ! 352: ! 353: (defun mh-msg-is-in-seq () ! 354: "Displays the sequences that the current messages is in." ! 355: (interactive) ! 356: (let ((msg (mh-get-msg-num t)) ! 357: (l mh-seq-list) ! 358: (seqs "")) ! 359: (while l ! 360: (if (memq msg (cdar l)) ! 361: (setq seqs (format "%s %s" (symbol-name (caar l)) seqs))) ! 362: (setq l (cdr l))) ! 363: (message "Message %d is in sequences: %s" msg seqs))) ! 364: ! 365: ! 366: (defun mh-move-msg (&optional seq) ! 367: "Move specified message(s) to another folder. Optional argument is a ! 368: SEQUENCE of messages to refile." ! 369: (interactive "P") ! 370: (setq mh-last-destination ! 371: (cons 'move (intern (mh-prompt-for-folder "Destination" "" t)))) ! 372: (if seq ! 373: (mh-map-to-seq-msgs 'mh-move-a-msg seq (cdr mh-last-destination)) ! 374: (mh-move-a-msg (cdr mh-last-destination))) ! 375: (mh-next-msg)) ! 376: ! 377: ! 378: (defun mh-move-or-write-again () ! 379: "Move or write the current message to same folder or file as the last move ! 380: or write." ! 381: (interactive) ! 382: (if (null mh-last-destination) ! 383: (error "No previous move")) ! 384: (cond ((eq (car mh-last-destination) 'move) ! 385: (mh-move-a-msg (cdr mh-last-destination)) ! 386: (message "Destination folder: %s" (cdr mh-last-destination))) ! 387: (t ! 388: (mh-write-msg-to-file (cdr mh-last-destination)) ! 389: (message "Destination: %s" (cdr mh-last-destination)))) ! 390: (sit-for 3) ! 391: (mh-next-msg)) ! 392: ! 393: ! 394: (defun mh-narrow-to-seq () ! 395: "Restrict the display of the current folder to the messages in the sequence ! 396: to be prompted for. Use \\[mh-widen] to undo this command." ! 397: (interactive "") ! 398: (let ((seq (mh-read-seq "Narrow to")) ! 399: (eob (point-max)) ! 400: (buffer-read-only nil)) ! 401: (cond ((mh-seq-to-msgs seq) ! 402: (mh-copy-seq-to-point seq eob) ! 403: (narrow-to-region eob (point-max)) ! 404: (setq mode-line-format (mh-make-mode-line (symbol-name seq))) ! 405: (recenter) ! 406: (setq mh-narrowed-to-seq seq)) ! 407: (t ! 408: (error "No messages in sequence `%s'" (symbol-name seq)))))) ! 409: ! 410: ! 411: (defun mh-next-line (&optional arg) ! 412: "Move to next undeleted message in window and display body if summary ! 413: flag set." ! 414: (interactive "p") ! 415: (forward-line (if arg arg 1)) ! 416: (setq mh-next-direction 'forward) ! 417: (cond ((re-search-forward mh-good-msg-regexp nil 0 arg) ! 418: (beginning-of-line) ! 419: (mh-maybe-show)) ! 420: (t ! 421: (forward-line -1) ! 422: (sit-for 2) ! 423: (message "No more messages")))) ! 424: ! 425: ! 426: (defun mh-renumber-folder () ! 427: "Renumber messages in folder to be 1..N." ! 428: (interactive) ! 429: (message "packing buffer...") ! 430: (mh-pack-folder) ! 431: (mh-unmark-all-headers nil) ! 432: (mh-goto-cur-msg) ! 433: (message "packing buffer...done")) ! 434: ! 435: ! 436: (defun mh-page-digest () ! 437: "Advance displayed message to next digested message." ! 438: (interactive) ! 439: (save-excursion ! 440: (switch-to-buffer-other-window mh-show-buffer) ! 441: (move-to-window-line nil) ! 442: (let ((case-fold-search nil)) ! 443: (when (not (search-forward "\nFrom:" nil t)) ! 444: (other-window -1) ! 445: (error "No more messages"))) ! 446: (recenter 0) ! 447: (other-window -1))) ! 448: ! 449: ! 450: (defun mh-page-msg (&optional arg) ! 451: (interactive "P") ! 452: (scroll-other-window arg)) ! 453: ! 454: ! 455: (defun mh-previous-line (&optional arg) ! 456: "Move to previous message in window and display body if summary flag set." ! 457: (interactive "p") ! 458: (setq mh-next-direction 'backward) ! 459: (if (not (re-search-backward mh-good-msg-regexp nil 0 arg)) ! 460: (message "Beginning of messages") ! 461: (mh-maybe-show))) ! 462: ! 463: ! 464: (defun mh-previous-page () ! 465: "Page the displayed message backwards." ! 466: (interactive) ! 467: (save-excursion ! 468: (switch-to-buffer-other-window mh-show-buffer) ! 469: (unwind-protect ! 470: (scroll-down nil) ! 471: (other-window -1)))) ! 472: ! 473: ! 474: (defun mh-print-msg (&optional seq) ! 475: "Print specified message(s) on a line printer. Optional argument is a ! 476: SEQUENCE of messages to print." ! 477: (interactive "P") ! 478: (let ((msgs (if seq ! 479: (reverse (mh-seq-to-msgs seq)) ! 480: (list (mh-get-msg-num t))))) ! 481: (message "printing message...") ! 482: (call-process shell-file-name nil 0 nil "-c" ! 483: (if seq ! 484: (format "(scan -clear %s ; %smhl -nobell -clear %s) | %s" ! 485: (mapconcat (function (lambda (msg) msg)) msgs " ") ! 486: mh-lib ! 487: (mh-msg-filenames msgs mh-folder-filename) ! 488: (format mh-lpr-command-format ! 489: (if seq ! 490: "Mail" ! 491: (format "%s/%d" mh-current-folder ! 492: (mh-get-msg-num t))))) ! 493: (format "%smhl -nobell -clear %s | %s" ! 494: mh-lib ! 495: (mh-msg-filenames msgs mh-folder-filename) ! 496: (format mh-lpr-command-format ! 497: (if seq ! 498: "Mail" ! 499: (format "%s/%d" mh-current-folder ! 500: (mh-get-msg-num t))))))) ! 501: (if seq ! 502: (mh-notate-seq msgs ?P mh-cmd-note) ! 503: (mh-notate ?P mh-cmd-note)) ! 504: (mh-add-msg-list-to-seq msgs 'printed t) ! 505: (message "printing message...done"))) ! 506: ! 507: ! 508: (defun mh-put-msg-in-seq (&optional arg) ! 509: "Add a message to a sequence or, if optional ARG is non-nil, add the ! 510: messages from a sequence to another sequence." ! 511: (interactive "P") ! 512: (if arg ! 513: (mh-add-msg-list-to-seq (mh-seq-to-msgs ! 514: (mh-read-seq "Add messages from")) ! 515: (mh-read-seq "to")) ! 516: (mh-add-msg-to-seq (mh-get-msg-num t) (mh-read-seq "Add to"))) ! 517: (mh-next-msg)) ! 518: ! 519: ! 520: (defun mh-rescan-folder (&optional arg) ! 521: "Rescan a folder after optionally processing the outstanding commands. If ! 522: the optional argument is non-nil, then prompt for the range of messages to ! 523: display, otherwise assume the whole buffer." ! 524: (interactive "P") ! 525: (if (and (or mh-delete-list mh-move-list) ! 526: (y-or-n-p "Process commands? ")) ! 527: (mh-process-commands mh-current-folder)) ! 528: (setq mh-next-direction 'forward) ! 529: (mh-scan-folder mh-current-folder ! 530: (if arg (read-string "Range [all]? ") "all"))) ! 531: ! 532: ! 533: (defun mh-redistribute (to cc) ! 534: "Redistribute a letter." ! 535: (interactive "sRedist-To: \nsRedist-Cc: ") ! 536: (let ((msg-filename (mh-msg-filename)) ! 537: (msg (mh-get-msg-num t)) ! 538: (folder mh-current-folder)) ! 539: (save-window-excursion ! 540: (mh-read-draft) ! 541: (when (or (zerop (buffer-size)) ! 542: (not (y-or-n-p ! 543: "The file 'draft' exists. Redistribute old version? "))) ! 544: (erase-buffer) ! 545: (insert-file-contents msg-filename)) ! 546: (re-search-forward "^$\\|^---") ! 547: (insert "Resent-To: " to "\n") ! 548: (if (not (equal cc "")) ! 549: (insert "Resent-cc: " cc "\n")) ! 550: (let ((mh-invisible-headers "^Message-Id:\\|^Received:\\|Return-Path:")) ! 551: (mh-clean-msg-header (point-min))) ! 552: (save-buffer) ! 553: (message "Redistributing...") ! 554: (call-process "/bin/sh" nil 0 nil "-c" ! 555: (format "mhdist=1 mhaltmsg=%s %s/send -push %s/draft" ! 556: msg-filename mh-progs mh-user-path)) ! 557: (mh-annotate-msg msg folder "R" ! 558: "-component" "Resent:" ! 559: "-text" (format "\"%s %s\"" to cc)) ! 560: (message "Redistributing...done")))) ! 561: ! 562: ! 563: (defun mh-write-msg-to-file (file) ! 564: "Append the current message to the end of a file." ! 565: (interactive "FSave message in file: ") ! 566: (setq mh-last-destination (cons 'write file)) ! 567: (call-process shell-file-name nil 0 nil "-c" ! 568: (format "cat %s >> %s " (mh-msg-filename) file))) ! 569: ! 570: ! 571: (defun mh-search-folder () ! 572: "Search the current folder for messages matching a pattern." ! 573: (interactive) ! 574: (let ((folder mh-current-folder)) ! 575: (switch-to-buffer-other-window "pick-pattern") ! 576: (if (or (zerop (buffer-size)) ! 577: (not (y-or-n-p "Reuse pattern? "))) ! 578: (mh-make-pick-template) ! 579: (message "")) ! 580: (setq mh-searching-folder folder))) ! 581: ! 582: ! 583: (defun mh-send (to cc subject) ! 584: "Compose and send a letter." ! 585: (interactive "sTo: \nsCc: \nsSubject: ") ! 586: (let ((folder (if (boundp 'mh-current-folder) mh-current-folder))) ! 587: (message "Composing a message...") ! 588: (mh-read-draft) ! 589: (delete-other-windows) ! 590: (when (or (zerop (buffer-size)) ! 591: (not (y-or-n-p "The file 'draft' exists. Use it? "))) ! 592: (erase-buffer) ! 593: (if (file-exists-p (format "%scomponents" mh-user-path)) ! 594: (insert-file-contents (format "%scomponents" mh-user-path)) ! 595: (if (file-exists-p (format "%scomponents" mh-lib)) ! 596: (insert-file-contents (format "%scomponents" mh-lib)) ! 597: (error "Can't find components"))) ! 598: (mh-insert-fields "To:" to "Subject:" subject "Cc:" cc) ! 599: (set-buffer-modified-p nil) ! 600: (goto-char (point-max)) ! 601: (message "Composing a message...done")) ! 602: (mh-compose-and-send-mail "" folder to subject cc))) ! 603: ! 604: ! 605: (defun mh-show () ! 606: "Show message indicated by cursor in scan buffer." ! 607: (interactive) ! 608: (setq mh-summarize nil) ! 609: (setq mode-name "Mh-Show") ! 610: (let ((msg-num (mh-get-msg-num t)) ! 611: (folder mh-current-folder)) ! 612: (mh-display-msg msg-num (mh-msg-filename) mh-show-buffer) ! 613: ! 614: ;; These contortions are to force the summary line to be the top window. ! 615: (switch-to-buffer-other-window folder) ! 616: (delete-other-windows) ! 617: (switch-to-buffer-other-window mh-show-buffer) ! 618: (switch-to-buffer-other-window folder) ! 619: (shrink-window (- (window-height) mh-summary-height)) ! 620: (recenter 1) ! 621: (push msg-num mh-seen-list))) ! 622: ! 623: ! 624: (defun mh-sort-folder () ! 625: "Sort the messages in the current folder by date." ! 626: (interactive "") ! 627: (mh-process-commands mh-current-folder) ! 628: (setq mh-next-direction 'forward) ! 629: (message "sorting folder...") ! 630: (mh-exec-cmd "sortm" mh-current-folder) ! 631: (message "sorting folder...done") ! 632: (mh-scan-folder mh-current-folder "all")) ! 633: ! 634: ! 635: (defun mh-toggle-summarize () ! 636: "Turn the summary mode of displaying messages on or off." ! 637: (interactive) ! 638: (setq mh-summarize (not mh-summarize)) ! 639: (cond (mh-summarize ! 640: (delete-other-windows) ! 641: (setq mode-name "Mh-Summarize") ! 642: (recenter (/ (window-height) 2))) ! 643: (t ! 644: (setq mode-name "Mh-Show") ! 645: (mh-show)))) ! 646: ! 647: ! 648: (defun mh-undo (&optional seq) ! 649: "Undo the deletion or move of the specified message(s). Optional argument ! 650: is a name of a sequence of messages to apply undo to." ! 651: (interactive "P") ! 652: (cond ((looking-at "^....D") ! 653: (cond (seq ! 654: (mapc (function (lambda (msg) ! 655: (setq mh-delete-list ! 656: (delq msg mh-delete-list)) ! 657: (mh-remove-msg-from-seq msg 'deleted))) ! 658: (mh-seq-to-msgs seq)) ! 659: (mh-notate-seq seq ? mh-cmd-note)) ! 660: (t ! 661: (let ((msg (mh-get-msg-num t))) ! 662: (setq mh-delete-list (delq msg mh-delete-list)) ! 663: (mh-remove-msg-from-seq msg 'deleted) ! 664: (mh-notate ? mh-cmd-note))))) ! 665: ! 666: ((looking-at "^....\\^") ! 667: (cond (seq ! 668: (mapc (function (lambda (msg) ! 669: (mapc (function ! 670: (lambda (dest) ! 671: (mh-remove-msg-from-seq msg dest))) ! 672: mh-move-list))) ! 673: (mh-seq-to-msgs seq)) ! 674: (mh-notate-seq seq ? mh-cmd-note)) ! 675: (t ! 676: (let ((msg (mh-get-msg-num t))) ! 677: (mapc (function (lambda (dest) ! 678: (mh-remove-msg-from-seq msg dest))) ! 679: mh-move-list) ! 680: (mh-notate ? mh-cmd-note))))) ! 681: ! 682: (t nil))) ! 683: ! 684: ! 685: (defun mh-undo-folder () ! 686: "Undo all commands in current folder." ! 687: (interactive "") ! 688: (cond ((yes-or-no-p "Undo all commands in folder? ") ! 689: (setq mh-delete-list nil ! 690: mh-move-list nil ! 691: mh-seq-list nil ! 692: mh-next-direction 'forward) ! 693: (mh-unmark-all-headers t)) ! 694: (t ! 695: (message "Commands not undone.")))) ! 696: ! 697: ! 698: (defun mh-visit-folder (&optional arg) ! 699: "Visit a new folder. If optional argument is non-nil, then prompt for the ! 700: range of messages, otherwise scan the whole buffer." ! 701: (interactive "p") ! 702: (let ((folder (mh-prompt-for-folder "Visit" "" t)) ! 703: (range (if arg (read-string "Range [all]? ") "all"))) ! 704: (mh-scan-folder folder (if (equal range "") "all" range)) ! 705: (delete-other-windows))) ! 706: ! 707: ! 708: (defun mh-widen () ! 709: "Remove restrictions from the current folder, thereby showing all messages." ! 710: (interactive "") ! 711: (let ((buffer-read-only nil)) ! 712: (delete-region (point-min) (point-max)) ! 713: (widen) ! 714: (setq mode-line-format (mh-make-mode-line))) ! 715: (setq mh-narrowed-to-seq nil)) ! 716: ! 717: ! 718: ! 719: ;;; Support routines. ! 720: ! 721: (defun mh-delete-a-msg () ! 722: "Delete the message pointed to by the cursor." ! 723: (let ((msg (mh-get-msg-num t))) ! 724: (if (looking-at "....\\^") ! 725: (error "Message %d already moved. Undo move before deleting." msg)) ! 726: (push msg mh-delete-list) ! 727: (mh-add-msg-to-seq msg 'deleted t) ! 728: (mh-notate ?D mh-cmd-note))) ! 729: ! 730: ! 731: (defun mh-move-a-msg (destination) ! 732: "Move the message pointed to by the cursor." ! 733: (if (looking-at "....D") ! 734: (error "Message %d is already deleted. Undo delete before moving." ! 735: (mh-get-msg-num nil)) ! 736: (let ((msg (mh-get-msg-num t))) ! 737: (if (not (memq destination mh-move-list)) ! 738: (push destination mh-move-list)) ! 739: (mh-add-msg-to-seq msg destination t) ! 740: (mh-notate ?^ mh-cmd-note)))) ! 741: ! 742: ! 743: (defun mh-display-msg (msg-num msg-filename show-buffer) ! 744: "Displays the message NUMBER and PATHNAME in BUFFER." ! 745: (if (not (file-exists-p msg-filename)) ! 746: (error "Message %d does not exist." msg-num)) ! 747: (switch-to-buffer show-buffer) ! 748: (buffer-flush-undo (current-buffer)) ! 749: (when (not (equal msg-filename buffer-file-name)) ! 750: ;; Buffer does not yet contain message. ! 751: (setq buffer-file-name msg-filename) ! 752: (erase-buffer) ! 753: (unlock-buffer) ! 754: (if mh-use-mhl ! 755: (mh-exec-lib-cmd-output "mhl" "-nobell" "-noclear" msg-filename) ! 756: (insert-file-contents msg-filename)) ! 757: (goto-char (point-min)) ! 758: (cond (mh-clean-message-header ! 759: (mh-clean-msg-header (point-min)) ! 760: (goto-char (point-min))) ! 761: (t ! 762: (let ((case-fold-search t)) ! 763: (re-search-forward "^To:\\|^From:\\|^Subject:\\|^Date:" nil t) ! 764: (beginning-of-line) ! 765: (recenter 0)))) ! 766: (set-buffer-modified-p nil) ! 767: (set-mark nil) ! 768: (setq mode-line-format ! 769: (concat "{%b} %[%p of " folder "/" msg-num "%] %M")))) ! 770: ! 771: ! 772: (defun mh-clean-msg-header (start) ! 773: "Flush extraneous lines in a message header. The variable ! 774: mh-invisible-headers contains a regular expression specifying these lines." ! 775: (save-restriction ! 776: (goto-char start) ! 777: (search-forward "\n\n" nil t) ! 778: (narrow-to-region start (point)) ! 779: (goto-char (point-min)) ! 780: (while (re-search-forward mh-invisible-headers nil t) ! 781: (beginning-of-line) ! 782: (kill-line 1) ! 783: (while (looking-at "^[ \t]+") ! 784: (beginning-of-line) ! 785: (kill-line 1))) ! 786: (unlock-buffer))) ! 787: ! 788: ! 789: (defun mh-read-draft () ! 790: "Read draft file into draft buffer. Avoids errors even if disk file has been ! 791: modified underneath the buffer. Causes an error if the folder is modified and ! 792: the user does not want to change it." ! 793: (switch-to-buffer "draft") ! 794: (if (buffer-modified-p) ! 795: (if (y-or-n-p "Draft is modified; kill anyways? ") ! 796: (set-buffer-modified-p nil) ! 797: (error "Draft is not killed."))) ! 798: (kill-buffer "draft") ! 799: (switch-to-buffer-other-window ! 800: (find-file-noselect (format "%sdraft" mh-user-path)))) ! 801: ! 802: ! 803: (defun mh-next-msg () ! 804: "Move backward or forward to the next message in the buffer." ! 805: (if (eq mh-next-direction 'forward) ! 806: (mh-next-line 1) ! 807: (mh-previous-line 1))) ! 808: ! 809: ! 810: (defun mh-maybe-show () ! 811: "If the scan listing is not summarized, show the message pointed to ! 812: by the cursor." ! 813: (if (not mh-summarize) (mh-show))) ! 814: ! 815: ! 816: ! 817: ;;; The folder data abstraction. ! 818: ! 819: (defun mh-make-folder (name) ! 820: "Create and initialize a new mail folder called NAME and make it the ! 821: current folder." ! 822: (switch-to-buffer name) ! 823: (buffer-flush-undo (current-buffer)) ! 824: (kill-all-local-variables) ! 825: (setq buffer-read-only nil) ! 826: (erase-buffer) ! 827: (make-local-vars ! 828: 'mh-current-folder name ;Name of folder ! 829: 'mh-show-buffer (format "show-%s" name) ; Buffer that displays messages ! 830: 'mh-folder-filename ; e.g. /usr/foobar/Mail/inbox/ ! 831: (format "%s%s/" mh-user-path (substring name 1)) ! 832: 'mh-summarize t ; Show scan list only? ! 833: 'mh-next-seq-num 0 ; Index of free sequence id ! 834: 'mh-delete-list nil ; List of msgs nums to delete ! 835: 'mh-move-list nil ; List of folder names in mh-seq-list ! 836: 'mh-seq-list nil ; Alist of seq . msgs nums ! 837: 'mh-seen-list nil ; List of displayed messages ! 838: 'mh-next-direction 'forward ; Direction to move to next message ! 839: 'mh-narrowed-to-seq nil) ; Sequence display is narrowed to ! 840: (mh-folder-mode) ! 841: (setq buffer-read-only t) ! 842: (setq mode-name "Mh-Summarize")) ! 843: ! 844: ! 845: (defun make-local-vars (&rest pairs) ! 846: "Takes VARIABLE-VALUE pairs and makes local variables initialized to the ! 847: value." ! 848: (while pairs ! 849: (make-local-variable (car pairs)) ! 850: (set (car pairs) (cadr pairs)) ! 851: (setq pairs (cddr pairs)))) ! 852: ! 853: ! 854: (defun mh-folder-mode () ! 855: "Major mode for \"editing\" an MH folder scan listing. ! 856: Messages can be marked for refiling and deletion. However, both actions ! 857: are defered until you request execution with \\[mh-execute-commands]. ! 858: \\{mh-folder-mode-map} ! 859: A prefix argument (\\[universal-argument]) to delete, move, list, or undo applies the action to a message sequence. ! 860: ! 861: Variables controlling mh-e operation are (defaults in parentheses): ! 862: ! 863: mh-clean-message-header (nil) ! 864: Non-nil means remove header lines matching the regular expression ! 865: specified in mh-invisible-headers from messages. ! 866: ! 867: mh-use-mhl (nil) ! 868: Non-nil means use mhl to format displayed messages. ! 869: ! 870: mh-lpr-command-format (\"lpr -p -J '%s'\") ! 871: Format for command used to print a message on a system printer. ! 872: ! 873: mh-summary-height (4) ! 874: Number of lines in the summary window. ! 875: ! 876: mh-ins-buf-prefix (\">> \") ! 877: String to insert before each non-blank line of a message as it is ! 878: inserted in a letter being composed." ! 879: ! 880: (use-local-map mh-folder-mode-map) ! 881: (setq major-mode 'mh-folder-mode) ! 882: (setq mode-name "mh-folder") ! 883: (if (and (boundp 'mh-folder-mode-hook) mh-folder-mode-hook) ! 884: (funcall mh-folder-mode-hook))) ! 885: ! 886: ! 887: (defun mh-scan-folder (folder range) ! 888: "Scan the folder FOLDER over the range RANGE. Return in the folder." ! 889: (cond ((null (get-buffer folder)) ! 890: (mh-make-folder folder)) ! 891: (t ! 892: (if (or mh-delete-list mh-move-list mh-seq-list) ! 893: (if (y-or-n-p "Process commands?") ! 894: (mh-process-commands folder) ! 895: (mh-undo-folder))) ! 896: (switch-to-buffer-other-window folder))) ! 897: (mh-regenerate-headers range) ! 898: (when (= (count-lines (point-min) (point-max)) 0) ! 899: (if (equal range "all") ! 900: (message "Folder %s is empty" folder) ! 901: (message "No messages in %s, range %s" folder range)) ! 902: (sit-for 5)) ! 903: (setq mode-line-format (mh-make-mode-line)) ! 904: (mh-unmark-all-headers nil) ! 905: (mh-goto-cur-msg)) ! 906: ! 907: ! 908: (defun mh-regenerate-headers (range) ! 909: "Replace buffer with scan of its contents over range RANGE." ! 910: (let ((buffer-read-only nil)) ! 911: (message (format "scanning %s..." (buffer-name))) ! 912: (delete-other-windows) ! 913: (erase-buffer) ! 914: (mh-exec-cmd-output "scan" "-noclear" "-noheader" (buffer-name) range) ! 915: (goto-char (point-min)) ! 916: (cond ((looking-at "scan: no messages in") ! 917: (keep-lines "^[ ]*[0-9]")) ; Flush random scan lines ! 918: ((looking-at "scan: ")) ; Keep error messages ! 919: (t ! 920: (keep-lines "^[ ]*[0-9]"))) ; Flush random scan lines ! 921: (message (format "scanning %s...done" (buffer-name))))) ! 922: ! 923: ! 924: (defun mh-get-new-mail () ! 925: "Read new mail into the current buffer. Return t if there was new mail, ! 926: nil otherwise. Leave cursor in current buffer." ! 927: (let ((buffer-read-only nil) ! 928: (point-before-inc (point))) ! 929: (message (format "inc %s..." (buffer-name))) ! 930: (mh-unmark-all-headers nil) ! 931: (setq mh-next-direction 'forward) ! 932: (flush-lines "^inc:\\|^scan:") ; Kill old error messages ! 933: (goto-char (point-max)) ! 934: (let ((start-of-inc (point))) ! 935: (mh-exec-cmd-output "inc") ! 936: (goto-char start-of-inc) ! 937: (message (format "inc %s...done" (buffer-name))) ! 938: (cond ((looking-at "inc: no mail") ! 939: (keep-lines "^[ ]*[0-9]") ; Flush random scan lines ! 940: (setq mode-line-format (mh-make-mode-line)) ! 941: (goto-char point-before-inc) ! 942: (message "No new mail.") ! 943: nil) ! 944: ((looking-at "inc:") ; Error messages ! 945: (setq mode-line-format (mh-make-mode-line)) ! 946: (goto-char point-before-inc) ! 947: (message "inc error") ! 948: nil) ! 949: (t ! 950: (keep-lines "^[ ]*[0-9]") ! 951: (setq mode-line-format (mh-make-mode-line)) ! 952: (mh-goto-cur-msg) ! 953: t))))) ! 954: ! 955: ! 956: (defun mh-make-mode-line (&optional annotation) ! 957: "Returns a string for mode-line-format. The optional ANNOTATION string is ! 958: displayed after the folder's name." ! 959: (save-excursion ! 960: (goto-char (point-min)) ! 961: (let ((lines (count-lines (point-min) (point-max)))) ! 962: (let* ((first (mh-get-msg-num nil)) ! 963: (case-fold-search nil) ! 964: (current (and (re-search-forward "....\\+" nil t) ! 965: (mh-get-msg-num nil)))) ! 966: (goto-char (point-max)) ! 967: (previous-line 1) ! 968: (let ((last (mh-get-msg-num nil))) ! 969: (format "{%%b%s} [%d messages%s%s] (%%p%%%% - %%m) %%M" ! 970: (if annotation (format "/%s" annotation) "") ! 971: lines ! 972: (if (> lines 0) ! 973: (format " (%d - %d)" first last) ! 974: "") ! 975: (if current ! 976: (format " cur = %d" current) ! 977: ""))))))) ! 978: ! 979: ! 980: (defun mh-unmark-all-headers (remove-all-flags) ! 981: "This function removes all + flags from the headers, and if called ! 982: with a non-nil argument, removes all D and ^ flags too." ! 983: (let ((buffer-read-only nil) ! 984: (case-fold-search nil)) ! 985: (goto-char (point-min)) ! 986: (while (if remove-all-flags ! 987: (re-search-forward "^....\\D\\|^....\\^\\|^....\\+\\|.....%" ! 988: nil t) ! 989: (re-search-forward "^....\\+" nil t)) ! 990: (delete-backward-char 1) ! 991: (insert " ")))) ! 992: ! 993: ! 994: (defun mh-goto-cur-msg () ! 995: "Position the cursor at the current message." ! 996: (let ((curmsg (mh-get-cur-msg mh-current-folder))) ! 997: (cond ((or (zerop curmsg) (not (mh-goto-msg curmsg t))) ! 998: (goto-char (point-max)) ! 999: (forward-line -1) ! 1000: (mh-maybe-show) ! 1001: (message "No current message")) ! 1002: (t ! 1003: (mh-notate ?+ 4) ! 1004: (recenter 0))))) ! 1005: ! 1006: ! 1007: (defun mh-pack-folder () ! 1008: "Closes and packs the current folder." ! 1009: (let ((buffer-read-only nil)) ! 1010: (message "closing folder...") ! 1011: (mh-process-commands mh-current-folder) ! 1012: (message "packing folder...") ! 1013: (save-excursion ! 1014: (mh-exec-cmd-quiet " *mh-temp*" "folder" mh-current-folder "-pack")) ! 1015: (mh-regenerate-headers "all") ! 1016: (message "packing done") ! 1017: (setq mode-line-format (mh-make-mode-line)))) ! 1018: ! 1019: ! 1020: (defun mh-process-commands (buffer) ! 1021: "Process outstanding commands for the buffer BUFFER." ! 1022: (message "Processing deletes and moves...") ! 1023: (switch-to-buffer buffer) ! 1024: (let ((buffer-read-only nil)) ! 1025: ;; Sequences must be first ! 1026: (mh-process-seq-commands mh-seq-list) ! 1027: ;; Update the unseen sequence ! 1028: (if mh-seen-list ! 1029: (let ((unseen-seq (mh-get-profile-field "Unseen-Sequence:"))) ! 1030: (if (null unseen-seq) ; For MH.5 ! 1031: (setq unseen-seq "unseen")) ! 1032: (apply 'mh-exec-cmd-quiet ! 1033: (nconc (list 0 "mark" mh-current-folder) ! 1034: mh-seen-list ! 1035: (list "-sequence" unseen-seq "-delete"))))) ! 1036: ! 1037: ;; Then refile messages ! 1038: (mapc (function ! 1039: (lambda (dest) ! 1040: (let ((msgs (mh-seq-to-msgs dest))) ! 1041: (when msgs ! 1042: (mh-delete-scan-msgs msgs) ! 1043: (apply 'mh-exec-cmd ! 1044: (nconc (cons "refile" msgs) ! 1045: (list "-src" buffer (symbol-name dest)))))))) ! 1046: mh-move-list) ! 1047: ! 1048: ;; Now delete messages ! 1049: (when mh-delete-list ! 1050: (apply 'mh-exec-cmd ! 1051: (nconc (list "rmm" (format "%s" buffer)) mh-delete-list)) ! 1052: (mh-delete-scan-msgs mh-delete-list)) ! 1053: ! 1054: ;; Mark as cur message. ! 1055: (cond ((mh-get-msg-num nil) ! 1056: (mh-exec-cmd "mark" mh-current-folder (mh-get-msg-num nil) ! 1057: "-seq" "cur" "-add" "-zero")) ! 1058: ((> (buffer-size) 0) ; Some messages left in folder. ! 1059: (mh-exec-cmd "mark" mh-current-folder ! 1060: "-seq" "cur" "-delete" "all"))) ! 1061: ! 1062: (save-excursion ! 1063: (switch-to-buffer mh-show-buffer) ! 1064: (setq buffer-file-name nil)) ; Invalidate buffer file cache ! 1065: ! 1066: (setq mh-delete-list nil ! 1067: mh-move-list nil ! 1068: mh-seq-list nil ! 1069: mh-seen-list nil)) ! 1070: (message "Processing deletes and moves...done")) ! 1071: ! 1072: ! 1073: (defun mh-delete-scan-msgs (msgs) ! 1074: "Delete the scan listing lines for each of the msgs in the LIST." ! 1075: (save-excursion ! 1076: (goto-char (point-min)) ! 1077: (while msgs ! 1078: (flush-lines (mh-msg-search-pat (car msgs))) ! 1079: (setq msgs (cdr msgs))))) ! 1080: ! 1081: ! 1082: ! 1083: ;;; A mode for composing and sending a message. ! 1084: ! 1085: (defun mh-letter-mode () ! 1086: "Mode for composing letters in mh-e. ! 1087: \\{mh-letter-mode-map}" ! 1088: (text-mode) ! 1089: (make-local-variable 'paragraph-start) ! 1090: (setq paragraph-start (concat "^[ \t]*[-_][-_][-_]+$\\|" paragraph-start)) ! 1091: (make-local-variable 'paragraph-separate) ! 1092: (setq paragraph-separate ! 1093: (concat "^[ \t]*[-_][-_][-_]+$\\|" paragraph-separate)) ! 1094: (use-local-map mh-letter-mode-map) ! 1095: (setq major-mode 'mh-letter-mode) ! 1096: (setq mode-name "mh-letter") ! 1097: (if (and (boundp 'mh-letter-mode-hook) mh-letter-mode-hook) ! 1098: (funcall mh-letter-mode-hook))) ! 1099: ! 1100: ! 1101: (defun mh-to-to () ! 1102: "Move point to end of To: field." ! 1103: (interactive) ! 1104: (expand-abbrev) ! 1105: (mh-position-on-field "To:" t)) ! 1106: ! 1107: ! 1108: (defun mh-to-subject () ! 1109: "Move point to end of Subject: field. Creates the field if necessary" ! 1110: (interactive) ! 1111: (expand-abbrev) ! 1112: (when (not (mh-position-on-field "Subject:" t)) ! 1113: (mh-position-on-field "To:" nil) ! 1114: (insert-string "\nSubject: "))) ! 1115: ! 1116: ! 1117: (defun mh-to-cc () ! 1118: "Move point to end of Cc: field. Creates the field if necessary" ! 1119: (interactive) ! 1120: (expand-abbrev) ! 1121: (when (not (mh-position-on-field "Cc:" t)) ! 1122: (mh-position-on-field "To:" nil) ! 1123: (insert-string "\nCc: "))) ! 1124: ! 1125: ! 1126: (defun mh-to-bcc () ! 1127: "Move point to end of Bcc: field. Creates the field if necessary" ! 1128: (interactive) ! 1129: (expand-abbrev) ! 1130: (when (not (mh-position-on-field "Bcc:" t)) ! 1131: (mh-position-on-field "To:" nil) ! 1132: (insert-string "\nBcc: "))) ! 1133: ! 1134: ! 1135: (defun mh-to-fcc () ! 1136: "Move point to end of Fcc: field. Creates the field if necessary" ! 1137: (interactive) ! 1138: (expand-abbrev) ! 1139: (when (not (mh-position-on-field "Fcc:" t)) ! 1140: (mh-position-on-field "To:" nil) ! 1141: (insert-string "\nFcc: "))) ! 1142: ! 1143: ! 1144: (defun mh-check-whom () ! 1145: "List recipients of the current message." ! 1146: (interactive) ! 1147: (let ((file-name (buffer-file-name))) ! 1148: (set-buffer-modified-p t) ; Force writing of contents ! 1149: (save-buffer) ! 1150: (message "Checking recipients...") ! 1151: (switch-to-buffer-other-window "*Mail Recipients*") ! 1152: (bury-buffer (current-buffer)) ! 1153: (erase-buffer) ! 1154: (mh-exec-cmd-output "whom" file-name) ! 1155: (other-window -1) ! 1156: (message "Checking recipients...done"))) ! 1157: ! 1158: ! 1159: ! 1160: ;;; Routines to make a search pattern and search for a message. ! 1161: ! 1162: (defun mh-make-pick-template () ! 1163: "Initialize a buffer with a template for a pick pattern." ! 1164: (erase-buffer) ! 1165: (kill-all-local-variables) ! 1166: (make-local-variable 'mh-searching-folder) ! 1167: (insert "From: \n" ! 1168: "To: \n" ! 1169: "Cc: \n" ! 1170: "Date: \n" ! 1171: "Subject: \n" ! 1172: "---------\n") ! 1173: (mh-letter-mode) ! 1174: (use-local-map mh-pick-mode-map) ! 1175: (setq mode-line-format "{%b}\tPick Pattern\t(^C^C to do search)") ! 1176: (goto-char (point-min)) ! 1177: (end-of-line)) ! 1178: ! 1179: ! 1180: (defun mh-do-pick-search () ! 1181: "Search the current folder for the messages matching the qualification ! 1182: in the current buffer and make them into a sequence called `search'." ! 1183: (interactive) ! 1184: (let* ((pattern-buffer (buffer-name)) ! 1185: (searching-buffer mh-searching-folder) ! 1186: (range "all") ! 1187: (pattern nil)) ! 1188: (message "Searching...") ! 1189: (goto-char (point-min)) ! 1190: (while (setq pattern (mh-next-pick-field pattern-buffer)) ! 1191: (setq msgs ! 1192: (mh-seq-from-command searching-buffer ! 1193: 'search ! 1194: (nconc (cons "pick" pattern) ! 1195: (list searching-buffer ! 1196: range ! 1197: "-sequence" "search" ! 1198: "-list")))) ! 1199: (setq range "search")) ! 1200: (message "Searching...done") ! 1201: (switch-to-buffer searching-buffer) ! 1202: (mh-notate-seq 'search ?% (+ mh-cmd-note 1)))) ! 1203: ! 1204: ! 1205: (defun mh-next-pick-field (buffer) ! 1206: "Return the next piece of a pick argument that can be extracted from the ! 1207: BUFFER. Returns nil if no pieces remain." ! 1208: (switch-to-buffer buffer) ! 1209: (let ((case-fold-search t)) ! 1210: (cond ((eobp) ! 1211: nil) ! 1212: ((re-search-forward "^\\([a-z].*\\):[ \t]*\\([a-z0-9].*\\)$" nil t) ! 1213: (let* ((component ! 1214: (format "-%s" ! 1215: (downcase (buffer-substring (match-beginning 1) ! 1216: (match-end 1))))) ! 1217: (pat (buffer-substring (match-beginning 2) (match-end 2)))) ! 1218: (forward-line 1) ! 1219: (list component pat))) ! 1220: ((re-search-forward "^-*$" nil t) ! 1221: (forward-char 1) ! 1222: (let ((body (buffer-substring (point) (point-max)))) ! 1223: (if (and (> (length body) 0) (not (equal body "\n"))) ! 1224: (list "-search" body) ! 1225: nil))) ! 1226: (t ! 1227: nil)))) ! 1228: ! 1229: ! 1230: ! 1231: ;;; Routines compose and send a letter. ! 1232: ! 1233: (defun mh-compose-and-send-mail (send-args sent-from-folder to subject cc ! 1234: &optional annotate-char ! 1235: annotate-field search-prefix) ! 1236: "Edit and compose a draft message and send or save it. ! 1237: SENT-FROM-FOLDER is buffer containing summary of current folder, if any. ! 1238: SEND-ARGS is an optional argument passed to the send command. ! 1239: The TO, SUBJECT, and CC fields are passed to the mh-compose-letter-hook. ! 1240: If ANNOTATE-CHAR is non-null, it is used to notate the scan listing of the ! 1241: current message. In that case, the ANNOTATE-FIELD is used to build a string ! 1242: for mh-annotate-msg." ! 1243: (let ((sent-from-msg)) ! 1244: (save-window-excursion ! 1245: (when sent-from-folder ! 1246: (switch-to-buffer sent-from-folder) ! 1247: (setq sent-from-msg (mh-get-msg-num nil)))) ! 1248: (pop-to-buffer "draft") ! 1249: (mh-letter-mode) ! 1250: (make-local-vars ! 1251: 'mh-send-args send-args ! 1252: 'mh-sent-from-folder sent-from-folder ! 1253: 'mh-sent-from-msg sent-from-msg ! 1254: 'mh-annotate-field annotate-field ! 1255: 'mh-annotate-char annotate-char ! 1256: 'mh-annotate-search-prefix (if search-prefix search-prefix "")) ! 1257: (setq mode-line-format "{%b} %[Mail/draft%] (%p - %m) (^C^C to send) %M") ! 1258: (if (and (boundp 'mh-compose-letter-hook) mh-compose-letter-hook) ! 1259: (funcall mh-compose-letter-hook to subject cc)))) ! 1260: ! 1261: ! 1262: (defun mh-send-letter (&optional arg) ! 1263: "Send the letter in the current buffer. If given an ARGUMENT, the delivery ! 1264: process is monitored and displayed." ! 1265: (interactive "P") ! 1266: (save-buffer) ! 1267: (message "Sending...") ! 1268: (if arg ! 1269: (let ((from-buffer (buffer-name)) ! 1270: (file-name (buffer-file-name))) ! 1271: (pop-to-buffer " *mh-temp*") ! 1272: (erase-buffer) ! 1273: (if mh-send-args ! 1274: (mh-exec-cmd-output "send" "-watch" "-unique" mh-send-args ! 1275: file-name) ! 1276: (mh-exec-cmd-output "send" "-watch" "-unique" file-name)) ! 1277: (pop-to-buffer from-buffer)) ! 1278: (if mh-send-args ! 1279: (mh-exec-cmd-quiet 0 "send" "-push" "-unique" mh-send-args ! 1280: (buffer-file-name)) ! 1281: (mh-exec-cmd-quiet 0 "send" "-push" "-unique" (buffer-file-name)))) ! 1282: (if mh-annotate-char ! 1283: (mh-annotate-msg mh-sent-from-msg ! 1284: mh-sent-from-folder ! 1285: mh-annotate-char ! 1286: "-component" mh-annotate-field ! 1287: "-text" (format "\"%s %s\"" ! 1288: (mh-get-field ! 1289: (format "%s%s" ! 1290: mh-annotate-search-prefix ! 1291: "To:")) ! 1292: (mh-get-field ! 1293: (format "%s%s" ! 1294: mh-annotate-search-prefix ! 1295: "Cc:"))))) ! 1296: (message "Sending...done") ! 1297: (kill-buffer (buffer-name))) ! 1298: ! 1299: ! 1300: (defun mh-insert-letter (&optional arg) ! 1301: "Insert a message in the current letter, asking for folder and number. ! 1302: Removes headers using mh-invisible-headers. ! 1303: Prefixes each non-blank line with mh-ins-buf-prefix (default \">> \"). ! 1304: Just \\[universal-argument] means do not indent and do not delete any ! 1305: header fields. Leaves point before the text and mark after it." ! 1306: (interactive "p") ! 1307: (let ((folder (mh-prompt-for-folder "Message from" mh-sent-from-folder nil)) ! 1308: (message (read-input (format "Message number%s: " ! 1309: (if mh-sent-from-msg ! 1310: (format " [%d]" mh-sent-from-msg) ! 1311: "")))) ! 1312: (start (point))) ! 1313: (if (equal message "") (setq message (format "%d" mh-sent-from-msg))) ! 1314: (mh-exec-lib-cmd-output "mhl" "-nobell" "-noclear" ! 1315: (format "%s%s/%s" mh-user-path ! 1316: (substring folder 1) message)) ! 1317: (when (not (equal arg 4)) ! 1318: (mh-clean-msg-header start) ! 1319: (narrow-to-region start (mark)) ! 1320: (mh-insert-prefix-string mh-ins-buf-prefix) ! 1321: (widen)) ! 1322: (exchange-point-and-mark))) ! 1323: ! 1324: ! 1325: (defun mh-insert-cur-msg () ! 1326: "Inserts the currently displayed message into the current draft buffer. ! 1327: Prefixes each non-blank line with the string mh-ins-buf-prefix. ! 1328: If there is a region set in the message's buffer, only the region will ! 1329: be inserted. Otherwise, the region from (point) to the end will be grabbed." ! 1330: (interactive) ! 1331: (let ((to-point (point)) ! 1332: (to-buffer (current-buffer))) ! 1333: (set-buffer mh-sent-from-folder) ! 1334: (set-buffer mh-show-buffer) ; Find displayed message ! 1335: (let ((mh-ins-str (if (mark) ! 1336: (buffer-substring (point) (mark)) ! 1337: (buffer-substring (point) (point-max))))) ! 1338: (set-buffer to-buffer) ! 1339: (narrow-to-region to-point to-point) ! 1340: (insert-string mh-ins-str) ! 1341: (mh-insert-prefix-string mh-ins-buf-prefix) ! 1342: (widen)))) ! 1343: ! 1344: ! 1345: (defun mh-insert-prefix-string (ins-string) ! 1346: "Preface each line in the current buffer with STRING." ! 1347: (goto-char (point-min)) ! 1348: (replace-regexp "^." (concat ins-string "\\&") nil) ! 1349: (goto-char (point-min))) ! 1350: ! 1351: ! 1352: (defun mh-fully-kill-draft () ! 1353: "Kill the draft message file and the draft message buffer. Use kill-buffer ! 1354: if you don't want to delete the draft message file." ! 1355: (interactive "") ! 1356: (if (file-exists-p (buffer-file-name)) ! 1357: (delete-file (buffer-file-name))) ! 1358: (kill-buffer (buffer-name))) ! 1359: ! 1360: ! 1361: ! 1362: ;;; Commands to manipulate sequences. Sequences are stored in an alist ! 1363: ;;; of the form: ! 1364: ;;; ((seq-name msgs ...) (seq-name msgs ...) ...) ! 1365: ! 1366: ! 1367: (defmacro mh-seq-name (pair) ! 1368: (list 'car pair)) ! 1369: ! 1370: (defmacro mh-seq-msgs (pair) ! 1371: (list 'cdr pair)) ! 1372: ! 1373: ! 1374: (defun mh-seq-to-msgs (seq) ! 1375: "Returns the messages in sequence SEQ." ! 1376: (mh-seq-msgs (assoc seq mh-seq-list))) ! 1377: ! 1378: ! 1379: (defun mh-msg-to-seq (msg) ! 1380: "Given a MESSAGE number, return the first sequence in which it occurs." ! 1381: (let ((l mh-seq-list)) ! 1382: (while (and l (not (memq msg (cdar l)))) ! 1383: (setq l (cdr l))) ! 1384: (caar l))) ! 1385: ! 1386: ! 1387: (defun mh-read-seq (prompt &optional seq) ! 1388: "Read and return a sequence name from the minibuffer, prompting with ! 1389: the string PROMPT and supplying the optional DEFAULT. ! 1390: % defaults to the sequences that the current message is in. ! 1391: Makes sure that the sequence is known to MH commands." ! 1392: (let ((input (completing-read ! 1393: (format "%s %s %s" prompt "sequence:" ! 1394: (if seq (format "[%s] " (symbol-name seq)) "")) ! 1395: (mh-seq-names mh-seq-list)))) ! 1396: (let ((seq (cond ((equal input "%") (mh-msg-to-seq (mh-get-msg-num t))) ! 1397: ((equal input "") seq) ! 1398: (t (intern input))))) ! 1399: (mh-process-seq seq (mh-seq-to-msgs seq)) ! 1400: seq))) ! 1401: ! 1402: ! 1403: (defun mh-seq-names (seq-list) ! 1404: "Returns an alist of the names of the SEQUENCES." ! 1405: (mapcar (function (lambda (entry) (cons (symbol-name (car entry)) nil))) ! 1406: seq-list)) ! 1407: ! 1408: ! 1409: (defun mh-seq-from-command (folder seq command) ! 1410: "In FOLDER, make a sequence named SEQ by executing COMMAND." ! 1411: (let ((msgs ()) ! 1412: (case-fold-search t)) ! 1413: (save-excursion ! 1414: (save-window-excursion ! 1415: (apply 'mh-exec-cmd-quiet (cons " *mh-temp*" command)) ! 1416: (goto-char (point-min)) ! 1417: (while (re-search-forward "\\([0-9]+\\)" nil t) ! 1418: (let ((num (string-to-int (buffer-substring (match-beginning 1) ! 1419: (match-end 1))))) ! 1420: (if (not (zerop num)) ! 1421: (push num msgs))))) ! 1422: (switch-to-buffer folder) ! 1423: (push (cons seq msgs) mh-seq-list) ! 1424: msgs))) ! 1425: ! 1426: ! 1427: (defun mh-remove-seq (seq) ! 1428: "Delete the sequence SEQ." ! 1429: (let ((entry (assoc seq mh-seq-list))) ! 1430: (setq mh-seq-list (delq (car entry) mh-seq-list)) ! 1431: (mh-notate-seq (mh-seq-msgs (car entry)) ? (+ mh-cmd-note 1)))) ! 1432: ! 1433: ! 1434: (defun mh-remove-msg-from-seq (msg-num seq &optional do-not-mark) ! 1435: "Remove a message MSG-NUM from the sequence SEQ. If optional FLAG is ! 1436: non-nil, do not mark the message as being part of a sequence." ! 1437: (let ((seq (assoc seq mh-seq-list))) ! 1438: (if seq ! 1439: (setcdr seq (delq msg-num (mh-seq-msgs seq))))) ! 1440: (if (not do-not-mark) (mh-notate ? (+ mh-cmd-note 1)))) ! 1441: ! 1442: ! 1443: (defun mh-add-msg-to-seq (msg-num seq &optional do-not-mark) ! 1444: "Add the message MSG-NUM to the SEQUENCE. If optional FLAG is non-nil, ! 1445: do not mark the message as being part of a sequence." ! 1446: (let ((seq-list (assoc seq mh-seq-list))) ! 1447: (if (not do-not-mark) (mh-notate ?% (+ mh-cmd-note 1))) ! 1448: (if (null seq-list) ! 1449: (push (cons seq (list msg-num)) mh-seq-list) ! 1450: (setcdr seq-list (cons msg-num (cdr seq-list)))))) ! 1451: ! 1452: ! 1453: (defun mh-add-msg-list-to-seq (msgs seq &optional do-not-mark) ! 1454: "Add the messages in LIST to the SEQUENCE. If optional FLAG is non-nil, ! 1455: do not mark the messages as being part of a sequence." ! 1456: (mapc (function (lambda (msg) (mh-add-msg-to-seq msg seq do-not-mark))) ! 1457: msgs)) ! 1458: ! 1459: ! 1460: (defun mh-rename-seq (seq new-name) ! 1461: "Rename a SEQUENCE to have a new NAME." ! 1462: (interactive "SOld sequence name: \nSNew name: ") ! 1463: (let ((old-seq (assoc seq mh-seq-list))) ! 1464: (if old-seq ! 1465: (rplaca old-seq new-name) ! 1466: (error "Sequence %s does not exists" (symbol-name seq))))) ! 1467: ! 1468: ! 1469: (defun mh-notate-seq (seq notation offset) ! 1470: "Mark all messages in the sequence SEQ with the NOTATION at character ! 1471: OFFSET." ! 1472: (mh-map-to-seq-msgs 'mh-notate seq notation offset)) ! 1473: ! 1474: ! 1475: (defun mh-map-to-seq-msgs (func seq &rest args) ! 1476: "Invoke the function FUNC at each message in the sequence SEQ, passing ! 1477: the remaining ARGS as arguments." ! 1478: (let ((msgs (mh-seq-to-msgs seq))) ! 1479: (while msgs ! 1480: (mh-goto-msg (car msgs)) ! 1481: (apply func args) ! 1482: (setq msgs (cdr msgs))))) ! 1483: ! 1484: ! 1485: (defun mh-map-over-seqs (func seq-list) ! 1486: "Apply the function FUNC to each element in the sequence LIST, ! 1487: passing the sequence name and a list of messages as arguments." ! 1488: (while seq-list ! 1489: (funcall func (caar seq-list) (cdar seq-list)) ! 1490: (setq seq-list (cdr seq-list)))) ! 1491: ! 1492: ! 1493: (defun mh-process-seq-commands (seq-list) ! 1494: "Process outstanding sequence commands for the sequences in SEQ-LIST." ! 1495: (mh-map-over-seqs 'mh-process-seq seq-list)) ! 1496: ! 1497: ! 1498: (defun mh-process-seq (seq msgs) ! 1499: "Mark sequence SEQ to contain MSGS." ! 1500: ;; Do not mark pseudo-sequences. ! 1501: (if (not (equal (substring (symbol-name seq) 0 1) "+")) ! 1502: (apply 'mh-exec-cmd ! 1503: (nconc (list "mark" "-zero" "-seq" (format "%s" seq) "-add") ! 1504: msgs)))) ! 1505: ! 1506: ! 1507: (defun mh-copy-seq-to-point (seq location) ! 1508: "Copy the messages in SEQUENCE to after the LOCATION in the current buffer." ! 1509: (mh-map-to-seq-msgs 'mh-copy-line-to-point seq location)) ! 1510: ! 1511: ! 1512: (defun mh-copy-line-to-point (location) ! 1513: "Copy the current line to the LOCATION in the current buffer." ! 1514: (beginning-of-line) ! 1515: (let ((beginning-of-line (point))) ! 1516: (forward-line 1) ! 1517: (copy-region-as-kill beginning-of-line (point)) ! 1518: (goto-char location) ! 1519: (yank) ! 1520: (goto-char beginning-of-line))) ! 1521: ! 1522: ! 1523: ! 1524: ;;; Issue commands to mh. ! 1525: ! 1526: (defun mh-exec-cmd (command &rest args) ! 1527: "Execute MH command COMMAND with ARGS. Any output is shown to the user." ! 1528: (save-excursion ! 1529: (switch-to-buffer-other-window " *mh-temp*") ! 1530: (erase-buffer) ! 1531: (apply 'call-process ! 1532: (nconc (list (format "%s%s" mh-progs command) nil t nil) ! 1533: (mh-list-to-string args))) ! 1534: (if (> (buffer-size) 0) ! 1535: (sit-for 5)))) ! 1536: ! 1537: ! 1538: (defun mh-exec-cmd-quiet (buffer command &rest args) ! 1539: "In BUFFER, execute MH command COMMAND with ARGS. Return in buffer, if ! 1540: one exists." ! 1541: (when (stringp buffer) ! 1542: (switch-to-buffer buffer) ! 1543: (erase-buffer)) ! 1544: (apply 'call-process ! 1545: (nconc (list (format "%s%s" mh-progs command) nil buffer nil) ! 1546: (mh-list-to-string args)))) ! 1547: ! 1548: ! 1549: (defun mh-exec-cmd-output (command &rest args) ! 1550: "Execute MH command COMMAND with ARGS putting the output into buffer after ! 1551: point. Set mark after inserted text." ! 1552: (set-mark (point)) ! 1553: (apply 'call-process ! 1554: (nconc (list (format "%s%s" mh-progs command) nil t nil) ! 1555: (mh-list-to-string args))) ! 1556: (exchange-point-and-mark)) ! 1557: ! 1558: ! 1559: ! 1560: (defun mh-exec-lib-cmd-output (command &rest args) ! 1561: "Execute MH library command COMMAND with ARGS. Put the output into ! 1562: buffer after point. Set mark after inserted text." ! 1563: (set-mark (point)) ! 1564: (apply 'call-process ! 1565: (nconc (list (format "%s%s" mh-lib command) nil t nil) ! 1566: (mh-list-to-string args))) ! 1567: (exchange-point-and-mark)) ! 1568: ! 1569: ! 1570: (defun mh-list-to-string (l) ! 1571: "Flattens the list L and makes every element a string." ! 1572: (let ((new-list nil)) ! 1573: (while l ! 1574: (cond ((symbolp (car l)) (push (format "%s" (car l)) new-list)) ! 1575: ((numberp (car l)) (push (format "%d" (car l)) new-list)) ! 1576: ((equal (car l) "")) ! 1577: ((stringp (car l)) (push (car l) new-list)) ! 1578: ((null (car l))) ! 1579: ((listp (car l)) (setq new-list ! 1580: (nconc (mh-list-to-string (car l)) ! 1581: new-list))) ! 1582: (t (error "Bad argument %s" (car l)))) ! 1583: (setq l (cdr l))) ! 1584: (nreverse new-list))) ! 1585: ! 1586: ! 1587: ! 1588: ;;; Commands to annotate a message. ! 1589: ! 1590: (defun mh-annotate-msg (msg buffer note &rest args) ! 1591: "Mark the MESSAGE in BUFFER listing with the character NOTE and annotate ! 1592: the saved message with ARGS." ! 1593: ;; Wait for annotation to finish, to avoid race condition with reading msg. ! 1594: (apply 'mh-exec-cmd (cons "anno" (cons buffer (cons msg args)))) ! 1595: (save-excursion ! 1596: (switch-to-buffer buffer) ! 1597: (if (mh-goto-msg msg t) ! 1598: (mh-notate note (+ mh-cmd-note 1))))) ! 1599: ! 1600: ! 1601: (defun mh-notate (notation offset) ! 1602: "Marks the current message with the character NOTATION at position OFFSET." ! 1603: (let ((buffer-read-only nil)) ! 1604: (beginning-of-line) ! 1605: (goto-char (+ (point) offset)) ! 1606: (delete-char 1) ! 1607: (insert notation) ! 1608: (beginning-of-line))) ! 1609: ! 1610: ! 1611: ! 1612: ;;; User prompting commands. ! 1613: ! 1614: (defun mh-prompt-for-folder (prompt default can-create) ! 1615: "Prompt for a folder name with PROMPT. Returns the folder's name. ! 1616: DEFAULT is used if the folder exists and the user types CR. ! 1617: If the CAN-CREATE flag is t,then a non-existant folder is made." ! 1618: (let* ((prompt (format "%s folder%s" prompt ! 1619: (if (equal "" default) ! 1620: "? " ! 1621: (format " [%s]? " default)))) ! 1622: name) ! 1623: (while (and (setq name (completing-read prompt mh-folder-list ! 1624: nil (not can-create) "+")) ! 1625: (equal name "") ! 1626: (equal default ""))) ! 1627: (cond ((or (equal name "") (equal name "+")) ! 1628: (setq name default)) ! 1629: ((not (equal (substring name 0 1) "+")) ! 1630: (setq name (format "+%s" name)))) ! 1631: (let ((new-file-p ! 1632: (not ! 1633: (file-exists-p (format "%s%s" mh-user-path (substring name 1)))))) ! 1634: (cond ((and new-file-p ! 1635: (y-or-n-p ! 1636: (format "Folder %s does not exist. Create it? " name))) ! 1637: (message "Creating %s" name) ! 1638: (call-process "mkdir" nil nil nil ! 1639: (format "%s%s" mh-user-path (substring name 1))) ! 1640: (message "Creating %s...done" name) ! 1641: (push (list name) mh-folder-list) ! 1642: (push (list (substring name 1 nil)) mh-folder-list)) ! 1643: (new-file-p ! 1644: (error "")) ! 1645: (t ! 1646: (when (null (assoc name mh-folder-list)) ! 1647: (push (list name) mh-folder-list) ! 1648: (push (list (substring name 1 nil)) mh-folder-list))))) ! 1649: name)) ! 1650: ! 1651: ! 1652: (defun mh-make-folder-list () ! 1653: "Returns a list of the user's folders in a form suitable for completing ! 1654: read." ! 1655: (interactive) ! 1656: (save-window-excursion ! 1657: (mh-exec-cmd-quiet " *mh-temp*" "folders" "-fast" "-norecurse") ! 1658: (goto-char (point-min)) ! 1659: (let ((list nil)) ! 1660: (while (not (eobp)) ! 1661: (let ((start (point))) ! 1662: (search-forward "\n" nil t) ! 1663: (let ((folder (buffer-substring start (- (point) 1)))) ! 1664: (push (list (format "+%s" folder)) list)))) ! 1665: list))) ! 1666: ! 1667: ! 1668: (defun mh-remove-folder-from-folder-list (folder) ! 1669: "Remove FOLDER from the list of folders." ! 1670: (setq mh-folder-list ! 1671: (delq (assoc (substring folder 1 nil) mh-folder-list) ! 1672: mh-folder-list))) ! 1673: ! 1674: ! 1675: ! 1676: ;;; Misc. functions. ! 1677: ! 1678: (defun mh-get-msg-num (error-if-no-message) ! 1679: "Returns the message number of the current message. If the argument ! 1680: ERROR-IF-NO-MESSAGE is non-nil, then complain if the cursor is not ! 1681: pointing to a message." ! 1682: (save-excursion ! 1683: (beginning-of-line) ! 1684: (cond ((looking-at "^[ ]*\\([0-9]+\\)") ! 1685: (string-to-int (buffer-substring (match-beginning 1) ! 1686: (match-end 1)))) ! 1687: (error-if-no-message ! 1688: (error "Cursor not pointing to message")) ! 1689: (t nil)))) ! 1690: ! 1691: ! 1692: (defun mh-msg-search-pat (n) ! 1693: "Returns a search pattern for message N in the scan listing." ! 1694: (cond ((< n 10) (format "^[^0-9][^0-9][^0-9]%d" n)) ! 1695: ((< n 100) (format "^[^0-9][^0-9]%d" n)) ! 1696: ((< n 1000) (format "^[^0-9]%d" n)) ! 1697: (t (format "^%d" n)))) ! 1698: ! 1699: ! 1700: (defun mh-msg-filename () ! 1701: "Returns a string containing the pathname for the file containing the ! 1702: current message." ! 1703: (format "%s%d" mh-folder-filename (mh-get-msg-num t))) ! 1704: ! 1705: ! 1706: (defun mh-msg-filenames (msgs folder) ! 1707: "Returns a string of filenames for MSGS in FOLDER." ! 1708: (mapconcat (function (lambda (msg) (concat folder msg))) msgs " ")) ! 1709: ! 1710: ! 1711: (defun mh-find-path () ! 1712: "Set mh-user-path to the user's Mail directory from ~/.mh_profile." ! 1713: (if (equal (setq mh-user-path (mh-get-profile-field "Path:")) "") ! 1714: (setq mh-user-path "Mail/") ! 1715: (setq mh-user-path (format "%s/" mh-user-path))) ! 1716: (if (not (equal (substring mh-user-path 0 1) "/")) ! 1717: (setq mh-user-path (format "%s/%s" (getenv "HOME") mh-user-path)))) ! 1718: ! 1719: ! 1720: (defun mh-get-profile-field (field) ! 1721: "Return FIELD from the user's .mh_profile file." ! 1722: (save-window-excursion ! 1723: (if (not (file-exists-p "~/.mh_profile")) ! 1724: (error "Cannot find .mh_profile file.")) ! 1725: (switch-to-buffer " *mh_temp*") ! 1726: (erase-buffer) ! 1727: (insert-file-contents "~/.mh_profile") ! 1728: (mh-get-field field))) ! 1729: ! 1730: ! 1731: (defun mh-get-cur-msg (folder) ! 1732: "Returns the number of the 'cur' message in FOLDER." ! 1733: (save-excursion ! 1734: (switch-to-buffer " *mh_temp*") ! 1735: (erase-buffer) ! 1736: (mh-exec-cmd-output "pick" folder "cur") ! 1737: (string-to-int (buffer-substring (point-min) (point-max))))) ! 1738: ! 1739: ! 1740: (defun mh-get-field (field) ! 1741: "Find and return the value of field FIELD in the current buffer. ! 1742: Returns the empty string if the field is not in the message." ! 1743: (let ((case-fold-search t)) ! 1744: (goto-char (point-min)) ! 1745: (cond ((not (search-forward field nil t)) "") ! 1746: ((looking-at "[\t ]*$") "") ! 1747: (t ! 1748: (re-search-forward "[\t ]*\\([^\t \n].*\\)$" nil t) ! 1749: (let ((field (buffer-substring (match-beginning 1) ! 1750: (match-end 1))) ! 1751: (end-of-match (point))) ! 1752: (forward-line) ! 1753: (while (looking-at "[ \t]") (forward-line 1)) ! 1754: (backward-char 1) ! 1755: (format "%s%s" field (buffer-substring end-of-match (point)))))))) ! 1756: ! 1757: ! 1758: (defun mh-insert-fields (&rest name-values) ! 1759: "Insert the NAME-VALUE pairs in the current buffer." ! 1760: (let ((case-fold-search t)) ! 1761: (while name-values ! 1762: (let ((field-name (car name-values)) ! 1763: (value (cadr name-values))) ! 1764: (goto-char (point-min)) ! 1765: (cond ((not (re-search-forward (format "^%s" field-name) nil t)) ! 1766: (re-search-forward "^---\\|^$") ! 1767: (beginning-of-line) ! 1768: (insert field-name " " value "\n")) ! 1769: (t ! 1770: (end-of-line) ! 1771: (insert " " value))) ! 1772: (setq name-values (cddr name-values)))))) ! 1773: ! 1774: ! 1775: (defun mh-position-on-field (field set-mark) ! 1776: "Set point to the end of the line beginning with FIELD. Sets the mark ! 1777: to the point, if SET-MARK is non-nil." ! 1778: (if set-mark (set-mark (point))) ! 1779: (goto-char (point-min)) ! 1780: (if (re-search-forward (format "^%s" field) nil t) ! 1781: (progn (end-of-line) t) ! 1782: nil)) ! 1783: ! 1784: ! 1785: ! 1786: ;;; Build the folder-mode keymap: ! 1787: ! 1788: (define-key mh-folder-mode-map "?" 'mh-msg-is-in-seq) ! 1789: (define-key mh-folder-mode-map "%" 'mh-put-msg-in-seq) ! 1790: (define-key mh-folder-mode-map "\e%" 'mh-delete-msg-from-seq) ! 1791: (define-key mh-folder-mode-map "\^xn" 'mh-narrow-to-seq) ! 1792: (define-key mh-folder-mode-map "\^xw" 'mh-widen) ! 1793: ! 1794: (define-key mh-folder-mode-map "\^c" 'my-apply-command-to-seq) ! 1795: (define-key mh-folder-mode-map "\eu" 'mh-undo-folder) ! 1796: (define-key mh-folder-mode-map "\e " 'mh-page-digest) ! 1797: (define-key mh-folder-mode-map "\ee" 'mh-extract-rejected-mail) ! 1798: (define-key mh-folder-mode-map "\ef" 'mh-visit-folder) ! 1799: (define-key mh-folder-mode-map "\ek" 'mh-kill-folder) ! 1800: (define-key mh-folder-mode-map "\el" 'mh-list-folders) ! 1801: (define-key mh-folder-mode-map "\ep" 'mh-renumber-folder) ! 1802: (define-key mh-folder-mode-map "\es" 'mh-search-folder) ! 1803: (define-key mh-folder-mode-map "\er" 'mh-rescan-folder) ! 1804: (define-key mh-folder-mode-map "l" 'mh-print-msg) ! 1805: (define-key mh-folder-mode-map "t" 'mh-toggle-summarize) ! 1806: (define-key mh-folder-mode-map "c" 'mh-copy-msg) ! 1807: (define-key mh-folder-mode-map ">" 'mh-write-msg-to-file) ! 1808: (define-key mh-folder-mode-map "i" 'mh-inc-folder) ! 1809: (define-key mh-folder-mode-map "x" 'mh-execute-commands) ! 1810: (define-key mh-folder-mode-map "e" 'mh-execute-commands) ! 1811: (define-key mh-folder-mode-map "r" 'mh-redistribute) ! 1812: (define-key mh-folder-mode-map "f" 'mh-forward) ! 1813: (define-key mh-folder-mode-map "s" 'mh-send) ! 1814: (define-key mh-folder-mode-map "a" 'mh-answer) ! 1815: (define-key mh-folder-mode-map "g" 'mh-goto-msg) ! 1816: (define-key mh-folder-mode-map "\177" 'mh-previous-page) ! 1817: (define-key mh-folder-mode-map " " 'mh-page-msg) ! 1818: (define-key mh-folder-mode-map "." 'mh-show) ! 1819: (define-key mh-folder-mode-map "u" 'mh-undo) ! 1820: (define-key mh-folder-mode-map "!" 'mh-move-or-write-again) ! 1821: (define-key mh-folder-mode-map "^" 'mh-move-msg) ! 1822: (define-key mh-folder-mode-map "d" 'mh-delete-msg) ! 1823: (define-key mh-folder-mode-map "p" 'mh-previous-line) ! 1824: (define-key mh-folder-mode-map "n" 'mh-next-line) ! 1825: ! 1826: ! 1827: ;;; Build the letter-mode keymap: ! 1828: ! 1829: (define-key mh-letter-mode-map "\^Cb" 'mh-to-bcc) ! 1830: (define-key mh-letter-mode-map "\^Cw" 'mh-check-whom) ! 1831: (define-key mh-letter-mode-map "\^Cc" 'mh-to-cc) ! 1832: (define-key mh-letter-mode-map "\^Cf" 'mh-to-fcc) ! 1833: (define-key mh-letter-mode-map "\^Cs" 'mh-to-subject) ! 1834: (define-key mh-letter-mode-map "\^Ct" 'mh-to-to) ! 1835: (define-key mh-letter-mode-map "\^Cq" 'mh-fully-kill-draft) ! 1836: (define-key mh-letter-mode-map "\^Cy" 'mh-insert-cur-msg) ! 1837: (define-key mh-letter-mode-map "\^C\^Y" 'mh-insert-letter) ! 1838: (define-key mh-letter-mode-map "\^C\^C" 'mh-send-letter) ! 1839: ! 1840: ;;; Build the pick-mode keymap: ! 1841: ! 1842: (define-key mh-pick-mode-map "\^C\^C" 'mh-do-pick-search) ! 1843: (define-key mh-pick-mode-map "\^Cb" 'mh-to-bcc) ! 1844: (define-key mh-pick-mode-map "\^Cc" 'mh-to-cc) ! 1845: (define-key mh-pick-mode-map "\^Cf" 'mh-to-fcc) ! 1846: (define-key mh-pick-mode-map "\^Cs" 'mh-to-subject) ! 1847: (define-key mh-pick-mode-map "\^Ct" 'mh-to-to) ! 1848: (define-key mh-pick-mode-map "\^Cw" 'mh-check-whom)
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.