[BACK]Return to tags.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / mg

Annotation of src/usr.bin/mg/tags.c, Revision 1.7

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