update.
[chise/xemacs-chise.git.1] / lisp / etags.el
index f68527b..1a910c9 100644 (file)
@@ -1,6 +1,6 @@
 ;;; etags.el --- etags facility for Emacs
 
-;; Copyright 1985, 1986, 1988, 1990, 1997 Free Software Foundation, Inc.
+;; Copyright 1985, 1986, 1988, 1990, 1997, 2003 Free Software Foundation, Inc.
 
 ;; Author: Their Name is Legion (see list below)
 ;; Maintainer: XEmacs Development Team
@@ -172,6 +172,24 @@ This affects the `tags-search' and `tags-query-replace' commands."
   :type 'boolean
   :group 'etags)
 
+(defcustom tags-check-parent-directories-for-tag-files t
+  "*If non-nil, look for TAGS files in all parent directories."
+  :type 'boolean
+  :group 'etags)
+
+(defcustom tags-exuberant-ctags-optimization-p nil
+  "*If this variable is nil (the default), then exact tag search is able
+to find tag names in the name part of the tagtable (enclosed by  ^?..^A)
+and in the sourceline part of the tagtable ( enclosed by ^..^?). 
+This is needed by xemacs etags as not every tag has a name field.
+It is slower for large tables and less precise than the other option.
+
+If it is non-nil, then exact tag will only search tag names in the name
+part (enclosed by ^?..^A). This is faster and more precise than the other
+option. This is only usable with exuberant etags, as it has a name field
+entry for every tag."
+:type 'boolean
+:group 'etags)
 \f
 ;; Buffer tag tables.
 
@@ -185,14 +203,28 @@ the current buffer."
     ;; Current directory
     (when (file-readable-p (concat default-directory "TAGS"))
       (push (concat default-directory "TAGS") result))
-    ;; Parent directory
-    (let ((parent-tag-file (expand-file-name "../TAGS" default-directory)))
-      (when (file-readable-p parent-tag-file)
-       (push parent-tag-file result)))
+    ;; Parent directories
+    (when tags-check-parent-directories-for-tag-files
+      (let ((cur default-directory))
+       ;; Fuck!  Shouldn't there be a more obvious portable way
+       ;; to determine if we're the root?  Shouldn't we have a
+       ;; proper path manipulation API?  Do you know how many
+       ;; god-damn bugs are lurking out there because of Unix/
+       ;; Windows differences?  And how much code is littered
+       ;; with stuff such as 10 lines down from here?
+       (while (not (and (equal (file-name-as-directory cur) cur)
+                        (equal (directory-file-name cur) cur)))
+         (setq cur (expand-file-name ".." cur))
+         (let ((parent-tag-file (expand-file-name "TAGS" cur)))
+           (when (file-readable-p parent-tag-file)
+             (push parent-tag-file result))))))
     ;; tag-table-alist
-    (let ((key (or buffer-file-name
-                  (concat default-directory (buffer-name))))
-         expression)
+    (let* ((key (or buffer-file-name
+                   (concat default-directory (buffer-name))))
+          (key (if (eq system-type 'windows-nt)
+                   (replace-in-string key "\\\\" "/")
+                 key))
+          expression)
       (dolist (item tag-table-alist)
        (setq expression (car item))
        ;; If the car of the alist item is a string, apply it as a regexp
@@ -210,26 +242,45 @@ the current buffer."
          (if (stringp expression)
              (push expression result)
            (error "Expression in tag-table-alist evaluated to non-string")))))
-    (setq result
-         (mapcar
-          (lambda (name)
-            (when (file-directory-p name)
-              (setq name (concat (file-name-as-directory name) "TAGS")))
-            (and (file-readable-p name)
-                 ;; get-tag-table-buffer has side-effects
-                 (symbol-value-in-buffer 'buffer-file-name
-                                         (get-tag-table-buffer name))))
-          result))
+    (setq result (buffer-tag-table-list-load result))
     (setq result (delq nil result))
     ;; If no TAGS file has been found, ask the user explicitly.
     ;; #### tags-file-name is *evil*.
     (or result tags-file-name
        (call-interactively 'visit-tags-table))
     (when tags-file-name
-      (setq result (nconc result (list tags-file-name))))
+      (setq result (nconc result (buffer-tag-table-list-load (list tags-file-name)))))
     (or result (error "Buffer has no associated tag tables"))
     (delete-duplicates (nreverse result) :test 'equal)))
 
+(defun buffer-tag-table-list-load (list &optional used-buffers)
+  "Load all tag buffers in LIST. Include directives inside the tag
+Buffers result in a recursive call off this function. The USED-BUFFERS
+parameter is just for internal use and prevents infinite inclusion
+loops. The return value is a list of loaded buffers with the order
+from LIST preserved.  The tag files loaded with the include directive
+are inserted into the returned list before their parents."
+  (let (result)
+    (and list 
+         (mapc 
+          #'(lambda (name)
+              (when (file-directory-p name)
+                (setq name (concat (file-name-as-directory name) "TAGS")))
+              (and 
+               (file-readable-p name)
+               (save-excursion
+                 (set-buffer (get-tag-table-buffer name))
+                 (when (not (member buffer-file-name used-buffers)) 
+                   (add-to-list 'used-buffers buffer-file-name)
+                   (let ((include-files (tag-table-include-files)))
+                     (when include-files 
+                       (setq result (nconc result
+                                           (buffer-tag-table-list-load
+                                            include-files used-buffers)))))
+                   (add-to-list 'result buffer-file-name t)))))
+          list))
+    result))
+
 ;;;###autoload
 (defun visit-tags-table (file)
   "Tell tags commands to use tags table file FILE when all else fails.
@@ -328,7 +379,9 @@ If appropriate, reverting the buffer, and possibly build a completion-table."
            ;; The user wants to build the table:
            (condition-case nil
                (progn
-                 (add-to-tag-completion-table)
+          (if tags-exuberant-ctags-optimization-p
+              (add-to-tag-completion-table-exuberant-ctags)
+            (add-to-tag-completion-table))
                  (setq tag-table-completion-status t))
              ;; Allow user to C-g out correctly
              (quit
@@ -371,12 +424,13 @@ File name returned is relative to tag table file's directory."
   ;; New include syntax
   ;;   filename,include
   ;; tacked on to the end of a tag file means use filename as a
-  ;; tag file before giving up.
+  ;; tag file before giving up.  The filenames are expanded to avoid 
+  ;; problems with relative paths being used in the wrong directory.
   (let ((files nil))
     (save-excursion
       (goto-char (point-min))
-      (while (re-search-forward "\f\n\\(.*\\),include$" nil t)
-       (push (match-string 1) files)))
+      (while (re-search-forward tags-include-pattern nil t)
+       (push (expand-file-name (match-string 1)) files)))
     files))
 
 (defun tag-table-files (tag-table)
@@ -405,7 +459,7 @@ File name returned is relative to tag table file's directory."
 (defun buffer-tag-table-files ()
   "Returns a list of all files referenced by all TAGS tables that 
 this buffer uses."
-  (apply #'nconc
+  (apply #'append
         (mapcar #'tag-table-files (buffer-tag-table-list))))
 
 \f
@@ -463,25 +517,59 @@ this buffer uses."
      (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
+;; Can't use "\\s-" in these patterns because that will include newline
+;; \2 matches an explicit name.
+(defconst tags-explicit-name-pattern "\177\\(\\([^\n\001]+\\)\001\\)?")
+;; \1 matches Lisp-name, \2 matches C-name, \5 (from
+;; tags-explicit-name-pattern) matches explicit name.
 (defconst tags-DEFUN-pattern
-          "DEFUN[ \t]*(\"\\([^\"]+\\)\",[ \t]*\\(\\(\\sw\\|\\s_\\)+\\),\C-?")
+  (concat "DEFUN[ \t]*(\"\\([^\"]+\\)\",[ \t]*\\(\\(\\sw\\|\\s_\\)+\\),"
+         tags-explicit-name-pattern))
+;; \1 matches an array name.  Explicit names unused?
 (defconst tags-array-pattern ".*[ \t]+\\([^ \[]+\\)\\[")
+;; \2 matches a Lispish name, \5 (from tags-explicit-name-pattern) matches
+;; explicit name.
 (defconst tags-def-pattern
-          "\\(.*[ \t]+\\)?\\**\\(\\(\\sw\\|\\s_\\)+\\)[ ();,\t]*\C-?"
-;; "\\(.*[ \t]+\\)?\\(\\(\\sw\\|\\s_\\)+\\)[ ()]*\C-?"
-;; "\\(\\sw\\|\\s_\\)+[ ()]*\C-?"
+  (concat "\\(.*[ \t]+\\)?\\**\\(\\(\\sw\\|\\s_\\)+\\)[ ();,\t]*"
+;; "\\(.*[ \t]+\\)?\\(\\(\\sw\\|\\s_\\)+\\)[ ()]*"
+;; "\\(\\sw\\|\\s_\\)+[ ()]*"
+         tags-explicit-name-pattern)
       )
+;; \1 matches Schemish name, \4 (from tags-explicit-name-pattern) matches
+;; explicit name
+(defconst tags-schemish-pattern
+  (concat "\\s-*(\\s-*def\\sw*\\s-*(?\\s-*\\(\\(\\sw\\|\\s_\\|:\\)+\\))?\\s-*"
+         tags-explicit-name-pattern))
 (defconst tags-file-pattern "^\f\n\\(.+\\),[0-9]+\n")
+(defconst tags-include-pattern "^\f\n\\(.+\\),include\n"
+  "Holds the pattern for finding the include directive in tagfiles.")
+
+
+(defun add-to-tag-completion-table-exuberant-ctags ()
+  "Sucks the current buffer (a TAGS table) into the completion-table.
+This is a version which is optimized for exuberant etags and will not
+work with xemacs etags."
+  (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
+       name tag-symbol
+       tag-symbol-tables
+       (case-fold-search nil))
+    (while (re-search-forward tags-explicit-name-pattern nil t)
+      ;; no need to check the mode here
+      (setq name (match-string 2))
+      (intern-tag-symbol name)))
+  (message "Adding %s to tags completion table...done" buffer-file-name))
+
 
-;; #### Should make it work with the `include' directive!
 (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
+       filename file-type name name2 name3 tag-symbol
        tag-symbol-tables
        (case-fold-search nil))
     ;; Loop over the files mentioned in the TAGS file for each file,
@@ -502,6 +590,7 @@ this buffer uses."
                            ((string-match "\\.scm\\'" filename)
                             'scheme-mode)
                            (t nil)))
+      (defvar c-mode-syntax-table)
       (set-syntax-table (cond ((and (eq file-type 'c-mode)
                                    c-mode-syntax-table)
                               c-mode-syntax-table)
@@ -509,7 +598,7 @@ this buffer uses."
                               lisp-mode-syntax-table)
                              (t (standard-syntax-table))))
       ;; Clear loop variables.
-      (setq name nil name2 nil)
+      (setq name nil name2 nil name3 nil)
       (lmessage 'progress "%s..." filename)
       ;; Loop over the individual tag lines.
       (while (not (or (eobp) (eq (char-after) ?\f)))
@@ -519,8 +608,9 @@ this buffer uses."
               (or (looking-at tags-DEFUN-pattern)
                   (error "DEFUN doesn't fit pattern"))
               (setq name (match-string 1)
-                    name2 (match-string 2)))
-             ;;((looking-at "\\s ")
+                    name2 (match-string 2)
+                    name3 (match-string 5)))
+             ;;((looking-at "\\s-")
              ;; skip probably bogus entry:
              ;;)
              ((and (eq file-type 'c-mode)
@@ -532,16 +622,24 @@ this buffer uses."
                     (t
                      (setq name (match-string 1)))))
              ((and (eq file-type 'scheme-mode)
-                   (looking-at "\\s-*(\\s-*def\\sw*\\s-*(?\\s-*\\(\\(\\sw\\|\\s_\\|:\\)+\\))?\\s-*\C-?"))
+                   (looking-at tags-schemish-pattern))
               ;; Something Schemish (is this really necessary??)
-              (setq name (match-string 1)))
+              (setq name (match-string 1)
+                    name2 (match-string 4)))
              ((looking-at tags-def-pattern)
               ;; ???
-              (setq name (match-string 2))))
+              (setq name (match-string 2)
+                    name2 (match-string 5))))
        ;; add the tags we found to the completion table
        (and name (intern-tag-symbol name))
        (and name2 (intern-tag-symbol name2))
+       (and name3 (intern-tag-symbol name3))
        (forward-line 1)))
+    ;; Skip over the include entries at the bottom of the file.
+    (while (looking-at tags-include-pattern)
+      (goto-char (match-end 0))
+      (setq filename (file-name-sans-versions (match-string 1)))
+      (forward-line 1))
     (or (eobp) (error "Bad TAGS file")))
   (message "Adding %s to tags completion table...done" buffer-file-name))
 
@@ -561,12 +659,10 @@ Make it buffer-local in a mode hook.  The function is called with no
 ;; 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)))
+          (with-trapping-errors 
+            :function 'find-tag-default-hook
+            :error-form nil
+            (funcall find-tag-default-hook)))
       (symbol-near-point)))
 
 ;; This function depends on the following symbols being bound properly:
@@ -618,6 +714,7 @@ If it returns non-nil, this file needs processing by evalling
 (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
@@ -627,7 +724,7 @@ If it returns non-nil, this file needs processing by evalling
        (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
+       tag-tables tag-table-point file linebeg line startpos buf
        offset found pat syn-tab)
     (when (consp tagname)
       (setq tagname (car tagname)))
@@ -638,7 +735,11 @@ If it returns non-nil, this file needs processing by evalling
          (t
           (setq tag-table-currently-matching-exact t)))
     ;; \_ in the tagname is used to indicate a symbol boundary.
-    (setq exact-tagname (format "\C-?\\_%s\\_\C-a\\|\\_%s\\_" tagname tagname))
+    (if tags-exuberant-ctags-optimization-p
+        (setq exact-tagname (format "\C-?%s\C-a" tagname))
+      (setq exact-tagname (format "\C-?%s\C-a\\|\
+\\_%s.?\C-?[0-9]*,[0-9]*$" tagname tagname))
+      )
     (while (string-match "\\\\_" exact-tagname)
       (aset exact-tagname (1- (match-end 0)) ?b))
     (save-excursion
@@ -683,8 +784,7 @@ If it returns non-nil, this file needs processing by evalling
                               (goto-char (match-beginning 0))
                               (not (looking-at exact-tagname)))))
                       (throw 'found t))))
-             (setq tag-tables
-                   (nconc (tag-table-include-files) (cdr tag-tables)))))
+             (setq tag-tables (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)))
@@ -693,48 +793,130 @@ If it returns non-nil, this file needs processing by evalling
               (if exact "matching" "containing")
               tagname))
       (beginning-of-line)
