Refill some copyright headers.
[bpt/emacs.git] / lisp / progmodes / etags.el
index 6fe9fec..20a6c22 100644 (file)
@@ -1,8 +1,8 @@
 ;;; etags.el --- etags facility for Emacs
 
-;; Copyright (C) 1985, 1986, 1988, 1989, 1992, 1993, 1994, 1995, 1996, 1998,
-;;               2000, 2001, 2002, 2003, 2004, 2005, 2006
-;;     Free Software Foundation, Inc.
+;; Copyright (C) 1985, 1986, 1988, 1989, 1992, 1993, 1994, 1995, 1996,
+;;   1998, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
+;;   2010, 2011  Free Software Foundation, Inc.
 
 ;; Author: Roland McGrath <roland@gnu.org>
 ;; Maintainer: FSF
 
 ;; This file is part of GNU Emacs.
 
-;; GNU Emacs is free software; you can redistribute it and/or modify
+;; GNU Emacs is free software: you can redistribute it and/or modify
 ;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 2, or (at your option)
-;; any later version.
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
 
 ;; GNU Emacs is distributed in the hope that it will be useful,
 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
 ;; 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, Inc., 51 Franklin Street, Fifth Floor,
-;; Boston, MA 02110-1301, USA.
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 
 ;;; Commentary:
 
 ;;; Code:
 
