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