>>>> "Heiko" == Heiko Muenkel
<muenkel(a)tnt.uni-hannover.de> writes:
>>>> "Hrvoje" == Hrvoje Niksic
<hniksic(a)srce.hr> writes:
Hrvoje> Martin Buchholz <martin(a)xemacs.org>
writes:
>> Exact searching for symbols in lisp mode seems to be messed
up.
>>
>> To reproduce, make a TAGS table for XEmacs using `make TAGS',
>> do xemacs -q eval (setq tags-always-exact t) visit the file
>> .../lisp/etags.el and do M-x find-tag RET find-tag RET ==> No
>> entries matching find-tag
Hrvoje> I am unable to repeat this bug. For me, `M-. find-tag
Hrvoje> RET' correctly finds `find-tag'.
Did you omit the step, `visit .../lisp/etags.el'? The key is changing
the syntax table of the current buffer.
>> Another bug: If I try M-x find-tag RET list* RET it will
find
>> the tag for `list', not `list*'.
Hrvoje> That's because the expression you type in is actually a
Hrvoje> regexp (tags-always-exact is something else, and it's
Hrvoje> almost totally obsolete now.)
The docstring for find-tag says nothing about regexps. If a user
wants to do regexp searching for tags, isn't the right tool
tags-apropos? (although this function could use some updating, too)
Hrvoje> `M-. list\* RET' will correctly find one definition of
Hrvoje> `list*' (and a subsequent `M-,' will find the other one.)
>> XEmacs should first look for an exact string match for the
>> symbol surrounded by the ^? and ^A delimiters before trying a
>> pattern match.
Heiko> The function find-tag-internal never looks between ^? and ^A and in my eyes
Heiko> that's a bug. I've fixed this yesterday and it seems to work, but
I've
Heiko> not tried it with inexact matches or with regular expressions. I need
Heiko> this changes, because I'm using tag tables, where the first part of
Heiko> an tag table entry is the real string found by etags in the source
Heiko> file and the second part (that's the one between the ^? and ^A) is the
Heiko> name of the constructed function or method (eg: some languages like
Heiko> Clips builds accessor methods from the slots of classes). With my
Heiko> changes I'm now able to find the definition of a slot, when I'm
Heiko> searching with the name of the method.
Heiko> In the original function there's a strange implementation of the
Heiko> search. I don't understand why it was implemented this way, so it's
Heiko> possible, that my changes breaks something else. Therefore I've
Heiko> included the changed file in this mail. Please try it and let me know
Heiko> if it works or not. I'll send a patch to xemacs-patches if it works.
Heiko, thanks for looking at this. Hrvoje, please take a look at
Heiko's changes.
Heiko> ;;; etags.el --- etags facility for Emacs
Heiko> ;; Copyright 1985, 1986, 1988, 1990, 1997 Free Software Foundation, Inc.
Heiko> ;; Author: Their Name is Legion (see list below)
Heiko> ;; Maintainer: XEmacs Development Team
Heiko> ;; Keywords: tools
Heiko> ;; This file is part of XEmacs.
Heiko> ;; XEmacs is free software; you can redistribute it and/or modify it
Heiko> ;; under the terms of the GNU General Public License as published by
Heiko> ;; the Free Software Foundation; either version 2, or (at your option)
Heiko> ;; any later version.
Heiko> ;; XEmacs is distributed in the hope that it will be useful, but
Heiko> ;; WITHOUT ANY WARRANTY; without even the implied warranty of
Heiko> ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Heiko> ;; General Public License for more details.
Heiko> ;; You should have received a copy of the GNU General Public License
Heiko> ;; along with XEmacs; see the file COPYING. If not, write to the
Heiko> ;; Free Software Foundation, 59 Temple Place - Suite 330,
Heiko> ;; Boston, MA 02111-1307, USA.
Heiko> ;;; Synched up with: Not synched with FSF.
Heiko> ;;; Commentary:
Heiko> ;; This file is completely different from FSF's etags.el. It appears
Heiko> ;; that an early version of this file (tags.el) has been rewritten by
Heiko> ;; two different people; we got one, FSF got the other. Various
Heiko> ;; people have said that our version is better and faster.
Heiko> ;; TODO:
Heiko> ;; - DOCUMENT!
Heiko> ;; Derived from the original lisp/tags.el.
Heiko> ;; Ideas and code from the work of the following people:
Heiko> ;; Andy Norman <ange(a)hplb.hpl.hp.com>, author of ange-tags.el
Heiko> ;; Ramana Rao <rao(a)arisia.xerox.com>
Heiko> ;; John Sturdy <jcgs(a)harlqn.co.uk>, author of tags-helper.el
Heiko> ;; Henry Kautz <kautz(a)allegra.att.com>, author of tag-completion.el
Heiko> ;; Dan LaLiberte <liberte(a)cs.uiuc.edu>, author of local-tags.el
Heiko> ;; Tom Dietterich <tgd(a)turing.cs.orst.edu>, author of quest.el
Heiko> ;; The author(s) of lisp/simple.el
Heiko> ;; Duke Briscoe <briscoe(a)cs.yale.edu>
Heiko> ;; Lynn Slater <lrs(a)indetech.com>, author of location.el
Heiko> ;; Shinichirou Sugou <shin(a)sgtp.apple.juice.or.jp>
Heiko> ;; an unidentified anonymous elisp hacker
Heiko> ;; Kyle Jones <kyle_jones(a)wonderworks.com>
Heiko> ;; added "Exact match, then inexact" code
Heiko> ;; added support for include directive.
Heiko> ;; Hrvoje Niksic <hniksic(a)srce.hr>
Heiko> ;; various changes.
Heiko>
Heiko> ;;; User variables.
Heiko> (defgroup etags nil
Heiko> "Etags facility for Emacs.
Heiko> Using etags, you can create tag tables for any number of files, and
Heiko> easily access the symbols in those files, using the `\\[find-tag]'
Heiko> command."
Heiko> :prefix "tags-"
Heiko> :group 'tools)
Heiko> (defcustom tags-build-completion-table 'ask
Heiko> "*If this variable is nil, then tags completion is disabled.
Heiko> If it is t, then things which prompt for tags will do so with completion
Heiko> across all known tags.
Heiko> If it is the symbol `ask', you will be asked whether each tags table
Heiko> should be added to the completion list as it is read in. (With the
Heiko> exception that for very small tags tables, you will not be asked,
Heiko> since they can be parsed quickly.)"
Heiko> :type '(choice (const :tag "Disabled" nil)
Heiko> (const :tag "Complete All" t)
Heiko> (const :tag "Ask" ask))
Heiko> :group 'etags)
Heiko> (defcustom tags-always-exact nil
Heiko> "*If this variable is non-nil, then tags always looks for exact matches.
Heiko> If it is nil (the default), tags will first go through exact matches,
Heiko> then through the non-exact ones."
Heiko> :type 'boolean
Heiko> :group 'etags)
Heiko> (defcustom tag-table-alist nil
Heiko> "*A list which determines which tags files are active for a buffer.
Heiko> This is not really an association list, in that all elements are
Heiko> checked. The CAR of each element of this list is a pattern against
Heiko> which the buffer's file name is compared; if it matches, then the CDR
Heiko> of the list should be the name of the tags table to use. If more than
Heiko> one element of this list matches the buffer's file name, then all of
Heiko> the associated tags tables will be used. Earlier ones will be
Heiko> searched first.
Heiko> If the CAR of elements of this list are strings, then they are treated
Heiko> as regular-expressions against which the file is compared (like the
Heiko> auto-mode-alist). If they are not strings, then they are evaluated.
Heiko> If they evaluate to non-nil, then the current buffer is considered to
Heiko> match.
Heiko> If the CDR of the elements of this list are strings, then they are
Heiko> assumed to name a TAGS file. If they name a directory, then the string
Heiko> \"TAGS\" is appended to them to get the file name. If they are not
Heiko> strings, then they are evaluated, and must return an appropriate string.
Heiko> For example:
Heiko> (setq tag-table-alist
Heiko> '((\"/usr/src/public/perl/\" .
\"/usr/src/public/perl/perl-3.0/\")
Heiko> (\"\\\\.el$\" . \"/usr/local/emacs/src/\")
Heiko> (\"/jbw/gnu/\" . \"/usr15/degree/stud/jbw/gnu/\")
Heiko> (\"\" . \"/usr/local/emacs/src/\")
Heiko> ))
Heiko> This means that anything in the /usr/src/public/perl/ directory should use
Heiko> the TAGS file /usr/src/public/perl/perl-3.0/TAGS; and file ending in .el should
Heiko> use the TAGS file /usr/local/emacs/src/TAGS; and anything in or below the
Heiko> directory /jbw/gnu/ should use the TAGS file /usr15/degree/stud/jbw/gnu/TAGS.
Heiko> A file called something like \"/usr/jbw/foo.el\" would use both the
TAGS files
Heiko> /usr/local/emacs/src/TAGS and /usr15/degree/stud/jbw/gnu/TAGS (in that order)
Heiko> because it matches both patterns.
Heiko> If the buffer-local variable `buffer-tag-table' is set, then it names a
tags
Heiko> table that is searched before all others when find-tag is executed from this
Heiko> buffer.
Heiko> If there is a file called \"TAGS\" in the same directory as the file
in
Heiko> question, then that tags file will always be used as well (after the
Heiko> `buffer-tag-table' but before the tables specified by this list.)
Heiko> If the variable tags-file-name is set, then the tags file it names will apply
Heiko> to all buffers (for backwards compatibility.) It is searched first."
Heiko> :type '(repeat (cons :format "%v"
Heiko> (choice :value ""
Heiko> (regexp :tag "Buffer regexp")
Heiko> sexp)
Heiko> (choice :value ""
Heiko> (string :tag "Tag file or directory")
Heiko> sexp)))
Heiko> :group 'etags)
Heiko> (defvar buffer-tag-table nil
Heiko> "*The additional name of one TAGS table to be used for this buffer.
Heiko> You can set this with `\\[set-buffer-tag-table]'. See the documentation
Heiko> for the variable `tag-table-alist' for more information.")
Heiko> (make-variable-buffer-local 'buffer-tag-table)
Heiko> (defvar tags-file-name nil
Heiko> "The name of the tags-table used by all buffers.
Heiko> This is for backwards compatibility, and is largely supplanted by the
Heiko> variable tag-table-alist.")
Heiko> (defcustom tags-auto-read-changed-tag-files nil
Heiko> "*If non-nil, always re-read changed TAGS file without prompting.
Heiko> If nil, prompt whether to re-read the changed TAGS file."
Heiko> :type 'boolean
Heiko> :group 'etags)
Heiko> (defcustom make-tags-files-invisible nil
Heiko> "*If non-nil, TAGS-files will not show up in buffer-lists or be
Heiko> selectable (or deletable.)"
Heiko> :type 'boolean
Heiko> :group 'etags)
Heiko> (defcustom tags-search-nuke-uninteresting-buffers t
Heiko> "*If non-nil, keep newly-visited files if they contain the search
target.
Heiko> This affects the `tags-search' and `tags-query-replace' commands."
Heiko> :type 'boolean
Heiko> :group 'etags)
Heiko>
Heiko> ;; Buffer tag tables.
Heiko> (defun buffer-tag-table-list ()
Heiko> "Returns a list (ordered) of the tags tables which should be used for
Heiko> the current buffer."
Heiko> (let (result)
Heiko> ;; Explicitly set buffer-tag-table
Heiko> (when buffer-tag-table
Heiko> (push buffer-tag-table result))
Heiko> ;; Current directory
Heiko> (when (file-readable-p (concat default-directory "TAGS"))
Heiko> (push (concat default-directory "TAGS") result))
Heiko> ;; Parent directory
Heiko> (let ((parent-tag-file (expand-file-name "../TAGS"
default-directory)))
Heiko> (when (file-readable-p parent-tag-file)
Heiko> (push parent-tag-file result)))
Heiko> ;; tag-table-alist
Heiko> (let ((key (or buffer-file-name
Heiko> (concat default-directory (buffer-name))))
Heiko> expression)
Heiko> (dolist (item tag-table-alist)
Heiko> (setq expression (car item))
Heiko> ;; If the car of the alist item is a string, apply it as a regexp
Heiko> ;; to the buffer-file-name. Otherwise, evaluate it. If the
Heiko> ;; regexp matches, or the expression evaluates non-nil, then this
Heiko> ;; item in tag-table-alist applies to this buffer.
Heiko> (when (if (stringp expression)
Heiko> (string-match expression key)
Heiko> (ignore-errors
Heiko> (eval expression)))
Heiko> ;; Now evaluate the cdr of the alist item to get the name of
Heiko> ;; the tag table file.
Heiko> (setq expression (ignore-errors
Heiko> (eval (cdr item))))
Heiko> (if (stringp expression)
Heiko> (push expression result)
Heiko> (error "Expression in tag-table-alist evaluated to
non-string")))))
Heiko> (setq result
Heiko> (mapcar
Heiko> (lambda (name)
Heiko> (when (file-directory-p name)
Heiko> (setq name (concat (file-name-as-directory name) "TAGS")))
Heiko> (and (file-readable-p name)
Heiko> ;; get-tag-table-buffer has side-effects
Heiko> (symbol-value-in-buffer 'buffer-file-name
Heiko> (get-tag-table-buffer name))))
Heiko> result))
Heiko> (setq result (delq nil result))
Heiko> ;; If no TAGS file has been found, ask the user explicitly.
Heiko> ;; #### tags-file-name is *evil*.
Heiko> (or result tags-file-name
Heiko> (call-interactively 'visit-tags-table))
Heiko> (when tags-file-name
Heiko> (setq result (nconc result (list tags-file-name))))
Heiko> (or result (error "Buffer has no associated tag tables"))
Heiko> (delete-duplicates (nreverse result) :test 'equal)))
Heiko> ;;;###autoload
Heiko> (defun visit-tags-table (file)
Heiko> "Tell tags commands to use tags table file FILE when all else fails.
Heiko> FILE should be the name of a file created with the `etags' program.
Heiko> A directory name is ok too; it means file TAGS in that directory."
Heiko> (interactive (list (read-file-name "Visit tags table: (default TAGS)
"
Heiko> default-directory
Heiko> (expand-file-name "TAGS" default-directory)
Heiko> t)))
Heiko> (if (string-equal file "")
Heiko> (setq tags-file-name nil)
Heiko> (setq file (expand-file-name file))
Heiko> (when (file-directory-p file)
Heiko> (setq file (expand-file-name "TAGS" file)))
Heiko> ;; It used to be that, if a user pressed RET by mistake, the bogus
Heiko> ;; `tags-file-name' would remain, causing the error at
Heiko> ;; `buffer-tag-table'.
Heiko> (when (file-exists-p file)
Heiko> (setq tags-file-name file))))
Heiko> (defun set-buffer-tag-table (file)
Heiko> "In addition to the tags tables specified by the variable
`tag-table-alist',
Heiko> each buffer can have one additional table. This command sets that.
Heiko> See the documentation for the variable `tag-table-alist' for more
information."
Heiko> (interactive
Heiko> (list
Heiko> (read-file-name "Visit tags table: (directory sufficient) "
Heiko> nil default-directory t)))
Heiko> (or file (error "No TAGS file name supplied"))
Heiko> (setq file (expand-file-name file))
Heiko> (when (file-directory-p file)
Heiko> (setq file (expand-file-name "TAGS" file)))
Heiko> (or (file-exists-p file) (error "TAGS file missing: %s" file))
Heiko> (setq buffer-tag-table file))
Heiko>
Heiko> ;; Manipulating the tag table buffer
Heiko> (defconst tag-table-completion-status nil
Heiko> "Indicates whether a completion table has been built.
Heiko> Either nil, t, or `disabled'.")
Heiko> (make-variable-buffer-local 'tag-table-completion-status)
Heiko> (defconst tag-table-files nil
Heiko> "If the current buffer is a TAGS table, this holds a list of the files
Heiko> referenced by this file, or nil if that hasn't been computed yet.")
Heiko> (make-variable-buffer-local 'tag-table-files)
Heiko> (defun get-tag-table-buffer (tag-table)
Heiko> "Returns a buffer visiting the given TAGS table.
Heiko> If appropriate, reverting the buffer, and possibly build a
completion-table."
Heiko> (or (stringp tag-table)
Heiko> (error "Bad tags file name supplied: %s" tag-table))
Heiko> ;; Remove symbolic links from name.
Heiko> (setq tag-table (symlink-expand-file-name tag-table))
Heiko> (let (buf build-completion check-name)
Heiko> (setq buf (get-file-buffer tag-table))
Heiko> (unless buf
Heiko> (if (file-readable-p tag-table)
Heiko> (setq buf (find-file-noselect tag-table)
Heiko> check-name t)
Heiko> (error "No such tags file: %s" tag-table)))
Heiko> (with-current-buffer buf
Heiko> ;; Make the TAGS buffer invisible.
Heiko> (when (and check-name
Heiko> make-tags-files-invisible
Heiko> (string-match "\\`[^ ]" (buffer-name)))
Heiko> (rename-buffer (generate-new-buffer-name
Heiko> (concat " " (buffer-name)))))
Heiko> (or (verify-visited-file-modtime buf)
Heiko> (cond ((or tags-auto-read-changed-tag-files
Heiko> (yes-or-no-p
Heiko> (format "Tags file %s has changed, read new contents? "
Heiko> tag-table)))
Heiko> (when tags-auto-read-changed-tag-files
Heiko> (message "Tags file %s has changed, reading new contents..."
Heiko> tag-table))
Heiko> (revert-buffer t t)
Heiko> (when (eq tag-table-completion-status t)
Heiko> (setq tag-table-completion-status nil))
Heiko> (setq tag-table-files nil))))
Heiko> (or (eq (char-after 1) ?\f)
Heiko> (error "File %s not a valid tags file" tag-table))
Heiko> (or (memq tag-table-completion-status '(t disabled))
Heiko> (setq build-completion t))
Heiko> (when build-completion
Heiko> (if (ecase tags-build-completion-table
Heiko> ((nil) nil)
Heiko> ((t) t)
Heiko> ((ask)
Heiko> ;; don't bother asking for small ones
Heiko> (or (< (buffer-size) 20000)
Heiko> (y-or-n-p
Heiko> (format "Build tag completion table for %s? "
Heiko> tag-table)))))
Heiko> ;; The user wants to build the table:
Heiko> (condition-case nil
Heiko> (progn
Heiko> (add-to-tag-completion-table)
Heiko> (setq tag-table-completion-status t))
Heiko> ;; Allow user to C-g out correctly
Heiko> (quit
Heiko> (message "Tags completion table construction aborted")
Heiko> (setq tag-table-completion-status nil
Heiko> quit-flag t)
Heiko> t))
Heiko> ;; The table is verboten.
Heiko> (setq tag-table-completion-status 'disabled))))
Heiko> buf))
Heiko> (defun file-of-tag ()
Heiko> "Return the file name of the file whose tags point is within.
Heiko> Assumes the tag table is the current buffer.
Heiko> File name returned is relative to tag table file's directory."
Heiko> (let ((opoint (point))
Heiko> prev size)
Heiko> (save-excursion
Heiko> (goto-char (point-min))
Heiko> (while (< (point) opoint)
Heiko> (forward-line 1)
Heiko> (end-of-line)
Heiko> (skip-chars-backward "^,\n")
Heiko> (setq prev (point)
Heiko> size (read (current-buffer)))
Heiko> (goto-char prev)
Heiko> (forward-line 1)
Heiko> ;; New include syntax
Heiko> ;; filename,include
Heiko> ;; tacked on to the end of a tag file means use filename
Heiko> ;; as a tag file before giving up.
Heiko> ;; Skip it here.
Heiko> (unless (eq size 'include)
Heiko> (forward-char size)))
Heiko> (goto-char (1- prev))
Heiko> (buffer-substring (point) (point-at-bol)))))
Heiko> (defun tag-table-include-files ()
Heiko> "Return all file names associated with `include' directives in a tag
buffer."
Heiko> ;; New include syntax
Heiko> ;; filename,include
Heiko> ;; tacked on to the end of a tag file means use filename as a
Heiko> ;; tag file before giving up.
Heiko> (let ((files nil))
Heiko> (save-excursion
Heiko> (goto-char (point-min))
Heiko> (while (re-search-forward "\f\n\\(.*\\),include$" nil t)
Heiko> (push (match-string 1) files)))
Heiko> files))
Heiko> (defun tag-table-files (tag-table)
Heiko> "Returns a list of the files referenced by the named TAGS table."
Heiko> (with-current-buffer (get-tag-table-buffer tag-table)
Heiko> (unless tag-table-files
Heiko> (let (files prev size)
Heiko> (goto-char (point-min))
Heiko> (while (not (eobp))
Heiko> (forward-line 1)
Heiko> (end-of-line)
Heiko> (skip-chars-backward "^,\n")
Heiko> (setq prev (point)
Heiko> size (read (current-buffer)))
Heiko> (goto-char prev)
Heiko> (push (expand-file-name (buffer-substring (1- (point))
Heiko> (point-at-bol))
Heiko> default-directory)
Heiko> files)
Heiko> (forward-line 1)
Heiko> (forward-char size))
Heiko> (setq tag-table-files (nreverse files))))
Heiko> tag-table-files))
Heiko> ;; #### should this be on previous page?
Heiko> (defun buffer-tag-table-files ()
Heiko> "Returns a list of all files referenced by all TAGS tables that
Heiko> this buffer uses."
Heiko> (apply #'nconc
Heiko> (mapcar #'tag-table-files (buffer-tag-table-list))))
Heiko>
Heiko> ;; Building the completion table
Heiko> ;; Test cases for building completion table; must handle these properly:
Heiko> ;; Lisp_Int, XSETINT, current_column 60,2282
Heiko> ;; Lisp_Int, XSETINT, point>NumCharacters ? 0 : CharAt(363,9935
Heiko> ;; Lisp_Int, XSETINT, point<=FirstCharacter ? 0 : CharAt(366,10108
Heiko> ;; point<=FirstCharacter || CharAt(378,10630
Heiko> ;; point>NumCharacters || CharAt(382,10825
Heiko> ;; DEFUN ("x-set-foreground-color", Fx_set_foreground_color,191,4562
Heiko> ;; DEFUN ("x-set-foreground-color", Fx_set_foreground_color,191,4562
Heiko> ;; DEFUN ("*", Ftimes,1172,32079
Heiko> ;; DEFUN ("/=", Fneq,1035,28839
Heiko> ;; defun_internal 4199,101362
Heiko> ;; int pure[PURESIZE / sizeof 53,1564
Heiko> ;; char staticvec1[NSTATICS * sizeof 667,17608
Heiko> ;; Date: 04 May 87 23:53:11 PDT 26,1077
Heiko> ;; #define anymacroname(324,4344
Heiko> ;; (define-key ctl-x-map 311,11784
Heiko> ;; (define-abbrev-table 'c-mode-abbrev-table 24,1016
Heiko> ;; static char *skip_white(116,3443
Heiko> ;; static foo 348,11643
Heiko> ;; (defun texinfo-insert-@code 91,3358
Heiko> ;; (defvar texinfo-kindex)29,1105
Heiko> ;; (defun texinfo-format-\. 548,18376
Heiko> ;; (defvar sm::menu-kludge-y 621,22726
Heiko> ;; (defvar *mouse-drag-window* 103,3642
Heiko> ;; (defun simula-back-level(317,11263
Heiko> ;; } DPxAC,380,14024
Heiko> ;; } BM_QCB;69,2990
Heiko> ;; #define MTOS_DONE\t
Heiko> ;; "^[^ ]+ +\\([^ ]+\\) "
Heiko> ;; void *find_cactus_segment(116,2444
Heiko> ;; void *find_pdb_segment(162,3688
Heiko> ;; void init_dclpool(410,10739
Heiko> ;; WORD insert_draw_command(342,8881
Heiko> ;; void *req_pdbmem(579,15574
Heiko> (defvar tag-completion-table (make-vector 511 0))
Heiko> (defvar tag-symbol)
Heiko> (defvar tag-table-symbol)
Heiko> (defvar tag-symbol-tables)
Heiko> (defvar buffer-tag-table-list)
Heiko> (defmacro intern-tag-symbol (tag)
Heiko> `(progn
Heiko> (setq tag-symbol (intern ,tag tag-completion-table)
Heiko> tag-symbol-tables (and (boundp tag-symbol)
Heiko> (symbol-value tag-symbol)))
Heiko> (or (memq tag-table-symbol tag-symbol-tables)
Heiko> (set tag-symbol (cons tag-table-symbol tag-symbol-tables)))))
Heiko> ;; Can't use "\\s " in these patterns because that will include
newline
Heiko> (defconst tags-DEFUN-pattern
Heiko> "DEFUN[ \t]*(\"\\([^\"]+\\)\",[
\t]*\\(\\(\\sw\\|\\s_\\)+\\),\C-?")
Heiko> (defconst tags-array-pattern ".*[ \t]+\\([^ \[]+\\)\\[")
Heiko> (defconst tags-def-pattern
Heiko> "\\(.*[ \t]+\\)?\\**\\(\\(\\sw\\|\\s_\\)+\\)[ ();,\t]*\C-?"
Heiko> ;; "\\(.*[ \t]+\\)?\\(\\(\\sw\\|\\s_\\)+\\)[ ()]*\C-?"
Heiko> ;; "\\(\\sw\\|\\s_\\)+[ ()]*\C-?"
Heiko> )
Heiko> (defconst tags-file-pattern "^\f\n\\(.+\\),[0-9]+\n")
Heiko> ;; #### Should make it work with the `include' directive!
Heiko> (defun add-to-tag-completion-table ()
Heiko> "Sucks the current buffer (a TAGS table) into the
completion-table."
Heiko> (message "Adding %s to tags completion table..." buffer-file-name)
Heiko> (goto-char (point-min))
Heiko> (let ((tag-table-symbol (intern buffer-file-name tag-completion-table))
Heiko> ;; tag-table-symbol is used by intern-tag-symbol
Heiko> filename file-type name name2 tag-symbol
Heiko> tag-symbol-tables
Heiko> (case-fold-search nil))
Heiko> ;; Loop over the files mentioned in the TAGS file for each file,
Heiko> ;; try to find its major-mode, then process tags appropriately.
Heiko> (while (looking-at tags-file-pattern)
Heiko> (goto-char (match-end 0))
Heiko> (setq filename (file-name-sans-versions (match-string 1))
Heiko> ;; We used to check auto-mode-alist for the proper
Heiko> ;; file-type. This was way too slow, as it had to process
Heiko> ;; an enormous amount of regexps for each time. Now we
Heiko> ;; use the shotgun approach with only two regexps.
Heiko> file-type (cond ((string-match "\\.\\([cC]\\|cc\\|cxx\\)\\'"
Heiko> filename)
Heiko> 'c-mode)
Heiko> ((string-match "\\.\\(el\\|cl\\|lisp\\)\\'"
Heiko> filename)
Heiko> 'lisp-mode)
Heiko> ((string-match "\\.scm\\'" filename)
Heiko> 'scheme-mode)
Heiko> (t nil)))
Heiko> (set-syntax-table (cond ((and (eq file-type 'c-mode)
Heiko> c-mode-syntax-table)
Heiko> c-mode-syntax-table)
Heiko> ((eq file-type 'lisp-mode)
Heiko> lisp-mode-syntax-table)
Heiko> (t (standard-syntax-table))))
Heiko> ;; Clear loop variables.
Heiko> (setq name nil name2 nil)
Heiko> (lmessage 'progress "%s..." filename)
Heiko> ;; Loop over the individual tag lines.
Heiko> (while (not (or (eobp) (eq (char-after) ?\f)))
Heiko> (cond ((and (eq file-type 'c-mode)
Heiko> (looking-at "DEFUN[ \t]"))
Heiko> ;; DEFUN
Heiko> (or (looking-at tags-DEFUN-pattern)
Heiko> (error "DEFUN doesn't fit pattern"))
Heiko> (setq name (match-string 1)
Heiko> name2 (match-string 2)))
Heiko> ;;((looking-at "\\s ")
Heiko> ;; skip probably bogus entry:
Heiko> ;;)
Heiko> ((and (eq file-type 'c-mode)
Heiko> (looking-at ".*\\["))
Heiko> ;; Array
Heiko> (cond ((not (looking-at tags-array-pattern))
Heiko> (message "array definition doesn't fit pattern")
Heiko> (setq name nil))
Heiko> (t
Heiko> (setq name (match-string 1)))))
Heiko> ((and (eq file-type 'scheme-mode)
Heiko> (looking-at
"\\s-*(\\s-*def\\sw*\\s-*(?\\s-*\\(\\(\\sw\\|\\s_\\|:\\)+\\))?\\s-*\C-?"))
Heiko> ;; Something Schemish (is this really necessary??)
Heiko> (setq name (match-string 1)))
Heiko> ((looking-at tags-def-pattern)
Heiko> ;; ???
Heiko> (setq name (match-string 2))))
Heiko> ;; add the tags we found to the completion table
Heiko> (and name (intern-tag-symbol name))
Heiko> (and name2 (intern-tag-symbol name2))
Heiko> (forward-line 1)))
Heiko> (or (eobp) (error "Bad TAGS file")))
Heiko> (message "Adding %s to tags completion table...done"
buffer-file-name))
Heiko>
Heiko> ;; Interactive find-tag
Heiko> (defvar find-tag-default-hook nil
Heiko> "Function to call to create a default tag.
Heiko> Make it buffer-local in a mode hook. The function is called with no
Heiko> arguments.")
Heiko> (defvar find-tag-hook nil
Heiko> "Function to call after a tag is found.
Heiko> Make it buffer-local in a mode hook. The function is called with no
Heiko> arguments.")
Heiko> ;; Return a default tag to search for, based on the text at point.
Heiko> (defun find-tag-default ()
Heiko> (or (and (not (memq find-tag-default-hook '(nil find-tag-default)))
Heiko> (condition-case data
Heiko> (funcall find-tag-default-hook)
Heiko> (error
Heiko> (warn "Error in find-tag-default-hook signalled error: %s"
Heiko> (error-message-string data))
Heiko> nil)))
Heiko> (symbol-near-point)))
Heiko> ;; This function depends on the following symbols being bound properly:
Heiko> ;; buffer-tag-table-list,
Heiko> ;; tag-symbol-tables (value irrelevant, bound outside for efficiency)
Heiko> (defun tag-completion-predicate (tag-symbol)
Heiko> (and (boundp tag-symbol)
Heiko> (setq tag-symbol-tables (symbol-value tag-symbol))
Heiko> (catch 'found
Heiko> (while tag-symbol-tables
Heiko> (when (memq (car tag-symbol-tables) buffer-tag-table-list)
Heiko> (throw 'found t))
Heiko> (setq tag-symbol-tables (cdr tag-symbol-tables))))))
Heiko> (defun buffer-tag-table-symbol-list ()
Heiko> (mapcar (lambda (table-name)
Heiko> (intern table-name tag-completion-table))
Heiko> (buffer-tag-table-list)))
Heiko> (defvar find-tag-history nil "History list for find-tag-tag.")
Heiko> (defun find-tag-tag (prompt)
Heiko> (let* ((default (find-tag-default))
Heiko> (buffer-tag-table-list (buffer-tag-table-symbol-list))
Heiko> tag-symbol-tables tag-name)
Heiko> (setq tag-name
Heiko> (completing-read
Heiko> (if default
Heiko> (format "%s(default %s) " prompt default)
Heiko> prompt)
Heiko> tag-completion-table 'tag-completion-predicate nil nil
Heiko> 'find-tag-history))
Heiko> (if (string-equal tag-name "")
Heiko> ;; #### - This is a really LAME way of doing it! --Stig
Heiko> default ;indicate exact symbol match
Heiko> tag-name)))
Heiko> (defvar last-tag-data nil
Heiko> "Information for continuing a tag search.
Heiko> Is of the form (TAG POINT MATCHING-EXACT TAG-TABLE TAG-TABLE ...).")
Heiko> (defvar tags-loop-operate nil
Heiko> "Form for `tags-loop-continue' to eval to change one file.")
Heiko> (defvar tags-loop-scan
Heiko> '(error "%s" (substitute-command-keys
Heiko> "No \\[tags-search] or \\[tags-query-replace] in progress."))
Heiko> "Form for `tags-loop-continue' to eval to scan one file.
Heiko> If it returns non-nil, this file needs processing by evalling
Heiko> \`tags-loop-operate'. Otherwise, move on to the next file.")
Heiko> (autoload 'get-symbol-syntax-table "symbol-syntax")
Heiko> (defun find-tag-internal (tagname)
Heiko> (let ((next (null tagname))
Heiko> (tmpnext (null tagname))
Heiko> ;; If tagname is a list: (TAGNAME), this indicates
Heiko> ;; requiring an exact symbol match.
Heiko> (exact (or tags-always-exact (consp tagname)))
Heiko> (normal-syntax-table (syntax-table))
Heiko> (exact-syntax-table (get-symbol-syntax-table (syntax-table)))
Heiko> tag-table-currently-matching-exact
Heiko> tag-target exact-tagname
Heiko> tag-tables tag-table-point file linebeg startpos buf
Heiko> offset found pat syn-tab)
Heiko> (when (consp tagname)
Heiko> (setq tagname (car tagname)))
Heiko> (cond (next
Heiko> (setq tagname (car last-tag-data))
Heiko> (setq tag-table-currently-matching-exact
Heiko> (car (cdr (cdr last-tag-data)))))
Heiko> (t
Heiko> (setq tag-table-currently-matching-exact t)))
Heiko> (setq exact-tagname (concat "\C-?" tagname "\C-A"))
Heiko> (save-excursion
Heiko> (catch 'found
Heiko> ;; Loop searching for exact matches and then inexact matches.
Heiko> (while (not (eq tag-table-currently-matching-exact 'neither))
Heiko> (cond (tmpnext
Heiko> (setq tag-tables (cdr (cdr (cdr last-tag-data)))
Heiko> tag-table-point (car (cdr last-tag-data)))
Heiko> ;; Start from the beginning of the table list on the
Heiko> ;; next iteration of the loop.
Heiko> (setq tmpnext nil))
Heiko> (t
Heiko> (setq tag-tables (buffer-tag-table-list)
Heiko> tag-table-point 1)))
Heiko> (if tag-table-currently-matching-exact
Heiko> (setq tag-target exact-tagname
Heiko> syn-tab exact-syntax-table)
Heiko> (setq tag-target tagname
Heiko> syn-tab normal-syntax-table))
Heiko> (with-search-caps-disable-folding tag-target t
Heiko> (while tag-tables
Heiko> (set-buffer (get-tag-table-buffer (car tag-tables)))
Heiko> (bury-buffer (current-buffer))
Heiko> (goto-char (or tag-table-point (point-min)))
Heiko> (setq tag-table-point nil)
Heiko> (letf (((syntax-table) syn-tab)
Heiko> (case-fold-search nil))
Heiko> ;; #### should there be support for non-regexp
Heiko> ;; tag searches?
Heiko> (while (re-search-forward tag-target nil t)
Heiko> (and
Heiko> ;; If we're looking for inexact matches, skip
Heiko> ;; exact matches since we've visited them
Heiko> ;; already.
Heiko> (or tag-table-currently-matching-exact
Heiko> (letf (((syntax-table) exact-syntax-table))
Heiko> (save-excursion
Heiko> (goto-char (match-beginning 0))
Heiko> (not (looking-at exact-tagname)))))
Heiko> (throw 'found t))))
Heiko> (setq tag-tables
Heiko> (nconc (tag-table-include-files) (cdr tag-tables)))))
Heiko> (if (and (not exact) (eq tag-table-currently-matching-exact t))
Heiko> (setq tag-table-currently-matching-exact nil)
Heiko> (setq tag-table-currently-matching-exact 'neither)))
Heiko> (error "No %sentries %s %s"
Heiko> (if next "more " "")
Heiko> (if exact "matching" "containing")
Heiko> tagname))
Heiko> (search-forward "\C-?")
Heiko> (setq file (expand-file-name (file-of-tag)
Heiko> ;; In XEmacs, this needs to be
Heiko> ;; relative to:
Heiko> (or (file-name-directory (car tag-tables))
Heiko> "./")))
Heiko> (setq linebeg (buffer-substring (1- (point)) (point-at-bol)))
Heiko> (search-forward ",")
Heiko> (setq startpos (read (current-buffer)))
Heiko> (setq last-tag-data
Heiko> (nconc (list tagname (point) tag-table-currently-matching-exact)
Heiko> tag-tables))
Heiko> (setq buf (find-file-noselect file))
Heiko> (with-current-buffer buf
Heiko> (save-excursion
Heiko> (save-restriction
Heiko> (widen)
Heiko> ;; Here we search for PAT in the range [STARTPOS - OFFSET,
Heiko> ;; STARTPOS + OFFSET], with increasing values of OFFSET.
Heiko> ;;
Heiko> ;; We used to set the initial offset to 1000, but the
Heiko> ;; actual sources show that finer-grained control is
Heiko> ;; needed (e.g. two `hash_string's in src/symbols.c.) So,
Heiko> ;; I changed 100 to 100, and (* 3 offset) to (* 5 offset).
Heiko> (setq offset 100)
Heiko> (setq pat (concat "^" (regexp-quote linebeg)))
Heiko> (or startpos (setq startpos (point-min)))
Heiko> (while (and (not found)
Heiko> (progn
Heiko> (goto-char (- startpos offset))
Heiko> (not (bobp))))
Heiko> (setq found (re-search-forward pat (+ startpos offset) t))
Heiko> (setq offset (* 5 offset)))
Heiko> ;; Finally, try finding it anywhere in the buffer.
Heiko> (or found
Heiko> (re-search-forward pat nil t)
Heiko> (error "%s not found in %s" pat file))
Heiko> (beginning-of-line)
Heiko> (setq startpos (point)))))
Heiko> (cons buf startpos))))
Heiko> ;;;###autoload
Heiko> (defun find-tag (tagname &optional other-window)
Heiko> "*Find tag whose name contains TAGNAME.
Heiko> Selects the buffer that the tag is contained in
Heiko> and puts point at its definition.
Heiko> If TAGNAME is a null string, the expression in the buffer
Heiko> around or before point is used as the tag name.
Heiko> If called interactively with a numeric argument, searches for the next tag
Heiko> in the tag table that matches the tagname used in the previous find-tag.
Heiko> If second arg OTHER-WINDOW is non-nil, uses another window to display
Heiko> the tag.
Heiko> This version of this function supports multiple active tags tables,
Heiko> and completion.
Heiko> Variables of note:
Heiko> tag-table-alist controls which tables apply to which buffers
Heiko> tags-file-name a default tags table
Heiko> tags-build-completion-table controls completion behavior
Heiko> buffer-tag-table another way of specifying a buffer-local table
Heiko> make-tags-files-invisible whether tags tables should be very hidden
Heiko> tag-mark-stack-max how many tags-based hops to remember"
Heiko> (interactive (if current-prefix-arg
Heiko> '(nil nil)
Heiko> (list (find-tag-tag "Find tag: ") nil)))
Heiko> (let* ((local-find-tag-hook find-tag-hook)
Heiko> (next (null tagname))
Heiko> (result (find-tag-internal tagname))
Heiko> (tag-buf (car result))
Heiko> (tag-point (cdr result)))
Heiko> ;; Push old position on the tags mark stack.
Heiko> (if (or (not next)
Heiko> (not (memq last-command
Heiko> '(find-tag find-tag-other-window tags-loop-continue))))
Heiko> (push-tag-mark))
Heiko> (if other-window
Heiko> (pop-to-buffer tag-buf)
Heiko> (switch-to-buffer tag-buf))
Heiko> (widen)
Heiko> (push-mark)
Heiko> (goto-char tag-point)
Heiko> (if find-tag-hook
Heiko> (funcall find-tag-hook)
Heiko> (if local-find-tag-hook
Heiko> (funcall local-find-tag-hook))))
Heiko> (setq tags-loop-scan (list 'find-tag nil nil)
Heiko> tags-loop-operate nil)
Heiko> ;; Return t in case used as the tags-loop-scan.
Heiko> t)
Heiko> ;;;###autoload
Heiko> (defun find-tag-other-window (tagname &optional next)
Heiko> "*Find tag whose name contains TAGNAME.
Heiko> Selects the buffer that the tag is contained in in another window
Heiko> and puts point at its definition.
Heiko> If TAGNAME is a null string, the expression in the buffer
Heiko> around or before point is used as the tag name.
Heiko> If second arg NEXT is non-nil (interactively, with prefix arg),
Heiko> searches for the next tag in the tag table
Heiko> that matches the tagname used in the previous find-tag.
Heiko> This version of this function supports multiple active tags tables,
Heiko> and completion.
Heiko> Variables of note:
Heiko> tag-table-alist controls which tables apply to which buffers
Heiko> tags-file-name a default tags table
Heiko> tags-build-completion-table controls completion behavior
Heiko> buffer-tag-table another way of specifying a buffer-local table
Heiko> make-tags-files-invisible whether tags tables should be very hidden
Heiko> tag-mark-stack-max how many tags-based hops to remember"
Heiko> (interactive (if current-prefix-arg
Heiko> '(nil t)
Heiko> (list (find-tag-tag "Find tag other window: "))))
Heiko> (if next
Heiko> (find-tag nil t)
Heiko> (find-tag tagname t)))
Heiko>
Heiko> ;; Completion on tags in the buffer.
Heiko> (defun complete-symbol (&optional table predicate prettify)
Heiko> (let* ((end (point))
Heiko> (beg (save-excursion
Heiko> (backward-sexp 1)
Heiko> ;;(while (= (char-syntax (following-char)) ?\')
Heiko> ;; (forward-char 1))
Heiko> (skip-syntax-forward "'")
Heiko> (point)))
Heiko> (pattern (buffer-substring beg end))
Heiko> (table (or table obarray))
Heiko> (completion (try-completion pattern table predicate)))
Heiko> (cond ((eq completion t))
Heiko> ((null completion)
Heiko> (error "Can't find completion for \"%s\"" pattern))
Heiko> ((not (string-equal pattern completion))
Heiko> (delete-region beg end)
Heiko> (insert completion))
Heiko> (t
Heiko> (message "Making completion list...")
Heiko> (let ((list (all-completions pattern table predicate)))
Heiko> (if prettify
Heiko> (setq list (funcall prettify list)))
Heiko> (with-output-to-temp-buffer "*Help*"
Heiko> (display-completion-list list)))
Heiko> (message "Making completion list...%s" "done")))))
Heiko> ;;;###autoload
Heiko> (defun tag-complete-symbol ()
Heiko> "The function used to do tags-completion (using
'tag-completion-predicate)."
Heiko> (interactive)
Heiko> (let* ((buffer-tag-table-list (buffer-tag-table-symbol-list))
Heiko> tag-symbol-tables)
Heiko> (complete-symbol tag-completion-table 'tag-completion-predicate)))
Heiko>
Heiko> ;; Applying a command to files mentioned in tag tables
Heiko> (defvar next-file-list nil
Heiko> "List of files for next-file to process.")
Heiko> ;;;###autoload
Heiko> (defun next-file (&optional initialize novisit)
Heiko> "Select next file among files in current tag table(s).
Heiko> A first argument of t (prefix arg, if interactive) initializes to the
Heiko> beginning of the list of files in the (first) tags table. If the argument
Heiko> is neither nil nor t, it is evalled to initialize the list of files.
Heiko> Non-nil second argument NOVISIT means use a temporary buffer
Heiko> to save time and avoid uninteresting warnings.
Heiko> Value is nil if the file was already visited;
Heiko> if the file was newly read in, the value is the filename."
Heiko> (interactive "P")
Heiko> (cond ((not initialize)
Heiko> ;; Not the first run.
Heiko> )
Heiko> ((eq initialize t)
Heiko> ;; Initialize the list from the tags table.
Heiko> (setq next-file-list (buffer-tag-table-files)))
Heiko> (t
Heiko> ;; Initialize the list by evalling the argument.
Heiko> (setq next-file-list (eval initialize))))
Heiko> (when (null next-file-list)
Heiko> (and novisit
Heiko> (get-buffer " *next-file*")
Heiko> (kill-buffer " *next-file*"))
Heiko> (error "All files processed"))
Heiko> (let* ((file (car next-file-list))
Heiko> (buf (get-file-buffer file))
Heiko> (new (not buf)))
Heiko> (pop next-file-list)
Heiko> (if (not (and new novisit))
Heiko> (switch-to-buffer (find-file-noselect file novisit) t)
Heiko> ;; Like find-file, but avoids random junk.
Heiko> (set-buffer (get-buffer-create " *next-file*"))
Heiko> (kill-all-local-variables)
Heiko> (erase-buffer)
Heiko> (insert-file-contents file nil))
Heiko> (widen)
Heiko> (when (> (point) (point-min))
Heiko> (push-mark nil t)
Heiko> (goto-char (point-min)))
Heiko> (and new file)))
Heiko> ;;;###autoload
Heiko> (defun tags-loop-continue (&optional first-time)
Heiko> "Continue last \\[tags-search] or \\[tags-query-replace] command.
Heiko> Used noninteractively with non-nil argument to begin such a command (the
Heiko> argument is passed to `next-file', which see).
Heiko> Two variables control the processing we do on each file:
Heiko> the value of `tags-loop-scan' is a form to be executed on each file
Heiko> to see if it is interesting (it returns non-nil if so)
Heiko> and `tags-loop-operate' is a form to execute to operate on an interesting
file
Heiko> If the latter returns non-nil, we exit; otherwise we scan the next file."
Heiko> (interactive)
Heiko> (let ((messaged nil)
Heiko> (more-files-p t)
Heiko> new)
Heiko> (while more-files-p
Heiko> ;; Scan files quickly for the first or next interesting one.
Heiko> (while (or first-time
Heiko> (save-restriction
Heiko> (widen)
Heiko> (not (eval tags-loop-scan))))
Heiko> (setq new (next-file first-time
Heiko> tags-search-nuke-uninteresting-buffers))
Heiko> ;; If NEW is non-nil, we got a temp buffer,
Heiko> ;; and NEW is the file name.
Heiko> (if (or messaged
Heiko> (and (not first-time)
Heiko> (> (device-baud-rate) search-slow-speed)
Heiko> (setq messaged t)))
Heiko> (lmessage 'progress
Heiko> "Scanning file %s..." (or new buffer-file-name)))
Heiko> (setq first-time nil)
Heiko> (goto-char (point-min)))
Heiko> ;; If we visited it in a temp buffer, visit it now for real.
Heiko> (if (and new tags-search-nuke-uninteresting-buffers)
Heiko> (let ((pos (point)))
Heiko> (erase-buffer)
Heiko> (set-buffer (find-file-noselect new))
Heiko> (widen)
Heiko> (goto-char pos)))
Heiko> (switch-to-buffer (current-buffer))
Heiko> ;; Now operate on the file.
Heiko> ;; If value is non-nil, continue to scan the next file.
Heiko> (setq more-files-p (eval tags-loop-operate)))
Heiko> (and messaged
Heiko> (null tags-loop-operate)
Heiko> (message "Scanning file %s...found" buffer-file-name))))
Heiko> ;;;###autoload
Heiko> (defun tags-search (regexp &optional file-list-form)
Heiko> "Search through all files listed in tags table for match for REGEXP.
Heiko> Stops when a match is found.
Heiko> To continue searching for next match, use command \\[tags-loop-continue].
Heiko> See documentation of variable `tag-table-alist'."
Heiko> (interactive "sTags search (regexp): ")
Heiko> (if (and (equal regexp "")
Heiko> (eq (car tags-loop-scan) 'with-search-caps-disable-folding)
Heiko> (null tags-loop-operate))
Heiko> ;; Continue last tags-search as if by `M-,'.
Heiko> (tags-loop-continue nil)
Heiko> (setq tags-loop-scan `(with-search-caps-disable-folding ,regexp t
Heiko> (re-search-forward ,regexp nil t))
Heiko> tags-loop-operate nil)
Heiko> (tags-loop-continue (or file-list-form t))))
Heiko> ;;;###autoload
Heiko> (defun tags-query-replace (from to &optional delimited file-list-form)
Heiko> "Query-replace-regexp FROM with TO through all files listed in tags
table.
Heiko> Third arg DELIMITED (prefix arg) means replace only word-delimited matches.
Heiko> If you exit (\\[keyboard-quit] or ESC), you can resume the query-replace
Heiko> with the command \\[tags-loop-continue].
Heiko> See documentation of variable `tag-table-alist'."
Heiko> (interactive
Heiko> "sTags query replace (regexp): \nsTags query replace %s by: \nP")
Heiko> (setq tags-loop-scan `(with-search-caps-disable-folding ,from t
Heiko> (if (re-search-forward ,from nil t)
Heiko> ;; When we find a match, move back
Heiko> ;; to the beginning of it so perform-replace
Heiko> ;; will see it.
Heiko> (progn (goto-char (match-beginning 0)) t)))
Heiko> tags-loop-operate (list 'perform-replace from to t t
Heiko> (not (null delimited))))
Heiko> (tags-loop-continue (or file-list-form t)))
Heiko>
Heiko> ;; Miscellaneous
Heiko> ;;;###autoload
Heiko> (defun list-tags (file)
Heiko> "Display list of tags in FILE."
Heiko> (interactive (list (read-file-name
Heiko> (if (buffer-file-name)
Heiko> (format "List tags (in file, %s by default): "
Heiko> (file-name-nondirectory (buffer-file-name)))
Heiko> "List tags (in file): ")
Heiko> nil (buffer-file-name) t)))
Heiko> (find-file-noselect file)
Heiko> (with-output-to-temp-buffer "*Tags List*"
Heiko> (princ "Tags in file ")
Heiko> (princ file)
Heiko> (terpri)
Heiko> (save-excursion
Heiko> (dolist (tags-file (with-current-buffer (get-file-buffer file)
Heiko> (buffer-tag-table-list)))
Heiko> ;; We don't want completions getting in the way.
Heiko> (let ((tags-build-completion-table nil))
Heiko> (set-buffer (get-tag-table-buffer tags-file)))
Heiko> (goto-char (point-min))
Heiko> (when
Heiko> (search-forward (concat "\f\n" (file-name-nondirectory file)
",")
Heiko> nil t)
Heiko> (forward-line 1)
Heiko> (while (not (or (eobp) (looking-at "\f")))
Heiko> (princ (buffer-substring (point)
Heiko> (progn (skip-chars-forward "^\C-?")
Heiko> (point))))
Heiko> (terpri)
Heiko> (forward-line 1)))))))
Heiko> ;;;###autoload
Heiko> (defun tags-apropos (string)
Heiko> "Display list of all tags in tag table REGEXP matches."
Heiko> (interactive "sTag apropos (regexp): ")
Heiko> (with-output-to-temp-buffer "*Tags List*"
Heiko> (princ "Tags matching regexp ")
Heiko> (prin1 string)
Heiko> (terpri)
Heiko> (save-excursion
Heiko> (visit-tags-table-buffer)
Heiko> (goto-char 1)
Heiko> (while (re-search-forward string nil t)
Heiko> (beginning-of-line)
Heiko> (princ (buffer-substring (point)
Heiko> (progn (skip-chars-forward "^\C-?")
Heiko> (point))))
Heiko> (terpri)
Heiko> (forward-line 1)))))
Heiko> ;; #### copied from tags.el. This function is *very* big in FSF.
Heiko> (defun visit-tags-table-buffer ()
Heiko> "Select the buffer containing the current tag table."
Heiko> (or tags-file-name
Heiko> (call-interactively 'visit-tags-table))
Heiko> (set-buffer (or (get-file-buffer tags-file-name)
Heiko> (progn
Heiko> (setq tag-table-files nil)
Heiko> (find-file-noselect tags-file-name))))
Heiko> (or (verify-visited-file-modtime (get-file-buffer tags-file-name))
Heiko> (cond ((yes-or-no-p "Tags file has changed, read new contents?
")
Heiko> (revert-buffer t t)
Heiko> (setq tag-table-files nil))))
Heiko> (or (eq (char-after 1) ?\^L)
Heiko> (error "File %s not a valid tag table" tags-file-name)))
Heiko>
Heiko> ;; Sample uses of find-tag-hook and find-tag-default-hook
Heiko> ;; This is wrong. We should either make this behaviour default and
Heiko> ;; back it up, or not use it at all. For now, I've commented it out.
Heiko> ;; --hniksic
Heiko> ;; Example buffer-local tag finding
Heiko> ;(add-hook 'emacs-lisp-mode-hook 'setup-emacs-lisp-default-tag-hook)
Heiko> ;(defun setup-emacs-lisp-default-tag-hook ()
Heiko> ; (cond ((eq major-mode 'emacs-lisp-mode)
Heiko> ; (make-variable-buffer-local 'find-tag-default-hook)
Heiko> ; (setq find-tag-default-hook 'emacs-lisp-default-tag))))
Heiko> ;;; Run it once immediately
Heiko> ;(setup-emacs-lisp-default-tag-hook)
Heiko> ;(when (get-buffer "*scratch*")
Heiko> ; (with-current-buffer "*scratch*"
Heiko> ; (setup-emacs-lisp-default-tag-hook)))
Heiko> ;(defun emacs-lisp-default-tag ()
Heiko> ; "Function to return a default tag for Emacs-Lisp mode."
Heiko> ; (let ((tag (or (variable-at-point)
Heiko> ; (function-at-point))))
Heiko> ; (if tag (symbol-name tag))))
Heiko>
Heiko> ;; Display short info on tag in minibuffer
Heiko> ;; Don't pollute `M-?' -- we may need it for more important stuff.
--hniksic
Heiko> ;(if (null (lookup-key esc-map "?"))
Heiko> ; (define-key esc-map "?" 'display-tag-info))
Heiko> (defun display-tag-info (tagname)
Heiko> "Prints a description of the first tag matching TAGNAME in the echo
area.
Heiko> If this is an elisp function, prints something like \"(defun foo (x y
z)\".
Heiko> That is, is prints the first line of the definition of the form.
Heiko> If this is a C-defined elisp function, it does something more clever."
Heiko> (interactive (if current-prefix-arg
Heiko> '(nil)
Heiko> (list (find-tag-tag "Display tag info: "))))
Heiko> (let* ((results (find-tag-internal tagname))
Heiko> (tag-buf (car results))
Heiko> (tag-point (cdr results))
Heiko> info lname min max fname args)
Heiko> (with-current-buffer tag-buf
Heiko> (save-excursion
Heiko> (save-restriction
Heiko> (widen)
Heiko> (goto-char tag-point)
Heiko> (cond ((let ((case-fold-search nil))
Heiko> (looking-at "^DEFUN[ \t]"))
Heiko> (forward-sexp 1)
Heiko> (down-list 1)
Heiko> (setq lname (read (current-buffer))
Heiko> fname (buffer-substring
Heiko> (progn (forward-sexp 1) (point))
Heiko> (progn (backward-sexp 1) (point)))
Heiko> min (buffer-substring
Heiko> (progn (forward-sexp 3) (point))
Heiko> (progn (backward-sexp 1) (point)))
Heiko> max (buffer-substring
Heiko> (progn (forward-sexp 2) (point))
Heiko> (progn (backward-sexp 1) (point))))
Heiko> (backward-up-list 1)
Heiko> (setq args (buffer-substring
Heiko> (progn (forward-sexp 2) (point))
Heiko> (progn (backward-sexp 1) (point))))
Heiko> (setq info (format "Elisp: %s, C: %s %s, #args: %s"
Heiko> lname
Heiko> fname args
Heiko> (if (string-equal min max)
Heiko> min
Heiko> (format "from %s to %s" min max)))))
Heiko> (t
Heiko> (setq info
Heiko> (buffer-substring
Heiko> (progn (beginning-of-line) (point))
Heiko> (progn (end-of-line) (point)))))))))
Heiko> (message "%s" info))
Heiko> (setq tags-loop-scan '(display-tag-info nil)
Heiko> tags-loop-operate nil)
Heiko> ;; Always return non-nil
Heiko> t)
Heiko>
Heiko> ;; Tag mark stack.
Heiko> (defvar tag-mark-stack1 nil)
Heiko> (defvar tag-mark-stack2 nil)
Heiko> (defcustom tag-mark-stack-max 16
Heiko> "*The maximum number of elements kept on the mark-stack used
Heiko> by tags-search. See also the commands `\\[push-tag-mark]' and
Heiko> and `\\[pop-tag-mark]'."
Heiko> :type 'integer
Heiko> :group 'etags)
Heiko> (defun push-mark-on-stack (stack-symbol &optional max-size)
Heiko> (let ((stack (symbol-value stack-symbol)))
Heiko> (push (point-marker) stack)
Heiko> (cond ((and max-size
Heiko> (> (length stack) max-size))
Heiko> (set-marker (car (nthcdr max-size stack)) nil)
Heiko> (setcdr (nthcdr (1- max-size) stack) nil)))
Heiko> (set stack-symbol stack)))
Heiko> (defun pop-mark-from-stack (stack-symbol1 stack-symbol2 &optional max-size)
Heiko> (let* ((stack (or (symbol-value stack-symbol1)
Heiko> (error "No more tag marks on stack")))
Heiko> (marker (car stack))
Heiko> (m-buf (marker-buffer marker)))
Heiko> (set stack-symbol1 (cdr stack))
Heiko> (or m-buf
Heiko> (error "Marker has no buffer"))
Heiko> (or (buffer-live-p m-buf)
Heiko> (error "Buffer has been killed"))
Heiko> (push-mark-on-stack stack-symbol2 max-size)
Heiko> (switch-to-buffer m-buf)
Heiko> (widen)
Heiko> (goto-char marker)))
Heiko> (defun push-tag-mark ()
Heiko> (push-mark-on-stack 'tag-mark-stack1 tag-mark-stack-max))
Heiko> ;;;###autoload (define-key esc-map "*" 'pop-tag-mark)
Heiko> (defun pop-tag-mark (arg)
Heiko> "Go to last tag position.
Heiko> `find-tag' maintains a mark-stack seperate from the \\[set-mark-command]
mark-stack.
Heiko> This function pops (and moves to) the tag at the top of this stack."
Heiko> (interactive "P")
Heiko> (if (not arg)
Heiko> (pop-mark-from-stack
Heiko> 'tag-mark-stack1 'tag-mark-stack2 tag-mark-stack-max)
Heiko> (pop-mark-from-stack
Heiko> 'tag-mark-stack2 'tag-mark-stack1 tag-mark-stack-max)))
Heiko>
Heiko> (provide 'etags)
Heiko> (provide 'tags)
Hrvoje> I think I prefer it to be consistent. Perhaps there
Hrvoje> should be a switch to turn off the regexp behaviour.
Heiko> I think, that there's already a comment in etags.el, which suggested this.
Hrvoje> -- Hrvoje Niksic <hniksic(a)srce.hr> | Student at FER
Hrvoje> Zagreb, Croatia
Hrvoje> --------------------------------+--------------------------------
Hrvoje> You know it's going to be a bad day when your twin brother
Hrvoje> forgot your birthday.
Heiko> --
Heiko> Heiko Muenkel <muenkel(a)tnt.uni-hannover.de>
Heiko> <A
href="http://www.tnt.uni-hannover.de/wiss/muenkel.html">Münkel</A>
Heiko> PGP-KeyID: 0x4845D4A5