(mark_kboards): Move to keyboard.c.
[bpt/emacs.git] / lisp / add-log.el
CommitLineData
5abdc915 1;;; add-log.el --- change log maintenance commands for Emacs
84fc2cfa 2
5960adc7 3;; Copyright (C) 1985, 86, 88, 93, 94, 97, 98, 2000 Free Software Foundation, Inc.
84fc2cfa 4
6228c05b 5;; Maintainer: FSF
df63ae66 6;; Keywords: tools
e9571d2a 7
84fc2cfa
ER
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software; you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
fd7fa35a 12;; the Free Software Foundation; either version 2, or (at your option)
84fc2cfa
ER
13;; any later version.
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
b578f267
EN
21;; along with GNU Emacs; see the file COPYING. If not, write to the
22;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23;; Boston, MA 02111-1307, USA.
84fc2cfa 24
e41b2db1
ER
25;;; Commentary:
26
27;; This facility is documented in the Emacs Manual.
28
fd7fa35a 29;;; Code:
84fc2cfa 30
776d8e16 31(eval-when-compile
5960adc7 32 (require 'timezone))
3697b807 33
fcad5199
RS
34(defgroup change-log nil
35 "Change log maintenance"
36 :group 'tools
3697b807 37 :link '(custom-manual "(emacs)Change Log")
fcad5199
RS
38 :prefix "change-log-"
39 :prefix "add-log-")
84fc2cfa 40
fcad5199
RS
41
42(defcustom change-log-default-name nil
43 "*Name of a change log file for \\[add-change-log-entry]."
44 :type '(choice (const :tag "default" nil)
45 string)
46 :group 'change-log)
47
c3979f12
DL
48(defcustom change-log-mode-hook nil
49 "Normal hook run by `change-log-mode'."
50 :type 'hook
51 :group 'change-log)
52
c882e115
RS
53;; Many modes set this variable, so avoid warnings.
54;;;###autoload
fcad5199 55(defcustom add-log-current-defun-function nil
5960adc7
DL
56 "*If non-nil, function to guess name of surrounding function.
57It is used by `add-log-current-defun' in preference to built-in rules.
58Returns function's name as a string, or nil if outside a function."
cea3855a 59 :type '(choice (const nil) function)
fcad5199 60 :group 'change-log)
6258d3af 61
29db528b 62;;;###autoload
fcad5199 63(defcustom add-log-full-name nil
02ec1592 64 "*Full name of user, for inclusion in ChangeLog daily headers.
075a6629 65This defaults to the value returned by the function `user-full-name'."
fcad5199
RS
66 :type '(choice (const :tag "Default" nil)
67 string)
68 :group 'change-log)
02ec1592 69
29db528b 70;;;###autoload
fcad5199 71(defcustom add-log-mailing-address nil
c1b3ae42
CW
72 "*Electronic mail addresses of user, for inclusion in ChangeLog headers.
73This defaults to the value of `user-mail-address'. In addition to
74being a simple string, this value can also be a list. All elements
75will be recognized as referring to the same user; when creating a new
76ChangeLog entry, one element will be chosen at random."
fcad5199 77 :type '(choice (const :tag "Default" nil)
24f4201f
MR
78 (string :tag "String")
79 (repeat :tag "List of Strings" string))
fcad5199
RS
80 :group 'change-log)
81
df63ae66
RS
82(defcustom add-log-time-format 'add-log-iso8601-time-string
83 "*Function that defines the time format.
84For example, `add-log-iso8601-time-string', which gives the
85date in international ISO 8601 format,
86and `current-time-string' are two valid values."
87 :type '(radio (const :tag "International ISO 8601 format"
88 add-log-iso8601-time-string)
89 (const :tag "Old format, as returned by `current-time-string'"
90 current-time-string)
91 (function :tag "Other"))
92 :group 'change-log)
02ec1592 93
83afd62c 94(defcustom add-log-keep-changes-together nil
3697b807
DL
95 "*If non-nil, normally keep day's log entries for one file together.
96
97Log entries for a given file made with \\[add-change-log-entry] or
98\\[add-change-log-entry-other-window] will only be added to others \
99for that file made
100today if this variable is non-nil or that file comes first in today's
101entries. Otherwise another entry for that file will be started. An
102original log:
103
104 * foo (...): ...
105 * bar (...): change 1
83afd62c 106
3697b807
DL
107in the latter case, \\[add-change-log-entry-other-window] in a \
108buffer visiting `bar', yields:
83afd62c 109
3697b807
DL
110 * bar (...): -!-
111 * foo (...): ...
112 * bar (...): change 1
83afd62c 113
3697b807 114and in the former:
83afd62c 115
3697b807
DL
116 * foo (...): ...
117 * bar (...): change 1
118 (...): -!-
83afd62c 119
3697b807
DL
120The NEW-ENTRY arg to `add-change-log-entry' can override the effect of
121this variable."
122 :version "20.3"
83afd62c
KH
123 :type 'boolean
124 :group 'change-log)
125
598f34fa
SS
126(defcustom add-log-always-start-new-record nil
127 "*If non-nil, `add-change-log-entry' will always start a new record."
89e7ad59 128 :version "21.4"
598f34fa
SS
129 :type 'boolean
130 :group 'change-log)
131
d68f7f1b
SM
132(defcustom add-log-buffer-file-name-function nil
133 "*If non-nil, function to call to identify the full filename of a buffer.
134This function is called with no argument. If this is nil, the default is to
135use `buffer-file-name'."
cea3855a 136 :type '(choice (const nil) function)
d68f7f1b
SM
137 :group 'change-log)
138
666f4056
RS
139(defcustom add-log-file-name-function nil
140 "*If non-nil, function to call to identify the filename for a ChangeLog entry.
075a6629
DL
141This function is called with one argument, the value of variable
142`buffer-file-name' in that buffer. If this is nil, the default is to
143use the file's name relative to the directory of the change log file."
cea3855a 144 :type '(choice (const nil) function)
666f4056
RS
145 :group 'change-log)
146
776d8e16
GM
147
148(defcustom change-log-version-info-enabled nil
149 "*If non-nil, enable recording version numbers with the changes."
150 :version "21.1"
151 :type 'boolean
152 :group 'change-log)
153
154(defcustom change-log-version-number-regexp-list
155 (let ((re "\\([0-9]+\.[0-9.]+\\)"))
156 (list
157 ;; (defconst ad-version "2.15"
158 (concat "^(def[^ \t\n]+[ \t]+[^ \t\n][ \t]\"" re)
159 ;; Revision: pcl-cvs.el,v 1.72 1999/09/05 20:21:54 monnier Exp
5960adc7 160 (concat "^;+ *Revision: +[^ \t\n]+[ \t]+" re)))
776d8e16 161 "*List of regexps to search for version number.
5960adc7 162The version number must be in group 1.
776d8e16
GM
163Note: The search is conducted only within 10%, at the beginning of the file."
164 :version "21.1"
165 :type '(repeat regexp)
166 :group 'change-log)
167
163f7b71
GM
168(defface change-log-date-face
169 '((t (:inherit font-lock-string-face)))
170 "Face used to highlight dates in date lines."
171 :version "21.1"
172 :group 'change-log)
173
174(defface change-log-name-face
175 '((t (:inherit font-lock-constant-face)))
176 "Face for highlighting author names."
177 :version "21.1"
178 :group 'change-log)
179
180(defface change-log-email-face
181 '((t (:inherit font-lock-variable-name-face)))
182 "Face for highlighting author email addresses."
183 :version "21.1"
184 :group 'change-log)
185
186(defface change-log-file-face
187 '((t (:inherit font-lock-function-name-face)))
188 "Face for highlighting file names."
189 :version "21.1"
190 :group 'change-log)
191
192(defface change-log-list-face
193 '((t (:inherit font-lock-keyword-face)))
194 "Face for highlighting parenthesized lists of functions or variables."
195 :version "21.1"
196 :group 'change-log)
598f34fa 197
163f7b71
GM
198(defface change-log-conditionals-face
199 '((t (:inherit font-lock-variable-name-face)))
200 "Face for highlighting conditionals of the form `[...]'."
201 :version "21.1"
202 :group 'change-log)
203
204(defface change-log-function-face
205 '((t (:inherit font-lock-variable-name-face)))
206 "Face for highlighting items of the form `<....>'."
207 :version "21.1"
208 :group 'change-log)
209
210(defface change-log-acknowledgement-face
211 '((t (:inherit font-lock-comment-face)))
212 "Face for highlighting acknowledgments."
213 :version "21.1"
214 :group 'change-log)
776d8e16 215
5f562719 216(defvar change-log-font-lock-keywords
5572c1d1
SM
217 '(;;
218 ;; Date lines, new and old styles.
3307b0ca 219 ("^\\sw.........[0-9:+ ]*"
163f7b71 220 (0 'change-log-date-face)
97a3278b 221 ;; Name and e-mail; some people put e-mail in parens, not angles.
8f4ca9a5 222 ("\\([^<(]+?\\)[ \t]*[(<]\\([A-Za-z0-9_.-]+@[A-Za-z0-9_.-]+\\)[>)]" nil nil
163f7b71
GM
223 (1 'change-log-name-face)
224 (2 'change-log-email-face)))
5572c1d1
SM
225 ;;
226 ;; File names.
227 ("^\t\\* \\([^ ,:([\n]+\\)"
163f7b71 228 (1 'change-log-file-face)
97a3278b 229 ;; Possibly further names in a list:
163f7b71 230 ("\\=, \\([^ ,:([\n]+\\)" nil nil (1 'change-log-file-face))
97a3278b 231 ;; Possibly a parenthesized list of names:
163f7b71
GM
232 ("\\= (\\([^) ,:\n]+\\)" nil nil (1 'change-log-list-face))
233 ("\\=, *\\([^) ,:\n]+\\)" nil nil (1 'change-log-list-face)))
5572c1d1
SM
234 ;;
235 ;; Function or variable names.
97a3278b 236 ("^\t(\\([^) ,:\n]+\\)"
163f7b71
GM
237 (1 'change-log-list-face)
238 ("\\=, *\\([^) ,:\n]+\\)" nil nil (1 'change-log-list-face)))
5572c1d1
SM
239 ;;
240 ;; Conditionals.
163f7b71 241 ("\\[!?\\([^]\n]+\\)\\]\\(:\\| (\\)" (1 'change-log-conditionals-face))
5572c1d1 242 ;;
a8d693d8 243 ;; Function of change.
163f7b71 244 ("<\\([^>\n]+\\)>\\(:\\| (\\)" (1 'change-log-function-face))
a8d693d8 245 ;;
a3cab9f0 246 ;; Acknowledgements.
a8d693d8 247 ("\\(^\t\\| \\)\\(From\\|Patch\\(es\\)? by\\|Report\\(ed by\\| from\\)\\|Suggest\\(ed by\\|ion from\\)\\)"
163f7b71 248 2 'change-log-acknowledgement-face))
5f562719
RS
249 "Additional expressions to highlight in Change Log mode.")
250
5960adc7 251(defvar change-log-mode-map (make-sparse-keymap)
45c50c5d 252 "Keymap for Change Log major mode.")
45c50c5d 253
51bd1843
EN
254(defvar change-log-time-zone-rule nil
255 "Time zone used for calculating change log time stamps.
256It takes the same format as the TZ argument of `set-time-zone-rule'.
257If nil, use local time.")
258
df63ae66 259(defun add-log-iso8601-time-zone (time)
51bd1843
EN
260 (let* ((utc-offset (or (car (current-time-zone time)) 0))
261 (sign (if (< utc-offset 0) ?- ?+))
262 (sec (abs utc-offset))
263 (ss (% sec 60))
264 (min (/ sec 60))
265 (mm (% min 60))
266 (hh (/ min 60)))
267 (format (cond ((not (zerop ss)) "%c%02d:%02d:%02d")
268 ((not (zerop mm)) "%c%02d:%02d")
269 (t "%c%02d"))
270 sign hh mm ss)))
271
df63ae66
RS
272(defun add-log-iso8601-time-string ()
273 (if change-log-time-zone-rule
274 (let ((tz (getenv "TZ"))
275 (now (current-time)))
276 (unwind-protect
277 (progn
918f4ac3 278 (set-time-zone-rule change-log-time-zone-rule)
df63ae66
RS
279 (concat
280 (format-time-string "%Y-%m-%d " now)
281 (add-log-iso8601-time-zone now)))
282 (set-time-zone-rule tz)))
283 (format-time-string "%Y-%m-%d")))
284
84fc2cfa 285(defun change-log-name ()
075a6629 286 "Return (system-dependent) default name for a change log file."
84fc2cfa 287 (or change-log-default-name
513063cf 288 (if (eq system-type 'vax-vms)
4a047d23
RS
289 "$CHANGE_LOG$.TXT"
290 "ChangeLog")))
84fc2cfa 291
287d149f 292;;;###autoload
84fc2cfa
ER
293(defun prompt-for-change-log-name ()
294 "Prompt for a change log name."
117aaf60
KH
295 (let* ((default (change-log-name))
296 (name (expand-file-name
297 (read-file-name (format "Log file (default %s): " default)
298 nil default))))
299 ;; Handle something that is syntactically a directory name.
300 ;; Look for ChangeLog or whatever in that directory.
301 (if (string= (file-name-nondirectory name) "")
302 (expand-file-name (file-name-nondirectory default)
303 name)
304 ;; Handle specifying a file that is a directory.
305 (if (file-directory-p name)
306 (expand-file-name (file-name-nondirectory default)
307 (file-name-as-directory name))
308 name))))
84fc2cfa 309
776d8e16 310(defun change-log-version-number-search ()
5960adc7
DL
311 "Return version number of current buffer's file.
312This is the value returned by `vc-workfile-version' or, if that is
313nil, by matching `change-log-version-number-regexp-list'."
776d8e16 314 (let* ((size (buffer-size))
fc9b0554 315 (limit
5960adc7
DL
316 ;; The version number can be anywhere in the file, but
317 ;; restrict search to the file beginning: 10% should be
318 ;; enough to prevent some mishits.
776d8e16 319 ;;
5960adc7
DL
320 ;; Apply percentage only if buffer size is bigger than
321 ;; approx 100 lines.
fc9b0554 322 (if (> size (* 100 80)) (+ (point) (/ size 10)))))
d68f7f1b 323 (or (and buffer-file-name (vc-workfile-version buffer-file-name))
5960adc7
DL
324 (save-restriction
325 (widen)
fc9b0554
SM
326 (let ((regexps change-log-version-number-regexp-list)
327 version)
5960adc7
DL
328 (while regexps
329 (save-excursion
330 (goto-char (point-min))
fc9b0554 331 (when (re-search-forward (pop regexps) limit t)
5960adc7 332 (setq version (match-string 1)
fc9b0554
SM
333 regexps nil))))
334 version)))))
776d8e16
GM
335
336
45a13f0d 337;;;###autoload
d68f7f1b 338(defun find-change-log (&optional file-name buffer-file)
45a13f0d 339 "Find a change log file for \\[add-change-log-entry] and return the name.
a82e2ed5
RS
340
341Optional arg FILE-NAME specifies the file to use.
de98fcaf
RS
342If FILE-NAME is nil, use the value of `change-log-default-name'.
343If 'change-log-default-name' is nil, behave as though it were 'ChangeLog'
344\(or whatever we use on this operating system).
345
346If 'change-log-default-name' contains a leading directory component, then
513063cf 347simply find it in the current directory. Otherwise, search in the current
de98fcaf 348directory and its successive parents for a file so named.
45a13f0d
RM
349
350Once a file is found, `change-log-default-name' is set locally in the
d68f7f1b
SM
351current buffer to the complete file name.
352Optional arg BUFFER-FILE overrides `buffer-file-name'."
a82e2ed5
RS
353 ;; If user specified a file name or if this buffer knows which one to use,
354 ;; just use that.
45a13f0d 355 (or file-name
de98fcaf
RS
356 (setq file-name (and change-log-default-name
357 (file-name-directory change-log-default-name)
358 change-log-default-name))
a82e2ed5
RS
359 (progn
360 ;; Chase links in the source file
361 ;; and use the change log in the dir where it points.
d68f7f1b 362 (setq file-name (or (and (or buffer-file buffer-file-name)
a82e2ed5 363 (file-name-directory
d68f7f1b
SM
364 (file-chase-links
365 (or buffer-file buffer-file-name))))
a82e2ed5
RS
366 default-directory))
367 (if (file-directory-p file-name)
368 (setq file-name (expand-file-name (change-log-name) file-name)))
369 ;; Chase links before visiting the file.
370 ;; This makes it easier to use a single change log file
371 ;; for several related directories.
372 (setq file-name (file-chase-links file-name))
373 (setq file-name (expand-file-name file-name))
374 ;; Move up in the dir hierarchy till we find a change log file.
375 (let ((file1 file-name)
376 parent-dir)
377 (while (and (not (or (get-file-buffer file1) (file-exists-p file1)))
378 (progn (setq parent-dir
379 (file-name-directory
380 (directory-file-name
381 (file-name-directory file1))))
382 ;; Give up if we are already at the root dir.
383 (not (string= (file-name-directory file1)
384 parent-dir))))
385 ;; Move up to the parent dir and try again.
513063cf 386 (setq file1 (expand-file-name
a82e2ed5
RS
387 (file-name-nondirectory (change-log-name))
388 parent-dir)))
389 ;; If we found a change log in a parent, use that.
390 (if (or (get-file-buffer file1) (file-exists-p file1))
391 (setq file-name file1)))))
392 ;; Make a local variable in this buffer so we needn't search again.
393 (set (make-local-variable 'change-log-default-name) file-name)
394 file-name)
45a13f0d 395
2eb7ccf4
SM
396(defun add-log-file-name (buffer-file log-file)
397 ;; Never want to add a change log entry for the ChangeLog file itself.
398 (unless (or (null buffer-file) (string= buffer-file log-file))
d68f7f1b
SM
399 (if add-log-file-name-function
400 (funcall add-log-file-name-function buffer-file)
401 (setq buffer-file
402 (if (string-match
403 (concat "^" (regexp-quote (file-name-directory log-file)))
404 buffer-file)
405 (substring buffer-file (match-end 0))
406 (file-name-nondirectory buffer-file)))
407 ;; If we have a backup file, it's presumably because we're
408 ;; comparing old and new versions (e.g. for deleted
409 ;; functions) and we'll want to use the original name.
410 (if (backup-file-name-p buffer-file)
411 (file-name-sans-versions buffer-file)
412 buffer-file))))
2eb7ccf4 413
84fc2cfa 414;;;###autoload
287d149f 415(defun add-change-log-entry (&optional whoami file-name other-window new-entry)
d882f144 416 "Find change log file, and add an entry for today and an item for this file.
83afd62c
KH
417Optional arg WHOAMI (interactive prefix) non-nil means prompt for user
418name and site.
419
d882f144
RS
420Second arg FILE-NAME is file name of the change log.
421If nil, use the value of `change-log-default-name'.
422
287d149f 423Third arg OTHER-WINDOW non-nil means visit in other window.
d882f144 424
287d149f 425Fourth arg NEW-ENTRY non-nil means always create a new entry at the front;
3697b807
DL
426never append to an existing entry. Option `add-log-keep-changes-together'
427otherwise affects whether a new entry is created.
428
598f34fa
SS
429Option `add-log-always-start-new-record' non-nil means always create a
430new record, even when the last record was made on the same date and by
431the same person.
432
d882f144
RS
433The change log file can start with a copyright notice and a copying
434permission notice. The first blank line indicates the end of these
435notices.
436
3697b807
DL
437Today's date is calculated according to `change-log-time-zone-rule' if
438non-nil, otherwise in local time."
84fc2cfa
ER
439 (interactive (list current-prefix-arg
440 (prompt-for-change-log-name)))
550d8777
RS
441 (or add-log-full-name
442 (setq add-log-full-name (user-full-name)))
443 (or add-log-mailing-address
444 (setq add-log-mailing-address user-mail-address))
02ec1592
BF
445 (if whoami
446 (progn
447 (setq add-log-full-name (read-input "Full name: " add-log-full-name))
84fc2cfa
ER
448 ;; Note that some sites have room and phone number fields in
449 ;; full name which look silly when inserted. Rather than do
450 ;; anything about that here, let user give prefix argument so that
451 ;; s/he can edit the full name field in prompter if s/he wants.
02ec1592
BF
452 (setq add-log-mailing-address
453 (read-input "Mailing address: " add-log-mailing-address))))
2eb7ccf4 454
d68f7f1b
SM
455 (let* ((defun (add-log-current-defun))
456 (version (and change-log-version-info-enabled
457 (change-log-version-number-search)))
8f530b95
SM
458 (buf-file-name (if add-log-buffer-file-name-function
459 (funcall add-log-buffer-file-name-function)
460 buffer-file-name))
461 (buffer-file (if buf-file-name (expand-file-name buf-file-name)))
d68f7f1b
SM
462 (file-name (expand-file-name
463 (or file-name (find-change-log file-name buffer-file))))
d882f144
RS
464 ;; Set ITEM to the file name to use in the new item.
465 (item (add-log-file-name buffer-file file-name))
d68f7f1b 466 bound)
2eb7ccf4 467
7cd017ba
SM
468 (unless (equal file-name buffer-file-name)
469 (if (or other-window (window-dedicated-p (selected-window)))
470 (find-file-other-window file-name)
471 (find-file file-name)))
675a998f
RS
472 (or (eq major-mode 'change-log-mode)
473 (change-log-mode))
84fc2cfa
ER
474 (undo-boundary)
475 (goto-char (point-min))
d882f144
RS
476
477 ;; If file starts with a copyright and permission notice, skip them.
478 ;; Assume they end at first blank line.
479 (when (looking-at "Copyright")
480 (search-forward "\n\n")
481 (skip-chars-forward "\n"))
482
483 ;; Advance into first entry if it is usable; else make new one.
c1b3ae42
CW
484 (let ((new-entries (mapcar (lambda (addr)
485 (concat (funcall add-log-time-format)
486 " " add-log-full-name
487 " <" addr ">"))
488 (if (consp add-log-mailing-address)
489 add-log-mailing-address
490 (list add-log-mailing-address)))))
598f34fa 491 (if (and (not add-log-always-start-new-record)
c1b3ae42
CW
492 (let ((hit nil))
493 (dolist (entry new-entries hit)
494 (when (looking-at (regexp-quote entry))
495 (setq hit t)))))
51bd1843 496 (forward-line 1)
c1b3ae42
CW
497 (insert (nth (random (length new-entries))
498 new-entries)
499 "\n\n")
d882f144 500 (forward-line -1)))
82f4acaf 501
d882f144
RS
502 ;; Determine where we should stop searching for a usable
503 ;; item to add to, within this entry.
3697b807 504 (setq bound
d882f144 505 (save-excursion
3697b807
DL
506 (if (looking-at "\n*[^\n* \t]")
507 (skip-chars-forward "\n")
508 (if add-log-keep-changes-together
509 (forward-page) ; page delimits entries for date
510 (forward-paragraph))) ; paragraph delimits entries for file
511 (point)))
d882f144
RS
512
513 ;; Now insert the new line for this item.
3697b807 514 (cond ((re-search-forward "^\\s *\\*\\s *$" bound t)
d882f144
RS
515 ;; Put this file name into the existing empty item.
516 (if item
517 (insert item)))
287d149f 518 ((and (not new-entry)
e172f546
KH
519 (let (case-fold-search)
520 (re-search-forward
d882f144 521 (concat (regexp-quote (concat "* " item))
e172f546
KH
522 ;; Don't accept `foo.bar' when
523 ;; looking for `foo':
524 "\\(\\s \\|[(),:]\\)")
3697b807 525 bound t)))
d882f144 526 ;; Add to the existing item for the same file.
82f4acaf 527 (re-search-forward "^\\s *$\\|^\\s \\*")
e172f546 528 (goto-char (match-beginning 0))
3697b807
DL
529 ;; Delete excess empty lines; make just 2.
530 (while (and (not (eobp)) (looking-at "^\\s *$"))
5960adc7 531 (delete-region (point) (line-beginning-position 2)))
918f4ac3 532 (insert-char ?\n 2)
3697b807
DL
533 (forward-line -2)
534 (indent-relative-maybe))
21d7e080 535 (t
d882f144 536 ;; Make a new item.
dd309224
RM
537 (while (looking-at "\\sW")
538 (forward-line 1))
09b389d0 539 (while (and (not (eobp)) (looking-at "^\\s *$"))
5960adc7 540 (delete-region (point) (line-beginning-position 2)))
918f4ac3 541 (insert-char ?\n 3)
1832dbd1 542 (forward-line -2)
dd309224 543 (indent-to left-margin)
918f4ac3 544 (insert "* ")
d882f144 545 (if item (insert item))))
1832dbd1 546 ;; Now insert the function name, if we have one.
d882f144 547 ;; Point is at the item for this file,
21d7e080 548 ;; either at the end of the line or at the first blank line.
5a9ac14b
SM
549 (if (not defun)
550 ;; No function name, so put in a colon unless we have just a star.
551 (unless (save-excursion
552 (beginning-of-line 1)
553 (looking-at "\\s *\\(\\*\\s *\\)?$"))
554 (insert ": ")
555 (if version (insert version ?\ )))
556 ;; Make it easy to get rid of the function name.
557 (undo-boundary)
5960adc7
DL
558 (unless (save-excursion
559 (beginning-of-line 1)
5a9ac14b
SM
560 (looking-at "\\s *$"))
561 (insert ?\ ))
562 ;; See if the prev function name has a message yet or not.
563 ;; If not, merge the two items.
564 (let ((pos (point-marker)))
565 (skip-syntax-backward " ")
566 (skip-chars-backward "):")
567 (if (and (looking-at "):")
fc9b0554
SM
568 (let ((pos (save-excursion (backward-sexp 1) (point))))
569 (when (equal (buffer-substring pos (point)) defun)
570 (delete-region pos (point)))
571 (> fill-column (+ (current-column) (length defun) 4))))
572 (progn (skip-chars-backward ", ")
573 (delete-region (point) pos)
574 (unless (memq (char-before) '(?\()) (insert ", ")))
5a9ac14b
SM
575 (if (looking-at "):")
576 (delete-region (+ 1 (point)) (line-end-position)))
577 (goto-char pos)
578 (insert "("))
579 (set-marker pos nil))
580 (insert defun "): ")
581 (if version (insert version ?\ )))))
84fc2cfa 582
84fc2cfa
ER
583;;;###autoload
584(defun add-change-log-entry-other-window (&optional whoami file-name)
d882f144
RS
585 "Find change log file in other window and add entry and item.
586This is just like `add-change-log-entry' except that it displays
587the change log file in another window."
84fc2cfa
ER
588 (interactive (if current-prefix-arg
589 (list current-prefix-arg
590 (prompt-for-change-log-name))))
591 (add-change-log-entry whoami file-name t))
82f4acaf 592;;;###autoload (define-key ctl-x-4-map "a" 'add-change-log-entry-other-window)
84fc2cfa 593
fc9b0554
SM
594(defvar add-log-indent-text 0)
595
596(defun add-log-indent ()
597 (let* ((indent
598 (save-excursion
599 (beginning-of-line)
600 (skip-chars-forward " \t")
601 (cond
602 ((and (looking-at "\\(.*\\) [^ \n].*[^ \n] <.*>$")
603 ;; Matching the output of add-log-time-format is difficult,
604 ;; but I'll get it has at least two adjacent digits.
605 (string-match "[[:digit:]][[:digit:]]" (match-string 1)))
606 0)
607 ((looking-at "[^*(]")
608 (+ (current-left-margin) add-log-indent-text))
609 (t (current-left-margin)))))
610 (pos (save-excursion (indent-line-to indent) (point))))
611 (if (> pos (point)) (goto-char pos))))
612
613
1da56800 614;;;###autoload
7cd017ba 615(define-derived-mode change-log-mode text-mode "Change Log"
eb8c3be9 616 "Major mode for editing change logs; like Indented Text Mode.
09b389d0 617Prevents numeric backups and sets `left-margin' to 8 and `fill-column' to 74.
3697b807 618New log entries are usually made with \\[add-change-log-entry] or \\[add-change-log-entry-other-window].
5516387c
RM
619Each entry behaves as a paragraph, and the entries for one day as a page.
620Runs `change-log-mode-hook'."
7cd017ba 621 (setq left-margin 8
4f675a8c 622 fill-column 74
60f10a06
KH
623 indent-tabs-mode t
624 tab-width 8)
625 (set (make-local-variable 'fill-paragraph-function)
626 'change-log-fill-paragraph)
fc9b0554
SM
627 (set (make-local-variable 'indent-line-function) 'add-log-indent)
628 (set (make-local-variable 'tab-always-indent) nil)
4b7d4d0d
DL
629 ;; We really do want "^" in paragraph-start below: it is only the
630 ;; lines that begin at column 0 (despite the left-margin of 8) that
631 ;; we are looking for. Adding `* ' allows eliding the blank line
632 ;; between entries for different files.
c9cfb0f2 633 (set (make-local-variable 'paragraph-start) "\\s *$\\|\f\\|^\\<")
4b7d4d0d 634 (set (make-local-variable 'paragraph-separate) paragraph-start)
2c91c85c
RS
635 ;; Match null string on the date-line so that the date-line
636 ;; is grouped with what follows.
964141f2 637 (set (make-local-variable 'page-delimiter) "^\\<\\|^\f")
dd309224 638 (set (make-local-variable 'version-control) 'never)
7cd017ba
SM
639 (set (make-local-variable 'smerge-resolve-function)
640 'change-log-resolve-conflict)
dd309224 641 (set (make-local-variable 'adaptive-fill-regexp) "\\s *")
4b286eca 642 (set (make-local-variable 'font-lock-defaults)
7cd017ba 643 '(change-log-font-lock-keywords t nil nil backward-paragraph)))
21d7e080 644
287d149f
RM
645;; It might be nice to have a general feature to replace this. The idea I
646;; have is a variable giving a regexp matching text which should not be
647;; moved from bol by filling. change-log-mode would set this to "^\\s *\\s(".
648;; But I don't feel up to implementing that today.
649(defun change-log-fill-paragraph (&optional justify)
650 "Fill the paragraph, but preserve open parentheses at beginning of lines.
651Prefix arg means justify as well."
652 (interactive "P")
8d6467a4
RS
653 (let ((end (progn (forward-paragraph) (point)))
654 (beg (progn (backward-paragraph) (point)))
14ee1953 655 (paragraph-start (concat paragraph-start "\\|\\s *\\s(")))
8d6467a4
RS
656 (fill-region beg end justify)
657 t))
287d149f 658\f
fcad5199 659(defcustom add-log-current-defun-header-regexp
a8d693d8 660 "^\\([[:upper:]][[:upper:]_ ]*[[:upper:]_]\\|[-_[:alpha:]]+\\)[ \t]*[:=]"
fcad5199
RS
661 "*Heuristic regexp used by `add-log-current-defun' for unknown major modes."
662 :type 'regexp
663 :group 'change-log)
21d7e080 664
fb644f48
EN
665;;;###autoload
666(defvar add-log-lisp-like-modes
3697b807 667 '(emacs-lisp-mode lisp-mode scheme-mode dsssl-mode lisp-interaction-mode)
fb644f48
EN
668 "*Modes that look like Lisp to `add-log-current-defun'.")
669
670;;;###autoload
671(defvar add-log-c-like-modes
672 '(c-mode c++-mode c++-c-mode objc-mode)
673 "*Modes that look like C to `add-log-current-defun'.")
674
675;;;###autoload
676(defvar add-log-tex-like-modes
677 '(TeX-mode plain-TeX-mode LaTeX-mode plain-tex-mode latex-mode)
678 "*Modes that look like TeX to `add-log-current-defun'.")
679
e332f80b 680;;;###autoload
21d7e080
ER
681(defun add-log-current-defun ()
682 "Return name of function definition point is in, or nil.
683
63314951 684Understands C, Lisp, LaTeX (\"functions\" are chapters, sections, ...),
5960adc7 685Texinfo (@node titles) and Perl.
21d7e080
ER
686
687Other modes are handled by a heuristic that looks in the 10K before
688point for uppercase headings starting in the first column or
5960adc7 689identifiers followed by `:' or `='. See variables
c1356086 690`add-log-current-defun-header-regexp' and
5ef08021 691`add-log-current-defun-function'.
21d7e080
ER
692
693Has a preference of looking backwards."
2cc0b765
RS
694 (condition-case nil
695 (save-excursion
696 (let ((location (point)))
5960adc7
DL
697 (cond (add-log-current-defun-function
698 (funcall add-log-current-defun-function))
c1356086 699 ((memq major-mode add-log-lisp-like-modes)
a0151877 700 ;; If we are now precisely at the beginning of a defun,
2cc0b765
RS
701 ;; make sure beginning-of-defun finds that one
702 ;; rather than the previous one.
703 (or (eobp) (forward-char 1))
704 (beginning-of-defun)
5960adc7
DL
705 ;; Make sure we are really inside the defun found,
706 ;; not after it.
42b1fc29
RS
707 (when (and (looking-at "\\s(")
708 (progn (end-of-defun)
709 (< location (point)))
710 (progn (forward-sexp -1)
711 (>= location (point))))
712 (if (looking-at "\\s(")
713 (forward-char 1))
714 ;; Skip the defining construct name, typically "defun"
715 ;; or "defvar".
716 (forward-sexp 1)
717 ;; The second element is usually a symbol being defined.
718 ;; If it is not, use the first symbol in it.
63c7727f 719 (skip-chars-forward " \t\n'(")
5960adc7
DL
720 (buffer-substring-no-properties (point)
721 (progn (forward-sexp 1)
722 (point)))))
fb644f48
EN
723 ((and (memq major-mode add-log-c-like-modes)
724 (save-excursion
725 (beginning-of-line)
726 ;; Use eq instead of = here to avoid
727 ;; error when at bob and char-after
728 ;; returns nil.
729 (while (eq (char-after (- (point) 2)) ?\\)
730 (forward-line -1))
731 (looking-at "[ \t]*#[ \t]*define[ \t]")))
2cc0b765
RS
732 ;; Handle a C macro definition.
733 (beginning-of-line)
734 (while (eq (char-after (- (point) 2)) ?\\) ;not =; note above
735 (forward-line -1))
736 (search-forward "define")
737 (skip-chars-forward " \t")
5960adc7
DL
738 (buffer-substring-no-properties (point)
739 (progn (forward-sexp 1)
740 (point))))
fb644f48 741 ((memq major-mode add-log-c-like-modes)
2cc0b765
RS
742 (beginning-of-line)
743 ;; See if we are in the beginning part of a function,
744 ;; before the open brace. If so, advance forward.
745 (while (not (looking-at "{\\|\\(\\s *$\\)"))
746 (forward-line 1))
747 (or (eobp)
748 (forward-char 1))
749 (beginning-of-defun)
5960adc7
DL
750 (when (progn (end-of-defun)
751 (< location (point)))
752 (backward-sexp 1)
753 (let (beg tem)
754
755 (forward-line -1)
756 ;; Skip back over typedefs of arglist.
757 (while (and (not (bobp))
758 (looking-at "[ \t\n]"))
759 (forward-line -1))
760 ;; See if this is using the DEFUN macro used in Emacs,
761 ;; or the DEFUN macro used by the C library.
762 (if (condition-case nil
763 (and (save-excursion
764 (end-of-line)
765 (while (= (preceding-char) ?\\)
766 (end-of-line 2))
767 (backward-sexp 1)
768 (beginning-of-line)
769 (setq tem (point))
770 (looking-at "DEFUN\\b"))
771 (>= location tem))
772 (error nil))
773 (progn
774 (goto-char tem)
775 (down-list 1)
776 (if (= (char-after (point)) ?\")
777 (progn
778 (forward-sexp 1)
779 (skip-chars-forward " ,")))
780 (buffer-substring-no-properties
781 (point)
782 (progn (forward-sexp 1)
783 (point))))
784 (if (looking-at "^[+-]")
785 (change-log-get-method-definition)
786 ;; Ordinary C function syntax.
787 (setq beg (point))
788 (if (and
789 ;; Protect against "Unbalanced parens" error.
790 (condition-case nil
791 (progn
792 (down-list 1) ; into arglist
793 (backward-up-list 1)
794 (skip-chars-backward " \t")
795 t)
796 (error nil))
797 ;; Verify initial pos was after
798 ;; real start of function.
799 (save-excursion
800 (goto-char beg)
801 ;; For this purpose, include the line
802 ;; that has the decl keywords. This
803 ;; may also include some of the
804 ;; comments before the function.
805 (while (and (not (bobp))
806 (save-excursion
807 (forward-line -1)
808 (looking-at "[^\n\f]")))
809 (forward-line -1))
810 (>= location (point)))
811 ;; Consistency check: going down and up
812 ;; shouldn't take us back before BEG.
813 (> (point) beg))
814 (let (end middle)
815 ;; Don't include any final whitespace
816 ;; in the name we use.
817 (skip-chars-backward " \t\n")
818 (setq end (point))
819 (backward-sexp 1)
820 ;; Now find the right beginning of the name.
821 ;; Include certain keywords if they
822 ;; precede the name.
823 (setq middle (point))
824 (forward-word -1)
825 ;; Ignore these subparts of a class decl
826 ;; and move back to the class name itself.
827 (while (looking-at "public \\|private ")
828 (skip-chars-backward " \t:")
829 (setq end (point))
830 (backward-sexp 1)
831 (setq middle (point))
832 (forward-word -1))
833 (and (bolp)
834 (looking-at
835 "enum \\|struct \\|union \\|class ")
836 (setq middle (point)))
837 (goto-char end)
838 (when (eq (preceding-char) ?=)
839 (forward-char -1)
840 (skip-chars-backward " \t")
841 (setq end (point)))
842 (buffer-substring-no-properties
843 middle end))))))))
fb644f48 844 ((memq major-mode add-log-tex-like-modes)
2cc0b765 845 (if (re-search-backward
5960adc7
DL
846 "\\\\\\(sub\\)*\\(section\\|paragraph\\|chapter\\)"
847 nil t)
2cc0b765
RS
848 (progn
849 (goto-char (match-beginning 0))
5960adc7
DL
850 (buffer-substring-no-properties
851 (1+ (point)) ; without initial backslash
852 (line-end-position)))))
2cc0b765 853 ((eq major-mode 'texinfo-mode)
3071ee28 854 (if (re-search-backward "^@node[ \t]+\\([^,\n]+\\)" nil t)
5960adc7 855 (match-string-no-properties 1)))
c06e9d8b 856 ((memq major-mode '(perl-mode cperl-mode))
5a9ac14b 857 (if (re-search-backward "^sub[ \t]+\\([^({ \t\n]+\\)" nil t)
5960adc7
DL
858 (match-string-no-properties 1)))
859 ;; Emacs's autoconf-mode installs its own
860 ;; `add-log-current-defun-function'. This applies to
861 ;; a different mode apparently for editing .m4
862 ;; autoconf source.
f654865f 863 ((eq major-mode 'autoconf-mode)
5960adc7
DL
864 (if (re-search-backward
865 "^\\(\\(m4_\\)?define\\|A._DEFUN\\)(\\[?\\([A-Za-z0-9_]+\\)" nil t)
866 (match-string-no-properties 3)))
2cc0b765
RS
867 (t
868 ;; If all else fails, try heuristics
c1356086
GM
869 (let (case-fold-search
870 result)
2cc0b765 871 (end-of-line)
c1356086
GM
872 (when (re-search-backward
873 add-log-current-defun-header-regexp
874 (- (point) 10000)
875 t)
5960adc7
DL
876 (setq result (or (match-string-no-properties 1)
877 (match-string-no-properties 0)))
c1356086
GM
878 ;; Strip whitespace away
879 (when (string-match "\\([^ \t\n\r\f].*[^ \t\n\r\f]\\)"
880 result)
5960adc7 881 (setq result (match-string-no-properties 1 result)))
c1356086 882 result))))))
2cc0b765 883 (error nil)))
ef15f270 884
83afd62c 885(defvar change-log-get-method-definition-md)
15319a8f 886
83afd62c 887;; Subroutine used within change-log-get-method-definition.
59c1a7de
RS
888;; Add the last match in the buffer to the end of `md',
889;; followed by the string END; move to the end of that match.
83afd62c
KH
890(defun change-log-get-method-definition-1 (end)
891 (setq change-log-get-method-definition-md
892 (concat change-log-get-method-definition-md
5960adc7 893 (match-string 1)
15319a8f 894 end))
59c1a7de
RS
895 (goto-char (match-end 0)))
896
83afd62c 897(defun change-log-get-method-definition ()
075a6629 898"For objective C, return the method name if we are in a method."
83afd62c 899 (let ((change-log-get-method-definition-md "["))
59c1a7de 900 (save-excursion
f27f16ed 901 (if (re-search-backward "^@implementation\\s-*\\([A-Za-z_]*\\)" nil t)
83afd62c 902 (change-log-get-method-definition-1 " ")))
59c1a7de
RS
903 (save-excursion
904 (cond
f27f16ed 905 ((re-search-forward "^\\([-+]\\)[ \t\n\f\r]*\\(([^)]*)\\)?\\s-*" nil t)
83afd62c 906 (change-log-get-method-definition-1 "")
59c1a7de
RS
907 (while (not (looking-at "[{;]"))
908 (looking-at
f27f16ed 909 "\\([A-Za-z_]*:?\\)\\s-*\\(([^)]*)\\)?[A-Za-z_]*[ \t\n\f\r]*")
83afd62c
KH
910 (change-log-get-method-definition-1 ""))
911 (concat change-log-get-method-definition-md "]"))))))
075a6629
DL
912\f
913(defun change-log-sortable-date-at ()
914 "Return date of log entry in a consistent form for sorting.
915Point is assumed to be at the start of the entry."
916 (require 'timezone)
917 (if (looking-at "^\\sw.........[0-9:+ ]*")
918 (let ((date (match-string-no-properties 0)))
919 (if date
920 (if (string-match "\\(....\\)-\\(..\\)-\\(..\\)\\s-+" date)
921 (concat (match-string 1 date) (match-string 2 date)
922 (match-string 3 date))
923 (condition-case nil
924 (timezone-make-date-sortable date)
925 (error nil)))))
926 (error "Bad date")))
59c1a7de 927
7cd017ba
SM
928(defun change-log-resolve-conflict ()
929 "Function to be used in `smerge-resolve-function'."
930 (let ((buf (current-buffer)))
931 (with-temp-buffer
932 (insert-buffer-substring buf (match-beginning 1) (match-end 1))
933 (save-match-data (change-log-mode))
934 (let ((other-buf (current-buffer)))
935 (with-current-buffer buf
936 (save-excursion
937 (save-restriction
938 (narrow-to-region (match-beginning 0) (match-end 0))
939 (replace-match (match-string 3) t t)
940 (change-log-merge other-buf))))))))
941
075a6629
DL
942;;;###autoload
943(defun change-log-merge (other-log)
944 "Merge the contents of ChangeLog file OTHER-LOG with this buffer.
945Both must be found in Change Log mode (since the merging depends on
7cd017ba
SM
946the appropriate motion commands). OTHER-LOG can be either a file name
947or a buffer.
075a6629 948
918f4ac3
DL
949Entries are inserted in chronological order. Both the current and
950old-style time formats for entries are supported."
075a6629
DL
951 (interactive "*fLog file name to merge: ")
952 (if (not (eq major-mode 'change-log-mode))
953 (error "Not in Change Log mode"))
7cd017ba
SM
954 (let ((other-buf (if (bufferp other-log) other-log
955 (find-file-noselect other-log)))
075a6629
DL
956 (buf (current-buffer))
957 date1 start end)
958 (save-excursion
959 (goto-char (point-min))
960 (set-buffer other-buf)
961 (goto-char (point-min))
962 (if (not (eq major-mode 'change-log-mode))
963 (error "%s not found in Change Log mode" other-log))
964 ;; Loop through all the entries in OTHER-LOG.
965 (while (not (eobp))
966 (setq date1 (change-log-sortable-date-at))
967 (setq start (point)
968 end (progn (forward-page) (point)))
969 ;; Look for an entry in original buffer that isn't later.
970 (with-current-buffer buf
971 (while (and (not (eobp))
972 (string< date1 (change-log-sortable-date-at)))
973 (forward-page))
974 (if (not (eobp))
975 (insert-buffer-substring other-buf start end)
976 ;; At the end of the original buffer, insert a newline to
977 ;; separate entries and then the rest of the file being
7cd017ba
SM
978 ;; merged.
979 (unless (or (bobp)
980 (and (= ?\n (char-before))
981 (or (<= (1- (point)) (point-min))
982 (= ?\n (char-before (1- (point)))))))
983 (insert "\n"))
984 ;; Move to the end of it to terminate outer loop.
985 (with-current-buffer other-buf
986 (goto-char (point-max)))
987 (insert-buffer-substring other-buf start)))))))
ef15f270 988
918f4ac3
DL
989;;;###autoload
990(defun change-log-redate ()
991 "Fix any old-style date entries in the current log file to default format."
992 (interactive)
993 (require 'timezone)
994 (save-excursion
995 (goto-char (point-min))
996 (while (re-search-forward "^\\sw.........[0-9:+ ]*" nil t)
997 (unless (= 12 (- (match-end 0) (match-beginning 0)))
998 (let* ((date (save-match-data
999 (timezone-fix-time (match-string 0) nil nil)))
1000 (zone (if (consp (aref date 6))
1001 (nth 1 (aref date 6)))))
1002 (replace-match (format-time-string
1003 "%Y-%m-%d "
1004 (encode-time (aref date 5)
1005 (aref date 4)
1006 (aref date 3)
1007 (aref date 2)
1008 (aref date 1)
1009 (aref date 0)
1010 zone))))))))
1011
1da56800
RS
1012(provide 'add-log)
1013
fd7fa35a 1014;;; add-log.el ends here