/* $OpenBSD: tags.c,v 1.5 2012/07/02 08:08:31 lum Exp $ */ /* * This file is in the public domain. * * Author: Sunil Nimmagadda */ #include #include #include #include #include #include #include #include #include #include "def.h" struct ctag; static int addctag(char *); static int atbow(void); void closetags(void); static int ctagcmp(struct ctag *, struct ctag *); static int loadbuffer(char *); static int loadtags(const char *); static int pushtag(char *); static int searchpat(char *); static struct ctag *searchtag(char *); static char *strip(char *, size_t); static void unloadtags(void); #define DEFAULTFN "tags" char *tagsfn = NULL; int loaded = FALSE; /* ctags(1) entries are parsed and maintained in a tree. */ struct ctag { RB_ENTRY(ctag) entry; char *tag; char *fname; char *pat; }; RB_HEAD(tagtree, ctag) tags = RB_INITIALIZER(&tags); RB_GENERATE(tagtree, ctag, entry, ctagcmp); struct tagpos { SLIST_ENTRY(tagpos) entry; int doto; int dotline; char *bname; }; SLIST_HEAD(tagstack, tagpos) shead = SLIST_HEAD_INITIALIZER(shead); int ctagcmp(struct ctag *s, struct ctag *t) { return strcmp(s->tag, t->tag); } /* * Record the filename that contain tags to be used while loading them * on first use. If a filename is already recorded, ask user to retain * already loaded tags (if any) and unload them if user chooses not to. */ /* ARGSUSED */ int tagsvisit(int f, int n) { char fname[NFILEN], *bufp, *temp; struct stat sb; if (getbufcwd(fname, sizeof(fname)) == FALSE) fname[0] = '\0'; if (strlcat(fname, DEFAULTFN, sizeof(fname)) >= sizeof(fname)) { ewprintf("Filename too long"); return (FALSE); } bufp = eread("visit tags table (default %s): ", fname, NFILEN, EFFILE | EFCR | EFNEW | EFDEF, DEFAULTFN); if (stat(bufp, &sb) == -1) { ewprintf("stat: %s", strerror(errno)); return (FALSE); } else if (S_ISREG(sb.st_mode) == 0) { ewprintf("Not a regular file"); return (FALSE); } else if (access(bufp, R_OK) == -1) { ewprintf("Cannot access file %s", bufp); return (FALSE); } if (tagsfn == NULL) { if (bufp == NULL) return (ABORT); else if (bufp[0] == '\0') { if ((tagsfn = strdup(fname)) == NULL) { ewprintf("Out of memory"); return (FALSE); } } else { /* bufp points to local variable, so duplicate. */ if ((tagsfn = strdup(bufp)) == NULL) { ewprintf("Out of memory"); return (FALSE); } } } else { if ((temp = strdup(bufp)) == NULL) { ewprintf("Out of memory"); return (FALSE); } free(tagsfn); tagsfn = temp; if (eyorn("Keep current list of tags table also") == FALSE) { ewprintf("Starting a new list of tags table"); unloadtags(); } loaded = FALSE; } return (TRUE); } /* * Ask user for a tag while treating word at dot as default. Visit tags * file if not yet done, load tags and jump to definition of the tag. */ int findtag(int f, int n) { char utok[MAX_TOKEN], dtok[MAX_TOKEN]; char *tok, *bufp; int ret; if (curtoken(f, n, dtok) == FALSE) return (FALSE); bufp = eread("Find tag (default %s) ", utok, MAX_TOKEN, EFNUL | EFNEW, dtok); if (bufp == NULL) return (ABORT); else if (bufp[0] == '\0') tok = dtok; else tok = utok; if (tok[0] == '\0') { ewprintf("There is no default tag"); return (FALSE); } if (tagsfn == NULL) if ((ret = tagsvisit(f, n)) != TRUE) return (ret); if (!loaded) { if (loadtags(tagsfn) == FALSE) { free(tagsfn); tagsfn = NULL; return (FALSE); } loaded = TRUE; } return pushtag(tok); } /* * Free tags tree. */ void unloadtags(void) { struct ctag *var, *nxt; for (var = RB_MIN(tagtree, &tags); var != NULL; var = nxt) { nxt = RB_NEXT(tagtree, &tags, var); RB_REMOVE(tagtree, &tags, var); /* line parsed with fparseln needs to be freed */ free(var->tag); free(var); } } /* * Lookup tag passed in tree and if found, push current location and * buffername onto stack, load the file with tag definition into a new * buffer and position dot at the pattern. */ /*ARGSUSED */ int pushtag(char *tok) { struct ctag *res; struct tagpos *s; char bname[NFILEN]; int doto, dotline; if ((res = searchtag(tok)) == NULL) return (FALSE); doto = curwp->w_doto; dotline = curwp->w_dotline; /* record absolute filenames. Fixes issues when mg's cwd is not the * same as buffer's directory. */ if (strlcpy(bname, curbp->b_cwd, sizeof(bname)) >= sizeof(bname)) { ewprintf("filename too long"); return (FALSE); } if (strlcat(bname, curbp->b_bname, sizeof(bname)) >= sizeof(bname)) { ewprintf("filename too long"); return (FALSE); } if (loadbuffer(res->fname) == FALSE) return (FALSE); if (searchpat(res->pat) == TRUE) { if ((s = malloc(sizeof(struct tagpos))) == NULL) { ewprintf("Out of memory"); return (FALSE); } if ((s->bname = strdup(bname)) == NULL) { ewprintf("Out of memory"); return (FALSE); } s->doto = doto; s->dotline = dotline; SLIST_INSERT_HEAD(&shead, s, entry); return (TRUE); } else { ewprintf("%s: pattern not found", res->tag); return (FALSE); } /* NOTREACHED */ return (FALSE); } /* * If tag stack is not empty pop stack and jump to recorded buffer, dot. */ /* ARGSUSED */ int poptag(int f, int n) { struct line *dotp; struct tagpos *s; if (SLIST_EMPTY(&shead)) { ewprintf("No previous location for find-tag invocation"); return (FALSE); } s = SLIST_FIRST(&shead); SLIST_REMOVE_HEAD(&shead, entry); if (loadbuffer(s->bname) == FALSE) return (FALSE); curwp->w_dotline = s->dotline; curwp->w_doto = s->doto; /* storing of dotp in tagpos wouldn't work out in cases when * that buffer is killed by user(dangling pointer). Explicitly * traverse till dotline for correct handling. */ dotp = curwp->w_bufp->b_headp; while (s->dotline--) dotp = dotp->l_fp; curwp->w_dotp = dotp; free(s->bname); free(s); return (TRUE); } /* * Parse the tags file and construct the tags tree. Remove escape * characters while parsing the file. */ int loadtags(const char *fn) { char *l; FILE *fd; if ((fd = fopen(fn, "r")) == NULL) { ewprintf("Unable to open tags file: %s", fn); return (FALSE); } while ((l = fparseln(fd, NULL, NULL, "\\\\\0", FPARSELN_UNESCCONT | FPARSELN_UNESCREST)) != NULL) { if (addctag(l) == FALSE) { fclose(fd); return (FALSE); } } fclose(fd); return (TRUE); } /* * Cleanup and destroy tree and stack. */ void closetags(void) { struct tagpos *s; while (!SLIST_EMPTY(&shead)) { s = SLIST_FIRST(&shead); SLIST_REMOVE_HEAD(&shead, entry); free(s->bname); free(s); } unloadtags(); free(tagsfn); } /* * Strip away any special characters in pattern. * The pattern in ctags isn't a true regular expression. Its of the form * /^xxx$/ or ?^xxx$? and in some cases the "$" would be missing. Strip * the leading and trailing special characters so the pattern matching * would be a simple string compare. Escape character is taken care by * fparseln. */ char * strip(char *s, size_t len) { /* first strip trailing special chars */ s[len - 1] = '\0'; if (s[len - 2] == '$') s[len - 2] = '\0'; /* then strip leading special chars */ s++; if (*s == '^') s++; return s; } /* * tags line is of the format "\t\t". Split them * by replacing '\t' with '\0'. This wouldn't alter the size of malloc'ed * l, and can be freed during cleanup. */ int addctag(char *l) { struct ctag *t; if ((t = malloc(sizeof(struct ctag))) == NULL) { ewprintf("Out of memory"); return (FALSE); } t->tag = l; if ((l = strchr(l, '\t')) == NULL) goto cleanup; *l++ = '\0'; t->fname = l; if ((l = strchr(l, '\t')) == NULL) goto cleanup; *l++ = '\0'; if (*l == '\0') goto cleanup; t->pat = strip(l, strlen(l)); RB_INSERT(tagtree, &tags, t); return (TRUE); cleanup: free(t); free(l); return (TRUE); } /* * Search through each line of buffer for pattern. */ int searchpat(char *pat) { struct line *lp; int dotline; size_t plen; plen = strlen(pat); dotline = 1; lp = lforw(curbp->b_headp); while (lp != curbp->b_headp) { if (ltext(lp) != NULL && plen <= llength(lp) && (strncmp(pat, ltext(lp), plen) == 0)) { curwp->w_doto = 0; curwp->w_dotp = lp; curwp->w_dotline = dotline; return (TRUE); } else { lp = lforw(lp); dotline++; } } return (FALSE); } /* * Return TRUE if dot is at beginning of a word or at beginning * of line, else FALSE. */ int atbow(void) { if (curwp->w_doto == 0) return (TRUE); if (ISWORD(curwp->w_dotp->l_text[curwp->w_doto]) && !ISWORD(curwp->w_dotp->l_text[curwp->w_doto - 1])) return (TRUE); return (FALSE); } /* * Extract the word at dot without changing dot position. */ int curtoken(int f, int n, char *token) { struct line *odotp; int odoto, tdoto, odotline, size, r; char c; /* Underscore character is to be treated as "inword" while * processing tokens unlike mg's default word traversal. Save * and restore it's cinfo value so that tag matching works for * identifier with underscore. */ c = cinfo['_']; cinfo['_'] = _MG_W; odotp = curwp->w_dotp; odoto = curwp->w_doto; odotline = curwp->w_dotline; /* Move backword unless we are at the beginning of a word or at * beginning of line. */ if (!atbow()) if ((r = backword(f, n)) == FALSE) goto cleanup; tdoto = curwp->w_doto; if ((r = forwword(f, n)) == FALSE) goto cleanup; /* strip away leading whitespace if any like emacs. */ while (ltext(curwp->w_dotp) && isspace(curwp->w_dotp->l_text[tdoto])) tdoto++; size = curwp->w_doto - tdoto; if (size <= 0 || size >= MAX_TOKEN || ltext(curwp->w_dotp) == NULL) { r = FALSE; goto cleanup; } strncpy(token, ltext(curwp->w_dotp) + tdoto, size); token[size] = '\0'; r = TRUE; cleanup: cinfo['_'] = c; curwp->w_dotp = odotp; curwp->w_doto = odoto; curwp->w_dotline = odotline; return (r); } /* * Search tagstree for a given token. */ struct ctag * searchtag(char *tok) { struct ctag t, *res; t.tag = tok; if ((res = RB_FIND(tagtree, &tags, &t)) == NULL) { ewprintf("No tag containing %s", tok); return (NULL); } return res; } /* * This is equivalent to filevisit from file.c. * Look around to see if we can find the file in another buffer; if we * can't find it, create a new buffer, read in the text, and switch to * the new buffer. *scratch*, *grep*, *compile* needs to be handled * differently from other buffers which have "filenames". */ int loadbuffer(char *bname) { struct buffer *bufp; char *adjf; /* check for special buffers which begin with '*' */ if (bname[0] == '*') { if ((bufp = bfind(bname, FALSE)) != NULL) { curbp = bufp; return (showbuffer(bufp, curwp, WFFULL)); } else { return (FALSE); } } else { if ((adjf = adjustname(bname, TRUE)) == NULL) return (FALSE); if ((bufp = findbuffer(adjf)) == NULL) return (FALSE); } curbp = bufp; if (showbuffer(bufp, curwp, WFFULL) != TRUE) return (FALSE); if (bufp->b_fname[0] == '\0') { if (readin(adjf) != TRUE) { killbuffer(bufp); return (FALSE); } } return (TRUE); }