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: }