]> code.delx.au - gnu-emacs/blobdiff - lisp/progmodes/etags.el
(bdf-generate-font): New argument CHARSET. Give WIDTH
[gnu-emacs] / lisp / progmodes / etags.el
index e72fdb3e7cd7912979697fc3cc335f19e4e0f210..fdc73dd47af099884d26593cc65db6c334e71df8 100644 (file)
@@ -1,6 +1,5 @@
 ;;; etags.el --- etags facility for Emacs
-
-;; Copyright (C) 1985, 1986, 1988, 1989, 1992, 1993
+;; Copyright (C) 1985, 1986, 1988, 1989, 1992, 1993, 1994, 1995, 1996, 1998
 ;;     Free Software Foundation, Inc.
 
 ;; Author: Roland McGrath <roland@gnu.ai.mit.edu>
 ;; 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.
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
 
 ;;; Code:
 
+(require 'ring)
+
 ;;;###autoload
 (defvar tags-file-name nil
   "*File name of tags table.
@@ -33,62 +35,99 @@ 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: ")
 
+(defgroup etags nil "Tags tables"
+  :group 'tools)
+
 ;;;###autoload
 ;; Use `visit-tags-table-buffer' to cycle through tags tables in this list.
-(defvar tags-table-list nil
+(defcustom 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.")
+Use the `etags' program to make a tags table file."
+  :group 'etags
+  :type '(repeat file))
+
+;;;###autoload
+(defcustom tags-add-tables 'ask-user
+  "*Control whether to add a new tags table to the current list.
+t means do; nil means don't (always start a new list).
+Any other value means ask the user whether to add a new tags table
+to the current list (as opposed to starting a new list)."
+  :group 'etags
+  :type '(choice (const :tag "Do" t)
+                (const :tag "Don't" nil)
+                (other :tag "Ask" ask-user)))
+
+(defcustom tags-revert-without-query nil
+  "*Non-nil means reread a TAGS table without querying, if it has changed."
+  :group 'etags
+  :type 'boolean)
+
+(defvar tags-table-computed-list nil
+  "List of tags tables to search, computed from `tags-table-list'.
+This includes tables implicitly included by other tables.  The list is not
+always complete: the included tables of a table are not known until that
+table is read into core.  An element that is `t' is a placeholder
+indicating that the preceding element is a table that has not been read
+into core and might contain included tables to search.
+See `tags-table-check-computed-list'.")
+
+(defvar tags-table-computed-list-for nil
+  "Value of `tags-table-list' that `tags-table-computed-list' corresponds to.
+If `tags-table-list' changes, `tags-table-computed-list' is thrown away and
+recomputed; see `tags-table-check-computed-list'.")
 
 (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.
+  "Pointer into `tags-table-computed-list' for the current state of searching.
 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.")
+  "Pointer into `tags-table-computed-list', where the current search started.")
 
 (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
+(defcustom 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.")
+not the value in the buffer \\[find-tag] goes to."
+  :group 'etags
+  :type 'hook)
 
 ;;;###autoload
-(defvar find-tag-default-function nil
+(defcustom 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.")
+Otherwise, `find-tag-default' is used."
+  :group 'etags
+  :type 'function)
+
+(defcustom find-tag-marker-ring-length 16
+  "*Length of marker rings `find-tag-marker-ring' and `tags-location-ring'."
+  :group 'etags
+  :type 'integer
+  :version "20.3")
+
+(defvar find-tag-marker-ring (make-ring find-tag-marker-ring-length)
+  "Ring of markers which are locations from which \\[find-tag] was invoked.")
 
-;;;###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.")
+  "If non-nil, a function to choose a default tags file for a buffer.
+This function receives no arguments and should return the default
+tags table file to use for the current buffer.")
 
-(defvar tags-location-stack nil
-  "List of markers which are locations visited by \\[find-tag].
+(defvar tags-location-ring (make-ring find-tag-marker-ring-length)
+  "Ring 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.")
@@ -106,8 +145,8 @@ nil means it has not yet been computed; use `tags-table-files' to do so.")
 
 (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,
+  "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.")
 
@@ -141,18 +180,18 @@ One argument, the tag info returned by `snarf-tag-function'.")
 (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.")
+  "Function to return t iff current buffer contains valid 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)
+  (setq find-tag-marker-ring (make-ring find-tag-marker-ring-length))
+  (setq tags-location-ring (make-ring find-tag-marker-ring-length))
   ;; Value is t if we have found a valid tags table buffer.
   (let ((hooks tags-table-format-hooks))
     (while (and hooks
@@ -177,12 +216,16 @@ file the tag was in."
                                                       default-directory)
                                     t)
                     current-prefix-arg))
+  (or (stringp file) (signal 'wrong-type-argument (list 'stringp file)))
+  ;; Bind tags-file-name so we can control below whether the local or
+  ;; global value gets set.  Calling visit-tags-table-buffer will
+  ;; initialize a buffer for the file and set tags-file-name to the
   ;; 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)
+      (or (visit-tags-table-buffer file)
          (signal 'file-error (list "Visiting tags table"
                                    "file does not exist"
                                    file)))
@@ -194,52 +237,83 @@ file the tag was in."
     ;; 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)))))
