(jka-compr-install): Use push and dolist.
[bpt/emacs.git] / lisp / log-edit.el
CommitLineData
5b467bf4
SM
1;;; log-edit.el --- Major mode for editing CVS commit messages
2
0d30b337
TTN
3;; Copyright (C) 1999, 2000, 2002, 2003, 2004,
4;; 2005 Free Software Foundation, Inc.
5b467bf4
SM
5
6;; Author: Stefan Monnier <monnier@cs.yale.edu>
7;; Keywords: pcl-cvs cvs commit log
5b467bf4
SM
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;; You should have received a copy of the GNU General Public License
22;; along with GNU Emacs; see the file COPYING. If not, write to the
086add15
LK
23;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24;; Boston, MA 02110-1301, USA.
5b467bf4
SM
25
26;;; Commentary:
27
28;; Todo:
29
5b467bf4
SM
30;; - Move in VC's code
31;; - Add compatibility for VC's hook variables
5b467bf4
SM
32
33;;; Code:
34
35(eval-when-compile (require 'cl))
36(require 'add-log) ; for all the ChangeLog goodies
37(require 'pcvs-util)
38(require 'ring)
5b467bf4 39
f1180544 40;;;;
5b467bf4 41;;;; Global Variables
f1180544 42;;;;
5b467bf4
SM
43
44(defgroup log-edit nil
bc35d341 45 "Major mode for editing RCS and CVS commit messages."
5b467bf4 46 :group 'pcl-cvs
bc35d341
DL
47 :group 'vc ; It's used by VC.
48 :version "21.1"
5b467bf4
SM
49 :prefix "log-edit-")
50
51;; compiler pacifiers
52(defvar cvs-buffer)
53
12dd83de
SM
54\f
55;; The main keymap
56
5b467bf4
SM
57(easy-mmode-defmap log-edit-mode-map
58 `(("\C-c\C-c" . log-edit-done)
59 ("\C-c\C-a" . log-edit-insert-changelog)
60 ("\C-c\C-f" . log-edit-show-files)
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 "--"
79 ["Insert ChangeLog" log-edit-insert-changelog]
80 ["Add to ChangeLog" log-edit-add-to-changelog]
81 "--"
82 ["List files" log-edit-show-files
83 :help "Show the list of relevant files."]
84 "--"
9fe89a26
SM
85 ["Previous comment" log-edit-previous-comment]
86 ["Next comment" log-edit-next-comment]
87 ["Search comment forward" log-edit-comment-search-forward]
88 ["Search comment backward" log-edit-comment-search-backward]))
d247e32d 89
f077c462 90(defcustom log-edit-confirm 'changed
5b467bf4
SM
91 "*If non-nil, `log-edit-done' will request confirmation.
92If 'changed, only request confirmation if the list of files has
93 changed since the beginning of the log-edit session."
94 :group 'log-edit
95 :type '(choice (const changed) (const t) (const nil)))
96
97(defcustom log-edit-keep-buffer nil
98 "*If non-nil, don't hide the buffer after `log-edit-done'."
99 :group 'log-edit
100 :type 'boolean)
101
a664185b
JB
102(defvar cvs-commit-buffer-require-final-newline t)
103(make-obsolete-variable 'cvs-commit-buffer-require-final-newline
104 'log-edit-require-final-newline)
5b467bf4
SM
105
106(defcustom log-edit-require-final-newline
107 cvs-commit-buffer-require-final-newline
108 "*Enforce a newline at the end of commit log messages.
109Enforce it silently if t, query if non-nil and don't do anything if nil."
110 :group 'log-edit
111 :type '(choice (const ask) (const t) (const nil)))
112
113(defcustom log-edit-setup-invert nil
114 "*Non-nil means `log-edit' should invert the meaning of its SETUP arg.
115If SETUP is 'force, this variable has no effect."
116 :group 'log-edit
117 :type 'boolean)
118
119(defcustom log-edit-hook '(log-edit-insert-cvs-template
120 log-edit-insert-changelog)
121 "*Hook run at the end of `log-edit'."
122 :group 'log-edit
123 :type '(hook :options (log-edit-insert-cvs-template
124 log-edit-insert-changelog)))
125
3831af62 126(defcustom log-edit-mode-hook (if (boundp 'vc-log-mode-hook) vc-log-mode-hook)
5b467bf4
SM
127 "*Hook run when entering `log-edit-mode'."
128 :group 'log-edit
129 :type 'hook)
130
131(defcustom log-edit-done-hook nil
132 "*Hook run before doing the actual commit.
133This hook can be used to cleanup the message, enforce various
134conventions, or to allow recording the message in some other database,
135such as a bug-tracking system. The list of files about to be committed
136can be obtained from `log-edit-files'."
137 :group 'log-edit
43a9a0c4 138 :type '(hook :options (log-edit-set-common-indentation
5b467bf4
SM
139 log-edit-add-to-changelog)))
140
a664185b
JB
141(defvar cvs-changelog-full-paragraphs t)
142(make-obsolete-variable 'cvs-changelog-full-paragraphs
143 'log-edit-changelog-full-paragraphs)
14188021
SM
144
145(defvar log-edit-changelog-full-paragraphs cvs-changelog-full-paragraphs
146 "*If non-nil, include full ChangeLog paragraphs in the log.
5b467bf4
SM
147This may be set in the ``local variables'' section of a ChangeLog, to
148indicate the policy for that ChangeLog.
149
150A ChangeLog paragraph is a bunch of log text containing no blank lines;
151a paragraph usually describes a set of changes with a single purpose,
152but perhaps spanning several functions in several files. Changes in
153different paragraphs are unrelated.
154
14188021 155You could argue that the log entry for a file should contain the
5b467bf4
SM
156full ChangeLog paragraph mentioning the change to the file, even though
157it may mention other files, because that gives you the full context you
5ab405e4 158need to understand the change. This is the behavior you get when this
5b467bf4
SM
159variable is set to t.
160
14188021 161On the other hand, you could argue that the log entry for a change
5b467bf4 162should contain only the text for the changes which occurred in that
5ab405e4 163file, because the log is per-file. This is the behavior you get
5b467bf4
SM
164when this variable is set to nil.")
165
166;;;; Internal global or buffer-local vars
167
168(defconst log-edit-files-buf "*log-edit-files*")
169(defvar log-edit-initial-files nil)
170(defvar log-edit-callback nil)
171(defvar log-edit-listfun nil)
cdf54749 172(defvar log-edit-parent-buffer nil)
5b467bf4 173
9fe89a26 174;;; Originally taken from VC-Log mode
12dd83de 175
9fe89a26 176(defconst log-edit-maximum-comment-ring-size 32
12dd83de 177 "Maximum number of saved comments in the comment ring.")
9fe89a26
SM
178(defvar log-edit-comment-ring (make-ring log-edit-maximum-comment-ring-size))
179(defvar log-edit-comment-ring-index nil)
180(defvar log-edit-last-comment-match "")
12dd83de 181
9fe89a26 182(defun log-edit-new-comment-index (stride len)
12dd83de 183 "Return the comment index STRIDE elements from the current one.
9fe89a26 184LEN is the length of `log-edit-comment-ring'."
12dd83de 185 (mod (cond
9fe89a26 186 (log-edit-comment-ring-index (+ log-edit-comment-ring-index stride))
12dd83de
SM
187 ;; Initialize the index on the first use of this command
188 ;; so that the first M-p gets index 0, and the first M-n gets
189 ;; index -1.
190 ((> stride 0) (1- stride))
191 (t stride))
192 len))
193
9fe89a26 194(defun log-edit-previous-comment (arg)
12dd83de
SM
195 "Cycle backwards through comment history.
196With a numeric prefix ARG, go back ARG comments."
197 (interactive "*p")
9fe89a26 198 (let ((len (ring-length log-edit-comment-ring)))
12dd83de
SM
199 (if (<= len 0)
200 (progn (message "Empty comment ring") (ding))
0916e956
SM
201 ;; Don't use `erase-buffer' because we don't want to `widen'.
202 (delete-region (point-min) (point-max))
9fe89a26
SM
203 (setq log-edit-comment-ring-index (log-edit-new-comment-index arg len))
204 (message "Comment %d" (1+ log-edit-comment-ring-index))
205 (insert (ring-ref log-edit-comment-ring log-edit-comment-ring-index)))))
12dd83de 206
9fe89a26 207(defun log-edit-next-comment (arg)
12dd83de
SM
208 "Cycle forwards through comment history.
209With a numeric prefix ARG, go forward ARG comments."
210 (interactive "*p")
9fe89a26 211 (log-edit-previous-comment (- arg)))
12dd83de 212
9fe89a26 213(defun log-edit-comment-search-backward (str &optional stride)
12dd83de
SM
214 "Search backwards through comment history for substring match of STR.
215If the optional argument STRIDE is present, that is a step-width to use
216when going through the comment ring."
217 ;; Why substring rather than regexp ? -sm
218 (interactive
9fe89a26 219 (list (read-string "Comment substring: " nil nil log-edit-last-comment-match)))
12dd83de
SM
220 (unless stride (setq stride 1))
221 (if (string= str "")
9fe89a26
SM
222 (setq str log-edit-last-comment-match)
223 (setq log-edit-last-comment-match str))
12dd83de 224 (let* ((str (regexp-quote str))
9fe89a26
SM
225 (len (ring-length log-edit-comment-ring))
226 (n (log-edit-new-comment-index stride len)))
12dd83de 227 (while (progn (when (or (>= n len) (< n 0)) (error "Not found"))
9fe89a26 228 (not (string-match str (ring-ref log-edit-comment-ring n))))
12dd83de 229 (setq n (+ n stride)))
9fe89a26
SM
230 (setq log-edit-comment-ring-index n)
231 (log-edit-previous-comment 0)))
12dd83de 232
9fe89a26 233(defun log-edit-comment-search-forward (str)
12dd83de
SM
234 "Search forwards through comment history for a substring match of STR."
235 (interactive
9fe89a26
SM
236 (list (read-string "Comment substring: " nil nil log-edit-last-comment-match)))
237 (log-edit-comment-search-backward str -1))
12dd83de 238
9fe89a26 239(defun log-edit-comment-to-change-log (&optional whoami file-name)
12dd83de
SM
240 "Enter last VC comment into the change log for the current file.
241WHOAMI (interactive prefix) non-nil means prompt for user name
242and site. FILE-NAME is the name of the change log; if nil, use
243`change-log-default-name'.
244
9fe89a26 245This may be useful as a `log-edit-checkin-hook' to update change logs
12dd83de
SM
246automatically."
247 (interactive (if current-prefix-arg
248 (list current-prefix-arg
249 (prompt-for-change-log-name))))
12dd83de 250 (let (;; Extract the comment first so we get any error before doing anything.
9fe89a26 251 (comment (ring-ref log-edit-comment-ring 0))
12dd83de
SM
252 ;; Don't let add-change-log-entry insert a defun name.
253 (add-log-current-defun-function 'ignore)
254 end)
255 ;; Call add-log to do half the work.
256 (add-change-log-entry whoami file-name t t)
257 ;; Insert the VC comment, leaving point before it.
258 (setq end (save-excursion (insert comment) (point-marker)))
259 (if (looking-at "\\s *\\s(")
260 ;; It starts with an open-paren, as in "(foo): Frobbed."
261 ;; So remove the ": " add-log inserted.
262 (delete-char -2))
263 ;; Canonicalize the white space between the file name and comment.
264 (just-one-space)
265 ;; Indent rest of the text the same way add-log indented the first line.
266 (let ((indentation (current-indentation)))
267 (save-excursion
268 (while (< (point) end)
269 (forward-line 1)
270 (indent-to indentation))
271 (setq end (point))))
272 ;; Fill the inserted text, preserving open-parens at bol.
0916e956 273 (let ((paragraph-start (concat paragraph-start "\\|\\s *\\s(")))
12dd83de
SM
274 (beginning-of-line)
275 (fill-region (point) end))
276 ;; Canonicalize the white space at the end of the entry so it is
277 ;; separated from the next entry by a single blank line.
278 (skip-syntax-forward " " end)
279 (delete-char (- (skip-syntax-backward " ")))
280 (or (eobp) (looking-at "\n\n")
281 (insert "\n"))))
282
9fe89a26
SM
283;; Compatibility with old names.
284(defvaralias 'vc-comment-ring 'log-edit-comment-ring)
bf247b6e 285(make-obsolete-variable 'vc-comment-ring 'log-edit-comment-ring "22.1")
9fe89a26 286(defvaralias 'vc-comment-ring-index 'log-edit-comment-ring-index)
bf247b6e 287(make-obsolete-variable 'vc-comment-ring-index 'log-edit-comment-ring-index "22.1")
9fe89a26 288(defalias 'vc-previous-comment 'log-edit-previous-comment)
bf247b6e 289(make-obsolete 'vc-previous-comment 'log-edit-previous-comment "22.1")
9fe89a26 290(defalias 'vc-next-comment 'log-edit-next-comment)
bf247b6e 291(make-obsolete 'vc-next-comment 'log-edit-next-comment "22.1")
9fe89a26 292(defalias 'vc-comment-search-reverse 'log-edit-comment-search-backward)
bf247b6e 293(make-obsolete 'vc-comment-search-reverse 'log-edit-comment-search-backward "22.1")
9fe89a26 294(defalias 'vc-comment-search-forward 'log-edit-comment-search-forward)
bf247b6e 295(make-obsolete 'vc-comment-search-forward 'log-edit-comment-search-forward "22.1")
9fe89a26 296(defalias 'vc-comment-to-change-log 'log-edit-comment-to-change-log)
bf247b6e 297(make-obsolete 'vc-comment-to-change-log 'log-edit-comment-to-change-log "22.1")
9fe89a26 298
cdf54749
SM
299;;;
300;;; Actual code
301;;;
5b467bf4 302
63fbe552 303(defvar log-edit-font-lock-keywords
165a7fbe
SM
304 '(("\\`\\(Summary:\\)\\(.*\\)"
305 (1 font-lock-keyword-face)
306 (2 font-lock-function-name-face))))
307
5b467bf4 308;;;###autoload
cdf54749 309(defun log-edit (callback &optional setup listfun buffer &rest ignore)
5b467bf4 310 "Setup a buffer to enter a log message.
f077c462 311\\<log-edit-mode-map>The buffer will be put in `log-edit-mode'.
5b467bf4
SM
312If SETUP is non-nil, the buffer is then erased and `log-edit-hook' is run.
313Mark and point will be set around the entire contents of the
314buffer so that it is easy to kill the contents of the buffer with \\[kill-region].
315Once you're done editing the message, pressing \\[log-edit-done] will call
cdf54749
SM
316`log-edit-done' which will end up calling CALLBACK to do the actual commit.
317LISTFUN if non-nil is a function of no arguments returning the list of files
318 that are concerned by the current operation (using relative names).
319If BUFFER is non-nil `log-edit' will jump to that buffer, use it to edit the
320 log message and go back to the current buffer when done. Otherwise, it
321 uses the current buffer."
322 (let ((parent (current-buffer)))
323 (if buffer (pop-to-buffer buffer))
324 (when (and log-edit-setup-invert (not (eq setup 'force)))
325 (setq setup (not setup)))
326 (when setup (erase-buffer))
327 (log-edit-mode)
328 (set (make-local-variable 'log-edit-callback) callback)
329 (set (make-local-variable 'log-edit-listfun) listfun)
330 (if buffer (set (make-local-variable 'log-edit-parent-buffer) parent))
70c2a484 331 (set (make-local-variable 'log-edit-initial-files) (log-edit-files))
cdf54749
SM
332 (when setup (run-hooks 'log-edit-hook))
333 (goto-char (point-min)) (push-mark (point-max))
8a26c165 334 (message "%s" (substitute-command-keys
cdf54749 335 "Press \\[log-edit-done] when you are done editing."))))
5b467bf4
SM
336
337(define-derived-mode log-edit-mode text-mode "Log-Edit"
54877f36
SM
338 "Major mode for editing version-control log messages.
339When done editing the log entry, just type \\[log-edit-done] which
340will trigger the actual commit of the file(s).
341Several other handy support commands are provided of course and
342the package from which this is used might also provide additional
343commands (under C-x v for VC, for example).
344
1be77002 345\\{log-edit-mode-map}"
165a7fbe
SM
346 (set (make-local-variable 'font-lock-defaults)
347 '(log-edit-font-lock-keywords t))
9fe89a26 348 (make-local-variable 'log-edit-comment-ring-index))
5b467bf4
SM
349
350(defun log-edit-hide-buf (&optional buf where)
351 (when (setq buf (get-buffer (or buf log-edit-files-buf)))
352 (let ((win (get-buffer-window buf where)))
353 (if win (ignore-errors (delete-window win))))
354 (bury-buffer buf)))
355
356(defun log-edit-done ()
357 "Finish editing the log message and commit the files.
5b467bf4
SM
358If you want to abort the commit, simply delete the buffer."
359 (interactive)
ffe7dc64
SM
360 ;; Get rid of trailing empty lines
361 (goto-char (point-max))
362 (skip-syntax-backward " ")
363 (when (equal (char-after) ?\n) (forward-char 1))
364 (delete-region (point) (point-max))
365 ;; Check for final newline
cdf54749
SM
366 (if (and (> (point-max) (point-min))
367 (/= (char-before (point-max)) ?\n)
5b467bf4
SM
368 (or (eq log-edit-require-final-newline t)
369 (and log-edit-require-final-newline
370 (y-or-n-p
371 (format "Buffer %s does not end in newline. Add one? "
372 (buffer-name))))))
373 (save-excursion
374 (goto-char (point-max))
375 (insert ?\n)))
1be77002 376 (let ((comment (buffer-string)))
9fe89a26
SM
377 (when (or (ring-empty-p log-edit-comment-ring)
378 (not (equal comment (ring-ref log-edit-comment-ring 0))))
379 (ring-insert log-edit-comment-ring comment)))
5b467bf4
SM
380 (let ((win (get-buffer-window log-edit-files-buf)))
381 (if (and log-edit-confirm
382 (not (and (eq log-edit-confirm 'changed)
383 (equal (log-edit-files) log-edit-initial-files)))
384 (progn
385 (log-edit-show-files)
ce5a3ac0 386 (not (y-or-n-p "Really commit? "))))
5b467bf4
SM
387 (progn (when (not win) (log-edit-hide-buf))
388 (message "Oh, well! Later maybe?"))
389 (run-hooks 'log-edit-done-hook)
390 (log-edit-hide-buf)
cdf54749
SM
391 (unless (or log-edit-keep-buffer (not log-edit-parent-buffer))
392 (cvs-bury-buffer (current-buffer) log-edit-parent-buffer))
5b467bf4
SM
393 (call-interactively log-edit-callback))))
394
395(defun log-edit-files ()
396 "Return the list of files that are about to be committed."
397 (ignore-errors (funcall log-edit-listfun)))
398
399
400(defun log-edit-insert-changelog ()
401 "Insert a log message by looking at the ChangeLog.
402The idea is to write your ChangeLog entries first, and then use this
403command to commit your changes.
404
405To select default log text, we:
406- find the ChangeLog entries for the files to be checked in,
407- verify that the top entry in the ChangeLog is on the current date
408 and by the current user; if not, we don't provide any default text,
409- search the ChangeLog entry for paragraphs containing the names of
410 the files we're checking in, and finally
411- use those paragraphs as the log text."
412 (interactive)
14188021 413 (log-edit-insert-changelog-entries (log-edit-files))
43a9a0c4 414 (log-edit-set-common-indentation)
14188021
SM
415 (goto-char (point-min))
416 (when (looking-at "\\*\\s-+")
417 (forward-line 1)
418 (when (not (re-search-forward "^\\*\\s-+" nil t))
419 (goto-char (point-min))
420 (skip-chars-forward "^():")
ffe7dc64 421 (skip-chars-forward ": ")
14188021 422 (delete-region (point-min) (point)))))
5b467bf4
SM
423
424(defun log-edit-mode-help ()
425 "Provide help for the `log-edit-mode-map'."
426 (interactive)
427 (if (eq last-command 'log-edit-mode-help)
428 (describe-function major-mode)
8a26c165 429 (message "%s"
5b467bf4
SM
430 (substitute-command-keys
431 "Type `\\[log-edit-done]' to finish commit. Try `\\[describe-function] log-edit-done' for more help."))))
432
43a9a0c4
SM
433(defcustom log-edit-common-indent 0
434 "Minimum indentation to use in `log-edit-set-common-indentation'."
435 :group 'log-edit
436 :type 'integer)
437
438(defun log-edit-set-common-indentation ()
439 "(Un)Indent the current buffer rigidly to `log-edit-common-indent'."
5b467bf4
SM
440 (save-excursion
441 (let ((common (point-max)))
442 (goto-char (point-min))
443 (while (< (point) (point-max))
444 (if (not (looking-at "^[ \t]*$"))
445 (setq common (min common (current-indentation))))
446 (forward-line 1))
43a9a0c4
SM
447 (indent-rigidly (point-min) (point-max)
448 (- log-edit-common-indent common)))))
5b467bf4
SM
449
450(defun log-edit-show-files ()
451 "Show the list of files to be committed."
452 (interactive)
453 (let* ((files (log-edit-files))
cdf54749 454 (buf (get-buffer-create log-edit-files-buf)))
5b467bf4
SM
455 (with-current-buffer buf
456 (log-edit-hide-buf buf 'all)
457 (setq buffer-read-only nil)
458 (erase-buffer)
a88e99b5 459 (cvs-insert-strings files)
5b467bf4
SM
460 (setq buffer-read-only t)
461 (goto-char (point-min))
462 (save-selected-window
463 (cvs-pop-to-buffer-same-frame buf)
464 (shrink-window-if-larger-than-buffer)
465 (selected-window)))))
466
467(defun log-edit-insert-cvs-template ()
468 "Insert the template specified by the CVS administrator, if any."
469 (interactive)
470 (when (file-readable-p "CVS/Template")
471 (insert-file-contents "CVS/Template")))
f1180544 472
5b467bf4
SM
473
474(defun log-edit-add-to-changelog ()
475 "Insert this log message into the appropriate ChangeLog file."
476 (interactive)
477 ;; Yuck!
9fe89a26
SM
478 (unless (string= (buffer-string) (ring-ref log-edit-comment-ring 0))
479 (ring-insert log-edit-comment-ring (buffer-string)))
5b467bf4
SM
480 (dolist (f (log-edit-files))
481 (let ((buffer-file-name (expand-file-name f)))
482 (save-excursion
9fe89a26 483 (log-edit-comment-to-change-log)))))
5b467bf4 484
f1180544 485;;;;
5b467bf4
SM
486;;;; functions for getting commit message from ChangeLog a file...
487;;;; Courtesy Jim Blandy
f1180544 488;;;;
5b467bf4 489
14188021 490(defun log-edit-narrow-changelog ()
5b467bf4
SM
491 "Narrow to the top page of the current buffer, a ChangeLog file.
492Actually, the narrowed region doesn't include the date line.
493A \"page\" in a ChangeLog file is the area between two dates."
494 (or (eq major-mode 'change-log-mode)
14188021 495 (error "log-edit-narrow-changelog: current buffer isn't a ChangeLog"))
5b467bf4
SM
496
497 (goto-char (point-min))
498
499 ;; Skip date line and subsequent blank lines.
500 (forward-line 1)
501 (if (looking-at "[ \t\n]*\n")
502 (goto-char (match-end 0)))
503
504 (let ((start (point)))
505 (forward-page 1)
506 (narrow-to-region start (point))
507 (goto-char (point-min))))
508
14188021 509(defun log-edit-changelog-paragraph ()
5b467bf4
SM
510 "Return the bounds of the ChangeLog paragraph containing point.
511If we are between paragraphs, return the previous paragraph."
512 (save-excursion
513 (beginning-of-line)
514 (if (looking-at "^[ \t]*$")
515 (skip-chars-backward " \t\n" (point-min)))
516 (list (progn
517 (if (re-search-backward "^[ \t]*\n" nil 'or-to-limit)
518 (goto-char (match-end 0)))
519 (point))
520 (if (re-search-forward "^[ \t\n]*$" nil t)
521 (match-beginning 0)
522 (point)))))
523
14188021 524(defun log-edit-changelog-subparagraph ()
5b467bf4
SM
525 "Return the bounds of the ChangeLog subparagraph containing point.
526A subparagraph is a block of non-blank lines beginning with an asterisk.
527If we are between sub-paragraphs, return the previous subparagraph."
528 (save-excursion
529 (end-of-line)
530 (if (search-backward "*" nil t)
531 (list (progn (beginning-of-line) (point))
bc35d341 532 (progn
5b467bf4
SM
533 (forward-line 1)
534 (if (re-search-forward "^[ \t]*[\n*]" nil t)
535 (match-beginning 0)
536 (point-max))))
537 (list (point) (point)))))
538
14188021 539(defun log-edit-changelog-entry ()
5b467bf4 540 "Return the bounds of the ChangeLog entry containing point.
14188021 541The variable `log-edit-changelog-full-paragraphs' decides whether an
5b467bf4
SM
542\"entry\" is a paragraph or a subparagraph; see its documentation string
543for more details."
14188021
SM
544 (if log-edit-changelog-full-paragraphs
545 (log-edit-changelog-paragraph)
546 (log-edit-changelog-subparagraph)))
5b467bf4
SM
547
548(defvar user-full-name)
549(defvar user-mail-address)
14188021 550(defun log-edit-changelog-ours-p ()
5b467bf4
SM
551 "See if ChangeLog entry at point is for the current user, today.
552Return non-nil iff it is."
553 ;; Code adapted from add-change-log-entry.
554 (let ((name (or (and (boundp 'add-log-full-name) add-log-full-name)
555 (and (fboundp 'user-full-name) (user-full-name))
556 (and (boundp 'user-full-name) user-full-name)))
557 (mail (or (and (boundp 'add-log-mailing-address) add-log-mailing-address)
558 ;;(and (fboundp 'user-mail-address) (user-mail-address))
559 (and (boundp 'user-mail-address) user-mail-address)))
560 (time (or (and (boundp 'add-log-time-format)
561 (functionp add-log-time-format)
562 (funcall add-log-time-format))
563 (format-time-string "%Y-%m-%d"))))
564 (looking-at (regexp-quote (format "%s %s <%s>" time name mail)))))
565
14188021 566(defun log-edit-changelog-entries (file)
5b467bf4
SM
567 "Return the ChangeLog entries for FILE, and the ChangeLog they came from.
568The return value looks like this:
569 (LOGBUFFER (ENTRYSTART . ENTRYEND) ...)
570where LOGBUFFER is the name of the ChangeLog buffer, and each
571\(ENTRYSTART . ENTRYEND\) pair is a buffer region."
572 (save-excursion
bc35d341 573 (let ((changelog-file-name
5b467bf4 574 (let ((default-directory
44d7d150
AS
575 (file-name-directory (expand-file-name file)))
576 (visiting-buffer (find-buffer-visiting file)))
577 ;; If there is a buffer visiting FILE, and it has a local
578 ;; value for `change-log-default-name', use that.
579 (if (and visiting-buffer
580 (local-variable-p 'change-log-default-name
581 visiting-buffer))
582 (save-excursion
583 (set-buffer visiting-buffer)
584 change-log-default-name)
585 ;; `find-change-log' uses `change-log-default-name' if set
586 ;; and sets it before exiting, so we need to work around
587 ;; that memoizing which is undesired here
588 (setq change-log-default-name nil)
589 (find-change-log)))))
5b467bf4
SM
590 (set-buffer (find-file-noselect changelog-file-name))
591 (unless (eq major-mode 'change-log-mode) (change-log-mode))
592 (goto-char (point-min))
593 (if (looking-at "\\s-*\n") (goto-char (match-end 0)))
14188021 594 (if (not (log-edit-changelog-ours-p))
5b467bf4
SM
595 (list (current-buffer))
596 (save-restriction
14188021 597 (log-edit-narrow-changelog)
5b467bf4 598 (goto-char (point-min))
f1180544 599
5b467bf4
SM
600 ;; Search for the name of FILE relative to the ChangeLog. If that
601 ;; doesn't occur anywhere, they're not using full relative
602 ;; filenames in the ChangeLog, so just look for FILE; we'll accept
603 ;; some false positives.
604 (let ((pattern (file-relative-name
605 file (file-name-directory changelog-file-name))))
606 (if (or (string= pattern "")
607 (not (save-excursion
608 (search-forward pattern nil t))))
609 (setq pattern (file-name-nondirectory file)))
610
b543ff57
NR
611 (setq pattern (concat "\\(^\\|[^[:alnum:]]\\)"
612 pattern
613 "\\($\\|[^[:alnum:]]\\)"))
614
5b467bf4 615 (let (texts)
b543ff57 616 (while (re-search-forward pattern nil t)
14188021 617 (let ((entry (log-edit-changelog-entry)))
5b467bf4
SM
618 (push entry texts)
619 (goto-char (elt entry 1))))
620
621 (cons (current-buffer) texts))))))))
622
14188021 623(defun log-edit-changelog-insert-entries (buffer regions)
5b467bf4
SM
624 "Insert those regions in BUFFER specified in REGIONS.
625Sort REGIONS front-to-back first."
626 (let ((regions (sort regions 'car-less-than-car))
627 (last))
628 (dolist (region regions)
629 (when (and last (< last (car region))) (newline))
630 (setq last (elt region 1))
631 (apply 'insert-buffer-substring buffer region))))
632
14188021 633(defun log-edit-insert-changelog-entries (files)
5b467bf4
SM
634 "Given a list of files FILES, insert the ChangeLog entries for them."
635 (let ((buffer-entries nil))
636
637 ;; Add each buffer to buffer-entries, and associate it with the list
638 ;; of entries we want from that file.
639 (dolist (file files)
14188021 640 (let* ((entries (log-edit-changelog-entries file))
5b467bf4
SM
641 (pair (assq (car entries) buffer-entries)))
642 (if pair
643 (setcdr pair (cvs-union (cdr pair) (cdr entries)))
644 (push entries buffer-entries))))
645
646 ;; Now map over each buffer in buffer-entries, sort the entries for
647 ;; each buffer, and extract them as strings.
648 (dolist (buffer-entry buffer-entries)
14188021 649 (log-edit-changelog-insert-entries (car buffer-entry) (cdr buffer-entry))
5b467bf4
SM
650 (when (cdr buffer-entry) (newline)))))
651
652(provide 'log-edit)
54877f36 653
b543ff57 654;; arch-tag: 8089b39c-983b-4e83-93cd-ed0a64c7fdcc
5b467bf4 655;;; log-edit.el ends here