tags.c revision 1.10
1/*
2 * Copyright (C) 1984-2011  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.
6 *
7 * For more information about less, or for information on how to
8 * contact the author, see the README file.
9 */
10
11
12#include "less.h"
13
14#define	WHITESP(c)	((c)==' ' || (c)=='\t')
15
16public char *tags = "tags";
17
18static int total;
19static int curseq;
20
21extern int linenums;
22extern volatile sig_atomic_t sigs;
23
24enum tag_result {
25	TAG_FOUND,
26	TAG_NOFILE,
27	TAG_NOTAG,
28	TAG_NOTYPE,
29	TAG_INTR
30};
31
32/*
33 * Tag type
34 */
35enum {
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
44static enum tag_result findctag();
45static enum tag_result findgtag();
46static char *nextgtag();
47static char *prevgtag();
48static POSITION ctagsearch();
49static POSITION gtagsearch();
50static 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.
57 * findctag() uses either pattern (in which case line number is 0),
58 * or line number (in which case pattern is NULL).
59 */
60struct taglist {
61	struct tag *tl_first;
62	struct tag *tl_last;
63};
64#define TAG_END  ((struct tag *) &taglist)
65static struct taglist taglist = { TAG_END, TAG_END };
66struct 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};
73static struct tag *curtag;
74
75#define TAG_INS(tp) \
76	(tp)->next = TAG_END; \
77	(tp)->prev = taglist.tl_last; \
78	taglist.tl_last->next = (tp); \
79	taglist.tl_last = (tp);
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
89cleantags()
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 *
111maketagent(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
135gettagtype()
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}
157
158/*
159 * Find tags in tag file.
160 * Find a tag in the "tags" file.
161 * Sets "tag_file" to the name of the file containing the tag,
162 * and "tagpattern" to the search pattern which should be used
163 * to find the tag.
164 */
165	public void
166findtag(tag)
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
197tagsearch()
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 *
211nexttag(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 *
225prevtag(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
239ntags()
240{
241	return total;
242}
243
244/*
245 * Return the sequence number of current tag.
246 */
247	public int
248curr_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
262findctag(tag)
263	register char *tag;
264{
265	char *p;
266	register FILE *f;
267	register int taglen;
268	LINENUM taglinenum;
269	char *tagfile;
270	char *tagpattern;
271	int tagendline;
272	int search_char;
273	int err;
274	char tline[TAGLINE_SIZE];
275	struct tag *tp;
276
277	p = shell_unquote(tags);
278	f = fopen(p, "r");
279	free(p);
280	if (f == NULL)
281		return TAG_NOFILE;
282
283	cleantags();
284	total = 0;
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	{
292		if (tline[0] == '!')
293			/* Skip header of extended format. */
294			continue;
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		 */
306		tagpattern = NULL;
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		 */
332		tagendline = 0;
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++;
346			tagpattern = p;
347			while (*p != search_char && *p != '\0')
348			{
349				if (*p == '\\')
350					p++;
351				p++;
352			}
353			tagendline = (p[-1] == '$');
354			if (tagendline)
355				p--;
356			*p = '\0';
357		}
358		tp = maketagent(tag, tagfile, taglinenum, tagpattern, tagendline);
359		TAG_INS(tp);
360		total++;
361	}
362	fclose(f);
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
374edit_tagfile()
375{
376	if (curtag == NULL)
377		return (1);
378	return (edit(curtag->tag_file));
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 */
390	static POSITION
391ctagsearch()
392{
393	POSITION pos, linepos;
394	LINENUM linenum;
395	int len;
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;
415		pos = forw_raw_line(pos, &line, (int *)NULL);
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.
440		 * If tagendline is set, make sure we match all
441		 * the way to end of line (no extra chars after the match).
442		 */
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);
448			break;
449		}
450	}
451
452	return (linepos);
453}
454
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
467findgtag(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;
474	size_t len;
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
496		char *command;
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;
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);
529		if (qtag != tag)
530			free(qtag);
531		fp = popen(command, "r");
532		free(command);
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			}
549			len = strlen(buf);
550			if (len > 0 && buf[len-1] == '\n')
551				buf[len-1] = '\0';
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
594static 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 *
602nextgtag()
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 *
632prevgtag()
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
659 * if it was unable to position at the tag, 0 if successful.
660 */
661	static POSITION
662gtagsearch()
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
698getentry(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
706	for (*tag = p;  *p && !IS_SPACE(*p);  p++)	/* tag name */
707		;
708	if (*p == 0)
709		return (-1);
710	*p++ = 0;
711	for ( ;  *p && IS_SPACE(*p);  p++)		/* (skip blanks) */
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	 */
719	if (!IS_DIGIT(*p))
720	{
721		for ( ;  *p && !IS_SPACE(*p);  p++)	/* (skip tag type) */
722			;
723		for (;  *p && IS_SPACE(*p);  p++)	/* (skip blanks) */
724			;
725	}
726	if (!IS_DIGIT(*p))
727		return (-1);
728	*line = p;					/* line number */
729	for (*line = p;  *p && !IS_SPACE(*p);  p++)
730		;
731	if (*p == 0)
732		return (-1);
733	*p++ = 0;
734	for ( ; *p && IS_SPACE(*p);  p++)		/* (skip blanks) */
735		;
736	if (*p == 0)
737		return (-1);
738	*file = p;					/* file name */
739	for (*file = p;  *p && !IS_SPACE(*p);  p++)
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}
750
751