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

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