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