1 /* $OpenBSD: tags.c,v 1.5 2012/07/02 08:08:31 lum Exp $ */
4 * This file is in the public domain.
6 * Author: Sunil Nimmagadda <sunil@sunilnimmagadda.com>
12 #include <sys/types.h>
24 static int addctag(char *);
25 static int atbow(void);
27 static int ctagcmp(struct ctag *, struct ctag *);
28 static int loadbuffer(char *);
29 static int loadtags(const char *);
30 static int pushtag(char *);
31 static int searchpat(char *);
32 static struct ctag *searchtag(char *);
33 static char *strip(char *, size_t);
34 static void unloadtags(void);
36 #define DEFAULTFN "tags"
41 /* ctags(1) entries are parsed and maintained in a tree. */
48 RB_HEAD(tagtree, ctag) tags = RB_INITIALIZER(&tags);
49 RB_GENERATE(tagtree, ctag, entry, ctagcmp);
52 SLIST_ENTRY(tagpos) entry;
57 SLIST_HEAD(tagstack, tagpos) shead = SLIST_HEAD_INITIALIZER(shead);
60 ctagcmp(struct ctag *s, struct ctag *t)
62 return strcmp(s->tag, t->tag);
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.
72 tagsvisit(int f, int n)
74 char fname[NFILEN], *bufp, *temp;
77 if (getbufcwd(fname, sizeof(fname)) == FALSE)
80 if (strlcat(fname, DEFAULTFN, sizeof(fname)) >= sizeof(fname)) {
81 ewprintf("Filename too long");
85 bufp = eread("visit tags table (default %s): ", fname,
86 NFILEN, EFFILE | EFCR | EFNEW | EFDEF, DEFAULTFN);
88 if (stat(bufp, &sb) == -1) {
89 ewprintf("stat: %s", strerror(errno));
91 } else if (S_ISREG(sb.st_mode) == 0) {
92 ewprintf("Not a regular file");
94 } else if (access(bufp, R_OK) == -1) {
95 ewprintf("Cannot access file %s", bufp);
102 else if (bufp[0] == '\0') {
103 if ((tagsfn = strdup(fname)) == NULL) {
104 ewprintf("Out of memory");
108 /* bufp points to local variable, so duplicate. */
109 if ((tagsfn = strdup(bufp)) == NULL) {
110 ewprintf("Out of memory");
115 if ((temp = strdup(bufp)) == NULL) {
116 ewprintf("Out of memory");
121 if (eyorn("Keep current list of tags table also") == FALSE) {
122 ewprintf("Starting a new list of tags table");
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.
135 findtag(int f, int n)
137 char utok[MAX_TOKEN], dtok[MAX_TOKEN];
141 if (curtoken(f, n, dtok) == FALSE)
144 bufp = eread("Find tag (default %s) ", utok, MAX_TOKEN,
145 EFNUL | EFNEW, dtok);
149 else if (bufp[0] == '\0')
154 if (tok[0] == '\0') {
155 ewprintf("There is no default tag");
160 if ((ret = tagsvisit(f, n)) != TRUE)
163 if (loadtags(tagsfn) == FALSE) {
179 struct ctag *var, *nxt;
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 */
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.
204 if ((res = searchtag(tok)) == NULL)
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.
212 if (strlcpy(bname, curbp->b_cwd, sizeof(bname)) >= sizeof(bname)) {
213 ewprintf("filename too long");
216 if (strlcat(bname, curbp->b_bname, sizeof(bname)) >= sizeof(bname)) {
217 ewprintf("filename too long");
221 if (loadbuffer(res->fname) == FALSE)
224 if (searchpat(res->pat) == TRUE) {
225 if ((s = malloc(sizeof(struct tagpos))) == NULL) {
226 ewprintf("Out of memory");
229 if ((s->bname = strdup(bname)) == NULL) {
230 ewprintf("Out of memory");
234 s->dotline = dotline;
235 SLIST_INSERT_HEAD(&shead, s, entry);
238 ewprintf("%s: pattern not found", res->tag);
246 * If tag stack is not empty pop stack and jump to recorded buffer, dot.
255 if (SLIST_EMPTY(&shead)) {
256 ewprintf("No previous location for find-tag invocation");
259 s = SLIST_FIRST(&shead);
260 SLIST_REMOVE_HEAD(&shead, entry);
261 if (loadbuffer(s->bname) == FALSE)
263 curwp->w_dotline = s->dotline;
264 curwp->w_doto = s->doto;
266 /* storing of dotp in tagpos wouldn't work out in cases when
267 * that buffer is killed by user(dangling pointer). Explicitly
268 * traverse till dotline for correct handling.
270 dotp = curwp->w_bufp->b_headp;
274 curwp->w_dotp = dotp;
281 * Parse the tags file and construct the tags tree. Remove escape
282 * characters while parsing the file.
285 loadtags(const char *fn)
290 if ((fd = fopen(fn, "r")) == NULL) {
291 ewprintf("Unable to open tags file: %s", fn);
294 while ((l = fparseln(fd, NULL, NULL, "\\\\\0",
295 FPARSELN_UNESCCONT | FPARSELN_UNESCREST)) != NULL) {
296 if (addctag(l) == FALSE) {
306 * Cleanup and destroy tree and stack.
313 while (!SLIST_EMPTY(&shead)) {
314 s = SLIST_FIRST(&shead);
315 SLIST_REMOVE_HEAD(&shead, entry);
324 * Strip away any special characters in pattern.
325 * The pattern in ctags isn't a true regular expression. Its of the form
326 * /^xxx$/ or ?^xxx$? and in some cases the "$" would be missing. Strip
327 * the leading and trailing special characters so the pattern matching
328 * would be a simple string compare. Escape character is taken care by
332 strip(char *s, size_t len)
334 /* first strip trailing special chars */
336 if (s[len - 2] == '$')
339 /* then strip leading special chars */
348 * tags line is of the format "<tag>\t<filename>\t<pattern>". Split them
349 * by replacing '\t' with '\0'. This wouldn't alter the size of malloc'ed
350 * l, and can be freed during cleanup.
357 if ((t = malloc(sizeof(struct ctag))) == NULL) {
358 ewprintf("Out of memory");
362 if ((l = strchr(l, '\t')) == NULL)
366 if ((l = strchr(l, '\t')) == NULL)
371 t->pat = strip(l, strlen(l));
372 RB_INSERT(tagtree, &tags, t);
381 * Search through each line of buffer for pattern.
392 lp = lforw(curbp->b_headp);
393 while (lp != curbp->b_headp) {
394 if (ltext(lp) != NULL && plen <= llength(lp) &&
395 (strncmp(pat, ltext(lp), plen) == 0)) {
398 curwp->w_dotline = dotline;
409 * Return TRUE if dot is at beginning of a word or at beginning
410 * of line, else FALSE.
415 if (curwp->w_doto == 0)
417 if (ISWORD(curwp->w_dotp->l_text[curwp->w_doto]) &&
418 !ISWORD(curwp->w_dotp->l_text[curwp->w_doto - 1]))
424 * Extract the word at dot without changing dot position.
427 curtoken(int f, int n, char *token)
430 int odoto, tdoto, odotline, size, r;
433 /* Underscore character is to be treated as "inword" while
434 * processing tokens unlike mg's default word traversal. Save
435 * and restore it's cinfo value so that tag matching works for
436 * identifier with underscore.
441 odotp = curwp->w_dotp;
442 odoto = curwp->w_doto;
443 odotline = curwp->w_dotline;
445 /* Move backword unless we are at the beginning of a word or at
449 if ((r = backword(f, n)) == FALSE)
452 tdoto = curwp->w_doto;
454 if ((r = forwword(f, n)) == FALSE)
457 /* strip away leading whitespace if any like emacs. */
458 while (ltext(curwp->w_dotp) &&
459 isspace(curwp->w_dotp->l_text[tdoto]))
462 size = curwp->w_doto - tdoto;
463 if (size <= 0 || size >= MAX_TOKEN ||
464 ltext(curwp->w_dotp) == NULL) {
468 strncpy(token, ltext(curwp->w_dotp) + tdoto, size);
474 curwp->w_dotp = odotp;
475 curwp->w_doto = odoto;
476 curwp->w_dotline = odotline;
481 * Search tagstree for a given token.
489 if ((res = RB_FIND(tagtree, &tags, &t)) == NULL) {
490 ewprintf("No tag containing %s", tok);
497 * This is equivalent to filevisit from file.c.
498 * Look around to see if we can find the file in another buffer; if we
499 * can't find it, create a new buffer, read in the text, and switch to
500 * the new buffer. *scratch*, *grep*, *compile* needs to be handled
501 * differently from other buffers which have "filenames".
504 loadbuffer(char *bname)
509 /* check for special buffers which begin with '*' */
510 if (bname[0] == '*') {
511 if ((bufp = bfind(bname, FALSE)) != NULL) {
513 return (showbuffer(bufp, curwp, WFFULL));
518 if ((adjf = adjustname(bname, TRUE)) == NULL)
520 if ((bufp = findbuffer(adjf)) == NULL)
524 if (showbuffer(bufp, curwp, WFFULL) != TRUE)
526 if (bufp->b_fname[0] == '\0') {
527 if (readin(adjf) != TRUE) {