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