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