aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard M. Stallman2009-02-14 04:01:53 +0000
committerRichard M. Stallman2009-02-14 04:01:53 +0000
commit1945c7a74c82d7e41af182ffae066642d51baaab (patch)
tree25f32d76f32e531e0154da498c4be0540c08fbd5
parentf3998865b37212095326c567ffbe4368a85735a3 (diff)
downloademacs-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/ChangeLog8
-rw-r--r--lisp/mail/rmailedit.el151
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
122009-02-14 Richard M Stallman <rms@gnu.org> 122009-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.
220Each element has the form (HEADER-NAME . ENTIRE-STRING).
221ENTIRE-STRING includes the name of the header field (which is HEADER-NAME)
222and has a final newline.
223If part of the text is not valid as a header field, HEADER-NAME
224is an integer and we use consecutive integers.
225
226If WIDEN is non-nil, operate on the entire buffer.
227
228If 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.
259The value is a list of three lists, (INSERTED DELETED CHANGED).
260
261INSERTED's elements describe inserted header fields
262and each looks like (AFTER-WHAT INSERT-WHAT)
263INSERT-WHAT is the header field to insert (a member of NEW-HEADERS).
264AFTER-WHAT is the field to insert it after (a member of NEW-HEADERS)
265or else nil to insert it at the beginning.
266
267DELETED's elements are elements of OLD-HEADERS.
268CHANGED's elements have the form (OLD . NEW)
269where 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.
299HEADER-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