[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.2

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