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