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