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