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