Initial revision
[bpt/emacs.git] / lisp / progmodes / etags.el
CommitLineData
ff1f0fa6
JB
1;; Tags facility for Emacs.
2;; Copyright (C) 1985, 1986, 1988 Free Software Foundation, Inc.
3
4;; This file is part of GNU Emacs.
5
6;; GNU Emacs is free software; you can redistribute it and/or modify
7;; it under the terms of the GNU General Public License as published by
8;; the Free Software Foundation; either version 1, or (at your option)
9;; any later version.
10
11;; GNU Emacs is distributed in the hope that it will be useful,
12;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;; GNU General Public License for more details.
15
16;; You should have received a copy of the GNU General Public License
17;; along with GNU Emacs; see the file COPYING. If not, write to
18;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
19
20
21(provide 'tags)
22
23(defvar tag-table-files nil
24 "List of file names covered by current tag table.
25nil means it has not been computed yet; do (tag-table-files) to compute it.")
26
27(defvar last-tag nil
28 "Tag found by the last find-tag.")
29
30(defun visit-tags-table (file)
31 "Tell tags commands to use tag table file FILE.
32FILE should be the name of a file created with the `etags' program.
33A directory name is ok too; it means file TAGS in that directory."
34 (interactive (list (read-file-name "Visit tags table: (default TAGS) "
35 default-directory
36 (concat default-directory "TAGS")
37 t)))
38 (setq file (expand-file-name file))
39 (if (file-directory-p file)
40 (setq file (concat file "TAGS")))
41 (setq tag-table-files nil
42 tags-file-name file))
43
44(defun visit-tags-table-buffer ()
45 "Select the buffer containing the current tag table.
46This is a file whose name is in the variable tags-file-name."
47 (or tags-file-name
48 (call-interactively 'visit-tags-table))
49 (set-buffer (or (get-file-buffer tags-file-name)
50 (progn
51 (setq tag-table-files nil)
52 (find-file-noselect tags-file-name))))
53 (or (verify-visited-file-modtime (get-file-buffer tags-file-name))
54 (cond ((yes-or-no-p "Tags file has changed, read new contents? ")
55 (revert-buffer t t)
56 (setq tag-table-files nil))))
57 (or (eq (char-after 1) ?\^L)
58 (error "File %s not a valid tag table" tags-file-name)))
59
60(defun file-of-tag ()
61 "Return the file name of the file whose tags point is within.
62Assumes the tag table is the current buffer.
63File name returned is relative to tag table file's directory."
64 (let ((opoint (point))
65 prev size)
66 (save-excursion
67 (goto-char (point-min))
68 (while (< (point) opoint)
69 (forward-line 1)
70 (end-of-line)
71 (skip-chars-backward "^,\n")
72 (setq prev (point))
73 (setq size (read (current-buffer)))
74 (goto-char prev)
75 (forward-line 1)
76 (forward-char size))
77 (goto-char (1- prev))
78 (buffer-substring (point)
79 (progn (beginning-of-line) (point))))))
80
81(defun tag-table-files ()
82 "Return a list of files in the current tag table.
83File names returned are absolute."
84 (save-excursion
85 (visit-tags-table-buffer)
86 (or tag-table-files
87 (let (files)
88 (goto-char (point-min))
89 (while (not (eobp))
90 (forward-line 1)
91 (end-of-line)
92 (skip-chars-backward "^,\n")
93 (setq prev (point))
94 (setq size (read (current-buffer)))
95 (goto-char prev)
96 (setq files (cons (expand-file-name
97 (buffer-substring (1- (point))
98 (save-excursion
99 (beginning-of-line)
100 (point)))
101 (file-name-directory tags-file-name))
102 files))
103 (forward-line 1)
104 (forward-char size))
105 (setq tag-table-files (nreverse files))))))
106
107;; Return a default tag to search for, based on the text at point.
108(defun find-tag-default ()
109 (save-excursion
110 (while (looking-at "\\sw\\|\\s_")
111 (forward-char 1))
112 (if (re-search-backward "\\sw\\|\\s_" nil t)
113 (progn (forward-char 1)
114 (buffer-substring (point)
115 (progn (forward-sexp -1)
116 (while (looking-at "\\s'")
117 (forward-char 1))
118 (point))))
119 nil)))
120
121(defun find-tag-tag (string)
122 (let* ((default (find-tag-default))
123 (spec (read-string
124 (if default
125 (format "%s(default %s) " string default)
126 string))))
127 (list (if (equal spec "")
128 default
129 spec))))
130
131(defun find-tag (tagname &optional next other-window)
132 "Find tag (in current tag table) whose name contains TAGNAME.
133 Selects the buffer that the tag is contained in
134and puts point at its definition.
135 If TAGNAME is a null string, the expression in the buffer
136around or before point is used as the tag name.
137 If second arg NEXT is non-nil (interactively, with prefix arg),
138searches for the next tag in the tag table
139that matches the tagname used in the previous find-tag.
140
141See documentation of variable tags-file-name."
142 (interactive (if current-prefix-arg
143 '(nil t)
144 (find-tag-tag "Find tag: ")))
145 (let (buffer file linebeg startpos)
146 (save-excursion
147 (visit-tags-table-buffer)
148 (if (not next)
149 (goto-char (point-min))
150 (setq tagname last-tag))
151 (setq last-tag tagname)
152 (while (progn
153 (if (not (search-forward tagname nil t))
154 (error "No %sentries containing %s"
155 (if next "more " "") tagname))
156 (not (looking-at "[^\n\177]*\177"))))
157 (search-forward "\177")
158 (setq file (expand-file-name (file-of-tag)
159 (file-name-directory tags-file-name)))
160 (setq linebeg
161 (buffer-substring (1- (point))
162 (save-excursion (beginning-of-line) (point))))
163 (search-forward ",")
164 (setq startpos (read (current-buffer))))
165 (if other-window
166 (find-file-other-window file)
167 (find-file file))
168 (widen)
169 (push-mark)
170 (let ((offset 1000)
171 found
172 (pat (concat "^" (regexp-quote linebeg))))
173 (or startpos (setq startpos (point-min)))
174 (while (and (not found)
175 (progn
176 (goto-char (- startpos offset))
177 (not (bobp))))
178 (setq found
179 (re-search-forward pat (+ startpos offset) t))
180 (setq offset (* 3 offset)))
181 (or found
182 (re-search-forward pat nil t)
183 (error "%s not found in %s" pat file)))
184 (beginning-of-line))
185 (setq tags-loop-form '(find-tag nil t))
186 ;; Return t in case used as the tags-loop-form.
187 t)
188
189(defun find-tag-other-window (tagname &optional next)
190 "Find tag (in current tag table) whose name contains TAGNAME.
191 Selects the buffer that the tag is contained in in another window
192and puts point at its definition.
193 If TAGNAME is a null string, the expression in the buffer
194around or before point is used as the tag name.
195 If second arg NEXT is non-nil (interactively, with prefix arg),
196searches for the next tag in the tag table
197that matches the tagname used in the previous find-tag.
198
199See documentation of variable tags-file-name."
200 (interactive (if current-prefix-arg
201 '(nil t)
202 (find-tag-tag "Find tag other window: ")))
203 (find-tag tagname next t))
204
205(defvar next-file-list nil
206 "List of files for next-file to process.")
207
208(defun next-file (&optional initialize)
209 "Select next file among files in current tag table.
210Non-nil argument (prefix arg, if interactive)
211initializes to the beginning of the list of files in the tag table."
212 (interactive "P")
213 (if initialize
214 (setq next-file-list (tag-table-files)))
215 (or next-file-list
216 (error "All files processed."))
217 (find-file (car next-file-list))
218 (setq next-file-list (cdr next-file-list)))
219
220(defvar tags-loop-form nil
221 "Form for tags-loop-continue to eval to process one file.
222If it returns nil, it is through with one file; move on to next.")
223
224(defun tags-loop-continue (&optional first-time)
225 "Continue last \\[tags-search] or \\[tags-query-replace] command.
226Used noninteractively with non-nil argument
227to begin such a command. See variable tags-loop-form."
228 (interactive)
229 (if first-time
230 (progn (next-file t)
231 (goto-char (point-min))))
232 (while (not (eval tags-loop-form))
233 (next-file)
234 (message "Scanning file %s..." buffer-file-name)
235 (goto-char (point-min))))
236
237(defun tags-search (regexp)
238 "Search through all files listed in tag table for match for REGEXP.
239Stops when a match is found.
240To continue searching for next match, use command \\[tags-loop-continue].
241
242See documentation of variable tags-file-name."
243 (interactive "sTags search (regexp): ")
244 (if (and (equal regexp "")
245 (eq (car tags-loop-form) 're-search-forward))
246 (tags-loop-continue nil)
247 (setq tags-loop-form
248 (list 're-search-forward regexp nil t))
249 (tags-loop-continue t)))
250
251(defun tags-query-replace (from to &optional delimited)
252 "Query-replace-regexp FROM with TO through all files listed in tag table.
253Third arg DELIMITED (prefix arg) means replace only word-delimited matches.
254If you exit (C-G or ESC), you can resume the query-replace
255with the command \\[tags-loop-continue].
256
257See documentation of variable tags-file-name."
258 (interactive "sTags query replace (regexp): \nsTags query replace %s by: \nP")
259 (setq tags-loop-form
260 (list 'and (list 'save-excursion
261 (list 're-search-forward from nil t))
262 (list 'not (list 'perform-replace from to t t
263 (not (null delimited))))))
264 (tags-loop-continue t))
265
266(defun list-tags (string)
267 "Display list of tags in file FILE.
268FILE should not contain a directory spec
269unless it has one in the tag table."
270 (interactive "sList tags (in file): ")
271 (with-output-to-temp-buffer "*Tags List*"
272 (princ "Tags in file ")
273 (princ string)
274 (terpri)
275 (save-excursion
276 (visit-tags-table-buffer)
277 (goto-char 1)
278 (search-forward (concat "\f\n" string ","))
279 (forward-line 1)
280 (while (not (or (eobp) (looking-at "\f")))
281 (princ (buffer-substring (point)
282 (progn (skip-chars-forward "^\177")
283 (point))))
284 (terpri)
285 (forward-line 1)))))
286
287(defun tags-apropos (string)
288 "Display list of all tags in tag table REGEXP matches."
289 (interactive "sTag apropos (regexp): ")
290 (with-output-to-temp-buffer "*Tags List*"
291 (princ "Tags matching regexp ")
292 (prin1 string)
293 (terpri)
294 (save-excursion
295 (visit-tags-table-buffer)
296 (goto-char 1)
297 (while (re-search-forward string nil t)
298 (beginning-of-line)
299 (princ (buffer-substring (point)
300 (progn (skip-chars-forward "^\177")
301 (point))))
302 (terpri)
303 (forward-line 1)))))