diff options
| author | Richard M. Stallman | 2009-02-14 04:01:53 +0000 |
|---|---|---|
| committer | Richard M. Stallman | 2009-02-14 04:01:53 +0000 |
| commit | 1945c7a74c82d7e41af182ffae066642d51baaab (patch) | |
| tree | 25f32d76f32e531e0154da498c4be0540c08fbd5 | |
| parent | f3998865b37212095326c567ffbe4368a85735a3 (diff) | |
| download | emacs-1945c7a74c82d7e41af182ffae066642d51baaab.tar.gz emacs-1945c7a74c82d7e41af182ffae066642d51baaab.zip | |
Handle editing of header fields.
(rmail-old-headers): New variable.
(rmail-edit-current-message): Set it, recording current headers.
(rmail-cease-edit): Compute new headers and diff against old ones.
Update the mbox buffer with the changes that were made.
(rmail-edit-headers-alist): New function.
(rmail-edit-diff-headers, rmail-edit-update-headers): New functions.
| -rw-r--r-- | lisp/ChangeLog | 8 | ||||
| -rw-r--r-- | lisp/mail/rmailedit.el | 151 |
2 files changed, 153 insertions, 6 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog index c045edb54ff..ab95e57abaf 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog | |||
| @@ -11,6 +11,14 @@ | |||
| 11 | 11 | ||
| 12 | 2009-02-14 Richard M Stallman <rms@gnu.org> | 12 | 2009-02-14 Richard M Stallman <rms@gnu.org> |
| 13 | 13 | ||
| 14 | * mail/rmailedit.el: Handle editing of header fields. | ||
| 15 | (rmail-old-headers): New variable. | ||
| 16 | (rmail-edit-current-message): Set it, recording current headers. | ||
| 17 | (rmail-cease-edit): Compute new headers and diff against old ones. | ||
| 18 | Update the mbox buffer with the changes that were made. | ||
| 19 | (rmail-edit-headers-alist): New function. | ||
| 20 | (rmail-edit-diff-headers, rmail-edit-update-headers): New functions. | ||
| 21 | |||
| 14 | * mail/rmailout.el (rmail-output-body-to-file): Avoid space and colon | 22 | * mail/rmailout.el (rmail-output-body-to-file): Avoid space and colon |
| 15 | in default file name. | 23 | in default file name. |
| 16 | 24 | ||
diff --git a/lisp/mail/rmailedit.el b/lisp/mail/rmailedit.el index cffd38ee59c..40d5f024894 100644 --- a/lisp/mail/rmailedit.el +++ b/lisp/mail/rmailedit.el | |||
| @@ -69,25 +69,33 @@ This functions runs the normal hook `rmail-edit-mode-hook'. | |||
| 69 | 69 | ||
| 70 | ;; Rmail Edit mode is suitable only for specially formatted data. | 70 | ;; Rmail Edit mode is suitable only for specially formatted data. |
| 71 | (put 'rmail-edit-mode 'mode-class 'special) | 71 | (put 'rmail-edit-mode 'mode-class 'special) |
| 72 | 72 | ||
| 73 | 73 | ||
| 74 | (defvar rmail-old-text) | 74 | (defvar rmail-old-text) |
| 75 | (defvar rmail-old-pruned nil | 75 | (defvar rmail-old-pruned nil |
| 76 | "Non-nil means the message being edited originally had pruned headers.") | 76 | "Non-nil means the message being edited originally had pruned headers.") |
| 77 | (put 'rmail-old-pruned 'permanent-local t) | 77 | (put 'rmail-old-pruned 'permanent-local t) |
| 78 | 78 | ||
| 79 | (defvar rmail-old-headers nil | ||
| 80 | "Holds the headers of this message before editing started.") | ||
| 81 | (put 'rmail-old-headers 'permanent-local t) | ||
| 82 | |||
| 79 | ;;;###autoload | 83 | ;;;###autoload |
| 80 | (defun rmail-edit-current-message () | 84 | (defun rmail-edit-current-message () |
| 81 | "Edit the contents of this message." | 85 | "Edit the contents of this message." |
| 82 | (interactive) | 86 | (interactive) |
| 83 | (if (zerop rmail-total-messages) | 87 | (if (zerop rmail-total-messages) |
| 84 | (error "No messages in this buffer")) | 88 | (error "No messages in this buffer")) |
| 85 | (set (make-local-variable 'rmail-old-pruned) (rmail-msg-is-pruned)) | 89 | (make-local-variable 'rmail-old-pruned) |
| 90 | (setq rmail-old-pruned (rmail-msg-is-pruned)) | ||
| 86 | (rmail-edit-mode) | 91 | (rmail-edit-mode) |
| 87 | (set (make-local-variable 'rmail-old-text) | 92 | (make-local-variable 'rmail-old-text) |
| 88 | (save-restriction | 93 | (setq rmail-old-text |
| 89 | (widen) | 94 | (save-restriction |
| 90 | (buffer-substring (point-min) (point-max)))) | 95 | (widen) |
| 96 | (buffer-substring (point-min) (point-max)))) | ||
| 97 | (make-local-variable 'rmail-old-headers) | ||
| 98 | (setq rmail-old-headers (rmail-edit-headers-alist t)) | ||
| 91 | (setq buffer-read-only nil) | 99 | (setq buffer-read-only nil) |
| 92 | (setq buffer-undo-list nil) | 100 | (setq buffer-undo-list nil) |
| 93 | ;; FIXME whether the buffer is initially marked as modified or not | 101 | ;; FIXME whether the buffer is initially marked as modified or not |
| @@ -128,6 +136,7 @@ This functions runs the normal hook `rmail-edit-mode-hook'. | |||
| 128 | (insert "\n"))) | 136 | (insert "\n"))) |
| 129 | (let ((old rmail-old-text) | 137 | (let ((old rmail-old-text) |
| 130 | (pruned rmail-old-pruned) | 138 | (pruned rmail-old-pruned) |
| 139 | new-headers | ||
| 131 | character-coding is-text-message coding-system | 140 | character-coding is-text-message coding-system |
| 132 | headers-end limit) | 141 | headers-end limit) |
| 133 | ;; Go back to Rmail mode, but carefully. | 142 | ;; Go back to Rmail mode, but carefully. |
| @@ -147,6 +156,7 @@ This functions runs the normal hook `rmail-edit-mode-hook'. | |||
| 147 | (goto-char (point-min)) | 156 | (goto-char (point-min)) |
| 148 | (search-forward "\n\n") | 157 | (search-forward "\n\n") |
| 149 | (setq headers-end (point)) | 158 | (setq headers-end (point)) |
| 159 | (setq new-headers (rmail-edit-headers-alist t)) | ||
| 150 | (rmail-swap-buffers-maybe) | 160 | (rmail-swap-buffers-maybe) |
| 151 | (narrow-to-region (rmail-msgbeg rmail-current-message) | 161 | (narrow-to-region (rmail-msgbeg rmail-current-message) |
| 152 | (rmail-msgend rmail-current-message)) | 162 | (rmail-msgend rmail-current-message)) |
| @@ -174,6 +184,11 @@ This functions runs the normal hook `rmail-edit-mode-hook'. | |||
| 174 | data-buffer)) | 184 | data-buffer)) |
| 175 | (delete-region end (point-max))) | 185 | (delete-region end (point-max))) |
| 176 | 186 | ||
| 187 | ;; Apply to the mbox buffer any changes in header fields | ||
| 188 | ;; that the user made while editing in the view buffer. | ||
| 189 | (rmail-edit-update-headers (rmail-edit-diff-headers | ||
| 190 | rmail-old-headers new-headers)) | ||
| 191 | |||
| 177 | ;; Re-apply content-transfer-encoding, if any, on the message body. | 192 | ;; Re-apply content-transfer-encoding, if any, on the message body. |
| 178 | (cond | 193 | (cond |
| 179 | ((string= character-coding "quoted-printable") | 194 | ((string= character-coding "quoted-printable") |
| @@ -199,6 +214,130 @@ This functions runs the normal hook `rmail-edit-mode-hook'. | |||
| 199 | (insert rmail-old-text) | 214 | (insert rmail-old-text) |
| 200 | (rmail-cease-edit) | 215 | (rmail-cease-edit) |
| 201 | (rmail-highlight-headers)) | 216 | (rmail-highlight-headers)) |
| 217 | |||
| 218 | (defun rmail-edit-headers-alist (&optional widen markers) | ||
| 219 | "Return an alist of the headers of the message in the current buffer. | ||
| 220 | Each element has the form (HEADER-NAME . ENTIRE-STRING). | ||
| 221 | ENTIRE-STRING includes the name of the header field (which is HEADER-NAME) | ||
| 222 | and has a final newline. | ||
| 223 | If part of the text is not valid as a header field, HEADER-NAME | ||
| 224 | is an integer and we use consecutive integers. | ||
| 225 | |||
| 226 | If WIDEN is non-nil, operate on the entire buffer. | ||
| 227 | |||
| 228 | If MARKERS is non-nil, the value looks like | ||
| 229 | \(HEADER-NAME ENTIRE-STRING BEG-MARKER END-MARKER)." | ||
| 230 | (let (header-alist (no-good-header-count 1)) | ||
| 231 | (save-excursion | ||
| 232 | (save-restriction | ||
| 233 | (if widen (widen)) | ||
| 234 | (goto-char (point-min)) | ||
| 235 | (search-forward "\n\n") | ||
| 236 | (narrow-to-region (point-min) (1- (point))) | ||
| 237 | (goto-char (point-min)) | ||
| 238 | (while (not (eobp)) | ||
| 239 | (let ((start (point)) | ||
| 240 | name header) | ||
| 241 | ;; Match the name. | ||
| 242 | (if (looking-at "[ \t]*\\([^:\n \t]\\(\\|[^:\n]*[^:\n \t]\\)\\)[ \t]*:") | ||
| 243 | (setq name (match-string-no-properties 1)) | ||
| 244 | (setq name no-good-header-count | ||
| 245 | no-good-header-count (1+ no-good-header-count))) | ||
| 246 | (forward-line 1) | ||
| 247 | (while (looking-at "[ \t]") | ||
| 248 | (forward-line 1)) | ||
| 249 | (setq header (buffer-substring-no-properties start (point))) | ||
| 250 | (if markers | ||
| 251 | (push (list header (copy-marker start) (point-marker)) | ||
| 252 | header-alist) | ||
| 253 | (push (cons name header) header-alist)))))) | ||
| 254 | (nreverse header-alist))) | ||
| 255 | |||
| 256 | |||
| 257 | (defun rmail-edit-diff-headers (old-headers new-headers) | ||
| 258 | "Compare OLD-HEADERS and NEW-HEADERS and return field differences. | ||
| 259 | The value is a list of three lists, (INSERTED DELETED CHANGED). | ||
| 260 | |||
| 261 | INSERTED's elements describe inserted header fields | ||
| 262 | and each looks like (AFTER-WHAT INSERT-WHAT) | ||
| 263 | INSERT-WHAT is the header field to insert (a member of NEW-HEADERS). | ||
| 264 | AFTER-WHAT is the field to insert it after (a member of NEW-HEADERS) | ||
| 265 | or else nil to insert it at the beginning. | ||
| 266 | |||
| 267 | DELETED's elements are elements of OLD-HEADERS. | ||
| 268 | CHANGED's elements have the form (OLD . NEW) | ||
| 269 | where OLD is a element of OLD-HEADERS and NEW is an element of NEW-HEADERS." | ||
| 270 | |||
| 271 | (let ((reverse-new (reverse new-headers)) | ||
| 272 | inserted deleted changed) | ||
| 273 | (dolist (old old-headers) | ||
| 274 | (let ((new (assoc (car old) new-headers))) | ||
| 275 | ;; If it's in OLD-HEADERS and has no new counterpart, | ||
| 276 | ;; it is a deletion. | ||
| 277 | (if (null new) | ||
| 278 | (push old deleted) | ||
| 279 | ;; If it has a new counterpart, maybe it was changed. | ||
| 280 | (unless (equal (cdr old) (cdr new)) | ||
| 281 | (push (cons old new) changed)) | ||
| 282 | ;; Remove the new counterpart, since it has been spoken for. | ||
| 283 | (setq new-headers (remq new new-headers))))) | ||
| 284 | ;; Look at the new headers with no old counterpart. | ||
| 285 | (dolist (new new-headers) | ||
| 286 | (let ((prev (cadr (member new reverse-new)))) | ||
| 287 | ;; Mark each one as an insertion. Show the previous new header. | ||
| 288 | (unless old | ||
| 289 | (push (list prev new) inserted)))) | ||
| 290 | ;; It is crucial to return the insertions in buffer order | ||
| 291 | ;; so that `rmail-edit-update-headers' can insert a field | ||
| 292 | ;; after a new field. | ||
| 293 | (list (nreverse inserted) | ||
| 294 | (nreverse deleted) | ||
| 295 | (nreverse changed)))) | ||
| 296 | |||
| 297 | (defun rmail-edit-update-headers (header-diff) | ||
| 298 | "Edit the mail headers in the buffer based on HEADER-DIFF. | ||
| 299 | HEADER-DIFF should be a return value from `rmail-edit-diff-headers'." | ||
| 300 | (let ((buf-headers (rmail-edit-headers-alist nil t))) | ||
| 301 | ;; Change all the fields scheduled for being changed. | ||
| 302 | (dolist (chg (nth 2 header-diff)) | ||
| 303 | (let* ((match (assoc (cdar chg) buf-headers)) | ||
| 304 | (end (marker-position (nth 2 match)))) | ||
| 305 | (goto-char end) | ||
| 306 | ;; Insert the new, then delete the old. | ||
| 307 | ;; That avoids collapsing markers. | ||
| 308 | (insert-before-markers (cddr chg)) | ||
| 309 | (delete-region (nth 1 match) end) | ||
| 310 | ;; Remove the old field from BUF-HEADERS. | ||
| 311 | (setq buf-headers (delq match buf-headers)) | ||
| 312 | ;; Update BUF-HEADERS to show the changed field. | ||
| 313 | (push (list (cddr chg) (point-marker) | ||
| 314 | (copy-marker (- (point) (length (cddr chg)))) | ||
| 315 | (point-marker)) | ||
| 316 | buf-headers))) | ||
| 317 | ;; Delete all the fields scheduled for deletion. | ||
| 318 | ;; We do deletion after changes | ||
| 319 | ;; because when two fields look alike and get replaced by one, | ||
| 320 | ;; the first of them is considered changed | ||
| 321 | ;; and the second is considered deleted. | ||
| 322 | (dolist (del (nth 1 header-diff)) | ||
| 323 | (let ((match (assoc (cdr del) buf-headers))) | ||
| 324 | (delete-region (nth 1 match) (nth 2 match)))) | ||
| 325 | ;; Insert all the fields scheduled for insertion. | ||
| 326 | (dolist (ins (nth 0 header-diff)) | ||
| 327 | (let* ((new (cadr ins)) | ||
| 328 | (after (car ins)) | ||
| 329 | (match (assoc (cdr after) buf-headers))) | ||
| 330 | (goto-char (if match (nth 2 match) (point-min))) | ||
| 331 | (insert (cdr new)) | ||
| 332 | ;; Update BUF-HEADERS to show the inserted field. | ||
| 333 | (push (list (cdr new) | ||
| 334 | (copy-marker (- (point) (length (cdr new)))) | ||
| 335 | (point-marker)) | ||
| 336 | buf-headers))) | ||
| 337 | ;; Disconnect the markers | ||
| 338 | (dolist (hdr buf-headers) | ||
| 339 | (set-marker (nth 1 hdr) nil) | ||
| 340 | (set-marker (nth 2 hdr) nil)))) | ||
| 202 | 341 | ||
| 203 | (provide 'rmailedit) | 342 | (provide 'rmailedit) |
| 204 | 343 | ||