Annotation of src/usr.bin/mg/tags.c, Revision 1.8
1.8 ! lum 1: /* $OpenBSD: tags.c,v 1.7 2014/03/06 14:51:48 jasper 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:
149: if (curtoken(f, n, dtok) == FALSE)
150: return (FALSE);
151:
152: bufp = eread("Find tag (default %s) ", utok, MAX_TOKEN,
153: EFNUL | EFNEW, dtok);
154:
155: if (bufp == NULL)
156: return (ABORT);
157: else if (bufp[0] == '\0')
158: tok = dtok;
159: else
160: tok = utok;
161:
162: if (tok[0] == '\0') {
1.8 ! lum 163: dobeep();
1.1 lum 164: ewprintf("There is no default tag");
165: return (FALSE);
166: }
167:
168: if (tagsfn == NULL)
169: if ((ret = tagsvisit(f, n)) != TRUE)
170: return (ret);
171: if (!loaded) {
172: if (loadtags(tagsfn) == FALSE) {
173: free(tagsfn);
174: tagsfn = NULL;
175: return (FALSE);
176: }
177: loaded = TRUE;
178: }
179: return pushtag(tok);
180: }
181:
182: /*
183: * Free tags tree.
184: */
185: void
186: unloadtags(void)
187: {
188: struct ctag *var, *nxt;
189:
190: for (var = RB_MIN(tagtree, &tags); var != NULL; var = nxt) {
191: nxt = RB_NEXT(tagtree, &tags, var);
192: RB_REMOVE(tagtree, &tags, var);
193: /* line parsed with fparseln needs to be freed */
194: free(var->tag);
195: free(var);
196: }
197: }
198:
199: /*
200: * Lookup tag passed in tree and if found, push current location and
201: * buffername onto stack, load the file with tag definition into a new
202: * buffer and position dot at the pattern.
203: */
204: /*ARGSUSED */
205: int
206: pushtag(char *tok)
207: {
208: struct ctag *res;
209: struct tagpos *s;
1.2 lum 210: char bname[NFILEN];
1.1 lum 211: int doto, dotline;
212:
213: if ((res = searchtag(tok)) == NULL)
214: return (FALSE);
215:
216: doto = curwp->w_doto;
217: dotline = curwp->w_dotline;
1.2 lum 218: /* record absolute filenames. Fixes issues when mg's cwd is not the
219: * same as buffer's directory.
220: */
221: if (strlcpy(bname, curbp->b_cwd, sizeof(bname)) >= sizeof(bname)) {
1.8 ! lum 222: dobeep();
! 223: ewprintf("filename too long");
! 224: return (FALSE);
1.2 lum 225: }
226: if (strlcat(bname, curbp->b_bname, sizeof(bname)) >= sizeof(bname)) {
1.8 ! lum 227: dobeep();
! 228: ewprintf("filename too long");
! 229: return (FALSE);
1.2 lum 230: }
1.1 lum 231:
232: if (loadbuffer(res->fname) == FALSE)
233: return (FALSE);
234:
235: if (searchpat(res->pat) == TRUE) {
236: if ((s = malloc(sizeof(struct tagpos))) == NULL) {
1.8 ! lum 237: dobeep();
1.1 lum 238: ewprintf("Out of memory");
239: return (FALSE);
240: }
241: if ((s->bname = strdup(bname)) == NULL) {
1.8 ! lum 242: dobeep();
1.2 lum 243: ewprintf("Out of memory");
1.6 jasper 244: free(s);
1.2 lum 245: return (FALSE);
1.1 lum 246: }
247: s->doto = doto;
248: s->dotline = dotline;
249: SLIST_INSERT_HEAD(&shead, s, entry);
250: return (TRUE);
251: } else {
1.8 ! lum 252: dobeep();
1.1 lum 253: ewprintf("%s: pattern not found", res->tag);
254: return (FALSE);
255: }
256: /* NOTREACHED */
257: return (FALSE);
258: }
259:
260: /*
261: * If tag stack is not empty pop stack and jump to recorded buffer, dot.
262: */
263: /* ARGSUSED */
264: int
265: poptag(int f, int n)
266: {
267: struct line *dotp;
268: struct tagpos *s;
269:
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;
281:
282: /* storing of dotp in tagpos wouldn't work out in cases when
283: * that buffer is killed by user(dangling pointer). Explicitly
284: * traverse till dotline for correct handling.
285: */
286: dotp = curwp->w_bufp->b_headp;
287: while (s->dotline--)
288: dotp = dotp->l_fp;
289:
290: curwp->w_dotp = dotp;
291: free(s->bname);
292: free(s);
293: return (TRUE);
294: }
295:
296: /*
297: * Parse the tags file and construct the tags tree. Remove escape
298: * characters while parsing the file.
299: */
300: int
301: loadtags(const char *fn)
302: {
303: char *l;
304: FILE *fd;
305:
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: {
328: struct tagpos *s;
329:
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
343: * /^xxx$/ or ?^xxx$? and in some cases the "$" would be missing. Strip
344: * the leading and trailing special characters so the pattern matching
345: * would be a simple string compare. Escape character is taken care by
346: * fparseln.
347: */
348: char *
349: strip(char *s, size_t len)
350: {
351: /* first strip trailing special chars */
352: s[len - 1] = '\0';
353: if (s[len - 2] == '$')
354: s[len - 2] = '\0';
355:
356: /* then strip leading special chars */
357: s++;
358: if (*s == '^')
359: s++;
360:
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
370: addctag(char *l)
371: {
372: struct ctag *t;
373:
374: if ((t = malloc(sizeof(struct ctag))) == NULL) {
1.8 ! lum 375: dobeep();
1.1 lum 376: ewprintf("Out of memory");
377: return (FALSE);
378: }
379: t->tag = l;
380: if ((l = strchr(l, '\t')) == NULL)
381: goto cleanup;
382: *l++ = '\0';
383: t->fname = l;
384: if ((l = strchr(l, '\t')) == NULL)
385: goto cleanup;
386: *l++ = '\0';
387: if (*l == '\0')
388: goto cleanup;
389: t->pat = strip(l, strlen(l));
390: RB_INSERT(tagtree, &tags, t);
391: return (TRUE);
392: cleanup:
393: free(t);
394: free(l);
395: return (TRUE);
396: }
397:
398: /*
399: * Search through each line of buffer for pattern.
400: */
401: int
402: searchpat(char *pat)
403: {
404: struct line *lp;
405: int dotline;
406: size_t plen;
407:
408: plen = strlen(pat);
409: dotline = 1;
410: lp = lforw(curbp->b_headp);
411: while (lp != curbp->b_headp) {
412: if (ltext(lp) != NULL && plen <= llength(lp) &&
413: (strncmp(pat, ltext(lp), plen) == 0)) {
414: curwp->w_doto = 0;
415: curwp->w_dotp = lp;
416: curwp->w_dotline = dotline;
417: return (TRUE);
418: } else {
419: lp = lforw(lp);
420: dotline++;
421: }
422: }
423: return (FALSE);
424: }
425:
426: /*
427: * Return TRUE if dot is at beginning of a word or at beginning
428: * of line, else FALSE.
429: */
430: int
431: atbow(void)
432: {
433: if (curwp->w_doto == 0)
434: return (TRUE);
435: if (ISWORD(curwp->w_dotp->l_text[curwp->w_doto]) &&
436: !ISWORD(curwp->w_dotp->l_text[curwp->w_doto - 1]))
437: return (TRUE);
438: return (FALSE);
439: }
440:
441: /*
442: * Extract the word at dot without changing dot position.
443: */
444: int
445: curtoken(int f, int n, char *token)
446: {
447: struct line *odotp;
448: int odoto, tdoto, odotline, size, r;
449: char c;
450:
451: /* Underscore character is to be treated as "inword" while
452: * processing tokens unlike mg's default word traversal. Save
453: * and restore it's cinfo value so that tag matching works for
454: * identifier with underscore.
455: */
456: c = cinfo['_'];
457: cinfo['_'] = _MG_W;
458:
459: odotp = curwp->w_dotp;
460: odoto = curwp->w_doto;
461: odotline = curwp->w_dotline;
462:
463: /* Move backword unless we are at the beginning of a word or at
464: * beginning of line.
465: */
466: if (!atbow())
467: if ((r = backword(f, n)) == FALSE)
468: goto cleanup;
469:
470: tdoto = curwp->w_doto;
471:
472: if ((r = forwword(f, n)) == FALSE)
473: goto cleanup;
474:
475: /* strip away leading whitespace if any like emacs. */
476: while (ltext(curwp->w_dotp) &&
477: isspace(curwp->w_dotp->l_text[tdoto]))
478: tdoto++;
479:
480: size = curwp->w_doto - tdoto;
481: if (size <= 0 || size >= MAX_TOKEN ||
482: ltext(curwp->w_dotp) == NULL) {
483: r = FALSE;
484: goto cleanup;
485: }
486: strncpy(token, ltext(curwp->w_dotp) + tdoto, size);
487: token[size] = '\0';
488: r = TRUE;
489:
490: cleanup:
491: cinfo['_'] = c;
492: curwp->w_dotp = odotp;
493: curwp->w_doto = odoto;
494: curwp->w_dotline = odotline;
495: return (r);
496: }
497:
498: /*
499: * Search tagstree for a given token.
500: */
501: struct ctag *
502: searchtag(char *tok)
503: {
504: struct ctag t, *res;
505:
506: t.tag = tok;
507: if ((res = RB_FIND(tagtree, &tags, &t)) == NULL) {
1.8 ! lum 508: dobeep();
1.1 lum 509: ewprintf("No tag containing %s", tok);
510: return (NULL);
511: }
512: return res;
513: }
514:
515: /*
516: * This is equivalent to filevisit from file.c.
517: * Look around to see if we can find the file in another buffer; if we
518: * can't find it, create a new buffer, read in the text, and switch to
519: * the new buffer. *scratch*, *grep*, *compile* needs to be handled
520: * differently from other buffers which have "filenames".
521: */
522: int
523: loadbuffer(char *bname)
524: {
525: struct buffer *bufp;
526: char *adjf;
527:
528: /* check for special buffers which begin with '*' */
529: if (bname[0] == '*') {
530: if ((bufp = bfind(bname, FALSE)) != NULL) {
531: curbp = bufp;
532: return (showbuffer(bufp, curwp, WFFULL));
533: } else {
534: return (FALSE);
535: }
536: } else {
537: if ((adjf = adjustname(bname, TRUE)) == NULL)
538: return (FALSE);
539: if ((bufp = findbuffer(adjf)) == NULL)
540: return (FALSE);
541: }
542: curbp = bufp;
543: if (showbuffer(bufp, curwp, WFFULL) != TRUE)
544: return (FALSE);
545: if (bufp->b_fname[0] == '\0') {
546: if (readin(adjf) != TRUE) {
547: killbuffer(bufp);
548: return (FALSE);
549: }
550: }
551: return (TRUE);
552: }