-      (search-forward "\C-?")
-      (setq file (expand-file-name (file-of-tag)
-                                  ;; In XEmacs, this needs to be
-                                  ;; relative to:
-                                  (or (file-name-directory (car tag-tables))
-                                      "./")))
-      (setq linebeg (buffer-substring (1- (point)) (point-at-bol)))
-      (search-forward ",")
-      (setq startpos (read (current-buffer)))
+
+      ;; from here down, synched with FSF 20.7
+      ;; etags-snarf-tag and etags-goto-tag-location. --ben
+
+      (if (save-excursion
+           (forward-line -1)
+           (looking-at "\f\n"))
+         (progn
+           ;; 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 linebeg t
+                 line nil
+                 startpos 1)
+           (setq file
+                 (expand-file-name (file-of-tag)
+                                   ;; In XEmacs, this needs to be
+                                   ;; relative to:
+                                   (or (file-name-directory (car tag-tables))
+                                       "./"))))
+       (search-forward "\C-?")
+       (setq file
+             (expand-file-name (file-of-tag)
+                               ;; In XEmacs, this needs to be
+                               ;; relative to:
+                               (or (file-name-directory (car tag-tables))
+                                   "./")))
+       (setq linebeg (buffer-substring (1- (point)) (point-at-bol)))
+       ;; 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)
       (setq last-tag-data
            (nconc (list tagname (point) tag-table-currently-matching-exact)
                   tag-tables))
       (setq buf (find-file-noselect file))
