|
|
1.1 ! root 1: ;;; mh-e.el (Version: 2.7) ! 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: ! 38: (defvar mh-progs "/usr/local/mh/" "Directory containing MH commands") ! 39: (defvar mh-lib "/usr/local/lib/mh/" "Directory of MH library") ! 40: ! 41: ! 42: ;;; Mode hooks: ! 43: ! 44: (defvar mh-folder-mode-hook nil "Invoked in mh-folder-mode") ! 45: (defvar mh-letter-mode-hook nil "Invoked in mh-letter-mode") ! 46: ! 47: ! 48: ;;; Personal preferences: ! 49: ! 50: (defvar mh-auto-fill-letters t "Invoke auto-fill-mode in letters") ! 51: (defvar mh-clean-message-header nil ! 52: "Remove invisible header lines in messages") ! 53: (defvar mh-lpr-command-format "lpr -p -J '%s'" ! 54: "Format for Unix command line to print a message. The format should be ! 55: a unix command line, with the string "%s" where the folder and message ! 56: number should appear.") ! 57: (defvar mh-summary-height 4 "Number of lines in summary window") ! 58: ! 59: ;;; Real constants: ! 60: ! 61: (defvar mh-cmd-note 4 "Offset to insert notation") ! 62: (defvar mh-invisible-headers ! 63: "^Received: \\|^Message-Id: \\|^Remailed-\\|^Via: \\|^Mail-from: \\|\^Return- ! 64: Path: \\|^In-Reply-To: \\|^Resent-" ! 65: "Regexp specifying headers that are not to be shown.") ! 66: ! 67: ! 68: ;;; Global variables: ! 69: ! 70: (defvar mh-user-path "" "User's mail folder") ! 71: (defvar mh-last-folder "inbox" "Last folder read by mh-rmail") ! 72: (defvar mh-last-destination nil "Destination of last "move" command") ! 73: (defvar mh-current-folder nil "Currently active folder") ! 74: (defvar mh-folder-buffer nil "Buffer name of currently active folder") ! 75: (defvar mh-show-buffer nil "Name of buffer that displays messages") ! 76: (defvar mh-letter-mode-map nil "Command map for composing mail") ! 77: ! 78: ;;; Macros: ! 79: ! 80: (defmacro push (v l) ! 81: (list 'setq l (list 'cons v l))) ! 82: ! 83: (defmacro caar (l) ! 84: (list 'car (list 'car l))) ! 85: ! 86: (defmacro cadr (l) ! 87: (list 'car (list 'cdr l))) ! 88: ! 89: (defmacro cdar (l) ! 90: (list 'cdr (list 'car l))) ! 91: ! 92: (defmacro cddr (l) ! 93: (list 'cdr (list 'cdr l))) ! 94: ! 95: (defmacro when (pred &rest body) ! 96: (list 'cond (cons pred body))) ! 97: ! 98: ! 99: ! 100: ;;; Entry points: ! 101: ! 102: (defun mh-rmail (&optional arg) ! 103: "Inc(orporate) new mail if optional ARG omitted, or scan a MH mail box ! 104: if arg is present. This front end uses the MH mail system, which uses ! 105: different conventions from the usual mail system." ! 106: (interactive "P") ! 107: (let ((make-backup-files nil) ! 108: (pop-up-windows t) ! 109: mh-current-folder ! 110: mh-folder-buffer) ! 111: ! 112: (mh-find-path) ! 113: (save-window-excursion ! 114: (cond (arg ! 115: (let ((folder (mh-get-folder-name "mh" mh-last-folder t)) ! 116: (range (read-string "range [all]? "))) ! 117: (mh-scan-folder folder (if (string= range "") "all" range)))) ! 118: (t ! 119: (mh-make-folder "inbox") ! 120: (mh-inc-folder))) ! 121: ! 122: (let ((mh-show-buffer (concat "show-" mh-current-folder))) ! 123: (pop-to-buffer mh-show-buffer) ! 124: (unwind-protect ! 125: (mh-command-loop) ! 126: (kill-buffer mh-folder-buffer) ! 127: (kill-buffer mh-show-buffer) ! 128: (setq mh-last-folder mh-current-folder)))))) ! 129: ! 130: ! 131: (defun mh-smail () ! 132: "Send mail using the MH mail system." ! 133: (interactive) ! 134: (let ((make-backup-files nil) ! 135: (pop-up-windows t)) ! 136: (mh-find-path) ! 137: (call-interactively 'mh-send))) ! 138: ! 139: ! 140: ! 141: ;;; User executable mh-e commands: ! 142: ! 143: (defun mh-answer () ! 144: "Answer a letter." ! 145: (interactive) ! 146: (save-window-excursion ! 147: (let ((msg-filename (mh-msg-filename)) ! 148: (msg (mh-get-msg-num t)) ! 149: (reply-to ! 150: (mh-get-response ! 151: "Reply to (f, t, c, ?): " ! 152: '(?f ?t ?c) ! 153: "Reply to F(rom), T(o + From), C(c + To + From): "))) ! 154: (message "Composing a reply...") ! 155: (cond ((equal reply-to ?f) ! 156: (mh-exec-cmd "repl" "-build" mh-folder-buffer msg "-nocc" "all")) ! 157: ((equal reply-to ?t) ! 158: (mh-exec-cmd "repl" "-build" mh-folder-buffer msg "-cc" "to" ! 159: "-nocc" "me")) ! 160: ((equal reply-to ?c) ! 161: (mh-exec-cmd "repl" "-build" mh-folder-buffer msg "-cc" "all" ! 162: "-nocc" "me"))) ! 163: ! 164: (mh-read-file (concat mh-user-path "draft") "draft") ! 165: (delete-other-windows) ! 166: (when (or (zerop (buffer-size)) ! 167: (not (y-or-n-p "The file 'draft' exists. Use for reply? "))) ! 168: (erase-buffer) ! 169: (insert-file-contents (concat mh-user-path "reply")) ! 170: (delete-file (concat mh-user-path "reply"))) ! 171: ! 172: (let ((to-names (mh-get-field "To:")) ! 173: (cc-names (mh-get-field "Cc:"))) ! 174: (goto-char (dot-max)) ! 175: (pop-to-buffer "*message*") ! 176: (erase-buffer) ! 177: (if (file-exists-p msg-filename) ! 178: (insert-file-contents msg-filename) ! 179: (error "File %s does not exist" msg-filename)) ! 180: (goto-char (dot-min)) ! 181: (let ((case-fold-search nil)) ! 182: (re-search-forward "^$\\|^-*$")) ! 183: (recenter 0) ! 184: (message "Composing a reply...done") ! 185: (if (mh-compose-and-send-mail "") ! 186: (mh-annotate "R" mh-folder-buffer msg ! 187: "-component" "Replied-To:" ! 188: "-text" (concat to-names ! 189: (if (string= cc-names "") ! 190: "" ! 191: (concat ", " cc-names))))))))) ! 192: ! 193: ! 194: (defun mh-close-folder () ! 195: "Process the outstanding delete and move commands in the current folder." ! 196: (interactive) ! 197: (message "closing folder...") ! 198: (mh-process-commands mh-folder-buffer) ! 199: (mh-unmark-all-headers t) ! 200: (mh-regenerate-headers "all") ! 201: (setq mode-line-format (mh-make-mode-line)) ! 202: (message "closing folder...done")) ! 203: ! 204: ! 205: (defun mh-copy-msg (&optional arg) ! 206: "Copy specified message(s) to another folder without deleting them." ! 207: (interactive "P") ! 208: (let ((msgs (if arg ! 209: (mh-seq-to-msgs (mh-read-seq "Copy")) ! 210: (mh-get-msg-num t)))) ! 211: (mh-exec-cmd-no-wait "refile" msgs "-link" "-src" ! 212: mh-folder-buffer ! 213: (format "+%s" (mh-get-folder-name "Copy to" "" t))))) ! 214: ! 215: ! 216: (defun mh-delete-msg (&optional arg) ! 217: "Marks the specified message(s) for later deletion." ! 218: (interactive "P") ! 219: (let ((msgs (if arg (mh-read-seq "Delete") (mh-get-msg-num t)))) ! 220: (push msgs mh-delete-list) ! 221: (if arg ! 222: (mh-notate-seq msgs ?D mh-cmd-note) ! 223: (mh-notate ?D mh-cmd-note)) ! 224: (mh-next-line 1))) ! 225: ! 226: ! 227: (defun mh-exit () ! 228: "Exit mh-e and process outstanding delete and move commands." ! 229: (interactive) ! 230: (cond ((not (or mh-delete-list mh-move-list)) ! 231: (throw 'exit nil)) ! 232: ((yes-or-no-p "Exit? ") ! 233: (mh-process-commands mh-folder-buffer) ! 234: (throw 'exit nil)))) ! 235: ! 236: ! 237: (defun mh-forward (to subject cc) ! 238: "Forward a letter." ! 239: (interactive "sTo: \nsSubject: \nsCc: ") ! 240: (save-window-excursion ! 241: (let ((msg-filename (mh-msg-filename)) ! 242: (msg (mh-get-msg-num t))) ! 243: (cond ((or (not (file-exists-p (concat mh-user-path "draft"))) ! 244: (y-or-n-p "The file 'draft' exists. Discard it? ")) ! 245: (mh-exec-cmd "forw" "-build" mh-folder-buffer msg) ! 246: (mh-read-file (concat mh-user-path "draft") "draft") ! 247: (mh-insert-fields "To:" to "Subject:" subject "Cc:" cc)) ! 248: (t ! 249: (mh-read-file (concat mh-user-path "draft") "draft"))) ! 250: ! 251: (goto-char (dot-min)) ! 252: (delete-other-windows) ! 253: (if (mh-compose-and-send-mail "") ! 254: (mh-annotate "F" mh-folder-buffer msg ! 255: "-component" "Forwared-To:" ! 256: "-text" (concat to ! 257: (if (string= cc "") ! 258: "" ! 259: (concat ", " cc)))))))) ! 260: ! 261: ! 262: (defun mh-goto (number &optional no-error-if-no-message) ! 263: "Position the cursor at a particular message." ! 264: (interactive "nMessage number? ") ! 265: (pop-to-buffer mh-folder-buffer) ! 266: (let ((starting-place (dot))) ! 267: (goto-char (dot-min)) ! 268: (cond ((not (re-search-forward (concat "^\+?[0-9a-z]*[ ]*" number) nil t)) ! 269: (goto-char starting-place) ! 270: (if (not no-error-if-no-message) (error "No message %d " number))) ! 271: (t ! 272: (beginning-of-line) ! 273: (if (not mh-summarize) (mh-show)))))) ! 274: ! 275: ! 276: (defun mh-inc-folder () ! 277: "inc(orporate) new mail in the current folder." ! 278: (interactive) ! 279: (mh-get-new-mail)) ! 280: ! 281: ! 282: (defun mh-indicate-seq (&optional arg) ! 283: "Add the specified message(s) to a sequence." ! 284: (interactive "P") ! 285: (let ((seq (mh-letter-to-seq last-input-char))) ! 286: (if (looking-at "^[0-9a-j]") ! 287: (if arg ! 288: (mh-remove-seq seq) ! 289: (mh-remove-msg-from-seq (mh-get-msg-num t) seq)) ! 290: (mh-add-msg-to-seq (mh-get-msg-num t) seq)))) ! 291: ! 292: ! 293: (defun mh-kill-folder () ! 294: "Removes the current folder." ! 295: (interactive) ! 296: (cond ((yes-or-no-p "Remove current folder ") ! 297: (pop-to-buffer " *mh-temp*") ! 298: (mh-exec-cmd "rmf" (buffer-name)) ! 299: (message "Folder removed") ! 300: (throw 'exit nil)) ! 301: (t ! 302: (message "Folder not removed")))) ! 303: ! 304: ! 305: (defun mh-list-folders () ! 306: "List mail folders." ! 307: (interactive) ! 308: (message "listing folders...") ! 309: (pop-to-buffer " *mh-temp*") ! 310: (erase-buffer) ! 311: (mh-exec-cmd-output "folders") ! 312: (goto-char (dot-min)) ! 313: (message "listing folders...done")) ! 314: ! 315: ! 316: (defun mh-print-msg (&optional arg) ! 317: "Print specified message(s) on a line printer." ! 318: (interactive "P") ! 319: (let ((msgs (if arg ! 320: (reverse (mh-seq-to-msgs (mh-read-seq "Print"))) ! 321: (list (mh-get-msg-num t))))) ! 322: (message "printing message...") ! 323: (shell-command ! 324: (concat mh-lib "mhl -noclear -nobell " ! 325: (mh-msg-filenames msgs mh-folder-filename) " | " ! 326: (format mh-lpr-command-format ! 327: (if arg ! 328: "Mail" ! 329: (concat mh-current-folder "/" (mh-get-msg-num t)))))) ! 330: (message "printing message...done"))) ! 331: ! 332: ! 333: (defun mh-move-msg (&optional arg) ! 334: "Move specified message(s) to another folder." ! 335: (interactive "P") ! 336: (let ((msgs (if arg (mh-read-seq "Move") (mh-get-msg-num t)))) ! 337: (setq mh-last-destination (mh-get-folder-name "Destination" "" t)) ! 338: (mh-refile msgs mh-last-destination) ! 339: (mh-next-line 1))) ! 340: ! 341: ! 342: (defun mh-next-line (&optional arg) ! 343: "Move to next undeleted message in window and display body if summary ! 344: flag set." ! 345: (interactive "p") ! 346: (pop-to-buffer mh-folder-buffer) ! 347: (forward-line (if arg arg 1)) ! 348: (if (not (re-search-forward "^....[^D^]" nil 0 arg)) ! 349: (progn ! 350: (forward-line -1) ! 351: (message "No more messages")) ! 352: (beginning-of-line)) ! 353: (if (not mh-summarize) (mh-show))) ! 354: ! 355: ! 356: (defun mh-renumber-folder () ! 357: "Renumber messages in folder to be 1..N." ! 358: (interactive) ! 359: (message "packing buffer...") ! 360: (pop-to-buffer mh-folder-buffer) ! 361: (mh-pack-folder) ! 362: (mh-unmark-all-headers nil) ! 363: (mh-position-to-current) ! 364: (message "packing buffer...done")) ! 365: ! 366: ! 367: (defun mh-page-digest () ! 368: "Advance displayed message to next digested message." ! 369: (interactive) ! 370: (save-excursion ! 371: (pop-to-buffer mh-show-buffer) ! 372: (move-to-window-line nil) ! 373: (let ((case-fold-search nil)) ! 374: (when (not (search-forward "\nFrom:" nil t)) ! 375: (other-window -1) ! 376: (error "No more messages"))) ! 377: (recenter 0) ! 378: (other-window -1))) ! 379: ! 380: ! 381: (defun mh-previous-line (&optional arg) ! 382: "Move to previous message in window and display body if summary flag set." ! 383: (interactive "p") ! 384: (pop-to-buffer mh-folder-buffer) ! 385: (forward-line (- (if arg arg 1))) ! 386: (if (not (re-search-backward "^....[^D^]" nil 0 arg)) ! 387: (message "Beginning of messages") ! 388: (if (not mh-summarize) (mh-show)))) ! 389: ! 390: ! 391: (defun mh-previous-page () ! 392: "Page the displayed message backwards." ! 393: (interactive) ! 394: (save-excursion ! 395: (pop-to-buffer mh-show-buffer) ! 396: (scroll-down nil) ! 397: (other-window -1))) ! 398: ! 399: ! 400: (defun mh-quit () ! 401: "Quit mh-e without processing outstanding delete and move commands." ! 402: (interactive) ! 403: (if (and (or mh-delete-list mh-move-list) ! 404: (not (yes-or-no-p "Quit without processing? "))) ! 405: (mh-process-commands mh-folder-buffer)) ! 406: (throw 'exit nil)) ! 407: ! 408: ! 409: (defun mh-rescan-folder (&optional arg) ! 410: "Optionally process commands in current folder and (re)scan it." ! 411: (interactive "P") ! 412: (pop-to-buffer mh-folder-buffer) ! 413: (if (and (or mh-delete-list mh-move-list) ! 414: (y-or-n-p "Process commands? ")) ! 415: (mh-process-commands mh-folder-buffer)) ! 416: (mh-regenerate-headers (if arg (read-string "Range? ") "all")) ! 417: (setq mode-line-format (mh-make-mode-line)) ! 418: (mh-unmark-all-headers nil) ! 419: (mh-position-to-current)) ! 420: ! 421: ! 422: (defun mh-redistribute (to cc) ! 423: "Redistribute a letter." ! 424: (interactive "sTo: \nsCc: ") ! 425: (save-window-excursion ! 426: (let ((msg-filename (mh-msg-filename)) ! 427: (msg (mh-get-msg-num t))) ! 428: (mh-read-file (concat mh-user-path "draft") "draft") ! 429: (delete-other-windows) ! 430: (when (or (zerop (buffer-size)) ! 431: (not (y-or-n-p "The file 'draft' exists. Redistribute? "))) ! 432: (erase-buffer) ! 433: (insert-file-contents msg-filename) ! 434: (goto-char (dot-min)) ! 435: (insert "Resent-To: " to "\n") ! 436: (if (not (string= cc "")) ! 437: (insert "Resent-cc: " cc "\n"))) ! 438: ! 439: (if (mh-compose-and-send-mail "-dist") ! 440: (mh-annotate "F" mh-folder-buffer msg ! 441: "-component" "Distributed-to:" ! 442: "-text" (concat to ! 443: (if (string= cc "") ! 444: "" ! 445: (concat ", " cc)))))))) ! 446: ! 447: ! 448: (defun mh-re-move () ! 449: "Move specified message to same folder as last move." ! 450: (interactive) ! 451: (if (null mh-last-destination) ! 452: (error "No previous move") ! 453: (mh-refile (mh-get-msg-num t) mh-last-destination))) ! 454: ! 455: ! 456: (defun mh-search-folder () ! 457: "Search folder for letters matching a pattern." ! 458: (interactive) ! 459: (let* ((range "all") ! 460: (seq (mh-new-seq)) ! 461: (pattern nil)) ! 462: (mh-get-pick-pattern " *pattern*") ! 463: (while (setq pattern (mh-next-pick-field " *pattern*")) ! 464: (setq msgs ! 465: (mh-seq-from-command seq ! 466: (nconc (cons "pick" pattern) ! 467: (list (concat "+" mh-current-folder) ! 468: range ! 469: "-sequence" seq "-list")))) ! 470: (setq range seq)) ! 471: (mh-apply-to-seq seq 'mh-notate (mh-seq-to-notation seq) 0))) ! 472: ! 473: ! 474: (defun mh-send (to subject cc) ! 475: "Compose and send a letter." ! 476: (interactive "sTo: \nsSubject: \nsCc: ") ! 477: (message "Composing a message...") ! 478: (save-window-excursion ! 479: (mh-read-file (concat mh-user-path "draft") "draft") ! 480: (delete-other-windows) ! 481: (when (or (zerop (buffer-size)) ! 482: (not (y-or-n-p "The file 'draft' exists. Use it? "))) ! 483: (erase-buffer) ! 484: (if (file-exists-p (concat mh-user-path "components")) ! 485: (insert-file-contents (concat mh-user-path "components")) ! 486: (if (file-exists-p (concat mh-lib "components")) ! 487: (insert-file-contents (concat mh-lib "components")) ! 488: (error "Can't find components"))) ! 489: (mh-insert-fields "To:" to "Subject:" subject "Cc:" cc) ! 490: (goto-char (dot-max)) ! 491: (message "Composing a message...done")) ! 492: (mh-compose-and-send-mail ""))) ! 493: ! 494: ! 495: (defun mh-show () ! 496: "Show message indicated by cursor in scan buffer." ! 497: (interactive) ! 498: (setq mh-summarize nil) ! 499: (pop-to-buffer mh-folder-buffer) ! 500: (let ((msgn (mh-get-msg-num t)) ! 501: (msg-filename (mh-msg-filename)) ! 502: (folder mh-current-folder)) ! 503: (if (not (file-exists-p msg-filename)) ! 504: (error "Message %d does not exist." msgn)) ! 505: (push msgn mh-shown-msgs) ! 506: (switch-to-buffer mh-show-buffer) ! 507: (erase-buffer) ! 508: (insert-file-contents msg-filename) ! 509: (setq buffer-file-name msg-filename) ! 510: (mh-letter-mode) ! 511: (cond (mh-clean-message-header ! 512: (mh-clean-message-header) ! 513: (goto-char (dot-min))) ! 514: (t ! 515: (let ((case-fold-search nil)) ! 516: (re-search-forward "^To:\\|^From:\\|^Subject:" nil t) ! 517: (beginning-of-line) ! 518: (recenter 0)))) ! 519: (set-buffer-modified-p nil) ! 520: (setq mode-line-format ! 521: (concat "{%b} %[%p of +" folder "/" msgn "%] %M")) ! 522: ;; These contortions are to force the summary line to be the top window. ! 523: (pop-to-buffer mh-folder-buffer) ! 524: (delete-other-windows) ! 525: (pop-to-buffer mh-show-buffer) ! 526: (pop-to-buffer mh-folder-buffer) ! 527: (shrink-window (- (window-height) mh-summary-height)) ! 528: (recenter 1))) ! 529: ! 530: ! 531: (defun mh-summary () ! 532: "Show a summary of mh-e commands." ! 533: (interactive) ! 534: (message ! 535: "Next Prev Go Del ^ ! Copy Undo . Toggle Ans Forw Redist Send List Quit Exit") ! 536: (sit-for 5)) ! 537: ! 538: ! 539: (defun mh-toggle-summarize () ! 540: "Turn the summary mode of displaying messages on or off." ! 541: (interactive) ! 542: (setq mh-summarize (not mh-summarize)) ! 543: (if (not mh-summarize) ! 544: (mh-show) ! 545: (delete-other-windows))) ! 546: ! 547: ! 548: (defun mh-undo (&optional arg) ! 549: "Undo the deletion or move of the specified message(s)." ! 550: (interactive "P") ! 551: (cond ((looking-at "^....D") ! 552: (let ((msgs (if arg (mh-read-seq "undelete") (mh-get-msg-num t)))) ! 553: (setq mh-delete-list (delq msgs mh-delete-list)) ! 554: (if arg ! 555: (mh-notate-seq msgs ? mh-cmd-note) ! 556: (mh-notate ? mh-cmd-note)))) ! 557: ! 558: ((looking-at "^....^") ! 559: (let ((msgs (if arg (mh-read-seq "unmove") (mh-get-msg-num t)))) ! 560: (mapcar ! 561: (function (lambda (move) (setcdr msgs (delq msgs (cdr move))))) ! 562: mh-move-list) ! 563: (if arg ! 564: (mh-notate-seq msgs ? mh-cmd-note) ! 565: (mh-notate ? mh-cmd-note)))) ! 566: ! 567: (t nil))) ! 568: ! 569: ! 570: (defun mh-visit-folder (&optional arg) ! 571: "Visit a new folder." ! 572: (interactive "p") ! 573: (let* (mh-current-folder ! 574: mh-folder-buffer ! 575: (folder (mh-get-folder-name "visit" "" t)) ! 576: (mh-show-buffer (concat "show-" folder))) ! 577: (save-window-excursion ! 578: (switch-to-buffer (concat "+" folder)) ! 579: (if (> (buffer-size) 0) ! 580: (error "folder +%s is open. close it before revisiting." folder)) ! 581: (mh-scan-folder folder (if arg (read-string "range? ") "all")) ! 582: (pop-to-buffer mh-show-buffer) ! 583: (unwind-protect ! 584: (mh-command-loop) ! 585: (kill-buffer mh-show-buffer) ! 586: (kill-buffer mh-folder-buffer))))) ! 587: ! 588: ! 589: ! 590: ;;; Support routines. ! 591: ! 592: (defun mh-command-loop () ! 593: "Read and execute mh commands." ! 594: (pop-to-buffer mh-folder-buffer) ! 595: (delete-other-windows) ! 596: (recursive-edit)) ! 597: ! 598: ! 599: (defun mh-refile (msgs destination) ! 600: "Refile the msgs in the folder called destination." ! 601: (let ((others (assoc destination mh-move-list))) ! 602: (if others ! 603: (setcdr others (cons msgs (cdr others))) ! 604: (push (cons destination (list msgs)) mh-move-list)) ! 605: (if (integerp msgs) ! 606: (mh-notate ?^ mh-cmd-note) ! 607: (mh-notate-seq msgs ?^ mh-cmd-note)))) ! 608: ! 609: ! 610: (defun mh-clean-message-header () ! 611: "Flush extraneous lines in a message header. The variable ! 612: mh-invisible-headers contains a regular expression specifying these lines." ! 613: (save-restriction ! 614: (goto-char (dot-min)) ! 615: (search-forward "\n\n" nil t) ! 616: (narrow-to-region (dot-min) (dot)) ! 617: (goto-char (dot-min)) ! 618: (while (re-search-forward mh-invisible-headers nil t) ! 619: (beginning-of-line) ! 620: (kill-line 1) ! 621: (while (looking-at "^[ \t]+") ! 622: (beginning-of-line) ! 623: (kill-line 1))))) ! 624: ! 625: ! 626: (defun mh-read-file (file-name buffer-name) ! 627: "Read file FILE-NAME into buffer BUFFER-NAME. No errors if disk file ! 628: has been modified." ! 629: (pop-to-buffer buffer-name) ! 630: (kill-buffer buffer-name) ! 631: (pop-to-buffer buffer-name) ! 632: (if (file-exists-p file-name) ! 633: (insert-file-contents file-name t) ! 634: (setq buffer-file-name file-name)) ! 635: (set-buffer-modified-p nil)) ! 636: ! 637: ! 638: ! 639: ;;; The folder data abstraction. ! 640: ! 641: (defun mh-make-folder (name) ! 642: "Create and initialize a new mail folder called NAME and make ! 643: it the current folder." ! 644: (setq mh-current-folder name) ! 645: (setq mh-folder-buffer (concat "+" name)) ! 646: (switch-to-buffer mh-folder-buffer) ! 647: (kill-all-local-variables) ! 648: (setq buffer-read-only nil) ! 649: (mh-folder-mode) ! 650: (erase-buffer) ! 651: (make-variable-buffer-local 'mh-folder-filename) ! 652: ;; "e.g./usr/foldbar/Mail/inbox/" ! 653: (setq mh-folder-filename (concat mh-user-path name "/")) ! 654: (make-variable-buffer-local 'mh-summarize) ; Show scan list only? ! 655: (setq mh-summarize t) ! 656: (make-variable-buffer-local 'mh-next-seq-num) ; Index of free sequence id ! 657: (setq mh-next-seq-num 0) ! 658: (make-variable-buffer-local 'mh-delete-list) ; List of msgs nums to delete ! 659: (setq mh-delete-list nil) ! 660: (make-variable-buffer-local 'mh-move-list) ; Alist of dest . msgs nums ! 661: (setq mh-move-list nil) ! 662: (make-variable-buffer-local 'mh-seq-list) ; Alist of seq . msgs nums ! 663: (setq mh-seq-list nil) ! 664: (make-variable-buffer-local 'mh-shown-msgs) ; List of msgs shown ! 665: (setq mh-shown-msgs nil) ! 666: (setq buffer-read-only t)) ! 667: ! 668: ! 669: (defun mh-folder-mode () ! 670: " \\[mh-next-line]: next message \\[mh-previous-line]: p ! 671: revious message ! 672: \\[mh-delete-msg]: delete (mark for deletion) \\[mh-move-msg]: put (m ! 673: ark for moving) ! 674: \\[mh-undo]: undo last delete or mark \\[mh-re-move]: repeat ! 675: last ^ command ! 676: \\[mh-copy-msg]: copy message to another folder ! 677: \\[mh-show]: type message \\[mh-toggle-summarize]: toggle ! 678: summarize mode ! 679: \\[scroll-other-window]: page message \\[mh-previous-p ! 680: age]: page message backwards ! 681: \\[mh-print-msg]: print message \\[mh-goto]: goto a mes ! 682: sage ! 683: \\[mh-exit]: exit \\[mh-quit]: quit ! 684: \\[mh-send]: send a message \\[mh-redistribute]: redistribu ! 685: te a message ! 686: \\[mh-answer]: answer a message \\[mh-forward]: forward a messa ! 687: ge ! 688: \\[mh-visit-folder]: visit folder \\[mh-inc-folder]: inc ma ! 689: il ! 690: \\[mh-close-folder]: close folder \\[mh-kill-folder]: kill ! 691: folder ! 692: \\[mh-list-folders]: list folders \\[mh-renumber-folder]: p ! 693: ack folder ! 694: \\[mh-rescan-folder]: rescan folder \\[mh-search-folder]: sea ! 695: rch folder ! 696: Edit the scan list, marking messages. ! 697: When you are done, type 'e'. The messages marked for deletion will be ! 698: deleted and messages marked for moving will be moved. ! 699: In any of the submodes, such as editing a message or composing a message, ! 700: exit with \\[exit-emacs]." ! 701: (auto-save-mode -1) ! 702: (use-local-map mh-keymap) ! 703: (setq major-mode 'mh-folder-mode) ! 704: (setq mode-name "mh-folder") ! 705: (if (and (boundp 'mh-folder-mode-hook) mh-folder-mode-hook) ! 706: (funcall mh-folder-mode-hook))) ! 707: ! 708: ! 709: (defun mh-scan-folder (folder range) ! 710: "Scan the folder FOLDER over the range RANGE." ! 711: (mh-make-folder folder) ! 712: (mh-regenerate-headers range) ! 713: (when (looking-at "scan: no messages ") ! 714: (let ((buffer-read-only nil)) ! 715: (erase-buffer)) ! 716: (if (string= range "all") ! 717: (message "Folder +%s is empty" folder) ! 718: (message "No messages in +%s, range %s" folder range)) ! 719: (sit-for 5)) ! 720: (setq mode-line-format (mh-make-mode-line)) ! 721: (mh-unmark-all-headers nil) ! 722: (mh-position-to-current)) ! 723: ! 724: ! 725: (defun mh-regenerate-headers (range) ! 726: "Replace buffer with scan of its contents over range RANGE." ! 727: (let ((buffer-read-only nil)) ! 728: (message (format "scanning %s..." (buffer-name))) ! 729: (delete-other-windows) ! 730: (erase-buffer) ! 731: (mh-exec-cmd-output "scan" (buffer-name) range) ! 732: (goto-char (dot-min)) ! 733: (message (format "scanning %s...done" (buffer-name))) ! 734: )) ! 735: ! 736: ! 737: (defun mh-get-new-mail () ! 738: "Read new mail into the current buffer." ! 739: (let ((buffer-read-only nil)) ! 740: (message (format "inc %s..." (buffer-name))) ! 741: (goto-char (dot-max)) ! 742: (set-mark (dot)) ! 743: (mh-exec-cmd-output "inc") ! 744: (message (format "inc %s...done" (buffer-name))) ! 745: (goto-char (mark)) ! 746: (cond ((looking-at "inc: no mail") ! 747: (kill-line 1) ! 748: (message "No new mail") ! 749: (sit-for 5)) ! 750: (t ! 751: (kill-line 2)))) ! 752: (setq mode-line-format (mh-make-mode-line))) ! 753: ! 754: ! 755: (defun mh-make-mode-line () ! 756: "Returns a string for mode-line-format." ! 757: (save-excursion ! 758: (goto-char (dot-min)) ! 759: (set-mark (dot)) ! 760: (goto-char (dot-max)) ! 761: (let ((lines (count-lines (dot) (mark)))) ! 762: (goto-char (dot-min)) ! 763: (let ((first (mh-get-msg-num nil)) ! 764: (case-fold-search nil)) ! 765: (re-search-forward "[ ]*[0-9]*\\+" nil t) ! 766: (let ((current (mh-get-msg-num nil))) ! 767: (goto-char (dot-max)) ! 768: (previous-line 1) ! 769: (let ((last (mh-get-msg-num nil))) ! 770: (concat "{%b} %[" lines " messages" ! 771: (if (> lines 0) ! 772: (concat " (" first " - " last ")") ! 773: "") ! 774: (if current ! 775: (concat " cur = " current) ! 776: "") ! 777: "%] "))))))) ! 778: ! 779: ! 780: (defun mh-unmark-all-headers (remove-all-flags) ! 781: "This function removes all + flags from the headers, and if called ! 782: with a non-nil argument, removes all D and ^ flags too." ! 783: (switch-to-buffer mh-folder-buffer) ! 784: (let ((buffer-read-only nil) ! 785: (case-fold-search nil)) ! 786: (goto-char (dot-min)) ! 787: (while (if remove-all-flags ! 788: (re-search-forward "^....\\+" nil t) ! 789: (re-search-forward "^\\D\\|^\\^\\|^....\\+" nil t)) ! 790: (delete-backward-char 1) ! 791: (insert " ")))) ! 792: ! 793: ! 794: (defun mh-position-to-current () ! 795: "Position the cursor at the current message." ! 796: (let ((curmsg (mh-get-cur-msg mh-folder-filename))) ! 797: (when (or (zerop curmsg) (mh-goto curmsg t)) ! 798: (message "No message %s" curmsg) ! 799: (goto-char (dot-max)) ! 800: (forward-line -1)) ! 801: (when (looking-at "[ ]+[0-9]+") ! 802: (mh-notate ?+ 0) ! 803: (recenter 0)))) ! 804: ! 805: ! 806: (defun mh-pack-folder () ! 807: "Closes and packs the current folder." ! 808: (let ((buffer-read-only nil)) ! 809: (message "closing folder...") ! 810: (mh-process-commands mh-folder-buffer) ! 811: (message "packing folder...") ! 812: (mh-exec-cmd "folder" mh-folder-buffer "-pack") ! 813: (mh-regenerate-headers "all") ! 814: (message "packing done")) ! 815: (setq mode-line-format (mh-make-mode-line))) ! 816: ! 817: ! 818: (defun mh-apply-to-message-list (func list) ! 819: "Apply function FUNC to each item in a message-list LIST, ! 820: passing the name and list of messages as arguments." ! 821: (mapcar (function (lambda (l) (apply func (list (car l) (cdr l))))) list)) ! 822: ! 823: ! 824: (defun mh-process-commands (buffer) ! 825: "Process outstanding commands for the buffer BUFFER." ! 826: (message "Processing deletes and moves...") ! 827: (switch-to-buffer buffer) ! 828: (let ((buffer-read-only nil)) ! 829: ;; Sequences must be first ! 830: (mh-process-seq-commands mh-seq-list) ! 831: ! 832: ;; Then refile messages ! 833: (mh-apply-to-message-list ! 834: (function (lambda (dest msgs) ! 835: (apply 'mh-exec-cmd ! 836: (nconc (cons "refile" msgs) ! 837: (list "-src" (format "%s" buffer) ! 838: (format "+%s" dest)))))) ! 839: mh-move-list) ! 840: ! 841: ;; Now delete messages ! 842: (if mh-delete-list ! 843: (apply 'mh-exec-cmd ! 844: (nconc (list "rmm" (format "%s" buffer)) mh-delete-list))) ! 845: ! 846: ;; Finally update unseen sequence ! 847: (if mh-shown-msgs ! 848: (apply 'mh-exec-cmd-no-wait ! 849: (nconc (list "show" (format "%s" buffer)) ! 850: mh-shown-msgs ! 851: (list "-noformat")))) ! 852: ! 853: (setq mh-delete-list nil ! 854: mh-move-list nil ! 855: mh-seq-list nil ! 856: mh-shown-msgs nil)) ! 857: (message "Processing deletes and moves...done")) ! 858: ! 859: ! 860: ! 861: ;;; Routines for editing a message. ! 862: ! 863: (defun mh-letter-mode () ! 864: "Mode for composing letters in mh. ! 865: ^N and ^P work normally in the body of a letter but hug the end of field names ! 866: in the header. ! 867: ^X^C exits and sends a letter." ! 868: (text-mode) ! 869: (if mh-auto-fill-letters ! 870: (auto-fill-mode 1)) ! 871: (setq paragraph-separate "^[- \t\^L]*$") ! 872: (setq paragraph-start "^$\\|^\^L\\|^-+$") ! 873: (when (not mh-letter-mode-map) ! 874: (setq mh-letter-mode-map (copy-sequence text-mode-map)) ! 875: (define-key mh-letter-mode-map "\^N" 'mh-header-next) ! 876: (define-key mh-letter-mode-map "\^P" 'mh-header-previous)) ! 877: (use-local-map mh-letter-mode-map) ! 878: (setq major-mode 'mh-letter-mode) ! 879: (setq mode-name "mh-letter") ! 880: (if (and (boundp 'mh-letter-mode-hook) mh-letter-mode-hook) ! 881: (funcall mh-letter-mode-hook))) ! 882: ! 883: ! 884: (defun mh-header-next (&optional arg) ! 885: "Modified ^N command that skips to end of header field names." ! 886: (interactive "p") ! 887: (next-line (if arg arg 1)) ! 888: (mh-header-line-position)) ! 889: ! 890: ! 891: (defun mh-header-previous (&optional arg) ! 892: "Modified ^P command that skips to end of header field names." ! 893: (interactive "p") ! 894: (previous-line (if arg arg 1)) ! 895: (mh-header-line-position)) ! 896: ! 897: ! 898: (defun mh-dot-in-header () ! 899: "t iff cursor in message header." ! 900: (save-excursion ! 901: (let ((wasdot (dot)) ! 902: (case-fold-search nil)) ! 903: (goto-char (dot-min)) ! 904: (re-search-forward "^-*$" nil t) ! 905: (beginning-of-line) ! 906: (backward-char 1) ! 907: (>= (dot) wasdot)))) ! 908: ! 909: ! 910: (defun mh-header-line-position () ! 911: "Position cursor at end of field name when in header." ! 912: (if (mh-dot-in-header) ! 913: (when (save-excursion (beginning-of-line) (not (looking-at " \\|\t"))) ! 914: (beginning-of-line) ! 915: (search-forward ":" nil t) ! 916: (if (eolp) ! 917: (insert " ") ! 918: (while (looking-at "[ \t]") (forward-char 1)))))) ! 919: ! 920: ! 921: ! 922: ;;; Routines to make a search pattern and search for a message. ! 923: ! 924: (defun mh-get-pick-pattern (buffer) ! 925: "Prompt the user for a pattern to search for in messages. Upon return, ! 926: current buffer contains the filled-in template." ! 927: (save-window-excursion ! 928: (pop-to-buffer buffer) ! 929: (if (or (zerop (buffer-size)) ! 930: (not (y-or-n-p "Reuse pattern? "))) ! 931: (mh-pick-template) ! 932: (message "")) ! 933: (local-set-key "\^X\^C" 'mh-make-pick-pattern) ! 934: (let ((mode-line-format "{%b}\tPick Pattern\t^X^C to exit and search")) ! 935: (catch 'mh-pattern (recursive-edit))))) ! 936: ! 937: ! 938: (defun mh-make-pick-pattern () ! 939: (interactive) ! 940: (goto-char (dot-min)) ! 941: (throw 'mh-pattern nil)) ! 942: ! 943: ! 944: (defun mh-pick-template () ! 945: (erase-buffer) ! 946: (insert "From: \n" ! 947: "To: \n" ! 948: "Cc: \n" ! 949: "Date: \n" ! 950: "Subject: \n" ! 951: "---------\n") ! 952: (goto-char (dot-min)) ! 953: (end-of-line) ! 954: (mh-letter-mode)) ! 955: ! 956: ! 957: (defun mh-next-pick-field (buffer) ! 958: "Return a pattern to search for messages containing the next field, or NIL ! 959: if no fields remain." ! 960: (save-excursion ! 961: (pop-to-buffer buffer) ! 962: (let ((pat ()) ! 963: (case-fold-search t)) ! 964: (cond ((re-search-forward "^\\([a-z].*\\):[ \t]*\\([a-z0-9].*\\)$" nil t) ! 965: (region-around-match 1) ! 966: (let ((component (concat "-" (downcase (region-to-string))))) ! 967: (region-around-match 2) ! 968: (setq pat (nconc (list component (region-to-string)) pat))) ! 969: (forward-line 1) ! 970: pat) ! 971: ((re-search-forward "^-*$" nil t) ! 972: (forward-char 1) ! 973: (set-mark (dot)) ! 974: (goto-char (dot-max)) ! 975: (let ((body (region-to-string))) ! 976: (if (> (length body) 0) ! 977: (list "-search" body) ! 978: nil))) ! 979: (t ! 980: nil))))) ! 981: ! 982: ! 983: ! 984: ;;; Routines compose and send a letter. ! 985: ! 986: (defun mh-compose-and-send-mail (send-args) ! 987: "Edit a draft message and send or save it. SEND-ARGS is passed to the ! 988: send command. Returns t if mail is being sent." ! 989: (save-window-excursion ! 990: (pop-to-buffer "draft") ! 991: (mh-letter-mode) ! 992: (local-set-key "\^X\^C" 'mh-send-letter) ! 993: (local-set-key "\^X\^Y" 'mh-insert-letter) ! 994: (mh-header-line-position) ! 995: (let ((mode-line-format ! 996: "{%b} %[Mail/draft%] (%p - %m) (^X^C to finish ^X^Y to yank msg) ! 997: %M")) ! 998: (catch 'mail-send (recursive-edit))))) ! 999: ! 1000: ! 1001: (defun mh-send-letter () ! 1002: "Prompt the user as to the disposition of the just-composed letter." ! 1003: (interactive) ! 1004: (save-buffer) ! 1005: (let ((mode-line-format "{%b} %[Mail/draft%] (%p - %m) %M") ! 1006: (action (mh-get-response ! 1007: "Ready to send. Action (s, q, e, ?): " ! 1008: '(?s ?b ?q ?e ?\^C) ! 1009: "S-end, Q-uit, E-dit "))) ! 1010: (cond ((equal action ?s) ! 1011: (message "Sending...") ! 1012: (mh-exec-cmd-no-wait "send" "-push" "-unique" send-args ! 1013: (buffer-file-name)) ! 1014: (message "Sending...done") ! 1015: (throw 'mail-send t)) ! 1016: ! 1017: ((equal action ?q) ! 1018: (message "Not sent... Message remains in buffer draft") ! 1019: (throw 'mail-send nil))))) ! 1020: ! 1021: ! 1022: (defun mh-insert-letter () ! 1023: "Insert a message in the current letter." ! 1024: (interactive) ! 1025: (let ((folder (mh-get-folder-name "Message from" mh-current-folder nil)) ! 1026: (message (string-to-int (read-input "Message number: " "")))) ! 1027: (insert-file-contents (concat mh-user-path folder "/" message)))) ! 1028: ! 1029: ! 1030: ! 1031: ;;; Commands to manipulate sequences. ! 1032: ! 1033: (defmacro mh-seq-name (pair) ! 1034: (list 'car pair)) ! 1035: ! 1036: (defmacro mh-seq-msgs (pair) ! 1037: (list 'cdr pair)) ! 1038: ! 1039: ! 1040: (defun mh-seq-to-msgs (seq) ! 1041: "Returns the list of messages in sequence SEQ." ! 1042: (mh-seq-msgs (assoc seq mh-seq-list))) ! 1043: ! 1044: ! 1045: (defun mh-read-seq (prompt) ! 1046: "Prompt the user with PROMPT and read a sequence name." ! 1047: (mh-letter-to-seq ! 1048: (string-to-char (read-string (format "%s %s" prompt "sequence: "))))) ! 1049: ! 1050: ! 1051: (defun mh-seq-from-command (seq command) ! 1052: "Make a sequence called SEQ by executing the form COMMAND." ! 1053: (let ((msgs ()) ! 1054: (case-fold-search t)) ! 1055: (save-window-excursion ! 1056: (apply 'mh-exec-cmd-quiet command) ! 1057: (pop-to-buffer " *mh-temp*") ! 1058: (goto-char (dot-min)) ! 1059: (while (re-search-forward "\\([0-9]+\\)" nil t) ! 1060: (region-around-match 0) ; BUG in GNU EMACS: should be 1! ! 1061: (let ((num (string-to-int (region-to-string)))) ! 1062: (if (not (zerop num)) ! 1063: (push num msgs))))) ! 1064: ! 1065: (push (cons seq msgs) mh-seq-list) ! 1066: msgs)) ! 1067: ! 1068: ! 1069: (defun mh-remove-seq (seq) ! 1070: "Delete the sequence SEQ." ! 1071: (let ((entry (assoc seq mh-seq-list))) ! 1072: (setq mh-seq-list (delq (car entry) mh-seq-list)) ! 1073: (mh-apply-to-seq (mh-seq-msgs (car entry)) 'mh-notate ? 0))) ! 1074: ! 1075: ! 1076: (defun mh-remove-msg-from-seq (msg-num seq) ! 1077: "Remove a message MSG-NUM from the sequence SEQ." ! 1078: (let ((seq (assoc seq mh-seq-list))) ! 1079: (setcdr (car seq) (delq msg-num (mh-seq-msgs (car seq))))) ! 1080: (mh-notate ? mh-cmd-note)) ! 1081: ! 1082: ! 1083: (defun mh-add-msg-to-seq (msg-num seq) ! 1084: "Add a message MSG-NUM to a sequence SEQ." ! 1085: (let ((seq-list (assoc seq mh-seq-list))) ! 1086: (mh-notate (mh-seq-to-notation seq) 0) ! 1087: (if (null seq-list) ! 1088: (push (cons seq (list msg-num)) mh-seq-list) ! 1089: (setcdr seq-list (cons msg-num (cdr seq-list)))))) ! 1090: ! 1091: ! 1092: ! 1093: (defun mh-new-seq () ! 1094: "Return a new sequence name." ! 1095: (save-excursion ! 1096: (switch-to-buffer mh-folder-buffer) ! 1097: (if (= mh-next-seq-num 10) ! 1098: (error "No more sequences")) ! 1099: (setq mh-next-seq-num (+ mh-next-seq-num 1)) ! 1100: (mh-letter-to-seq (+ (1- mh-next-seq-num) ?a)))) ! 1101: ! 1102: ! 1103: (defun mh-letter-to-seq (letter) ! 1104: "Given a LETTER, return a string that is a valid sequence name." ! 1105: (cond ((and (>= letter ?0) (< letter ?9)) ! 1106: (intern (concat "mhe" (char-to-string letter)))) ! 1107: ((and (>= letter ?a) (< letter ?z)) ! 1108: (intern (concat "mhe" (char-to-string letter)))) ! 1109: (t ! 1110: (error "A sequence is named 0...9")))) ! 1111: ! 1112: ! 1113: (defun mh-seq-to-notation (seq) ! 1114: "Return the string used to indicate sequence SEQ in a scan listing." ! 1115: (string-to-char (substring (symbol-name seq) 3 4))) ! 1116: ! 1117: ! 1118: (defun mh-notate-seq (seq notation offset) ! 1119: "Mark all messages in the sequence SEQ with the NOTATION at character ! 1120: OFFSET." ! 1121: (mh-apply-to-seq seq 'mh-notate notation offset)) ! 1122: ! 1123: ! 1124: (defun mh-apply-to-seq (seq function &rest args) ! 1125: "For each message in sequence SEQ, evaluate the FUNCTION with ARGS." ! 1126: (mapcar (function (lambda (msg) (mh-goto msg) (apply function args))) ! 1127: (mh-seq-to-msgs seq))) ! 1128: ! 1129: ! 1130: (defun mh-process-seq-commands (seq-list) ! 1131: "Process outstanding sequence commands for the sequences in SEQ-LIST." ! 1132: (mh-apply-to-message-list ! 1133: (function (lambda (seq msgs) ! 1134: (apply 'mh-exec-cmd-quiet ! 1135: (nconc (list "mark" "-zero" "-seq" (format "%s" seq) ! 1136: "-add") ! 1137: msgs)))) ! 1138: seq-list)) ! 1139: ! 1140: ! 1141: ! 1142: ;;; Issue commands to mh. ! 1143: ! 1144: (defun mh-exec-cmd (command &rest args) ! 1145: "Execute MH command COMMAND with ARGS. Any output is shown to the user." ! 1146: (save-excursion ! 1147: (pop-to-buffer " *mh-temp*") ! 1148: (erase-buffer) ! 1149: (set-mark (dot)) ! 1150: (apply 'call-process (nconc (list (concat mh-progs command) nil t nil) ! 1151: (mh-list-to-string args))) ! 1152: (when (> (buffer-size) 0) ! 1153: (message "%s" (region-to-string)) ! 1154: (sit-for 5)))) ! 1155: ! 1156: ! 1157: (defun mh-exec-cmd-quiet (command &rest args) ! 1158: "Execute MH command COMMAND with ARGS. Output is collected, but not shown ! 1159: to the user." ! 1160: (pop-to-buffer " *mh-temp*") ! 1161: (erase-buffer) ! 1162: (set-mark (dot)) ! 1163: (apply 'call-process (nconc (list (concat mh-progs command) nil t nil) ! 1164: (mh-list-to-string args)))) ! 1165: ! 1166: ! 1167: (defun mh-exec-cmd-output (command &rest args) ! 1168: "Execute MH command COMMAND with ARGS putting the output into the current ! 1169: buffer." ! 1170: (apply 'call-process (nconc (list (concat mh-progs command) nil t nil) ! 1171: (mh-list-to-string args)))) ! 1172: ! 1173: ! 1174: (defun mh-exec-cmd-no-wait (command &rest args) ! 1175: "Execute MH command COMMAND with ARGS and do not wait until it finishes." ! 1176: (apply 'call-process (nconc (list (concat mh-progs command) nil 0 nil) ! 1177: (mh-list-to-string args)))) ! 1178: ! 1179: ! 1180: (defun mh-list-to-string (l) ! 1181: "Flattens the list L and makes every element a string." ! 1182: (let ((new-list nil)) ! 1183: (while l ! 1184: (cond ((symbolp (car l)) (push (format "%s" (car l)) new-list)) ! 1185: ((numberp (car l)) (push (format "%d" (car l)) new-list)) ! 1186: ((string= (car l) "")) ! 1187: ((stringp (car l)) (push (car l) new-list)) ! 1188: ((null (car l))) ! 1189: ((listp (car l)) (setq new-list ! 1190: (nconc (mh-list-to-string (car l)) ! 1191: new-list))) ! 1192: (t (error "Bad argument %s" (car l)))) ! 1193: (setq l (cdr l))) ! 1194: (nreverse new-list))) ! 1195: ! 1196: ! 1197: ! 1198: ;;; Commands to annotate a message. ! 1199: ! 1200: (defun mh-annotate (note &rest args) ! 1201: "Mark the current message with the character NOTE and annotate the message ! 1202: with ARGS." ! 1203: (apply 'mh-exec-cmd-no-wait (cons "anno" args)) ! 1204: (mh-notate note 5)) ! 1205: ! 1206: ! 1207: (defun mh-notate (notation offset) ! 1208: "Marks the current message with the character NOTATION at position OFFSET." ! 1209: (save-excursion ! 1210: (pop-to-buffer mh-folder-buffer) ! 1211: (let ((buffer-read-only nil)) ! 1212: (beginning-of-line) ! 1213: (goto-char (+ (dot) offset)) ! 1214: (delete-char 1) ! 1215: (insert notation) ! 1216: (beginning-of-line)))) ! 1217: ! 1218: ! 1219: ! 1220: ;;; User prompting commands. ! 1221: ! 1222: (defun mh-get-folder-name (prompt default can-create) ! 1223: "Prompt for a folder name with PROMPT. DEFAULT is the default folder. ! 1224: If the CAN-CREATE flag is t, then the folder can be made if it does not exist." ! 1225: (let ((exists nil) ! 1226: (prompt (concat prompt " folder" ! 1227: (if (string= "" default) ! 1228: "? " ! 1229: (concat " [" default "]? ")))) ! 1230: (file-name)) ! 1231: (let ((name)) ! 1232: (while (not exists) ! 1233: (setq name (read-string prompt)) ! 1234: (if (string= name "") ! 1235: (setq name default)) ! 1236: (if (string= (substring name 0 1) "+") ! 1237: (setq name (substring name 1))) ! 1238: (if (not (string= (substring name 0 1) "/")) ! 1239: (setq file-name (concat mh-user-path name)) ! 1240: (setq file-name name)) ! 1241: (setq exists (file-exists-p file-name)) ! 1242: (if (not exists) ! 1243: (cond ((and can-create ! 1244: (y-or-n-p (concat "Folder +" name ! 1245: " does not exist. Create it? "))) ! 1246: (message "Creating %s" name) ! 1247: (call-process "mkdir" nil nil nil file-name) ! 1248: (message "Creating %s...done" name) ! 1249: (setq exists t)) ! 1250: ! 1251: (can-create ! 1252: (error "")) ! 1253: ! 1254: (t ! 1255: (setq prompt (concat "Sorry, no such folder as `" name ! 1256: "' Folder name? ")))))) ! 1257: name))) ! 1258: ! 1259: ! 1260: (defun mh-get-response (prompt possibilities help) ! 1261: "Prints PROMPT, reads a character, and checks it against the list ! 1262: of POSSIBILITIES. Returns the character if it is legal. The HELP string ! 1263: is displayed if the character is not legal." ! 1264: (let ((ok nil) ! 1265: (first-char)) ! 1266: (while (not ok) ! 1267: (let ((pos possibilities)) ! 1268: (message prompt) ! 1269: (setq first-char (read-char)) ! 1270: (while (and (not ok) pos) ! 1271: (if (equal first-char (car pos)) ! 1272: (setq ok t)) ! 1273: (setq pos (cdr pos))) ! 1274: ! 1275: (cond ((equal first-char ??) ! 1276: (message help) ! 1277: (sit-for 5)) ! 1278: ((not ok) ! 1279: (message "Illegal response '%c'" first-char) ! 1280: (sit-for 5))))) ! 1281: first-char)) ! 1282: ! 1283: ! 1284: ! 1285: ;;; Misc. functions. ! 1286: ! 1287: (defun mh-get-msg-num (error-if-no-message) ! 1288: "Returns the message number of the current message. If the argument ! 1289: ERROR-IF-NO-MESSAGE is t, then complain if the cursor is not pointing to a ! 1290: message." ! 1291: (save-excursion ! 1292: (switch-to-buffer mh-folder-buffer) ! 1293: (beginning-of-line) ! 1294: (cond ((looking-at "^\+?\\([0-9]+\\)") ! 1295: (region-around-match 1) ! 1296: (string-to-int (region-to-string))) ! 1297: ((looking-at "^\+?[0-9a-z]?[ ]+\\([0-9]+\\)") ! 1298: (region-around-match 1) ! 1299: (string-to-int (region-to-string))) ! 1300: (error-if-no-message ! 1301: (error "Cursor not pointing to message")) ! 1302: (t nil)))) ! 1303: ! 1304: ! 1305: (defun mh-msg-filename () ! 1306: "Returns a string containing the pathname for a message." ! 1307: (save-excursion ! 1308: (switch-to-buffer mh-folder-buffer) ! 1309: (concat mh-folder-filename (mh-get-msg-num t)))) ! 1310: ! 1311: ! 1312: (defun mh-msg-filenames (msgs folder) ! 1313: "Returns an arglist for ls specifying the messages MSGS in folder FOLDER." ! 1314: (if msgs ! 1315: (let ((args (concat folder "{"))) ! 1316: (while (cdr msgs) ! 1317: (setq args (concat args (car msgs) ",")) ! 1318: (setq msgs (cdr msgs))) ! 1319: (concat args (car msgs) "}")) ! 1320: "")) ! 1321: ! 1322: ! 1323: (defun mh-find-path () ! 1324: "Set mh_path from ~/.mh_profile." ! 1325: (save-window-excursion ! 1326: (if (not (file-exists-p "~/.mh_profile")) ! 1327: (error "Can find .mh_profile file.")) ! 1328: (switch-to-buffer " *mh_profile*") ! 1329: (erase-buffer) ! 1330: (insert-file-contents "~/.mh_profile") ! 1331: (if (string= (setq mh-user-path (mh-get-field "Path:")) "") ! 1332: (setq mh-user-path "Mail/") ! 1333: (setq mh-user-path (concat mh-user-path "/"))) ! 1334: (if (not (string= (substring mh-user-path 0 1) "/")) ! 1335: (setq mh-user-path (concat (getenv "HOME") "/" mh-user-path))))) ! 1336: ! 1337: ! 1338: (defun mh-get-cur-msg (folder) ! 1339: "Returns the cur message from FOLDER." ! 1340: (let ((seq-filename (concat folder ".mh_sequences"))) ! 1341: (save-window-excursion ! 1342: (cond ((file-exists-p seq-filename) ! 1343: (switch-to-buffer " *mh_sequences*") ! 1344: (erase-buffer) ! 1345: (insert-file-contents seq-filename) ! 1346: (string-to-int (mh-get-field "cur: "))) ! 1347: (t 0))))) ! 1348: ! 1349: ! 1350: (defun mh-get-field (field) ! 1351: "Get the value of field FIELD in the current buffer." ! 1352: (let ((case-fold-search t)) ! 1353: (goto-char (dot-min)) ! 1354: (cond ((not (search-forward field nil t)) "") ! 1355: (t ! 1356: (re-search-forward "[\t ]*\\([a-zA-z0-9/].*\\)$" nil t) ! 1357: (region-around-match 1) ! 1358: (let ((field (region-to-string))) ! 1359: (set-mark (dot)) ! 1360: (forward-line) ! 1361: (while (looking-at "[ \t]") (forward-line 1)) ! 1362: (backward-char 1) ! 1363: (concat field (region-to-string))))))) ! 1364: ! 1365: ! 1366: (defun mh-insert-fields (&rest name-values) ! 1367: "Insert the NAME-VALUE pairs in the current buffer." ! 1368: (let ((case-fold-search t)) ! 1369: (while name-values ! 1370: (let ((field-name (car name-values)) ! 1371: (value (cadr name-values))) ! 1372: (goto-char (dot-min)) ! 1373: (cond ((not (search-forward (concat "\n" field-name) nil t)) ! 1374: (search-forward "---") ! 1375: (beginning-of-line) ! 1376: (insert field-name " " value "\n")) ! 1377: (t ! 1378: (end-of-line) ! 1379: (insert " " value))) ! 1380: (setq name-values (cddr name-values)))))) ! 1381: ! 1382: ! 1383: ! 1384: ;;; Build the keymap for mh: ! 1385: ! 1386: (defvar mh-keymap (make-sparse-keymap)) ! 1387: ! 1388: (define-key mh-keymap "?" 'mh-summary) ! 1389: (define-key mh-keymap "c" 'mh-copy-msg) ! 1390: (define-key mh-keymap "d" 'mh-delete-msg) ! 1391: (define-key mh-keymap "^" 'mh-move-msg) ! 1392: (define-key mh-keymap "!" 'mh-re-move) ! 1393: (define-key mh-keymap "u" 'mh-undo) ! 1394: (define-key mh-keymap "l" 'mh-print-msg) ! 1395: (define-key mh-keymap "p" 'mh-previous-line) ! 1396: (define-key mh-keymap "n" 'mh-next-line) ! 1397: (define-key mh-keymap "g" 'mh-goto) ! 1398: (define-key mh-keymap " " 'scroll-other-window) ! 1399: (define-key mh-keymap "\e " 'mh-page-digest) ! 1400: (define-key mh-keymap "\^H" 'mh-previous-page) ! 1401: (define-key mh-keymap "t" 'mh-toggle-summarize) ! 1402: (define-key mh-keymap "." 'mh-show) ! 1403: (define-key mh-keymap "a" 'mh-answer) ! 1404: (define-key mh-keymap "f" 'mh-forward) ! 1405: (define-key mh-keymap "r" 'mh-redistribute) ! 1406: (define-key mh-keymap "s" 'mh-send) ! 1407: (define-key mh-keymap "\^X\^C" 'mh-quit) ! 1408: (define-key mh-keymap "q" 'mh-quit) ! 1409: (define-key mh-keymap "e" 'mh-exit) ! 1410: (define-key mh-keymap "0" 'mh-indicate-seq) ! 1411: (define-key mh-keymap "1" 'mh-indicate-seq) ! 1412: (define-key mh-keymap "2" 'mh-indicate-seq) ! 1413: (define-key mh-keymap "3" 'mh-indicate-seq) ! 1414: (define-key mh-keymap "4" 'mh-indicate-seq) ! 1415: (define-key mh-keymap "5" 'mh-indicate-seq) ! 1416: (define-key mh-keymap "6" 'mh-indicate-seq) ! 1417: (define-key mh-keymap "7" 'mh-indicate-seq) ! 1418: (define-key mh-keymap "8" 'mh-indicate-seq) ! 1419: (define-key mh-keymap "9" 'mh-indicate-seq) ! 1420: (define-key mh-keymap "\ef" 'mh-visit-folder) ! 1421: (define-key mh-keymap "\ei" 'mh-inc-folder) ! 1422: (define-key mh-keymap "\ec" 'mh-close-folder) ! 1423: (define-key mh-keymap "\ek" 'mh-kill-folder) ! 1424: (define-key mh-keymap "\el" 'mh-list-folders) ! 1425: (define-key mh-keymap "\ep" 'mh-renumber-folder) ! 1426: (define-key mh-keymap "\er" 'mh-rescan-folder) ! 1427: (define-key mh-keymap "\es" 'mh-search-folder)
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.