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