comparison lisp/packages/add-log.el @ 0:376386a54a3c r19-14

Import from CVS: tag r19-14
author cvs
date Mon, 13 Aug 2007 08:45:50 +0200
parents
children ac2d302a0011
comparison
equal deleted inserted replaced
-1:000000000000 0:376386a54a3c
1 ;;; add-log.el --- change log maintenance commands for Emacs
2
3 ;; Copyright (C) 1985, 1986, 1988, 1993, 1994 Free Software Foundation, Inc.
4
5 ;; Keywords: maint
6
7 ;; This file is part of XEmacs.
8
9 ;; XEmacs is free software; you can redistribute it and/or modify it
10 ;; under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; any later version.
13
14 ;; XEmacs is distributed in the hope that it will be useful, but
15 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 ;; General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with XEmacs; see the file COPYING. If not, write to the Free
21 ;; Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
22
23 ;;; Synched up with: FSF 19.30.
24
25 ;;; Commentary:
26
27 ;; This facility is documented in the Emacs Manual.
28
29 ;;; Code:
30
31 ;;;###autoload
32 (defvar change-log-default-name nil
33 "*Name of a change log file for \\[add-change-log-entry].")
34
35 ;;;###autoload
36 (defvar add-log-current-defun-function nil
37 "\
38 *If non-nil, function to guess name of current function from surrounding text.
39 \\[add-change-log-entry] calls this function (if nil, `add-log-current-defun'
40 instead) with no arguments. It returns a string or nil if it cannot guess.")
41
42 ;;;###autoload
43 (defvar add-log-full-name nil
44 "*Full name of user, for inclusion in ChangeLog daily headers.
45 This defaults to the value returned by the `user-full-name' function.")
46
47 ;; So that the dump-time value doesn't go into loaddefs.el with the autoload.
48 (or add-log-full-name (setq add-log-full-name (user-full-name)))
49
50 ;;;###autoload
51 (defvar add-log-mailing-address nil
52 "*Electronic mail address of user, for inclusion in ChangeLog daily headers.
53 This defaults to the value of `user-mail-address'.")
54
55 ;; So that the dump-time value doesn't go into loaddefs.el with the autoload.
56 (or add-log-mailing-address
57 (setq add-log-mailing-address user-mail-address))
58
59 (defvar change-log-font-lock-keywords
60 '(("^[SMTWF].+" . font-lock-function-name-face) ; Date line.
61 ("^\t\\* \\([^ :\n]+\\)" 1 font-lock-comment-face) ; File name.
62 ("\(\\([^)\n]+\\)\)" 1 font-lock-keyword-face)) ; Function name.
63 "Additional expressions to highlight in Change Log mode.")
64 (put 'change-log-mode 'font-lock-defaults
65 '(change-log-font-lock-keywords t))
66
67 (defvar change-log-mode-map nil
68 "Keymap for Change Log major mode.")
69 (if change-log-mode-map
70 nil
71 (setq change-log-mode-map (make-sparse-keymap))
72 (define-key change-log-mode-map "\M-q" 'change-log-fill-paragraph))
73
74 (defun change-log-name ()
75 (or change-log-default-name
76 (if (eq system-type 'vax-vms)
77 "$CHANGE_LOG$.TXT"
78 (if (or (eq system-type 'ms-dos) (eq system-type 'windows-nt))
79 "changelo"
80 "ChangeLog"))))
81
82 ;;;###autoload
83 (defun prompt-for-change-log-name ()
84 "Prompt for a change log name."
85 (let* ((default (change-log-name))
86 (name (expand-file-name
87 (read-file-name (format "Log file (default %s): " default)
88 nil default))))
89 ;; Handle something that is syntactically a directory name.
90 ;; Look for ChangeLog or whatever in that directory.
91 (if (string= (file-name-nondirectory name) "")
92 (expand-file-name (file-name-nondirectory default)
93 name)
94 ;; Handle specifying a file that is a directory.
95 (if (file-directory-p name)
96 (expand-file-name (file-name-nondirectory default)
97 (file-name-as-directory name))
98 name))))
99
100 ;;;###autoload
101 (defun find-change-log (&optional file-name)
102 "Find a change log file for \\[add-change-log-entry] and return the name.
103 Optional arg FILE-NAME specifies the file to use.
104 If FILE-NAME is nil, use the value of `change-log-default-name'.
105 If 'change-log-default-name' is nil, behave as though it were 'ChangeLog'
106 \(or whatever we use on this operating system).
107
108 If 'change-log-default-name' contains a leading directory component, then
109 simply find it in the current directory. Otherwise, search in the current
110 directory and its successive parents for a file so named.
111
112 Once a file is found, `change-log-default-name' is set locally in the
113 current buffer to the complete file name."
114 ;; If user specified a file name or if this buffer knows which one to use,
115 ;; just use that.
116 (or file-name
117 (setq file-name (and change-log-default-name
118 (file-name-directory change-log-default-name)
119 change-log-default-name))
120 (progn
121 ;; Chase links in the source file
122 ;; and use the change log in the dir where it points.
123 (setq file-name (or (and buffer-file-name
124 (file-name-directory
125 (file-chase-links buffer-file-name)))
126 default-directory))
127 (if (file-directory-p file-name)
128 (setq file-name (expand-file-name (change-log-name) file-name)))
129 ;; Chase links before visiting the file.
130 ;; This makes it easier to use a single change log file
131 ;; for several related directories.
132 (setq file-name (file-chase-links file-name))
133 (setq file-name (expand-file-name file-name))
134 ;; Move up in the dir hierarchy till we find a change log file.
135 (let ((file1 file-name)
136 parent-dir)
137 (while (and (not (or (get-file-buffer file1) (file-exists-p file1)))
138 (progn (setq parent-dir
139 (file-name-directory
140 (directory-file-name
141 (file-name-directory file1))))
142 ;; Give up if we are already at the root dir.
143 (not (string= (file-name-directory file1)
144 parent-dir))))
145 ;; Move up to the parent dir and try again.
146 (setq file1 (expand-file-name
147 (file-name-nondirectory (change-log-name))
148 parent-dir)))
149 ;; If we found a change log in a parent, use that.
150 (if (or (get-file-buffer file1) (file-exists-p file1))
151 (setq file-name file1)))))
152 ;; Make a local variable in this buffer so we needn't search again.
153 (set (make-local-variable 'change-log-default-name) file-name)
154 file-name)
155
156 ;;;###autoload
157 (defun add-change-log-entry (&optional whoami file-name other-window new-entry)
158 "Find change log file and add an entry for today.
159 Optional arg (interactive prefix) non-nil means prompt for user name and site.
160 Second arg is file name of change log. If nil, uses `change-log-default-name'.
161 Third arg OTHER-WINDOW non-nil means visit in other window.
162 Fourth arg NEW-ENTRY non-nil means always create a new entry at the front;
163 never append to an existing entry."
164 (interactive (list current-prefix-arg
165 (prompt-for-change-log-name)))
166 (or add-log-full-name
167 (setq add-log-full-name (user-full-name)))
168 (or add-log-mailing-address
169 (setq add-log-mailing-address user-mail-address))
170 (if whoami
171 (progn
172 (setq add-log-full-name (read-string "Full name: " add-log-full-name))
173 ;; Note that some sites have room and phone number fields in
174 ;; full name which look silly when inserted. Rather than do
175 ;; anything about that here, let user give prefix argument so that
176 ;; s/he can edit the full name field in prompter if s/he wants.
177 (setq add-log-mailing-address
178 (read-string "Mailing address: " add-log-mailing-address))))
179 (let ((defun (funcall (or add-log-current-defun-function
180 'add-log-current-defun)))
181 paragraph-end entry)
182
183 (setq file-name (expand-file-name (find-change-log file-name)))
184
185 ;; Set ENTRY to the file name to use in the new entry.
186 (and buffer-file-name
187 ;; Never want to add a change log entry for the ChangeLog file itself.
188 (not (string= buffer-file-name file-name))
189 (setq entry (if (string-match
190 (concat "^" (regexp-quote (file-name-directory
191 file-name)))
192 buffer-file-name)
193 (substring buffer-file-name (match-end 0))
194 (file-name-nondirectory buffer-file-name))))
195
196 (if (and other-window (not (equal file-name buffer-file-name)))
197 (find-file-other-window file-name)
198 (find-file file-name))
199 (undo-boundary)
200 (goto-char (point-min))
201 (if (looking-at (concat (regexp-quote (substring (current-time-string)
202 0 10))
203 ".* " (regexp-quote add-log-full-name)
204 " <" (regexp-quote add-log-mailing-address)))
205 (forward-line 1)
206 (insert (current-time-string)
207 " " add-log-full-name
208 " <" add-log-mailing-address ">\n\n"))
209
210 ;; Search only within the first paragraph.
211 (if (looking-at "\n*[^\n* \t]")
212 (skip-chars-forward "\n")
213 (forward-paragraph 1))
214 (setq paragraph-end (point))
215 (goto-char (point-min))
216
217 ;; Now insert the new line for this entry.
218 (cond ((re-search-forward "^\\s *\\*\\s *$" paragraph-end t)
219 ;; Put this file name into the existing empty entry.
220 (if entry
221 (insert entry)))
222 ((and (not new-entry)
223 (let (case-fold-search)
224 (re-search-forward
225 (concat (regexp-quote (concat "* " entry))
226 ;; Don't accept `foo.bar' when
227 ;; looking for `foo':
228 "\\(\\s \\|[(),:]\\)")
229 paragraph-end t)))
230 ;; Add to the existing entry for the same file.
231 (re-search-forward "^\\s *$\\|^\\s \\*")
232 (goto-char (match-beginning 0))
233 ;; Delete excess empty lines; make just 2.
234 (while (and (not (eobp)) (looking-at "^\\s *$"))
235 (delete-region (point) (save-excursion (forward-line 1) (point))))
236 (insert "\n\n")
237 (forward-line -2)
238 (indent-relative-maybe))
239 (t
240 ;; Make a new entry.
241 (forward-line 1)
242 (while (looking-at "\\sW")
243 (forward-line 1))
244 (while (and (not (eobp)) (looking-at "^\\s *$"))
245 (delete-region (point) (save-excursion (forward-line 1) (point))))
246 (insert "\n\n\n")
247 (forward-line -2)
248 (indent-to left-margin)
249 (insert "* " (or entry ""))))
250 ;; Now insert the function name, if we have one.
251 ;; Point is at the entry for this file,
252 ;; either at the end of the line or at the first blank line.
253 (if defun
254 (progn
255 ;; Make it easy to get rid of the function name.
256 (undo-boundary)
257 (insert (if (save-excursion
258 (beginning-of-line 1)
259 (looking-at "\\s *$"))
260 ""
261 " ")
262 "(" defun "): "))
263 ;; No function name, so put in a colon unless we have just a star.
264 (if (not (save-excursion
265 (beginning-of-line 1)
266 (looking-at "\\s *\\(\\*\\s *\\)?$")))
267 (insert ": ")))))
268
269 ;;;###autoload
270 (defun add-change-log-entry-other-window (&optional whoami file-name)
271 "Find change log file in other window and add an entry for today.
272 Optional arg (interactive prefix) non-nil means prompt for user name and site.
273 Second arg is file name of change log. \
274 If nil, uses `change-log-default-name'."
275 (interactive (if current-prefix-arg
276 (list current-prefix-arg
277 (prompt-for-change-log-name))))
278 (add-change-log-entry whoami file-name t))
279 ;;;###autoload (define-key ctl-x-4-map "a" 'add-change-log-entry-other-window)
280
281 (defvar change-log-mode-map nil
282 "Keymap for Change Log major mode.")
283 (if change-log-mode-map
284 nil
285 (setq change-log-mode-map (make-sparse-keymap))
286 (set-keymap-name change-log-mode-map 'change-log-mode-map)
287 (define-key change-log-mode-map "\M-q" 'change-log-fill-paragraph))
288
289 ;;;###autoload
290 (defun change-log-mode ()
291 "Major mode for editing change logs; like Indented Text Mode.
292 Prevents numeric backups and sets `left-margin' to 8 and `fill-column' to 74.
293 New log entries are usually made with \\[add-change-log-entry] or \\[add-change-log-entry-other-window].
294 Each entry behaves as a paragraph, and the entries for one day as a page.
295 Runs `change-log-mode-hook'."
296 (interactive)
297 (kill-all-local-variables)
298 (indented-text-mode)
299 (setq major-mode 'change-log-mode
300 mode-name "Change Log"
301 left-margin 8
302 fill-column 74)
303 (use-local-map change-log-mode-map)
304 ;; Let each entry behave as one paragraph:
305 ;; We really do want "^" in paragraph-start below: it is only the lines that
306 ;; begin at column 0 (despite the left-margin of 8) that we are looking for.
307 (set (make-local-variable 'paragraph-start) "\\s *$\\|\f\\|^\\sw")
308 (set (make-local-variable 'paragraph-separate) "\\s *$\\|\f\\|^\\sw")
309 ;; Let all entries for one day behave as one page.
310 ;; Match null string on the date-line so that the date-line
311 ;; is grouped with what follows.
312 (set (make-local-variable 'page-delimiter) "^\\<\\|^\f")
313 (set (make-local-variable 'version-control) 'never)
314 (set (make-local-variable 'adaptive-fill-regexp) "\\s *")
315 (run-hooks 'change-log-mode-hook))
316
317 ;; It might be nice to have a general feature to replace this. The idea I
318 ;; have is a variable giving a regexp matching text which should not be
319 ;; moved from bol by filling. change-log-mode would set this to "^\\s *\\s(".
320 ;; But I don't feel up to implementing that today.
321 (defun change-log-fill-paragraph (&optional justify)
322 "Fill the paragraph, but preserve open parentheses at beginning of lines.
323 Prefix arg means justify as well."
324 (interactive "P")
325 (let ((end (save-excursion (forward-paragraph) (point)))
326 (beg (save-excursion (backward-paragraph)(point)))
327 (paragraph-start (concat paragraph-start "\\|\\s *\\s(")))
328 (fill-region beg end justify)))
329
330 (defvar add-log-current-defun-header-regexp
331 "^\\([A-Z][A-Z_ ]*[A-Z_]\\|[-_a-zA-Z]+\\)[ \t]*[:=]"
332 "*Heuristic regexp used by `add-log-current-defun' for unknown major modes.")
333
334 ;;;###autoload
335 (defun add-log-current-defun ()
336 "Return name of function definition point is in, or nil.
337
338 Understands C, Lisp, LaTeX (\"functions\" are chapters, sections, ...),
339 Texinfo (@node titles), Perl, and Fortran.
340
341 Other modes are handled by a heuristic that looks in the 10K before
342 point for uppercase headings starting in the first column or
343 identifiers followed by `:' or `=', see variable
344 `add-log-current-defun-header-regexp'.
345
346 Has a preference of looking backwards."
347 (condition-case nil
348 (save-excursion
349 (let ((location (point)))
350 (cond ((memq major-mode '(emacs-lisp-mode lisp-mode scheme-mode
351 lisp-interaction-mode))
352 ;; If we are now precisely at the beginning of a defun,
353 ;; make sure beginning-of-defun finds that one
354 ;; rather than the previous one.
355 (or (eobp) (forward-char 1))
356 (beginning-of-defun)
357 ;; Make sure we are really inside the defun found, not after it.
358 (if (and (progn (end-of-defun)
359 (< location (point)))
360 (progn (forward-sexp -1)
361 (>= location (point))))
362 (progn
363 (if (looking-at "\\s(")
364 (forward-char 1))
365 (forward-sexp 1)
366 (skip-chars-forward " '")
367 (buffer-substring (point)
368 (progn (forward-sexp 1) (point))))))
369 ((and (memq major-mode '(c-mode c++-mode c++-c-mode objc-mode))
370 (save-excursion (beginning-of-line)
371 ;; Use eq instead of = here to avoid
372 ;; error when at bob and char-after
373 ;; returns nil.
374 (while (eq (char-after (- (point) 2)) ?\\)
375 (forward-line -1))
376 (looking-at "[ \t]*#[ \t]*define[ \t]")))
377 ;; Handle a C macro definition.
378 (beginning-of-line)
379 (while (eq (char-after (- (point) 2)) ?\\) ;not =; note above
380 (forward-line -1))
381 (search-forward "define")
382 (skip-chars-forward " \t")
383 (buffer-substring (point)
384 (progn (forward-sexp 1) (point))))
385 ((memq major-mode '(c-mode c++-mode c++-c-mode objc-mode))
386 (beginning-of-line)
387 ;; See if we are in the beginning part of a function,
388 ;; before the open brace. If so, advance forward.
389 (while (not (looking-at "{\\|\\(\\s *$\\)"))
390 (forward-line 1))
391 (or (eobp)
392 (forward-char 1))
393 (beginning-of-defun)
394 (if (progn (end-of-defun)
395 (< location (point)))
396 (progn
397 (backward-sexp 1)
398 (let (beg tem)
399
400 (forward-line -1)
401 ;; Skip back over typedefs of arglist.
402 (while (and (not (bobp))
403 (looking-at "[ \t\n]"))
404 (forward-line -1))
405 ;; See if this is using the DEFUN macro used in Emacs,
406 ;; or the DEFUN macro used by the C library.
407 (if (condition-case nil
408 (and (save-excursion
409 (end-of-line)
410 (while (= (preceding-char) ?\\)
411 (end-of-line 2))
412 (backward-sexp 1)
413 (beginning-of-line)
414 (setq tem (point))
415 (looking-at "DEFUN\\b"))
416 (>= location tem))
417 (error nil))
418 (progn
419 (goto-char tem)
420 (down-list 1)
421 (if (= (char-after (point)) ?\")
422 (progn
423 (forward-sexp 1)
424 (skip-chars-forward " ,")))
425 (buffer-substring (point)
426 (progn (forward-sexp 1) (point))))
427 (if (looking-at "^[+-]")
428 (get-method-definition)
429 ;; Ordinary C function syntax.
430 (setq beg (point))
431 (if (and (condition-case nil
432 ;; Protect against "Unbalanced parens" error.
433 (progn
434 (down-list 1) ; into arglist
435 (backward-up-list 1)
436 (skip-chars-backward " \t")
437 t)
438 (error nil))
439 ;; Verify initial pos was after
440 ;; real start of function.
441 (save-excursion
442 (goto-char beg)
443 ;; For this purpose, include the line
444 ;; that has the decl keywords. This
445 ;; may also include some of the
446 ;; comments before the function.
447 (while (and (not (bobp))
448 (save-excursion
449 (forward-line -1)
450 (looking-at "[^\n\f]")))
451 (forward-line -1))
452 (>= location (point)))
453 ;; Consistency check: going down and up
454 ;; shouldn't take us back before BEG.
455 (> (point) beg))
456 (let (end middle)
457 ;; Don't include any final newline
458 ;; in the name we use.
459 (if (= (preceding-char) ?\n)
460 (forward-char -1))
461 (setq end (point))
462 (backward-sexp 1)
463 ;; Now find the right beginning of the name.
464 ;; Include certain keywords if they
465 ;; precede the name.
466 (setq middle (point))
467 (forward-word -1)
468 ;; Ignore these subparts of a class decl
469 ;; and move back to the class name itself.
470 (while (looking-at "public \\|private ")
471 (skip-chars-backward " \t:")
472 (setq end (point))
473 (backward-sexp 1)
474 (setq middle (point))
475 (forward-word -1))
476 (and (bolp)
477 (looking-at "struct \\|union \\|class ")
478 (setq middle (point)))
479 (buffer-substring middle end)))))))))
480 ((memq major-mode
481 '(TeX-mode plain-TeX-mode LaTeX-mode;; tex-mode.el
482 plain-tex-mode latex-mode;; cmutex.el
483 ))
484 (if (re-search-backward
485 "\\\\\\(sub\\)*\\(section\\|paragraph\\|chapter\\)" nil t)
486 (progn
487 (goto-char (match-beginning 0))
488 (buffer-substring (1+ (point));; without initial backslash
489 (progn
490 (end-of-line)
491 (point))))))
492 ((eq major-mode 'texinfo-mode)
493 (if (re-search-backward "^@node[ \t]+\\([^,\n]+\\)" nil t)
494 (buffer-substring (match-beginning 1)
495 (match-end 1))))
496 ((eq major-mode 'perl-mode)
497 (if (re-search-backward "^sub[ \t]+\\([^ \t\n]+\\)" nil t)
498 (buffer-substring (match-beginning 1)
499 (match-end 1))))
500 ((eq major-mode 'fortran-mode)
501 ;; must be inside function body for this to work
502 (beginning-of-fortran-subprogram)
503 (let ((case-fold-search t)) ; case-insensitive
504 ;; search for fortran subprogram start
505 (if (re-search-forward
506 "^[ \t]*\\(program\\|subroutine\\|function\
507 \\|[ \ta-z0-9*]*[ \t]+function\\)"
508 nil t)
509 (progn
510 ;; move to EOL or before first left paren
511 (if (re-search-forward "[(\n]" nil t)
512 (progn (forward-char -1)
513 (skip-chars-backward " \t"))
514 (end-of-line))
515 ;; Use the name preceding that.
516 (buffer-substring (point)
517 (progn (forward-sexp -1)
518 (point)))))))
519 (t
520 ;; If all else fails, try heuristics
521 (let (case-fold-search)
522 (end-of-line)
523 (if (re-search-backward add-log-current-defun-header-regexp
524 (- (point) 10000)
525 t)
526 (buffer-substring (match-beginning 1)
527 (match-end 1))))))))
528 (error nil)))
529
530 (defvar get-method-definition-md)
531
532 ;; Subroutine used within get-method-definition.
533 ;; Add the last match in the buffer to the end of `md',
534 ;; followed by the string END; move to the end of that match.
535 (defun get-method-definition-1 (end)
536 (setq get-method-definition-md
537 (concat get-method-definition-md
538 (buffer-substring (match-beginning 1) (match-end 1))
539 end))
540 (goto-char (match-end 0)))
541
542 ;; For objective C, return the method name if we are in a method.
543 (defun get-method-definition ()
544 (let ((get-method-definition-md "["))
545 (save-excursion
546 (if (re-search-backward "^@implementation\\s-*\\([A-Za-z_]*\\)" nil t)
547 (get-method-definition-1 " ")))
548 (save-excursion
549 (cond
550 ((re-search-forward "^\\([-+]\\)[ \t\n\f\r]*\\(([^)]*)\\)?\\s-*" nil t)
551 (get-method-definition-1 "")
552 (while (not (looking-at "[{;]"))
553 (looking-at
554 "\\([A-Za-z_]*:?\\)\\s-*\\(([^)]*)\\)?[A-Za-z_]*[ \t\n\f\r]*")
555 (get-method-definition-1 ""))
556 (concat get-method-definition-md "]"))))))
557
558
559 (provide 'add-log)
560
561 ;;; add-log.el ends here