1 /* 2 * Copyright (C) 1984-2022 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(line, linepos) 400 char constant *line; 401 POSITION linepos; 402 { 403 /* 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). 409 */ 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')) 413 { 414 curtag->tag_linenum = find_linenum(linepos); 415 return 1; 416 } 417 return 0; 418 } 419 420 /* 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). 428 */ 429 static POSITION 430 ctagsearch(VOID_PARAM) 431 { 432 POSITION pos, linepos; 433 LINENUM linenum; 434 int line_len; 435 char *line; 436 int found; 437 438 pos = ch_zero(); 439 linenum = find_linenum(pos); 440 441 for (found = 0; !found;) 442 { 443 /* 444 * Get lines until we find a matching one or 445 * until we hit end-of-file. 446 */ 447 if (ABORT_SIGS()) 448 return (NULL_POSITION); 449 450 /* 451 * Read the next line, and save the 452 * starting position of that line in linepos. 453 */ 454 linepos = pos; 455 pos = forw_raw_line(pos, &line, &line_len); 456 if (linenum != 0) 457 linenum++; 458 459 if (pos == NULL_POSITION) 460 { 461 /* 462 * We hit EOF without a match. 463 */ 464 error("Tag not found", NULL_PARG); 465 return (NULL_POSITION); 466 } 467 468 /* 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). 472 */ 473 if (linenums) 474 add_lnum(linenum, pos); 475 476 if (ctldisp != OPT_ONPLUS) 477 { 478 if (curtag_match(line, linepos)) 479 found = 1; 480 } else 481 { 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); 491 } 492 } 493 494 return (linepos); 495 } 496 497 /******************************************************************************* 498 * gtags 499 */ 500 501 /* 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. 507 */ 508 static enum tag_result 509 findgtag(tag, type) 510 char *tag; /* tag to load */ 511 int type; /* tags type */ 512 { 513 char buf[1024]; 514 FILE *fp; 515 struct tag *tp; 516 517 if (type != T_CTAGS_X && tag == NULL) 518 return TAG_NOFILE; 519 520 cleantags(); 521 total = 0; 522 523 /* 524 * If type == T_CTAGS_X then read ctags's -x format from stdin 525 * else execute global(1) and read from it. 526 */ 527 if (type == T_CTAGS_X) 528 { 529 fp = stdin; 530 /* Set tag default because we cannot read stdin again. */ 531 tags = ztags; 532 } else 533 { 534 #if !HAVE_POPEN 535 return TAG_NOFILE; 536 #else 537 char *command; 538 char *flag; 539 char *qtag; 540 char *cmd = lgetenv("LESSGLOBALTAGS"); 541 542 if (isnullenv(cmd)) 543 return TAG_NOFILE; 544 /* Get suitable flag value for global(1). */ 545 switch (type) 546 { 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; 561 } 562 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 575 } 576 if (fp != NULL) 577 { 578 while (fgets(buf, sizeof(buf), fp)) 579 { 580 char *name, *file, *line; 581 int len; 582 583 if (sigs) 584 { 585 #if HAVE_POPEN 586 if (fp != stdin) 587 pclose(fp); 588 #endif 589 return TAG_INTR; 590 } 591 len = (int) strlen(buf); 592 if (len > 0 && buf[len-1] == '\n') 593 buf[len-1] = '\0'; 594 else 595 { 596 int c; 597 do { 598 c = fgetc(fp); 599 } while (c != '\n' && c != EOF); 600 } 601 602 if (getentry(buf, &name, &file, &line)) 603 { 604 /* 605 * Couldn't parse this line for some reason. 606 * We'll just pretend it never happened. 607 */ 608 break; 609 } 610 611 /* Make new entry and add to list. */ 612 tp = maketagent(name, file, (LINENUM) atoi(line), NULL, 0); 613 TAG_INS(tp); 614 total++; 615 } 616 if (fp != stdin) 617 { 618 if (pclose(fp)) 619 { 620 curtag = NULL; 621 total = curseq = 0; 622 return TAG_NOFILE; 623 } 624 } 625 } 626 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; 634 } 635 636 static int circular = 0; /* 1: circular tag structure */ 637 638 /* 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. 642 */ 643 static char * 644 nextgtag(VOID_PARAM) 645 { 646 struct tag *tp; 647 648 if (curtag == NULL) 649 /* No tag loaded */ 650 return NULL; 651 652 tp = curtag->next; 653 if (tp == TAG_END) 654 { 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 661 { 662 curtag = tp; 663 curseq++; 664 } 665 return (curtag->tag_file); 666 } 667 668 /* 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. 672 */ 673 static char * 674 prevgtag(VOID_PARAM) 675 { 676 struct tag *tp; 677 678 if (curtag == NULL) 679 /* No tag loaded */ 680 return NULL; 681 682 tp = curtag->prev; 683 if (tp == TAG_END) 684 { 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 691 { 692 curtag = tp; 693 curseq--; 694 } 695 return (curtag->tag_file); 696 } 697 698 /* 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. 702 */ 703 static POSITION 704 gtagsearch(VOID_PARAM) 705 { 706 if (curtag == NULL) 707 return (NULL_POSITION); /* No gtags loaded! */ 708 return (find_pos(curtag->tag_linenum)); 709 } 710 711 /* 712 * The getentry() parses both standard and extended ctags -x format. 713 * 714 * [standard format] 715 * <tag> <lineno> <file> <image> 716 * +------------------------------------------------ 717 * |main 30 main.c main(argc, argv) 718 * |func 21 subr.c func(arg) 719 * 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> 724 * 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) 730 * 731 * The following commands write this format. 732 * o Exuberant Ctags with -x option 733 * See <http://ctags.sourceforge.net> 734 * 735 * Returns 0 on success, -1 on error. 736 * The tag, file, and line will each be NUL-terminated pointers 737 * into buf. 738 */ 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 */ 745 { 746 char *p = buf; 747 748 for (*tag = p; *p && !IS_SPACE(*p); p++) /* tag name */ 749 ; 750 if (*p == 0) 751 return (-1); 752 *p++ = 0; 753 for ( ; *p && IS_SPACE(*p); p++) /* (skip blanks) */ 754 ; 755 if (*p == 0) 756 return (-1); 757 /* 758 * If the second part begin with other than digit, 759 * it is assumed tag type. Skip it. 760 */ 761 if (!IS_DIGIT(*p)) 762 { 763 for ( ; *p && !IS_SPACE(*p); p++) /* (skip tag type) */ 764 ; 765 for (; *p && IS_SPACE(*p); p++) /* (skip blanks) */ 766 ; 767 } 768 if (!IS_DIGIT(*p)) 769 return (-1); 770 *line = p; /* line number */ 771 for (*line = p; *p && !IS_SPACE(*p); p++) 772 ; 773 if (*p == 0) 774 return (-1); 775 *p++ = 0; 776 for ( ; *p && IS_SPACE(*p); p++) /* (skip blanks) */ 777 ; 778 if (*p == 0) 779 return (-1); 780 *file = p; /* file name */ 781 for (*file = p; *p && !IS_SPACE(*p); p++) 782 ; 783 if (*p == 0) 784 return (-1); 785 *p = 0; 786 787 /* value check */ 788 if (strlen(*tag) && strlen(*line) && strlen(*file) && atoi(*line) > 0) 789 return (0); 790 return (-1); 791 } 792 793 #endif 794