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