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