(next-file): If arg is neither t nor nil, then eval it to return initial
[bpt/emacs.git] / lisp / progmodes / etags.el
dissimilarity index 77%
index d869d0c..452d9ad 100644 (file)
-;;; etags.el --- tags facility for Emacs.
-
-;; Copyright (C) 1985, 1986, 1988, 1992 Free Software Foundation, Inc.
-
-;; This file is part of GNU Emacs.
-
-;; GNU Emacs is free software; you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 2, or (at your option)
-;; any later version.
-
-;; GNU Emacs is distributed in the hope that it will be useful,
-;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-;; GNU General Public License for more details.
-
-;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs; see the file COPYING.  If not, write to
-;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
-
-
-;;;###autoload
-(defvar tags-file-name nil "\
-*File name of tag table.
-To switch to a new tag table, setting this variable is sufficient.
-Use the `etags' program to make a tag table file.")
-
-(defvar tag-table-files nil
-  "List of file names covered by current tag table.
-nil means it has not been computed yet; do (tag-table-files) to compute it.")
-
-(defvar last-tag nil
-  "Tag found by the last find-tag.")
-
-;;;###autoload
-(defun visit-tags-table (file)
-  "Tell tags commands to use tag table file FILE.
-FILE should be the name of a file created with the `etags' program.
-A directory name is ok too; it means file TAGS in that directory."
-  (interactive (list (read-file-name "Visit tags table: (default TAGS) "
-                                    default-directory
-                                    (concat default-directory "TAGS")
-                                    t)))
-  (setq file (expand-file-name file))
-  ;; Get rid of the prefixes added by the automounter.
-  (if (and (string-match "^/tmp_mnt/" file)
-          (file-exists-p (file-name-directory
-                          (substring file (1- (match-end 0))))))
-      (setq file (substring file (1- (match-end 0)))))
-  (if (file-directory-p file)
-      (setq file (concat file "TAGS")))
-  (setq tag-table-files nil
-       tags-file-name file))
-
-(defun visit-tags-table-buffer ()
-  "Select the buffer containing the current tag table.
-This is a file whose name is in the variable tags-file-name."
-  (or tags-file-name
-      (call-interactively 'visit-tags-table))
-  (set-buffer (or (get-file-buffer tags-file-name)
-                 (progn
-                   (setq tag-table-files nil)
-                   (find-file-noselect tags-file-name))))
-  (or (verify-visited-file-modtime (get-file-buffer tags-file-name))
-      (cond ((yes-or-no-p "Tags file has changed, read new contents? ")
-            (revert-buffer t t)
-            (setq tag-table-files nil))))
-  (or (eq (char-after 1) ?\^L)
-      (error "File %s not a valid tag table" tags-file-name)))
-
-(defun file-of-tag ()
-  "Return the file name of the file whose tags point is within.
-Assumes the tag table is the current buffer.
-File name returned is relative to tag table file's directory."
-  (let ((opoint (point))
-       prev size)
-    (save-excursion
-     (goto-char (point-min))
-     (while (< (point) opoint)
-       (forward-line 1)
-       (end-of-line)
-       (skip-chars-backward "^,\n")
-       (setq prev (point))
-       (setq size (read (current-buffer)))
-       (goto-char prev)
-       (forward-line 1)
-       (forward-char size))
-     (goto-char (1- prev))
-     (buffer-substring (point)
-                      (progn (beginning-of-line) (point))))))
-
-;;;###autoload
-(defun tag-table-files ()
-  "Return a list of files in the current tag table.
-File names returned are absolute."
-  (save-excursion
-   (visit-tags-table-buffer)
-   (or tag-table-files
-       (let (files)
-       (goto-char (point-min))
-       (while (not (eobp))
-         (forward-line 1)
-         (end-of-line)
-         (skip-chars-backward "^,\n")
-         (setq prev (point))
-         (setq size (read (current-buffer)))
-         (goto-char prev)
-         (setq files (cons (expand-file-name
-                            (buffer-substring (1- (point))
-                                              (save-excursion
-                                                (beginning-of-line)
-                                                (point)))
-                            (file-name-directory tags-file-name))
-                           files))
-         (forward-line 1)
-         (forward-char size))
-       (setq tag-table-files (nreverse files))))))
-
-;; Return a default tag to search for, based on the text at point.
-(defun find-tag-default ()
-  (save-excursion
-    (while (looking-at "\\sw\\|\\s_")
-      (forward-char 1))
-    (if (re-search-backward "\\sw\\|\\s_" nil t)
-       (progn (forward-char 1)
-              (buffer-substring (point)
-                                (progn (forward-sexp -1)
-                                       (while (looking-at "\\s'")
-                                         (forward-char 1))
-                                       (point))))
-      nil)))
-
-(defun find-tag-tag (string)
-  (let* ((default (find-tag-default))
-        (spec (read-string
-               (if default
-                   (format "%s(default %s) " string default)
-                 string))))
-    (list (if (equal spec "")
-             default
-           spec))))
-
-(defun tags-tag-match (tagname exact)
-  "Search for a match to the given tagname."
-  (if (not exact)
-      (search-forward tagname nil t)
-    (not (error-occurred
-         (while
-             (progn
-               (search-forward tagname)
-               (let ((before (char-syntax (char-after (1- (match-beginning 1)))))
-                     (after (char-syntax (char-after (match-end 1)))))
-                 (not (or (= before ?w) (= before ?_))
-                          (= after ?w) (= after ?_)))
-               ))))
-    )
-  )
-
-(defun find-tag-noselect (tagname exact &optional next)
-  "Find a tag and return its buffer, but don't select or display it."
-  (let (buffer file linebeg startpos (obuf (current-buffer)))
-    ;; save-excursion will do the wrong thing if the buffer containing the
-    ;; tag being searched for is current-buffer
-    (unwind-protect
-       (progn
-         (visit-tags-table-buffer)
-         (if (not next)
-             (goto-char (point-min))
-           (setq tagname last-tag))
-         (setq last-tag tagname)
-         (while (progn
-                  (if (not (tags-tag-match tagname exact))
-                      (error "No %sentries matching %s"
-                             (if next "more " "") tagname))
-                  (not (looking-at "[^\n\177]*\177"))))
-         (search-forward "\177")
-         (setq file (expand-file-name (file-of-tag)
-                                      (file-name-directory tags-file-name)))
-         (setq linebeg
-               (buffer-substring (1- (point))
-                                 (save-excursion (beginning-of-line) (point))))
-         (search-forward ",")
-         (setq startpos (read (current-buffer)))
-         (prog1
-             (set-buffer (find-file-noselect file))
-           (widen)
-           (push-mark)
-           (let ((offset 1000)
-                 found
-                 (pat (concat "^" (regexp-quote linebeg))))
-             (or startpos (setq startpos (point-min)))
-             (while (and (not found)
-                         (progn
-                           (goto-char (- startpos offset))
-                           (not (bobp))))
-               (setq found
-                     (re-search-forward pat (+ startpos offset) t))
-               (setq offset (* 3 offset)))
-             (or found
-                 (re-search-forward pat nil t)
-                 (error "%s not found in %s" pat file)))
-           (beginning-of-line)))
-      (set-buffer obuf))
-    ))
-
-;;;###autoload
-(defun find-tag (tagname &optional next other-window)
-  "Find tag (in current tag table) whose name contains TAGNAME.
- Selects the buffer that the tag is contained in
-and puts point at its definition.
- If TAGNAME is a null string, the expression in the buffer
-around or before point is used as the tag name.
- If second arg NEXT is non-nil (interactively, with prefix arg),
-searches for the next tag in the tag table
-that matches the tagname used in the previous find-tag.
-
-See documentation of variable tags-file-name."
-  (interactive (if current-prefix-arg
-                  '(nil t)
-                (find-tag-tag "Find tag: ")))
-  (let ((tagbuf (find-tag-noselect tagname nil next)))
-    (if other-window
-       (switch-to-buffer-other-window tagbuf)
-      (switch-to-buffer tagbuf))
-    )
-  (setq tags-loop-form '(find-tag nil t))
-  ;; Return t in case used as the tags-loop-form.
-  t)
-
-;;;###autoload
-(define-key esc-map "." 'find-tag)
-
-;;;###autoload
-(defun find-tag-other-window (tagname &optional next)
-  "Find tag (in current tag table) whose name contains TAGNAME.
- Selects the buffer that the tag is contained in in another window
-and puts point at its definition.
- If TAGNAME is a null string, the expression in the buffer
-around or before point is used as the tag name.
- If second arg NEXT is non-nil (interactively, with prefix arg),
-searches for the next tag in the tag table
-that matches the tagname used in the previous find-tag.
-
-See documentation of variable tags-file-name."
-  (interactive (if current-prefix-arg
-                  '(nil t)
-                  (find-tag-tag "Find tag other window: ")))
-  (find-tag tagname next t))
-;;;###autoload
-(define-key ctl-x-4-map "." 'find-tag-other-window)
-
-;;;###autoload
-(defun find-tag-other-frame (tagname &optional next)
-  "Find tag (in current tag table) whose name contains TAGNAME.
- Selects the buffer that the tag is contained in in another frame
-and puts point at its definition.
- If TAGNAME is a null string, the expression in the buffer
-around or before point is used as the tag name.
- If second arg NEXT is non-nil (interactively, with prefix arg),
-searches for the next tag in the tag table
-that matches the tagname used in the previous find-tag.
-
-See documentation of variable tags-file-name."
-  (interactive (if current-prefix-arg
-                  '(nil t)
-                  (find-tag-tag "Find tag other window: ")))
-  (let ((pop-up-screens t))
-    (find-tag tagname next t)))
-;;;###autoload
-(define-key ctl-x-5-map "." 'find-tag-other-frame)
-
-(defvar next-file-list nil
-  "List of files for next-file to process.")
-
-;;;###autoload
-(defun next-file (&optional initialize)
-  "Select next file among files in current tag table.
-Non-nil argument (prefix arg, if interactive)
-initializes to the beginning of the list of files in the tag table."
-  (interactive "P")
-  (if initialize
-      (setq next-file-list (tag-table-files)))
-  (or next-file-list
-      (error "All files processed."))
-  (find-file (car next-file-list))
-  (setq next-file-list (cdr next-file-list)))
-
-(defvar tags-loop-form nil
-  "Form for tags-loop-continue to eval to process one file.
-If it returns nil, it is through with one file; move on to next.")
-
-;;;###autoload
-(defun tags-loop-continue (&optional first-time)
-  "Continue last \\[tags-search] or \\[tags-query-replace] command.
-Used noninteractively with non-nil argument
-to begin such a command.  See variable tags-loop-form."
-  (interactive)
-  (if first-time
-      (progn (next-file t)
-            (goto-char (point-min))))
-  (while (not (eval tags-loop-form))
-    (next-file)
-    (message "Scanning file %s..." buffer-file-name)
-    (goto-char (point-min))))
-;;;###autoload
-(define-key esc-map "," 'tags-loop-continue)
-
-;;;###autoload
-(defun tags-search (regexp)
-  "Search through all files listed in tag table for match for REGEXP.
-Stops when a match is found.
-To continue searching for next match, use command \\[tags-loop-continue].
-
-See documentation of variable tags-file-name."
-  (interactive "sTags search (regexp): ")
-  (if (and (equal regexp "")
-          (eq (car tags-loop-form) 're-search-forward))
-      (tags-loop-continue nil)
-    (setq tags-loop-form
-         (list 're-search-forward regexp nil t))
-    (tags-loop-continue t)))
-
-;;;###autoload
-(defun tags-query-replace (from to &optional delimited)
-  "Query-replace-regexp FROM with TO through all files listed in tag table.
-Third arg DELIMITED (prefix arg) means replace only word-delimited matches.
-If you exit (C-G or ESC), you can resume the query-replace
-with the command \\[tags-loop-continue].
-
-See documentation of variable tags-file-name."
-  (interactive "sTags query replace (regexp): \nsTags query replace %s by: \nP")
-  (setq tags-loop-form
-       (list 'and (list 'save-excursion
-                        (list 're-search-forward from nil t))
-             (list 'not (list 'perform-replace from to t t 
-                              (not (null delimited))))))
-  (tags-loop-continue t))
-
-;;;###autoload
-(defun list-tags (string)
-  "Display list of tags in file FILE.
-FILE should not contain a directory spec
-unless it has one in the tag table."
-  (interactive "sList tags (in file): ")
-  (with-output-to-temp-buffer "*Tags List*"
-    (princ "Tags in file ")
-    (princ string)
-    (terpri)
-    (save-excursion
-     (visit-tags-table-buffer)
-     (goto-char 1)
-     (search-forward (concat "\f\n" string ","))
-     (forward-line 1)
-     (while (not (or (eobp) (looking-at "\f")))
-       (princ (buffer-substring (point)
-                               (progn (skip-chars-forward "^\177")
-                                      (point))))
-       (terpri)
-       (forward-line 1)))))
-
-;;;###autoload
-(defun tags-apropos (string)
-  "Display list of all tags in tag table REGEXP matches."
-  (interactive "sTag apropos (regexp): ")
-  (with-output-to-temp-buffer "*Tags List*"
-    (princ "Tags matching regexp ")
-    (prin1 string)
-    (terpri)
-    (save-excursion
-     (visit-tags-table-buffer)
-     (goto-char 1)
-     (while (re-search-forward string nil t)
-       (beginning-of-line)
-       (princ (buffer-substring (point)
-                               (progn (skip-chars-forward "^\177")
-                                      (point))))
-       (terpri)
-       (forward-line 1)))))
-
-(provide 'etags)
-
-;;; etags.el ends here
+;;; etags.el --- etags facility for Emacs
+
+;; Copyright (C) 1985, 1986, 1988, 1989, 1992, 1993
+;;     Free Software Foundation, Inc.
+
+;; Author: Roland McGrath <roland@gnu.ai.mit.edu>
+;; Keywords: tools
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to
+;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;; Code:
+
+;;;###autoload
+(defvar tags-file-name nil
+  "*File name of tags table.
+To switch to a new tags table, setting this variable is sufficient.
+If you set this variable, do not also set `tags-table-list'.
+Use the `etags' program to make a tags table file.")
+;; Make M-x set-variable tags-file-name like M-x visit-tags-table.
+;;;###autoload (put 'tags-file-name 'variable-interactive "fVisit tags table: ")
+
+;;;###autoload
+;; Use `visit-tags-table-buffer' to cycle through tags tables in this list.
+(defvar tags-table-list nil
+  "*List of file names of tags tables to search.
+An element that is a directory means the file \"TAGS\" in that directory.
+To switch to a new list of tags tables, setting this variable is sufficient.
+If you set this variable, do not also set `tags-file-name'.
+Use the `etags' program to make a tags table file.")
+
+(defvar tags-table-list-pointer nil
+  "Pointer into `tags-table-list' where the current state of searching is.
+Might instead point into a list of included tags tables.
+Use `visit-tags-table-buffer' to cycle through tags tables in this list.")
+
+(defvar tags-table-list-started-at nil
+  "Pointer into `tags-table-list', where the current search started.")
+
+(defvar tags-table-parent-pointer-list nil
+  "Saved state of the tags table that included this one.
+Each element is (POINTER . STARTED-AT), giving the values of
+ `tags-table-list-pointer' and `tags-table-list-started-at' from
+ before we moved into the current table.")
+
+(defvar tags-table-set-list nil
+  "List of sets of tags table which have been used together in the past.
+Each element is a list of strings which are file names.")
+
+;;;###autoload
+(defvar find-tag-hook nil
+  "*Hook to be run by \\[find-tag] after finding a tag.  See `run-hooks'.
+The value in the buffer in which \\[find-tag] is done is used,
+not the value in the buffer \\[find-tag] goes to.")
+
+;;;###autoload
+(defvar find-tag-default-function nil
+  "*A function of no arguments used by \\[find-tag] to pick a default tag.
+If nil, and the symbol that is the value of `major-mode'
+has a `find-tag-default-function' property (see `put'), that is used.
+Otherwise, `find-tag-default' is used.")
+
+;;;###autoload
+(defvar default-tags-table-function nil
+  "*If non-nil, a function of no arguments to choose a default tags file
+for a particular buffer.")
+
+(defvar tags-location-stack nil
+  "List of markers which are locations visited by \\[find-tag].
+Pop back to the last location with \\[negative-argument] \\[find-tag].")
+\f
+;; Tags table state.
+;; These variables are local in tags table buffers.
+
+(defvar tag-lines-already-matched nil
+  "List of positions of beginnings of lines within the tags table
+that are already matched.")
+
+(defvar tags-table-files nil
+  "List of file names covered by current tags table.
+nil means it has not yet been computed; use `tags-table-files' to do so.")
+
+(defvar tags-completion-table nil
+  "Alist of tag names defined in current tags table.")
+
+(defvar tags-included-tables nil
+  "List of tags tables included by the current tags table.")
+
+(defvar next-file-list nil
+  "List of files for \\[next-file] to process.")
+\f
+;; Hooks for file formats.
+
+(defvar tags-table-format-hooks '(etags-recognize-tags-table
+                                 recognize-empty-tags-table)
+  "List of functions to be called in a tags table buffer to identify
+the type of tags table.  The functions are called in order, with no arguments,
+until one returns non-nil.  The function should make buffer-local bindings
+of the format-parsing tags function variables if successful.")
+
+(defvar file-of-tag-function nil
+  "Function to do the work of `file-of-tag' (which see).")
+(defvar tags-table-files-function nil
+  "Function to do the work of `tags-table-files' (which see).")
+(defvar tags-completion-table-function nil
+  "Function to build the tags-completion-table.")
+(defvar snarf-tag-function nil
+  "Function to get info about a matched tag for `goto-tag-location-function'.")
+(defvar goto-tag-location-function nil
+  "Function of to go to the location in the buffer specified by a tag.
+One argument, the tag info returned by `snarf-tag-function'.")
+(defvar find-tag-regexp-search-function nil
+  "Search function passed to `find-tag-in-order' for finding a regexp tag.")
+(defvar find-tag-regexp-tag-order nil
+  "Tag order passed to `find-tag-in-order' for finding a regexp tag.")
+(defvar find-tag-regexp-next-line-after-failure-p nil
+  "Flag passed to `find-tag-in-order' for finding a regexp tag.")
+(defvar find-tag-search-function nil
+  "Search function passed to `find-tag-in-order' for finding a tag.")
+(defvar find-tag-tag-order nil
+  "Tag order passed to `find-tag-in-order' for finding a tag.")
+(defvar find-tag-next-line-after-failure-p nil
+  "Flag passed to `find-tag-in-order' for finding a tag.")
+(defvar list-tags-function nil
+  "Function to do the work of `list-tags' (which see).")
+(defvar tags-apropos-function nil
+  "Function to do the work of `tags-apropos' (which see).")
+(defvar tags-included-tables-function nil
+  "Function to do the work of `tags-included-tables' (which see).")
+(defvar verify-tags-table-function nil
+  "Function to return t iff the current buffer contains a valid
+\(already initialized\) tags file.")
+\f
+;; Initialize the tags table in the current buffer.
+;; Returns non-nil iff it is a valid tags table.  On
+;; non-nil return, the tags table state variable are
+;; made buffer-local and initialized to nil.
+(defun initialize-new-tags-table ()
+  (set (make-local-variable 'tag-lines-already-matched) nil)
+  (set (make-local-variable 'tags-table-files) nil)
+  (set (make-local-variable 'tags-completion-table) nil)
+  (set (make-local-variable 'tags-included-tables) nil)
+  ;; Value is t if we have found a valid tags table buffer.
+  (let ((hooks tags-table-format-hooks))
+    (while (and hooks
+               (not (funcall (car hooks))))
+      (setq hooks (cdr hooks)))
+    hooks))
+
+;;;###autoload
+(defun visit-tags-table (file &optional local)
+  "Tell tags commands to use tags table file FILE.
+FILE should be the name of a file created with the `etags' program.
+A directory name is ok too; it means file TAGS in that directory.
+
+Normally \\[visit-tags-table] sets the global value of `tags-file-name'.
+With a prefix arg, set the buffer-local value instead.
+When you find a tag with \\[find-tag], the buffer it finds the tag
+in is given a local value of this variable which is the name of the tags
+file the tag was in."
+  (interactive (list (read-file-name "Visit tags table: (default TAGS) "
+                                    default-directory
+                                    (expand-file-name "TAGS"
+                                                      default-directory)
+                                    t)
+                    current-prefix-arg))
+  ;; Calling visit-tags-table-buffer with tags-file-name set to FILE will
+  ;; initialize a buffer for FILE and set tags-file-name to the
+  ;; fully-expanded name.
+  (let ((tags-file-name file))
+    (save-excursion
+      (or (visit-tags-table-buffer 'same)
+         (signal 'file-error (list "Visiting tags table"
+                                   "file does not exist"
+                                   file)))
+      ;; Set FILE to the expanded name.
+      (setq file tags-file-name)))
+  (if local
+      ;; Set the local value of tags-file-name.
+      (set (make-local-variable 'tags-file-name) file)
+    ;; Set the global value of tags-file-name.
+    (setq-default tags-file-name file)))
+
+;; Move tags-table-list-pointer along and set tags-file-name.
+;; If NO-INCLUDES is non-nil, ignore included tags tables.
+;; Returns nil when out of tables.
+(defun tags-next-table (&optional no-includes)
+  ;; Do we have any included tables?
+  (if (and (not no-includes)
+          (visit-tags-table-buffer 'same)
+          (tags-included-tables))
+
+      ;; Move into the included tags tables.
+      (setq tags-table-parent-pointer-list
+           ;; Save the current state of what table we are in.
+           (cons (cons tags-table-list-pointer tags-table-list-started-at)
+                 tags-table-parent-pointer-list)
+           ;; Start the pointer in the list of included tables.
+           tags-table-list-pointer tags-included-tables
+           tags-table-list-started-at tags-included-tables)
+
+    ;; No included tables.  Go to the next table in the list.
+    (setq tags-table-list-pointer
+         (cdr tags-table-list-pointer))
+    (or tags-table-list-pointer
+       ;; Wrap around.
+       (setq tags-table-list-pointer tags-table-list))
+
+    (if (eq tags-table-list-pointer tags-table-list-started-at)
+       ;; We have come full circle.  No more tables.
+       (if tags-table-parent-pointer-list
+           ;; Pop back to the tags table which includes this one.
+           (progn
+             ;; Restore the state variables.
+             (setq tags-table-list-pointer
+                   (car (car tags-table-parent-pointer-list))
+                   tags-table-list-started-at
+                   (cdr (car tags-table-parent-pointer-list))
+                   tags-table-parent-pointer-list
+                   (cdr tags-table-parent-pointer-list))
+             ;; Recurse to skip to the next table after the parent.
+             (tags-next-table t))
+         ;; All out of tags tables.
+         (setq tags-table-list-pointer nil))))
+
+  (and tags-table-list-pointer
+       ;; Set tags-file-name to the fully-expanded name.
+       (setq tags-file-name
+            (tags-expand-table-name (car tags-table-list-pointer)))))
+
+;; Expand tags table name FILE into a complete file name.
+(defun tags-expand-table-name (file)
+  (setq file (expand-file-name file))
+  (if (file-directory-p file)
+      (expand-file-name "TAGS" file)
+    file))
+
+;; Return the cdr of LIST (default: tags-table-list) whose car
+;; is equal to FILE after tags-expand-table-name on both sides.
+(defun tags-table-list-member (file &optional list)
+  (or list
+      (setq list tags-table-list))
+  (setq file (tags-expand-table-name file))
+  (while (and list
+             (not (string= file (tags-expand-table-name (car list)))))
+    (setq list (cdr list)))
+  list)
+
+;; Local var in visit-tags-table-buffer-cont
+;; which is set by tags-table-including.
+(defvar visit-tags-table-buffer-cont)
+
+;; Subroutine of visit-tags-table-buffer.  Frobs its local vars.
+;; Search TABLES for one that has tags for THIS-FILE.  Recurses on
+;; included tables.  Returns the tail of TABLES (or of an inner
+;; included list) whose car is a table listing THIS-FILE.  If
+;; CORE-ONLY is non-nil, check only tags tables that are already in
+;; buffers--don't visit any new files.
+(defun tags-table-including (this-file tables core-only &optional recursing)
+  (let ((found nil))
+    ;; Loop over TABLES, looking for one containing tags for THIS-FILE.
+    (while (and (not found)
+               tables)
+      (let ((tags-file-name (tags-expand-table-name (car tables))))
+       (if (or (get-file-buffer tags-file-name)
+               (and (not core-only)
+                    (file-exists-p tags-file-name)))
+           (progn
+             ;; Select the tags table buffer and get the file list up to date.
+             (visit-tags-table-buffer 'same)
+             (or tags-table-files
+                 (setq tags-table-files
+                       (funcall tags-table-files-function)))
+
+             (cond ((member this-file tags-table-files)
+                    ;; Found it.
+                    (setq found tables))
+
+                   ((tags-included-tables)
+                    ;; This table has included tables.  Check them.
+                    (let ((old tags-table-parent-pointer-list))
+                      (unwind-protect
+                          (progn
+                            (or recursing
+                                ;; At top level (not in an included tags
+                                ;; table), set the list to nil so we can
+                                ;; collect just the elts from this run.
+                                (setq tags-table-parent-pointer-list nil))
+                            (setq found
+                                  ;; Recurse on the list of included tables.
+                                  (tags-table-including this-file
+                                                        tags-included-tables
+                                                        core-only
+                                                        t))
+                            (if found
+                                ;; One of them lists THIS-FILE.
+                                ;; Set the table list state variables to move
+                                ;; us inside the list of included tables.
+                                (setq tags-table-parent-pointer-list
+                                      (cons
+                                       (cons tags-table-list-pointer
+                                             tags-table-list-started-at)
+                                       tags-table-parent-pointer-list)
+                                      tags-table-list-pointer found
+                                      tags-table-list-started-at found
+                                      ;; Set a local variable of
+                                      ;; our caller, visit-tags-table-buffer.
+                                      ;; Set it so we won't frob lists later.
+                                      visit-tags-table-buffer-cont
+                                      'included)))
+                        (or recursing
+                            ;; tags-table-parent-pointer-list now describes
+                            ;; the path of included tables taken by recursive
+                            ;; invocations of this function.  The recursive
+                            ;; calls have consed onto the front of the list,
+                            ;; so it is now outermost first.  We want it
+                            ;; innermost first, so reverse it.  Then append
+                            ;; the old list (from before we were called the
+                            ;; outermost time), to get the complete current
+                            ;; state of included tables.
+                            (setq tags-table-parent-pointer-list
+                                  (nconc (nreverse
+                                          tags-table-parent-pointer-list)
+                                         old))))))))))
+      (setq tables (cdr tables)))
+    found))
+
+(defun visit-tags-table-buffer (&optional cont)
+  "Select the buffer containing the current tags table.
+If optional arg is t, visit the next table in `tags-table-list'.
+If optional arg is the atom `same', don't look for a new table;
+ just select the buffer visiting `tags-file-name'.
+If arg is nil or absent, choose a first buffer from information in
+ `tags-file-name', `tags-table-list', `tags-table-list-pointer'.
+Returns t if it visits a tags table, or nil if there are no more in the list."
+
+  ;; Set tags-file-name to the tags table file we want to visit.
+  (let ((visit-tags-table-buffer-cont cont))
+    (cond ((eq visit-tags-table-buffer-cont 'same)
+          ;; Use the ambient value of tags-file-name.
+          (or tags-file-name
+              (error (substitute-command-keys
+                      (concat "No tags table in use!  "
+                              "Use \\[visit-tags-table] to select one."))))
+          ;; Set VISIT-TAGS-TABLE-BUFFER-CONT to nil
+          ;; so the code below will make sure tags-file-name
+          ;; is in tags-table-list.
+          (setq visit-tags-table-buffer-cont nil))
+
+         (visit-tags-table-buffer-cont
+          ;; Find the next table.
+          (if (tags-next-table)
+              ;; Skip over nonexistent files.
+              (while (and (let ((file (tags-expand-table-name tags-file-name)))
+                            (not (or (get-file-buffer file)
+                                     (file-exists-p file))))
+                          (tags-next-table)))))
+
+         (t
+          ;; Pick a table out of our hat.
+          (setq tags-file-name
+                (or
+                 ;; First, try a local variable.
+                 (cdr (assq 'tags-file-name (buffer-local-variables)))
+                 ;; Second, try a user-specified function to guess.
+                 (and default-tags-table-function
+                      (funcall default-tags-table-function))
+                 ;; Third, look for a tags table that contains
+                 ;; tags for the current buffer's file.
+                 ;; If one is found, the lists will be frobnicated,
+                 ;; and VISIT-TAGS-TABLE-BUFFER-CONT
+                 ;; will be set non-nil so we don't do it below.
+                 (car (or 
+                       ;; First check only tables already in buffers.
+                       (save-excursion (tags-table-including buffer-file-name
+                                                                tags-table-list
+                                                                t))
+                       ;; Since that didn't find any, now do the
+                       ;; expensive version: reading new files.
+                       (save-excursion (tags-table-including buffer-file-name
+                                                             tags-table-list
+                                                             nil))))
+                 ;; Fourth, use the user variable tags-file-name, if it is not
+                 ;; already in tags-table-list.
+                 (and tags-file-name
+                      (not (tags-table-list-member tags-file-name))
+                      tags-file-name)
+                 ;; Fifth, use the user variable giving the table list.
+                 ;; Find the first element of the list that actually exists.
+                 (let ((list tags-table-list)
+                       file)
+                   (while (and list
+                               (setq file (tags-expand-table-name (car list)))
+                               (not (get-file-buffer file))
+                               (not (file-exists-p file)))
+                     (setq list (cdr list)))
+                   (car list))
+                 ;; Finally, prompt the user for a file name.
+                 (expand-file-name
+                  (read-file-name "Visit tags table: (default TAGS) "
+                                  default-directory
+                                  "TAGS"
+                                  t))))))
+
+    ;; Expand the table name into a full file name.
+    (setq tags-file-name (tags-expand-table-name tags-file-name))
+
+    (if (and (eq visit-tags-table-buffer-cont t) (null tags-table-list-pointer))
+       ;; All out of tables.
+       nil
+
+      ;; Verify that tags-file-name is a valid tags table.
+      (if (if (get-file-buffer tags-file-name)
+             ;; The file is already in a buffer.  Check for the visited file
+             ;; having changed since we last used it.
+             (let (win)
+               (set-buffer (get-file-buffer tags-file-name))
+               (setq win (or verify-tags-table-function
+                             (initialize-new-tags-table)))
+               (if (or (verify-visited-file-modtime (current-buffer))
+                       (not (yes-or-no-p
+                             "Tags file has changed, read new contents? ")))
+                   (and win (funcall verify-tags-table-function))
+                 (revert-buffer t t)
+                 (initialize-new-tags-table)))
+           (set-buffer (find-file-noselect tags-file-name))
+           (or (string= tags-file-name buffer-file-name)
+               ;; find-file-noselect has changed the file name.
+               ;; Propagate the change to tags-file-name and tags-table-list.
+               (let ((tail (member tags-file-name tags-table-list)))
+                 (if tail
+                     (setcar tail buffer-file-name))
+                 (setq tags-file-name buffer-file-name)))
+           (initialize-new-tags-table))
+
+         ;; We have a valid tags table.
+         (progn
+           ;; Bury the tags table buffer so it
+           ;; doesn't get in the user's way.
+           (bury-buffer (current-buffer))
+
+           (if visit-tags-table-buffer-cont
+               ;; No list frobbing required.
+               nil
+
+             ;; Look in the list for the table we chose.
+             (let ((elt (tags-table-list-member tags-file-name)))
+               (or elt
+                   ;; The table is not in the current set.
+                   ;; Try to find it in another previously used set.
+                   (let ((sets tags-table-set-list))
+                     (while (and sets
+                                 (not (setq elt (tags-table-list-member
+                                                 tags-file-name (car sets)))))
+                       (setq sets (cdr sets)))
+                     (if sets
+                         ;; Found in some other set.  Switch to that set.
+                         (progn
+                           (or (memq tags-table-list tags-table-set-list)
+                               ;; Save the current list.
+                               (setq tags-table-set-list
+                                     (cons tags-table-list
+                                           tags-table-set-list)))
+                           (setq tags-table-list (car sets)))
+
+                       ;; Not found in any existing set.
+                       (if (and tags-table-list
+                                (y-or-n-p (concat "Add " tags-file-name
+                                                  " to current list"
+                                                  " of tags tables? ")))
+                           ;; Add it to the current list.
+                           (setq tags-table-list (cons tags-file-name
+                                                       tags-table-list))
+                         ;; Make a fresh list, and store the old one.
+                         (or (memq tags-table-list tags-table-set-list)
+                             (setq tags-table-set-list
+                                   (cons tags-table-list tags-table-set-list)))
+                         (setq tags-table-list (list tags-file-name)))
+                       (setq elt tags-table-list))))
+
+               ;; Set the tags table list state variables to point at the table
+               ;; we want to use first.
+               (setq tags-table-list-started-at elt
+                     tags-table-list-pointer elt)))
+
+           ;; Return of t says the tags table is valid.
+           t)
+
+       ;; The buffer was not valid.  Don't use it again.
+       (let ((file tags-file-name))
+         (kill-local-variable 'tags-file-name)
+         (if (eq file tags-file-name)
+             (setq tags-file-name nil)))
+       (error "File %s is not a valid tags table" buffer-file-name)))))
+\f
+(defun file-of-tag ()
+  "Return the file name of the file whose tags point is within.
+Assumes the tags table is the current buffer.
+File name returned is relative to tags table file's directory."
+  (funcall file-of-tag-function))
+
+;;;###autoload
+(defun tags-table-files ()
+  "Return a list of files in the current tags table.
+Assumes the tags table is the current buffer.
+File names returned are absolute."
+  (or tags-table-files
+      (setq tags-table-files
+           (funcall tags-table-files-function))))
+
+(defun tags-included-tables ()
+  "Return a list of tags tables included by the current table.
+Assumes the tags table is the current buffer."
+  (or tags-included-tables
+      (setq tags-included-tables (funcall tags-included-tables-function))))
+\f
+;; Build tags-completion-table on demand.  The single current tags table
+;; and its included tags tables (and their included tables, etc.) have
+;; their tags included in the completion table.
+(defun tags-completion-table ()
+  (or tags-completion-table
+      (condition-case ()
+         (prog2
+          (message "Making tags completion table for %s..." buffer-file-name)
+          (let ((included (tags-included-tables))
+                (table (funcall tags-completion-table-function)))
+            (save-excursion
+              ;; Iterate over the list of included tables, and combine each
+              ;; included table's completion obarray to the parent obarray.
+              (while included
+                ;; Visit the buffer.
+                (let ((tags-file-name (car included)))
+                  (visit-tags-table-buffer 'same))
+                ;; Recurse in that buffer to compute its completion table.
+                (if (tags-completion-table)
+                    ;; Combine the tables.
+                    (mapatoms (function
+                               (lambda (sym)
+                                 (intern (symbol-name sym) table)))
+                              tags-completion-table))
+                (setq included (cdr included))))
+            (setq tags-completion-table table))
+          (message "Making tags completion table for %s...done"
+                   buffer-file-name))
+       (quit (message "Tags completion table construction aborted.")
+             (setq tags-completion-table nil)))))
+
+;; Completion function for tags.  Does normal try-completion,
+;; but builds tags-completion-table on demand.
+(defun tags-complete-tag (string predicate what)
+  (save-excursion
+    (visit-tags-table-buffer)
+    (if (eq what t)
+       (all-completions string (tags-completion-table) predicate)
+      (try-completion string (tags-completion-table) predicate))))
+\f
+;; Return a default tag to search for, based on the text at point.
+(defun find-tag-default ()
+  (save-excursion
+    (while (looking-at "\\sw\\|\\s_")
+      (forward-char 1))
+    (if (or (re-search-backward "\\sw\\|\\s_"
+                               (save-excursion (beginning-of-line) (point))
+                               t)
+           (re-search-forward "\\(\\sw\\|\\s_\\)+"
+                              (save-excursion (end-of-line) (point))
+                              t))
+       (progn (goto-char (match-end 0))
+              (buffer-substring (point)
+                                (progn (forward-sexp -1)
+                                       (while (looking-at "\\s'")
+                                         (forward-char 1))
+                                       (point))))
+      nil)))
+
+;; Read a tag name from the minibuffer with defaulting and completion.
+(defun find-tag-tag (string)
+  (let* ((default (funcall (or find-tag-default-function
+                              (get major-mode 'find-tag-default-function)
+                              'find-tag-default)))
+        (spec (completing-read (if default
+                                   (format "%s(default %s) " string default)
+                                 string)
+                               'tags-complete-tag)))
+    (if (equal spec "")
+       (or default (error "There is no default tag"))
+      spec)))
+
+(defvar last-tag nil
+  "Last tag found by \\[find-tag].")
+
+;; Get interactive args for find-tag{-noselect,-other-window,-regexp}.
+(defun find-tag-interactive (prompt &optional no-default)
+  (if current-prefix-arg
+      (list nil (if (< (prefix-numeric-value current-prefix-arg) 0)
+                   '-
+                 t))
+    (list (if no-default
+             (read-string prompt)
+           (find-tag-tag prompt)))))
+
+;;;###autoload
+(defun find-tag-noselect (tagname &optional next-p regexp-p)
+  "Find tag (in current tags table) whose name contains TAGNAME.
+Returns the buffer containing the tag's definition and moves its point there,
+but does not select the buffer.
+The default for TAGNAME is the expression in the buffer near point.
+
+If second arg NEXT-P is t (interactively, with prefix arg), search for
+another tag that matches the last tagname or regexp used.  When there are
+multiple matches for a tag, more exact matches are found first.  If NEXT-P
+is the atom `-' (interactively, with prefix arg that is a negative number
+or just \\[negative-argument]), pop back to the previous tag gone to.
+
+If third arg REGEXP-P is non-nil, treat TAGNAME as a regexp.
+
+See documentation of variable `tags-file-name'."
+  (interactive (find-tag-interactive "Find tag: "))
+
+  ;; Save the current buffer's value of `find-tag-hook' before selecting the
+  ;; tags table buffer.
+  (let ((local-find-tag-hook find-tag-hook))
+    (if (eq '- next-p)
+       ;; Pop back to a previous location.
+       (if (null tags-location-stack)
+           (error "No previous tag locations")
+         (let ((marker (car tags-location-stack)))
+           ;; Pop the stack.
+           (setq tags-location-stack (cdr tags-location-stack))
+           (prog1
+               ;; Move to the saved location.
+               (set-buffer (marker-buffer marker))
+             (goto-char (marker-position marker))
+             ;; Kill that marker so it doesn't slow down editing.
+             (set-marker marker nil nil)
+             ;; Run the user's hook.  Do we really want to do this for pop?
+             (run-hooks 'local-find-tag-hook))))
+      (if next-p
+         ;; Find the same table we last used.
+         (visit-tags-table-buffer 'same)
+       ;; Pick a table to use.
+       (visit-tags-table-buffer)
+       ;; Record TAGNAME for a future call with NEXT-P non-nil.
+       (setq last-tag tagname))
+      (prog1
+         ;; Record the location so we can pop back to it later.
+         (marker-buffer
+          (car
+           (setq tags-location-stack
+                 (cons (let ((marker (make-marker)))
+                         (save-excursion
+                           (set-buffer
+                            ;; find-tag-in-order does the real work.
+                            (find-tag-in-order
+                             (if next-p last-tag tagname)
+                             (if regexp-p
+                                 find-tag-regexp-search-function
+                               find-tag-search-function)
+                             (if regexp-p
+                                 find-tag-regexp-tag-order
+                               find-tag-tag-order)
+                             (if regexp-p
+                                 find-tag-regexp-next-line-after-failure-p
+                               find-tag-next-line-after-failure-p)
+                             (if regexp-p "matching" "containing")
+                             (not next-p)))
+                           (set-marker marker (point))))
+                       tags-location-stack))))
+       (run-hooks 'local-find-tag-hook)))))
+
+;;;###autoload
+(defun find-tag (tagname &optional next-p regexp-p)
+  "Find tag (in current tags table) whose name contains TAGNAME.
+Select the buffer containing the tag's definition, and move point there.
+The default for TAGNAME is the expression in the buffer around or before point.
+
+If second arg NEXT-P is t (interactively, with prefix arg), search for
+another tag that matches the last tagname or regexp used.  When there are
+multiple matches for a tag, more exact matches are found first.  If NEXT-P
+is the atom `-' (interactively, with prefix arg that is a negative number
+or just \\[negative-argument]), pop back to the previous tag gone to.
+
+See documentation of variable `tags-file-name'."
+  (interactive (find-tag-interactive "Find tag: "))
+  (switch-to-buffer (find-tag-noselect tagname next-p regexp-p)))
+;;;###autoload (define-key esc-map "." 'find-tag)
+
+;;;###autoload
+(defun find-tag-other-window (tagname &optional next-p regexp-p)
+  "Find tag (in current tags table) whose name contains TAGNAME.
+Select the buffer containing the tag's definition in another window, and
+move point there.  The default for TAGNAME is the expression in the buffer
+around or before point.
+
+If second arg NEXT-P is t (interactively, with prefix arg), search for
+another tag that matches the last tagname or regexp used.  When there are
+multiple matches for a tag, more exact matches are found first.  If NEXT-P
+is negative (interactively, with prefix arg that is a negative number or
+just \\[negative-argument]), pop back to the previous tag gone to.
+
+See documentation of variable `tags-file-name'."
+  (interactive (find-tag-interactive "Find tag other window: "))
+
+  ;; This hair is to deal with the case where the tag is found in the
+  ;; selected window's buffer; without the hair, point is moved in both
+  ;; windows.  To prevent this, we save the selected window's point before
+  ;; doing find-tag-noselect, and restore it after.
+  (let* ((window-point (window-point (selected-window)))
+        (tagbuf (find-tag-noselect tagname next-p regexp-p))
+        (tagpoint (progn (set-buffer tagbuf) (point))))
+    (set-window-point (prog1
+                         (selected-window)
+                       (switch-to-buffer-other-window tagbuf)
+                       ;; We have to set this new window's point; it
+                       ;; might already have been displaying a
+                       ;; different portion of tagbuf, in which case
+                       ;; switch-to-buffer-other-window doesn't set
+                       ;; the window's point from the buffer.
+                       (set-window-point (selected-window) tagpoint))
+                     window-point)))
+;;;###autoload (define-key ctl-x-4-map "." 'find-tag-other-window)
+
+;;;###autoload
+(defun find-tag-other-frame (tagname &optional next-p)
+  "Find tag (in current tags table) whose name contains TAGNAME.
+Select the buffer containing the tag's definition in another frame, and
+move point there.  The default for TAGNAME is the expression in the buffer
+around or before point.
+
+If second arg NEXT-P is t (interactively, with prefix arg), search for
+another tag that matches the last tagname or regexp used.  When there are
+multiple matches for a tag, more exact matches are found first.  If NEXT-P
+is negative (interactively, with prefix arg that is a negative number or
+just \\[negative-argument]), pop back to the previous tag gone to.
+
+See documentation of variable `tags-file-name'."
+  (interactive (find-tag-interactive "Find tag other frame: "))
+  (let ((pop-up-frames t))
+    (find-tag-other-window tagname next-p)))
+;;;###autoload (define-key ctl-x-5-map "." 'find-tag-other-frame)
+
+;;;###autoload
+(defun find-tag-regexp (regexp &optional next-p other-window)
+  "Find tag (in current tags table) whose name matches REGEXP.
+Select the buffer containing the tag's definition and move point there.
+
+If second arg NEXT-P is t (interactively, with prefix arg), search for
+another tag that matches the last tagname or regexp used.  When there are
+multiple matches for a tag, more exact matches are found first.  If NEXT-P
+is negative (interactively, with prefix arg that is a negative number or
+just \\[negative-argument]), pop back to the previous tag gone to.
+
+If third arg OTHER-WINDOW is non-nil, select the buffer in another window.
+
+See documentation of variable `tags-file-name'."
+  (interactive (find-tag-interactive "Find tag regexp: " t))
+  ;; We go through find-tag-other-window to do all the display hair there.
+  (funcall (if other-window 'find-tag-other-window 'find-tag)
+          regexp next-p t))
+\f
+;; Internal tag finding function.
+
+;; PATTERN is a string to pass to second arg SEARCH-FORWARD-FUNC, and to
+;; any member of the function list ORDER (third arg).  If ORDER is nil,
+;; use saved state to continue a previous search.
+
+;; Fourth arg MATCHING is a string, an English '-ing' word, to be used in
+;; an error message.
+
+;; Fifth arg NEXT-LINE-AFTER-FAILURE-P is non-nil if after a failed match,
+;; point should be moved to the next line.
+
+;; Algorithm is as follows.  For each qualifier-func in ORDER, go to
+;; beginning of tags file, and perform inner loop: for each naive match for
+;; PATTERN found using SEARCH-FORWARD-FUNC, qualify the naive match using
+;; qualifier-func.  If it qualifies, go to the specified line in the
+;; specified source file and return.  Qualified matches are remembered to
+;; avoid repetition.  State is saved so that the loop can be continued.
+
+(defun find-tag-in-order (pattern
+                         search-forward-func
+                         order
+                         next-line-after-failure-p
+                         matching
+                         first-search)
+  (let (file                           ;name of file containing tag
+       tag-info                        ;where to find the tag in FILE
+       tags-table-file                 ;name of tags file
+       (first-table t)
+       (tag-order order)
+       goto-func
+       )
+    (save-excursion
+      (or first-search                 ;find-tag-noselect has already done it.
+         (visit-tags-table-buffer 'same))
+
+      ;; Get a qualified match.
+      (catch 'qualified-match-found
+
+       ;; Iterate over the list of tags tables.
+       (while (or first-table
+                  (visit-tags-table-buffer t))
+
+         (if first-search
+             (setq tag-lines-already-matched nil))
+
+         (and first-search first-table
+              ;; Start at beginning of tags file.
+              (goto-char (point-min)))
+         (setq first-table nil)
+
+         (setq tags-table-file buffer-file-name)
+         ;; Iterate over the list of ordering predicates.
+         (while order
+           (while (funcall search-forward-func pattern nil t)
+             ;; Naive match found.  Qualify the match.
+             (and (funcall (car order) pattern)
+                  ;; Make sure it is not a previous qualified match.
+                  ;; Use of `memq' depends on numbers being eq.
+                  (not (memq (save-excursion (beginning-of-line) (point))
+                             tag-lines-already-matched))
+                  (throw 'qualified-match-found nil))
+             (if next-line-after-failure-p
+                 (forward-line 1)))
+           ;; Try the next flavor of match.
+           (setq order (cdr order))
+           (goto-char (point-min)))
+         (setq order tag-order))
+       ;; We throw out on match, so only get here if there were no matches.
+       (error "No %stags %s %s" (if first-search "" "more ")
+              matching pattern))
+      
+      ;; Found a tag; extract location info.
+      (beginning-of-line)
+      (setq tag-lines-already-matched (cons (point)
+                                           tag-lines-already-matched))
+      ;; Expand the filename, using the tags table buffer's default-directory.
+      (setq file (expand-file-name (file-of-tag))
+           tag-info (funcall snarf-tag-function))
+
+      ;; Get the local value in the tags table buffer before switching buffers.
+      (setq goto-func goto-tag-location-function)
+
+      ;; Find the right line in the specified file.
+      (set-buffer (find-file-noselect file))
+      (widen)
+      (push-mark)
+      (funcall goto-func tag-info)
+      
+      ;; Give this buffer a local value of tags-file-name.
+      ;; The next time visit-tags-table-buffer is called,
+      ;; it will use the same tags table that found a match in this buffer.
+      (make-local-variable 'tags-file-name)
+      (setq tags-file-name tags-table-file)
+      
+      ;; Return the buffer where the tag was found.
+      (current-buffer))))
+\f
+;; `etags' TAGS file format support.
+
+;; If the current buffer is a valid etags TAGS file, give it local values of
+;; the tags table format variables, and return non-nil.
+(defun etags-recognize-tags-table ()
+  (and (etags-verify-tags-table)
+       ;; It is annoying to flash messages on the screen briefly,
+       ;; and this message is not useful.  -- rms
+       ;; (message "%s is an `etags' TAGS file" buffer-file-name)
+       (mapcar (function (lambda (elt)
+                          (set (make-local-variable (car elt)) (cdr elt))))
+              '((file-of-tag-function . etags-file-of-tag)
+                (tags-table-files-function . etags-tags-table-files)
+                (tags-completion-table-function . etags-tags-completion-table)
+                (snarf-tag-function . etags-snarf-tag)
+                (goto-tag-location-function . etags-goto-tag-location)
+                (find-tag-regexp-search-function . re-search-forward)
+                (find-tag-regexp-tag-order . (tag-re-match-p))
+                (find-tag-regexp-next-line-after-failure-p . t)
+                (find-tag-search-function . search-forward)
+                (find-tag-tag-order . (tag-exact-match-p tag-word-match-p
+                                                         tag-any-match-p))
+                (find-tag-next-line-after-failure-p . nil)
+                (list-tags-function . etags-list-tags)
+                (tags-apropos-function . etags-tags-apropos)
+                (tags-included-tables-function . etags-tags-included-tables)
+                (verify-tags-table-function . etags-verify-tags-table)
+                ))))
+
+;; Return non-nil iff the current buffer is a valid etags TAGS file.
+(defun etags-verify-tags-table ()
+  ;; Use eq instead of = in case char-after returns nil.
+  (eq (char-after 1) ?\f))
+
+(defun etags-file-of-tag ()
+  (save-excursion
+    (search-backward "\f\n")
+    (forward-char 2)
+    (buffer-substring (point)
+                     (progn (skip-chars-forward "^,") (point)))))
+
+(defun etags-tags-completion-table ()
+  (let ((table (make-vector 511 0)))
+    (save-excursion
+      (goto-char (point-min))
+      ;; This monster regexp matches an etags tag line.
+      ;;   \1 is the string to match;
+      ;;   \2 is not interesting;
+      ;;   \3 is the guessed tag name; XXX guess should be better eg DEFUN
+      ;;   \4 is not interesting;
+      ;;   \5 is the explicitly-specified tag name.
+      ;;   \6 is the line to start searching at;
+      ;;   \7 is the char to start searching at.
+      (while (re-search-forward
+             "^\\(\\(.+[^-a-zA-Z0-9_$]+\\)?\\([-a-zA-Z0-9_$]+\\)\
+\[^-a-zA-Z0-9_$]*\\)\177\\(\\([^\n\001]+\\)\001\\)?\\([0-9]+\\),\\([0-9]+\\)\n"
+             nil t)
+       (intern (if (match-beginning 5)
+                   ;; There is an explicit tag name.
+                   (buffer-substring (match-beginning 5) (match-end 5))
+                 ;; No explicit tag name.  Best guess.
+                 (buffer-substring (match-beginning 3) (match-end 3)))
+               table)))
+    table))
+
+(defun etags-snarf-tag ()
+  (let (tag-text startpos)
+    (search-forward "\177")
+    (setq tag-text (buffer-substring (1- (point))
+                                    (save-excursion (beginning-of-line)
+                                                    (point))))
+    ;; Skip explicit tag name if present.
+    (search-forward "\001" (save-excursion (forward-line 1) (point)) t)
+    (search-forward ",")
+    (setq startpos (string-to-int (buffer-substring
+                                  (point)
+                                  (progn (skip-chars-forward "0-9")
+                                         (point)))))
+    ;; Leave point on the next line of the tags file.
+    (forward-line 1)
+    (cons tag-text startpos)))
+
+(defun etags-goto-tag-location (tag-info)
+  (let ((startpos (cdr tag-info))
+       ;; This constant is 1/2 the initial search window.
+       ;; There is no sense in making it too small,
+       ;; since just going around the loop once probably
+       ;; costs about as much as searching 2000 chars.
+       (offset 1000)
+       (found nil)
+       (pat (concat (if (eq selective-display t)
+                        "\\(^\\|\^m\\)" "^")
+                    (regexp-quote (car tag-info)))))
+    (or startpos
+       (setq startpos (point-min)))
+    (while (and (not found)
+               (progn
+                 (goto-char (- startpos offset))
+                 (not (bobp))))
+      (setq found
+           (re-search-forward pat (+ startpos offset) t)
+           offset (* 3 offset)))       ; expand search window
+    (or found
+       (re-search-forward pat nil t)
+       (error "`%s' not found in %s; time to rerun etags"
+              pat buffer-file-name)))
+  ;; Position point at the right place
+  ;; if the search string matched an extra Ctrl-m at the beginning.
+  (and (eq selective-display t)
+       (looking-at "\^m")
+       (forward-char 1))
+  (beginning-of-line))
+
+(defun etags-list-tags (file)
+  (goto-char 1)
+  (if (not (search-forward (concat "\f\n" file ",") nil t))
+      nil
+    (forward-line 1)
+    (while (not (or (eobp) (looking-at "\f")))
+      (princ (buffer-substring (point)
+                              (progn (skip-chars-forward "^\177")
+                                     (point))))
+      (terpri)
+      (forward-line 1))))
+
+(defun etags-tags-apropos (string)
+  (goto-char 1)
+  (while (re-search-forward string nil t)
+    (beginning-of-line)
+    (princ (buffer-substring (point)
+                            (progn (skip-chars-forward "^\177")
+                                   (point))))
+    (terpri)
+    (forward-line 1)))
+
+(defun etags-tags-table-files ()
+  (let ((files nil)
+       beg)
+    (goto-char (point-min))
+    (while (search-forward "\f\n" nil t)
+      (setq beg (point))
+      (skip-chars-forward "^,\n")
+      (or (looking-at ",include$")
+         ;; Expand in the default-directory of the tags table buffer.
+         (setq files (cons (expand-file-name (buffer-substring beg (point)))
+                           files))))
+    (nreverse files)))
+
+(defun etags-tags-included-tables ()
+  (let ((files nil)
+       beg)
+    (goto-char (point-min))
+    (while (search-forward "\f\n" nil t)
+      (setq beg (point))
+      (skip-chars-forward "^,\n")
+      (if (looking-at ",include$")
+         ;; Expand in the default-directory of the tags table buffer.
+         (setq files (cons (expand-file-name (buffer-substring beg (point)))
+                           files))))
+    (nreverse files)))
+\f
+;; Empty tags file support.
+
+;; Recognize an empty file and give it local values of the tags table format
+;; variables which do nothing.
+(defun recognize-empty-tags-table ()
+  (and (zerop (buffer-size))
+       (mapcar (function (lambda (sym)
+                          (set (make-local-variable sym) 'ignore)))
+              '(tags-table-files-function
+                tags-completion-table-function
+                find-tag-regexp-search-function
+                find-tag-search-function
+                tags-apropos-function
+                tags-included-tables-function))
+       (set (make-local-variable 'verify-tags-table-function)
+           (function (lambda ()
+                       (zerop (buffer-size)))))))
+\f
+;;; Match qualifier functions for tagnames.
+;;; XXX these functions assume etags file format.
+
+;; This might be a neat idea, but it's too hairy at the moment.
+;;(defmacro tags-with-syntax (&rest body)
+;;  (` (let ((current (current-buffer))
+;;        (otable (syntax-table))
+;;        (buffer (find-file-noselect (file-of-tag)))
+;;        table)
+;;       (unwind-protect
+;;        (progn
+;;          (set-buffer buffer)
+;;          (setq table (syntax-table))
+;;          (set-buffer current)
+;;          (set-syntax-table table)
+;;          (,@ body))
+;;      (set-syntax-table otable)))))
+;;(put 'tags-with-syntax 'edebug-form-spec '(&rest form))
+
+;; t if point is at a tag line that matches TAG "exactly".
+;; point should be just after a string that matches TAG.
+(defun tag-exact-match-p (tag)
+  (and (looking-at "\\Sw.*\177") (looking-at "\\S_.*\177") ;not a symbol char
+       (save-excursion
+        (backward-char (1+ (length tag)))
+        (and (looking-at "\\Sw") (looking-at "\\S_")))))
+
+;; t if point is at a tag line that matches TAG as a word.
+;; point should be just after a string that matches TAG.
+(defun tag-word-match-p (tag)
+  (and (looking-at "\\b.*\177")
+       (save-excursion (backward-char (1+ (length tag)))
+                      (looking-at "\\b"))))
+
+;; t if point is in a tag line with a tag containing TAG as a substring.
+(defun tag-any-match-p (tag)
+  (looking-at ".*\177"))
+
+;; t if point is at a tag line that matches RE as a regexp.
+(defun tag-re-match-p (re)
+  (save-excursion
+    (beginning-of-line)
+    (let ((bol (point)))
+      (and (search-forward "\177" (save-excursion (end-of-line) (point)) t)
+          (re-search-backward re bol t)))))
+\f
+;;;###autoload
+(defun next-file (&optional initialize novisit)
+  "Select next file among files in current tags table.
+
+A first argument of t (prefix arg, if interactive) initializes to the
+beginning of the list of files in the tags table.  If the argument is
+neither nil nor t, it is evalled to initialize the list of files.
+
+Non-nil second argument NOVISIT means use a temporary buffer
+ to save time and avoid uninteresting warnings.
+
+Value is nil if the file was already visited;
+if the file was newly read in, the value is the filename."
+  (interactive "P")
+  (cond ((not initialize)
+        ;; Not the first run.
+        )
+       ((eq initialize t)
+        ;; Initialize the list from the tags table.
+        (save-excursion
+          ;; Visit the tags table buffer to get its list of files.
+          (visit-tags-table-buffer)
+          (setq next-file-list (tags-table-files))))
+       (t
+        ;; Initialize the list by evalling the argument.
+        (setq next-file-list (eval initialize))))
+  (or next-file-list
+      (save-excursion
+       ;; Get the files from the next tags table.
+       ;; When doing (visit-tags-table-buffer t),
+       ;; the tags table buffer must be current.
+       (if (and (visit-tags-table-buffer 'same)
+                (visit-tags-table-buffer t))
+           (setq next-file-list (tags-table-files))
+         (and novisit
+              (get-buffer " *next-file*")
+              (kill-buffer " *next-file*"))
+         (error "All files processed."))))
+  (let ((new (not (get-file-buffer (car next-file-list)))))
+    (if (not (and new novisit))
+       (set-buffer (find-file-noselect (car next-file-list) novisit))
+      ;; Like find-file, but avoids random warning messages.
+      (set-buffer (get-buffer-create " *next-file*"))
+      (kill-all-local-variables)
+      (erase-buffer)
+      (setq new (car next-file-list))
+      (insert-file-contents new nil))
+    (setq next-file-list (cdr next-file-list))
+    new))
+
+(defvar tags-loop-operate nil
+  "Form for `tags-loop-continue' to eval to change one file.")
+
+(defvar tags-loop-scan
+  '(error (substitute-command-keys
+          "No \\[tags-search] or \\[tags-query-replace] in progress."))
+  "Form for `tags-loop-continue' to eval to scan one file.
+If it returns non-nil, this file needs processing by evalling
+\`tags-loop-operate'.  Otherwise, move on to the next file.")
+
+;;;###autoload
+(defun tags-loop-continue (&optional first-time)
+  "Continue last \\[tags-search] or \\[tags-query-replace] command.
+Used noninteractively with non-nil argument to begin such a command (the
+argument is passed to `next-file', which see).
+Two variables control the processing we do on each file:
+the value of `tags-loop-scan' is a form to be executed on each file
+to see if it is interesting (it returns non-nil if so)
+and `tags-loop-operate' is a form to execute to operate on an interesting file
+If the latter returns non-nil, we exit; otherwise we scan the next file."
+  (interactive)
+  (let (new
+       (messaged nil))
+    (while
+       (progn
+         ;; Scan files quickly for the first or next interesting one.
+         (while (or first-time
+                    (save-restriction
+                      (widen)
+                      (not (eval tags-loop-scan))))
+           (setq new (next-file first-time t))
+           ;; If NEW is non-nil, we got a temp buffer,
+           ;; and NEW is the file name.
+           (if (or messaged
+                   (and (not first-time)
+                        (> baud-rate search-slow-speed)
+                        (setq messaged t)))
+               (message "Scanning file %s..." (or new buffer-file-name)))
+           (setq first-time nil)
+           (goto-char (point-min)))
+
+         ;; If we visited it in a temp buffer, visit it now for real.
+         (if new
+             (let ((pos (point)))
+               (erase-buffer)
+               (set-buffer (find-file-noselect new))
+               (widen)
+               (goto-char pos)))
+
+         (switch-to-buffer (current-buffer))
+
+         ;; Now operate on the file.
+         ;; If value is non-nil, continue to scan the next file.
+         (eval tags-loop-operate)))
+    (and messaged
+        (null tags-loop-operate)
+        (message "Scanning file %s...found" buffer-file-name))))
+;;;###autoload (define-key esc-map "," 'tags-loop-continue)
+
+;;;###autoload
+(defun tags-search (regexp &optional file-list-form)
+  "Search through all files listed in tags table for match for REGEXP.
+Stops when a match is found.
+To continue searching for next match, use command \\[tags-loop-continue].
+
+See documentation of variable `tags-file-name'."
+  (interactive "sTags search (regexp): ")
+  (if (and (equal regexp "")
+          (eq (car tags-loop-scan) 're-search-forward)
+          (null tags-loop-operate))
+      ;; Continue last tags-search as if by M-,.
+      (tags-loop-continue nil)
+    (setq tags-loop-scan
+         (list 're-search-forward regexp nil t)
+         tags-loop-operate nil)
+    (tags-loop-continue (or file-list-form t))))
+
+;;;###autoload
+(defun tags-query-replace (from to &optional delimited file-list-form)
+  "Query-replace-regexp FROM with TO through all files listed in tags table.
+Third arg DELIMITED (prefix arg) means replace only word-delimited matches.
+If you exit (\\[keyboard-quit] or ESC), you can resume the query-replace
+with the command \\[tags-loop-continue].
+
+See documentation of variable `tags-file-name'."
+  (interactive
+   "sTags query replace (regexp): \nsTags query replace %s by: \nP")
+  (setq tags-loop-scan (list 'prog1
+                            (list 'if (list 're-search-forward from nil t)
+                                  ;; When we find a match, move back
+                                  ;; to the beginning of it so perform-replace
+                                  ;; will see it.
+                                  '(goto-char (match-beginning 0))))
+       tags-loop-operate (list 'perform-replace from to t t delimited))
+  (tags-loop-continue (or file-list-form t)))
+\f
+;;;###autoload
+(defun list-tags (file)
+  "Display list of tags in file FILE.
+FILE should not contain a directory specification
+unless it has one in the tags table."
+  (interactive (list (completing-read "List tags in file: " nil
+                                     'tags-table-files t nil)))
+  (with-output-to-temp-buffer "*Tags List*"
+    (princ "Tags in file ")
+    (princ file)
+    (terpri)
+    (save-excursion
+      (let ((first-time t)
+           (gotany nil))
+       (while (visit-tags-table-buffer (not first-time))
+         (if (funcall list-tags-function file)
+             (setq gotany t)))
+       (or gotany
+           (error "File %s not in current tags tables"))))))
+
+;;;###autoload
+(defun tags-apropos (regexp)
+  "Display list of all tags in tags table REGEXP matches."
+  (interactive "sTags apropos (regexp): ")
+  (with-output-to-temp-buffer "*Tags List*"
+    (princ "Tags matching regexp ")
+    (prin1 regexp)
+    (terpri)
+    (save-excursion
+      (let ((first-time t))
+       (while (visit-tags-table-buffer (not first-time))
+         (setq first-time nil)
+         (funcall tags-apropos-function regexp))))))
+\f
+;;; XXX Kludge interface.
+
+;; XXX If a file is in multiple tables, selection may get the wrong one.
+;;;###autoload
+(defun select-tags-table ()
+  "Select a tags table file from a menu of those you have already used.
+The list of tags tables to select from is stored in `tags-table-file-list';
+see the doc of that variable if you want to add names to the list."
+  (interactive)
+  (pop-to-buffer "*Tags Table List*")
+  (setq buffer-read-only nil)
+  (erase-buffer)
+  (setq selective-display t
+       selective-display-ellipses nil)
+  (let ((set-list tags-table-set-list)
+       (desired-point nil))
+    (if tags-table-list
+       (progn
+         (setq desired-point (point-marker))
+         (princ tags-table-list (current-buffer))
+         (insert "\C-m")
+         (prin1 (car tags-table-list) (current-buffer)) ;invisible
+         (insert "\n")))
+    (while set-list
+      (if (eq (car set-list) tags-table-list)
+         ;; Already printed it.
+         ()
+       (princ (car set-list) (current-buffer))
+       (insert "\C-m")
+       (prin1 (car (car set-list)) (current-buffer)) ;invisible
+       (insert "\n"))
+      (setq set-list (cdr set-list)))
+    (if tags-file-name
+       (progn
+         (or desired-point
+             (setq desired-point (point-marker)))
+         (insert tags-file-name "\C-m")
+         (prin1 tags-file-name (current-buffer)) ;invisible
+         (insert "\n")))
+    (setq set-list (delete tags-file-name
+                          (apply 'nconc (cons tags-table-list
+                                              (mapcar 'copy-sequence
+                                                      tags-table-set-list)))))
+    (while set-list
+      (insert (car set-list) "\C-m")
+      (prin1 (car set-list) (current-buffer)) ;invisible
+      (insert "\n")
+      (setq set-list (delete (car set-list) set-list)))
+    (goto-char 1)
+    (insert-before-markers
+     "Type `t' to select a tags table or set of tags tables:\n\n")
+    (if desired-point
+       (goto-char desired-point))
+    (set-window-start (selected-window) 1 t))
+  (set-buffer-modified-p nil)
+  (setq buffer-read-only t
+       mode-name "Select Tags Table")
+  (let ((map (make-sparse-keymap)))
+    (define-key map "t" 'select-tags-table-select)
+    (define-key map " " 'next-line)
+    (define-key map "\^?" 'previous-line)
+    (define-key map "n" 'next-line)
+    (define-key map "p" 'previous-line)
+    (define-key map "q" 'select-tags-table-quit)
+    (use-local-map map)))
+  
+(defun select-tags-table-select ()
+  "Select the tags table named on this line."
+  (interactive)
+  (search-forward "\C-m")
+  (let ((name (read (current-buffer))))
+    (visit-tags-table name)
+    (select-tags-table-quit)
+    (message "Tags table now %s" name)))
+
+(defun select-tags-table-quit ()
+  "Kill the buffer and delete the selected window."
+  (interactive)
+  (kill-buffer (current-buffer))
+  (or (one-window-p)
+      (delete-window)))  
+\f
+;;;###autoload
+(defun complete-tag ()
+  "Perform tags completion on the text around point.
+Completes to the set of names listed in the current tags table.  
+The string to complete is chosen in the same way as the default
+for \\[find-tag] (which see)."
+  (interactive)
+  (or tags-table-list
+      tags-file-name
+      (error (substitute-command-keys
+             "No tags table loaded.  Try \\[visit-tags-table].")))
+  (let ((pattern (funcall (or find-tag-default-function
+                             (get major-mode 'find-tag-default-function)
+                             'find-tag-default)))
+       beg
+       completion)
+    (or pattern
+       (error "Nothing to complete"))
+    (search-backward pattern)
+    (setq beg (point))
+    (forward-char (length pattern))
+    (setq completion (try-completion pattern 'tags-complete-tag nil))
+    (cond ((eq completion t))
+         ((null completion)
+          (message "Can't find completion for \"%s\"" pattern)
+          (ding))
+         ((not (string= pattern completion))
+          (delete-region beg (point))
+          (insert completion))
+         (t
+          (message "Making completion list...")
+          (with-output-to-temp-buffer " *Completions*"
+            (display-completion-list
+             (all-completions pattern 'tags-complete-tag nil)))
+          (message "Making completion list...%s" "done")))))
+
+;;;###autoload (define-key esc-map "\t" 'complete-tag)
+\f
+(provide 'etags)
+
+;;; etags.el ends here