usr.sbin/makefs/hammer2: Remove redundant hammer2_inode_modify()
[dragonfly.git] / contrib / less / tags.c
blob03e29736488a7323a878d3f84bcf40bc3aaa40b9
1 /*
2 * Copyright (C) 1984-2022 Mark Nudelman
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.
7 * For more information, see the README file.
8 */
11 #include "less.h"
13 #define WHITESP(c) ((c)==' ' || (c)=='\t')
15 #if TAGS
17 public char ztags[] = "tags";
18 public char *tags = ztags;
20 static int total;
21 static int curseq;
23 extern int linenums;
24 extern int sigs;
25 extern int ctldisp;
27 enum tag_result {
28 TAG_FOUND,
29 TAG_NOFILE,
30 TAG_NOTAG,
31 TAG_NOTYPE,
32 TAG_INTR
36 * Tag type
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 definition (global) */
42 T_GRTAGS, /* 'GRTAGS': function reference (global) */
43 T_GSYMS, /* 'GSYMS': other symbols (global) */
44 T_GPATH /* 'GPATH': path name (global) */
47 static enum tag_result findctag LESSPARAMS((char *tag));
48 static enum tag_result findgtag LESSPARAMS((char *tag, int type));
49 static char *nextgtag(VOID_PARAM);
50 static char *prevgtag(VOID_PARAM);
51 static POSITION ctagsearch(VOID_PARAM);
52 static POSITION gtagsearch(VOID_PARAM);
53 static int getentry LESSPARAMS((char *buf, char **tag, char **file, char **line));
56 * The list of tags generated by the last findgtag() call.
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).
63 struct taglist {
64 struct tag *tl_first;
65 struct tag *tl_last;
67 struct tag {
68 struct tag *next, *prev; /* List links */
69 char *tag_file; /* Source file containing the tag */
70 LINENUM tag_linenum; /* Appropriate line number in source file */
71 char *tag_pattern; /* Pattern used to find the tag */
72 char tag_endline; /* True if the pattern includes '$' */
74 #define TAG_END ((struct tag *) &taglist)
75 static struct taglist taglist = { TAG_END, TAG_END };
76 static struct tag *curtag;
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);
84 #define TAG_RM(tp) \
85 (tp)->next->prev = (tp)->prev; \
86 (tp)->prev->next = (tp)->next;
89 * Delete tag structures.
91 public void
92 cleantags(VOID_PARAM)
94 struct tag *tp;
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. }}
101 while ((tp = taglist.tl_first) != TAG_END)
103 TAG_RM(tp);
104 free(tp->tag_file);
105 free(tp->tag_pattern);
106 free(tp);
108 curtag = NULL;
109 total = curseq = 0;
113 * Create a new tag entry.
115 static struct tag *
116 maketagent(name, file, linenum, pattern, endline)
117 char *name;
118 char *file;
119 LINENUM linenum;
120 char *pattern;
121 int endline;
123 struct tag *tp;
125 tp = (struct tag *) ecalloc(sizeof(struct tag), 1);
126 tp->tag_file = (char *) ecalloc(strlen(file) + 1, sizeof(char));
127 strcpy(tp->tag_file, file);
128 tp->tag_linenum = linenum;
129 tp->tag_endline = endline;
130 if (pattern == NULL)
131 tp->tag_pattern = NULL;
132 else
134 tp->tag_pattern = (char *) ecalloc(strlen(pattern) + 1, sizeof(char));
135 strcpy(tp->tag_pattern, pattern);
137 return (tp);
141 * Get tag mode.
143 public int
144 gettagtype(VOID_PARAM)
146 int f;
148 if (strcmp(tags, "GTAGS") == 0)
149 return T_GTAGS;
150 if (strcmp(tags, "GRTAGS") == 0)
151 return T_GRTAGS;
152 if (strcmp(tags, "GSYMS") == 0)
153 return T_GSYMS;
154 if (strcmp(tags, "GPATH") == 0)
155 return T_GPATH;
156 if (strcmp(tags, "-") == 0)
157 return T_CTAGS_X;
158 f = open(tags, OPEN_READ);
159 if (f >= 0)
161 close(f);
162 return T_CTAGS;
164 return T_GTAGS;
168 * Find tags in tag file.
169 * Find a tag in the "tags" file.
170 * Sets "tag_file" to the name of the file containing the tag,
171 * and "tagpattern" to the search pattern which should be used
172 * to find the tag.
174 public void
175 findtag(tag)
176 char *tag;
178 int type = gettagtype();
179 enum tag_result result;
181 if (type == T_CTAGS)
182 result = findctag(tag);
183 else
184 result = findgtag(tag, type);
185 switch (result)
187 case TAG_FOUND:
188 case TAG_INTR:
189 break;
190 case TAG_NOFILE:
191 error("No tags file", NULL_PARG);
192 break;
193 case TAG_NOTAG:
194 error("No such tag in tags file", NULL_PARG);
195 break;
196 case TAG_NOTYPE:
197 error("unknown tag type", NULL_PARG);
198 break;
203 * Search for a tag.
205 public POSITION
206 tagsearch(VOID_PARAM)
208 if (curtag == NULL)
209 return (NULL_POSITION); /* No gtags loaded! */
210 if (curtag->tag_linenum != 0)
211 return gtagsearch();
212 else
213 return ctagsearch();
217 * Go to the next tag.
219 public char *
220 nexttag(n)
221 int n;
223 char *tagfile = (char *) NULL;
225 while (n-- > 0)
226 tagfile = nextgtag();
227 return tagfile;
231 * Go to the previous tag.
233 public char *
234 prevtag(n)
235 int n;
237 char *tagfile = (char *) NULL;
239 while (n-- > 0)
240 tagfile = prevgtag();
241 return tagfile;
245 * Return the total number of tags.
247 public int
248 ntags(VOID_PARAM)
250 return total;
254 * Return the sequence number of current tag.
256 public int
257 curr_tag(VOID_PARAM)
259 return curseq;
262 /*****************************************************************************
263 * ctags
267 * Find tags in the "tags" file.
268 * Sets curtag to the first tag entry.
270 static enum tag_result
271 findctag(tag)
272 char *tag;
274 char *p;
275 char *q;
276 FILE *f;
277 int taglen;
278 LINENUM taglinenum;
279 char *tagfile;
280 char *tagpattern;
281 int tagendline;
282 int search_char;
283 int err;
284 char tline[TAGLINE_SIZE];
285 struct tag *tp;
287 p = shell_unquote(tags);
288 f = fopen(p, "r");
289 free(p);
290 if (f == NULL)
291 return TAG_NOFILE;
293 cleantags();
294 total = 0;
295 taglen = (int) strlen(tag);
298 * Search the tags file for the desired tag.
300 while (fgets(tline, sizeof(tline), f) != NULL)
302 if (tline[0] == '!')
303 /* Skip header of extended format. */
304 continue;
305 if (strncmp(tag, tline, taglen) != 0 || !WHITESP(tline[taglen]))
306 continue;
309 * Found it.
310 * The line contains the tag, the filename and the
311 * location in the file, separated by white space.
312 * The location is either a decimal line number,
313 * or a search pattern surrounded by a pair of delimiters.
314 * Parse the line and extract these parts.
316 tagpattern = NULL;
319 * Skip over the whitespace after the tag name.
321 p = skipsp(tline+taglen);
322 if (*p == '\0')
323 /* File name is missing! */
324 continue;
327 * Save the file name.
328 * Skip over the whitespace after the file name.
330 tagfile = p;
331 while (!WHITESP(*p) && *p != '\0')
332 p++;
333 *p++ = '\0';
334 p = skipsp(p);
335 if (*p == '\0')
336 /* Pattern is missing! */
337 continue;
340 * First see if it is a line number.
342 tagendline = 0;
343 taglinenum = getnum(&p, 0, &err);
344 if (err)
347 * No, it must be a pattern.
348 * Delete the initial "^" (if present) and
349 * the final "$" from the pattern.
350 * Delete any backslash in the pattern.
352 taglinenum = 0;
353 search_char = *p++;
354 if (*p == '^')
355 p++;
356 tagpattern = q = p;
357 while (*p != search_char && *p != '\0')
359 if (*p == '\\')
360 p++;
361 if (q != p)
363 *q++ = *p++;
364 } else
366 q++;
367 p++;
370 tagendline = (q[-1] == '$');
371 if (tagendline)
372 q--;
373 *q = '\0';
375 tp = maketagent(tag, tagfile, taglinenum, tagpattern, tagendline);
376 TAG_INS(tp);
377 total++;
379 fclose(f);
380 if (total == 0)
381 return TAG_NOTAG;
382 curtag = taglist.tl_first;
383 curseq = 1;
384 return TAG_FOUND;
388 * Edit current tagged file.
390 public int
391 edit_tagfile(VOID_PARAM)
393 if (curtag == NULL)
394 return (1);
395 return (edit(curtag->tag_file));
398 static int
399 curtag_match(line, linepos)
400 char constant *line;
401 POSITION linepos;
404 * Test the line to see if we have a match.
405 * Use strncmp because the pattern may be
406 * truncated (in the tags file) if it is too long.
407 * If tagendline is set, make sure we match all
408 * the way to end of line (no extra chars after the match).
410 int len = (int) strlen(curtag->tag_pattern);
411 if (strncmp(curtag->tag_pattern, line, len) == 0 &&
412 (!curtag->tag_endline || line[len] == '\0' || line[len] == '\r'))
414 curtag->tag_linenum = find_linenum(linepos);
415 return 1;
417 return 0;
421 * Search for a tag.
422 * This is a stripped-down version of search().
423 * We don't use search() for several reasons:
424 * - We don't want to blow away any search string we may have saved.
425 * - The various regular-expression functions (from different systems:
426 * regcmp vs. re_comp) behave differently in the presence of
427 * parentheses (which are almost always found in a tag).
429 static POSITION
430 ctagsearch(VOID_PARAM)
432 POSITION pos, linepos;
433 LINENUM linenum;
434 int line_len;
435 char *line;
436 int found;
438 pos = ch_zero();
439 linenum = find_linenum(pos);
441 for (found = 0; !found;)
444 * Get lines until we find a matching one or
445 * until we hit end-of-file.
447 if (ABORT_SIGS())
448 return (NULL_POSITION);
451 * Read the next line, and save the
452 * starting position of that line in linepos.
454 linepos = pos;
455 pos = forw_raw_line(pos, &line, &line_len);
456 if (linenum != 0)
457 linenum++;
459 if (pos == NULL_POSITION)
462 * We hit EOF without a match.
464 error("Tag not found", NULL_PARG);
465 return (NULL_POSITION);
469 * If we're using line numbers, we might as well
470 * remember the information we have now (the position
471 * and line number of the current line).
473 if (linenums)
474 add_lnum(linenum, pos);
476 if (ctldisp != OPT_ONPLUS)
478 if (curtag_match(line, linepos))
479 found = 1;
480 } else
482 int cvt_ops = CVT_ANSI;
483 int cvt_len = cvt_length(line_len, cvt_ops);
484 int *chpos = cvt_alloc_chpos(cvt_len);
485 char *cline = (char *) ecalloc(1, cvt_len);
486 cvt_text(cline, line, chpos, &line_len, cvt_ops);
487 if (curtag_match(cline, linepos))
488 found = 1;
489 free(chpos);
490 free(cline);
494 return (linepos);
497 /*******************************************************************************
498 * gtags
502 * Find tags in the GLOBAL's tag file.
503 * The findgtag() will try and load information about the requested tag.
504 * It does this by calling "global -x tag" and storing the parsed output
505 * for future use by gtagsearch().
506 * Sets curtag to the first tag entry.
508 static enum tag_result
509 findgtag(tag, type)
510 char *tag; /* tag to load */
511 int type; /* tags type */
513 char buf[1024];
514 FILE *fp;
515 struct tag *tp;
517 if (type != T_CTAGS_X && tag == NULL)
518 return TAG_NOFILE;
520 cleantags();
521 total = 0;
524 * If type == T_CTAGS_X then read ctags's -x format from stdin
525 * else execute global(1) and read from it.
527 if (type == T_CTAGS_X)
529 fp = stdin;
530 /* Set tag default because we cannot read stdin again. */
531 tags = ztags;
532 } else
534 #if !HAVE_POPEN
535 return TAG_NOFILE;
536 #else
537 char *command;
538 char *flag;
539 char *qtag;
540 char *cmd = lgetenv("LESSGLOBALTAGS");
542 if (isnullenv(cmd))
543 return TAG_NOFILE;
544 /* Get suitable flag value for global(1). */
545 switch (type)
547 case T_GTAGS:
548 flag = "" ;
549 break;
550 case T_GRTAGS:
551 flag = "r";
552 break;
553 case T_GSYMS:
554 flag = "s";
555 break;
556 case T_GPATH:
557 flag = "P";
558 break;
559 default:
560 return TAG_NOTYPE;
563 /* Get our data from global(1). */
564 qtag = shell_quote(tag);
565 if (qtag == NULL)
566 qtag = tag;
567 command = (char *) ecalloc(strlen(cmd) + strlen(flag) +
568 strlen(qtag) + 5, sizeof(char));
569 sprintf(command, "%s -x%s %s", cmd, flag, qtag);
570 if (qtag != tag)
571 free(qtag);
572 fp = popen(command, "r");
573 free(command);
574 #endif
576 if (fp != NULL)
578 while (fgets(buf, sizeof(buf), fp))
580 char *name, *file, *line;
581 int len;
583 if (sigs)
585 #if HAVE_POPEN
586 if (fp != stdin)
587 pclose(fp);
588 #endif
589 return TAG_INTR;
591 len = (int) strlen(buf);
592 if (len > 0 && buf[len-1] == '\n')
593 buf[len-1] = '\0';
594 else
596 int c;
597 do {
598 c = fgetc(fp);
599 } while (c != '\n' && c != EOF);
602 if (getentry(buf, &name, &file, &line))
605 * Couldn't parse this line for some reason.
606 * We'll just pretend it never happened.
608 break;
611 /* Make new entry and add to list. */
612 tp = maketagent(name, file, (LINENUM) atoi(line), NULL, 0);
613 TAG_INS(tp);
614 total++;
616 if (fp != stdin)
618 if (pclose(fp))
620 curtag = NULL;
621 total = curseq = 0;
622 return TAG_NOFILE;
627 /* Check to see if we found anything. */
628 tp = taglist.tl_first;
629 if (tp == TAG_END)
630 return TAG_NOTAG;
631 curtag = tp;
632 curseq = 1;
633 return TAG_FOUND;
636 static int circular = 0; /* 1: circular tag structure */
639 * Return the filename required for the next gtag in the queue that was setup
640 * by findgtag(). The next call to gtagsearch() will try to position at the
641 * appropriate tag.
643 static char *
644 nextgtag(VOID_PARAM)
646 struct tag *tp;
648 if (curtag == NULL)
649 /* No tag loaded */
650 return NULL;
652 tp = curtag->next;
653 if (tp == TAG_END)
655 if (!circular)
656 return NULL;
657 /* Wrapped around to the head of the queue */
658 curtag = taglist.tl_first;
659 curseq = 1;
660 } else
662 curtag = tp;
663 curseq++;
665 return (curtag->tag_file);
669 * Return the filename required for the previous gtag in the queue that was
670 * setup by findgtat(). The next call to gtagsearch() will try to position
671 * at the appropriate tag.
673 static char *
674 prevgtag(VOID_PARAM)
676 struct tag *tp;
678 if (curtag == NULL)
679 /* No tag loaded */
680 return NULL;
682 tp = curtag->prev;
683 if (tp == TAG_END)
685 if (!circular)
686 return NULL;
687 /* Wrapped around to the tail of the queue */
688 curtag = taglist.tl_last;
689 curseq = total;
690 } else
692 curtag = tp;
693 curseq--;
695 return (curtag->tag_file);
699 * Position the current file at at what is hopefully the tag that was chosen
700 * using either findtag() or one of nextgtag() and prevgtag(). Returns -1
701 * if it was unable to position at the tag, 0 if successful.
703 static POSITION
704 gtagsearch(VOID_PARAM)
706 if (curtag == NULL)
707 return (NULL_POSITION); /* No gtags loaded! */
708 return (find_pos(curtag->tag_linenum));
712 * The getentry() parses both standard and extended ctags -x format.
714 * [standard format]
715 * <tag> <lineno> <file> <image>
716 * +------------------------------------------------
717 * |main 30 main.c main(argc, argv)
718 * |func 21 subr.c func(arg)
720 * The following commands write this format.
721 * o Traditinal Ctags with -x option
722 * o Global with -x option
723 * See <http://www.gnu.org/software/global/global.html>
725 * [extended format]
726 * <tag> <type> <lineno> <file> <image>
727 * +----------------------------------------------------------
728 * |main function 30 main.c main(argc, argv)
729 * |func function 21 subr.c func(arg)
731 * The following commands write this format.
732 * o Exuberant Ctags with -x option
733 * See <http://ctags.sourceforge.net>
735 * Returns 0 on success, -1 on error.
736 * The tag, file, and line will each be NUL-terminated pointers
737 * into buf.
739 static int
740 getentry(buf, tag, file, line)
741 char *buf; /* standard or extended ctags -x format data */
742 char **tag; /* name of the tag we actually found */
743 char **file; /* file in which to find this tag */
744 char **line; /* line number of file where this tag is found */
746 char *p = buf;
748 for (*tag = p; *p && !IS_SPACE(*p); p++) /* tag name */
750 if (*p == 0)
751 return (-1);
752 *p++ = 0;
753 for ( ; *p && IS_SPACE(*p); p++) /* (skip blanks) */
755 if (*p == 0)
756 return (-1);
758 * If the second part begin with other than digit,
759 * it is assumed tag type. Skip it.
761 if (!IS_DIGIT(*p))
763 for ( ; *p && !IS_SPACE(*p); p++) /* (skip tag type) */
765 for (; *p && IS_SPACE(*p); p++) /* (skip blanks) */
768 if (!IS_DIGIT(*p))
769 return (-1);
770 *line = p; /* line number */
771 for (*line = p; *p && !IS_SPACE(*p); p++)
773 if (*p == 0)
774 return (-1);
775 *p++ = 0;
776 for ( ; *p && IS_SPACE(*p); p++) /* (skip blanks) */
778 if (*p == 0)
779 return (-1);
780 *file = p; /* file name */
781 for (*file = p; *p && !IS_SPACE(*p); p++)
783 if (*p == 0)
784 return (-1);
785 *p = 0;
787 /* value check */
788 if (strlen(*tag) && strlen(*line) && strlen(*file) && atoi(*line) > 0)
789 return (0);
790 return (-1);
793 #endif