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