+(defun tags-table-check-computed-list ()
+  "Compute `tags-table-computed-list' from `tags-table-list' if necessary."
+  (let ((expanded-list (mapcar 'tags-expand-table-name tags-table-list)))
+    (or (equal tags-table-computed-list-for expanded-list)
+       ;; The list (or default-directory) has changed since last computed.
+       (let* ((compute-for (mapcar 'copy-sequence expanded-list))
+              (tables (copy-sequence compute-for)) ;Mutated in the loop.
+              (computed nil)
+              table-buffer)
+
+         (while tables
+           (setq computed (cons (car tables) computed)
+                 table-buffer (get-file-buffer (car tables)))
+           (if (and table-buffer
+                    ;; There is a buffer visiting the file.  Now make sure
+                    ;; it is initialized as a tag table buffer.
+                    (save-excursion
+                      (tags-verify-table (buffer-file-name table-buffer))))
+               (save-excursion
+                 (set-buffer table-buffer)
+                 (if (tags-included-tables)
+                     ;; Insert the included tables into the list we
+                     ;; are processing.
+                     (setcdr tables (nconc (mapcar 'tags-expand-table-name
+                                                   (tags-included-tables))
+                                           (cdr tables)))))
+             ;; This table is not in core yet.  Insert a placeholder
+             ;; saying we must read it into core to check for included
+             ;; tables before searching the next table in the list.
+             (setq computed (cons t computed)))
+           (setq tables (cdr tables)))
+
+         ;; Record the tags-table-list value (and the context of the
+         ;; current directory) we computed from.
+         (setq tags-table-computed-list-for compute-for
+               tags-table-computed-list (nreverse computed))))))
+
+;; Extend `tags-table-computed-list' to remove the first `t' placeholder.
+;; An element of the list that is `t' is a placeholder indicating that the
+;; preceding element is a table that has not been read into core and might
+;; contain included tables to search.  On return, the first placeholder
+;; element will be gone and the element before it read into core and its
+;; included tables inserted into the list.
+(defun tags-table-extend-computed-list ()
+  (let ((list tags-table-computed-list))
+    (while (not (eq (nth 1 list) t))
+      (setq list (cdr list)))
+    (save-excursion
+      (if (tags-verify-table (car list))
+         ;; We are now in the buffer visiting (car LIST).  Extract its
+         ;; list of included tables and insert it into the computed list.
+         (let ((tables (tags-included-tables))
+               (computed nil)
+               table-buffer)
+           (while tables
+             (setq computed (cons (car tables) computed)
+                   table-buffer (get-file-buffer (car tables)))
+             (if table-buffer
+                 (save-excursion
+                   (set-buffer table-buffer)
+                   (if (tags-included-tables)
+                       ;; Insert the included tables into the list we
+                       ;; are processing.
+                       (setcdr tables (append (tags-included-tables)
+                                              tables))))
+               ;; This table is not in core yet.  Insert a placeholder
+               ;; saying we must read it into core to check for included
+               ;; tables before searching the next table in the list.
+               (setq computed (cons t computed)))
+             (setq tables (cdr tables)))
+           (setq computed (nreverse computed))
+           ;; COMPUTED now contains the list of included tables (and
+           ;; tables included by them, etc.).  Now splice this into the
+           ;; current list.
+           (setcdr list (nconc computed (cdr (cdr list)))))
+       ;; It was not a valid table, so just remove the following placeholder.
+       (setcdr list (cdr (cdr list)))))))
 
 ;; Expand tags table name FILE into a complete file name.
 (defun tags-expand-table-name (file)
@@ -248,93 +322,132 @@ file the tag was in."
       (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))
+;; Like member, but comparison is done after tags-expand-table-name on both
+;; sides and elements of LIST that are t are skipped.
+(defun tags-table-list-member (file list)
   (setq file (tags-expand-table-name file))
   (while (and list
-             (not (string= file (tags-expand-table-name (car list)))))
+             (or (eq (car list) t)
+                 (not (string= file (tags-expand-table-name (car list))))))
     (setq list (cdr list)))
   list)
 
-;; 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.
+(defun tags-verify-table (file)
+  "Read FILE into a buffer and verify that it is a valid tags table.
+Sets the current buffer to one visiting FILE (if it exists).
+Returns non-nil iff it is a valid table."
+  (if (get-file-buffer file)
+      ;; 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 file))
+       (setq win (or verify-tags-table-function (initialize-new-tags-table)))
+       (if (or (verify-visited-file-modtime (current-buffer))
+               ;; Decide whether to revert the file.
+               ;; revert-without-query can say to revert
+               ;; or the user can say to revert.
+               (not (or (let ((tail revert-without-query)
+                              (found nil))
+                          (while tail
+                            (if (string-match (car tail) buffer-file-name)
+                                (setq found t))
+                            (setq tail (cdr tail)))
+                          found)
+                        tags-revert-without-query
+                        (yes-or-no-p
+                         (format "Tags file %s has changed, read new contents? "
+                                 file)))))
+           (and verify-tags-table-function
+                (funcall verify-tags-table-function))
+         (revert-buffer t t)
+         (initialize-new-tags-table)))
+    (and (file-exists-p file)
+        (progn
+          (set-buffer (find-file-noselect file))
+          (or (string= file 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 file tags-table-list)))
+                (if tail
+                    (setcar tail buffer-file-name))
+                (if (eq file tags-file-name)
+                    (setq tags-file-name buffer-file-name))))
+          (initialize-new-tags-table)))))
+
+;; Subroutine of visit-tags-table-buffer.  Search the current tags tables
+;; for one that has tags for THIS-FILE (or that includes a table that
+;; does).  Return the name of the first table table listing THIS-FILE; if
+;; the table is one included by another table, it is the master table that
+;; we return.  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 core-only)
+  (let ((tables tags-table-computed-list)
+       (found nil))
+    ;; Loop over the list, looking for a table 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
-                                      ;; CONT is a local variable of
-                                      ;; our caller, visit-tags-table-buffer.
-                                      ;; Set it so we won't frob lists later.
-                                      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))))))))))
+
+      (if core-only
+         ;; Skip tables not in core.
+         (while (eq (nth 1 tables) t)
+           (setq tables (cdr (cdr tables))))
+       (if (eq (nth 1 tables) t)
+           ;; This table has not been read into core yet.  Read it in now.
+           (tags-table-extend-computed-list)))
+
+      (if tables
+         ;; Select the tags table buffer and get the file list up to date.
+         (let ((tags-file-name (car tables)))
+           (visit-tags-table-buffer 'same)
+           (if (member this-file (mapcar 'expand-file-name
+                                         (tags-table-files)))
+               ;; Found it.
+               (setq found tables))))
       (setq tables (cdr tables)))
-    found))
+    (if found
+       ;; Now determine if the table we found was one included by another
+       ;; table, not explicitly listed.  We do this by checking each
+       ;; element of the computed list to see if it appears in the user's
+       ;; explicit list; the last element we will check is FOUND itself.
+       ;; Then we return the last one which did in fact appear in
+       ;; tags-table-list.
+       (let ((could-be nil)
+             (elt tags-table-computed-list))
+         (while (not (eq elt (cdr found)))
+           (if (tags-table-list-member (car elt) tags-table-list)
+               ;; This table appears in the user's list, so it could be
+               ;; the one which includes the table we found.
+               (setq could-be (car elt)))
+           (setq elt (cdr elt))
+           (if (eq t (car elt))
+               (setq elt (cdr elt))))
+         ;; The last element we found in the computed list before FOUND
+         ;; that appears in the user's list will be the table that
+         ;; included the one we found.
+         could-be))))
+
+;; Subroutine of visit-tags-table-buffer.  Move tags-table-list-pointer
+;; along and set tags-file-name.  Returns nil when out of tables.
+(defun tags-next-table ()
+  ;; If there is a placeholder element next, compute the list to replace it.
+  (while (eq (nth 1 tags-table-list-pointer) t)
+    (tags-table-extend-computed-list))
+
+  ;; 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-computed-list))
+
+  (if (eq tags-table-list-pointer tags-table-list-started-at)
+      ;; We have come full circle.  No more tables.
+      (setq tags-table-list-pointer nil)
+    ;; Set tags-file-name to the name from the list.  It is already expanded.
+    (setq tags-file-name (car tags-table-list-pointer))))
 
 (defun visit-tags-table-buffer (&optional cont)
   "Select the buffer containing the current tags table.
+If optional arg is a string, visit that file as a 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'.
@@ -346,49 +459,49 @@ Returns t if it visits a tags table, or nil if there are no more in the list."
   (cond ((eq 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 CONT to nil so the code below will make sure tags-file-name
-        ;; is in tags-table-list.
-        (setq cont nil))
-
-       (cont
+            (error "%s"
+                   (substitute-command-keys
+                    (concat "No tags table in use; "
+                            "use \\[visit-tags-table] to select one")))))
+
+       ((eq t 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))))
+            (while (and (not (or (get-file-buffer tags-file-name)
+                                 (file-exists-p tags-file-name)))
                         (tags-next-table)))))
 
        (t
         ;; Pick a table out of our hat.
+        (tags-table-check-computed-list) ;Get it up to date, we might use it.
         (setq tags-file-name
               (or
+               ;; If passed a string, use that.
+               (if (stringp cont)
+                   (prog1 cont
+                     (setq cont nil)))
                ;; 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 CONT will be set non-nil so we don't do it below.
-               (car (or 
+               ;; 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 CONT will be set non-nil so we don't
+               ;; do it below.
+               (and buffer-file-name
+                    (or
                      ;; First check only tables already in buffers.
-                     (save-excursion (tags-table-including buffer-file-name
-                                                              tags-table-list
-                                                              t))
+                     (tags-table-including buffer-file-name 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.
+                     (tags-table-including buffer-file-name nil)))
+               ;; Fourth, use the user variable tags-file-name, if it is
+               ;; not already in the current list.
                (and tags-file-name
-                    (not (tags-table-list-member tags-file-name))
+                    (not (tags-table-list-member tags-file-name
+                                                 tags-table-computed-list))
                     tags-file-name)
                ;; Fifth, use the user variable giving the table list.
                ;; Find the first element of the list that actually exists.
@@ -410,93 +523,113 @@ Returns t if it visits a tags table, or nil if there are no more in the list."
   ;; Expand the table name into a full file name.
   (setq tags-file-name (tags-expand-table-name tags-file-name))
 
-  (if (and (eq cont t) (null tags-table-list-pointer))
+  (if (and (eq 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 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.
+    ;; Verify that tags-file-name names a valid tags table.
+    ;; Bind another variable with the value of tags-file-name
+    ;; before we switch buffers, in case tags-file-name is buffer-local.
+    (let ((curbuf (current-buffer))
+         (local-tags-file-name tags-file-name))
+      (if (tags-verify-table local-tags-file-name)
+
+         ;; 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 this was a new table selection (CONT is nil), make
+           ;; sure tags-table-list includes the chosen table, and
+           ;; update the list pointer variables.
+           (or cont
+               ;; Look in the list for the table we chose.
+               (let ((found (tags-table-list-member
+                             local-tags-file-name
+                             tags-table-computed-list)))
+                 (if found
+                     ;; There it is.  Just switch to it.
+                     (setq tags-table-list-pointer found
+                           tags-table-list-started-at found)
+
+                   ;; 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 (tags-table-list-member
+                                       local-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
+                                (or (eq t tags-add-tables)
+                                    (and tags-add-tables
+                                         (y-or-n-p
+                                          (concat "Keep current list of "
+                                                  "tags tables also? ")))))
+                           ;; Add it to the current list.
+                           (setq tags-table-list (cons local-tags-file-name
+                                                       tags-table-list))
+
+                         ;; Make a fresh list, and store the old one.
+                         (message "Starting a new list of tags tables")
+                         (or (null tags-table-list)
+                             (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 (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))
+                         (setq tags-table-list (list local-tags-file-name))))
+
+                     ;; Recompute tags-table-computed-list.
+                     (tags-table-check-computed-list)
+                     ;; Set the tags table list state variables to start
+                     ;; over from tags-table-computed-list.
+                     (setq tags-table-list-started-at tags-table-computed-list
+                           tags-table-list-pointer
+                           tags-table-computed-list)))))
+
+           ;; Return of t says the tags table is valid.
+           t)
+
+       ;; The buffer was not valid.  Don't use it again.
+       (set-buffer curbuf)
        (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))))
+       (if (eq local-tags-file-name tags-file-name)
+           (setq tags-file-name nil))
+       (error "File %s is not a valid tags table" local-tags-file-name)))))
+
+(defun tags-reset-tags-tables ()
+  "Reset tags state to cancel effect of any previous \\[visit-tags-table] or \\[find-tag]."
+  (interactive)
+  (setq tags-file-name nil
+       tags-location-ring (progn
+                            (mapcar (lambda (m)
+                                      (set-marker m nil))
+                                    tags-location-ring)
+                            (make-ring find-tag-marker-ring-length))
+       find-tag-marker-ring (progn
+                              (mapcar (lambda (m)
+                                        (set-marker m nil))
+                                      find-tag-marker-ring)
+                              (make-ring find-tag-marker-ring-length))
+       tags-table-list nil
+       tags-table-computed-list nil
+       tags-table-computed-list-for nil
+       tags-table-list-pointer nil
+       tags-table-list-started-at nil
+       tags-table-set-list nil))
 \f
 (defun file-of-tag ()
   "Return the file name of the file whose tags point is within.
@@ -507,8 +640,9 @@ File name returned is relative to tags table file's directory."
 ;;;###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."
+Assumes the tags table is the current buffer.  The file names are returned
+as they appeared in the `etags' command that created the table, usually
+without directory names."
   (or tags-table-files
       (setq tags-table-files
            (funcall tags-table-files-function))))
@@ -554,7 +688,9 @@ Assumes the tags table is the current buffer."
 ;; but builds tags-completion-table on demand.
 (defun tags-complete-tag (string predicate what)
   (save-excursion
-    (visit-tags-table-buffer)
+    ;; If we need to ask for the tag table, allow that.
+    (let ((enable-recursive-minibuffers t))
+      (visit-tags-table-buffer))
     (if (eq what t)
        (all-completions string (tags-completion-table) predicate)
       (try-completion string (tags-completion-table) predicate))))
@@ -586,7 +722,8 @@ Assumes the tags table is the current buffer."
         (spec (completing-read (if default
                                    (format "%s(default %s) " string default)
                                  string)
-                               'tags-complete-tag)))
+                               'tags-complete-tag
+                               nil nil nil nil default)))
     (if (equal spec "")
        (or default (error "There is no default tag"))
       spec)))
@@ -604,6 +741,8 @@ Assumes the tags table is the current buffer."
              (read-string prompt)
            (find-tag-tag prompt)))))
 
