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