Changeset View
Changeset View
Standalone View
Standalone View
contrib/mg/tags.c
- This file was added.
| /* $OpenBSD: tags.c,v 1.16 2017/08/06 04:39:45 bcallah Exp $ */ | |||||
| /* | |||||
| * This file is in the public domain. | |||||
| * | |||||
| * Author: Sunil Nimmagadda <sunil@openbsd.org> | |||||
| */ | |||||
| #include <sys/queue.h> | |||||
| #include <sys/stat.h> | |||||
| #if defined(__linux__) || defined(__DragonFly__) || defined(__APPLE__) | |||||
| #include "tree.h" | |||||
| #else | |||||
| #include <sys/tree.h> | |||||
| #endif | |||||
| #include <sys/types.h> | |||||
| #include <ctype.h> | |||||
| #include <err.h> | |||||
| #include <errno.h> | |||||
| #include <signal.h> | |||||
| #include <stdio.h> | |||||
| #include <stdlib.h> | |||||
| #include <string.h> | |||||
| #include <unistd.h> | |||||
| #if defined(__linux__) || defined(__CYGWIN__) | |||||
| #include "util.h" | |||||
| #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) | |||||
| #include <util.h> | |||||
| #else | |||||
| #include <libutil.h> | |||||
| #endif | |||||
| #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)) { | |||||
| dobeep(); | |||||
| ewprintf("Filename too long"); | |||||
| return (FALSE); | |||||
| } | |||||
| bufp = eread("Visit tags table (default %s): ", fname, | |||||
| NFILEN, EFFILE | EFCR | EFNEW | EFDEF, DEFAULTFN); | |||||
| if (bufp == NULL) | |||||
| return (ABORT); | |||||
| if (stat(bufp, &sb) == -1) { | |||||
| dobeep(); | |||||
| ewprintf("stat: %s", strerror(errno)); | |||||
| return (FALSE); | |||||
| } else if (S_ISREG(sb.st_mode) == 0) { | |||||
| dobeep(); | |||||
| ewprintf("Not a regular file"); | |||||
| return (FALSE); | |||||
| } else if (access(bufp, R_OK) == -1) { | |||||
| dobeep(); | |||||
| ewprintf("Cannot access file %s", bufp); | |||||
| return (FALSE); | |||||
| } | |||||
| if (tagsfn == NULL) { | |||||
| if (bufp[0] == '\0') { | |||||
| if ((tagsfn = strdup(fname)) == NULL) { | |||||
| dobeep(); | |||||
| ewprintf("Out of memory"); | |||||
| return (FALSE); | |||||
| } | |||||
| } else { | |||||
| /* bufp points to local variable, so duplicate. */ | |||||
| if ((tagsfn = strdup(bufp)) == NULL) { | |||||
| dobeep(); | |||||
| ewprintf("Out of memory"); | |||||
| return (FALSE); | |||||
| } | |||||
| } | |||||
| } else { | |||||
| if ((temp = strdup(bufp)) == NULL) { | |||||
| dobeep(); | |||||
| 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) { | |||||
| dtok[0] = '\0'; | |||||
| bufp = eread("Find tag: ", utok, MAX_TOKEN, EFNUL | EFNEW); | |||||
| } else | |||||
| 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') { | |||||
| dobeep(); | |||||
| 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)) { | |||||
| dobeep(); | |||||
| ewprintf("filename too long"); | |||||
| return (FALSE); | |||||
| } | |||||
| if (strlcat(bname, curbp->b_bname, sizeof(bname)) >= sizeof(bname)) { | |||||
| dobeep(); | |||||
| 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) { | |||||
| dobeep(); | |||||
| ewprintf("Out of memory"); | |||||
| return (FALSE); | |||||
| } | |||||
| if ((s->bname = strdup(bname)) == NULL) { | |||||
| dobeep(); | |||||
| ewprintf("Out of memory"); | |||||
| free(s); | |||||
| return (FALSE); | |||||
| } | |||||
| s->doto = doto; | |||||
| s->dotline = dotline; | |||||
| SLIST_INSERT_HEAD(&shead, s, entry); | |||||
| return (TRUE); | |||||
| } else { | |||||
| dobeep(); | |||||
| 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)) { | |||||
| dobeep(); | |||||
| 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) { | |||||
| dobeep(); | |||||
| 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 "<tag>\t<filename>\t<pattern>". 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) { | |||||
| dobeep(); | |||||
| 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 (FALSE); | |||||
| } | |||||
| /* | |||||
| * Search through each line of buffer for pattern. | |||||
| */ | |||||
| int | |||||
| searchpat(char *s_pat) | |||||
| { | |||||
| struct line *lp; | |||||
| int dotline; | |||||
| size_t plen; | |||||
| plen = strlen(s_pat); | |||||
| dotline = 1; | |||||
| lp = lforw(curbp->b_headp); | |||||
| while (lp != curbp->b_headp) { | |||||
| if (ltext(lp) != NULL && plen <= llength(lp) && | |||||
| (strncmp(s_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(lgetc(curwp->w_dotp, 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) { | |||||
| dobeep(); | |||||
| 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); | |||||
| } | |||||