Annotation of src/usr.bin/mg/tags.c, Revision 1.2
1.1 lum 1: /*
2: * Copyright (c) 2011 Sunil Nimmagadda <sunil@sunilnimmagadda.com>
3: *
4: * Permission to use, copy, modify, and distribute this software for any
5: * purpose with or without fee is hereby granted, provided that the above
6: * copyright notice and this permission notice appear in all copies.
7: *
8: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15: */
16:
17: #include <sys/queue.h>
18: #include <sys/stat.h>
19: #include <sys/tree.h>
20: #include <sys/types.h>
21:
22: #include <ctype.h>
23: #include <err.h>
24: #include <stdlib.h>
25: #include <string.h>
26: #include <util.h>
27:
28: #include "def.h"
29:
30: struct ctag;
31:
32: static int addctag(char *);
33: static int atbow(void);
34: void closetags(void);
35: static int ctagcmp(struct ctag *, struct ctag *);
36: static int curtoken(int, int, char *);
37: static int loadbuffer(char *);
38: static int loadtags(const char *);
39: static int pushtag(char *);
40: static int searchpat(char *);
41: static struct ctag *searchtag(char *);
42: static char *strip(char *, size_t);
43: static void unloadtags(void);
44:
45: #define MAX_TOKEN 64
46: #define DEFAULTFN "tags"
47:
48: char *tagsfn = NULL;
49: int loaded = FALSE;
50:
51: /* ctags(1) entries are parsed and maintained in a tree. */
52: struct ctag {
53: RB_ENTRY(ctag) entry;
54: char *tag;
55: char *fname;
56: char *pat;
57: };
58: RB_HEAD(tagtree, ctag) tags = RB_INITIALIZER(&tags);
59: RB_GENERATE(tagtree, ctag, entry, ctagcmp);
60:
61: struct tagpos {
62: SLIST_ENTRY(tagpos) entry;
63: int doto;
64: int dotline;
65: char *bname;
66: };
67: SLIST_HEAD(tagstack, tagpos) shead = SLIST_HEAD_INITIALIZER(shead);
68:
69: int
70: ctagcmp(struct ctag *s, struct ctag *t)
71: {
72: return strcmp(s->tag, t->tag);
73: }
74:
75: /*
76: * Record the filename that contain tags to be used while loading them
77: * on first use. If a filename is already recorded, ask user to retain
78: * already loaded tags (if any) and unload them if user chooses not to.
79: */
80: /* ARGSUSED */
81: int
82: tagsvisit(int f, int n)
83: {
84: char fname[NFILEN], *bufp, *temp;
85: struct stat sb;
86:
87: if (getbufcwd(fname, sizeof(fname)) == FALSE)
88: fname[0] = '\0';
89:
90: if (strlcat(fname, DEFAULTFN, sizeof(fname)) >= sizeof(fname)) {
91: ewprintf("Filename too long");
92: return (FALSE);
93: }
94:
95: bufp = eread("visit tags table (default %s): ", fname,
96: NFILEN, EFFILE | EFCR | EFNEW | EFDEF, DEFAULTFN);
97:
98: if (stat(bufp, &sb) == -1) {
99: ewprintf("stat: %s", strerror(errno));
100: return (FALSE);
101: } else if (S_ISREG(sb.st_mode) == 0) {
102: ewprintf("Not a regular file");
103: return (FALSE);
104: } else if (access(bufp, R_OK) == -1) {
105: ewprintf("Cannot access file %s", bufp);
106: return (FALSE);
107: }
108:
109: if (tagsfn == NULL) {
110: if (bufp == NULL)
111: return (ABORT);
112: else if (bufp[0] == '\0') {
113: if ((tagsfn = strdup(fname)) == NULL) {
114: ewprintf("Out of memory");
115: return (FALSE);
116: }
117: } else {
118: /* bufp points to local variable, so duplicate. */
119: if ((tagsfn = strdup(bufp)) == NULL) {
120: ewprintf("Out of memory");
121: return (FALSE);
122: }
123: }
124: } else {
125: if ((temp = strdup(bufp)) == NULL) {
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:
151: if (curtoken(f, n, dtok) == FALSE)
152: return (FALSE);
153:
154: bufp = eread("Find tag (default %s) ", utok, MAX_TOKEN,
155: EFNUL | EFNEW, dtok);
156:
157: if (bufp == NULL)
158: return (ABORT);
159: else if (bufp[0] == '\0')
160: tok = dtok;
161: else
162: tok = utok;
163:
164: if (tok[0] == '\0') {
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)) {
! 223: ewprintf("filename too long");
! 224: return (FALSE);
! 225: }
! 226: if (strlcat(bname, curbp->b_bname, sizeof(bname)) >= sizeof(bname)) {
! 227: ewprintf("filename too long");
! 228: return (FALSE);
! 229: }
1.1 lum 230:
231: if (loadbuffer(res->fname) == FALSE)
232: return (FALSE);
233:
234: if (searchpat(res->pat) == TRUE) {
235: if ((s = malloc(sizeof(struct tagpos))) == NULL) {
236: ewprintf("Out of memory");
237: return (FALSE);
238: }
239: if ((s->bname = strdup(bname)) == NULL) {
1.2 ! lum 240: ewprintf("Out of memory");
! 241: return (FALSE);
1.1 lum 242: }
243: s->doto = doto;
244: s->dotline = dotline;
245: SLIST_INSERT_HEAD(&shead, s, entry);
246: return (TRUE);
247: } else {
248: ewprintf("%s: pattern not found", res->tag);
249: return (FALSE);
250: }
251: /* NOTREACHED */
252: return (FALSE);
253: }
254:
255: /*
256: * If tag stack is not empty pop stack and jump to recorded buffer, dot.
257: */
258: /* ARGSUSED */
259: int
260: poptag(int f, int n)
261: {
262: struct line *dotp;
263: struct tagpos *s;
264:
265: if (SLIST_EMPTY(&shead)) {
266: ewprintf("No previous location for find-tag invocation");
267: return (FALSE);
268: }
269: s = SLIST_FIRST(&shead);
270: SLIST_REMOVE_HEAD(&shead, entry);
271: if (loadbuffer(s->bname) == FALSE)
272: return (FALSE);
273: curwp->w_dotline = s->dotline;
274: curwp->w_doto = s->doto;
275:
276: /* storing of dotp in tagpos wouldn't work out in cases when
277: * that buffer is killed by user(dangling pointer). Explicitly
278: * traverse till dotline for correct handling.
279: */
280: dotp = curwp->w_bufp->b_headp;
281: while (s->dotline--)
282: dotp = dotp->l_fp;
283:
284: curwp->w_dotp = dotp;
285: free(s->bname);
286: free(s);
287: return (TRUE);
288: }
289:
290: /*
291: * Parse the tags file and construct the tags tree. Remove escape
292: * characters while parsing the file.
293: */
294: int
295: loadtags(const char *fn)
296: {
297: char *l;
298: FILE *fd;
299:
300: if ((fd = fopen(fn, "r")) == NULL) {
301: ewprintf("Unable to open tags file: %s", fn);
302: return (FALSE);
303: }
304: while ((l = fparseln(fd, NULL, NULL, "\\\\\0",
305: FPARSELN_UNESCCONT | FPARSELN_UNESCREST)) != NULL) {
306: if (addctag(l) == FALSE) {
307: fclose(fd);
308: return (FALSE);
309: }
310: }
311: fclose(fd);
312: return (TRUE);
313: }
314:
315: /*
316: * Cleanup and destroy tree and stack.
317: */
318: void
319: closetags(void)
320: {
321: struct tagpos *s;
322:
323: while (!SLIST_EMPTY(&shead)) {
324: s = SLIST_FIRST(&shead);
325: SLIST_REMOVE_HEAD(&shead, entry);
326: free(s->bname);
327: free(s);
328: }
329: unloadtags();
330: free(tagsfn);
331: }
332:
333: /*
334: * Strip away any special characters in pattern.
335: * The pattern in ctags isn't a true regular expression. Its of the form
336: * /^xxx$/ or ?^xxx$? and in some cases the "$" would be missing. Strip
337: * the leading and trailing special characters so the pattern matching
338: * would be a simple string compare. Escape character is taken care by
339: * fparseln.
340: */
341: char *
342: strip(char *s, size_t len)
343: {
344: /* first strip trailing special chars */
345: s[len - 1] = '\0';
346: if (s[len - 2] == '$')
347: s[len - 2] = '\0';
348:
349: /* then strip leading special chars */
350: s++;
351: if (*s == '^')
352: s++;
353:
354: return s;
355: }
356:
357: /*
358: * tags line is of the format "<tag>\t<filename>\t<pattern>". Split them
359: * by replacing '\t' with '\0'. This wouldn't alter the size of malloc'ed
360: * l, and can be freed during cleanup.
361: */
362: int
363: addctag(char *l)
364: {
365: struct ctag *t;
366:
367: if ((t = malloc(sizeof(struct ctag))) == NULL) {
368: ewprintf("Out of memory");
369: return (FALSE);
370: }
371: t->tag = l;
372: if ((l = strchr(l, '\t')) == NULL)
373: goto cleanup;
374: *l++ = '\0';
375: t->fname = l;
376: if ((l = strchr(l, '\t')) == NULL)
377: goto cleanup;
378: *l++ = '\0';
379: if (*l == '\0')
380: goto cleanup;
381: t->pat = strip(l, strlen(l));
382: RB_INSERT(tagtree, &tags, t);
383: return (TRUE);
384: cleanup:
385: free(t);
386: free(l);
387: return (TRUE);
388: }
389:
390: /*
391: * Search through each line of buffer for pattern.
392: */
393: int
394: searchpat(char *pat)
395: {
396: struct line *lp;
397: int dotline;
398: size_t plen;
399:
400: plen = strlen(pat);
401: dotline = 1;
402: lp = lforw(curbp->b_headp);
403: while (lp != curbp->b_headp) {
404: if (ltext(lp) != NULL && plen <= llength(lp) &&
405: (strncmp(pat, ltext(lp), plen) == 0)) {
406: curwp->w_doto = 0;
407: curwp->w_dotp = lp;
408: curwp->w_dotline = dotline;
409: return (TRUE);
410: } else {
411: lp = lforw(lp);
412: dotline++;
413: }
414: }
415: return (FALSE);
416: }
417:
418: /*
419: * Return TRUE if dot is at beginning of a word or at beginning
420: * of line, else FALSE.
421: */
422: int
423: atbow(void)
424: {
425: if (curwp->w_doto == 0)
426: return (TRUE);
427: if (ISWORD(curwp->w_dotp->l_text[curwp->w_doto]) &&
428: !ISWORD(curwp->w_dotp->l_text[curwp->w_doto - 1]))
429: return (TRUE);
430: return (FALSE);
431: }
432:
433: /*
434: * Extract the word at dot without changing dot position.
435: */
436: int
437: curtoken(int f, int n, char *token)
438: {
439: struct line *odotp;
440: int odoto, tdoto, odotline, size, r;
441: char c;
442:
443: /* Underscore character is to be treated as "inword" while
444: * processing tokens unlike mg's default word traversal. Save
445: * and restore it's cinfo value so that tag matching works for
446: * identifier with underscore.
447: */
448: c = cinfo['_'];
449: cinfo['_'] = _MG_W;
450:
451: odotp = curwp->w_dotp;
452: odoto = curwp->w_doto;
453: odotline = curwp->w_dotline;
454:
455: /* Move backword unless we are at the beginning of a word or at
456: * beginning of line.
457: */
458: if (!atbow())
459: if ((r = backword(f, n)) == FALSE)
460: goto cleanup;
461:
462: tdoto = curwp->w_doto;
463:
464: if ((r = forwword(f, n)) == FALSE)
465: goto cleanup;
466:
467: /* strip away leading whitespace if any like emacs. */
468: while (ltext(curwp->w_dotp) &&
469: isspace(curwp->w_dotp->l_text[tdoto]))
470: tdoto++;
471:
472: size = curwp->w_doto - tdoto;
473: if (size <= 0 || size >= MAX_TOKEN ||
474: ltext(curwp->w_dotp) == NULL) {
475: r = FALSE;
476: goto cleanup;
477: }
478: strncpy(token, ltext(curwp->w_dotp) + tdoto, size);
479: token[size] = '\0';
480: r = TRUE;
481:
482: cleanup:
483: cinfo['_'] = c;
484: curwp->w_dotp = odotp;
485: curwp->w_doto = odoto;
486: curwp->w_dotline = odotline;
487: return (r);
488: }
489:
490: /*
491: * Search tagstree for a given token.
492: */
493: struct ctag *
494: searchtag(char *tok)
495: {
496: struct ctag t, *res;
497:
498: t.tag = tok;
499: if ((res = RB_FIND(tagtree, &tags, &t)) == NULL) {
500: ewprintf("No tag containing %s", tok);
501: return (NULL);
502: }
503: return res;
504: }
505:
506: /*
507: * This is equivalent to filevisit from file.c.
508: * Look around to see if we can find the file in another buffer; if we
509: * can't find it, create a new buffer, read in the text, and switch to
510: * the new buffer. *scratch*, *grep*, *compile* needs to be handled
511: * differently from other buffers which have "filenames".
512: */
513: int
514: loadbuffer(char *bname)
515: {
516: struct buffer *bufp;
517: char *adjf;
518:
519: /* check for special buffers which begin with '*' */
520: if (bname[0] == '*') {
521: if ((bufp = bfind(bname, FALSE)) != NULL) {
522: curbp = bufp;
523: return (showbuffer(bufp, curwp, WFFULL));
524: } else {
525: return (FALSE);
526: }
527: } else {
528: if ((adjf = adjustname(bname, TRUE)) == NULL)
529: return (FALSE);
530: if ((bufp = findbuffer(adjf)) == NULL)
531: return (FALSE);
532: }
533: curbp = bufp;
534: if (showbuffer(bufp, curwp, WFFULL) != TRUE)
535: return (FALSE);
536: if (bufp->b_fname[0] == '\0') {
537: if (readin(adjf) != TRUE) {
538: killbuffer(bufp);
539: return (FALSE);
540: }
541: }
542: return (TRUE);
543: }