Annotation of src/usr.bin/less/tags.c, Revision 1.14
1.1 etheisen 1: /*
1.11 shadchin 2: * Copyright (C) 1984-2012 Mark Nudelman
1.13 nicm 3: * Modified for use with illumos by Garrett D'Amore.
4: * Copyright 2014 Garrett D'Amore <garrett@damore.org>
1.4 millert 5: *
6: * You may distribute under the terms of either the GNU General Public
7: * License or the Less License, as specified in the README file.
1.1 etheisen 8: *
1.11 shadchin 9: * For more information, see the README file.
1.12 nicm 10: */
1.1 etheisen 11:
12: #include "less.h"
13:
1.12 nicm 14: #define WHITESP(c) ((c) == ' ' || (c) == '\t')
1.1 etheisen 15:
1.12 nicm 16: char *tags = "tags";
1.1 etheisen 17:
1.4 millert 18: static int total;
19: static int curseq;
1.1 etheisen 20:
21: extern int linenums;
1.10 millert 22: extern volatile sig_atomic_t sigs;
1.4 millert 23:
24: enum tag_result {
25: TAG_FOUND,
26: TAG_NOFILE,
27: TAG_NOTAG,
28: TAG_NOTYPE,
29: TAG_INTR
30: };
31:
32: /*
33: * Tag type
34: */
35: enum {
36: T_CTAGS, /* 'tags': standard and extended format (ctags) */
37: T_CTAGS_X, /* stdin: cross reference format (ctags) */
38: T_GTAGS, /* 'GTAGS': function defenition (global) */
39: T_GRTAGS, /* 'GRTAGS': function reference (global) */
40: T_GSYMS, /* 'GSYMS': other symbols (global) */
41: T_GPATH /* 'GPATH': path name (global) */
42: };
43:
44: static enum tag_result findctag();
45: static enum tag_result findgtag();
46: static char *nextgtag();
47: static char *prevgtag();
1.12 nicm 48: static off_t ctagsearch();
49: static off_t gtagsearch();
1.4 millert 50: static int getentry();
51:
52: /*
53: * The list of tags generated by the last findgtag() call.
54: *
55: * Use either pattern or line number.
56: * findgtag() always uses line number, so pattern is always NULL.
1.9 shadchin 57: * findctag() uses either pattern (in which case line number is 0),
1.4 millert 58: * or line number (in which case pattern is NULL).
59: */
60: struct taglist {
61: struct tag *tl_first;
62: struct tag *tl_last;
63: };
1.12 nicm 64: #define TAG_END ((struct tag *)&taglist)
1.4 millert 65: static struct taglist taglist = { TAG_END, TAG_END };
66: struct tag {
67: struct tag *next, *prev; /* List links */
68: char *tag_file; /* Source file containing the tag */
69: LINENUM tag_linenum; /* Appropriate line number in source file */
70: char *tag_pattern; /* Pattern used to find the tag */
1.12 nicm 71: int tag_endline; /* True if the pattern includes '$' */
1.4 millert 72: };
73: static struct tag *curtag;
74:
1.12 nicm 75: #define TAG_INS(tp) \
1.9 shadchin 76: (tp)->next = TAG_END; \
77: (tp)->prev = taglist.tl_last; \
78: taglist.tl_last->next = (tp); \
79: taglist.tl_last = (tp);
1.4 millert 80:
1.12 nicm 81: #define TAG_RM(tp) \
1.4 millert 82: (tp)->next->prev = (tp)->prev; \
83: (tp)->prev->next = (tp)->next;
84:
85: /*
86: * Delete tag structures.
87: */
1.12 nicm 88: void
89: cleantags(void)
1.4 millert 90: {
1.12 nicm 91: struct tag *tp;
1.4 millert 92:
93: /*
94: * Delete any existing tag list.
95: * {{ Ideally, we wouldn't do this until after we know that we
96: * can load some other tag information. }}
97: */
1.12 nicm 98: while ((tp = taglist.tl_first) != TAG_END) {
1.4 millert 99: TAG_RM(tp);
100: free(tp);
101: }
102: curtag = NULL;
103: total = curseq = 0;
104: }
105:
106: /*
107: * Create a new tag entry.
108: */
1.12 nicm 109: static struct tag *
110: maketagent(char *file, LINENUM linenum, char *pattern, int endline)
1.4 millert 111: {
1.12 nicm 112: struct tag *tp;
1.4 millert 113:
1.12 nicm 114: tp = ecalloc(sizeof (struct tag), 1);
1.14 ! tedu 115: tp->tag_file = estrdup(file);
1.4 millert 116: tp->tag_linenum = linenum;
117: tp->tag_endline = endline;
118: if (pattern == NULL)
119: tp->tag_pattern = NULL;
120: else
1.14 ! tedu 121: tp->tag_pattern = estrdup(pattern);
1.4 millert 122: return (tp);
123: }
124:
125: /*
126: * Get tag mode.
127: */
1.12 nicm 128: static int
129: gettagtype(void)
1.4 millert 130: {
131: int f;
132:
133: if (strcmp(tags, "GTAGS") == 0)
1.12 nicm 134: return (T_GTAGS);
1.4 millert 135: if (strcmp(tags, "GRTAGS") == 0)
1.12 nicm 136: return (T_GRTAGS);
1.4 millert 137: if (strcmp(tags, "GSYMS") == 0)
1.12 nicm 138: return (T_GSYMS);
1.4 millert 139: if (strcmp(tags, "GPATH") == 0)
1.12 nicm 140: return (T_GPATH);
1.4 millert 141: if (strcmp(tags, "-") == 0)
1.12 nicm 142: return (T_CTAGS_X);
1.4 millert 143: f = open(tags, OPEN_READ);
1.12 nicm 144: if (f >= 0) {
145: (void) close(f);
146: return (T_CTAGS);
1.4 millert 147: }
1.12 nicm 148: return (T_GTAGS);
1.4 millert 149: }
1.1 etheisen 150:
151: /*
1.4 millert 152: * Find tags in tag file.
1.1 etheisen 153: * Find a tag in the "tags" file.
1.4 millert 154: * Sets "tag_file" to the name of the file containing the tag,
1.1 etheisen 155: * and "tagpattern" to the search pattern which should be used
156: * to find the tag.
157: */
1.12 nicm 158: void
159: findtag(char *tag)
1.4 millert 160: {
161: int type = gettagtype();
162: enum tag_result result;
163:
164: if (type == T_CTAGS)
165: result = findctag(tag);
166: else
167: result = findgtag(tag, type);
1.12 nicm 168: switch (result) {
1.4 millert 169: case TAG_FOUND:
170: case TAG_INTR:
171: break;
172: case TAG_NOFILE:
173: error("No tags file", NULL_PARG);
174: break;
175: case TAG_NOTAG:
176: error("No such tag in tags file", NULL_PARG);
177: break;
178: case TAG_NOTYPE:
179: error("unknown tag type", NULL_PARG);
180: break;
181: }
182: }
183:
184: /*
185: * Search for a tag.
186: */
1.12 nicm 187: off_t
188: tagsearch(void)
1.4 millert 189: {
190: if (curtag == NULL)
1.12 nicm 191: return (-1); /* No gtags loaded! */
1.4 millert 192: if (curtag->tag_linenum != 0)
1.12 nicm 193: return (gtagsearch());
1.4 millert 194: else
1.12 nicm 195: return (ctagsearch());
1.4 millert 196: }
197:
198: /*
199: * Go to the next tag.
200: */
1.12 nicm 201: char *
202: nexttag(int n)
1.4 millert 203: {
1.12 nicm 204: char *tagfile = NULL;
1.4 millert 205:
206: while (n-- > 0)
207: tagfile = nextgtag();
1.12 nicm 208: return (tagfile);
1.4 millert 209: }
210:
211: /*
212: * Go to the previous tag.
213: */
1.12 nicm 214: char *
215: prevtag(int n)
1.4 millert 216: {
1.12 nicm 217: char *tagfile = NULL;
1.4 millert 218:
219: while (n-- > 0)
220: tagfile = prevgtag();
1.12 nicm 221: return (tagfile);
1.4 millert 222: }
223:
224: /*
225: * Return the total number of tags.
226: */
1.12 nicm 227: int
228: ntags(void)
1.4 millert 229: {
1.12 nicm 230: return (total);
1.4 millert 231: }
232:
233: /*
234: * Return the sequence number of current tag.
235: */
1.12 nicm 236: int
237: curr_tag(void)
1.4 millert 238: {
1.12 nicm 239: return (curseq);
1.4 millert 240: }
241:
1.12 nicm 242: /*
1.4 millert 243: * ctags
244: */
245:
246: /*
247: * Find tags in the "tags" file.
248: * Sets curtag to the first tag entry.
249: */
1.12 nicm 250: static enum tag_result
251: findctag(char *tag)
1.1 etheisen 252: {
253: char *p;
1.12 nicm 254: FILE *f;
255: int taglen;
1.4 millert 256: LINENUM taglinenum;
257: char *tagfile;
258: char *tagpattern;
259: int tagendline;
1.1 etheisen 260: int search_char;
261: int err;
1.4 millert 262: char tline[TAGLINE_SIZE];
263: struct tag *tp;
1.1 etheisen 264:
1.4 millert 265: p = shell_unquote(tags);
266: f = fopen(p, "r");
267: free(p);
268: if (f == NULL)
1.12 nicm 269: return (TAG_NOFILE);
1.1 etheisen 270:
1.4 millert 271: cleantags();
272: total = 0;
1.1 etheisen 273: taglen = strlen(tag);
274:
275: /*
276: * Search the tags file for the desired tag.
277: */
1.12 nicm 278: while (fgets(tline, sizeof (tline), f) != NULL) {
1.4 millert 279: if (tline[0] == '!')
280: /* Skip header of extended format. */
281: continue;
1.1 etheisen 282: if (strncmp(tag, tline, taglen) != 0 || !WHITESP(tline[taglen]))
283: continue;
284:
285: /*
286: * Found it.
287: * The line contains the tag, the filename and the
288: * location in the file, separated by white space.
1.12 nicm 289: * The location is either a decimal line number,
1.1 etheisen 290: * or a search pattern surrounded by a pair of delimiters.
291: * Parse the line and extract these parts.
292: */
1.4 millert 293: tagpattern = NULL;
1.1 etheisen 294:
295: /*
296: * Skip over the whitespace after the tag name.
297: */
298: p = skipsp(tline+taglen);
299: if (*p == '\0')
300: /* File name is missing! */
301: continue;
302:
303: /*
304: * Save the file name.
305: * Skip over the whitespace after the file name.
306: */
307: tagfile = p;
308: while (!WHITESP(*p) && *p != '\0')
309: p++;
310: *p++ = '\0';
311: p = skipsp(p);
312: if (*p == '\0')
313: /* Pattern is missing! */
314: continue;
315:
316: /*
1.12 nicm 317: * First see if it is a line number.
1.1 etheisen 318: */
1.4 millert 319: tagendline = 0;
1.1 etheisen 320: taglinenum = getnum(&p, 0, &err);
1.12 nicm 321: if (err) {
1.1 etheisen 322: /*
323: * No, it must be a pattern.
1.12 nicm 324: * Delete the initial "^" (if present) and
1.1 etheisen 325: * the final "$" from the pattern.
326: * Delete any backslash in the pattern.
327: */
328: taglinenum = 0;
329: search_char = *p++;
330: if (*p == '^')
331: p++;
1.4 millert 332: tagpattern = p;
1.12 nicm 333: while (*p != search_char && *p != '\0') {
1.1 etheisen 334: if (*p == '\\')
335: p++;
1.4 millert 336: p++;
1.1 etheisen 337: }
1.4 millert 338: tagendline = (p[-1] == '$');
339: if (tagendline)
340: p--;
341: *p = '\0';
1.1 etheisen 342: }
1.12 nicm 343: tp = maketagent(tagfile, taglinenum, tagpattern, tagendline);
1.4 millert 344: TAG_INS(tp);
345: total++;
1.1 etheisen 346: }
347: fclose(f);
1.4 millert 348: if (total == 0)
1.12 nicm 349: return (TAG_NOTAG);
1.4 millert 350: curtag = taglist.tl_first;
351: curseq = 1;
1.12 nicm 352: return (TAG_FOUND);
1.4 millert 353: }
354:
355: /*
356: * Edit current tagged file.
357: */
1.12 nicm 358: int
359: edit_tagfile(void)
1.4 millert 360: {
361: if (curtag == NULL)
362: return (1);
363: return (edit(curtag->tag_file));
1.1 etheisen 364: }
365:
366: /*
367: * Search for a tag.
368: * This is a stripped-down version of search().
369: * We don't use search() for several reasons:
370: * - We don't want to blow away any search string we may have saved.
371: * - The various regular-expression functions (from different systems:
1.12 nicm 372: * regcmp vs. re_comp) behave differently in the presence of
1.1 etheisen 373: * parentheses (which are almost always found in a tag).
374: */
1.12 nicm 375: static off_t
376: ctagsearch(void)
1.1 etheisen 377: {
1.12 nicm 378: off_t pos, linepos;
1.4 millert 379: LINENUM linenum;
380: int len;
1.1 etheisen 381: char *line;
382:
383: pos = ch_zero();
384: linenum = find_linenum(pos);
385:
1.12 nicm 386: for (;;) {
1.1 etheisen 387: /*
1.12 nicm 388: * Get lines until we find a matching one or
1.1 etheisen 389: * until we hit end-of-file.
390: */
391: if (ABORT_SIGS())
1.12 nicm 392: return (-1);
1.1 etheisen 393:
394: /*
1.12 nicm 395: * Read the next line, and save the
1.1 etheisen 396: * starting position of that line in linepos.
397: */
398: linepos = pos;
1.9 shadchin 399: pos = forw_raw_line(pos, &line, (int *)NULL);
1.1 etheisen 400: if (linenum != 0)
401: linenum++;
402:
1.12 nicm 403: if (pos == -1) {
1.1 etheisen 404: /*
405: * We hit EOF without a match.
406: */
407: error("Tag not found", NULL_PARG);
1.12 nicm 408: return (-1);
1.1 etheisen 409: }
410:
411: /*
412: * If we're using line numbers, we might as well
413: * remember the information we have now (the position
414: * and line number of the current line).
415: */
416: if (linenums)
417: add_lnum(linenum, pos);
418:
419: /*
420: * Test the line to see if we have a match.
421: * Use strncmp because the pattern may be
422: * truncated (in the tags file) if it is too long.
1.4 millert 423: * If tagendline is set, make sure we match all
424: * the way to end of line (no extra chars after the match).
1.1 etheisen 425: */
1.4 millert 426: len = strlen(curtag->tag_pattern);
427: if (strncmp(curtag->tag_pattern, line, len) == 0 &&
1.12 nicm 428: (!curtag->tag_endline || line[len] == '\0' ||
429: line[len] == '\r')) {
1.4 millert 430: curtag->tag_linenum = find_linenum(linepos);
1.1 etheisen 431: break;
1.4 millert 432: }
1.1 etheisen 433: }
434:
435: return (linepos);
436: }
437:
1.12 nicm 438: /*
1.4 millert 439: * gtags
440: */
441:
442: /*
443: * Find tags in the GLOBAL's tag file.
444: * The findgtag() will try and load information about the requested tag.
445: * It does this by calling "global -x tag" and storing the parsed output
446: * for future use by gtagsearch().
447: * Sets curtag to the first tag entry.
448: */
1.12 nicm 449: static enum tag_result
450: findgtag(char *tag, int type)
1.4 millert 451: {
452: char buf[256];
453: FILE *fp;
454: struct tag *tp;
455:
456: if (type != T_CTAGS_X && tag == NULL)
1.12 nicm 457: return (TAG_NOFILE);
1.4 millert 458:
459: cleantags();
460: total = 0;
461:
462: /*
463: * If type == T_CTAGS_X then read ctags's -x format from stdin
464: * else execute global(1) and read from it.
465: */
1.12 nicm 466: if (type == T_CTAGS_X) {
1.4 millert 467: fp = stdin;
468: /* Set tag default because we cannot read stdin again. */
469: tags = "tags";
1.12 nicm 470: } else {
1.9 shadchin 471: char *command;
1.4 millert 472: char *flag;
473: char *qtag;
474: char *cmd = lgetenv("LESSGLOBALTAGS");
475:
476: if (cmd == NULL || *cmd == '\0')
1.12 nicm 477: return (TAG_NOFILE);
1.4 millert 478: /* Get suitable flag value for global(1). */
1.12 nicm 479: switch (type) {
1.4 millert 480: case T_GTAGS:
1.12 nicm 481: flag = "";
1.4 millert 482: break;
483: case T_GRTAGS:
484: flag = "r";
485: break;
486: case T_GSYMS:
487: flag = "s";
488: break;
489: case T_GPATH:
490: flag = "P";
491: break;
492: default:
1.12 nicm 493: return (TAG_NOTYPE);
1.4 millert 494: }
495:
496: /* Get our data from global(1). */
497: qtag = shell_quote(tag);
498: if (qtag == NULL)
499: qtag = tag;
1.12 nicm 500: command = easprintf("%s -x%s %s", cmd, flag, qtag);
1.4 millert 501: if (qtag != tag)
502: free(qtag);
503: fp = popen(command, "r");
1.9 shadchin 504: free(command);
1.4 millert 505: }
1.12 nicm 506: if (fp != NULL) {
507: while (fgets(buf, sizeof (buf), fp)) {
1.4 millert 508: char *name, *file, *line;
1.12 nicm 509: int len;
1.4 millert 510:
1.12 nicm 511: if (sigs) {
1.4 millert 512: if (fp != stdin)
513: pclose(fp);
1.12 nicm 514: return (TAG_INTR);
1.4 millert 515: }
1.9 shadchin 516: len = strlen(buf);
1.12 nicm 517: if (len > 0 && buf[len-1] == '\n') {
1.9 shadchin 518: buf[len-1] = '\0';
1.12 nicm 519: } else {
1.4 millert 520: int c;
521: do {
522: c = fgetc(fp);
523: } while (c != '\n' && c != EOF);
524: }
525:
1.12 nicm 526: if (getentry(buf, &name, &file, &line)) {
1.4 millert 527: /*
528: * Couldn't parse this line for some reason.
529: * We'll just pretend it never happened.
530: */
531: break;
532: }
533:
534: /* Make new entry and add to list. */
1.12 nicm 535: tp = maketagent(file, (LINENUM) atoi(line), NULL, 0);
1.4 millert 536: TAG_INS(tp);
537: total++;
538: }
1.12 nicm 539: if (fp != stdin) {
540: if (pclose(fp)) {
1.4 millert 541: curtag = NULL;
542: total = curseq = 0;
1.12 nicm 543: return (TAG_NOFILE);
1.4 millert 544: }
545: }
546: }
547:
548: /* Check to see if we found anything. */
549: tp = taglist.tl_first;
550: if (tp == TAG_END)
1.12 nicm 551: return (TAG_NOTAG);
1.4 millert 552: curtag = tp;
553: curseq = 1;
1.12 nicm 554: return (TAG_FOUND);
1.4 millert 555: }
556:
557: static int circular = 0; /* 1: circular tag structure */
558:
559: /*
560: * Return the filename required for the next gtag in the queue that was setup
561: * by findgtag(). The next call to gtagsearch() will try to position at the
562: * appropriate tag.
563: */
1.12 nicm 564: static char *
565: nextgtag(void)
1.4 millert 566: {
567: struct tag *tp;
568:
569: if (curtag == NULL)
570: /* No tag loaded */
1.12 nicm 571: return (NULL);
1.4 millert 572:
573: tp = curtag->next;
1.12 nicm 574: if (tp == TAG_END) {
1.4 millert 575: if (!circular)
1.12 nicm 576: return (NULL);
1.4 millert 577: /* Wrapped around to the head of the queue */
578: curtag = taglist.tl_first;
579: curseq = 1;
1.12 nicm 580: } else {
1.4 millert 581: curtag = tp;
582: curseq++;
583: }
584: return (curtag->tag_file);
585: }
586:
587: /*
588: * Return the filename required for the previous gtag in the queue that was
589: * setup by findgtat(). The next call to gtagsearch() will try to position
590: * at the appropriate tag.
591: */
1.12 nicm 592: static char *
593: prevgtag(void)
1.4 millert 594: {
595: struct tag *tp;
596:
597: if (curtag == NULL)
598: /* No tag loaded */
1.12 nicm 599: return (NULL);
1.4 millert 600:
601: tp = curtag->prev;
1.12 nicm 602: if (tp == TAG_END) {
1.4 millert 603: if (!circular)
1.12 nicm 604: return (NULL);
1.4 millert 605: /* Wrapped around to the tail of the queue */
606: curtag = taglist.tl_last;
607: curseq = total;
1.12 nicm 608: } else {
1.4 millert 609: curtag = tp;
610: curseq--;
611: }
612: return (curtag->tag_file);
613: }
614:
615: /*
616: * Position the current file at at what is hopefully the tag that was chosen
617: * using either findtag() or one of nextgtag() and prevgtag(). Returns -1
1.5 jmc 618: * if it was unable to position at the tag, 0 if successful.
1.4 millert 619: */
1.12 nicm 620: static off_t
621: gtagsearch(void)
1.4 millert 622: {
623: if (curtag == NULL)
1.12 nicm 624: return (-1); /* No gtags loaded! */
1.4 millert 625: return (find_pos(curtag->tag_linenum));
626: }
627:
628: /*
629: * The getentry() parses both standard and extended ctags -x format.
630: *
631: * [standard format]
632: * <tag> <lineno> <file> <image>
633: * +------------------------------------------------
634: * |main 30 main.c main(argc, argv)
635: * |func 21 subr.c func(arg)
636: *
637: * The following commands write this format.
638: * o Traditinal Ctags with -x option
639: * o Global with -x option
640: * See <http://www.gnu.org/software/global/global.html>
641: *
642: * [extended format]
643: * <tag> <type> <lineno> <file> <image>
644: * +----------------------------------------------------------
645: * |main function 30 main.c main(argc, argv)
646: * |func function 21 subr.c func(arg)
647: *
648: * The following commands write this format.
649: * o Exuberant Ctags with -x option
650: * See <http://ctags.sourceforge.net>
651: *
652: * Returns 0 on success, -1 on error.
653: * The tag, file, and line will each be NUL-terminated pointers
654: * into buf.
655: */
1.12 nicm 656: static int
657: getentry(char *buf, char **tag, char **file, char **line)
1.4 millert 658: {
659: char *p = buf;
660:
1.12 nicm 661: for (*tag = p; *p && !isspace(*p); p++) /* tag name */
1.4 millert 662: ;
663: if (*p == 0)
664: return (-1);
665: *p++ = 0;
1.12 nicm 666: for (; *p && isspace(*p); p++) /* (skip blanks) */
1.4 millert 667: ;
668: if (*p == 0)
669: return (-1);
670: /*
671: * If the second part begin with other than digit,
672: * it is assumed tag type. Skip it.
673: */
1.12 nicm 674: if (!isdigit(*p)) {
675: for (; *p && !isspace(*p); p++) /* (skip tag type) */
1.4 millert 676: ;
1.12 nicm 677: for (; *p && isspace(*p); p++) /* (skip blanks) */
1.4 millert 678: ;
679: }
1.12 nicm 680: if (!isdigit(*p))
1.4 millert 681: return (-1);
682: *line = p; /* line number */
1.12 nicm 683: for (*line = p; *p && !isspace(*p); p++)
1.4 millert 684: ;
685: if (*p == 0)
686: return (-1);
687: *p++ = 0;
1.12 nicm 688: for (; *p && isspace(*p); p++) /* (skip blanks) */
1.4 millert 689: ;
690: if (*p == 0)
691: return (-1);
692: *file = p; /* file name */
1.12 nicm 693: for (*file = p; *p && !isspace(*p); p++)
1.4 millert 694: ;
695: if (*p == 0)
696: return (-1);
697: *p = 0;
698:
699: /* value check */
700: if (strlen(*tag) && strlen(*line) && strlen(*file) && atoi(*line) > 0)
701: return (0);
702: return (-1);
703: }