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