tags.c revision 1.6
1/*	$OpenBSD: tags.c,v 1.6 2012/10/20 09:05:33 jasper Exp $	*/
2
3/*
4 * This file is in the public domain.
5 *
6 * Author: Sunil Nimmagadda <sunil@sunilnimmagadda.com>
7 */
8
9#include <sys/queue.h>
10#include <sys/stat.h>
11#include <sys/tree.h>
12#include <sys/types.h>
13
14#include <ctype.h>
15#include <err.h>
16#include <stdlib.h>
17#include <string.h>
18#include <util.h>
19
20#include "def.h"
21
22struct ctag;
23
24static int               addctag(char *);
25static int               atbow(void);
26void                     closetags(void);
27static int               ctagcmp(struct ctag *, struct ctag *);
28static int               loadbuffer(char *);
29static int               loadtags(const char *);
30static int               pushtag(char *);
31static int               searchpat(char *);
32static struct ctag       *searchtag(char *);
33static char              *strip(char *, size_t);
34static void              unloadtags(void);
35
36#define DEFAULTFN "tags"
37
38char *tagsfn = NULL;
39int  loaded  = FALSE;
40
41/* ctags(1) entries are parsed and maintained in a tree. */
42struct ctag {
43	RB_ENTRY(ctag) entry;
44	char *tag;
45	char *fname;
46	char *pat;
47};
48RB_HEAD(tagtree, ctag) tags = RB_INITIALIZER(&tags);
49RB_GENERATE(tagtree, ctag, entry, ctagcmp);
50
51struct tagpos {
52	SLIST_ENTRY(tagpos) entry;
53	int    doto;
54	int    dotline;
55	char   *bname;
56};
57SLIST_HEAD(tagstack, tagpos) shead = SLIST_HEAD_INITIALIZER(shead);
58
59int
60ctagcmp(struct ctag *s, struct ctag *t)
61{
62	return strcmp(s->tag, t->tag);
63}
64
65/*
66 * Record the filename that contain tags to be used while loading them
67 * on first use. If a filename is already recorded, ask user to retain
68 * already loaded tags (if any) and unload them if user chooses not to.
69 */
70/* ARGSUSED */
71int
72tagsvisit(int f, int n)
73{
74	char fname[NFILEN], *bufp, *temp;
75	struct stat sb;
76
77	if (getbufcwd(fname, sizeof(fname)) == FALSE)
78		fname[0] = '\0';
79
80	if (strlcat(fname, DEFAULTFN, sizeof(fname)) >= sizeof(fname)) {
81		ewprintf("Filename too long");
82		return (FALSE);
83	}
84
85	bufp = eread("visit tags table (default %s): ", fname,
86	    NFILEN, EFFILE | EFCR | EFNEW | EFDEF, DEFAULTFN);
87
88	if (stat(bufp, &sb) == -1) {
89		ewprintf("stat: %s", strerror(errno));
90		return (FALSE);
91	} else if (S_ISREG(sb.st_mode) == 0) {
92		ewprintf("Not a regular file");
93		return (FALSE);
94	} else if (access(bufp, R_OK) == -1) {
95		ewprintf("Cannot access file %s", bufp);
96		return (FALSE);
97	}
98
99	if (tagsfn == NULL) {
100		if (bufp == NULL)
101			return (ABORT);
102		else if (bufp[0] == '\0') {
103			if ((tagsfn = strdup(fname)) == NULL) {
104				ewprintf("Out of memory");
105				return (FALSE);
106			}
107		} else {
108			/* bufp points to local variable, so duplicate. */
109			if ((tagsfn = strdup(bufp)) == NULL) {
110				ewprintf("Out of memory");
111				return (FALSE);
112			}
113		}
114	} else {
115		if ((temp = strdup(bufp)) == NULL) {
116			ewprintf("Out of memory");
117			return (FALSE);
118		}
119		free(tagsfn);
120		tagsfn = temp;
121		if (eyorn("Keep current list of tags table also") == FALSE) {
122			ewprintf("Starting a new list of tags table");
123			unloadtags();
124		}
125		loaded = FALSE;
126	}
127	return (TRUE);
128}
129
130/*
131 * Ask user for a tag while treating word at dot as default. Visit tags
132 * file if not yet done, load tags and jump to definition of the tag.
133 */
134int
135findtag(int f, int n)
136{
137	char utok[MAX_TOKEN], dtok[MAX_TOKEN];
138	char *tok, *bufp;
139	int  ret;
140
141	if (curtoken(f, n, dtok) == FALSE)
142		return (FALSE);
143
144	bufp = eread("Find tag (default %s) ", utok, MAX_TOKEN,
145	    EFNUL | EFNEW, dtok);
146
147	if (bufp == NULL)
148		return (ABORT);
149	else if	(bufp[0] == '\0')
150		tok = dtok;
151	else
152		tok = utok;
153
154	if (tok[0] == '\0') {
155		ewprintf("There is no default tag");
156		return (FALSE);
157	}
158
159	if (tagsfn == NULL)
160		if ((ret = tagsvisit(f, n)) != TRUE)
161			return (ret);
162	if (!loaded) {
163		if (loadtags(tagsfn) == FALSE) {
164			free(tagsfn);
165			tagsfn = NULL;
166			return (FALSE);
167		}
168		loaded = TRUE;
169	}
170	return pushtag(tok);
171}
172
173/*
174 * Free tags tree.
175 */
176void
177unloadtags(void)
178{
179	struct ctag *var, *nxt;
180
181	for (var = RB_MIN(tagtree, &tags); var != NULL; var = nxt) {
182		nxt = RB_NEXT(tagtree, &tags, var);
183		RB_REMOVE(tagtree, &tags, var);
184		/* line parsed with fparseln needs to be freed */
185		free(var->tag);
186		free(var);
187	}
188}
189
190/*
191 * Lookup tag passed in tree and if found, push current location and
192 * buffername onto stack, load the file with tag definition into a new
193 * buffer and position dot at the pattern.
194 */
195/*ARGSUSED */
196int
197pushtag(char *tok)
198{
199	struct ctag *res;
200	struct tagpos *s;
201	char bname[NFILEN];
202	int doto, dotline;
203
204	if ((res = searchtag(tok)) == NULL)
205		return (FALSE);
206
207	doto = curwp->w_doto;
208	dotline = curwp->w_dotline;
209	/* record absolute filenames. Fixes issues when mg's cwd is not the
210	 * same as buffer's directory.
211	 */
212	if (strlcpy(bname, curbp->b_cwd, sizeof(bname)) >= sizeof(bname)) {
213		    ewprintf("filename too long");
214		    return (FALSE);
215	}
216	if (strlcat(bname, curbp->b_bname, sizeof(bname)) >= sizeof(bname)) {
217		    ewprintf("filename too long");
218		    return (FALSE);
219	}
220
221	if (loadbuffer(res->fname) == FALSE)
222		return (FALSE);
223
224	if (searchpat(res->pat) == TRUE) {
225		if ((s = malloc(sizeof(struct tagpos))) == NULL) {
226			ewprintf("Out of memory");
227			return (FALSE);
228		}
229		if ((s->bname = strdup(bname)) == NULL) {
230			ewprintf("Out of memory");
231			free(s);
232			return (FALSE);
233		}
234		s->doto = doto;
235		s->dotline = dotline;
236		SLIST_INSERT_HEAD(&shead, s, entry);
237		return (TRUE);
238	} else {
239		ewprintf("%s: pattern not found", res->tag);
240		return (FALSE);
241	}
242	/* NOTREACHED */
243	return (FALSE);
244}
245
246/*
247 * If tag stack is not empty pop stack and jump to recorded buffer, dot.
248 */
249/* ARGSUSED */
250int
251poptag(int f, int n)
252{
253	struct line *dotp;
254	struct tagpos *s;
255
256	if (SLIST_EMPTY(&shead)) {
257		ewprintf("No previous location for find-tag invocation");
258		return (FALSE);
259	}
260	s = SLIST_FIRST(&shead);
261	SLIST_REMOVE_HEAD(&shead, entry);
262	if (loadbuffer(s->bname) == FALSE)
263		return (FALSE);
264	curwp->w_dotline = s->dotline;
265	curwp->w_doto = s->doto;
266
267	/* storing of dotp in tagpos wouldn't work out in cases when
268	 * that buffer is killed by user(dangling pointer). Explicitly
269	 * traverse till dotline for correct handling.
270	 */
271	dotp = curwp->w_bufp->b_headp;
272	while (s->dotline--)
273		dotp = dotp->l_fp;
274
275	curwp->w_dotp = dotp;
276	free(s->bname);
277	free(s);
278	return (TRUE);
279}
280
281/*
282 * Parse the tags file and construct the tags tree. Remove escape
283 * characters while parsing the file.
284 */
285int
286loadtags(const char *fn)
287{
288	char *l;
289	FILE *fd;
290
291	if ((fd = fopen(fn, "r")) == NULL) {
292		ewprintf("Unable to open tags file: %s", fn);
293		return (FALSE);
294	}
295	while ((l = fparseln(fd, NULL, NULL, "\\\\\0",
296	    FPARSELN_UNESCCONT | FPARSELN_UNESCREST)) != NULL) {
297		if (addctag(l) == FALSE) {
298			fclose(fd);
299			return (FALSE);
300		}
301	}
302	fclose(fd);
303	return (TRUE);
304}
305
306/*
307 * Cleanup and destroy tree and stack.
308 */
309void
310closetags(void)
311{
312	struct tagpos *s;
313
314	while (!SLIST_EMPTY(&shead)) {
315		s = SLIST_FIRST(&shead);
316		SLIST_REMOVE_HEAD(&shead, entry);
317		free(s->bname);
318		free(s);
319	}
320	unloadtags();
321	free(tagsfn);
322}
323
324/*
325 * Strip away any special characters in pattern.
326 * The pattern in ctags isn't a true regular expression. Its of the form
327 * /^xxx$/ or ?^xxx$? and in some cases the "$" would be missing. Strip
328 * the leading and trailing special characters so the pattern matching
329 * would be a simple string compare. Escape character is taken care by
330 * fparseln.
331 */
332char *
333strip(char *s, size_t len)
334{
335	/* first strip trailing special chars */
336	s[len - 1] = '\0';
337	if (s[len - 2] == '$')
338		s[len - 2] = '\0';
339
340	/* then strip leading special chars */
341	s++;
342	if (*s == '^')
343		s++;
344
345	return s;
346}
347
348/*
349 * tags line is of the format "<tag>\t<filename>\t<pattern>". Split them
350 * by replacing '\t' with '\0'. This wouldn't alter the size of malloc'ed
351 * l, and can be freed during cleanup.
352 */
353int
354addctag(char *l)
355{
356	struct ctag *t;
357
358	if ((t = malloc(sizeof(struct ctag))) == NULL) {
359		ewprintf("Out of memory");
360		return (FALSE);
361	}
362	t->tag = l;
363	if ((l = strchr(l, '\t')) == NULL)
364		goto cleanup;
365	*l++ = '\0';
366	t->fname = l;
367	if ((l = strchr(l, '\t')) == NULL)
368		goto cleanup;
369	*l++ = '\0';
370	if (*l == '\0')
371		goto cleanup;
372	t->pat = strip(l, strlen(l));
373	RB_INSERT(tagtree, &tags, t);
374	return (TRUE);
375cleanup:
376	free(t);
377	free(l);
378	return (TRUE);
379}
380
381/*
382 * Search through each line of buffer for pattern.
383 */
384int
385searchpat(char *pat)
386{
387	struct line *lp;
388	int dotline;
389	size_t plen;
390
391	plen = strlen(pat);
392	dotline = 1;
393	lp = lforw(curbp->b_headp);
394	while (lp != curbp->b_headp) {
395		if (ltext(lp) != NULL && plen <= llength(lp) &&
396		    (strncmp(pat, ltext(lp), plen) == 0)) {
397			curwp->w_doto = 0;
398			curwp->w_dotp = lp;
399			curwp->w_dotline = dotline;
400			return (TRUE);
401		} else {
402			lp = lforw(lp);
403			dotline++;
404		}
405	}
406	return (FALSE);
407}
408
409/*
410 * Return TRUE if dot is at beginning of a word or at beginning
411 * of line, else FALSE.
412 */
413int
414atbow(void)
415{
416	if (curwp->w_doto == 0)
417		return (TRUE);
418	if (ISWORD(curwp->w_dotp->l_text[curwp->w_doto]) &&
419	    !ISWORD(curwp->w_dotp->l_text[curwp->w_doto - 1]))
420	    	return (TRUE);
421	return (FALSE);
422}
423
424/*
425 * Extract the word at dot without changing dot position.
426 */
427int
428curtoken(int f, int n, char *token)
429{
430	struct line *odotp;
431	int odoto, tdoto, odotline, size, r;
432	char c;
433
434	/* Underscore character is to be treated as "inword" while
435	 * processing tokens unlike mg's default word traversal. Save
436	 * and restore it's cinfo value so that tag matching works for
437	 * identifier with underscore.
438	 */
439	c = cinfo['_'];
440	cinfo['_'] = _MG_W;
441
442	odotp = curwp->w_dotp;
443	odoto = curwp->w_doto;
444	odotline = curwp->w_dotline;
445
446	/* Move backword unless we are at the beginning of a word or at
447	 * beginning of line.
448	 */
449	if (!atbow())
450		if ((r = backword(f, n)) == FALSE)
451			goto cleanup;
452
453	tdoto = curwp->w_doto;
454
455	if ((r = forwword(f, n)) == FALSE)
456		goto cleanup;
457
458	/* strip away leading whitespace if any like emacs. */
459	while (ltext(curwp->w_dotp) &&
460	    isspace(curwp->w_dotp->l_text[tdoto]))
461		tdoto++;
462
463	size = curwp->w_doto - tdoto;
464	if (size <= 0 || size >= MAX_TOKEN ||
465	    ltext(curwp->w_dotp) == NULL) {
466		r = FALSE;
467		goto cleanup;
468	}
469	strncpy(token, ltext(curwp->w_dotp) + tdoto, size);
470	token[size] = '\0';
471	r = TRUE;
472
473cleanup:
474	cinfo['_'] = c;
475	curwp->w_dotp = odotp;
476	curwp->w_doto = odoto;
477	curwp->w_dotline = odotline;
478	return (r);
479}
480
481/*
482 * Search tagstree for a given token.
483 */
484struct ctag *
485searchtag(char *tok)
486{
487	struct ctag t, *res;
488
489	t.tag = tok;
490	if ((res = RB_FIND(tagtree, &tags, &t)) == NULL) {
491		ewprintf("No tag containing %s", tok);
492		return (NULL);
493	}
494	return res;
495}
496
497/*
498 * This is equivalent to filevisit from file.c.
499 * Look around to see if we can find the file in another buffer; if we
500 * can't find it, create a new buffer, read in the text, and switch to
501 * the new buffer. *scratch*, *grep*, *compile* needs to be handled
502 * differently from other buffers which have "filenames".
503 */
504int
505loadbuffer(char *bname)
506{
507	struct buffer *bufp;
508	char *adjf;
509
510	/* check for special buffers which begin with '*' */
511	if (bname[0] == '*') {
512		if ((bufp = bfind(bname, FALSE)) != NULL) {
513			curbp = bufp;
514			return (showbuffer(bufp, curwp, WFFULL));
515		} else {
516			return (FALSE);
517		}
518	} else {
519		if ((adjf = adjustname(bname, TRUE)) == NULL)
520			return (FALSE);
521		if ((bufp = findbuffer(adjf)) == NULL)
522			return (FALSE);
523	}
524	curbp = bufp;
525	if (showbuffer(bufp, curwp, WFFULL) != TRUE)
526		return (FALSE);
527	if (bufp->b_fname[0] == '\0') {
528		if (readin(adjf) != TRUE) {
529			killbuffer(bufp);
530			return (FALSE);
531		}
532	}
533	return (TRUE);
534}
535