diff options
| author | Richard M. Stallman | 1994-05-02 05:16:59 +0000 |
|---|---|---|
| committer | Richard M. Stallman | 1994-05-02 05:16:59 +0000 |
| commit | 813f532d2f0d18dcda7d93be2c6cd841815ff8b8 (patch) | |
| tree | 9532b7d9540d093d97772918eee701de11e54326 /lisp/mail | |
| parent | 7ce486180bb65a66b58103891224a563ec4b4d7b (diff) | |
| download | emacs-813f532d2f0d18dcda7d93be2c6cd841815ff8b8.tar.gz emacs-813f532d2f0d18dcda7d93be2c6cd841815ff8b8.zip | |
Initial revision
Diffstat (limited to 'lisp/mail')
| -rw-r--r-- | lisp/mail/mail-hist.el | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/lisp/mail/mail-hist.el b/lisp/mail/mail-hist.el new file mode 100644 index 00000000000..ffa3003a45c --- /dev/null +++ b/lisp/mail/mail-hist.el | |||
| @@ -0,0 +1,282 @@ | |||
| 1 | ;;; mail-hist.el --- Headers and message body history for outgoing mail. | ||
| 2 | ;; Copyright (C) 1994 Free Software Foundation, Inc. | ||
| 3 | |||
| 4 | ;; Author: Karl Fogel <kfogel@cs.oberlin.edu> | ||
| 5 | ;; Created: March, 1994 | ||
| 6 | ;; Version: 1.2.2 | ||
| 7 | ;; Keywords: mail | ||
| 8 | |||
| 9 | ;; This file is part of GNU Emacs. | ||
| 10 | |||
| 11 | ;; GNU Emacs is free software; you can redistribute it and/or modify | ||
| 12 | ;; it under the terms of the GNU General Public License as published by | ||
| 13 | ;; the Free Software Foundation; either version 2, or (at your option) | ||
| 14 | ;; any later version. | ||
| 15 | |||
| 16 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 19 | ;; GNU General Public License for more details. | ||
| 20 | |||
| 21 | ;;; Commentary: | ||
| 22 | |||
| 23 | ;; You should have received a copy of the GNU General Public License | ||
| 24 | ;; along with GNU Emacs; see the file COPYING. If not, write to | ||
| 25 | ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | ||
| 26 | |||
| 27 | ;; Thanks to Jim Blandy for mentioning ring.el. It saved a lot of | ||
| 28 | ;; time. | ||
| 29 | ;; | ||
| 30 | ;; To use this package, put it in a directory in your load-path, and | ||
| 31 | ;; put this in your .emacs file: | ||
| 32 | ;; | ||
| 33 | ;; (load "mail-hist" nil t) | ||
| 34 | ;; | ||
| 35 | ;; Or you could do it with autoloads and hooks in your .emacs: | ||
| 36 | ;; | ||
| 37 | ;; (add-hook 'mail-mode-hook 'mail-hist-define-keys) | ||
| 38 | ;; (add-hook 'mail-send-hook 'mail-hist-put-headers-into-history) | ||
| 39 | ;; (add-hook 'vm-mail-mode-hook 'mail-hist-define-keys) ;or rmail, etc | ||
| 40 | ;; (autoload 'mail-hist-define-keys "mail-hist") | ||
| 41 | ;; (autoload 'mail-hist-put-headers-into-history "mail-hist") | ||
| 42 | ;; | ||
| 43 | ;; Once it's installed, use M-p and M-n from mail headers to recover | ||
| 44 | ;; previous/next contents in the history for that header, or, in the | ||
| 45 | ;; body of the message, to recover previous/next text of the message. | ||
| 46 | ;; This only applies to outgoing mail -- mail-hist ignores received | ||
| 47 | ;; messages. | ||
| 48 | ;; | ||
| 49 | ;; Although repeated history requests do clear out the text from the | ||
| 50 | ;; previous request, an isolated request just inserts its text at | ||
| 51 | ;; point, so that you can mix the histories of different messages | ||
| 52 | ;; easily. This might be confusing at times, but there should be no | ||
| 53 | ;; problems that undo can't handle. | ||
| 54 | |||
| 55 | ;;; Code: | ||
| 56 | (require 'ring) | ||
| 57 | |||
| 58 | ;;;###autoload | ||
| 59 | (defun mail-hist-define-keys () | ||
| 60 | "Define keys for accessing mail header history. For use in hooks." | ||
| 61 | (local-set-key "\M-p" 'mail-hist-previous-input) | ||
| 62 | (local-set-key "\M-n" 'mail-hist-next-input)) | ||
| 63 | |||
| 64 | ;;;###autoload | ||
| 65 | (add-hook 'mail-mode-hook 'mail-hist-define-keys) | ||
| 66 | |||
| 67 | ;;;###autoload | ||
| 68 | (add-hook 'vm-mail-mode-hook 'mail-hist-define-keys) | ||
| 69 | |||
| 70 | ;;;###autoload | ||
| 71 | (add-hook 'mail-send-hook 'mail-hist-put-headers-into-history) | ||
| 72 | |||
| 73 | (defvar mail-hist-header-ring-alist nil | ||
| 74 | "Alist of form (header-name . history-ring). | ||
| 75 | Used for knowing which history list to look in when the user asks for | ||
| 76 | previous/next input.") | ||
| 77 | |||
| 78 | (defvar mail-hist-history-size (or kill-ring-max 1729) | ||
| 79 | "*The maximum number of elements in a mail field's history. | ||
| 80 | Oldest elements are dumped first.") | ||
| 81 | |||
| 82 | ;;;###autoload | ||
| 83 | (defvar mail-hist-keep-history t | ||
| 84 | "*Non-nil means keep a history for headers and text of outgoing mail.") | ||
| 85 | |||
| 86 | ;; For handling repeated history requests | ||
| 87 | (defvar mail-hist-access-count 0) | ||
| 88 | |||
| 89 | (defvar mail-hist-last-bounds nil) | ||
| 90 | ;; (start . end) A pair indicating the buffer positions delimiting the | ||
| 91 | ;; last inserted history, so it can be replaced by a new input if the | ||
| 92 | ;; command is repeated. | ||
| 93 | |||
| 94 | (defvar mail-hist-header-regexp "^[^:]*:" | ||
| 95 | "Regular expression for matching headers in a mail message.") | ||
| 96 | |||
| 97 | (defsubst mail-hist-current-header-name () | ||
| 98 | "Get name of mail header point is currently in, without the colon. | ||
| 99 | Returns nil if not in a header, implying that point is in the body of | ||
| 100 | the message." | ||
| 101 | (if (save-excursion | ||
| 102 | (re-search-backward (concat "^" mail-header-separator) nil t)) | ||
| 103 | nil ; then we are in the body of the message | ||
| 104 | (save-excursion | ||
| 105 | (let* ((body-start ; limit possibility of false headers | ||
| 106 | (save-excursion | ||
| 107 | (re-search-forward (concat "^" mail-header-separator) nil t))) | ||
| 108 | (name-start | ||
| 109 | (re-search-backward mail-hist-header-regexp nil t)) | ||
| 110 | (name-end | ||
| 111 | (prog2 (search-forward ":" body-start t) (1- (point))))) | ||
| 112 | (and | ||
| 113 | name-start | ||
| 114 | name-end | ||
| 115 | (buffer-substring name-start name-end)))))) | ||
| 116 | |||
| 117 | (defsubst mail-hist-forward-header (count) | ||
| 118 | "Move forward COUNT headers (backward if COUNT is negative). | ||
| 119 | If last/first header is encountered first, stop there and returns | ||
| 120 | nil. | ||
| 121 | |||
| 122 | Places point on the first non-whitespace on the line following the | ||
| 123 | colon after the header name, or on the second space following that if | ||
| 124 | the header is empty." | ||
| 125 | (let ((boundary (save-excursion | ||
| 126 | (re-search-forward (concat "^" mail-header-separator) nil t)))) | ||
| 127 | (and | ||
| 128 | boundary | ||
| 129 | (let ((unstopped t)) | ||
| 130 | (setq boundary (save-excursion | ||
| 131 | (goto-char boundary) | ||
| 132 | (beginning-of-line) | ||
| 133 | (1- (point)))) | ||
| 134 | (if (> count 0) | ||
| 135 | (while (> count 0) | ||
| 136 | (setq | ||
| 137 | unstopped | ||
| 138 | (re-search-forward mail-hist-header-regexp boundary t)) | ||
| 139 | (setq count (1- count))) | ||
| 140 | ;; because the current header will match too. | ||
| 141 | (setq count (1- count)) | ||
| 142 | ;; count is negative | ||
| 143 | (while (< count 0) | ||
| 144 | (setq | ||
| 145 | unstopped | ||
| 146 | (re-search-backward mail-hist-header-regexp nil t)) | ||
| 147 | (setq count (1+ count))) | ||
| 148 | ;; we end up behind the header, so must move to the front | ||
| 149 | (re-search-forward mail-hist-header-regexp boundary t)) | ||
| 150 | ;; Now we are right after the colon | ||
| 151 | (and (looking-at "\\s-") (forward-char 1)) | ||
| 152 | ;; return nil if didn't go as far as asked, otherwise point | ||
| 153 | unstopped)))) | ||
| 154 | |||
| 155 | (defsubst mail-hist-beginning-of-header () | ||
| 156 | "Move to the start of the current header. | ||
| 157 | The start of the current header is defined as one space after the | ||
| 158 | colon, or just after the colon if it is not followed by whitespace." | ||
| 159 | ;; this is slick as all heck: | ||
| 160 | (if (mail-hist-forward-header -1) | ||
| 161 | (mail-hist-forward-header 1) | ||
| 162 | (mail-hist-forward-header 1) | ||
| 163 | (mail-hist-forward-header -1))) | ||
| 164 | |||
| 165 | (defsubst mail-hist-current-header-contents () | ||
| 166 | "Get the contents of the mail header in which point is located." | ||
| 167 | (save-excursion | ||
| 168 | (mail-hist-beginning-of-header) | ||
| 169 | (let ((start (point))) | ||
| 170 | (or (mail-hist-forward-header 1) | ||
| 171 | (re-search-forward (concat "^" mail-header-separator))) | ||
| 172 | (beginning-of-line) | ||
| 173 | (buffer-substring start (1- (point)))))) | ||
| 174 | |||
| 175 | (defsubst mail-hist-get-header-ring (header) | ||
| 176 | "Get HEADER's history ring, or nil if none. | ||
| 177 | HEADER is a string without the colon." | ||
| 178 | (cdr (assoc header mail-hist-header-ring-alist))) | ||
| 179 | |||
| 180 | (defsubst mail-hist-add-header-contents-to-ring (header &optional contents) | ||
| 181 | "Add the contents of HEADER to the header history ring. | ||
| 182 | Optional argument CONTENTS is a string which will be the contents | ||
| 183 | (instead of whatever's found in the header)." | ||
| 184 | (let ((ring (cdr (assoc header mail-hist-header-ring-alist)))) | ||
| 185 | (or ring | ||
| 186 | ;; If the ring doesn't exist, we'll have to make it and add it | ||
| 187 | ;; to the mail-header-ring-alist: | ||
| 188 | (prog1 | ||
| 189 | (setq ring (make-ring mail-hist-history-size)) | ||
| 190 | (setq mail-hist-header-ring-alist | ||
| 191 | (cons (cons header ring) mail-hist-header-ring-alist)))) | ||
| 192 | (ring-insert | ||
| 193 | ring | ||
| 194 | (or contents (mail-hist-current-header-contents))))) | ||
| 195 | |||
| 196 | ;;;###autoload | ||
| 197 | (defun mail-hist-put-headers-into-history () | ||
| 198 | "Put headers and contents of this message into mail header history. | ||
| 199 | Each header has its own independent history, as does the body of the | ||
| 200 | message. | ||
| 201 | |||
| 202 | This function normally would be called when the message is sent." | ||
| 203 | (and | ||
| 204 | mail-hist-keep-history | ||
| 205 | (progn | ||
| 206 | (goto-char (point-min)) | ||
| 207 | (while (mail-hist-forward-header 1) | ||
| 208 | (mail-hist-add-header-contents-to-ring | ||
| 209 | (mail-hist-current-header-name))) | ||
| 210 | (let ((body-contents | ||
| 211 | (save-excursion | ||
| 212 | (goto-char (point-min)) | ||
| 213 | (re-search-forward (concat "^" mail-header-separator) nil) | ||
| 214 | (forward-line 1) | ||
| 215 | (buffer-substring (point) (point-max))))) | ||
| 216 | (mail-hist-add-header-contents-to-ring "body" body-contents))))) | ||
| 217 | |||
| 218 | (defun mail-hist-previous-input (header) | ||
| 219 | "Insert the previous contents of this mail header or message body. | ||
| 220 | Moves back through the history of sent mail messages. Each header has | ||
| 221 | its own independent history, as does the body of the message. | ||
| 222 | |||
| 223 | The history only contains the contents of outgoing messages, not | ||
| 224 | received mail." | ||
| 225 | (interactive (list (or (mail-hist-current-header-name) "body"))) | ||
| 226 | (let* ((ring (cdr (assoc header mail-hist-header-ring-alist))) | ||
| 227 | (len (ring-length ring)) | ||
| 228 | (repeat (eq last-command 'mail-hist-input-access))) | ||
| 229 | (if repeat | ||
| 230 | (setq mail-hist-access-count | ||
| 231 | (ring-plus1 mail-hist-access-count len)) | ||
| 232 | (setq mail-hist-access-count 0)) | ||
| 233 | (if (null ring) | ||
| 234 | (progn | ||
| 235 | (ding) | ||
| 236 | (message "No history for \"%s\"." header)) | ||
| 237 | (if (ring-empty-p ring) | ||
| 238 | (error "\"%s\" ring is empty." header) | ||
| 239 | (and repeat | ||
| 240 | (delete-region (car mail-hist-last-bounds) | ||
| 241 | (cdr mail-hist-last-bounds))) | ||
| 242 | (let ((start (point))) | ||
| 243 | (insert (ring-ref ring mail-hist-access-count)) | ||
| 244 | (setq mail-hist-last-bounds (cons start (point))) | ||
| 245 | (setq this-command 'mail-hist-input-access)))))) | ||
| 246 | |||
| 247 | (defun mail-hist-next-input (header) | ||
| 248 | "Insert next contents of this mail header or message body. | ||
| 249 | Moves back through the history of sent mail messages. Each header has | ||
| 250 | its own independent history, as does the body of the message. | ||
| 251 | |||
| 252 | Although you can do so, it does not make much sense to call this | ||
| 253 | without having called `mail-hist-previous-header' first | ||
| 254 | (\\[mail-hist-previous-header]). | ||
| 255 | |||
| 256 | The history only contains the contents of outgoing messages, not | ||
| 257 | received mail." | ||
| 258 | (interactive (list (or (mail-hist-current-header-name) "body"))) | ||
| 259 | (let* ((ring (cdr (assoc header mail-hist-header-ring-alist))) | ||
| 260 | (len (ring-length ring)) | ||
| 261 | (repeat (eq last-command 'mail-hist-input-access))) | ||
| 262 | (if repeat | ||
| 263 | (setq mail-hist-access-count | ||
| 264 | (ring-minus1 mail-hist-access-count len)) | ||
| 265 | (setq mail-hist-access-count 0)) | ||
| 266 | (if (null ring) | ||
| 267 | (progn | ||
| 268 | (ding) | ||
| 269 | (message "No history for \"%s\"." header)) | ||
| 270 | (if (ring-empty-p ring) | ||
| 271 | (error "\"%s\" ring is empty." header) | ||
| 272 | (and repeat | ||
| 273 | (delete-region (car mail-hist-last-bounds) | ||
| 274 | (cdr mail-hist-last-bounds))) | ||
| 275 | (let ((start (point))) | ||
| 276 | (insert (ring-ref ring mail-hist-access-count)) | ||
| 277 | (setq mail-hist-last-bounds (cons start (point))) | ||
| 278 | (setq this-command 'mail-hist-input-access)))))) | ||
| 279 | |||
| 280 | (provide 'mail-hist) | ||
| 281 | |||
| 282 | ;; mail-hist.el ends here | ||