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