1 /* 2 * Copyright (C) 1984-2019 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(); 48 static enum tag_result findgtag(); 49 static char *nextgtag(); 50 static char *prevgtag(); 51 static POSITION ctagsearch(); 52 static POSITION gtagsearch(); 53 static int getentry(); 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 FILE *f; 276 int taglen; 277 LINENUM taglinenum; 278 char *tagfile; 279 char *tagpattern; 280 int tagendline; 281 int search_char; 282 int err; 283 char tline[TAGLINE_SIZE]; 284 struct tag *tp; 285 286 p = shell_unquote(tags); 287 f = fopen(p, "r"); 288 free(p); 289 if (f == NULL) 290 return TAG_NOFILE; 291 292 cleantags(); 293 total = 0; 294 taglen = (int) strlen(tag); 295 296 /* 297 * Search the tags file for the desired tag. 298 */ 299 while (fgets(tline, sizeof(tline), f) != NULL) 300 { 301 if (tline[0] == '!') 302 /* Skip header of extended format. */ 303 continue; 304 if (strncmp(tag, tline, taglen) != 0 || !WHITESP(tline[taglen])) 305 continue; 306 307 /* 308 * Found it. 309 * The line contains the tag, the filename and the 310 * location in the file, separated by white space. 311 * The location is either a decimal line number, 312 * or a search pattern surrounded by a pair of delimiters. 313 * Parse the line and extract these parts. 314 */ 315 tagpattern = NULL; 316 317 /* 318 * Skip over the whitespace after the tag name. 319 */ 320 p = skipsp(tline+taglen); 321 if (*p == '\0') 322 /* File name is missing! */ 323 continue; 324 325 /* 326 * Save the file name. 327 * Skip over the whitespace after the file name. 328 */ 329 tagfile = p; 330 while (!WHITESP(*p) && *p != '\0') 331 p++; 332 *p++ = '\0'; 333 p = skipsp(p); 334 if (*p == '\0') 335 /* Pattern is missing! */ 336 continue; 337 338 /* 339 * First see if it is a line number. 340 */ 341 tagendline = 0; 342 taglinenum = getnum(&p, 0, &err); 343 if (err) 344 { 345 /* 346 * No, it must be a pattern. 347 * Delete the initial "^" (if present) and 348 * the final "$" from the pattern. 349 * Delete any backslash in the pattern. 350 */ 351 taglinenum = 0; 352 search_char = *p++; 353 if (*p == '^') 354 p++; 355 tagpattern = p; 356 while (*p != search_char && *p != '\0') 357 { 358 if (*p == '\\') 359 p++; 360 p++; 361 } 362 tagendline = (p[-1] == '$'); 363 if (tagendline) 364 p--; 365 *p = '\0'; 366 } 367 tp = maketagent(tag, tagfile, taglinenum, tagpattern, tagendline); 368 TAG_INS(tp); 369 total++; 370 } 371 fclose(f); 372 if (total == 0) 373 return TAG_NOTAG; 374 curtag = taglist.tl_first; 375 curseq = 1; 376 return TAG_FOUND; 377 } 378 379 /* 380 * Edit current tagged file. 381 */ 382 public int 383 edit_tagfile(VOID_PARAM) 384 { 385 if (curtag == NULL) 386 return (1); 387 return (edit(curtag->tag_file)); 388 } 389 390 static int 391 curtag_match(char const *line, POSITION linepos) 392 { 393 /* 394 * Test the line to see if we have a match. 395 * Use strncmp because the pattern may be 396 * truncated (in the tags file) if it is too long. 397 * If tagendline is set, make sure we match all 398 * the way to end of line (no extra chars after the match). 399 */ 400 int len = (int) strlen(curtag->tag_pattern); 401 if (strncmp(curtag->tag_pattern, line, len) == 0 && 402 (!curtag->tag_endline || line[len] == '\0' || line[len] == '\r')) 403 { 404 curtag->tag_linenum = find_linenum(linepos); 405 return 1; 406 } 407 return 0; 408 } 409 410 /* 411 * Search for a tag. 412 * This is a stripped-down version of search(). 413 * We don't use search() for several reasons: 414 * - We don't want to blow away any search string we may have saved. 415 * - The various regular-expression functions (from different systems: 416 * regcmp vs. re_comp) behave differently in the presence of 417 * parentheses (which are almost always found in a tag). 418 */ 419 static POSITION 420 ctagsearch(VOID_PARAM) 421 { 422 POSITION pos, linepos; 423 LINENUM linenum; 424 int line_len; 425 char *line; 426 int found; 427 428 pos = ch_zero(); 429 linenum = find_linenum(pos); 430 431 for (found = 0; !found;) 432 { 433 /* 434 * Get lines until we find a matching one or 435 * until we hit end-of-file. 436 */ 437 if (ABORT_SIGS()) 438 return (NULL_POSITION); 439 440 /* 441 * Read the next line, and save the 442 * starting position of that line in linepos. 443 */ 444 linepos = pos; 445 pos = forw_raw_line(pos, &line, &line_len); 446 if (linenum != 0) 447 linenum++; 448 449 if (pos == NULL_POSITION) 450 { 451 /* 452 * We hit EOF without a match. 453 */ 454 error("Tag not found", NULL_PARG); 455 return (NULL_POSITION); 456 } 457 458 /* 459 * If we're using line numbers, we might as well 460 * remember the information we have now (the position 461 * and line number of the current line). 462 */ 463 if (linenums) 464 add_lnum(linenum, pos); 465 466 if (ctldisp != OPT_ONPLUS) 467 { 468 if (curtag_match(line, linepos)) 469 found = 1; 470 } else 471 { 472 int cvt_ops = CVT_ANSI; 473 int cvt_len = cvt_length(line_len, cvt_ops); 474 int *chpos = cvt_alloc_chpos(cvt_len); 475 char *cline = (char *) ecalloc(1, cvt_len); 476 cvt_text(cline, line, chpos, &line_len, cvt_ops); 477 if (curtag_match(cline, linepos)) 478 found = 1; 479 free(chpos); 480 free(cline); 481 } 482 } 483 484 return (linepos); 485 } 486 487 /******************************************************************************* 488 * gtags 489 */ 490 491 /* 492 * Find tags in the GLOBAL's tag file. 493 * The findgtag() will try and load information about the requested tag. 494 * It does this by calling "global -x tag" and storing the parsed output 495 * for future use by gtagsearch(). 496 * Sets curtag to the first tag entry. 497 */ 498 static enum tag_result 499 findgtag(tag, type) 500 char *tag; /* tag to load */ 501 int type; /* tags type */ 502 { 503 char buf[256]; 504 FILE *fp; 505 struct tag *tp; 506 507 if (type != T_CTAGS_X && tag == NULL) 508 return TAG_NOFILE; 509 510 cleantags(); 511 total = 0; 512 513 /* 514 * If type == T_CTAGS_X then read ctags's -x format from stdin 515 * else execute global(1) and read from it. 516 */ 517 if (type == T_CTAGS_X) 518 { 519 fp = stdin; 520 /* Set tag default because we cannot read stdin again. */ 521 tags = ztags; 522 } else 523 { 524 #if !HAVE_POPEN 525 return TAG_NOFILE; 526 #else 527 char *command; 528 char *flag; 529 char *qtag; 530 char *cmd = lgetenv("LESSGLOBALTAGS"); 531 532 if (isnullenv(cmd)) 533 return TAG_NOFILE; 534 /* Get suitable flag value for global(1). */ 535 switch (type) 536 { 537 case T_GTAGS: 538 flag = "" ; 539 break; 540 case T_GRTAGS: 541 flag = "r"; 542 break; 543 case T_GSYMS: 544 flag = "s"; 545 break; 546 case T_GPATH: 547 flag = "P"; 548 break; 549 default: 550 return TAG_NOTYPE; 551 } 552 553 /* Get our data from global(1). */ 554 qtag = shell_quote(tag); 555 if (qtag == NULL) 556 qtag = tag; 557 command = (char *) ecalloc(strlen(cmd) + strlen(flag) + 558 strlen(qtag) + 5, sizeof(char)); 559 sprintf(command, "%s -x%s %s", cmd, flag, qtag); 560 if (qtag != tag) 561 free(qtag); 562 fp = popen(command, "r"); 563 free(command); 564 #endif 565 } 566 if (fp != NULL) 567 { 568 while (fgets(buf, sizeof(buf), fp)) 569 { 570 char *name, *file, *line; 571 int len; 572 573 if (sigs) 574 { 575 #if HAVE_POPEN 576 if (fp != stdin) 577 pclose(fp); 578 #endif 579 return TAG_INTR; 580 } 581 len = (int) strlen(buf); 582 if (len > 0 && buf[len-1] == '\n') 583 buf[len-1] = '\0'; 584 else 585 { 586 int c; 587 do { 588 c = fgetc(fp); 589 } while (c != '\n' && c != EOF); 590 } 591 592 if (getentry(buf, &name, &file, &line)) 593 { 594 /* 595 * Couldn't parse this line for some reason. 596 * We'll just pretend it never happened. 597 */ 598 break; 599 } 600 601 /* Make new entry and add to list. */ 602 tp = maketagent(name, file, (LINENUM) atoi(line), NULL, 0); 603 TAG_INS(tp); 604 total++; 605 } 606 if (fp != stdin) 607 { 608 if (pclose(fp)) 609 { 610 curtag = NULL; 611 total = curseq = 0; 612 return TAG_NOFILE; 613 } 614 } 615 } 616 617 /* Check to see if we found anything. */ 618 tp = taglist.tl_first; 619 if (tp == TAG_END) 620 return TAG_NOTAG; 621 curtag = tp; 622 curseq = 1; 623 return TAG_FOUND; 624 } 625 626 static int circular = 0; /* 1: circular tag structure */ 627 628 /* 629 * Return the filename required for the next gtag in the queue that was setup 630 * by findgtag(). The next call to gtagsearch() will try to position at the 631 * appropriate tag. 632 */ 633 static char * 634 nextgtag(VOID_PARAM) 635 { 636 struct tag *tp; 637 638 if (curtag == NULL) 639 /* No tag loaded */ 640 return NULL; 641 642 tp = curtag->next; 643 if (tp == TAG_END) 644 { 645 if (!circular) 646 return NULL; 647 /* Wrapped around to the head of the queue */ 648 curtag = taglist.tl_first; 649 curseq = 1; 650 } else 651 { 652 curtag = tp; 653 curseq++; 654 } 655 return (curtag->tag_file); 656 } 657 658 /* 659 * Return the filename required for the previous gtag in the queue that was 660 * setup by findgtat(). The next call to gtagsearch() will try to position 661 * at the appropriate tag. 662 */ 663 static char * 664 prevgtag(VOID_PARAM) 665 { 666 struct tag *tp; 667 668 if (curtag == NULL) 669 /* No tag loaded */ 670 return NULL; 671 672 tp = curtag->prev; 673 if (tp == TAG_END) 674 { 675 if (!circular) 676 return NULL; 677 /* Wrapped around to the tail of the queue */ 678 curtag = taglist.tl_last; 679 curseq = total; 680 } else 681 { 682 curtag = tp; 683 curseq--; 684 } 685 return (curtag->tag_file); 686 } 687 688 /* 689 * Position the current file at at what is hopefully the tag that was chosen 690 * using either findtag() or one of nextgtag() and prevgtag(). Returns -1 691 * if it was unable to position at the tag, 0 if successful. 692 */ 693 static POSITION 694 gtagsearch(VOID_PARAM) 695 { 696 if (curtag == NULL) 697 return (NULL_POSITION); /* No gtags loaded! */ 698 return (find_pos(curtag->tag_linenum)); 699 } 700 701 /* 702 * The getentry() parses both standard and extended ctags -x format. 703 * 704 * [standard format] 705 * <tag> <lineno> <file> <image> 706 * +------------------------------------------------ 707 * |main 30 main.c main(argc, argv) 708 * |func 21 subr.c func(arg) 709 * 710 * The following commands write this format. 711 * o Traditinal Ctags with -x option 712 * o Global with -x option 713 * See <http://www.gnu.org/software/global/global.html> 714 * 715 * [extended format] 716 * <tag> <type> <lineno> <file> <image> 717 * +---------------------------------------------------------- 718 * |main function 30 main.c main(argc, argv) 719 * |func function 21 subr.c func(arg) 720 * 721 * The following commands write this format. 722 * o Exuberant Ctags with -x option 723 * See <http://ctags.sourceforge.net> 724 * 725 * Returns 0 on success, -1 on error. 726 * The tag, file, and line will each be NUL-terminated pointers 727 * into buf. 728 */ 729 static int 730 getentry(buf, tag, file, line) 731 char *buf; /* standard or extended ctags -x format data */ 732 char **tag; /* name of the tag we actually found */ 733 char **file; /* file in which to find this tag */ 734 char **line; /* line number of file where this tag is found */ 735 { 736 char *p = buf; 737 738 for (*tag = p; *p && !IS_SPACE(*p); p++) /* tag name */ 739 ; 740 if (*p == 0) 741 return (-1); 742 *p++ = 0; 743 for ( ; *p && IS_SPACE(*p); p++) /* (skip blanks) */ 744 ; 745 if (*p == 0) 746 return (-1); 747 /* 748 * If the second part begin with other than digit, 749 * it is assumed tag type. Skip it. 750 */ 751 if (!IS_DIGIT(*p)) 752 { 753 for ( ; *p && !IS_SPACE(*p); p++) /* (skip tag type) */ 754 ; 755 for (; *p && IS_SPACE(*p); p++) /* (skip blanks) */ 756 ; 757 } 758 if (!IS_DIGIT(*p)) 759 return (-1); 760 *line = p; /* line number */ 761 for (*line = p; *p && !IS_SPACE(*p); p++) 762 ; 763 if (*p == 0) 764 return (-1); 765 *p++ = 0; 766 for ( ; *p && IS_SPACE(*p); p++) /* (skip blanks) */ 767 ; 768 if (*p == 0) 769 return (-1); 770 *file = p; /* file name */ 771 for (*file = p; *p && !IS_SPACE(*p); p++) 772 ; 773 if (*p == 0) 774 return (-1); 775 *p = 0; 776 777 /* value check */ 778 if (strlen(*tag) && strlen(*line) && strlen(*file) && atoi(*line) > 0) 779 return (0); 780 return (-1); 781 } 782 783 #endif 784