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