+(defvar find-tag-history nil)
+
 ;;;###autoload
 (defun find-tag-noselect (tagname &optional next-p regexp-p)
   "Find tag (in current tags table) whose name contains TAGNAME.
@@ -619,27 +758,33 @@ 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.
 
+A marker representing the point when this command is onvoked is pushed
+onto a ring and may be popped back to with \\[pop-tag-mark].
+Contrast this with the ring of marks gone to by the command.
+
 See documentation of variable `tags-file-name'."
   (interactive (find-tag-interactive "Find tag: "))
 
+  (setq find-tag-history (cons tagname find-tag-history))
   ;; 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)
+       (if (ring-empty-p tags-location-ring)
            (error "No previous tag locations")
-         (let ((marker (car tags-location-stack)))
-           ;; Pop the stack.
-           (setq tags-location-stack (cdr tags-location-stack))
+         (let ((marker (ring-remove tags-location-ring 0)))
            (prog1
                ;; Move to the saved location.
-               (set-buffer (marker-buffer marker))
+               (set-buffer (or (marker-buffer marker)
+                                (error "The marked buffer has been deleted")))
              (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))))
+      ;; Record whence we came.
+      (ring-insert find-tag-marker-ring (point-marker))
       (if next-p
          ;; Find the same table we last used.
          (visit-tags-table-buffer 'same)
@@ -647,31 +792,28 @@ See documentation of variable `tags-file-name'."
        (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)))))
