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