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