+      ;; Record the location so we can pop back to it later.
+      (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))
+         (run-hooks 'local-find-tag-hook)
+         (ring-insert tags-location-ring marker)
+         (current-buffer))))))
 
 ;;;###autoload
 (defun find-tag (tagname &optional next-p regexp-p)
@@ -685,6 +827,12 @@ 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.
+
+A marker representing the point when this command is onvoked is pushed
+onto a ring and may be popped back to with \\[pop-tag-mark].
+Contrast this with the ring of marks gone to by the command.
+
 See documentation of variable `tags-file-name'."
   (interactive (find-tag-interactive "Find tag: "))
   (switch-to-buffer (find-tag-noselect tagname next-p regexp-p)))
@@ -703,6 +851,12 @@ 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 REGEXP-P is non-nil, treat TAGNAME as a regexp.
+
+A marker representing the point when this command is onvoked is pushed
+onto a ring and may be popped back to with \\[pop-tag-mark].
+Contrast this with the ring of marks gone to by the command.
+
 See documentation of variable `tags-file-name'."
   (interactive (find-tag-interactive "Find tag other window: "))
 
@@ -738,6 +892,12 @@ 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 REGEXP-P is non-nil, treat TAGNAME as a regexp.
+
+A marker representing the point when this command is onvoked is pushed
+onto a ring and may be popped back to with \\[pop-tag-mark].
+Contrast this with the ring of marks gone to by the command.
+
 See documentation of variable `tags-file-name'."
   (interactive (find-tag-interactive "Find tag other frame: "))
   (let ((pop-up-frames t))
@@ -757,11 +917,34 @@ 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.
 
+A marker representing the point when this command is onvoked is pushed
+onto a ring and may be popped back to with \\[pop-tag-mark].
+Contrast this with the ring of marks gone to by the command.
+
 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))
+;;;###autoload (define-key esc-map [?\C-.] 'find-tag-regexp)
+
+;;;###autoload (define-key esc-map "*" 'pop-tag-mark)
+
+;;;###autoload
+(defun pop-tag-mark ()
+  "Pop back to where \\[find-tag] was last invoked.
+
+This is distinct from invoking \\[find-tag] with a negative argument
+since that pops a stack of markers at which tags were found, not from
+where they were found."
+  (interactive)
+  (if (ring-empty-p find-tag-marker-ring)
+      (error "No previous locations for find-tag invocation"))
+  (let ((marker (ring-remove find-tag-marker-ring 0)))
+    (switch-to-buffer (or (marker-buffer marker)
+                          (error "The marked buffer has been deleted")))
+    (goto-char (marker-position marker))
+    (set-marker marker nil nil)))
 \f
 ;; Internal tag finding function.
 
@@ -782,6 +965,8 @@ See documentation of variable `tags-file-name'."
 ;; specified source file and return.  Qualified matches are remembered to
 ;; avoid repetition.  State is saved so that the loop can be continued.
 
+(defvar tag-lines-already-matched nil) ;matches remembered here between calls
+
 (defun find-tag-in-order (pattern
                          search-forward-func
                          order
@@ -790,14 +975,25 @@ See documentation of variable `tags-file-name'."
                          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)
+       (match-marker (make-marker))
        goto-func
        )
     (save-excursion
-      (or first-search                 ;find-tag-noselect has already done it.
-         (visit-tags-table-buffer 'same))
+
+      (if first-search
+         ;; This is the start of a search for a fresh tag.
+         ;; Clear the list of tags matched by the previous search.
+         ;; find-tag-noselect has already put us in the first tags table
+         ;; buffer before we got called.
+         (setq tag-lines-already-matched nil)
+       ;; Continuing to search for the tag specified last time.
+       ;; tag-lines-already-matched lists locations matched in previous
+       ;; calls so we don't visit the same tag twice if it matches twice
+       ;; during two passes with different qualification predicates.
+       ;; Switch to the current tags table buffer.
+       (visit-tags-table-buffer 'same))
 
       ;; Get a qualified match.
       (catch 'qualified-match-found
@@ -806,24 +1002,22 @@ See documentation of variable `tags-file-name'."
        (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))
+                  (not (member (set-marker match-marker (save-excursion
+                                                          (beginning-of-line)
+                                                          (point)))
+                               tag-lines-already-matched))
                   (throw 'qualified-match-found nil))
              (if next-line-after-failure-p
                  (forward-line 1)))
@@ -832,12 +1026,18 @@ See documentation of variable `tags-file-name'."
            (goto-char (point-min)))
          (setq order tag-order))
        ;; We throw out on match, so only get here if there were no matches.
+       ;; Clear out the markers we use to avoid duplicate matches so they
+       ;; don't slow down editting and are immediately available for GC.
+       (while tag-lines-already-matched
+         (set-marker (car tag-lines-already-matched) nil nil)
+         (setq tag-lines-already-matched (cdr tag-lines-already-matched)))
+       (set-marker match-marker nil nil)
        (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)
+      (setq tag-lines-already-matched (cons match-marker
                                            tag-lines-already-matched))
       ;; Expand the filename, using the tags table buffer's default-directory.
       (setq file (expand-file-name (file-of-tag))
@@ -851,13 +1051,7 @@ See documentation of variable `tags-file-name'."
       (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
@@ -881,8 +1075,11 @@ See documentation of variable `tags-file-name'."
                 (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-tag-order . (tag-exact-file-name-match-p
+                                       tag-exact-match-p
+                                       tag-symbol-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)
@@ -897,10 +1094,10 @@ See documentation of variable `tags-file-name'."
 
 (defun etags-file-of-tag ()
   (save-excursion
-    (search-backward "\f\n")
-    (forward-char 2)
-    (buffer-substring (point)
-                     (progn (skip-chars-forward "^,") (point)))))
+    (re-search-backward "\f\n\\([^\n]+\\),[0-9]*\n")
+    (expand-file-name (buffer-substring (match-beginning 1) (match-end 1))
+                     (file-truename default-directory))))
+
 
 (defun etags-tags-completion-table ()
   (let ((table (make-vector 511 0)))
@@ -910,61 +1107,113 @@ See documentation of variable `tags-file-name'."
       ;;   \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 the char to start searching at;
-      ;;   \5 is the line to start searching at;
-      ;;   \6 is not interesting;
-      ;;   \7 is the explicitly-specified tag name.
+      ;;   \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\
-\\([0-9]+\\),\\([0-9]+\\)\\(,\001\\([^\n]+\\)\\)?\n"
+             "^\\(\\([^\177]+[^-a-zA-Z0-9_$\177]+\\)?\\([-a-zA-Z0-9_$?:]+\\)\
+\[^-a-zA-Z0-9_$?:\177]*\\)\177\\(\\([^\n\001]+\\)\001\\)?\
+\\([0-9]+\\)?,\\([0-9]+\\)?\n"
              nil t)
-       (intern (if (match-beginning 6)
+       (intern (if (match-beginning 5)
                    ;; There is an explicit tag name.
-                   (buffer-substring (match-beginning 6) (match-end 6))
+                   (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))))
-    (search-forward ",")
-    (setq startpos (string-to-int (buffer-substring
-                                  (point)
-                                  (progn (skip-chars-forward "0-9")
-                                         (point)))))
+  (let (tag-text line startpos)
+    (if (save-excursion
+         (forward-line -1)
+         (looking-at "\f\n"))
+       ;; The match was for a source file name, not any tag within a file.
+       ;; Give text of t, meaning to go exactly to the location we specify,
+       ;; the beginning of the file.
+       (setq tag-text t
+             line nil
+             startpos 1)
+
+      ;; Find the end of the tag and record the whole tag text.
+      (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)
+      (if (looking-at "[0-9]")
+         (setq line (string-to-int (buffer-substring
+                                    (point)
+                                    (progn (skip-chars-forward "0-9")
+                                           (point))))))
+      (search-forward ",")
+      (if (looking-at "[0-9]")
+         (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)))
-
+    (cons tag-text (cons line startpos))))
+
+;; TAG-INFO is a cons (TEXT LINE . POSITION) where TEXT is the initial part
+;; of a line containing the tag and POSITION is the character position of
+;; TEXT within the file (starting from 1); LINE is the line number.  If
+;; TEXT is t, it means the tag refers to exactly LINE or POSITION
+;; (whichever is present, LINE having preference, no searching.  Either
+;; LINE or POSITION may be nil; POSITION is used if present.  If the tag
+;; isn't exactly at the given position then look around that position using
+;; a search window which expands until it hits the start of file.
 (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 "^" (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)))
-  (beginning-of-line))
+  (let ((startpos (cdr (cdr tag-info)))
+       (line (car (cdr tag-info)))
+       offset found pat)
+    (if (eq (car tag-info) t)
+       ;; Direct file tag.
+       (cond (line (goto-line line))
+             (startpos (goto-char startpos))
+             (t (error "etags.el BUG: bogus direct file tag")))
+      ;; 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.
+      (setq offset 1000
+           found nil
+           pat (concat (if (eq selective-display t)
+                           "\\(^\\|\^m\\)" "^")
+                       (regexp-quote (car tag-info))))
+      ;; The character position in the tags table is 0-origin.
+      ;; Convert it to a 1-origin Emacs character position.
+      (if startpos (setq startpos (1+ startpos)))
+      ;; If no char pos was given, try the given line number.
+      (or startpos
+         (if line
+             (setq startpos (progn (goto-line line)
+                                   (point)))))
+      (or startpos
+         (setq startpos (point-min)))
+      ;; First see if the tag is right at the specified location.
+      (goto-char startpos)
+      (setq found (looking-at pat))
+      (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 "Rerun etags: `%s' not found in %s"
+                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)
@@ -972,11 +1221,18 @@ See documentation of variable `tags-file-name'."
       nil
     (forward-line 1)
     (while (not (or (eobp) (looking-at "\f")))
-      (princ (buffer-substring (point)
-                              (progn (skip-chars-forward "^\177")
-                                     (point))))
+      (let ((tag (buffer-substring (point)
+                                  (progn (skip-chars-forward "^\177")
+                                         (point)))))
+       (princ (if (looking-at "[^\n]+\001")
+                  ;; There is an explicit tag name; use that.
+                  (buffer-substring (1+ (point)) ;skip \177
+                                    (progn (skip-chars-forward "^\001")
+                                           (point)))
+                tag)))
       (terpri)
-      (forward-line 1))))
+      (forward-line 1))
+    t))
 
 (defun etags-tags-apropos (string)
   (goto-char 1)
@@ -994,11 +1250,10 @@ See documentation of variable `tags-file-name'."
     (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))))
