* lisp/vc/log-edit.el: Fix highlighting of summary when it's the first line.
[bpt/emacs.git] / lisp / vc / log-edit.el
CommitLineData
ba83908c 1;;; log-edit.el --- Major mode for editing CVS commit messages -*- lexical-binding: t -*-
5b467bf4 2
ba318903 3;; Copyright (C) 1999-2014 Free Software Foundation, Inc.
5b467bf4 4
cc1eecfd 5;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
9766adfb 6;; Keywords: pcl-cvs cvs commit log vc
5b467bf4
SM
7
8;; This file is part of GNU Emacs.
9
eb3fa2cf 10;; GNU Emacs is free software: you can redistribute it and/or modify
5b467bf4 11;; it under the terms of the GNU General Public License as published by
eb3fa2cf
GM
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
5b467bf4
SM
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
eb3fa2cf 21;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
5b467bf4
SM
22
23;;; Commentary:
24
25;; Todo:
26
5b467bf4
SM
27;; - Move in VC's code
28;; - Add compatibility for VC's hook variables
5b467bf4
SM
29
30;;; Code:
31
5b467bf4
SM
32(require 'add-log) ; for all the ChangeLog goodies
33(require 'pcvs-util)
34(require 'ring)
52789f7f 35(require 'message)
5b467bf4 36
f1180544 37;;;;
5b467bf4 38;;;; Global Variables
f1180544 39;;;;
5b467bf4
SM
40
41(defgroup log-edit nil
bc35d341 42 "Major mode for editing RCS and CVS commit messages."
5b467bf4 43 :group 'pcl-cvs
bc35d341
DL
44 :group 'vc ; It's used by VC.
45 :version "21.1"
5b467bf4
SM
46 :prefix "log-edit-")
47
48;; compiler pacifiers
49(defvar cvs-buffer)
50
12dd83de
SM
51\f
52;; The main keymap
53
5b467bf4
SM
54(easy-mmode-defmap log-edit-mode-map
55 `(("\C-c\C-c" . log-edit-done)
56 ("\C-c\C-a" . log-edit-insert-changelog)
93a142e1 57 ("\C-c\C-d" . log-edit-show-diff)
5b467bf4 58 ("\C-c\C-f" . log-edit-show-files)
d9dfe8ca 59 ("\C-c\C-k" . log-edit-kill-buffer)
52789f7f 60 ("\C-a" . log-edit-beginning-of-line)
9fe89a26
SM
61 ("\M-n" . log-edit-next-comment)
62 ("\M-p" . log-edit-previous-comment)
63 ("\M-r" . log-edit-comment-search-backward)
64 ("\M-s" . log-edit-comment-search-forward)
0916e956 65 ("\C-c?" . log-edit-mode-help))
cdf54749 66 "Keymap for the `log-edit-mode' (to edit version control log messages)."
0916e956
SM
67 :group 'log-edit)
68
69;; Compatibility with old names. Should we bother ?
70(defvar vc-log-mode-map log-edit-mode-map)
71(defvar vc-log-entry-mode vc-log-mode-map)
5b467bf4 72
d247e32d
SM
73(easy-menu-define log-edit-menu log-edit-mode-map
74 "Menu used for `log-edit-mode'."
75 '("Log-Edit"
76 ["Done" log-edit-done
77 :help "Exit log-edit and proceed with the actual action."]
78 "--"
799224fe
DN
79 ["Insert ChangeLog" log-edit-insert-changelog
80 :help "Insert a log message by looking at the ChangeLog"]
81 ["Add to ChangeLog" log-edit-add-to-changelog
82 :help "Insert this log message into the appropriate ChangeLog file"]
d247e32d 83 "--"
93a142e1
DN
84 ["Show diff" log-edit-show-diff
85 :help "Show the diff for the files to be committed."]
d247e32d
SM
86 ["List files" log-edit-show-files
87 :help "Show the list of relevant files."]
88 "--"
799224fe
DN
89 ["Previous comment" log-edit-previous-comment
90 :help "Cycle backwards through comment history"]
91 ["Next comment" log-edit-next-comment
92 :help "Cycle forwards through comment history."]
93 ["Search comment forward" log-edit-comment-search-forward
94 :help "Search forwards through comment history for a substring match of str"]
95 ["Search comment backward" log-edit-comment-search-backward
96 :help "Search backwards through comment history for substring match of str"]))
d247e32d 97
f077c462 98(defcustom log-edit-confirm 'changed
9201cc28 99 "If non-nil, `log-edit-done' will request confirmation.
5b467bf4
SM
100If 'changed, only request confirmation if the list of files has
101 changed since the beginning of the log-edit session."
102 :group 'log-edit
103 :type '(choice (const changed) (const t) (const nil)))
104
105(defcustom log-edit-keep-buffer nil
9201cc28 106 "If non-nil, don't hide the buffer after `log-edit-done'."
5b467bf4
SM
107 :group 'log-edit
108 :type 'boolean)
109
0c765e5f 110(defcustom log-edit-require-final-newline t
9201cc28 111 "Enforce a newline at the end of commit log messages.
5b467bf4
SM
112Enforce it silently if t, query if non-nil and don't do anything if nil."
113 :group 'log-edit
114 :type '(choice (const ask) (const t) (const nil)))
115
116(defcustom log-edit-setup-invert nil
9201cc28 117 "Non-nil means `log-edit' should invert the meaning of its SETUP arg.
5b467bf4
SM
118If SETUP is 'force, this variable has no effect."
119 :group 'log-edit
120 :type 'boolean)
121
52789f7f 122(defcustom log-edit-setup-add-author nil
bb098075
GM
123 "Non-nil means `log-edit' may add the `Author:' header.
124This applies when its SETUP argument is non-nil."
125 :version "24.4"
52789f7f
DG
126 :group 'log-edit
127 :type 'boolean
128 :safe 'booleanp)
129
0cda6b7b
JL
130(defcustom log-edit-hook '(log-edit-insert-message-template
131 log-edit-insert-cvs-template
132 log-edit-insert-changelog
133 log-edit-show-files)
9201cc28 134 "Hook run at the end of `log-edit'."
5b467bf4 135 :group 'log-edit
0cda6b7b
JL
136 :type '(hook :options (log-edit-insert-message-template
137 log-edit-insert-cvs-rcstemplate
138 log-edit-insert-cvs-template
139 log-edit-insert-changelog
140 log-edit-insert-filenames
3d6e95e7 141 log-edit-insert-filenames-without-changelog
0cda6b7b 142 log-edit-show-files)))
5b467bf4 143
3831af62 144(defcustom log-edit-mode-hook (if (boundp 'vc-log-mode-hook) vc-log-mode-hook)
9201cc28 145 "Hook run when entering `log-edit-mode'."
5b467bf4
SM
146 :group 'log-edit
147 :type 'hook)
148
149(defcustom log-edit-done-hook nil
9201cc28 150 "Hook run before doing the actual commit.
5b467bf4
SM
151This hook can be used to cleanup the message, enforce various
152conventions, or to allow recording the message in some other database,
153such as a bug-tracking system. The list of files about to be committed
154can be obtained from `log-edit-files'."
155 :group 'log-edit
43a9a0c4 156 :type '(hook :options (log-edit-set-common-indentation
5b467bf4
SM
157 log-edit-add-to-changelog)))
158
bef4957b 159(defcustom log-edit-strip-single-file-name nil
31764e15 160 "If non-nil, remove file name from single-file log entries."
c6265c10
GM
161 :type 'boolean
162 :safe 'booleanp
163 :group 'log-edit
bef4957b 164 :version "24.1")
31764e15 165
0c765e5f 166(defvar log-edit-changelog-full-paragraphs t
fb7ada5f 167 "If non-nil, include full ChangeLog paragraphs in the log.
5b467bf4
SM
168This may be set in the ``local variables'' section of a ChangeLog, to
169indicate the policy for that ChangeLog.
170
171A ChangeLog paragraph is a bunch of log text containing no blank lines;
172a paragraph usually describes a set of changes with a single purpose,
173but perhaps spanning several functions in several files. Changes in
174different paragraphs are unrelated.
175
14188021 176You could argue that the log entry for a file should contain the
5b467bf4
SM
177full ChangeLog paragraph mentioning the change to the file, even though
178it may mention other files, because that gives you the full context you
5ab405e4 179need to understand the change. This is the behavior you get when this
5b467bf4
SM
180variable is set to t.
181
14188021 182On the other hand, you could argue that the log entry for a change
5b467bf4 183should contain only the text for the changes which occurred in that
5ab405e4 184file, because the log is per-file. This is the behavior you get
5b467bf4
SM
185when this variable is set to nil.")
186
187;;;; Internal global or buffer-local vars
188
189(defconst log-edit-files-buf "*log-edit-files*")
190(defvar log-edit-initial-files nil)
191(defvar log-edit-callback nil)
93a142e1 192(defvar log-edit-diff-function nil)
5b467bf4 193(defvar log-edit-listfun nil)
495b517c 194
cdf54749 195(defvar log-edit-parent-buffer nil)
5b467bf4 196
9af57756
CY
197(defvar log-edit-vc-backend nil
198 "VC fileset corresponding to the current log.")
199
9fe89a26 200;;; Originally taken from VC-Log mode
12dd83de 201
9fe89a26 202(defconst log-edit-maximum-comment-ring-size 32
12dd83de 203 "Maximum number of saved comments in the comment ring.")
e5bd0a28 204(define-obsolete-variable-alias 'vc-comment-ring 'log-edit-comment-ring "22.1")
9fe89a26 205(defvar log-edit-comment-ring (make-ring log-edit-maximum-comment-ring-size))
e5bd0a28
SM
206(define-obsolete-variable-alias 'vc-comment-ring-index
207 'log-edit-comment-ring-index "22.1")
9fe89a26
SM
208(defvar log-edit-comment-ring-index nil)
209(defvar log-edit-last-comment-match "")
12dd83de 210
9fe89a26 211(defun log-edit-new-comment-index (stride len)
12dd83de 212 "Return the comment index STRIDE elements from the current one.
9fe89a26 213LEN is the length of `log-edit-comment-ring'."
12dd83de 214 (mod (cond
9fe89a26 215 (log-edit-comment-ring-index (+ log-edit-comment-ring-index stride))
12dd83de
SM
216 ;; Initialize the index on the first use of this command
217 ;; so that the first M-p gets index 0, and the first M-n gets
218 ;; index -1.
219 ((> stride 0) (1- stride))
220 (t stride))
221 len))
222
9fe89a26 223(defun log-edit-previous-comment (arg)
12dd83de
SM
224 "Cycle backwards through comment history.
225With a numeric prefix ARG, go back ARG comments."
226 (interactive "*p")
9fe89a26 227 (let ((len (ring-length log-edit-comment-ring)))
12dd83de
SM
228 (if (<= len 0)
229 (progn (message "Empty comment ring") (ding))
0916e956
SM
230 ;; Don't use `erase-buffer' because we don't want to `widen'.
231 (delete-region (point-min) (point-max))
9fe89a26
SM
232 (setq log-edit-comment-ring-index (log-edit-new-comment-index arg len))
233 (message "Comment %d" (1+ log-edit-comment-ring-index))
234 (insert (ring-ref log-edit-comment-ring log-edit-comment-ring-index)))))
12dd83de 235
9fe89a26 236(defun log-edit-next-comment (arg)
12dd83de
SM
237 "Cycle forwards through comment history.
238With a numeric prefix ARG, go forward ARG comments."
239 (interactive "*p")
9fe89a26 240 (log-edit-previous-comment (- arg)))
12dd83de 241
9fe89a26 242(defun log-edit-comment-search-backward (str &optional stride)
12dd83de
SM
243 "Search backwards through comment history for substring match of STR.
244If the optional argument STRIDE is present, that is a step-width to use
245when going through the comment ring."
246 ;; Why substring rather than regexp ? -sm
247 (interactive
9fe89a26 248 (list (read-string "Comment substring: " nil nil log-edit-last-comment-match)))
12dd83de
SM
249 (unless stride (setq stride 1))
250 (if (string= str "")
9fe89a26
SM
251 (setq str log-edit-last-comment-match)
252 (setq log-edit-last-comment-match str))
12dd83de 253 (let* ((str (regexp-quote str))
9fe89a26
SM
254 (len (ring-length log-edit-comment-ring))
255 (n (log-edit-new-comment-index stride len)))
12dd83de 256 (while (progn (when (or (>= n len) (< n 0)) (error "Not found"))
9fe89a26 257 (not (string-match str (ring-ref log-edit-comment-ring n))))
12dd83de 258 (setq n (+ n stride)))
9fe89a26
SM
259 (setq log-edit-comment-ring-index n)
260 (log-edit-previous-comment 0)))
12dd83de 261
9fe89a26 262(defun log-edit-comment-search-forward (str)
12dd83de
SM
263 "Search forwards through comment history for a substring match of STR."
264 (interactive
9fe89a26
SM
265 (list (read-string "Comment substring: " nil nil log-edit-last-comment-match)))
266 (log-edit-comment-search-backward str -1))
12dd83de 267
9fe89a26 268(defun log-edit-comment-to-change-log (&optional whoami file-name)
12dd83de
SM
269 "Enter last VC comment into the change log for the current file.
270WHOAMI (interactive prefix) non-nil means prompt for user name
271and site. FILE-NAME is the name of the change log; if nil, use
272`change-log-default-name'.
273
9fe89a26 274This may be useful as a `log-edit-checkin-hook' to update change logs
12dd83de
SM
275automatically."
276 (interactive (if current-prefix-arg
277 (list current-prefix-arg
278 (prompt-for-change-log-name))))
12dd83de 279 (let (;; Extract the comment first so we get any error before doing anything.
9fe89a26 280 (comment (ring-ref log-edit-comment-ring 0))
12dd83de
SM
281 ;; Don't let add-change-log-entry insert a defun name.
282 (add-log-current-defun-function 'ignore)
283 end)
284 ;; Call add-log to do half the work.
285 (add-change-log-entry whoami file-name t t)
286 ;; Insert the VC comment, leaving point before it.
287 (setq end (save-excursion (insert comment) (point-marker)))
288 (if (looking-at "\\s *\\s(")
289 ;; It starts with an open-paren, as in "(foo): Frobbed."
290 ;; So remove the ": " add-log inserted.
291 (delete-char -2))
292 ;; Canonicalize the white space between the file name and comment.
293 (just-one-space)
294 ;; Indent rest of the text the same way add-log indented the first line.
295 (let ((indentation (current-indentation)))
296 (save-excursion
297 (while (< (point) end)
298 (forward-line 1)
299 (indent-to indentation))
300 (setq end (point))))
301 ;; Fill the inserted text, preserving open-parens at bol.
0916e956 302 (let ((paragraph-start (concat paragraph-start "\\|\\s *\\s(")))
12dd83de
SM
303 (beginning-of-line)
304 (fill-region (point) end))
305 ;; Canonicalize the white space at the end of the entry so it is
306 ;; separated from the next entry by a single blank line.
307 (skip-syntax-forward " " end)
308 (delete-char (- (skip-syntax-backward " ")))
309 (or (eobp) (looking-at "\n\n")
310 (insert "\n"))))
311
9fe89a26 312;; Compatibility with old names.
f7eeab0d
SM
313(define-obsolete-function-alias 'vc-previous-comment 'log-edit-previous-comment "22.1")
314(define-obsolete-function-alias 'vc-next-comment 'log-edit-next-comment "22.1")
315(define-obsolete-function-alias 'vc-comment-search-reverse 'log-edit-comment-search-backward "22.1")
316(define-obsolete-function-alias 'vc-comment-search-forward 'log-edit-comment-search-forward "22.1")
317(define-obsolete-function-alias 'vc-comment-to-change-log 'log-edit-comment-to-change-log "22.1")
9fe89a26 318
cdf54749
SM
319;;;
320;;; Actual code
321;;;
5b467bf4 322
e97a42c1
SM
323(defface log-edit-summary '((t :inherit font-lock-function-name-face))
324 "Face for the summary in `log-edit-mode' buffers.")
325
326(defface log-edit-header '((t :inherit font-lock-keyword-face))
327 "Face for the headers in `log-edit-mode' buffers.")
328
329(defface log-edit-unknown-header '((t :inherit font-lock-comment-face))
330 "Face for unknown headers in `log-edit-mode' buffers.")
331
332(defvar log-edit-headers-alist '(("Summary" . log-edit-summary)
333 ("Fixes") ("Author"))
334 "AList of known headers and the face to use to highlight them.")
335
336(defconst log-edit-header-contents-regexp
b83a2ddd
GM
337 "[ \t]*\\(.*\\(\n[ \t].*\\)*\\)\n?"
338 "Regular expression matching a header field.
339The first subexpression is the actual text of the field.")
e97a42c1 340
ba83908c 341(defun log-edit-match-to-eoh (_limit)
e97a42c1
SM
342 ;; FIXME: copied from message-match-to-eoh.
343 (let ((start (point)))
344 (rfc822-goto-eoh)
345 ;; Typical situation: some temporary change causes the header to be
346 ;; incorrect, so EOH comes earlier than intended: the last lines of the
347 ;; intended headers are now not considered part of the header any more,
348 ;; so they don't have the multiline property set. When the change is
349 ;; completed and the header has its correct shape again, the lack of the
350 ;; multiline property means we won't rehighlight the last lines of
351 ;; the header.
352 (if (< (point) start)
353 nil ;No header within start..limit.
354 ;; Here we disregard LIMIT so that we may extend the area again.
355 (set-match-data (list start (point)))
356 (point))))
357
c055d654
SM
358(defun log-edit--match-first-line (limit)
359 (let ((start (point)))
360 (rfc822-goto-eoh)
361 (skip-chars-forward "\n")
362 (and (< start (line-end-position))
363 (< (point) limit)
364 (save-excursion
365 (not (re-search-backward "^Summary:[ \t]*[^ \t\n]" nil t)))
366 (looking-at ".+")
367 (progn
368 (goto-char (match-end 0))
369 (put-text-property (point-min) (point)
370 'jit-lock-defer-multiline t)
371 (point)))))
372
63fbe552 373(defvar log-edit-font-lock-keywords
e97a42c1
SM
374 ;; Copied/inspired by message-font-lock-keywords.
375 `((log-edit-match-to-eoh
9f7b98f8 376 (,(concat "^\\(\\([[:alpha:]-]+\\):\\)" log-edit-header-contents-regexp)
e97a42c1 377 (progn (goto-char (match-beginning 0)) (match-end 0)) nil
02661b3a 378 (1 (if (assoc-string (match-string 2) log-edit-headers-alist t)
e97a42c1
SM
379 'log-edit-header
380 'log-edit-unknown-header)
381 nil lax)
402c8a49 382 ;; From `log-edit-header-contents-regexp':
02661b3a 383 (3 (or (cdr (assoc-string (match-string 2) log-edit-headers-alist t))
e97a42c1 384 'log-edit-header)
02661b3a
SM
385 nil lax))
386 ("^\n"
387 (progn (goto-char (match-end 0)) (1+ (match-end 0))) nil
c055d654
SM
388 (0 '(:height 0.1 :inverse-video t))))
389 (log-edit--match-first-line (0 'log-edit-summary))))
165a7fbe 390
49ed9c8e
SM
391(defvar log-edit-font-lock-gnu-style nil
392 "If non-nil, highlight common failures to follow the GNU coding standards.")
393(put 'log-edit-font-lock-gnu-style 'safe-local-variable 'booleanp)
394
395(defconst log-edit-font-lock-gnu-keywords
396 ;; Use
397 ;; * foo.el (bla, bli)
398 ;; (blo, blu): Toto.
399 ;; Rather than
400 ;; * foo.el (bla, bli,
401 ;; blo, blu): Toto.
402 '(("^[ \t]*\\(?:\\* .*\\)?\\(([^\n)]*,\\s-*\\)$"
403 (1 '(face font-lock-warning-face
404 help-echo "Continue function lists with \")\\n(\".") t))
405 ;; Don't leave a lone word on a single line.
406 ;;("^\\s-*\\(\\S-*[^\n:)]\\)\\s-*$" (1 font-lock-warning-face t))
407 ;; Don't cut a sentence right after the first word (better to move
408 ;; the sentence on the next line, then).
409 ;;("[.:]\\s-+\\(\\sw+\\)\\s-*$" (1 font-lock-warning-face t))
410 ;; Change Log entries should use present tense.
411 ("):[ \t\n]*[[:alpha:]]+\\(ed\\)\\>"
412 (1 '(face font-lock-warning-face help-echo "Use present tense.") t))
413 ;; Change log entries start with a capital letter.
414 ("): [a-z]" (0 '(face font-lock-warning-face help-echo "Capitalize.") t))
415 ("[^[:upper:]]\\(\\. [[:upper:]]\\)"
416 (1 '(face font-lock-warning-face
417 help-echo "Use two spaces to end a sentence") t))
418 ("^("
419 (0 (let ((beg (max (point-min) (- (match-beginning 0) 2))))
420 (put-text-property beg (match-end 0) 'font-lock-multiline t)
421 (if (eq (char-syntax (char-after beg)) ?w)
422 '(face font-lock-warning-face
423 help-echo "Punctuate previous line.")))
424 t))
425 ))
426
427(defun log-edit-font-lock-keywords ()
428 (if log-edit-font-lock-gnu-style
429 (append log-edit-font-lock-keywords
430 log-edit-font-lock-gnu-keywords)
431 log-edit-font-lock-keywords))
432
5b467bf4 433;;;###autoload
ba83908c 434(defun log-edit (callback &optional setup params buffer mode &rest _ignore)
5b467bf4 435 "Setup a buffer to enter a log message.
9af57756
CY
436The buffer is put in mode MODE or `log-edit-mode' if MODE is nil.
437\\<log-edit-mode-map>
438If SETUP is non-nil, erase the buffer and run `log-edit-hook'.
439Set mark and point around the entire contents of the buffer, so
440that it is easy to kill the contents of the buffer with
441\\[kill-region]. Once the user is done editing the message,
442invoking the command \\[log-edit-done] (`log-edit-done') will
443call CALLBACK to do the actual commit.
444
445PARAMS if non-nil is an alist of variables and buffer-local
446values to give them in the Log Edit buffer. Possible keys and
447associated values:
2507310c
TTN
448 `log-edit-listfun' -- function taking no arguments that returns the list of
449 files that are concerned by the current operation (using relative names);
450 `log-edit-diff-function' -- function taking no arguments that
451 displays a diff of the files concerned by the current operation.
9af57756 452 `vc-log-fileset' -- the VC fileset to be committed (if any).
2507310c 453
9af57756
CY
454If BUFFER is non-nil `log-edit' will jump to that buffer, use it
455to edit the log message and go back to the current buffer when
456done. Otherwise, it uses the current buffer."
cdf54749
SM
457 (let ((parent (current-buffer)))
458 (if buffer (pop-to-buffer buffer))
459 (when (and log-edit-setup-invert (not (eq setup 'force)))
460 (setq setup (not setup)))
09158997
DN
461 (if mode
462 (funcall mode)
463 (log-edit-mode))
cdf54749 464 (set (make-local-variable 'log-edit-callback) callback)
93a142e1
DN
465 (if (listp params)
466 (dolist (crt params)
467 (set (make-local-variable (car crt)) (cdr crt)))
468 ;; For backward compatibility with log-edit up to version 22.2
469 ;; accept non-list PARAMS to mean `log-edit-list'.
470 (set (make-local-variable 'log-edit-listfun) params))
471
cdf54749 472 (if buffer (set (make-local-variable 'log-edit-parent-buffer) parent))
70c2a484 473 (set (make-local-variable 'log-edit-initial-files) (log-edit-files))
0cda6b7b
JL
474 (when setup
475 (erase-buffer)
476 (run-hooks 'log-edit-hook))
52789f7f 477 (push-mark (point-max))
8a26c165 478 (message "%s" (substitute-command-keys
cdf54749 479 "Press \\[log-edit-done] when you are done editing."))))
5b467bf4
SM
480
481(define-derived-mode log-edit-mode text-mode "Log-Edit"
54877f36
SM
482 "Major mode for editing version-control log messages.
483When done editing the log entry, just type \\[log-edit-done] which
484will trigger the actual commit of the file(s).
485Several other handy support commands are provided of course and
486the package from which this is used might also provide additional
487commands (under C-x v for VC, for example).
488
1be77002 489\\{log-edit-mode-map}"
165a7fbe 490 (set (make-local-variable 'font-lock-defaults)
49ed9c8e 491 '(log-edit-font-lock-keywords t))
c055d654 492 (setq-local jit-lock-contextually t) ;For the "first line is summary".
8117868f 493 (make-local-variable 'log-edit-comment-ring-index)
dda61916 494 (add-hook 'kill-buffer-hook 'log-edit-remember-comment nil t)
8117868f 495 (hack-dir-local-variables-non-file-buffer))
5b467bf4
SM
496
497(defun log-edit-hide-buf (&optional buf where)
498 (when (setq buf (get-buffer (or buf log-edit-files-buf)))
d9dfe8ca
DG
499 ;; FIXME: Should use something like `quit-windows-on' here, but
500 ;; that function never deletes this buffer's window because it
501 ;; was created using `cvs-pop-to-buffer-same-frame'.
3adc9c6d
DG
502 (save-selected-window
503 (let ((win (get-buffer-window buf where)))
504 (if win (ignore-errors (delete-window win))))
505 (bury-buffer buf))))
5b467bf4 506
dda61916
DG
507(defun log-edit-remember-comment (&optional comment)
508 (unless comment (setq comment (buffer-string)))
d9dfe8ca
DG
509 (when (or (ring-empty-p log-edit-comment-ring)
510 (not (equal comment (ring-ref log-edit-comment-ring 0))))
511 (ring-insert log-edit-comment-ring comment)))
512
5b467bf4
SM
513(defun log-edit-done ()
514 "Finish editing the log message and commit the files.
5b467bf4
SM
515If you want to abort the commit, simply delete the buffer."
516 (interactive)
e97a42c1
SM
517 ;; Clean up empty headers.
518 (goto-char (point-min))
519 (while (looking-at (concat "^[a-z]*:" log-edit-header-contents-regexp))
520 (let ((beg (match-beginning 0)))
521 (goto-char (match-end 0))
522 (if (string-match "\\`[ \n\t]*\\'" (match-string 1))
523 (delete-region beg (point)))))
524 ;; Get rid of leading empty lines.
525 (goto-char (point-min))
526 (when (looking-at "\\([ \t]*\n\\)+")
527 (delete-region (match-beginning 0) (match-end 0)))
ffe7dc64
SM
528 ;; Get rid of trailing empty lines
529 (goto-char (point-max))
530 (skip-syntax-backward " ")
531 (when (equal (char-after) ?\n) (forward-char 1))
532 (delete-region (point) (point-max))
533 ;; Check for final newline
cdf54749
SM
534 (if (and (> (point-max) (point-min))
535 (/= (char-before (point-max)) ?\n)
5b467bf4
SM
536 (or (eq log-edit-require-final-newline t)
537 (and log-edit-require-final-newline
538 (y-or-n-p
539 (format "Buffer %s does not end in newline. Add one? "
540 (buffer-name))))))
541 (save-excursion
542 (goto-char (point-max))
543 (insert ?\n)))
dda61916 544 (log-edit-remember-comment)
5b467bf4
SM
545 (let ((win (get-buffer-window log-edit-files-buf)))
546 (if (and log-edit-confirm
547 (not (and (eq log-edit-confirm 'changed)
548 (equal (log-edit-files) log-edit-initial-files)))
549 (progn
550 (log-edit-show-files)
ce5a3ac0 551 (not (y-or-n-p "Really commit? "))))
5b467bf4
SM
552 (progn (when (not win) (log-edit-hide-buf))
553 (message "Oh, well! Later maybe?"))
554 (run-hooks 'log-edit-done-hook)
555 (log-edit-hide-buf)
cdf54749
SM
556 (unless (or log-edit-keep-buffer (not log-edit-parent-buffer))
557 (cvs-bury-buffer (current-buffer) log-edit-parent-buffer))
5b467bf4
SM
558 (call-interactively log-edit-callback))))
559
d9dfe8ca
DG
560(defun log-edit-kill-buffer ()
561 "Kill the current buffer.
562Also saves its contents in the comment history and hides
563`log-edit-files-buf'."
564 (interactive)
3adc9c6d 565 (log-edit-hide-buf)
d9dfe8ca
DG
566 (let ((buf (current-buffer)))
567 (quit-windows-on buf)
568 (kill-buffer buf)))
569
5b467bf4
SM
570(defun log-edit-files ()
571 "Return the list of files that are about to be committed."
572 (ignore-errors (funcall log-edit-listfun)))
573
5b467bf4
SM
574(defun log-edit-mode-help ()
575 "Provide help for the `log-edit-mode-map'."
576 (interactive)
577 (if (eq last-command 'log-edit-mode-help)
578 (describe-function major-mode)
8a26c165 579 (message "%s"
5b467bf4
SM
580 (substitute-command-keys
581 "Type `\\[log-edit-done]' to finish commit. Try `\\[describe-function] log-edit-done' for more help."))))
582
43a9a0c4
SM
583(defcustom log-edit-common-indent 0
584 "Minimum indentation to use in `log-edit-set-common-indentation'."
585 :group 'log-edit
586 :type 'integer)
587
588(defun log-edit-set-common-indentation ()
589 "(Un)Indent the current buffer rigidly to `log-edit-common-indent'."
5b467bf4
SM
590 (save-excursion
591 (let ((common (point-max)))
e97a42c1 592 (rfc822-goto-eoh)
5b467bf4
SM
593 (while (< (point) (point-max))
594 (if (not (looking-at "^[ \t]*$"))
595 (setq common (min common (current-indentation))))
596 (forward-line 1))
e97a42c1
SM
597 (rfc822-goto-eoh)
598 (indent-rigidly (point) (point-max)
43a9a0c4 599 (- log-edit-common-indent common)))))
5b467bf4 600
93a142e1
DN
601(defun log-edit-show-diff ()
602 "Show the diff for the files to be committed."
603 (interactive)
37b72bf5
DN
604 (if (functionp log-edit-diff-function)
605 (funcall log-edit-diff-function)
606 (error "Diff functionality has not been setup")))
93a142e1 607
5b467bf4
SM
608(defun log-edit-show-files ()
609 "Show the list of files to be committed."
610 (interactive)
611 (let* ((files (log-edit-files))
cdf54749 612 (buf (get-buffer-create log-edit-files-buf)))
5b467bf4
SM
613 (with-current-buffer buf
614 (log-edit-hide-buf buf 'all)
615 (setq buffer-read-only nil)
616 (erase-buffer)
a88e99b5 617 (cvs-insert-strings files)
5b467bf4
SM
618 (setq buffer-read-only t)
619 (goto-char (point-min))
620 (save-selected-window
621 (cvs-pop-to-buffer-same-frame buf)
622 (shrink-window-if-larger-than-buffer)
3adc9c6d 623 (set-window-dedicated-p (selected-window) t)
5b467bf4
SM
624 (selected-window)))))
625
52789f7f
DG
626(defun log-edit-beginning-of-line (&optional n)
627 "Move point to beginning of header value or to beginning of line.
628
629It works the same as `message-beginning-of-line', but it uses a
630different header separator appropriate for `log-edit-mode'."
631 (interactive "p")
632 (let ((mail-header-separator ""))
633 (message-beginning-of-line n)))
634
b605679c
GM
635(defun log-edit-empty-buffer-p ()
636 "Return non-nil if the buffer is \"empty\"."
637 (or (= (point-min) (point-max))
638 (save-excursion
639 (goto-char (point-min))
02661b3a 640 (while (and (looking-at "^\\([a-zA-Z]+: ?\\)?$")
b605679c
GM
641 (zerop (forward-line 1))))
642 (eobp))))
643
0cda6b7b
JL
644(defun log-edit-insert-message-template ()
645 "Insert the default template with Summary and Author."
646 (interactive)
647 (when (or (called-interactively-p 'interactive)
648 (log-edit-empty-buffer-p))
649 (insert "Summary: ")
650 (when log-edit-setup-add-author
651 (insert "\nAuthor: "))
652 (insert "\n\n")
653 (message-position-point)))
654
5b467bf4 655(defun log-edit-insert-cvs-template ()
f7eeab0d
SM
656 "Insert the template specified by the CVS administrator, if any.
657This simply uses the local CVS/Template file."
5b467bf4 658 (interactive)
32226619 659 (when (or (called-interactively-p 'interactive)
b605679c
GM
660 (log-edit-empty-buffer-p))
661 ;; Should the template take precedence over an empty Summary:,
662 ;; ie should we first erase the buffer?
f7eeab0d 663 (when (file-readable-p "CVS/Template")
b605679c 664 (goto-char (point-max))
f7eeab0d
SM
665 (insert-file-contents "CVS/Template"))))
666
667(defun log-edit-insert-cvs-rcstemplate ()
668 "Insert the rcstemplate from the CVS repository.
669This contacts the repository to get the rcstemplate file and
670can thus take some time."
671 (interactive)
32226619 672 (when (or (called-interactively-p 'interactive)
b605679c 673 (log-edit-empty-buffer-p))
21227135 674 (when (file-readable-p "CVS/Root")
b605679c 675 (goto-char (point-max))
21227135
SM
676 ;; Ignore the stderr stuff, even if it's an error.
677 (call-process "cvs" nil '(t nil) nil
678 "checkout" "-p" "CVSROOT/rcstemplate"))))
f1180544 679
f7eeab0d
SM
680(defun log-edit-insert-filenames ()
681 "Insert the list of files that are to be committed."
682 (interactive)
683 (insert "Affected files: \n"
684 (mapconcat 'identity (log-edit-files) " \n")))
5b467bf4 685
3d6e95e7
JL
686(defun log-edit-insert-filenames-without-changelog ()
687 "Insert the list of files that have no ChangeLog message."
688 (interactive)
689 (let ((files
690 (delq nil
691 (mapcar
692 (lambda (file)
693 (unless (or (cdr-safe (log-edit-changelog-entries file))
694 (equal (file-name-nondirectory file) "ChangeLog"))
695 file))
696 (log-edit-files)))))
697 (when files
698 (goto-char (point-max))
699 (insert (mapconcat 'identity files ", ") ": "))))
700
5b467bf4
SM
701(defun log-edit-add-to-changelog ()
702 "Insert this log message into the appropriate ChangeLog file."
703 (interactive)
dda61916 704 (log-edit-remember-comment)
5b467bf4
SM
705 (dolist (f (log-edit-files))
706 (let ((buffer-file-name (expand-file-name f)))
707 (save-excursion
9fe89a26 708 (log-edit-comment-to-change-log)))))
5b467bf4 709
f7eeab0d 710(defvar log-edit-changelog-use-first nil)
ce8794df
SM
711
712(defvar log-edit-rewrite-fixes nil
713 "Rule to rewrite bug numbers into Fixes: headers.
714The value should be of the form (REGEXP . REPLACEMENT)
715where REGEXP should match the expression referring to a bug number
716in the text, and REPLACEMENT is an expression to pass to `replace-match'
717to build the Fixes: header.")
a62b88d4
SM
718(put 'log-edit-rewrite-fixes 'safe-local-variable
719 (lambda (v) (and (stringp (car-safe v)) (stringp (cdr v)))))
ce8794df 720
7a6c0941
SM
721(defun log-edit-add-field (field value)
722 (rfc822-goto-eoh)
723 (if (save-excursion (re-search-backward (concat "^" field ":\\([ \t]*\\)$")
724 nil t))
725 (replace-match (concat " " value) t t nil 1)
726 (insert field ": " value "\n" (if (looking-at "\n") "" "\n"))))
727
f7eeab0d
SM
728(defun log-edit-insert-changelog (&optional use-first)
729 "Insert a log message by looking at the ChangeLog.
730The idea is to write your ChangeLog entries first, and then use this
731command to commit your changes.
732
733To select default log text, we:
734- find the ChangeLog entries for the files to be checked in,
735- verify that the top entry in the ChangeLog is on the current date
736 and by the current user; if not, we don't provide any default text,
737- search the ChangeLog entry for paragraphs containing the names of
738 the files we're checking in, and finally
739- use those paragraphs as the log text.
740
741If the optional prefix arg USE-FIRST is given (via \\[universal-argument]),
742or if the command is repeated a second time in a row, use the first log entry
743regardless of user name or time."
744 (interactive "P")
0cda6b7b
JL
745 (save-excursion
746 (let ((eoh (save-excursion (rfc822-goto-eoh) (point))))
747 (when (<= (point) eoh)
748 (goto-char eoh)
749 (if (looking-at "\n") (forward-char 1))))
750 (let ((author
751 (let ((log-edit-changelog-use-first
752 (or use-first (eq last-command 'log-edit-insert-changelog))))
753 (log-edit-insert-changelog-entries (log-edit-files)))))
754 (log-edit-set-common-indentation)
755 ;; Add an Author: field if appropriate.
756 (when author (log-edit-add-field "Author" author))
757 ;; Add a Fixes: field if applicable.
758 (when (consp log-edit-rewrite-fixes)
759 (rfc822-goto-eoh)
760 (when (re-search-forward (car log-edit-rewrite-fixes) nil t)
761 (let ((start (match-beginning 0))
762 (end (match-end 0))
763 (fixes (match-substitute-replacement
764 (cdr log-edit-rewrite-fixes))))
765 (delete-region start end)
766 (log-edit-add-field "Fixes" fixes))))
767 (and log-edit-strip-single-file-name
768 (progn (rfc822-goto-eoh)
769 (if (looking-at "\n") (forward-char 1))
770 (looking-at "\\*\\s-+"))
771 (let ((start (point)))
772 (forward-line 1)
773 (when (not (re-search-forward "^\\*\\s-+" nil t))
774 (goto-char start)
775 (skip-chars-forward "^():")
776 (skip-chars-forward ": ")
777 (delete-region start (point))))))))
f7eeab0d 778
f1180544 779;;;;
5b467bf4
SM
780;;;; functions for getting commit message from ChangeLog a file...
781;;;; Courtesy Jim Blandy
f1180544 782;;;;
5b467bf4 783
14188021 784(defun log-edit-narrow-changelog ()
5b467bf4
SM
785 "Narrow to the top page of the current buffer, a ChangeLog file.
786Actually, the narrowed region doesn't include the date line.
787A \"page\" in a ChangeLog file is the area between two dates."
788 (or (eq major-mode 'change-log-mode)
14188021 789 (error "log-edit-narrow-changelog: current buffer isn't a ChangeLog"))
5b467bf4
SM
790
791 (goto-char (point-min))
792
793 ;; Skip date line and subsequent blank lines.
794 (forward-line 1)
795 (if (looking-at "[ \t\n]*\n")
796 (goto-char (match-end 0)))
797
798 (let ((start (point)))
799 (forward-page 1)
800 (narrow-to-region start (point))
801 (goto-char (point-min))))
802
14188021 803(defun log-edit-changelog-paragraph ()
5b467bf4
SM
804 "Return the bounds of the ChangeLog paragraph containing point.
805If we are between paragraphs, return the previous paragraph."
8390fb80
SM
806 (beginning-of-line)
807 (if (looking-at "^[ \t]*$")
808 (skip-chars-backward " \t\n" (point-min)))
809 (list (progn
810 (if (re-search-backward "^[ \t]*\n" nil 'or-to-limit)
811 (goto-char (match-end 0)))
812 (point))
813 (if (re-search-forward "^[ \t\n]*$" nil t)
814 (match-beginning 0)
815 (point-max))))
5b467bf4 816
14188021 817(defun log-edit-changelog-subparagraph ()
5b467bf4
SM
818 "Return the bounds of the ChangeLog subparagraph containing point.
819A subparagraph is a block of non-blank lines beginning with an asterisk.
820If we are between sub-paragraphs, return the previous subparagraph."
5b467bf4
SM
821 (end-of-line)
822 (if (search-backward "*" nil t)
823 (list (progn (beginning-of-line) (point))
bc35d341 824 (progn
5b467bf4
SM
825 (forward-line 1)
826 (if (re-search-forward "^[ \t]*[\n*]" nil t)
827 (match-beginning 0)
828 (point-max))))
8390fb80 829 (list (point) (point))))
5b467bf4 830
14188021 831(defun log-edit-changelog-entry ()
5b467bf4 832 "Return the bounds of the ChangeLog entry containing point.
14188021 833The variable `log-edit-changelog-full-paragraphs' decides whether an
5b467bf4
SM
834\"entry\" is a paragraph or a subparagraph; see its documentation string
835for more details."
8390fb80
SM
836 (save-excursion
837 (if log-edit-changelog-full-paragraphs
838 (log-edit-changelog-paragraph)
839 (log-edit-changelog-subparagraph))))
5b467bf4
SM
840
841(defvar user-full-name)
842(defvar user-mail-address)
ce8794df
SM
843
844(defvar log-edit-author) ;Dynamically scoped.
845
14188021 846(defun log-edit-changelog-ours-p ()
5b467bf4 847 "See if ChangeLog entry at point is for the current user, today.
4837b516 848Return non-nil if it is."
5b467bf4
SM
849 ;; Code adapted from add-change-log-entry.
850 (let ((name (or (and (boundp 'add-log-full-name) add-log-full-name)
851 (and (fboundp 'user-full-name) (user-full-name))
852 (and (boundp 'user-full-name) user-full-name)))
853 (mail (or (and (boundp 'add-log-mailing-address) add-log-mailing-address)
854 ;;(and (fboundp 'user-mail-address) (user-mail-address))
855 (and (boundp 'user-mail-address) user-mail-address)))
856 (time (or (and (boundp 'add-log-time-format)
857 (functionp add-log-time-format)
858 (funcall add-log-time-format))
859 (format-time-string "%Y-%m-%d"))))
ce8794df
SM
860 (if (null log-edit-changelog-use-first)
861 (looking-at (regexp-quote (format "%s %s <%s>" time name mail)))
862 ;; Check the author, to potentially add it as a "Author: " header.
863 (when (looking-at "[^ \t]")
864 (when (and (boundp 'log-edit-author)
865 (not (looking-at (format ".+ .+ <%s>"
866 (regexp-quote mail))))
867 (looking-at ".+ \\(.+ <.+>\\)"))
868 (let ((author (replace-regexp-in-string " " " "
869 (match-string 1))))
870 (unless (and log-edit-author
871 (string-match (regexp-quote author) log-edit-author))
872 (setq log-edit-author
873 (if log-edit-author
874 (concat log-edit-author ", " author)
875 author)))))
876 t))))
5b467bf4 877
14188021 878(defun log-edit-changelog-entries (file)
5b467bf4
SM
879 "Return the ChangeLog entries for FILE, and the ChangeLog they came from.
880The return value looks like this:
bef4957b 881 (LOGBUFFER (ENTRYSTART ENTRYEND) ...)
5b467bf4
SM
882where LOGBUFFER is the name of the ChangeLog buffer, and each
883\(ENTRYSTART . ENTRYEND\) pair is a buffer region."
d944ee49
SM
884 (let ((changelog-file-name
885 (let ((default-directory
886 (file-name-directory (expand-file-name file)))
887 (visiting-buffer (find-buffer-visiting file)))
888 ;; If there is a buffer visiting FILE, and it has a local
889 ;; value for `change-log-default-name', use that.
890 (if (and visiting-buffer
891 (local-variable-p 'change-log-default-name
892 visiting-buffer))
893 (with-current-buffer visiting-buffer
894 change-log-default-name)
895 ;; `find-change-log' uses `change-log-default-name' if set
896 ;; and sets it before exiting, so we need to work around
02661b3a 897 ;; that memoizing which is undesired here.
d944ee49
SM
898 (setq change-log-default-name nil)
899 (find-change-log)))))
900 (with-current-buffer (find-file-noselect changelog-file-name)
5b467bf4
SM
901 (unless (eq major-mode 'change-log-mode) (change-log-mode))
902 (goto-char (point-min))
903 (if (looking-at "\\s-*\n") (goto-char (match-end 0)))
14188021 904 (if (not (log-edit-changelog-ours-p))
5b467bf4
SM
905 (list (current-buffer))
906 (save-restriction
14188021 907 (log-edit-narrow-changelog)
5b467bf4 908 (goto-char (point-min))
f1180544 909
5b467bf4
SM
910 ;; Search for the name of FILE relative to the ChangeLog. If that
911 ;; doesn't occur anywhere, they're not using full relative
912 ;; filenames in the ChangeLog, so just look for FILE; we'll accept
913 ;; some false positives.
914 (let ((pattern (file-relative-name
915 file (file-name-directory changelog-file-name))))
916 (if (or (string= pattern "")
917 (not (save-excursion
918 (search-forward pattern nil t))))
919 (setq pattern (file-name-nondirectory file)))
920
b543ff57 921 (setq pattern (concat "\\(^\\|[^[:alnum:]]\\)"
9b2a758a 922 (regexp-quote pattern)
b543ff57
NR
923 "\\($\\|[^[:alnum:]]\\)"))
924
8390fb80
SM
925 (let (texts
926 (pos (point)))
927 (while (and (not (eobp)) (re-search-forward pattern nil t))
14188021 928 (let ((entry (log-edit-changelog-entry)))
8390fb80
SM
929 (if (< (elt entry 1) (max (1+ pos) (point)))
930 ;; This is not relevant, actually.
931 nil
932 (push entry texts))
933 ;; Make sure we make progress.
934 (setq pos (max (1+ pos) (elt entry 1)))
935 (goto-char pos)))
5b467bf4
SM
936
937 (cons (current-buffer) texts))))))))
938
bef4957b
CY
939(defun log-edit-changelog-insert-entries (buffer beg end &rest files)
940 "Insert the text from BUFFER between BEG and END.
941Rename relative filenames in the ChangeLog entry as FILES."
942 (let ((opoint (point))
943 (log-name (buffer-file-name buffer))
944 (case-fold-search nil)
945 bound)
946 (insert-buffer-substring buffer beg end)
947 (setq bound (point-marker))
948 (when log-name
949 (dolist (f files)
950 (save-excursion
951 (goto-char opoint)
952 (when (re-search-forward
953 (concat "\\(^\\|[ \t]\\)\\("
954 (file-relative-name f (file-name-directory log-name))
955 "\\)[, :\n]")
956 bound t)
957 (replace-match f t t nil 2)))))
958 ;; Eliminate tabs at the beginning of the line.
959 (save-excursion
960 (goto-char opoint)
961 (while (re-search-forward "^\\(\t+\\)" bound t)
962 (replace-match "")))))
5b467bf4 963
14188021 964(defun log-edit-insert-changelog-entries (files)
5b467bf4 965 "Given a list of files FILES, insert the ChangeLog entries for them."
ce8794df
SM
966 (let ((log-entries nil)
967 (log-edit-author nil))
bef4957b
CY
968 ;; Note that any ChangeLog entry can apply to more than one file.
969 ;; Here we construct a log-entries list with elements of the form
970 ;; ((LOGBUFFER ENTRYSTART ENTRYEND) FILE1 FILE2...)
5b467bf4 971 (dolist (file files)
14188021 972 (let* ((entries (log-edit-changelog-entries file))
bef4957b
CY
973 (buf (car entries))
974 key entry)
975 (dolist (region (cdr entries))
976 (setq key (cons buf region))
977 (if (setq entry (assoc key log-entries))
978 (setcdr entry (append (cdr entry) (list file)))
979 (push (list key file) log-entries)))))
980 ;; Now map over log-entries, and extract the strings.
981 (dolist (log-entry (nreverse log-entries))
982 (apply 'log-edit-changelog-insert-entries
983 (append (car log-entry) (cdr log-entry)))
ce8794df
SM
984 (insert "\n"))
985 log-edit-author))
5b467bf4 986
9f7b98f8
DG
987(defun log-edit-toggle-header (header value)
988 "Toggle a boolean-type header in the current buffer.
0f457a37
DG
989See `log-edit-set-header' for details."
990 (log-edit-set-header header value t))
991
992(defun log-edit-set-header (header value &optional toggle)
993 "Set the value of HEADER to VALUE in the current buffer.
994If TOGGLE is non-nil, and the value of HEADER already is VALUE,
995clear it. Make sure there is an empty line after the headers.
996Return t if toggled on (or TOGGLE is nil), otherwise nil."
9f7b98f8
DG
997 (let ((val t)
998 (line (concat header ": " value "\n")))
999 (save-excursion
1000 (save-restriction
1001 (rfc822-goto-eoh)
1002 (narrow-to-region (point-min) (point))
1003 (goto-char (point-min))
1004 (if (re-search-forward (concat "^" header ":"
1005 log-edit-header-contents-regexp)
1006 nil t)
0f457a37 1007 (if (setq val (not (and toggle (string= (match-string 1) value))))
9f7b98f8
DG
1008 (replace-match line t t)
1009 (replace-match "" t t nil 1))
1010 (insert line)))
1011 (rfc822-goto-eoh)
1012 (delete-horizontal-space)
1013 (unless (looking-at "\n")
1014 (insert "\n")))
1015 val))
1016
e97a42c1
SM
1017(defun log-edit-extract-headers (headers comment)
1018 "Extract headers from COMMENT to form command line arguments.
9f7b98f8
DG
1019HEADERS should be an alist with elements (HEADER . CMDARG)
1020or (HEADER . FUNCTION) associating headers to command line
1021options and the result is then a list of the form (MSG ARGUMENTS...)
1022where MSG is the remaining text from COMMENT.
1023FUNCTION should be a function of one argument that takes the
1024header value and returns the list of strings to be appended to
1025ARGUMENTS. CMDARG will be added to ARGUMENTS followed by the
1026header value. If \"Summary\" is not in HEADERS, then the
1027\"Summary\" header is extracted anyway and put back as the first
1028line of MSG."
e97a42c1
SM
1029 (with-temp-buffer
1030 (insert comment)
1031 (rfc822-goto-eoh)
1032 (narrow-to-region (point-min) (point))
1033 (let ((case-fold-search t)
1034 (summary ())
1035 (res ()))
1036 (dolist (header (if (assoc "Summary" headers) headers
1037 (cons '("Summary" . t) headers)))
1038 (goto-char (point-min))
1039 (while (re-search-forward (concat "^" (car header)
1040 ":" log-edit-header-contents-regexp)
1041 nil t)
a0784609
SM
1042 (let ((txt (match-string 1)))
1043 (replace-match "" t t)
1044 (if (eq t (cdr header))
1045 (setq summary txt)
1046 (if (functionp (cdr header))
1047 (setq res (nconc res (funcall (cdr header) txt)))
1048 (push txt res)
1049 (push (or (cdr header) (car header)) res))))))
e97a42c1
SM
1050 ;; Remove header separator if the header is empty.
1051 (widen)
09158997 1052 (goto-char (point-min))
e97a42c1
SM
1053 (when (looking-at "\\([ \t]*\n\\)+")
1054 (delete-region (match-beginning 0) (match-end 0)))
0f457a37 1055 (if summary (insert summary "\n\n"))
e97a42c1 1056 (cons (buffer-string) res))))
09158997 1057
5b467bf4 1058(provide 'log-edit)
54877f36 1059
5b467bf4 1060;;; log-edit.el ends here