tags.c revision 1.8
1/*
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.
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 int 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() usually 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 = taglist.tl_first; \
77	(tp)->prev = TAG_END; \
78	taglist.tl_first->prev = (tp); \
79	taglist.tl_first = (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);
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
475	if (type != T_CTAGS_X && tag == NULL)
476		return TAG_NOFILE;
477
478	cleantags();
479	total = 0;
480
481	/*
482	 * If type == T_CTAGS_X then read ctags's -x format from stdin
483	 * else execute global(1) and read from it.
484	 */
485	if (type == T_CTAGS_X)
486	{
487		fp = stdin;
488		/* Set tag default because we cannot read stdin again. */
489		tags = "tags";
490	} else
491	{
492#if !HAVE_POPEN
493		return TAG_NOFILE;
494#else
495		char command[512];
496		char *flag;
497		char *qtag;
498		char *cmd = lgetenv("LESSGLOBALTAGS");
499
500		if (cmd == NULL || *cmd == '\0')
501			return TAG_NOFILE;
502		/* Get suitable flag value for global(1). */
503		switch (type)
504		{
505		case T_GTAGS:
506			flag = "" ;
507			break;
508		case T_GRTAGS:
509			flag = "r";
510			break;
511		case T_GSYMS:
512			flag = "s";
513			break;
514		case T_GPATH:
515			flag = "P";
516			break;
517		default:
518			return TAG_NOTYPE;
519		}
520
521		/* Get our data from global(1). */
522		qtag = shell_quote(tag);
523		if (qtag == NULL)
524			qtag = tag;
525		snprintf(command, sizeof(command), "%s -x%s %s", cmd,
526		    flag, qtag);
527		if (qtag != tag)
528			free(qtag);
529		fp = popen(command, "r");
530#endif
531	}
532	if (fp != NULL)
533	{
534		while (fgets(buf, sizeof(buf), fp))
535		{
536			char *name, *file, *line;
537			size_t len;
538
539			if (sigs)
540			{
541#if HAVE_POPEN
542				if (fp != stdin)
543					pclose(fp);
544#endif
545				return TAG_INTR;
546			}
547			if ((len = strlen(buf)) && buf[len - 1] == '\n')
548				buf[len - 1] = 0;
549			else
550			{
551				int c;
552				do {
553					c = fgetc(fp);
554				} while (c != '\n' && c != EOF);
555			}
556
557 			if (getentry(buf, &name, &file, &line))
558			{
559				/*
560				 * Couldn't parse this line for some reason.
561				 * We'll just pretend it never happened.
562				 */
563				break;
564			}
565
566			/* Make new entry and add to list. */
567			tp = maketagent(name, file, (LINENUM) atoi(line), NULL, 0);
568			TAG_INS(tp);
569			total++;
570		}
571		if (fp != stdin)
572		{
573			if (pclose(fp))
574			{
575				curtag = NULL;
576				total = curseq = 0;
577				return TAG_NOFILE;
578			}
579		}
580	}
581
582	/* Check to see if we found anything. */
583	tp = taglist.tl_first;
584	if (tp == TAG_END)
585		return TAG_NOTAG;
586	curtag = tp;
587	curseq = 1;
588	return TAG_FOUND;
589}
590
591static int circular = 0;	/* 1: circular tag structure */
592
593/*
594 * Return the filename required for the next gtag in the queue that was setup
595 * by findgtag().  The next call to gtagsearch() will try to position at the
596 * appropriate tag.
597 */
598	static char *
599nextgtag()
600{
601	struct tag *tp;
602
603	if (curtag == NULL)
604		/* No tag loaded */
605		return NULL;
606
607	tp = curtag->next;
608	if (tp == TAG_END)
609	{
610		if (!circular)
611			return NULL;
612		/* Wrapped around to the head of the queue */
613		curtag = taglist.tl_first;
614		curseq = 1;
615	} else
616	{
617		curtag = tp;
618		curseq++;
619	}
620	return (curtag->tag_file);
621}
622
623/*
624 * Return the filename required for the previous gtag in the queue that was
625 * setup by findgtat().  The next call to gtagsearch() will try to position
626 * at the appropriate tag.
627 */
628	static char *
629prevgtag()
630{
631	struct tag *tp;
632
633	if (curtag == NULL)
634		/* No tag loaded */
635		return NULL;
636
637	tp = curtag->prev;
638	if (tp == TAG_END)
639	{
640		if (!circular)
641			return NULL;
642		/* Wrapped around to the tail of the queue */
643		curtag = taglist.tl_last;
644		curseq = total;
645	} else
646	{
647		curtag = tp;
648		curseq--;
649	}
650	return (curtag->tag_file);
651}
652
653/*
654 * Position the current file at at what is hopefully the tag that was chosen
655 * using either findtag() or one of nextgtag() and prevgtag().  Returns -1
656 * if it was unable to position at the tag, 0 if successful.
657 */
658	static POSITION
659gtagsearch()
660{
661	if (curtag == NULL)
662		return (NULL_POSITION);  /* No gtags loaded! */
663	return (find_pos(curtag->tag_linenum));
664}
665
666/*
667 * The getentry() parses both standard and extended ctags -x format.
668 *
669 * [standard format]
670 * <tag>   <lineno>  <file>         <image>
671 * +------------------------------------------------
672 * |main     30      main.c         main(argc, argv)
673 * |func     21      subr.c         func(arg)
674 *
675 * The following commands write this format.
676 *	o Traditinal Ctags with -x option
677 *	o Global with -x option
678 *		See <http://www.gnu.org/software/global/global.html>
679 *
680 * [extended format]
681 * <tag>   <type>  <lineno>   <file>        <image>
682 * +----------------------------------------------------------
683 * |main     function 30      main.c         main(argc, argv)
684 * |func     function 21      subr.c         func(arg)
685 *
686 * The following commands write this format.
687 *	o Exuberant Ctags with -x option
688 *		See <http://ctags.sourceforge.net>
689 *
690 * Returns 0 on success, -1 on error.
691 * The tag, file, and line will each be NUL-terminated pointers
692 * into buf.
693 */
694
695#ifndef isspace
696#define isspace(c)	((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\r' || (c) == '\f')
697#endif
698#ifndef isdigit
699#define isdigit(c)	((c) >= '0' && (c <= '9'))
700#endif
701
702	static int
703getentry(buf, tag, file, line)
704	char *buf;	/* standard or extended ctags -x format data */
705	char **tag;	/* name of the tag we actually found */
706	char **file;	/* file in which to find this tag */
707	char **line;	/* line number of file where this tag is found */
708{
709	char *p = buf;
710
711	for (*tag = p;  *p && !isspace(*p);  p++)	/* tag name */
712		;
713	if (*p == 0)
714		return (-1);
715	*p++ = 0;
716	for ( ;  isspace(*p);  p++)			/* (skip blanks) */
717		;
718	if (*p == 0)
719		return (-1);
720	/*
721	 * If the second part begin with other than digit,
722	 * it is assumed tag type. Skip it.
723	 */
724	if (!isdigit(*p))
725	{
726		for ( ;  *p && !isspace(*p);  p++)	/* (skip tag type) */
727			;
728		for (;  isspace(*p);  p++)		/* (skip blanks) */
729			;
730	}
731	if (!isdigit(*p))
732		return (-1);
733	*line = p;					/* line number */
734	for (*line = p;  *p && !isspace(*p);  p++)
735		;
736	if (*p == 0)
737		return (-1);
738	*p++ = 0;
739	for ( ; isspace(*p);  p++)			/* (skip blanks) */
740		;
741	if (*p == 0)
742		return (-1);
743	*file = p;					/* file name */
744	for (*file = p;  *p && !isspace(*p);  p++)
745		;
746	if (*p == 0)
747		return (-1);
748	*p = 0;
749
750	/* value check */
751	if (strlen(*tag) && strlen(*line) && strlen(*file) && atoi(*line) > 0)
752		return (0);
753	return (-1);
754}
755
756