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