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

Import from CVS: tag r20-4b7
author cvs
date Mon, 13 Aug 2007 10:08:34 +0200
parents
children 262b8bb4a523
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/etags.el	Mon Aug 13 10:08:34 2007 +0200
@@ -0,0 +1,1219 @@
+;;; etags.el --- etags facility for Emacs
+
+;; Copyright 1985, 1986, 1988, 1990 Free Software Foundation, Inc.
+
+;; Keywords: tools
+
+;; This file is part of XEmacs.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY.  No author or distributor
+;; accepts responsibility to anyone for the consequences of using it
+;; or for whether it serves any particular purpose or works at all,
+;; unless he says so in writing.  Refer to the GNU Emacs General Public
+;; License for full details.
+
+;; Everyone is granted permission to copy, modify and redistribute
+;; GNU Emacs, but only under the conditions described in the
+;; GNU Emacs General Public License.   A copy of this license is
+;; supposed to have been given to you along with GNU Emacs so you
+;; can know your rights and responsibilities.  It should be in a
+;; file named COPYING.  Among other things, the copyright notice
+;; and this notice must be preserved on all copies.
+
+;;; Synched up with: Not synched with FSF. (This file is almost
+;;; completely different from FSF's etags.el.  It appears that an
+;;; early version of this file (tags.el) was rewritten by two
+;;; different people; we got one, FSF got the other.  Various
+;;; people have said that our version is better and faster.
+
+
+;; TODO:
+;; 1. place cursor in echo area while searching
+;; 2. document!
+;; 3. determine semantics of interactively setting the tags file for a buffer
+
+;; Comments with **** mean something is left to be done.
+
+;; Derived from the original lisp/tags.el.
+
+;; Ideas and code from the work of the following people:
+;; Andy Norman <ange@hplb.hpl.hp.com>, author of ange-tags.el
+;; Ramana Rao <rao@arisia.xerox.com>
+;; John Sturdy <jcgs@harlqn.co.uk>, author of tags-helper.el
+;; Henry Kautz <kautz@allegra.att.com>, author of tag-completion.el
+;; Dan LaLiberte <liberte@cs.uiuc.edu>, author of local-tags.el
+;; Tom Dietterich <tgd@turing.cs.orst.edu>, author of quest.el
+;; The author(s) of lisp/simple.el
+;; Duke Briscoe <briscoe@cs.yale.edu>
+;; Lynn Slater <lrs@indetech.com>, author of location.el
+;; Shinichirou Sugou <shin@sgtp.apple.juice.or.jp>
+;; an unidentified anonymous elisp hacker
+;; Kyle Jones <kyle_jones@wonderworks.com>
+;;   added "Exact match, then inexact" code
+;;   added support for include directive.
+
+(require 'thing)
+
+
+;; Auxiliary functions
+
+(defun tags-delete (item list)
+  "Delete the item from the list, testing with equal.  Copies the list."
+  (delete item (copy-list list)))
+
+(defun tags-remove-duplicates (list)
+  "Delete equal duplicates from the list; copies the list."
+  (let (res)
+    (dolist (el list)
+      (unless (member el res)
+	(push el res)))
+    (nreverse res)))
+
+
+;; Tag tables for a buffer
+
+(defgroup etags nil
+  "Etags facility for Emacs"
+  :prefix "tags-"
+  :group 'tools)
+
+
+;;;###autoload
+(defcustom tags-build-completion-table 'ask
+  "*If this variable is nil, then tags completion is disabled.
+If this variable is t, then things which prompt for tags will do so with 
+ completion across all known tags.
+If this variable is the symbol `ask', then you will be asked whether each
+ tags table should be added to the completion list as it is read in.
+ (With the exception that for very small tags tables, you will not be asked,
+ since they can be parsed quickly.)"
+  :type '(radio (const :tag "Disabled" nil)
+		(const :tag "Complete All" t)
+		(const :tag "Ask" ask))
+  :group 'etags)
+
+;;;###autoload
+(defcustom tags-always-exact nil
+  "*If this variable is non-nil, then tags always looks for exact matches."
+  :type 'boolean
+  :group 'etags)
+
+;;;###autoload
+(defcustom tag-table-alist nil
+  "*A list which determines which tags files are active for a buffer.
+This is not really an association list, in that all elements are
+checked.  The CAR of each element of this list is a pattern against
+which the buffer's file name is compared; if it matches, then the CDR
+of the list should be the name of the tags table to use.  If more than
+one element of this list matches the buffer's file name, then all of
+the associated tags tables will be used.  Earlier ones will be
+searched first.
+
+If the CAR of elements of this list are strings, then they are treated
+as regular-expressions against which the file is compared (like the
+auto-mode-alist).  If they are not strings, then they are evaluated.
+If they evaluate to non-nil, then the current buffer is considered to
+match.
+
+If the CDR of the elements of this list are strings, then they are
+assumed to name a TAGS file.  If they name a directory, then the string
+\"TAGS\" is appended to them to get the file name.  If they are not 
+strings, then they are evaluated, and must return an appropriate string.
+
+For example:
+  (setq tag-table-alist
+	'((\"/usr/src/public/perl/\" . \"/usr/src/public/perl/perl-3.0/\")
+	 (\"\\\\.el$\" . \"/usr/local/emacs/src/\")
+	 (\"/jbw/gnu/\" . \"/usr15/degree/stud/jbw/gnu/\")
+	 (\"\" . \"/usr/local/emacs/src/\")
+	 ))
+
+This means that anything in the /usr/src/public/perl/ directory should use
+the TAGS file /usr/src/public/perl/perl-3.0/TAGS; and file ending in .el should
+use the TAGS file /usr/local/emacs/src/TAGS; and anything in or below the
+directory /jbw/gnu/ should use the TAGS file /usr15/degree/stud/jbw/gnu/TAGS.
+A file called something like \"/usr/jbw/foo.el\" would use both the TAGS files
+/usr/local/emacs/src/TAGS and /usr15/degree/stud/jbw/gnu/TAGS (in that order)
+because it matches both patterns.
+
+If the buffer-local variable `buffer-tag-table' is set, then it names a tags
+table that is searched before all others when find-tag is executed from this
+buffer.
+
+If there is a file called \"TAGS\" in the same directory as the file in 
+question, then that tags file will always be used as well (after the
+`buffer-tag-table' but before the tables specified by this list.)
+
+If the variable tags-file-name is set, then the tags file it names will apply
+to all buffers (for backwards compatibility.)  It is searched first.
+"
+  :type '(repeat (cons (choice :value ""
+			       (regexp :tag "Buffer regexp")
+			       (function :tag "Expression"))
+		       (string :tag "Tag file or directory")))
+  :group 'etags)
+
+(defvar buffer-tag-table nil
+  "*The additional name of one TAGS table to be used for this buffer.
+You can set this with meta-x set-buffer-tag-table.  See the documentation
+for the variable `tag-table-alist' for more information.")
+(make-variable-buffer-local 'buffer-tag-table)
+
+(defcustom tags-file-name nil
+  "*The name of the tags-table used by all buffers.
+This is for backwards compatibility, and is largely supplanted by the
+variable tag-table-alist."
+  :type '(choice (const nil) string)
+  :group 'etags)
+
+
+;; XEmacs change: added tags-auto-read-changed-tag-files
+(defcustom tags-auto-read-changed-tag-files nil
+  "*If non-nil, always re-read changed TAGS file without prompting, if nil
+then prompt if changed TAGS file should be re-read."
+  :type 'boolean
+  :group 'etags)
+
+(defun buffer-tag-table-list ()
+  "Returns a list (ordered) of the tags tables which should be used for 
+the current buffer."
+  (let (result expression)
+    (when buffer-tag-table
+      (push buffer-tag-table result))
+    (when (file-readable-p (concat default-directory "TAGS"))
+      (push (concat default-directory "TAGS") result))
+    (let ((key (or buffer-file-name
+		   (concat default-directory (buffer-name))))
+	  (alist tag-table-alist))
+      (while alist
+	(setq expression (car (car alist)))
+	;; If the car of the alist item is a string, apply it as a regexp
+	;; to the buffer-file-name.  Otherwise, evaluate it.  If the
+	;; regexp matches, or the expression evaluates non-nil, then this
+	;; item in tag-table-alist applies to this buffer.
+	(when (if (stringp expression)
+		  (string-match expression key)
+		(condition-case nil
+		    (eval expression)
+		  (error nil)))
+	  ;; Now evaluate the cdr of the alist item to get the name of
+	  ;; the tag table file.
+	  (setq expression 
+		(condition-case nil
+		    (eval (cdr (car alist)))
+		  (error nil)))
+	  (if (stringp expression)
+	      (setq result (cons expression result))
+	    (error "Expression in tag-table-alist evaluated to non-string")))
+	(pop alist)))
+    (or result tags-file-name
+	;; **** I don't know if this is the right place to do this,
+	;; **** Maybe it would be better to do this after (delq nil result).
+	(call-interactively 'visit-tags-table))
+    (when tags-file-name
+      (setq result (nconc result (list tags-file-name))))
+    (setq result
+	  (mapcar
+	   (lambda (name)
+	     (if (file-directory-p name)
+		 (setq name (concat (file-name-as-directory name) "TAGS")))
+	     (if (file-readable-p name)
+		 (save-current-buffer
+		   ;; get-tag-table-buffer has side-effects
+		   (set-buffer (get-tag-table-buffer name))
+		   buffer-file-name)))
+	   result))
+    (setq result (delq nil result))
+    (or result (error "Buffer has no associated tag tables"))
+    (tags-remove-duplicates (nreverse result))))
+
+;;;###autoload
+(defun visit-tags-table (file)
+  "Tell tags commands to use tags table file FILE first.
+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
+				     (expand-file-name "TAGS" default-directory)
+				     t)))
+  (if (string-equal file "") 
+      (setq tags-file-name nil)
+    (progn
+      (setq file (expand-file-name file))
+      (if (file-directory-p file)
+	  (setq file (expand-file-name "TAGS" file)))
+      (setq tags-file-name file))))
+
+(defun set-buffer-tag-table (file)
+  "In addition to the tags tables specified by the variable `tag-table-alist',
+each buffer can have one additional table.  This command sets that.
+See the documentation for the variable `tag-table-alist' for more information."
+  (interactive
+   (list
+     (read-file-name "Visit tags table: (directory sufficient) "
+		     nil default-directory t)))
+  (or file (error "No TAGS file name supplied"))
+  (setq file (expand-file-name file))
+  (when (file-directory-p file)
+    (setq file (concat file "TAGS")))
+  (or (file-exists-p file) (error "TAGS file missing: %s" file))
+  (setq buffer-tag-table file))
+
+
+;; Manipulating the tag table buffer
+
+(defconst tag-table-completion-status nil
+  "Indicates whether a completion table has been built, or has explicitly not 
+been built.  this is nil, t, or 'disabled.")
+(make-variable-buffer-local 'tag-table-completion-status)
+
+(defcustom make-tags-files-invisible nil
+  "*If non-nil, TAGS-files will not show up in buffer-lists or be 
+selectable (or deletable.)"
+  :type 'boolean
+  :group 'etags)
+
+(defconst tag-table-files nil
+  "If the current buffer is a TAGS table, this holds a list of the files 
+referenced by this file, or nil if that hasn't been computed yet.")
+(make-variable-buffer-local 'tag-table-files)
+
+(defun get-tag-table-buffer (tag-table)
+  "Returns a buffer visiting the given TAGS table, reverting if appropriate,
+and possibly building a completion-table."
+  (or (stringp tag-table)
+      (error "Bad tags file name supplied: %s" tag-table))
+  ;; add support for removing symbolic links from name
+  (if (fboundp 'symlink-expand-file-name)
+      (setq tag-table (symlink-expand-file-name tag-table)))
+  (let (buf build-completion check-name)
+    (setq buf (get-file-buffer tag-table))
+    (or buf
+	(if (file-readable-p tag-table)
+	    (setq buf (find-file-noselect tag-table)
+		  check-name t)
+	  (error "No such tags file: %s" tag-table)))
+    (with-current-buffer buf
+      ;; make the TAGS buffer invisible
+      (when (and check-name
+		 make-tags-files-invisible
+		 (string-match "\\`[^ ]" (buffer-name)))
+	(rename-buffer (generate-new-buffer-name
+			(concat " " (buffer-name)))))
+      (or (verify-visited-file-modtime buf)
+          ;; XEmacs change: added tags-auto-read-changed-tag-files
+	  (cond ((or tags-auto-read-changed-tag-files (yes-or-no-p
+		  (format "Tags file %s has changed, read new contents? "
+                        tag-table)))
+		 (when tags-auto-read-changed-tag-files
+		   (message "Tags file %s has changed, reading new contents..."
+			    tag-table))
+		 (revert-buffer t t)
+		 (if (eq tag-table-completion-status t)
+		     (setq tag-table-completion-status nil))
+		 (setq tag-table-files nil))))
+      (or (eq (char-after 1) ?\f)
+	  (error "File %s not a valid tags file" tag-table))
+      (or (memq tag-table-completion-status '(t disabled))
+	  (setq build-completion t))
+      (and build-completion
+	   (if (cond
+		((eq tags-build-completion-table nil)
+		 nil)
+		((eq tags-build-completion-table t)
+		 t)
+		((eq tags-build-completion-table 'ask)
+		 ;; don't bother asking for small ones
+		 (or (< (buffer-size) 20000)
+		     (y-or-n-p
+		      (format "Build tag completion table for %s? "
+			      tag-table))))
+		(t (error
+		    "tags-build-completion-table is not t, nil, or ask.")))
+	       (condition-case nil
+		   (progn
+		     (add-to-tag-completion-table)
+		     (setq tag-table-completion-status t))
+		 ;; Allow user to C-g out correctly
+		 (quit
+		  (setq tag-table-completion-status nil)
+		  (setq quit-flag t)
+		  (eval t)))
+	     (setq tag-table-completion-status 'disabled))))
+    buf))
+
+(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)
+	     size (read (current-buffer)))
+       (goto-char prev)
+       (forward-line 1)
+       ;; New include syntax
+       ;;   filename,include
+       ;; tacked on to the end of a tag file means use filename
+       ;; as a tag file before giving up.
+       ;; Skip it here.
+       (if (not (eq size 'include))
+	   (forward-char size)))
+     (goto-char (1- prev))
+     (buffer-substring (point) (point-at-bol)))))
+
+(defun tag-table-include-files ()
+  "Return all file names associated with `include' directives in a tag buffer."
+  ;; New include syntax
+  ;;   filename,include
+  ;; tacked on to the end of a tag file means use filename as a
+  ;; tag file before giving up.
+  (let ((files nil))
+    (save-excursion
+     (goto-char (point-min))
+     (while (re-search-forward "\f\n\\(.*\\),include$" nil t)
+       (setq files (cons (match-string 1) files))))
+    files ))
+
+(defun tag-table-files (tag-table)
+  "Returns a list of the files referenced by the named TAGS table."
+  (with-current-buffer (get-tag-table-buffer tag-table)
+    (or tag-table-files
+	(let (files prev size)
+	  (goto-char (point-min))
+	  (while (not (eobp))
+	    (forward-line 1)
+	    (end-of-line)
+	    (skip-chars-backward "^,\n")
+	    (setq prev (point)
+		  size (read (current-buffer)))
+	    (goto-char prev)
+	    (push (expand-file-name (buffer-substring (1- (point))
+						      (point-at-bol))
+				    default-directory)
+		  files)
+	    (forward-line 1)
+	    (forward-char size))
+	  (setq tag-table-files (nreverse files))))
+    tag-table-files))
+
+;; **** should this be on previous page?
+(defun buffer-tag-table-files ()
+  "Returns a list of all files referenced by all TAGS tables that 
+this buffer uses."
+  (apply #'nconc
+	 (mapcar #'tag-table-files (buffer-tag-table-list))))
+
+
+;; Building the completion table
+
+;; Test cases for building completion table; must handle these properly:
+;; Lisp_Int, XSETINT, current_column 60,2282
+;;	   Lisp_Int, XSETINT, point>NumCharacters ? 0 : CharAt(363,9935
+;;	   Lisp_Int, XSETINT, point<=FirstCharacter ? 0 : CharAt(366,10108
+;;	 point<=FirstCharacter || CharAt(378,10630
+;;	 point>NumCharacters || CharAt(382,10825
+;; DEFUN ("x-set-foreground-color", Fx_set_foreground_color,191,4562
+;; DEFUN ("x-set-foreground-color", Fx_set_foreground_color,191,4562
+;; DEFUN ("*", Ftimes,1172,32079
+;; DEFUN ("/=", Fneq,1035,28839
+;; defun_internal 4199,101362
+;; int pure[PURESIZE / sizeof 53,1564
+;; char staticvec1[NSTATICS * sizeof 667,17608
+;;  Date: 04 May 87 23:53:11 PDT 26,1077
+;; #define anymacroname(324,4344
+;; (define-key ctl-x-map 311,11784
+;; (define-abbrev-table 'c-mode-abbrev-table 24,1016
+;; static char *skip_white(116,3443
+;; static foo 348,11643
+;; (defun texinfo-insert-@code 91,3358
+;; (defvar texinfo-kindex)29,1105
+;; (defun texinfo-format-\. 548,18376
+;; (defvar sm::menu-kludge-y 621,22726
+;; (defvar *mouse-drag-window* 103,3642
+;; (defun simula-back-level(317,11263
+;; } DPxAC,380,14024
+;; } BM_QCB;69,2990
+;; #define MTOS_DONE\t
+
+;; "^[^ ]+ +\\([^ ]+\\) "
+
+;; void *find_cactus_segment(116,2444
+;; void *find_pdb_segment(162,3688
+;; void init_dclpool(410,10739
+;; WORD insert_draw_command(342,8881
+;; void *req_pdbmem(579,15574
+
+(defvar tag-completion-table (make-vector 511 0))
+
+(defvar tag-symbol)
+(defvar tag-table-symbol)
+(defvar tag-symbol-tables)
+(defvar buffer-tag-table-list)
+
+(defmacro intern-tag-symbol (tag)
+  `(progn
+     (setq tag-symbol (intern ,tag tag-completion-table)
+	   tag-symbol-tables (and (boundp tag-symbol)
+				  (symbol-value tag-symbol)))
+     (or (memq tag-table-symbol tag-symbol-tables)
+	 (set tag-symbol (cons tag-table-symbol tag-symbol-tables)))))
+
+;; Can't use "\\s " in these patterns because that will include newline
+(defconst tags-DEFUN-pattern
+          "DEFUN[ \t]*(\"\\([^\"]+\\)\",[ \t]*\\(\\(\\sw\\|\\s_\\)+\\),\C-?")
+(defconst tags-array-pattern ".*[ \t]+\\([^ \[]+\\)\\[")
+(defconst tags-def-pattern
+          "\\(.*[ \t]+\\)?\\**\\(\\(\\sw\\|\\s_\\)+\\)[ ();,\t]*\C-?"
+;; "\\(.*[ \t]+\\)?\\(\\(\\sw\\|\\s_\\)+\\)[ ()]*\C-?"
+;; "\\(\\sw\\|\\s_\\)+[ ()]*\C-?"
+      )
+(defconst tags-file-pattern "^\f\n\\(.+\\),[0-9]+\n")
+
+(defun add-to-tag-completion-table ()
+  "Sucks the current buffer (a TAGS table) into the completion-table."
+  (message "Adding %s to tags completion table..."
+	   buffer-file-name)
+  (goto-char (point-min))
+  (let ((tag-table-symbol (intern buffer-file-name tag-completion-table))
+	;; tag-table-symbol is used by intern-tag-symbol
+	filename file-type name name2 tag-symbol
+	tag-symbol-tables
+	(case-fold-search nil))
+    ;; loop over the files mentioned in the TAGS file
+    ;; for each file, try to find its major-mode,
+    ;; then process tags appropriately
+    (while (looking-at tags-file-pattern)
+      (goto-char (match-end 0))
+      (setq filename (file-name-sans-versions
+		      (buffer-substring (match-beginning 1)
+					(match-end 1)))
+	    ;; Old code used to check auto-mode-alist for the proper
+	    ;; file-type.  This is too slow, as it breaks the
+	    ;; compiled-regexp caching, and slows the whole thing
+	    ;; down.  We'll use the shotgun approach with only two
+	    ;; regexps.
+	    file-type (cond ((string-match "\\.\\([cC]\\|cc\\|cxx\\)\\'"
+					   filename)
+			     'c-mode)
+			    ((string-match "\\.\\(el\\|cl\\|lisp\\)\\'"
+					   filename)
+			     'lisp-mode)
+			    ((string-match "\\.scm\\'" filename)
+			     'scheme-mode)
+			    (t nil)))
+      (cond ((and (eq file-type 'c-mode)
+		  c-mode-syntax-table)
+	     (set-syntax-table c-mode-syntax-table))
+	    ((eq file-type 'lisp-mode)
+	     (set-syntax-table lisp-mode-syntax-table))
+	    (t
+	     (set-syntax-table (standard-syntax-table))))
+      ;; clear loop variables
+      (setq name nil name2 nil)
+      (message "%s..." filename)
+      ;; loop over the individual tag lines
+      (while (not (or (eobp) (eq (following-char) ?\f)))
+	(cond ((and (eq file-type 'c-mode)
+		    (looking-at "DEFUN[ \t]"))
+	       (or (looking-at tags-DEFUN-pattern)
+		   (error "DEFUN doesn't fit pattern"))
+	       (setq name (buffer-substring (match-beginning 1)
+					    (match-end 1))
+		     name2 (buffer-substring (match-beginning 2)
+					     (match-end 2))))
+;;;		  ((looking-at "\\s ")
+;;;		   ;; skip probably bogus entry:
+;;;		   )
+	      ((and (eq file-type 'c-mode)
+		    (looking-at ".*\\["))
+	       (cond ((not (looking-at tags-array-pattern))
+		      (message "array definition doesn't fit pattern")
+		      (setq name nil))
+		     (t
+		      (setq name (buffer-substring (match-beginning 1)
+						   (match-end 1))))))
+	      ((and (eq file-type 'scheme-mode)
+		    (looking-at "\\s-*(\\s-*def\\sw*\\s-*(?\\s-*\\(\\(\\sw\\|\\s_\\|:\\)+\\))?\\s-*\C-?"))
+	       (setq name (buffer-substring (match-beginning 1)
+					    (match-end 1))))
+	      ((looking-at tags-def-pattern)
+	       (setq name (buffer-substring (match-beginning 2)
+					    (match-end 2)))))
+	;; add the tags we found to the completion table
+	(and name (intern-tag-symbol name))
+	(and name2 (intern-tag-symbol name2))
+	(forward-line 1)))
+    (or (eobp) (error "Bad TAGS file")))
+  (message "Adding %s to tags completion table...done"
+	   buffer-file-name))
+
+
+;; Interactive find-tag
+
+(defvar find-tag-default-hook nil
+  "Function to call to create a default tag.
+Make it buffer-local in a mode hook.  The function is called with no
+ arguments.")
+
+(defvar find-tag-hook nil
+  "Function to call after a hook is found.
+Make it buffer-local in a mode hook.  The function is called with no
+ argsuments.")
+
+;; Return a default tag to search for, based on the text at point.
+(defun find-tag-default ()
+  (or (and (not (memq find-tag-default-hook '(nil find-tag-default)))
+	   (condition-case data
+	       (funcall find-tag-default-hook)
+	     (error
+	      (warn "Error in find-tag-default-hook signalled error: %s"
+		    (error-message-string data))
+	      nil)))
+      (let ((pair (thing-symbol (point))))
+	(and pair
+	     (buffer-substring (car pair) (cdr pair))))))
+
+;; This function depends on the following symbols being bound properly:
+;; buffer-tag-table-list,
+;; tag-symbol-tables (value irrelevant, bound outside for efficiency)
+(defun tag-completion-predicate (tag-symbol)
+  (and (boundp tag-symbol)
+       (setq tag-symbol-tables (symbol-value tag-symbol))
+       (catch 'found
+	 (while tag-symbol-tables
+	   (when (memq (car tag-symbol-tables) buffer-tag-table-list)
+	     (throw 'found t))
+	   (setq tag-symbol-tables (cdr tag-symbol-tables))))))
+
+(defun buffer-tag-table-symbol-list ()
+  (mapcar (lambda (table-name)
+	    (intern table-name tag-completion-table))
+	  (buffer-tag-table-list)))
+
+(defvar find-tag-history nil "History list for find-tag-tag")
+
+(defun find-tag-tag (prompt)
+  (let* ((default (find-tag-default))
+	 (buffer-tag-table-list (buffer-tag-table-symbol-list))
+	 tag-symbol-tables tag-name)
+    (setq tag-name
+	  (completing-read
+	   (if default
+	       (format "%s(default %s) " prompt default)
+	     prompt)
+	   tag-completion-table 'tag-completion-predicate nil nil
+	   'find-tag-history))
+    (if (string-equal tag-name "")
+	;; #### - This is a really LAME way of doing it!  --Stig
+	default			;indicate exact symbol match
+      tag-name)))
+
+(defvar last-tag-data nil
+  "Information for continuing a tag search.
+Is of the form (TAG POINT MATCHING-EXACT TAG-TABLE TAG-TABLE ...).")
+
+(defvar tags-loop-operate nil
+  "Form for `tags-loop-continue' to eval to change one file.")
+
+(defvar tags-loop-scan
+  '(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.")
+
+(autoload 'get-symbol-syntax-table "symbol-syntax")
+
+(defun find-tag-internal (tagname)
+  (let ((next (null tagname))
+	(tmpnext (null tagname))
+	;; If tagname is a list: (TAGNAME), this indicates
+	;; requiring an exact symbol match.
+	(exact (or tags-always-exact (consp tagname)))
+	(normal-syntax-table (syntax-table))
+	(exact-syntax-table (get-symbol-syntax-table (syntax-table)))
+	tag-table-currently-matching-exact
+	tag-target exact-tagname
+	tag-tables tag-table-point file linebeg startpos buf
+	offset found pat syn-tab)
+    (if (consp tagname) (setq tagname (car tagname)))
+    (cond (next
+	   (setq tagname (car last-tag-data))
+	   (setq tag-table-currently-matching-exact
+		 (car (cdr (cdr last-tag-data)))))
+	  (t
+	   (setq tag-table-currently-matching-exact t)))
+    ;; \_ in the tagname is used to indicate a symbol boundary.
+    (setq exact-tagname (concat "\\_" tagname "\\_"))
+    (while (string-match "\\\\_" exact-tagname)
+      (aset exact-tagname (1- (match-end 0)) ?b))
+    (save-excursion
+      (catch 'found
+	;; loop searching for exact matches and then inexact matches.
+	(while (not (eq tag-table-currently-matching-exact 'neither))
+	  (cond (tmpnext
+		 (setq tag-tables (cdr (cdr (cdr last-tag-data))))
+		 (setq tag-table-point (car (cdr last-tag-data)))
+		 ;; start from the beginning of the table list
+		 ;; on the next iteration of the loop.
+		 (setq tmpnext nil))
+		(t
+		 (setq tag-tables (buffer-tag-table-list))
+		 (setq tag-table-point 1)))
+	  (if tag-table-currently-matching-exact
+	      (progn
+		(setq tag-target exact-tagname)
+		(setq syn-tab exact-syntax-table))
+	    (setq tag-target tagname)
+	    (setq syn-tab normal-syntax-table))
+	  (with-caps-disable-folding tag-target
+	    (while tag-tables
+	      (set-buffer (get-tag-table-buffer (car tag-tables)))
+	      (bury-buffer (current-buffer))
+	      (goto-char (or tag-table-point (point-min)))
+	      (setq tag-table-point nil)
+	      (let ((osyn (syntax-table))
+		    case-fold-search)
+		(unwind-protect
+		    (progn
+		      (set-syntax-table syn-tab)
+		      ;; **** should there be support for non-regexp
+		      ;;      tag searches?
+		      (while (re-search-forward tag-target nil t)
+			(if (and (save-match-data
+				   (looking-at "[^\n\C-?]*\C-?"))
+				 ;; if we're looking for inexact
+				 ;; matches, skip exact matches
+				 ;; since we've visited them
+				 ;; already.
+				 (or tag-table-currently-matching-exact
+				     (unwind-protect
+					 (save-excursion
+					   (set-syntax-table
+					    exact-syntax-table)
+					   (goto-char (match-beginning 0))
+					   (not (looking-at exact-tagname)))
+				       (set-syntax-table syn-tab))))
+			    (throw 'found t))))
+		  (set-syntax-table osyn)))
+	      (setq tag-tables
+		    (nconc (tag-table-include-files) (cdr tag-tables)))))
+	  (if (and (not exact) (eq tag-table-currently-matching-exact t))
+	      (setq tag-table-currently-matching-exact nil)
+	    (setq tag-table-currently-matching-exact 'neither)))
+	(error "No %sentries %s %s"
+	       (if next "more " "")
+	       (if exact "matching" "containing")
+	       tagname))
+      (search-forward "\C-?")
+      (setq file (expand-file-name (file-of-tag)
+				   ;; XEmacs change: this needs to be
+				   ;; relative to the 
+				   (or (file-name-directory (car tag-tables))
+				       "./")))
+      (setq linebeg (buffer-substring (1- (point)) (point-at-bol)))
+      (search-forward ",")
+      (setq startpos (read (current-buffer)))
+      (setq last-tag-data
+	    (nconc (list tagname (point) tag-table-currently-matching-exact)
+		   tag-tables))
+      (setq buf (find-file-noselect file))
+      (with-current-buffer buf
+        (save-excursion
+          (save-restriction
+            (widen)
+            (setq offset 1000)
+            (setq 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)
+            (setq startpos (point)))))
+      (cons buf startpos))))
+
+;;;###autoload
+(defun find-tag (tagname &optional other-window)
+  "*Find tag 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 called interactively with a numeric argument, searches for the next tag
+in the tag table that matches the tagname used in the previous find-tag.
+ If second arg OTHER-WINDOW is non-nil, uses another window to display
+the tag.
+
+This version of this function supports multiple active tags tables,
+and completion.
+
+Variables of note:
+
+  tag-table-alist		controls which tables apply to which buffers
+  tags-file-name		a default tags table
+  tags-build-completion-table   controls completion behavior
+  buffer-tag-table		another way of specifying a buffer-local table
+  make-tags-files-invisible	whether tags tables should be very hidden
+  tag-mark-stack-max		how many tags-based hops to remember"
+  (interactive (if current-prefix-arg
+		   '(nil nil)
+		 (list (find-tag-tag "Find tag: ") nil)))
+  (let* ((local-find-tag-hook find-tag-hook)
+	 (next (null tagname))
+	 (result (find-tag-internal tagname))
+	 (tag-buf (car result))
+	 (tag-point (cdr result)))
+    ;; push old position
+    (if (or (not next)
+	    (not (memq last-command
+		       '(find-tag find-tag-other-window tags-loop-continue))))
+	(push-tag-mark))
+    (if other-window
+	(pop-to-buffer tag-buf)
+      (switch-to-buffer tag-buf))
+    (widen)
+    (push-mark)
+    (goto-char tag-point)
+    (if find-tag-hook
+	(funcall find-tag-hook)
+      (if local-find-tag-hook
+	  (funcall local-find-tag-hook))))
+  (setq tags-loop-scan (list 'find-tag nil nil)
+	tags-loop-operate nil)
+  ;; Return t in case used as the tags-loop-scan.
+  t)
+
+;; This function is unchanged from lisp/tags.el:
+;;;###autoload
+(defun find-tag-other-window (tagname &optional next)
+  "*Find tag 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.
+
+This version of this function supports multiple active tags tables,
+and completion.
+
+Variables of note:
+
+  tag-table-alist		controls which tables apply to which buffers
+  tags-file-name		a default tags table
+  tags-build-completion-table   controls completion behavior
+  buffer-tag-table		another way of specifying a buffer-local table
+  make-tags-files-invisible	whether tags tables should be very hidden
+  tag-mark-stack-max		how many tags-based hops to remember"
+  (interactive (if current-prefix-arg
+		   '(nil t)
+		 (list (find-tag-tag "Find tag other window: "))))
+  (if next
+      (find-tag nil t)
+    (find-tag tagname t)))
+
+
+;; Completion on tags in the buffer
+
+(defun complete-symbol (&optional table predicate prettify)
+  (let* ((end (point))
+	 (beg (save-excursion
+		(backward-sexp 1)
+		(while (= (char-syntax (following-char)) ?\')
+		  (forward-char 1))
+		(point)))
+	 (pattern (buffer-substring beg end))
+	 (table (or table obarray))
+	 (completion (try-completion pattern table predicate)))
+    (cond ((eq completion t))
+	  ((null completion)
+	   (message "Can't find completion for \"%s\"" pattern)
+	   (ding))
+	  ((not (string-equal pattern completion))
+	   (delete-region beg end)
+	   (insert completion))
+	  (t
+	   (message "Making completion list...")
+	   (let ((list (all-completions pattern table predicate)))
+	     (if prettify
+		 (setq list (funcall prettify list)))
+	     (with-output-to-temp-buffer "*Help*"
+	       (display-completion-list list)))
+	   (message "Making completion list...%s" "done")))))
+
+(defun tag-complete-symbol ()
+  "The function used to do tags-completion (using 'tag-completion-predicate)."
+  (interactive)
+  (let* ((buffer-tag-table-list (buffer-tag-table-symbol-list))
+	 tag-symbol-tables)
+    (complete-symbol tag-completion-table 'tag-completion-predicate)))
+
+
+;; Applying a command to files mentioned in tag tables
+
+(defvar next-file-list nil
+  "List of files for next-file to process.")
+
+;;;###autoload
+(defun next-file (&optional initialize novisit)
+  "Select next file among files in current tag table(s).
+
+A first argument of t (prefix arg, if interactive) initializes to the
+beginning of the list of files in the (first) 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.
+	 (setq next-file-list (buffer-tag-table-files)))
+        (t
+         ;; Initialize the list by evalling the argument.
+         (setq next-file-list (eval initialize))))
+  (if (null next-file-list)
+      (progn
+	(and novisit
+	     (get-buffer " *next-file*")
+ 	     (kill-buffer " *next-file*"))
+	(error "All files processed.")))
+  (let* ((file (car next-file-list))
+	 (buf (get-file-buffer file))
+	 (new (not buf)))
+    (setq next-file-list (cdr next-file-list))
+
+    (if (not (and new novisit))
+        (switch-to-buffer (find-file-noselect file novisit) t)
+      ;; Like find-file, but avoids random warning messages.
+      (set-buffer (get-buffer-create " *next-file*"))
+      (kill-all-local-variables)
+      (erase-buffer)
+      (insert-file-contents file nil))
+    (widen)
+    (cond ((> (point) (point-min))
+	   (push-mark nil t)
+	   (goto-char (point-min))))
+    (and new file)))
+
+(defcustom tags-search-nuke-uninteresting-buffers t
+  "*If t (the default), tags-search and tags-query-replace will only
+keep newly-visited buffers if they contain the search target."
+  :type 'boolean
+  :group 'etags)
+
+;;;###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
+				 tags-search-nuke-uninteresting-buffers))
+            ;; If NEW is non-nil, we got a temp buffer,
+            ;; and NEW is the file name.
+            (if (or messaged
+                    (and (not first-time)
+                         (> (device-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 (and new tags-search-nuke-uninteresting-buffers)
+              (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
+(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 `tag-table-alist'."
+  (interactive "sTags search (regexp): ")
+  (if (and (equal regexp "")
+           (eq (car tags-loop-scan) 'with-caps-disable-folding)
+           (null tags-loop-operate))
+      ;; Continue last tags-search as if by M-,.
+      (tags-loop-continue nil)
+    (setq tags-loop-scan `(with-caps-disable-folding ,regexp
+                            (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 `tag-table-alist'."
+  (interactive
+   "sTags query replace (regexp): \nsTags query replace %s by: \nP")
+  (setq tags-loop-scan `(with-caps-disable-folding ,from
+                          (if (re-search-forward ,from nil t)
+                              ;; When we find a match, move back
+                              ;; to the beginning of it so perform-replace
+                              ;; will see it.
+                              (progn (goto-char (match-beginning 0)) t)))
+        tags-loop-operate (list 'perform-replace from to t t 
+                                (not (null delimited))))
+   (tags-loop-continue (or file-list-form t)))
+
+;; Miscellaneous
+
+;; **** need to alter
+;; This function is unchanged from lisp/tags.el:
+;;;###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 "fList tags (in file): ")
+  (setq string (expand-file-name string))
+  (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 "^\C-?")
+				       (point))))
+       (terpri)
+       (forward-line 1)))))
+
+;; **** need to alter
+;; This function is unchanged from lisp/tags.el:
+;;;###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 "^\C-?")
+				       (point))))
+       (terpri)
+       (forward-line 1)))))
+
+;; **** copied from tags.el
+(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)))
+
+
+;; Sample uses of find-tag-hook and find-tag-default-hook
+
+;; Example buffer-local tag finding
+
+(or (boundp 'emacs-lisp-mode-hook)
+    (setq emacs-lisp-mode-hook nil))
+(if (eq (car-safe emacs-lisp-mode-hook) 'lambda)
+    (setq emacs-lisp-mode-hook (list emacs-lisp-mode-hook)))
+(or (memq 'setup-emacs-lisp-default-tag-hook emacs-lisp-mode-hook)
+    (setq emacs-lisp-mode-hook
+	  (cons 'setup-emacs-lisp-default-tag-hook emacs-lisp-mode-hook)))
+
+(defun setup-emacs-lisp-default-tag-hook ()
+  (cond ((eq major-mode 'emacs-lisp-mode)
+	 (make-variable-buffer-local 'find-tag-default-hook)
+	 (setq find-tag-default-hook 'emacs-lisp-default-tag))))
+;; Run it once immediately
+(setup-emacs-lisp-default-tag-hook)
+(when (get-buffer "*scratch*")
+  (with-current-buffer "*scratch*"
+    (setup-emacs-lisp-default-tag-hook)))
+
+(defun emacs-lisp-default-tag ()
+  "Function to return a default tag for Emacs-Lisp mode."
+  (let ((tag (or (variable-at-point)
+		 (function-at-point))))
+    (if tag (symbol-name tag))))
+
+
+;; Display short info on tag in minibuffer
+
+(if (null (lookup-key esc-map "?"))
+    (define-key esc-map "?" 'display-tag-info))
+
+(defun display-tag-info (tagname)
+  "Prints a description of the first tag matching TAGNAME in the echo area.
+If this is an elisp function, prints something like \"(defun foo (x y z)\".
+That is, is prints the first line of the definition of the form.
+If this is a C-defined elisp function, it does something more clever."
+  (interactive (if current-prefix-arg
+		   '(nil)
+		 (list (find-tag-tag "Display tag info: "))))
+  (let* ((results (find-tag-internal tagname))
+	 (tag-buf (car results))
+	 (tag-point (cdr results))
+	 info lname min max fname args)
+    (with-current-buffer tag-buf
+      (save-excursion
+	(save-restriction
+	  (widen)
+	  (goto-char tag-point)
+	  (cond ((let ((case-fold-search nil))
+		   (looking-at "^DEFUN[ \t]"))
+		 (forward-sexp 1)
+		 (down-list 1)
+		 (setq lname (read (current-buffer))
+		       fname (buffer-substring
+			      (progn (forward-sexp 1) (point))
+			      (progn (backward-sexp 1) (point)))
+		       min (buffer-substring
+			    (progn (forward-sexp 3) (point))
+			    (progn (backward-sexp 1) (point)))
+		       max (buffer-substring
+			    (progn (forward-sexp 2) (point))
+			    (progn (backward-sexp 1) (point))))
+		 (backward-up-list 1)
+		 (setq args (buffer-substring
+			     (progn (forward-sexp 2) (point))
+			     (progn (backward-sexp 1) (point))))
+		 (setq info (format "Elisp: %s, C: %s %s, #args: %s"
+				    lname
+				    fname args
+				    (if (string-equal min max)
+					min
+				      (format "from %s to %s" min max)))))
+		(t
+		 (setq info
+		       (buffer-substring
+			(progn (beginning-of-line) (point))
+			(progn (end-of-line) (point)))))))))
+    (message "%s" info))
+  (setq tags-loop-scan '(display-tag-info nil)
+	tags-loop-operate nil)
+  ;; Always return non-nil
+  t)
+
+
+;; Keep track of old locations before finding tags
+
+(defvar tag-mark-stack1 nil)
+(defvar tag-mark-stack2 nil)
+(defcustom tag-mark-stack-max 16
+  "*The maximum number of elements kept on the mark-stack used
+by tags-search.  See also the commands push-tag-mark (\\[push-tag-mark])
+and pop-tag-mark. (\\[pop-tag-mark])."
+  :type 'integer
+  :group 'etags)
+
+(defun push-mark-on-stack (stack-symbol &optional max-size)
+  (let ((stack (symbol-value stack-symbol)))
+    (push (point-marker) stack)
+    (cond ((and max-size
+		(> (length stack) max-size))
+	   (set-marker (car (nthcdr max-size stack)) nil)
+	   (setcdr (nthcdr (1- max-size) stack) nil)))
+    (set stack-symbol stack)))
+
+(defun pop-mark-from-stack (stack-symbol1 stack-symbol2 &optional max-size)
+  (let* ((stack (or (symbol-value stack-symbol1)
+		    (error "No more tag marks on stack")))
+	 (marker (car stack))
+	 (m-buf (marker-buffer marker)))
+    (set stack-symbol1 (cdr stack))
+    (or m-buf
+	(error "Marker has no buffer"))
+    (if (null (buffer-name m-buf))
+	(error "Buffer has been killed"))
+    (push-mark-on-stack stack-symbol2 max-size)
+    (switch-to-buffer m-buf)
+    (widen)
+    (goto-char (marker-position marker))))
+
+(defun push-tag-mark ()
+  (push-mark-on-stack 'tag-mark-stack1 tag-mark-stack-max))
+
+(if (memq (lookup-key esc-map "*") '(nil undefined))
+    (define-key esc-map "*" 'pop-tag-mark))
+
+(defun pop-tag-mark (arg)
+  "find-tag maintains a mark-stack seperate from the \\[set-mark-command] mark-stack.
+This function pops (and moves to) the tag at the top of this stack."
+  (interactive "P")
+  (if (not arg)
+      (pop-mark-from-stack
+       'tag-mark-stack1 'tag-mark-stack2 tag-mark-stack-max)
+    (pop-mark-from-stack
+     'tag-mark-stack2 'tag-mark-stack1 tag-mark-stack-max)))
+
+
+(provide 'etags)
+(provide 'tags)