comparison lisp/etags.el @ 217:d44af0c54775 r20-4b7

Import from CVS: tag r20-4b7
author cvs
date Mon, 13 Aug 2007 10:08:34 +0200
parents
children 262b8bb4a523
comparison
equal deleted inserted replaced
216:43306a74e31c 217:d44af0c54775
1 ;;; etags.el --- etags facility for Emacs
2
3 ;; Copyright 1985, 1986, 1988, 1990 Free Software Foundation, Inc.
4
5 ;; Keywords: tools
6
7 ;; This file is part of XEmacs.
8
9 ;; GNU Emacs is distributed in the hope that it will be useful,
10 ;; but WITHOUT ANY WARRANTY. No author or distributor
11 ;; accepts responsibility to anyone for the consequences of using it
12 ;; or for whether it serves any particular purpose or works at all,
13 ;; unless he says so in writing. Refer to the GNU Emacs General Public
14 ;; License for full details.
15
16 ;; Everyone is granted permission to copy, modify and redistribute
17 ;; GNU Emacs, but only under the conditions described in the
18 ;; GNU Emacs General Public License. A copy of this license is
19 ;; supposed to have been given to you along with GNU Emacs so you
20 ;; can know your rights and responsibilities. It should be in a
21 ;; file named COPYING. Among other things, the copyright notice
22 ;; and this notice must be preserved on all copies.
23
24 ;;; Synched up with: Not synched with FSF. (This file is almost
25 ;;; completely different from FSF's etags.el. It appears that an
26 ;;; early version of this file (tags.el) was rewritten by two
27 ;;; different people; we got one, FSF got the other. Various
28 ;;; people have said that our version is better and faster.
29
30
31 ;; TODO:
32 ;; 1. place cursor in echo area while searching
33 ;; 2. document!
34 ;; 3. determine semantics of interactively setting the tags file for a buffer
35
36 ;; Comments with **** mean something is left to be done.
37
38 ;; Derived from the original lisp/tags.el.
39
40 ;; Ideas and code from the work of the following people:
41 ;; Andy Norman <ange@hplb.hpl.hp.com>, author of ange-tags.el
42 ;; Ramana Rao <rao@arisia.xerox.com>
43 ;; John Sturdy <jcgs@harlqn.co.uk>, author of tags-helper.el
44 ;; Henry Kautz <kautz@allegra.att.com>, author of tag-completion.el
45 ;; Dan LaLiberte <liberte@cs.uiuc.edu>, author of local-tags.el
46 ;; Tom Dietterich <tgd@turing.cs.orst.edu>, author of quest.el
47 ;; The author(s) of lisp/simple.el
48 ;; Duke Briscoe <briscoe@cs.yale.edu>
49 ;; Lynn Slater <lrs@indetech.com>, author of location.el
50 ;; Shinichirou Sugou <shin@sgtp.apple.juice.or.jp>
51 ;; an unidentified anonymous elisp hacker
52 ;; Kyle Jones <kyle_jones@wonderworks.com>
53 ;; added "Exact match, then inexact" code
54 ;; added support for include directive.
55
56 (require 'thing)
57
58
59 ;; Auxiliary functions
60
61 (defun tags-delete (item list)
62 "Delete the item from the list, testing with equal. Copies the list."
63 (delete item (copy-list list)))
64
65 (defun tags-remove-duplicates (list)
66 "Delete equal duplicates from the list; copies the list."
67 (let (res)
68 (dolist (el list)
69 (unless (member el res)
70 (push el res)))
71 (nreverse res)))
72
73
74 ;; Tag tables for a buffer
75
76 (defgroup etags nil
77 "Etags facility for Emacs"
78 :prefix "tags-"
79 :group 'tools)
80
81
82 ;;;###autoload
83 (defcustom tags-build-completion-table 'ask
84 "*If this variable is nil, then tags completion is disabled.
85 If this variable is t, then things which prompt for tags will do so with
86 completion across all known tags.
87 If this variable is the symbol `ask', then you will be asked whether each
88 tags table should be added to the completion list as it is read in.
89 (With the exception that for very small tags tables, you will not be asked,
90 since they can be parsed quickly.)"
91 :type '(radio (const :tag "Disabled" nil)
92 (const :tag "Complete All" t)
93 (const :tag "Ask" ask))
94 :group 'etags)
95
96 ;;;###autoload
97 (defcustom tags-always-exact nil
98 "*If this variable is non-nil, then tags always looks for exact matches."
99 :type 'boolean
100 :group 'etags)
101
102 ;;;###autoload
103 (defcustom tag-table-alist nil
104 "*A list which determines which tags files are active for a buffer.
105 This is not really an association list, in that all elements are
106 checked. The CAR of each element of this list is a pattern against
107 which the buffer's file name is compared; if it matches, then the CDR
108 of the list should be the name of the tags table to use. If more than
109 one element of this list matches the buffer's file name, then all of
110 the associated tags tables will be used. Earlier ones will be
111 searched first.
112
113 If the CAR of elements of this list are strings, then they are treated
114 as regular-expressions against which the file is compared (like the
115 auto-mode-alist). If they are not strings, then they are evaluated.
116 If they evaluate to non-nil, then the current buffer is considered to
117 match.
118
119 If the CDR of the elements of this list are strings, then they are
120 assumed to name a TAGS file. If they name a directory, then the string
121 \"TAGS\" is appended to them to get the file name. If they are not
122 strings, then they are evaluated, and must return an appropriate string.
123
124 For example:
125 (setq tag-table-alist
126 '((\"/usr/src/public/perl/\" . \"/usr/src/public/perl/perl-3.0/\")
127 (\"\\\\.el$\" . \"/usr/local/emacs/src/\")
128 (\"/jbw/gnu/\" . \"/usr15/degree/stud/jbw/gnu/\")
129 (\"\" . \"/usr/local/emacs/src/\")
130 ))
131
132 This means that anything in the /usr/src/public/perl/ directory should use
133 the TAGS file /usr/src/public/perl/perl-3.0/TAGS; and file ending in .el should
134 use the TAGS file /usr/local/emacs/src/TAGS; and anything in or below the
135 directory /jbw/gnu/ should use the TAGS file /usr15/degree/stud/jbw/gnu/TAGS.
136 A file called something like \"/usr/jbw/foo.el\" would use both the TAGS files
137 /usr/local/emacs/src/TAGS and /usr15/degree/stud/jbw/gnu/TAGS (in that order)
138 because it matches both patterns.
139
140 If the buffer-local variable `buffer-tag-table' is set, then it names a tags
141 table that is searched before all others when find-tag is executed from this
142 buffer.
143
144 If there is a file called \"TAGS\" in the same directory as the file in
145 question, then that tags file will always be used as well (after the
146 `buffer-tag-table' but before the tables specified by this list.)
147
148 If the variable tags-file-name is set, then the tags file it names will apply
149 to all buffers (for backwards compatibility.) It is searched first.
150 "
151 :type '(repeat (cons (choice :value ""
152 (regexp :tag "Buffer regexp")
153 (function :tag "Expression"))
154 (string :tag "Tag file or directory")))
155 :group 'etags)
156
157 (defvar buffer-tag-table nil
158 "*The additional name of one TAGS table to be used for this buffer.
159 You can set this with meta-x set-buffer-tag-table. See the documentation
160 for the variable `tag-table-alist' for more information.")
161 (make-variable-buffer-local 'buffer-tag-table)
162
163 (defcustom tags-file-name nil
164 "*The name of the tags-table used by all buffers.
165 This is for backwards compatibility, and is largely supplanted by the
166 variable tag-table-alist."
167 :type '(choice (const nil) string)
168 :group 'etags)
169
170
171 ;; XEmacs change: added tags-auto-read-changed-tag-files
172 (defcustom tags-auto-read-changed-tag-files nil
173 "*If non-nil, always re-read changed TAGS file without prompting, if nil
174 then prompt if changed TAGS file should be re-read."
175 :type 'boolean
176 :group 'etags)
177
178 (defun buffer-tag-table-list ()
179 "Returns a list (ordered) of the tags tables which should be used for
180 the current buffer."
181 (let (result expression)
182 (when buffer-tag-table
183 (push buffer-tag-table result))
184 (when (file-readable-p (concat default-directory "TAGS"))
185 (push (concat default-directory "TAGS") result))
186 (let ((key (or buffer-file-name
187 (concat default-directory (buffer-name))))
188 (alist tag-table-alist))
189 (while alist
190 (setq expression (car (car alist)))
191 ;; If the car of the alist item is a string, apply it as a regexp
192 ;; to the buffer-file-name. Otherwise, evaluate it. If the
193 ;; regexp matches, or the expression evaluates non-nil, then this
194 ;; item in tag-table-alist applies to this buffer.
195 (when (if (stringp expression)
196 (string-match expression key)
197 (condition-case nil
198 (eval expression)
199 (error nil)))
200 ;; Now evaluate the cdr of the alist item to get the name of
201 ;; the tag table file.
202 (setq expression
203 (condition-case nil
204 (eval (cdr (car alist)))
205 (error nil)))
206 (if (stringp expression)
207 (setq result (cons expression result))
208 (error "Expression in tag-table-alist evaluated to non-string")))
209 (pop alist)))
210 (or result tags-file-name
211 ;; **** I don't know if this is the right place to do this,
212 ;; **** Maybe it would be better to do this after (delq nil result).
213 (call-interactively 'visit-tags-table))
214 (when tags-file-name
215 (setq result (nconc result (list tags-file-name))))
216 (setq result
217 (mapcar
218 (lambda (name)
219 (if (file-directory-p name)
220 (setq name (concat (file-name-as-directory name) "TAGS")))
221 (if (file-readable-p name)
222 (save-current-buffer
223 ;; get-tag-table-buffer has side-effects
224 (set-buffer (get-tag-table-buffer name))
225 buffer-file-name)))
226 result))
227 (setq result (delq nil result))
228 (or result (error "Buffer has no associated tag tables"))
229 (tags-remove-duplicates (nreverse result))))
230
231 ;;;###autoload
232 (defun visit-tags-table (file)
233 "Tell tags commands to use tags table file FILE first.
234 FILE should be the name of a file created with the `etags' program.
235 A directory name is ok too; it means file TAGS in that directory."
236 (interactive (list (read-file-name "Visit tags table: (default TAGS) "
237 default-directory
238 (expand-file-name "TAGS" default-directory)
239 t)))
240 (if (string-equal file "")
241 (setq tags-file-name nil)
242 (progn
243 (setq file (expand-file-name file))
244 (if (file-directory-p file)
245 (setq file (expand-file-name "TAGS" file)))
246 (setq tags-file-name file))))
247
248 (defun set-buffer-tag-table (file)
249 "In addition to the tags tables specified by the variable `tag-table-alist',
250 each buffer can have one additional table. This command sets that.
251 See the documentation for the variable `tag-table-alist' for more information."
252 (interactive
253 (list
254 (read-file-name "Visit tags table: (directory sufficient) "
255 nil default-directory t)))
256 (or file (error "No TAGS file name supplied"))
257 (setq file (expand-file-name file))
258 (when (file-directory-p file)
259 (setq file (concat file "TAGS")))
260 (or (file-exists-p file) (error "TAGS file missing: %s" file))
261 (setq buffer-tag-table file))
262
263
264 ;; Manipulating the tag table buffer
265
266 (defconst tag-table-completion-status nil
267 "Indicates whether a completion table has been built, or has explicitly not
268 been built. this is nil, t, or 'disabled.")
269 (make-variable-buffer-local 'tag-table-completion-status)
270
271 (defcustom make-tags-files-invisible nil
272 "*If non-nil, TAGS-files will not show up in buffer-lists or be
273 selectable (or deletable.)"
274 :type 'boolean
275 :group 'etags)
276
277 (defconst tag-table-files nil
278 "If the current buffer is a TAGS table, this holds a list of the files
279 referenced by this file, or nil if that hasn't been computed yet.")
280 (make-variable-buffer-local 'tag-table-files)
281
282 (defun get-tag-table-buffer (tag-table)
283 "Returns a buffer visiting the given TAGS table, reverting if appropriate,
284 and possibly building a completion-table."
285 (or (stringp tag-table)
286 (error "Bad tags file name supplied: %s" tag-table))
287 ;; add support for removing symbolic links from name
288 (if (fboundp 'symlink-expand-file-name)
289 (setq tag-table (symlink-expand-file-name tag-table)))
290 (let (buf build-completion check-name)
291 (setq buf (get-file-buffer tag-table))
292 (or buf
293 (if (file-readable-p tag-table)
294 (setq buf (find-file-noselect tag-table)
295 check-name t)
296 (error "No such tags file: %s" tag-table)))
297 (with-current-buffer buf
298 ;; make the TAGS buffer invisible
299 (when (and check-name
300 make-tags-files-invisible
301 (string-match "\\`[^ ]" (buffer-name)))
302 (rename-buffer (generate-new-buffer-name
303 (concat " " (buffer-name)))))
304 (or (verify-visited-file-modtime buf)
305 ;; XEmacs change: added tags-auto-read-changed-tag-files
306 (cond ((or tags-auto-read-changed-tag-files (yes-or-no-p
307 (format "Tags file %s has changed, read new contents? "
308 tag-table)))
309 (when tags-auto-read-changed-tag-files
310 (message "Tags file %s has changed, reading new contents..."
311 tag-table))
312 (revert-buffer t t)
313 (if (eq tag-table-completion-status t)
314 (setq tag-table-completion-status nil))
315 (setq tag-table-files nil))))
316 (or (eq (char-after 1) ?\f)
317 (error "File %s not a valid tags file" tag-table))
318 (or (memq tag-table-completion-status '(t disabled))
319 (setq build-completion t))
320 (and build-completion
321 (if (cond
322 ((eq tags-build-completion-table nil)
323 nil)
324 ((eq tags-build-completion-table t)
325 t)
326 ((eq tags-build-completion-table 'ask)
327 ;; don't bother asking for small ones
328 (or (< (buffer-size) 20000)
329 (y-or-n-p
330 (format "Build tag completion table for %s? "
331 tag-table))))
332 (t (error
333 "tags-build-completion-table is not t, nil, or ask.")))
334 (condition-case nil
335 (progn
336 (add-to-tag-completion-table)
337 (setq tag-table-completion-status t))
338 ;; Allow user to C-g out correctly
339 (quit
340 (setq tag-table-completion-status nil)
341 (setq quit-flag t)
342 (eval t)))
343 (setq tag-table-completion-status 'disabled))))
344 buf))
345
346 (defun file-of-tag ()
347 "Return the file name of the file whose tags point is within.
348 Assumes the tag table is the current buffer.
349 File name returned is relative to tag table file's directory."
350 (let ((opoint (point))
351 prev size)
352 (save-excursion
353 (goto-char (point-min))
354 (while (< (point) opoint)
355 (forward-line 1)
356 (end-of-line)
357 (skip-chars-backward "^,\n")
358 (setq prev (point)
359 size (read (current-buffer)))
360 (goto-char prev)
361 (forward-line 1)
362 ;; New include syntax
363 ;; filename,include
364 ;; tacked on to the end of a tag file means use filename
365 ;; as a tag file before giving up.
366 ;; Skip it here.
367 (if (not (eq size 'include))
368 (forward-char size)))
369 (goto-char (1- prev))
370 (buffer-substring (point) (point-at-bol)))))
371
372 (defun tag-table-include-files ()
373 "Return all file names associated with `include' directives in a tag buffer."
374 ;; New include syntax
375 ;; filename,include
376 ;; tacked on to the end of a tag file means use filename as a
377 ;; tag file before giving up.
378 (let ((files nil))
379 (save-excursion
380 (goto-char (point-min))
381 (while (re-search-forward "\f\n\\(.*\\),include$" nil t)
382 (setq files (cons (match-string 1) files))))
383 files ))
384
385 (defun tag-table-files (tag-table)
386 "Returns a list of the files referenced by the named TAGS table."
387 (with-current-buffer (get-tag-table-buffer tag-table)
388 (or tag-table-files
389 (let (files prev size)
390 (goto-char (point-min))
391 (while (not (eobp))
392 (forward-line 1)
393 (end-of-line)
394 (skip-chars-backward "^,\n")
395 (setq prev (point)
396 size (read (current-buffer)))
397 (goto-char prev)
398 (push (expand-file-name (buffer-substring (1- (point))
399 (point-at-bol))
400 default-directory)
401 files)
402 (forward-line 1)
403 (forward-char size))
404 (setq tag-table-files (nreverse files))))
405 tag-table-files))
406
407 ;; **** should this be on previous page?
408 (defun buffer-tag-table-files ()
409 "Returns a list of all files referenced by all TAGS tables that
410 this buffer uses."
411 (apply #'nconc
412 (mapcar #'tag-table-files (buffer-tag-table-list))))
413
414
415 ;; Building the completion table
416
417 ;; Test cases for building completion table; must handle these properly:
418 ;; Lisp_Int, XSETINT, current_column 60,2282
419 ;; Lisp_Int, XSETINT, point>NumCharacters ? 0 : CharAt(363,9935
420 ;; Lisp_Int, XSETINT, point<=FirstCharacter ? 0 : CharAt(366,10108
421 ;; point<=FirstCharacter || CharAt(378,10630
422 ;; point>NumCharacters || CharAt(382,10825
423 ;; DEFUN ("x-set-foreground-color", Fx_set_foreground_color,191,4562
424 ;; DEFUN ("x-set-foreground-color", Fx_set_foreground_color,191,4562
425 ;; DEFUN ("*", Ftimes,1172,32079
426 ;; DEFUN ("/=", Fneq,1035,28839
427 ;; defun_internal 4199,101362
428 ;; int pure[PURESIZE / sizeof 53,1564
429 ;; char staticvec1[NSTATICS * sizeof 667,17608
430 ;; Date: 04 May 87 23:53:11 PDT 26,1077
431 ;; #define anymacroname(324,4344
432 ;; (define-key ctl-x-map 311,11784
433 ;; (define-abbrev-table 'c-mode-abbrev-table 24,1016
434 ;; static char *skip_white(116,3443
435 ;; static foo 348,11643
436 ;; (defun texinfo-insert-@code 91,3358
437 ;; (defvar texinfo-kindex)29,1105
438 ;; (defun texinfo-format-\. 548,18376
439 ;; (defvar sm::menu-kludge-y 621,22726
440 ;; (defvar *mouse-drag-window* 103,3642
441 ;; (defun simula-back-level(317,11263
442 ;; } DPxAC,380,14024
443 ;; } BM_QCB;69,2990
444 ;; #define MTOS_DONE\t
445
446 ;; "^[^ ]+ +\\([^ ]+\\) "
447
448 ;; void *find_cactus_segment(116,2444
449 ;; void *find_pdb_segment(162,3688
450 ;; void init_dclpool(410,10739
451 ;; WORD insert_draw_command(342,8881
452 ;; void *req_pdbmem(579,15574
453
454 (defvar tag-completion-table (make-vector 511 0))
455
456 (defvar tag-symbol)
457 (defvar tag-table-symbol)
458 (defvar tag-symbol-tables)
459 (defvar buffer-tag-table-list)
460
461 (defmacro intern-tag-symbol (tag)
462 `(progn
463 (setq tag-symbol (intern ,tag tag-completion-table)
464 tag-symbol-tables (and (boundp tag-symbol)
465 (symbol-value tag-symbol)))
466 (or (memq tag-table-symbol tag-symbol-tables)
467 (set tag-symbol (cons tag-table-symbol tag-symbol-tables)))))
468
469 ;; Can't use "\\s " in these patterns because that will include newline
470 (defconst tags-DEFUN-pattern
471 "DEFUN[ \t]*(\"\\([^\"]+\\)\",[ \t]*\\(\\(\\sw\\|\\s_\\)+\\),\C-?")
472 (defconst tags-array-pattern ".*[ \t]+\\([^ \[]+\\)\\[")
473 (defconst tags-def-pattern
474 "\\(.*[ \t]+\\)?\\**\\(\\(\\sw\\|\\s_\\)+\\)[ ();,\t]*\C-?"
475 ;; "\\(.*[ \t]+\\)?\\(\\(\\sw\\|\\s_\\)+\\)[ ()]*\C-?"
476 ;; "\\(\\sw\\|\\s_\\)+[ ()]*\C-?"
477 )
478 (defconst tags-file-pattern "^\f\n\\(.+\\),[0-9]+\n")
479
480 (defun add-to-tag-completion-table ()
481 "Sucks the current buffer (a TAGS table) into the completion-table."
482 (message "Adding %s to tags completion table..."
483 buffer-file-name)
484 (goto-char (point-min))
485 (let ((tag-table-symbol (intern buffer-file-name tag-completion-table))
486 ;; tag-table-symbol is used by intern-tag-symbol
487 filename file-type name name2 tag-symbol
488 tag-symbol-tables
489 (case-fold-search nil))
490 ;; loop over the files mentioned in the TAGS file
491 ;; for each file, try to find its major-mode,
492 ;; then process tags appropriately
493 (while (looking-at tags-file-pattern)
494 (goto-char (match-end 0))
495 (setq filename (file-name-sans-versions
496 (buffer-substring (match-beginning 1)
497 (match-end 1)))
498 ;; Old code used to check auto-mode-alist for the proper
499 ;; file-type. This is too slow, as it breaks the
500 ;; compiled-regexp caching, and slows the whole thing
501 ;; down. We'll use the shotgun approach with only two
502 ;; regexps.
503 file-type (cond ((string-match "\\.\\([cC]\\|cc\\|cxx\\)\\'"
504 filename)
505 'c-mode)
506 ((string-match "\\.\\(el\\|cl\\|lisp\\)\\'"
507 filename)
508 'lisp-mode)
509 ((string-match "\\.scm\\'" filename)
510 'scheme-mode)
511 (t nil)))
512 (cond ((and (eq file-type 'c-mode)
513 c-mode-syntax-table)
514 (set-syntax-table c-mode-syntax-table))
515 ((eq file-type 'lisp-mode)
516 (set-syntax-table lisp-mode-syntax-table))
517 (t
518 (set-syntax-table (standard-syntax-table))))
519 ;; clear loop variables
520 (setq name nil name2 nil)
521 (message "%s..." filename)
522 ;; loop over the individual tag lines
523 (while (not (or (eobp) (eq (following-char) ?\f)))
524 (cond ((and (eq file-type 'c-mode)
525 (looking-at "DEFUN[ \t]"))
526 (or (looking-at tags-DEFUN-pattern)
527 (error "DEFUN doesn't fit pattern"))
528 (setq name (buffer-substring (match-beginning 1)
529 (match-end 1))
530 name2 (buffer-substring (match-beginning 2)
531 (match-end 2))))
532 ;;; ((looking-at "\\s ")
533 ;;; ;; skip probably bogus entry:
534 ;;; )
535 ((and (eq file-type 'c-mode)
536 (looking-at ".*\\["))
537 (cond ((not (looking-at tags-array-pattern))
538 (message "array definition doesn't fit pattern")
539 (setq name nil))
540 (t
541 (setq name (buffer-substring (match-beginning 1)
542 (match-end 1))))))
543 ((and (eq file-type 'scheme-mode)
544 (looking-at "\\s-*(\\s-*def\\sw*\\s-*(?\\s-*\\(\\(\\sw\\|\\s_\\|:\\)+\\))?\\s-*\C-?"))
545 (setq name (buffer-substring (match-beginning 1)
546 (match-end 1))))
547 ((looking-at tags-def-pattern)
548 (setq name (buffer-substring (match-beginning 2)
549 (match-end 2)))))
550 ;; add the tags we found to the completion table
551 (and name (intern-tag-symbol name))
552 (and name2 (intern-tag-symbol name2))
553 (forward-line 1)))
554 (or (eobp) (error "Bad TAGS file")))
555 (message "Adding %s to tags completion table...done"
556 buffer-file-name))
557
558
559 ;; Interactive find-tag
560
561 (defvar find-tag-default-hook nil
562 "Function to call to create a default tag.
563 Make it buffer-local in a mode hook. The function is called with no
564 arguments.")
565
566 (defvar find-tag-hook nil
567 "Function to call after a hook is found.
568 Make it buffer-local in a mode hook. The function is called with no
569 argsuments.")
570
571 ;; Return a default tag to search for, based on the text at point.
572 (defun find-tag-default ()
573 (or (and (not (memq find-tag-default-hook '(nil find-tag-default)))
574 (condition-case data
575 (funcall find-tag-default-hook)
576 (error
577 (warn "Error in find-tag-default-hook signalled error: %s"
578 (error-message-string data))
579 nil)))
580 (let ((pair (thing-symbol (point))))
581 (and pair
582 (buffer-substring (car pair) (cdr pair))))))
583
584 ;; This function depends on the following symbols being bound properly:
585 ;; buffer-tag-table-list,
586 ;; tag-symbol-tables (value irrelevant, bound outside for efficiency)
587 (defun tag-completion-predicate (tag-symbol)
588 (and (boundp tag-symbol)
589 (setq tag-symbol-tables (symbol-value tag-symbol))
590 (catch 'found
591 (while tag-symbol-tables
592 (when (memq (car tag-symbol-tables) buffer-tag-table-list)
593 (throw 'found t))
594 (setq tag-symbol-tables (cdr tag-symbol-tables))))))
595
596 (defun buffer-tag-table-symbol-list ()
597 (mapcar (lambda (table-name)
598 (intern table-name tag-completion-table))
599 (buffer-tag-table-list)))
600
601 (defvar find-tag-history nil "History list for find-tag-tag")
602
603 (defun find-tag-tag (prompt)
604 (let* ((default (find-tag-default))
605 (buffer-tag-table-list (buffer-tag-table-symbol-list))
606 tag-symbol-tables tag-name)
607 (setq tag-name
608 (completing-read
609 (if default
610 (format "%s(default %s) " prompt default)
611 prompt)
612 tag-completion-table 'tag-completion-predicate nil nil
613 'find-tag-history))
614 (if (string-equal tag-name "")
615 ;; #### - This is a really LAME way of doing it! --Stig
616 default ;indicate exact symbol match
617 tag-name)))
618
619 (defvar last-tag-data nil
620 "Information for continuing a tag search.
621 Is of the form (TAG POINT MATCHING-EXACT TAG-TABLE TAG-TABLE ...).")
622
623 (defvar tags-loop-operate nil
624 "Form for `tags-loop-continue' to eval to change one file.")
625
626 (defvar tags-loop-scan
627 '(error "%s" (substitute-command-keys
628 "No \\[tags-search] or \\[tags-query-replace] in progress."))
629 "Form for `tags-loop-continue' to eval to scan one file.
630 If it returns non-nil, this file needs processing by evalling
631 \`tags-loop-operate'. Otherwise, move on to the next file.")
632
633 (autoload 'get-symbol-syntax-table "symbol-syntax")
634
635 (defun find-tag-internal (tagname)
636 (let ((next (null tagname))
637 (tmpnext (null tagname))
638 ;; If tagname is a list: (TAGNAME), this indicates
639 ;; requiring an exact symbol match.
640 (exact (or tags-always-exact (consp tagname)))
641 (normal-syntax-table (syntax-table))
642 (exact-syntax-table (get-symbol-syntax-table (syntax-table)))
643 tag-table-currently-matching-exact
644 tag-target exact-tagname
645 tag-tables tag-table-point file linebeg startpos buf
646 offset found pat syn-tab)
647 (if (consp tagname) (setq tagname (car tagname)))
648 (cond (next
649 (setq tagname (car last-tag-data))
650 (setq tag-table-currently-matching-exact
651 (car (cdr (cdr last-tag-data)))))
652 (t
653 (setq tag-table-currently-matching-exact t)))
654 ;; \_ in the tagname is used to indicate a symbol boundary.
655 (setq exact-tagname (concat "\\_" tagname "\\_"))
656 (while (string-match "\\\\_" exact-tagname)
657 (aset exact-tagname (1- (match-end 0)) ?b))
658 (save-excursion
659 (catch 'found
660 ;; loop searching for exact matches and then inexact matches.
661 (while (not (eq tag-table-currently-matching-exact 'neither))
662 (cond (tmpnext
663 (setq tag-tables (cdr (cdr (cdr last-tag-data))))
664 (setq tag-table-point (car (cdr last-tag-data)))
665 ;; start from the beginning of the table list
666 ;; on the next iteration of the loop.
667 (setq tmpnext nil))
668 (t
669 (setq tag-tables (buffer-tag-table-list))
670 (setq tag-table-point 1)))
671 (if tag-table-currently-matching-exact
672 (progn
673 (setq tag-target exact-tagname)
674 (setq syn-tab exact-syntax-table))
675 (setq tag-target tagname)
676 (setq syn-tab normal-syntax-table))
677 (with-caps-disable-folding tag-target
678 (while tag-tables
679 (set-buffer (get-tag-table-buffer (car tag-tables)))
680 (bury-buffer (current-buffer))
681 (goto-char (or tag-table-point (point-min)))
682 (setq tag-table-point nil)
683 (let ((osyn (syntax-table))
684 case-fold-search)
685 (unwind-protect
686 (progn
687 (set-syntax-table syn-tab)
688 ;; **** should there be support for non-regexp
689 ;; tag searches?
690 (while (re-search-forward tag-target nil t)
691 (if (and (save-match-data
692 (looking-at "[^\n\C-?]*\C-?"))
693 ;; if we're looking for inexact
694 ;; matches, skip exact matches
695 ;; since we've visited them
696 ;; already.
697 (or tag-table-currently-matching-exact
698 (unwind-protect
699 (save-excursion
700 (set-syntax-table
701 exact-syntax-table)
702 (goto-char (match-beginning 0))
703 (not (looking-at exact-tagname)))
704 (set-syntax-table syn-tab))))
705 (throw 'found t))))
706 (set-syntax-table osyn)))
707 (setq tag-tables
708 (nconc (tag-table-include-files) (cdr tag-tables)))))
709 (if (and (not exact) (eq tag-table-currently-matching-exact t))
710 (setq tag-table-currently-matching-exact nil)
711 (setq tag-table-currently-matching-exact 'neither)))
712 (error "No %sentries %s %s"
713 (if next "more " "")
714 (if exact "matching" "containing")
715 tagname))
716 (search-forward "\C-?")
717 (setq file (expand-file-name (file-of-tag)
718 ;; XEmacs change: this needs to be
719 ;; relative to the
720 (or (file-name-directory (car tag-tables))
721 "./")))
722 (setq linebeg (buffer-substring (1- (point)) (point-at-bol)))
723 (search-forward ",")
724 (setq startpos (read (current-buffer)))
725 (setq last-tag-data
726 (nconc (list tagname (point) tag-table-currently-matching-exact)
727 tag-tables))
728 (setq buf (find-file-noselect file))
729 (with-current-buffer buf
730 (save-excursion
731 (save-restriction
732 (widen)
733 (setq offset 1000)
734 (setq pat (concat "^" (regexp-quote linebeg)))
735 (or startpos (setq startpos (point-min)))
736 (while (and (not found)
737 (progn
738 (goto-char (- startpos offset))
739 (not (bobp))))
740 (setq found (re-search-forward pat (+ startpos offset) t))
741 (setq offset (* 3 offset)))
742 (or found
743 (re-search-forward pat nil t)
744 (error "%s not found in %s" pat file))
745 (beginning-of-line)
746 (setq startpos (point)))))
747 (cons buf startpos))))
748
749 ;;;###autoload
750 (defun find-tag (tagname &optional other-window)
751 "*Find tag whose name contains TAGNAME.
752 Selects the buffer that the tag is contained in
753 and puts point at its definition.
754 If TAGNAME is a null string, the expression in the buffer
755 around or before point is used as the tag name.
756 If called interactively with a numeric argument, searches for the next tag
757 in the tag table that matches the tagname used in the previous find-tag.
758 If second arg OTHER-WINDOW is non-nil, uses another window to display
759 the tag.
760
761 This version of this function supports multiple active tags tables,
762 and completion.
763
764 Variables of note:
765
766 tag-table-alist controls which tables apply to which buffers
767 tags-file-name a default tags table
768 tags-build-completion-table controls completion behavior
769 buffer-tag-table another way of specifying a buffer-local table
770 make-tags-files-invisible whether tags tables should be very hidden
771 tag-mark-stack-max how many tags-based hops to remember"
772 (interactive (if current-prefix-arg
773 '(nil nil)
774 (list (find-tag-tag "Find tag: ") nil)))
775 (let* ((local-find-tag-hook find-tag-hook)
776 (next (null tagname))
777 (result (find-tag-internal tagname))
778 (tag-buf (car result))
779 (tag-point (cdr result)))
780 ;; push old position
781 (if (or (not next)
782 (not (memq last-command
783 '(find-tag find-tag-other-window tags-loop-continue))))
784 (push-tag-mark))
785 (if other-window
786 (pop-to-buffer tag-buf)
787 (switch-to-buffer tag-buf))
788 (widen)
789 (push-mark)
790 (goto-char tag-point)
791 (if find-tag-hook
792 (funcall find-tag-hook)
793 (if local-find-tag-hook
794 (funcall local-find-tag-hook))))
795 (setq tags-loop-scan (list 'find-tag nil nil)
796 tags-loop-operate nil)
797 ;; Return t in case used as the tags-loop-scan.
798 t)
799
800 ;; This function is unchanged from lisp/tags.el:
801 ;;;###autoload
802 (defun find-tag-other-window (tagname &optional next)
803 "*Find tag whose name contains TAGNAME.
804 Selects the buffer that the tag is contained in in another window
805 and puts point at its definition.
806 If TAGNAME is a null string, the expression in the buffer
807 around or before point is used as the tag name.
808 If second arg NEXT is non-nil (interactively, with prefix arg),
809 searches for the next tag in the tag table
810 that matches the tagname used in the previous find-tag.
811
812 This version of this function supports multiple active tags tables,
813 and completion.
814
815 Variables of note:
816
817 tag-table-alist controls which tables apply to which buffers
818 tags-file-name a default tags table
819 tags-build-completion-table controls completion behavior
820 buffer-tag-table another way of specifying a buffer-local table
821 make-tags-files-invisible whether tags tables should be very hidden
822 tag-mark-stack-max how many tags-based hops to remember"
823 (interactive (if current-prefix-arg
824 '(nil t)
825 (list (find-tag-tag "Find tag other window: "))))
826 (if next
827 (find-tag nil t)
828 (find-tag tagname t)))
829
830
831 ;; Completion on tags in the buffer
832
833 (defun complete-symbol (&optional table predicate prettify)
834 (let* ((end (point))
835 (beg (save-excursion
836 (backward-sexp 1)
837 (while (= (char-syntax (following-char)) ?\')
838 (forward-char 1))
839 (point)))
840 (pattern (buffer-substring beg end))
841 (table (or table obarray))
842 (completion (try-completion pattern table predicate)))
843 (cond ((eq completion t))
844 ((null completion)
845 (message "Can't find completion for \"%s\"" pattern)
846 (ding))
847 ((not (string-equal pattern completion))
848 (delete-region beg end)
849 (insert completion))
850 (t
851 (message "Making completion list...")
852 (let ((list (all-completions pattern table predicate)))
853 (if prettify
854 (setq list (funcall prettify list)))
855 (with-output-to-temp-buffer "*Help*"
856 (display-completion-list list)))
857 (message "Making completion list...%s" "done")))))
858
859 (defun tag-complete-symbol ()
860 "The function used to do tags-completion (using 'tag-completion-predicate)."
861 (interactive)
862 (let* ((buffer-tag-table-list (buffer-tag-table-symbol-list))
863 tag-symbol-tables)
864 (complete-symbol tag-completion-table 'tag-completion-predicate)))
865
866
867 ;; Applying a command to files mentioned in tag tables
868
869 (defvar next-file-list nil
870 "List of files for next-file to process.")
871
872 ;;;###autoload
873 (defun next-file (&optional initialize novisit)
874 "Select next file among files in current tag table(s).
875
876 A first argument of t (prefix arg, if interactive) initializes to the
877 beginning of the list of files in the (first) tags table. If the argument
878 is neither nil nor t, it is evalled to initialize the list of files.
879
880 Non-nil second argument NOVISIT means use a temporary buffer
881 to save time and avoid uninteresting warnings.
882
883 Value is nil if the file was already visited;
884 if the file was newly read in, the value is the filename."
885 (interactive "P")
886 (cond ((not initialize)
887 ;; Not the first run.
888 )
889 ((eq initialize t)
890 ;; Initialize the list from the tags table.
891 (setq next-file-list (buffer-tag-table-files)))
892 (t
893 ;; Initialize the list by evalling the argument.
894 (setq next-file-list (eval initialize))))
895 (if (null next-file-list)
896 (progn
897 (and novisit
898 (get-buffer " *next-file*")
899 (kill-buffer " *next-file*"))
900 (error "All files processed.")))
901 (let* ((file (car next-file-list))
902 (buf (get-file-buffer file))
903 (new (not buf)))
904 (setq next-file-list (cdr next-file-list))
905
906 (if (not (and new novisit))
907 (switch-to-buffer (find-file-noselect file novisit) t)
908 ;; Like find-file, but avoids random warning messages.
909 (set-buffer (get-buffer-create " *next-file*"))
910 (kill-all-local-variables)
911 (erase-buffer)
912 (insert-file-contents file nil))
913 (widen)
914 (cond ((> (point) (point-min))
915 (push-mark nil t)
916 (goto-char (point-min))))
917 (and new file)))
918
919 (defcustom tags-search-nuke-uninteresting-buffers t
920 "*If t (the default), tags-search and tags-query-replace will only
921 keep newly-visited buffers if they contain the search target."
922 :type 'boolean
923 :group 'etags)
924
925 ;;;###autoload
926 (defun tags-loop-continue (&optional first-time)
927 "Continue last \\[tags-search] or \\[tags-query-replace] command.
928 Used noninteractively with non-nil argument to begin such a command (the
929 argument is passed to `next-file', which see).
930 Two variables control the processing we do on each file:
931 the value of `tags-loop-scan' is a form to be executed on each file
932 to see if it is interesting (it returns non-nil if so)
933 and `tags-loop-operate' is a form to execute to operate on an interesting file
934 If the latter returns non-nil, we exit; otherwise we scan the next file."
935 (interactive)
936 (let (new
937 (messaged nil))
938 (while
939 (progn
940 ;; Scan files quickly for the first or next interesting one.
941 (while (or first-time
942 (save-restriction
943 (widen)
944 (not (eval tags-loop-scan))))
945 (setq new (next-file first-time
946 tags-search-nuke-uninteresting-buffers))
947 ;; If NEW is non-nil, we got a temp buffer,
948 ;; and NEW is the file name.
949 (if (or messaged
950 (and (not first-time)
951 (> (device-baud-rate) search-slow-speed)
952 (setq messaged t)))
953 (message "Scanning file %s..." (or new buffer-file-name)))
954 (setq first-time nil)
955 (goto-char (point-min)))
956
957 ;; If we visited it in a temp buffer, visit it now for real.
958 (if (and new tags-search-nuke-uninteresting-buffers)
959 (let ((pos (point)))
960 (erase-buffer)
961 (set-buffer (find-file-noselect new))
962 (widen)
963 (goto-char pos)))
964
965 (switch-to-buffer (current-buffer))
966
967 ;; Now operate on the file.
968 ;; If value is non-nil, continue to scan the next file.
969 (eval tags-loop-operate)))
970 (and messaged
971 (null tags-loop-operate)
972 (message "Scanning file %s...found" buffer-file-name))))
973
974
975 ;;;###autoload
976 (defun tags-search (regexp &optional file-list-form)
977 "Search through all files listed in tags table for match for REGEXP.
978 Stops when a match is found.
979 To continue searching for next match, use command \\[tags-loop-continue].
980
981 See documentation of variable `tag-table-alist'."
982 (interactive "sTags search (regexp): ")
983 (if (and (equal regexp "")
984 (eq (car tags-loop-scan) 'with-caps-disable-folding)
985 (null tags-loop-operate))
986 ;; Continue last tags-search as if by M-,.
987 (tags-loop-continue nil)
988 (setq tags-loop-scan `(with-caps-disable-folding ,regexp
989 (re-search-forward ,regexp nil t))
990 tags-loop-operate nil)
991 (tags-loop-continue (or file-list-form t))))
992
993 ;;;###autoload
994 (defun tags-query-replace (from to &optional delimited file-list-form)
995 "Query-replace-regexp FROM with TO through all files listed in tags table.
996 Third arg DELIMITED (prefix arg) means replace only word-delimited matches.
997 If you exit (\\[keyboard-quit] or ESC), you can resume the query-replace
998 with the command \\[tags-loop-continue].
999
1000 See documentation of variable `tag-table-alist'."
1001 (interactive
1002 "sTags query replace (regexp): \nsTags query replace %s by: \nP")
1003 (setq tags-loop-scan `(with-caps-disable-folding ,from
1004 (if (re-search-forward ,from nil t)
1005 ;; When we find a match, move back
1006 ;; to the beginning of it so perform-replace
1007 ;; will see it.
1008 (progn (goto-char (match-beginning 0)) t)))
1009 tags-loop-operate (list 'perform-replace from to t t
1010 (not (null delimited))))
1011 (tags-loop-continue (or file-list-form t)))
1012
1013 ;; Miscellaneous
1014
1015 ;; **** need to alter
1016 ;; This function is unchanged from lisp/tags.el:
1017 ;;;###autoload
1018 (defun list-tags (string)
1019 "Display list of tags in file FILE.
1020 FILE should not contain a directory spec
1021 unless it has one in the tag table."
1022 (interactive "fList tags (in file): ")
1023 (setq string (expand-file-name string))
1024 (with-output-to-temp-buffer "*Tags List*"
1025 (princ "Tags in file ")
1026 (princ string)
1027 (terpri)
1028 (save-excursion
1029 (visit-tags-table-buffer)
1030 (goto-char 1)
1031 (search-forward (concat "\f\n" string ","))
1032 (forward-line 1)
1033 (while (not (or (eobp) (looking-at "\f")))
1034 (princ (buffer-substring (point)
1035 (progn (skip-chars-forward "^\C-?")
1036 (point))))
1037 (terpri)
1038 (forward-line 1)))))
1039
1040 ;; **** need to alter
1041 ;; This function is unchanged from lisp/tags.el:
1042 ;;;###autoload
1043 (defun tags-apropos (string)
1044 "Display list of all tags in tag table REGEXP matches."
1045 (interactive "sTag apropos (regexp): ")
1046 (with-output-to-temp-buffer "*Tags List*"
1047 (princ "Tags matching regexp ")
1048 (prin1 string)
1049 (terpri)
1050 (save-excursion
1051 (visit-tags-table-buffer)
1052 (goto-char 1)
1053 (while (re-search-forward string nil t)
1054 (beginning-of-line)
1055 (princ (buffer-substring (point)
1056 (progn (skip-chars-forward "^\C-?")
1057 (point))))
1058 (terpri)
1059 (forward-line 1)))))
1060
1061 ;; **** copied from tags.el
1062 (defun visit-tags-table-buffer ()
1063 "Select the buffer containing the current tag table.
1064 This is a file whose name is in the variable tags-file-name."
1065 (or tags-file-name
1066 (call-interactively 'visit-tags-table))
1067 (set-buffer (or (get-file-buffer tags-file-name)
1068 (progn
1069 (setq tag-table-files nil)
1070 (find-file-noselect tags-file-name))))
1071 (or (verify-visited-file-modtime (get-file-buffer tags-file-name))
1072 (cond ((yes-or-no-p "Tags file has changed, read new contents? ")
1073 (revert-buffer t t)
1074 (setq tag-table-files nil))))
1075 (or (eq (char-after 1) ?\^L)
1076 (error "File %s not a valid tag table" tags-file-name)))
1077
1078
1079 ;; Sample uses of find-tag-hook and find-tag-default-hook
1080
1081 ;; Example buffer-local tag finding
1082
1083 (or (boundp 'emacs-lisp-mode-hook)
1084 (setq emacs-lisp-mode-hook nil))
1085 (if (eq (car-safe emacs-lisp-mode-hook) 'lambda)
1086 (setq emacs-lisp-mode-hook (list emacs-lisp-mode-hook)))
1087 (or (memq 'setup-emacs-lisp-default-tag-hook emacs-lisp-mode-hook)
1088 (setq emacs-lisp-mode-hook
1089 (cons 'setup-emacs-lisp-default-tag-hook emacs-lisp-mode-hook)))
1090
1091 (defun setup-emacs-lisp-default-tag-hook ()
1092 (cond ((eq major-mode 'emacs-lisp-mode)
1093 (make-variable-buffer-local 'find-tag-default-hook)
1094 (setq find-tag-default-hook 'emacs-lisp-default-tag))))
1095 ;; Run it once immediately
1096 (setup-emacs-lisp-default-tag-hook)
1097 (when (get-buffer "*scratch*")
1098 (with-current-buffer "*scratch*"
1099 (setup-emacs-lisp-default-tag-hook)))
1100
1101 (defun emacs-lisp-default-tag ()
1102 "Function to return a default tag for Emacs-Lisp mode."
1103 (let ((tag (or (variable-at-point)
1104 (function-at-point))))
1105 (if tag (symbol-name tag))))
1106
1107
1108 ;; Display short info on tag in minibuffer
1109
1110 (if (null (lookup-key esc-map "?"))
1111 (define-key esc-map "?" 'display-tag-info))
1112
1113 (defun display-tag-info (tagname)
1114 "Prints a description of the first tag matching TAGNAME in the echo area.
1115 If this is an elisp function, prints something like \"(defun foo (x y z)\".
1116 That is, is prints the first line of the definition of the form.
1117 If this is a C-defined elisp function, it does something more clever."
1118 (interactive (if current-prefix-arg
1119 '(nil)
1120 (list (find-tag-tag "Display tag info: "))))
1121 (let* ((results (find-tag-internal tagname))
1122 (tag-buf (car results))
1123 (tag-point (cdr results))
1124 info lname min max fname args)
1125 (with-current-buffer tag-buf
1126 (save-excursion
1127 (save-restriction
1128 (widen)
1129 (goto-char tag-point)
1130 (cond ((let ((case-fold-search nil))
1131 (looking-at "^DEFUN[ \t]"))
1132 (forward-sexp 1)
1133 (down-list 1)
1134 (setq lname (read (current-buffer))
1135 fname (buffer-substring
1136 (progn (forward-sexp 1) (point))
1137 (progn (backward-sexp 1) (point)))
1138 min (buffer-substring
1139 (progn (forward-sexp 3) (point))
1140 (progn (backward-sexp 1) (point)))
1141 max (buffer-substring
1142 (progn (forward-sexp 2) (point))
1143 (progn (backward-sexp 1) (point))))
1144 (backward-up-list 1)
1145 (setq args (buffer-substring
1146 (progn (forward-sexp 2) (point))
1147 (progn (backward-sexp 1) (point))))
1148 (setq info (format "Elisp: %s, C: %s %s, #args: %s"
1149 lname
1150 fname args
1151 (if (string-equal min max)
1152 min
1153 (format "from %s to %s" min max)))))
1154 (t
1155 (setq info
1156 (buffer-substring
1157 (progn (beginning-of-line) (point))
1158 (progn (end-of-line) (point)))))))))
1159 (message "%s" info))
1160 (setq tags-loop-scan '(display-tag-info nil)
1161 tags-loop-operate nil)
1162 ;; Always return non-nil
1163 t)
1164
1165
1166 ;; Keep track of old locations before finding tags
1167
1168 (defvar tag-mark-stack1 nil)
1169 (defvar tag-mark-stack2 nil)
1170 (defcustom tag-mark-stack-max 16
1171 "*The maximum number of elements kept on the mark-stack used
1172 by tags-search. See also the commands push-tag-mark (\\[push-tag-mark])
1173 and pop-tag-mark. (\\[pop-tag-mark])."
1174 :type 'integer
1175 :group 'etags)
1176
1177 (defun push-mark-on-stack (stack-symbol &optional max-size)
1178 (let ((stack (symbol-value stack-symbol)))
1179 (push (point-marker) stack)
1180 (cond ((and max-size
1181 (> (length stack) max-size))
1182 (set-marker (car (nthcdr max-size stack)) nil)
1183 (setcdr (nthcdr (1- max-size) stack) nil)))
1184 (set stack-symbol stack)))
1185
1186 (defun pop-mark-from-stack (stack-symbol1 stack-symbol2 &optional max-size)
1187 (let* ((stack (or (symbol-value stack-symbol1)
1188 (error "No more tag marks on stack")))
1189 (marker (car stack))
1190 (m-buf (marker-buffer marker)))
1191 (set stack-symbol1 (cdr stack))
1192 (or m-buf
1193 (error "Marker has no buffer"))
1194 (if (null (buffer-name m-buf))
1195 (error "Buffer has been killed"))
1196 (push-mark-on-stack stack-symbol2 max-size)
1197 (switch-to-buffer m-buf)
1198 (widen)
1199 (goto-char (marker-position marker))))
1200
1201 (defun push-tag-mark ()
1202 (push-mark-on-stack 'tag-mark-stack1 tag-mark-stack-max))
1203
1204 (if (memq (lookup-key esc-map "*") '(nil undefined))
1205 (define-key esc-map "*" 'pop-tag-mark))
1206
1207 (defun pop-tag-mark (arg)
1208 "find-tag maintains a mark-stack seperate from the \\[set-mark-command] mark-stack.
1209 This function pops (and moves to) the tag at the top of this stack."
1210 (interactive "P")
1211 (if (not arg)
1212 (pop-mark-from-stack
1213 'tag-mark-stack1 'tag-mark-stack2 tag-mark-stack-max)
1214 (pop-mark-from-stack
1215 'tag-mark-stack2 'tag-mark-stack1 tag-mark-stack-max)))
1216
1217
1218 (provide 'etags)
1219 (provide 'tags)