+(eval-when-compile
+  (require 'cl))
 (require 'ring)
 (require 'button)
 
@@ -39,7 +39,8 @@ To switch to a new tags table, setting this variable is sufficient.
 If you set this variable, do not also set `tags-table-list'.
 Use the `etags' program to make a tags table file.")
 ;; Make M-x set-variable tags-file-name like M-x visit-tags-table.
-;;;###autoload (put 'tags-file-name 'variable-interactive "fVisit tags table: ")
+;;;###autoload (put 'tags-file-name 'variable-interactive (purecopy "fVisit tags table: "))
+;;;###autoload (put 'tags-file-name 'safe-local-variable 'stringp)
 
 (defgroup etags nil "Tags tables."
   :group 'tools)
@@ -67,12 +68,14 @@ Use the `etags' program to make a tags table file."
   :type '(repeat file))
 
 ;;;###autoload
-(defcustom tags-compression-info-list '("" ".Z" ".bz2" ".gz" ".tgz")
+(defcustom tags-compression-info-list
+  (purecopy '("" ".Z" ".bz2" ".gz" ".xz" ".tgz"))
   "*List of extensions tried by etags when jka-compr is used.
 An empty string means search the non-compressed file.
 These extensions will be tried only if jka-compr was activated
 \(i.e. via customize of `auto-compression-mode' or by calling the function
 `auto-compression-mode')."
+  :version "24.1"                      ; added xz
   :type  '(repeat string)
   :group 'etags)
 
@@ -256,13 +259,13 @@ 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 current buffer contains valid tags file.")
+  "Function to return t if 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 ()
+  "Initialize the tags table in the current buffer.
+Return non-nil if it is a valid tags table, and
+in that case, also make the tags table state variables 
+buffer-local and set them to 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)
@@ -277,8 +280,9 @@ One argument, the tag info returned by `snarf-tag-function'.")
 (defun tags-table-mode ()
   "Major mode for tags table file buffers."
   (interactive)
-  (setq major-mode 'tags-table-mode)
-  (setq mode-name "Tags Table")
+  (setq major-mode 'tags-table-mode     ;FIXME: Use define-derived-mode.
+        mode-name "Tags Table"
+        buffer-undo-list t)
   (initialize-new-tags-table))
 
 ;;;###autoload
@@ -300,8 +304,7 @@ file the tag was in."
                     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
+  ;; global value gets set.
   ;; 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.
@@ -337,8 +340,7 @@ file the tag was in."
                     ;; it is initialized as a tag table buffer.
                     (save-excursion
                       (tags-verify-table (buffer-file-name table-buffer))))
-               (save-excursion
-                 (set-buffer table-buffer)
+               (with-current-buffer table-buffer
                  (if (tags-included-tables)
                      ;; Insert the included tables into the list we
                      ;; are processing.
@@ -356,13 +358,13 @@ file the tag was in."
          (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 ()
+  "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 in and might
+contain included tables to search.  This function reads in the first
+such table and puts its included tables into the list."
   (let ((list tags-table-computed-list))
     (while (not (eq (nth 1 list) t))
       (setq list (cdr list)))
@@ -377,8 +379,7 @@ file the tag was in."
              (setq computed (cons (car tables) computed)
                    table-buffer (get-file-buffer (car tables)))
              (if table-buffer
-                 (save-excursion
-                   (set-buffer table-buffer)
+                 (with-current-buffer table-buffer
                    (if (tags-included-tables)
                        ;; Insert the included tables into the list we
                        ;; are processing.
@@ -397,8 +398,8 @@ file the tag was in."
        ;; 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)
+  "Expand tags table name FILE into a complete file name."
   (setq file (expand-file-name file))
   (if (file-directory-p file)
       (expand-file-name "TAGS" file)
@@ -407,6 +408,10 @@ file the tag was in."
 ;; 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)
+  "Like (member FILE LIST) after applying `tags-expand-table-name'.
+More precisely, apply `tags-expand-table-name' to FILE
+and each element of LIST, returning the link whose car is the first match.
+If an element of LIST is t, ignore it."
   (setq file (tags-expand-table-name file))
   (while (and list
              (or (eq (car list) t)
@@ -417,13 +422,13 @@ file the tag was in."
 (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."
+Returns non-nil if 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)
+      (progn
        (set-buffer (get-file-buffer file))
-       (setq win (or verify-tags-table-function (tags-table-mode)))
+        (or verify-tags-table-function (tags-table-mode))
        (if (or (verify-visited-file-modtime (current-buffer))
                ;; Decide whether to revert the file.
                ;; revert-without-query can say to revert
@@ -443,18 +448,19 @@ Returns non-nil iff it is a valid table."
                 (funcall verify-tags-table-function))
          (revert-buffer t t)
          (tags-table-mode)))
-    (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))))
-          (tags-table-mode)))))
+    (when (file-exists-p file)
+      (let* ((buf (find-file-noselect file))
+             (newfile (buffer-file-name buf)))
+        (unless (string= file newfile)
+          ;; 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 newfile)))
+          (if (eq file tags-file-name) (setq tags-file-name newfile)))
+        ;; Only change buffer now that we're done using potentially
+        ;; buffer-local variables.
+        (set-buffer buf)
+        (tags-table-mode)))))
 
 ;; 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
@@ -463,6 +469,12 @@ Returns non-nil iff it is a valid table."
 ;; 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)
+  "Search current tags tables for tags for THIS-FILE.
+Subroutine of `visit-tags-table-buffer'.
+Looks for a tags table that has such tags or that includes a table
+that has them.  Returns the name of the first such table.
+Non-nil CORE-ONLY means check only tags tables that are already in
+buffers.  If CORE-ONLY is nil, it is ignored."
   (let ((tables tags-table-computed-list)
        (found nil))
     ;; Loop over the list, looking for a table containing tags for THIS-FILE.
@@ -508,9 +520,10 @@ Returns non-nil iff it is a valid table."
          ;; 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 ()
+  "Move `tags-table-list-pointer' along and set `tags-file-name'.
+Subroutine of `visit-tags-table-buffer'.\
+Returns nil when out of tables."
   ;; 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))
@@ -671,7 +684,7 @@ Returns t if it visits a tags table, or nil if there are no more in the list."
                                          tags-table-set-list)))
                          ;; Clear out buffers holding old tables.
                          (dolist (table tags-table-list)
-                           ;; The list can contain items `t'.
+                           ;; The list can contain items t.
                            (if (stringp table)
                                (let ((buffer (find-buffer-visiting table)))
                              (if buffer
@@ -741,48 +754,69 @@ Assumes the tags table is the current buffer."
   (or tags-included-tables
       (setq tags-included-tables (funcall tags-included-tables-function))))
 \f
-;; Build tags-completion-table on demand.  The single current tags table
-;; and its included tags tables (and their included tables, etc.) have
-;; their tags included in the completion table.
 (defun tags-completion-table ()
+  "Build `tags-completion-table' on demand.
+The tags included in the completion table are those in the current
+tags table and its (recursively) included tags tables."
   (or tags-completion-table
+      ;; No cached value for this buffer.
       (condition-case ()
-         (prog2
-          (message "Making tags completion table for %s..." buffer-file-name)
-          (let ((included (tags-included-tables))
-                (table (funcall tags-completion-table-function)))
-            (save-excursion
-              ;; Iterate over the list of included tables, and combine each
-              ;; included table's completion obarray to the parent obarray.
-              (while included
-                ;; Visit the buffer.
-                (let ((tags-file-name (car included)))
-                  (visit-tags-table-buffer 'same))
-                ;; Recurse in that buffer to compute its completion table.
-                (if (tags-completion-table)
-                    ;; Combine the tables.
-                    (mapatoms (lambda (sym) (intern (symbol-name sym) table))
-                              tags-completion-table))
-                (setq included (cdr included))))
-            (setq tags-completion-table table))
-          (message "Making tags completion table for %s...done"
-                   buffer-file-name))
+         (let (current-table combined-table)
+           (message "Making tags completion table for %s..." buffer-file-name)
+           (save-excursion
+             ;; Iterate over the current list of tags tables.
+             (while (visit-tags-table-buffer (and combined-table t))
+               ;; Find possible completions in this table.
+               (setq current-table (funcall tags-completion-table-function))
+               ;; Merge this buffer's completions into the combined table.
+               (if combined-table
+                   (mapatoms
+                    (lambda (sym) (intern (symbol-name sym) combined-table))
+                    current-table)
+                 (setq combined-table current-table))))
+           (message "Making tags completion table for %s...done"
+                    buffer-file-name)
+           ;; Cache the result in a buffer-local variable.
+           (setq tags-completion-table combined-table))
        (quit (message "Tags completion table construction aborted.")
              (setq tags-completion-table nil)))))
 
-;; Completion function for tags.  Does normal try-completion,
-;; but builds tags-completion-table on demand.
-(defun tags-complete-tag (string predicate what)
-  (save-excursion
-    ;; 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))))
+(defun tags-lazy-completion-table ()
+  (lexical-let ((buf (current-buffer)))
+    (lambda (string pred action)
+      (with-current-buffer buf
+        (save-excursion
+          ;; If we need to ask for the tag table, allow that.
+          (let ((enable-recursive-minibuffers t))
+            (visit-tags-table-buffer))
+          (complete-with-action action (tags-completion-table) string pred))))))
+
+;;;###autoload (defun tags-completion-at-point-function ()
+;;;###autoload   (if (or tags-table-list tags-file-name)
+;;;###autoload       (progn
+;;;###autoload         (load "etags")
+;;;###autoload         (tags-completion-at-point-function))))
+
+(defun tags-completion-at-point-function ()
+  "Using tags, return a completion table for the text around point.
+If no tags table is loaded, do nothing and return nil."
+  (when (or tags-table-list tags-file-name)
+    (let ((completion-ignore-case (if (memq tags-case-fold-search '(t nil))
+                                     tags-case-fold-search
+                                   case-fold-search))
+         (pattern (funcall (or find-tag-default-function
+                               (get major-mode 'find-tag-default-function)
+                               'find-tag-default)))
+         beg)
+      (when pattern
+       (save-excursion
+         (search-backward pattern) ;FIXME: will fail if we're inside pattern.
+         (setq beg (point))
+         (forward-char (length pattern))
+         (list beg (point) (tags-lazy-completion-table)))))))
 \f
-;; Read a tag name from the minibuffer with defaulting and completion.
 (defun find-tag-tag (string)
+  "Read a tag name, with defaulting and completion."
   (let* ((completion-ignore-case (if (memq tags-case-fold-search '(t nil))
                                     tags-case-fold-search
                                   case-fold-search))
@@ -794,7 +828,7 @@ Assumes the tags table is the current buffer."
                                            (substring string 0 (string-match "[ :]+\\'" string))
                                            default)
                                  string)
-                               'tags-complete-tag
+                               (tags-lazy-completion-table)
                                nil nil nil nil default)))
     (if (equal spec "")
        (or default (error "There is no default tag"))
@@ -803,8 +837,10 @@ Assumes the tags table is the current buffer."
 (defvar last-tag nil
   "Last tag found by \\[find-tag].")
 
-;; Get interactive args for find-tag{-noselect,-other-window,-regexp}.
 (defun find-tag-interactive (prompt &optional no-default)
+  "Get interactive arguments for tag functions.
+The functions using this are `find-tag-noselect',
+`find-tag-other-window', and `find-tag-regexp'."
   (if (and current-prefix-arg last-tag)
       (list nil (if (< (prefix-numeric-value current-prefix-arg) 0)
                    '-
@@ -813,12 +849,11 @@ Assumes the tags table is the current buffer."
              (read-string prompt)
            (find-tag-tag prompt)))))
 
-(defvar find-tag-history nil)
+(defvar find-tag-history nil) ; Doc string?
 
 ;; Dynamic bondage:
-(eval-when-compile
-  (defvar etags-case-fold-search)
-  (defvar etags-syntax-table))
+(defvar etags-case-fold-search)
+(defvar etags-syntax-table)
 
 ;;;###autoload
 (defun find-tag-noselect (tagname &optional next-p regexp-p)
@@ -872,22 +907,21 @@ See documentation of variable `tags-file-name'."
        (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))))
+       (with-current-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)
@@ -1029,26 +1063,8 @@ where they were found."
     (goto-char (marker-position marker))
     (set-marker marker nil nil)))
 \f
-;; Internal tag finding function.
-
-;; PATTERN is a string to pass to second arg SEARCH-FORWARD-FUNC, and to
-;; any member of the function list ORDER (third arg).  If ORDER is nil,
-;; use saved state to continue a previous search.
-
-;; Fourth arg NEXT-LINE-AFTER-FAILURE-P is non-nil if after a failed match,
-;; point should be moved to the next line.
-
-;; Fifth arg MATCHING is a string, an English '-ing' word, to be used in
-;; an error message.
-
-;; Algorithm is as follows.  For each qualifier-func in ORDER, go to
-;; beginning of tags file, and perform inner loop: for each naive match for
-;; PATTERN found using SEARCH-FORWARD-FUNC, qualify the naive match using
-;; qualifier-func.  If it qualifies, go to the specified line in the
-;; specified source file and return.  Qualified matches are remembered to
-;; avoid repetition.  State is saved so that the loop can be continued.
-
-(defvar tag-lines-already-matched nil) ;matches remembered here between calls
+(defvar tag-lines-already-matched nil
+  "Matches remembered between calls.") ; Doc string: calls to what?
 
 (defun find-tag-in-order (pattern
                          search-forward-func
@@ -1056,6 +1072,23 @@ where they were found."
                          next-line-after-failure-p
                          matching
                          first-search)
+  "Internal tag-finding function.
+PATTERN is a string to pass to arg SEARCH-FORWARD-FUNC, and to any
+member of the function list ORDER.  If ORDER is nil, use saved state
+to continue a previous search.
+
+Arg NEXT-LINE-AFTER-FAILURE-P is non-nil if after a failed match,
+point should be moved to the next line.
+
+Arg MATCHING is a string, an English `-ing' word, to be used in an
+error message."
+;; Algorithm is as follows:
+;; For each qualifier-func in ORDER, go to beginning of tags file, and
+;; perform inner loop: for each naive match for PATTERN found using
+;; SEARCH-FORWARD-FUNC, qualify the naive match using qualifier-func.  If
+;; it qualifies, go to the specified line in the specified source file
+;; and return.  Qualified matches are remembered to avoid repetition.
+;; State is saved so that the loop can be continued.
   (let (file                           ;name of file containing tag
        tag-info                        ;where to find the tag in FILE
        (first-table t)
@@ -1100,9 +1133,7 @@ where they were found."
              ;; Naive match found.  Qualify the match.
              (and (funcall (car order) pattern)
                   ;; Make sure it is not a previous qualified match.
-                  (not (member (set-marker match-marker (save-excursion
-                                                          (beginning-of-line)
-                                                          (point)))
+                  (not (member (set-marker match-marker (point-at-bol))
                                tag-lines-already-matched))
                   (throw 'qualified-match-found nil))
              (if next-line-after-failure-p
@@ -1132,7 +1163,7 @@ where they were found."
                  (if (memq (car order) '(tag-exact-file-name-match-p
                                          tag-file-name-match-p
                                          tag-partial-file-name-match-p))
-                      (save-excursion (next-line 1)
+                      (save-excursion (forward-line 1)
                                       (file-of-tag))
                     (file-of-tag)))
            tag-info (funcall snarf-tag-function))
@@ -1148,10 +1179,9 @@ where they were found."
       (current-buffer))))
 
 (defun tag-find-file-of-tag-noselect (file)
-  ;; Find the right line in the specified file.
-  ;; If we are interested in compressed-files,
-  ;; we search files with extensions.
-  ;; otherwise only the real file.
+  "Find the right line in the specified FILE."
+  ;; If interested in compressed-files, search files with extensions.
+  ;; Otherwise, search only the real file.
   (let* ((buffer-search-extensions (if (featurep 'jka-compr)
                                       tags-compression-info-list
                                     '("")))
@@ -1181,7 +1211,7 @@ where they were found."
          (error "File %s not found" file))
       (set-buffer the-buffer))))
 
-(defun tag-find-file-of-tag (file)
+(defun tag-find-file-of-tag (file) ; Doc string?
   (let ((buf (tag-find-file-of-tag-noselect file)))
     (condition-case nil
        (switch-to-buffer buf)
@@ -1189,9 +1219,10 @@ where they were found."
 \f
 ;; `etags' TAGS file format support.
 
-;; If the current buffer is a valid etags TAGS file, give it local values of
-;; the tags table format variables, and return non-nil.
 (defun etags-recognize-tags-table ()
+  "If `etags-verify-tags-table', make buffer-local format variables.
+If current buffer is a valid etags TAGS file, then give it
+buffer-local values of tags table format variables."
   (and (etags-verify-tags-table)
        ;; It is annoying to flash messages on the screen briefly,
        ;; and this message is not useful.  -- rms
@@ -1221,12 +1252,12 @@ where they were found."
               (verify-tags-table-function . etags-verify-tags-table)
               ))))
 
-;; Return non-nil iff the current buffer is a valid etags TAGS file.
 (defun etags-verify-tags-table ()
+  "Return non-nil if the current buffer is a valid etags TAGS file."
   ;; Use eq instead of = in case char-after returns nil.
   (eq (char-after (point-min)) ?\f))
 
-(defun etags-file-of-tag (&optional relative)
+(defun etags-file-of-tag (&optional relative) ; Doc string?
   (save-excursion
     (re-search-backward "\f\n\\([^\n]+\\),[0-9]*\n")
     (let ((str (buffer-substring (match-beginning 1) (match-end 1))))
@@ -1236,7 +1267,7 @@ where they were found."
                          (file-truename default-directory))))))
 
 
-(defun etags-tags-completion-table ()
+(defun etags-tags-completion-table () ; Doc string?
   (let ((table (make-vector 511 0))
        (progress-reporter
         (make-progress-reporter
@@ -1266,7 +1297,7 @@ where they were found."
                table)))
     table))
 
-(defun etags-snarf-tag (&optional use-explicit)
+(defun etags-snarf-tag (&optional use-explicit) ; Doc string?
   (let (tag-text line startpos explicit-start)
     (if (save-excursion
          (forward-line -1)
@@ -1280,13 +1311,11 @@ where they were found."
 
       ;; 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))))
+      (setq tag-text (buffer-substring (1- (point)) (point-at-bol)))
       ;; If use-explicit is non nil and explicit tag is present, use it as part of
       ;; return value. Else just skip it.
       (setq explicit-start (point))
-      (when (and (search-forward "\001" (save-excursion (forward-line 1) (point)) t)
+      (when (and (search-forward "\001" (point-at-bol 2) t)
                 use-explicit)
        (setq tag-text (buffer-substring explicit-start (1- (point)))))
 
@@ -1306,21 +1335,27 @@ where they were found."
     (forward-line 1)
     (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)
+  "Go to location of tag specified by TAG-INFO.
+TAG-INFO is a cons (TEXT LINE . POSITION).
+TEXT is the initial part of a line containing the tag.
+LINE is the line number.
+POSITION is the (one-based) char position of TEXT within the file.
+
+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 can be nil.  POSITION is used if present.
+
+If the tag isn't exactly at the given position, then look near that
+position using a search window that expands progressively until it
+hits the start of file."
   (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))
+       (cond (line (progn (goto-char (point-min))
+                          (forward-line (1- line))))
              (startpos (goto-char startpos))
              (t (error "etags.el BUG: bogus direct file tag")))
       ;; This constant is 1/2 the initial search window.
@@ -1338,7 +1373,8 @@ where they were found."
       ;; If no char pos was given, try the given line number.
       (or startpos
          (if line
-             (setq startpos (progn (goto-line line)
+             (setq startpos (progn (goto-char (point-min))
+                                   (forward-line (1- line))
                                    (point)))))
       (or startpos
          (setq startpos (point-min)))
@@ -1363,7 +1399,7 @@ where they were found."
         (forward-char 1))
     (beginning-of-line)))
 
-(defun etags-list-tags (file)
+(defun etags-list-tags (file) ; Doc string?
   (goto-char (point-min))
   (when (re-search-forward (concat "\f\n" "\\(" file "\\)" ",") nil t)
     (let ((path (save-excursion (forward-line 1) (file-of-tag)))
@@ -1389,7 +1425,8 @@ where they were found."
                                      (tag-find-file-of-tag (button-get button 'file-path))
                                      (widen)
                                      (funcall goto-func tag-info)))
-                         'face 'tags-tag-face
+                         'follow-link t
+                         'face tags-tag-face
                          'type 'button))
       (terpri)
       (forward-line 1))
@@ -1423,6 +1460,7 @@ where they were found."
                                                                    (button-get button 'item)))
                                          'item sn
                                          'face tags-tag-face
+                                         'follow-link t
                                          'type 'button)
                              (terpri))))))
         (when (symbolp symbs)
@@ -1436,7 +1474,7 @@ where they were found."
            (funcall ins-symb (car sy))))
         (sort-lines nil beg (point))))))
 
-(defun etags-tags-apropos (string)
+(defun etags-tags-apropos (string) ; Doc string?
   (when tags-apropos-verbose
     (princ "Tags in file `")
     (tags-with-face 'highlight (princ buffer-file-name))
@@ -1456,10 +1494,10 @@ where they were found."
             (tag-info (save-excursion (funcall snarf-tag-function)))
             (tag (if (eq t (car tag-info)) nil (car tag-info)))
             (file-path (save-excursion (if tag (file-of-tag)
-                                         (save-excursion (next-line 1)
+                                         (save-excursion (forward-line 1)
                                                          (file-of-tag)))))
             (file-label (if tag (file-of-tag t)
-                          (save-excursion (next-line 1)
+                          (save-excursion (forward-line 1)
                                           (file-of-tag t))))
             (pt (with-current-buffer standard-output (point))))
        (if tag
@@ -1478,7 +1516,8 @@ where they were found."
                                              (tag-find-file-of-tag (button-get button 'file-path))
                                              (widen)
                                              (funcall goto-func tag-info)))
-                                 'face 'tags-tag-face
+                                 'follow-link t
+                                 'face tags-tag-face
                                  'type 'button)))
          (princ (format "- %s" file-label))
          (with-current-buffer standard-output
@@ -1489,15 +1528,15 @@ where they were found."
                                        ;; Get the local value in the tags table
                                        ;; buffer before switching buffers.
                                        (goto-char (point-min)))
-                             'face 'tags-tag-face
-                             'type 'button))
-         ))
+                             'follow-link t
+                             'face tags-tag-face
+                             'type 'button))))
       (terpri)
       (forward-line 1))
     (message nil))
   (when tags-apropos-verbose (princ "\n")))
 
-(defun etags-tags-table-files ()
+(defun etags-tags-table-files () ; Doc string?
   (let ((files nil)
        beg)
     (goto-char (point-min))
@@ -1509,7 +1548,7 @@ where they were found."
          (setq files (cons (buffer-substring beg (1- (point))) files))))
     (nreverse files)))
 
-(defun etags-tags-included-tables ()
+(defun etags-tags-included-tables () ; Doc string?
   (let ((files nil)
        beg)
     (goto-char (point-min))
@@ -1525,9 +1564,10 @@ where they were found."
 \f
 ;; Empty tags file support.
 
-;; Recognize an empty file and give it local values of the tags table format
-;; variables which do nothing.
 (defun tags-recognize-empty-tags-table ()
+  "Return non-nil if current buffer is empty.
+If empty, make buffer-local values of the tags table format variables
+that do nothing."
   (and (zerop (buffer-size))
        (mapc (lambda (sym) (set (make-local-variable sym) 'ignore))
             '(tags-table-files-function
@@ -1544,32 +1584,29 @@ where they were found."
 
 ;; This might be a neat idea, but it's too hairy at the moment.
 ;;(defmacro tags-with-syntax (&rest body)
-;;   `(let ((current (current-buffer))
-;;        (otable (syntax-table))
-;;        (buffer (find-file-noselect (file-of-tag)))
-;;        table)
-;;       (unwind-protect
-;;        (progn
-;;          (set-buffer buffer)
-;;          (setq table (syntax-table))
-;;          (set-buffer current)
-;;          (set-syntax-table table)
-;;            ,@body)
-;;       (set-syntax-table otable))))
+;;   `(with-syntax-table
+;;        (with-current-buffer (find-file-noselect (file-of-tag))
+;;          (syntax-table))
+;;      ,@body))
 ;;(put 'tags-with-syntax 'edebug-form-spec '(&rest form))
 
 ;; exact file name match, i.e. searched tag must match complete file
 ;; name including directories parts if there are some.
 (defun tag-exact-file-name-match-p (tag)
+  "Return non-nil if TAG matches complete file name.
+Any directory part of the file name is also matched."
   (and (looking-at ",[0-9\n]")
        (save-excursion (backward-char (+ 2 (length tag)))
                       (looking-at "\f\n"))))
+
 ;; file name match as above, but searched tag must match the file
 ;; name not including the directories if there are some.
 (defun tag-file-name-match-p (tag)
+  "Return non-nil if TAG matches file name, excluding directory part."
   (and (looking-at ",[0-9\n]")
        (save-excursion (backward-char (1+ (length tag)))
                       (looking-at "/"))))
+
 ;; this / to detect we are after a directory separator is ok for unix,
 ;; is there a variable that contains the regexp for directory separator
 ;; on whatever operating system ?
@@ -1578,6 +1615,8 @@ where they were found."
 ;; 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)
+  "Return non-nil if current tag line matches TAG exactly.
+Point should be just after a string that matches TAG."
   ;; 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))
@@ -1587,6 +1626,8 @@ where they were found."
 ;; t if point is at a tag line that has an implicit name.
 ;; point should be just after a string that matches TAG.
 (defun tag-implicit-name-match-p (tag)
+  "Return non-nil if current tag line has an implicit name.
+Point should be just after a string that matches TAG."
   ;; Look at the comment of the make_tag function in lib-src/etags.c for
   ;; a textual description of the four rules.
   (and (string-match "^[^ \t()=,;]+$" tag) ;rule #1
@@ -1598,6 +1639,8 @@ where they were found."
 ;; 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)
+  "Return non-nil if current tag line matches TAG as a symbol.
+Point should be just after a string that matches TAG."
   (and (looking-at "\\Sw.*\177") (looking-at "\\S_.*\177")
        (save-excursion
         (backward-char (1+ (length tag)))
@@ -1606,6 +1649,8 @@ where they were found."
 ;; t if point is at a tag line that matches TAG as a word.
 ;; point should be just after a string that matches TAG.
 (defun tag-word-match-p (tag)
+  "Return non-nil if current tag line matches TAG as a word.
+Point should be just after a string that matches TAG."
   (and (looking-at "\\b.*\177")
        (save-excursion (backward-char (length tag))
                       (looking-at "\\b"))))
@@ -1613,6 +1658,9 @@ where they were found."
 ;; partial file name match, i.e. searched tag must match a substring
 ;; of the file name (potentially including a directory separator).
 (defun tag-partial-file-name-match-p (tag)
+  "Return non-nil if current tag matches file name.
+This is a substring match, and it can include directory separators.
+Point should be just after a string that matches TAG."
   (and (looking-at ".*,[0-9\n]")
        (save-excursion (beginning-of-line)
                        (backward-char 2)
@@ -1620,14 +1668,16 @@ where they were found."
 
 ;; t if point is in a tag line with a tag containing TAG as a substring.
 (defun tag-any-match-p (tag)
+  "Return non-nil if current tag line contains TAG as a substring."
   (looking-at ".*\177"))
 
 ;; t if point is at a tag line that matches RE as a regexp.
 (defun tag-re-match-p (re)
+  "Return non-nil if current tag line matches regexp RE."
   (save-excursion
     (beginning-of-line)
     (let ((bol (point)))
-      (and (search-forward "\177" (save-excursion (end-of-line) (point)) t)
+      (and (search-forward "\177" (line-end-position) t)
           (re-search-backward re bol t)))))
 \f
 (defcustom tags-loop-revert-buffers nil
@@ -1697,8 +1747,14 @@ if the file was newly read in, the value is the filename."
     ;; if the files have changed on disk.
     (and buffer tags-loop-revert-buffers
         (not (verify-visited-file-modtime buffer))
+        (y-or-n-p
+         (format
+          (if (buffer-modified-p buffer)
+              "File %s changed on disk.  Discard your edits? "
+            "File %s changed on disk.  Reread from disk? ")
+          next))
         (with-current-buffer buffer
-          (revert-buffer t)))
+          (revert-buffer t t)))
     (if (not (and new novisit))
        (set-buffer (find-file-noselect next novisit))
       ;; Like find-file, but avoids random warning messages.
@@ -1816,13 +1872,19 @@ See documentation of variable `tags-file-name'."
     (tags-loop-continue (or file-list-form t))))
 
 ;;;###autoload
-(defun tags-query-replace (from to &optional delimited file-list-form start end)
+(defun tags-query-replace (from to &optional delimited file-list-form)
   "Do `query-replace-regexp' of FROM with TO on all files listed in tags table.
 Third arg DELIMITED (prefix arg) means replace only word-delimited matches.
 If you exit (\\[keyboard-quit], RET or q), you can resume the query replace
 with the command \\[tags-loop-continue].
+Fourth arg FILE-LIST-FORM non-nil means initialize the replacement loop.
+Fifth and sixth arguments START and END are accepted, for compatibility
+with `query-replace-regexp', and ignored.
 
-See documentation of variable `tags-file-name'."
+If FILE-LIST-FORM is non-nil, it is a form to evaluate to
+produce the list of files to search.
+
+See also the documentation of the variable `tags-file-name'."
   (interactive (query-replace-read-args "Tags query replace (regexp)" t t))
   (setq tags-loop-scan `(let ,(unless (equal from (downcase from))
                                '((case-fold-search nil)))
@@ -1831,10 +1893,11 @@ See documentation of variable `tags-file-name'."
                              ;; to the beginning of it so perform-replace
                              ;; will see it.
                              (goto-char (match-beginning 0))))
-       tags-loop-operate `(perform-replace ',from ',to t t ',delimited))
+       tags-loop-operate `(perform-replace ',from ',to t t ',delimited
+                                           nil multi-query-replace-map))
   (tags-loop-continue (or file-list-form t)))
 \f
-(defun tags-complete-tags-table-file (string predicate what)
+(defun tags-complete-tags-table-file (string predicate what) ; Doc string?
   (save-excursion
     ;; If we need to ask for the tag table, allow that.
     (let ((enable-recursive-minibuffers t))
@@ -1886,7 +1949,7 @@ directory specification."
          (funcall tags-apropos-function regexp))))
     (etags-tags-apropos-additional regexp))
   (with-current-buffer "*Tags List*"
-    (require 'apropos)
+    (eval-and-compile (require 'apropos))
     (apropos-mode)
     ;; apropos-mode is derived from fundamental-mode and it kills
     ;; all local variables.
@@ -1896,6 +1959,7 @@ directory specification."
 
 (define-button-type 'tags-select-tags-table
   'action 'select-tags-table-select
+  'follow-link t
   'help-echo "RET, t or mouse-2: select tags table")
 
 ;; XXX If a file is in multiple tables, selection may get the wrong one.
@@ -1906,7 +1970,8 @@ 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)
+  (setq buffer-read-only nil
+       buffer-undo-list t)
   (erase-buffer)
   (let ((set-list tags-table-set-list)
        (desired-point nil)
@@ -1954,7 +2019,7 @@ see the doc of that variable if you want to add names to the list."
   (set-buffer-modified-p nil)
   (select-tags-table-mode))
 
-(defvar select-tags-table-mode-map
+(defvar select-tags-table-mode-map ; Doc string?
   (let ((map (make-sparse-keymap)))
     (set-keymap-parent map button-buffer-map)
     (define-key map "t" 'push-button)
@@ -1965,10 +2030,8 @@ see the doc of that variable if you want to add names to the list."
     (define-key map "q" 'select-tags-table-quit)
     map))
 
-(define-derived-mode select-tags-table-mode fundamental-mode "Select Tags Table"
-  "Major mode for choosing a current tags table among those already loaded.
-
-\\{select-tags-table-mode-map}"
+(define-derived-mode select-tags-table-mode special-mode "Select Tags Table"
+  "Major mode for choosing a current tags table among those already loaded."
   (setq buffer-read-only t))
 
 (defun select-tags-table-select (button)
@@ -1985,7 +2048,6 @@ see the doc of that variable if you want to add names to the list."
   (interactive)
   (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.
@@ -1998,34 +2060,10 @@ for \\[find-tag] (which see)."
       (error "%s"
             (substitute-command-keys
              "No tags table loaded; try \\[visit-tags-table]")))
-  (let ((completion-ignore-case (if (memq tags-case-fold-search '(t nil))
-                                   tags-case-fold-search
-                                 case-fold-search))
-       (pattern (funcall (or find-tag-default-function
-                             (get major-mode 'find-tag-default-function)
-                             'find-tag-default)))
-       beg
-       completion)
-    (or pattern
-       (error "Nothing to complete"))
-    (search-backward pattern)
-    (setq beg (point))
-    (forward-char (length pattern))
-    (setq completion (tags-complete-tag pattern nil nil))
-    (cond ((eq completion t))
-         ((null completion)
-          (message "Can't find completion for \"%s\"" pattern)
-          (ding))
-         ((not (string= pattern completion))
-          (delete-region beg (point))
-          (insert completion))
-         (t
-          (message "Making completion list...")
-          (with-output-to-temp-buffer "*Completions*"
-            (display-completion-list
-             (all-completions pattern 'tags-complete-tag nil)
-             pattern))
-          (message "Making completion list...%s" "done")))))
+  (let ((comp-data (tags-completion-at-point-function)))
+    (if (null comp-data)
+       (error "Nothing to complete")
+      (apply 'completion-in-region comp-data))))
 
 (dolist (x '("^No tags table in use; use .* to select one$"
             "^There is no default tag$"
@@ -2042,5 +2080,4 @@ for \\[find-tag] (which see)."
 \f
 (provide 'etags)
 
-;; arch-tag: b897c2b5-08f3-4837-b2d3-0e7d6db1b63e
 ;;; etags.el ends here