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