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

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