Patch to abstract `tag' finding, allowing major modes to use specialized
techniques for navigating other than TAGS files.  Thus M-. can try to find
the tag in a database provided by the lanaguage system, such as is done for
Erlang by Distel, for instance.  (This can trace the source files for the
modules used by the current source file, for example.)

2005-10-08  Dave Love  <fx@gnu.org>

	* progmodes/etags.el (find-tag-function, find-tag-use-imenu)
	(find-tag-normally): New.
	(find-tag-noselect): Rewritten to use find-tag-function.
	(etags-tags-completion-table): Allow non-ASCII.

Index: etags.el
===================================================================
RCS file: /cvsroot/emacs/emacs/lisp/progmodes/etags.el,v
retrieving revision 1.188
diff -u -p -u -r1.188 etags.el
--- etags.el	24 Sep 2005 13:43:58 -0000	1.188
+++ etags.el	8 Oct 2005 14:17:47 -0000
@@ -812,6 +812,22 @@ Assumes the tags table is the current bu
   (defvar etags-case-fold-search)
   (defvar etags-syntax-table))
 
+(defvar find-tag-function nil
+  "If non-nil, function to call to find a tag.
+This can be redefined by major modes as appropriate to use special
+information from the relevant language system.
+
+It takes args (PATTERN REGEXP-P FIRST CONT), where PATTERN is the
+tag to search for, REGEXP-P non-nil means to do a regexp search
+for it, and FIRST non-nil means to fid the first occurrence
+rather than the next.  CONT non-nil means to continue, i.e. look
+for the next occurrence when the tag was found previously; when
+using tags tables, for instance, this means not visiting a new
+table.
+
+It should find the file for the tag and return the buffer in
+which it was found or nil.")
+
 ;;;###autoload
 (defun find-tag-noselect (tagname &optional next-p regexp-p)
   "Find tag (in current tags table) whose name contains TAGNAME.
@@ -831,7 +847,12 @@ A marker representing the point when thi
 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'."
+See documentation of variable `tags-file-name'.
+
+The tag may be found other than by using a normal tags table.  If
+the current major mode sets `find-tag-function' specially, it
+will first be called to find the tag.  If that fails,
+`find-tag-in-order' is used."
   (interactive (find-tag-interactive "Find tag: "))
 
   (setq find-tag-history (cons tagname find-tag-history))
@@ -855,35 +876,67 @@ See documentation of variable `tags-file
 	      (run-hooks 'local-find-tag-hook))))
       ;; Record whence we came.
       (ring-insert find-tag-marker-ring (point-marker))
-      (if (and next-p last-tag)
-	  ;; Find the same table we last used.
-	  (visit-tags-table-buffer 'same)
-	;; Pick a table to use.
-	(visit-tags-table-buffer)
+      (unless (and next-p last-tag)
 	;; Record TAGNAME for a future call with NEXT-P non-nil.
 	(setq last-tag tagname))
-      ;; 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 (and next-p last-tag) 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")
-	    (or (not next-p) (not last-tag))))
-	  (set-marker marker (point))
-	  (run-hooks 'local-find-tag-hook)
-	  (ring-insert tags-location-ring marker)
-	  (current-buffer))))))
+      ;; We need a dummy buffer here in case of a tag location in the
+      ;; buffer from which we're invoking the command.  This is
+      ;; inherited from the logic before introducing
+      ;; `find-tag-function', and that should probably be re-done.
+      (with-temp-buffer
+	;; Record the location so we can pop back to it later.
+	(let ((marker (make-marker)))
+	  (save-excursion
+	    (set-buffer
+	     ;; This does the real work.
+	     (or (if find-tag-function
+		     (funcall find-tag-function
+			      (if (and next-p last-tag) last-tag tagname)
+			      regexp-p (or (not next-p) (not last-tag))
+			      (and next-p last-tag)))
+		 (find-tag-normally (if (and next-p last-tag) last-tag tagname)
+				    regexp-p (or (not next-p) (not last-tag))
+				    (and next-p last-tag))))
+	    (set-marker marker (point))
+	    (run-hooks 'local-find-tag-hook)
+	    (ring-insert tags-location-ring marker)
+	    (current-buffer)))))))
+
+(defcustom find-tag-use-imenu nil
+  "*Non-nil means try to find a tag using Imenu before trying tags tables.
+This is used by `find-tag-normally', which may not get called,
+depending on the value of `find-tag-function'."
+  :group 'etags
+  :version "22.1"
+  :type 'boolean)
+
+(defun find-tag-normally (pattern regexp-p first cont)
+  "Normal value for `find-tag-function'.
+Uses `find-tag-in-order' to search TAGS files."
+  (or (if find-tag-use-imenu
+	  (condition-case nil
+	      (progn
+		(imenu-all pattern (not first))
+		(current-buffer))
+	    (error nil)))
+      (progn
+	(if cont
+	    ;; Find the same table we last used.
+	    (visit-tags-table-buffer 'same)
+	  ;; Pick a table to use.
+	  (visit-tags-table-buffer))
+	(find-tag-in-order pattern
+			   (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")
+			   first))))
 
 ;;;###autoload
 (defun find-tag (tagname &optional next-p regexp-p)
@@ -1245,8 +1298,8 @@ where they were found."
       ;;   \6 is the line to start searching at;
       ;;   \7 is the char to start searching at.
       (while (re-search-forward
-	      "^\\(\\([^\177]+[^-a-zA-Z0-9_+*$:\177]+\\)?\
-\\([-a-zA-Z0-9_+*$?:]+\\)[^-a-zA-Z0-9_+*$?:\177]*\\)\177\
+	      "^\\(\\([^\177]+[^-[:alnum:]_+*$:\177]+\\)?\
+\\([-[:alnum:]_+*$?:]+\\)[^-[:alnum:]_+*$?:\177]*\\)\177\
 \\(\\([^\n\001]+\\)\001\\)?\\([0-9]+\\)?,\\([0-9]+\\)?\n"
 	      nil t)
 	(intern	(prog1 (if (match-beginning 5)
@@ -1987,7 +2040,7 @@ see the doc of that variable if you want
   (interactive)
   (quit-window t (selected-window)))
 
-;; Note, there is another definition of this function in bindings.el.
+;; Fixme: Maybe there should be a hook for modes to override this.
 ;;;###autoload
 (defun complete-tag ()
   "Perform tags completion on the text around point.