+      (end-of-line)
+      (skip-chars-backward "^," beg)
+      (or (looking-at "include$")
+         (setq files (cons (buffer-substring beg (1- (point))) files))))
     (nreverse files)))
 
 (defun etags-tags-included-tables ()
@@ -1007,10 +1262,11 @@ See documentation of variable `tags-file-name'."
     (goto-char (point-min))
     (while (search-forward "\f\n" nil t)
       (setq beg (point))
-      (skip-chars-forward "^,\n")
-      (if (looking-at ",include$")
+      (end-of-line)
+      (skip-chars-backward "^," beg)
+      (if (looking-at "include$")
          ;; Expand in the default-directory of the tags table buffer.
-         (setq files (cons (expand-file-name (buffer-substring beg (point)))
+         (setq files (cons (expand-file-name (buffer-substring beg (1- (point))))
                            files))))
     (nreverse files)))
 \f
@@ -1051,10 +1307,19 @@ See documentation of variable `tags-file-name'."
 ;;      (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".
+;; 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
+  ;; The match is really exact if there is an explicit tag name.
+  (or (and (eq (char-after (point)) ?\001)
+          (eq (char-after (- (point) (length tag) 1)) ?\177))
+      ;; We are not on the explicit tag name, but perhaps it follows.
+      (looking-at (concat "[^\177\n]*\177" (regexp-quote tag) "\001"))))
+
+;; t if point is at a tag line that matches TAG as a symbol.
+;; point should be just after a string that matches TAG.
+(defun tag-symbol-match-p (tag)
+  (and (looking-at "\\Sw.*\177") (looking-at "\\S_.*\177")
        (save-excursion
         (backward-char (1+ (length tag)))
         (and (looking-at "\\Sw") (looking-at "\\S_")))))
@@ -1063,9 +1328,14 @@ See documentation of variable `tags-file-name'."
 ;; 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)))
+       (save-excursion (backward-char (length tag))
                       (looking-at "\\b"))))
 
+(defun tag-exact-file-name-match-p (tag)
+  (and (looking-at ",")
+       (save-excursion (backward-char (length tag))
+                      (looking-at "\f\n"))))
+
 ;; 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"))
@@ -1081,50 +1351,74 @@ See documentation of variable `tags-file-name'."
 ;;;###autoload
 (defun next-file (&optional initialize novisit)
   "Select next file among files in current tags table.
-Non-nil first argument (prefix arg, if interactive)
-initializes to the beginning of the list of files in the 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")
-  (and initialize
-       (save-excursion
-        ;; Visit the tags table buffer to get its list of files.
-        (visit-tags-table-buffer)
-        (setq next-file-list (tags-table-files))))
-  (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)))))
+  ;; Make the interactive arg t if there was any prefix arg.
+  (interactive (list (if current-prefix-arg t)))
+  (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)
+          ;; Copy the list so we can setcdr below, and expand the file
+          ;; names while we are at it, in this buffer's default directory.
+          (setq next-file-list (mapcar 'expand-file-name (tags-table-files)))
+          ;; Iterate over all the tags table files, collecting
+          ;; a complete list of referenced file names.
+          (while (visit-tags-table-buffer t)
+            ;; Find the tail of the working list and chain on the new
+            ;; sublist for this tags table.
+            (let ((tail next-file-list))
+              (while (cdr tail)
+                (setq tail (cdr tail)))
+              ;; Use a copy so the next loop iteration will not modify the
+              ;; list later returned by (tags-table-files).
+              (if tail
+                  (setcdr tail (mapcar 'expand-file-name (tags-table-files)))
+                (setq next-file-list (mapcar 'expand-file-name
+                                             (tags-table-files))))))))
+       (t
+        ;; Initialize the list by evalling the argument.
+        (setq next-file-list (eval initialize))))
+  (if next-file-list
+      ()
+    (and novisit
+        (get-buffer " *next-file*")
+        (kill-buffer " *next-file*"))
+    (error "All files processed"))
+  (let* ((next (car next-file-list))
+        (new (not (get-file-buffer next))))
+    ;; Advance the list before trying to find the file.
+    ;; If we get an error finding the file, don't get stuck on it.
+    (setq next-file-list (cdr next-file-list))
     (if (not (and new novisit))
-       (set-buffer (find-file-noselect (car next-file-list) novisit))
+       (set-buffer (find-file-noselect next 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))
+      (setq new next)
       (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."))
+  '(error "%s"
+         (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.")
@@ -1132,12 +1426,14 @@ If it returns non-nil, this file needs processing by evalling
 ;;;###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.
-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."
+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
+evaluate to operate on an interesting file.  If the latter evaluates to
+nil, we exit; otherwise we scan the next file."
   (interactive)
   (let (new
        (messaged nil))
@@ -1164,6 +1460,7 @@ If the latter returns non-nil, we exit; otherwise we scan the next file."
              (let ((pos (point)))
                (erase-buffer)
                (set-buffer (find-file-noselect new))
+               (setq new nil)          ;No longer in a temp buffer.
                (widen)
                (goto-char pos)))
 
@@ -1178,7 +1475,7 @@ If the latter returns non-nil, we exit; otherwise we scan the next file."
 ;;;###autoload (define-key esc-map "," 'tags-loop-continue)
 
 ;;;###autoload
-(defun tags-search (regexp)
+(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].
@@ -1191,36 +1488,51 @@ See documentation of variable `tags-file-name'."
       ;; Continue last tags-search as if by M-,.
       (tags-loop-continue nil)
     (setq tags-loop-scan
-         (list 're-search-forward regexp nil t)
+         (list 're-search-forward (list 'quote regexp) nil t)
          tags-loop-operate nil)
-    (tags-loop-continue t)))
+    (tags-loop-continue (or file-list-form t))))
 
 ;;;###autoload
-(defun tags-query-replace (from to &optional delimited)
+(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")
+  (interactive (query-replace-read-args "Tags query replace (regexp)" t))
   (setq tags-loop-scan (list 'prog1
-                            (list 'if (list 're-search-forward from nil t)
+                            (list 'if (list 're-search-forward
+                                            (list 'quote 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 t))
+       tags-loop-operate (list 'perform-replace
+                               (list 'quote from) (list 'quote to)
+                               t t (list 'quote delimited)))
+  (tags-loop-continue (or file-list-form t)))
 \f
+(defun tags-complete-tags-table-file (string predicate what)
+  (save-excursion
+    ;; If we need to ask for the tag table, allow that.
+    (let ((enable-recursive-minibuffers t))
+      (visit-tags-table-buffer))
+    (if (eq what t)
+       (all-completions string (mapcar 'list (tags-table-files))
+                        predicate)
+      (try-completion string (mapcar 'list (tags-table-files))
+                     predicate))))
+
 ;;;###autoload
-(defun list-tags (file)
+(defun list-tags (file &optional next-match)
   "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)))
+This searches only the first table in the list, and no included tables.
+FILE should be as it appeared in the `etags' command, usually without a
+directory specification."
+  (interactive (list (completing-read "List tags in file: "
+                                     'tags-complete-tags-table-file
+                                     nil t nil)))
   (with-output-to-temp-buffer "*Tags List*"
     (princ "Tags in file ")
     (princ file)
@@ -1229,10 +1541,11 @@ unless it has one in the tags table."
       (let ((first-time t)
            (gotany nil))
        (while (visit-tags-table-buffer (not first-time))
+         (setq first-time nil)
          (if (funcall list-tags-function file)
              (setq gotany t)))
        (or gotany
-           (error "File %s not in current tags tables"))))))
+           (error "File %s not in current tags tables" file))))))
 
 ;;;###autoload
 (defun tags-apropos (regexp)
@@ -1254,14 +1567,12 @@ unless it has one in the tags table."
 ;;;###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';
+The list of tags tables to select from is stored in `tags-table-set-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
@@ -1288,7 +1599,7 @@ see the doc of that variable if you want to add names to the list."
          (prin1 tags-file-name (current-buffer)) ;invisible
          (insert "\n")))
     (setq set-list (delete tags-file-name
-                          (apply 'nconc (cons tags-table-list
+                          (apply 'nconc (cons (copy-sequence tags-table-list)
                                               (mapcar 'copy-sequence
                                                       tags-table-set-list)))))
     (while set-list
@@ -1303,17 +1614,31 @@ see the doc of that variable if you want to add names to the list."
        (goto-char desired-point))
     (set-window-start (selected-window) 1 t))
   (set-buffer-modified-p nil)
+  (select-tags-table-mode))
+
+(defvar select-tags-table-mode-map)
+(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)
+  (setq select-tags-table-mode-map map))
+
+(defun select-tags-table-mode ()
+  "Major mode for choosing a current tags table among those already loaded.
+
+\\{select-tags-table-mode-map}"
+  (interactive)
+  (kill-all-local-variables)
   (setq buffer-read-only t
+       major-mode 'select-tags-table-mode
        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)))
-  
+  (use-local-map select-tags-table-mode-map)
+  (setq selective-display t
+       selective-display-ellipses nil))
+
 (defun select-tags-table-select ()
   "Select the tags table named on this line."
   (interactive)
@@ -1326,21 +1651,21 @@ see the doc of that variable if you want to add names to the list."
 (defun select-tags-table-quit ()
   "Kill the buffer and delete the selected window."
   (interactive)
-  (kill-buffer (current-buffer))
-  (or (one-window-p)
-      (delete-window)))  
+  (quit-window t (selected-window)))
 \f
+;;; Note, there is another definition of this function in bindings.el.
 ;;;###autoload
 (defun complete-tag ()
   "Perform tags completion on the text around point.
-Completes to the set of names listed in the current tags table.  
+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].")))
+      (error "%s"
+            (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)))
@@ -1361,12 +1686,10 @@ for \\[find-tag] (which see)."
           (insert completion))
          (t
           (message "Making completion list...")
-          (with-output-to-temp-buffer " *Completions*"
+          (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)