+
+      ;; LINEBEG is the initial part of a line containing the tag and
+      ;; STARTPOS is the character position of LINEBEG within the file
+      ;; (starting from 1); LINE is the line number.  If LINEBEG is t,
+      ;; it means the tag refers to exactly LINE or STARTPOS
+      ;; (whichever is present, LINE having preference, no searching).
+      ;; Either LINE or STARTPOS may be nil; STARTPOS 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.
+
       (with-current-buffer buf
        (save-excursion
          (save-restriction
            (widen)
-           ;; Here we search for PAT in the range [STARTPOS - OFFSET,
-           ;; STARTPOS + OFFSET], with increasing values of OFFSET.
-           ;;
-           ;; We used to set the initial offset to 1000, but the
-           ;; actual sources show that finer-grained control is
-           ;; needed (e.g. two `hash_string's in src/symbols.c.)  So,
-           ;; I changed 100 to 100, and (* 3 offset) to (* 5 offset).
-           (setq offset 100)
-           (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 (* 5 offset)))
-           ;; Finally, try finding it anywhere in the buffer.
-           (or found
-               (re-search-forward pat nil t)
-               (error "%s not found in %s" pat file))
-           (beginning-of-line)
-           (setq startpos (point)))))
+           (if (eq linebeg t)
+               ;; Direct file tag.
+               (cond (line (goto-line line))
+                     (startpos (goto-char startpos))
+                     (t (error "etags.el BUG: bogus direct file tag")))
+             ;; Here we search for PAT in the range [STARTPOS - OFFSET,
+             ;; STARTPOS + OFFSET], with increasing values of OFFSET.
+             ;;
+             ;; We used to set the initial offset to 1000, but the
+             ;; actual sources show that finer-grained control is
+             ;; needed (e.g. two `hash_string's in src/symbols.c.)  So,
+             ;; I changed 1000 to 100, and (* 3 offset) to (* 5 offset).
+             (setq offset 100)
+             (setq pat (concat (if (eq selective-display t)
+                                   "\\(^\\|\^m\\)" "^")
+                               (regexp-quote linebeg)))
+
+             ;; 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 (* 5 offset))) ; expand search window
+             ;; Finally, try finding it anywhere in the buffer.
+             (or found
+                 (re-search-forward pat nil t)
+                 (error "Rerun etags: `%s' not found in %s"
+                        pat file))))
+         ;; 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)
+         (setq startpos (point))))
       (cons buf startpos))))
 
 ;;;###autoload
+(defun find-tag-at-point (tagname &optional other-window)
+  "*Find tag whose name contains TAGNAME.
+Identical to `find-tag' but does not prompt for tag when called interactively;
+instead, uses tag around or before point."
+  (interactive (if current-prefix-arg
+                  '(nil nil)
+                (list (find-tag-default) nil)))
+  (find-tag tagname other-window))
+
+;;;###autoload
 (defun find-tag (tagname &optional other-window)
   "*Find tag whose name contains TAGNAME.
  Selects the buffer that the tag is contained in