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