diff options
Diffstat (limited to 'tags.c')
-rwxr-xr-x | tags.c | 756 |
1 files changed, 756 insertions, 0 deletions
@@ -0,0 +1,756 @@ +/* + * Copyright (C) 1984-2012 Mark Nudelman + * + * You may distribute under the terms of either the GNU General Public + * License or the Less License, as specified in the README file. + * + * For more information, see the README file. + */ + + +#include "less.h" + +#define WHITESP(c) ((c)==' ' || (c)=='\t') + +#if TAGS + +public char *tags = "tags"; + +static int total; +static int curseq; + +extern int linenums; +extern int sigs; + +enum tag_result { + TAG_FOUND, + TAG_NOFILE, + TAG_NOTAG, + TAG_NOTYPE, + TAG_INTR +}; + +/* + * Tag type + */ +enum { + T_CTAGS, /* 'tags': standard and extended format (ctags) */ + T_CTAGS_X, /* stdin: cross reference format (ctags) */ + T_GTAGS, /* 'GTAGS': function defenition (global) */ + T_GRTAGS, /* 'GRTAGS': function reference (global) */ + T_GSYMS, /* 'GSYMS': other symbols (global) */ + T_GPATH /* 'GPATH': path name (global) */ +}; + +static enum tag_result findctag(); +static enum tag_result findgtag(); +static char *nextgtag(); +static char *prevgtag(); +static POSITION ctagsearch(); +static POSITION gtagsearch(); +static int getentry(); + +/* + * The list of tags generated by the last findgtag() call. + * + * Use either pattern or line number. + * findgtag() always uses line number, so pattern is always NULL. + * findctag() uses either pattern (in which case line number is 0), + * or line number (in which case pattern is NULL). + */ +struct taglist { + struct tag *tl_first; + struct tag *tl_last; +}; +#define TAG_END ((struct tag *) &taglist) +static struct taglist taglist = { TAG_END, TAG_END }; +struct tag { + struct tag *next, *prev; /* List links */ + char *tag_file; /* Source file containing the tag */ + LINENUM tag_linenum; /* Appropriate line number in source file */ + char *tag_pattern; /* Pattern used to find the tag */ + char tag_endline; /* True if the pattern includes '$' */ +}; +static struct tag *curtag; + +#define TAG_INS(tp) \ + (tp)->next = TAG_END; \ + (tp)->prev = taglist.tl_last; \ + taglist.tl_last->next = (tp); \ + taglist.tl_last = (tp); + +#define TAG_RM(tp) \ + (tp)->next->prev = (tp)->prev; \ + (tp)->prev->next = (tp)->next; + +/* + * Delete tag structures. + */ + public void +cleantags() +{ + register struct tag *tp; + + /* + * Delete any existing tag list. + * {{ Ideally, we wouldn't do this until after we know that we + * can load some other tag information. }} + */ + while ((tp = taglist.tl_first) != TAG_END) + { + TAG_RM(tp); + free(tp); + } + curtag = NULL; + total = curseq = 0; +} + +/* + * Create a new tag entry. + */ + static struct tag * +maketagent(name, file, linenum, pattern, endline) + char *name; + char *file; + LINENUM linenum; + char *pattern; + int endline; +{ + register struct tag *tp; + + tp = (struct tag *) ecalloc(sizeof(struct tag), 1); + tp->tag_file = (char *) ecalloc(strlen(file) + 1, sizeof(char)); + strcpy(tp->tag_file, file); + tp->tag_linenum = linenum; + tp->tag_endline = endline; + if (pattern == NULL) + tp->tag_pattern = NULL; + else + { + tp->tag_pattern = (char *) ecalloc(strlen(pattern) + 1, sizeof(char)); + strcpy(tp->tag_pattern, pattern); + } + return (tp); +} + +/* + * Get tag mode. + */ + public int +gettagtype() +{ + int f; + + if (strcmp(tags, "GTAGS") == 0) + return T_GTAGS; + if (strcmp(tags, "GRTAGS") == 0) + return T_GRTAGS; + if (strcmp(tags, "GSYMS") == 0) + return T_GSYMS; + if (strcmp(tags, "GPATH") == 0) + return T_GPATH; + if (strcmp(tags, "-") == 0) + return T_CTAGS_X; + f = open(tags, OPEN_READ); + if (f >= 0) + { + close(f); + return T_CTAGS; + } + return T_GTAGS; +} + +/* + * Find tags in tag file. + * Find a tag in the "tags" file. + * Sets "tag_file" to the name of the file containing the tag, + * and "tagpattern" to the search pattern which should be used + * to find the tag. + */ + public void +findtag(tag) + register char *tag; +{ + int type = gettagtype(); + enum tag_result result; + + if (type == T_CTAGS) + result = findctag(tag); + else + result = findgtag(tag, type); + switch (result) + { + case TAG_FOUND: + case TAG_INTR: + break; + case TAG_NOFILE: + error("No tags file", NULL_PARG); + break; + case TAG_NOTAG: + error("No such tag in tags file", NULL_PARG); + break; + case TAG_NOTYPE: + error("unknown tag type", NULL_PARG); + break; + } +} + +/* + * Search for a tag. + */ + public POSITION +tagsearch() +{ + if (curtag == NULL) + return (NULL_POSITION); /* No gtags loaded! */ + if (curtag->tag_linenum != 0) + return gtagsearch(); + else + return ctagsearch(); +} + +/* + * Go to the next tag. + */ + public char * +nexttag(n) + int n; +{ + char *tagfile = (char *) NULL; + + while (n-- > 0) + tagfile = nextgtag(); + return tagfile; +} + +/* + * Go to the previous tag. + */ + public char * +prevtag(n) + int n; +{ + char *tagfile = (char *) NULL; + + while (n-- > 0) + tagfile = prevgtag(); + return tagfile; +} + +/* + * Return the total number of tags. + */ + public int +ntags() +{ + return total; +} + +/* + * Return the sequence number of current tag. + */ + public int +curr_tag() +{ + return curseq; +} + +/***************************************************************************** + * ctags + */ + +/* + * Find tags in the "tags" file. + * Sets curtag to the first tag entry. + */ + static enum tag_result +findctag(tag) + register char *tag; +{ + char *p; + register FILE *f; + register int taglen; + LINENUM taglinenum; + char *tagfile; + char *tagpattern; + int tagendline; + int search_char; + int err; + char tline[TAGLINE_SIZE]; + struct tag *tp; + + p = shell_unquote(tags); + f = fopen(p, "r"); + free(p); + if (f == NULL) + return TAG_NOFILE; + + cleantags(); + total = 0; + taglen = strlen(tag); + + /* + * Search the tags file for the desired tag. + */ + while (fgets(tline, sizeof(tline), f) != NULL) + { + if (tline[0] == '!') + /* Skip header of extended format. */ + continue; + if (strncmp(tag, tline, taglen) != 0 || !WHITESP(tline[taglen])) + continue; + + /* + * Found it. + * The line contains the tag, the filename and the + * location in the file, separated by white space. + * The location is either a decimal line number, + * or a search pattern surrounded by a pair of delimiters. + * Parse the line and extract these parts. + */ + tagpattern = NULL; + + /* + * Skip over the whitespace after the tag name. + */ + p = skipsp(tline+taglen); + if (*p == '\0') + /* File name is missing! */ + continue; + + /* + * Save the file name. + * Skip over the whitespace after the file name. + */ + tagfile = p; + while (!WHITESP(*p) && *p != '\0') + p++; + *p++ = '\0'; + p = skipsp(p); + if (*p == '\0') + /* Pattern is missing! */ + continue; + + /* + * First see if it is a line number. + */ + tagendline = 0; + taglinenum = getnum(&p, 0, &err); + if (err) + { + /* + * No, it must be a pattern. + * Delete the initial "^" (if present) and + * the final "$" from the pattern. + * Delete any backslash in the pattern. + */ + taglinenum = 0; + search_char = *p++; + if (*p == '^') + p++; + tagpattern = p; + while (*p != search_char && *p != '\0') + { + if (*p == '\\') + p++; + p++; + } + tagendline = (p[-1] == '$'); + if (tagendline) + p--; + *p = '\0'; + } + tp = maketagent(tag, tagfile, taglinenum, tagpattern, tagendline); + TAG_INS(tp); + total++; + } + fclose(f); + if (total == 0) + return TAG_NOTAG; + curtag = taglist.tl_first; + curseq = 1; + return TAG_FOUND; +} + +/* + * Edit current tagged file. + */ + public int +edit_tagfile() +{ + if (curtag == NULL) + return (1); + return (edit(curtag->tag_file)); +} + +/* + * Search for a tag. + * This is a stripped-down version of search(). + * We don't use search() for several reasons: + * - We don't want to blow away any search string we may have saved. + * - The various regular-expression functions (from different systems: + * regcmp vs. re_comp) behave differently in the presence of + * parentheses (which are almost always found in a tag). + */ + static POSITION +ctagsearch() +{ + POSITION pos, linepos; + LINENUM linenum; + int len; + char *line; + + pos = ch_zero(); + linenum = find_linenum(pos); + + for (;;) + { + /* + * Get lines until we find a matching one or + * until we hit end-of-file. + */ + if (ABORT_SIGS()) + return (NULL_POSITION); + + /* + * Read the next line, and save the + * starting position of that line in linepos. + */ + linepos = pos; + pos = forw_raw_line(pos, &line, (int *)NULL); + if (linenum != 0) + linenum++; + + if (pos == NULL_POSITION) + { + /* + * We hit EOF without a match. + */ + error("Tag not found", NULL_PARG); + return (NULL_POSITION); + } + + /* + * If we're using line numbers, we might as well + * remember the information we have now (the position + * and line number of the current line). + */ + if (linenums) + add_lnum(linenum, pos); + + /* + * Test the line to see if we have a match. + * Use strncmp because the pattern may be + * truncated (in the tags file) if it is too long. + * If tagendline is set, make sure we match all + * the way to end of line (no extra chars after the match). + */ + len = strlen(curtag->tag_pattern); + if (strncmp(curtag->tag_pattern, line, len) == 0 && + (!curtag->tag_endline || line[len] == '\0' || line[len] == '\r')) + { + curtag->tag_linenum = find_linenum(linepos); + break; + } + } + + return (linepos); +} + +/******************************************************************************* + * gtags + */ + +/* + * Find tags in the GLOBAL's tag file. + * The findgtag() will try and load information about the requested tag. + * It does this by calling "global -x tag" and storing the parsed output + * for future use by gtagsearch(). + * Sets curtag to the first tag entry. + */ + static enum tag_result +findgtag(tag, type) + char *tag; /* tag to load */ + int type; /* tags type */ +{ + char buf[256]; + FILE *fp; + struct tag *tp; + + if (type != T_CTAGS_X && tag == NULL) + return TAG_NOFILE; + + cleantags(); + total = 0; + + /* + * If type == T_CTAGS_X then read ctags's -x format from stdin + * else execute global(1) and read from it. + */ + if (type == T_CTAGS_X) + { + fp = stdin; + /* Set tag default because we cannot read stdin again. */ + tags = "tags"; + } else + { +#if !HAVE_POPEN + return TAG_NOFILE; +#else + char *command; + char *flag; + char *qtag; + char *cmd = lgetenv("LESSGLOBALTAGS"); + + if (cmd == NULL || *cmd == '\0') + return TAG_NOFILE; + /* Get suitable flag value for global(1). */ + switch (type) + { + case T_GTAGS: + flag = "" ; + break; + case T_GRTAGS: + flag = "r"; + break; + case T_GSYMS: + flag = "s"; + break; + case T_GPATH: + flag = "P"; + break; + default: + return TAG_NOTYPE; + } + + /* Get our data from global(1). */ + qtag = shell_quote(tag); + if (qtag == NULL) + qtag = tag; + command = (char *) ecalloc(strlen(cmd) + strlen(flag) + + strlen(qtag) + 5, sizeof(char)); + sprintf(command, "%s -x%s %s", cmd, flag, qtag); + if (qtag != tag) + free(qtag); + fp = popen(command, "r"); + free(command); +#endif + } + if (fp != NULL) + { + while (fgets(buf, sizeof(buf), fp)) + { + char *name, *file, *line; + int len; + + if (sigs) + { +#if HAVE_POPEN + if (fp != stdin) + pclose(fp); +#endif + return TAG_INTR; + } + len = strlen(buf); + if (len > 0 && buf[len-1] == '\n') + buf[len-1] = '\0'; + else + { + int c; + do { + c = fgetc(fp); + } while (c != '\n' && c != EOF); + } + + if (getentry(buf, &name, &file, &line)) + { + /* + * Couldn't parse this line for some reason. + * We'll just pretend it never happened. + */ + break; + } + + /* Make new entry and add to list. */ + tp = maketagent(name, file, (LINENUM) atoi(line), NULL, 0); + TAG_INS(tp); + total++; + } + if (fp != stdin) + { + if (pclose(fp)) + { + curtag = NULL; + total = curseq = 0; + return TAG_NOFILE; + } + } + } + + /* Check to see if we found anything. */ + tp = taglist.tl_first; + if (tp == TAG_END) + return TAG_NOTAG; + curtag = tp; + curseq = 1; + return TAG_FOUND; +} + +static int circular = 0; /* 1: circular tag structure */ + +/* + * Return the filename required for the next gtag in the queue that was setup + * by findgtag(). The next call to gtagsearch() will try to position at the + * appropriate tag. + */ + static char * +nextgtag() +{ + struct tag *tp; + + if (curtag == NULL) + /* No tag loaded */ + return NULL; + + tp = curtag->next; + if (tp == TAG_END) + { + if (!circular) + return NULL; + /* Wrapped around to the head of the queue */ + curtag = taglist.tl_first; + curseq = 1; + } else + { + curtag = tp; + curseq++; + } + return (curtag->tag_file); +} + +/* + * Return the filename required for the previous gtag in the queue that was + * setup by findgtat(). The next call to gtagsearch() will try to position + * at the appropriate tag. + */ + static char * +prevgtag() +{ + struct tag *tp; + + if (curtag == NULL) + /* No tag loaded */ + return NULL; + + tp = curtag->prev; + if (tp == TAG_END) + { + if (!circular) + return NULL; + /* Wrapped around to the tail of the queue */ + curtag = taglist.tl_last; + curseq = total; + } else + { + curtag = tp; + curseq--; + } + return (curtag->tag_file); +} + +/* + * Position the current file at at what is hopefully the tag that was chosen + * using either findtag() or one of nextgtag() and prevgtag(). Returns -1 + * if it was unable to position at the tag, 0 if successful. + */ + static POSITION +gtagsearch() +{ + if (curtag == NULL) + return (NULL_POSITION); /* No gtags loaded! */ + return (find_pos(curtag->tag_linenum)); +} + +/* + * The getentry() parses both standard and extended ctags -x format. + * + * [standard format] + * <tag> <lineno> <file> <image> + * +------------------------------------------------ + * |main 30 main.c main(argc, argv) + * |func 21 subr.c func(arg) + * + * The following commands write this format. + * o Traditinal Ctags with -x option + * o Global with -x option + * See <http://www.gnu.org/software/global/global.html> + * + * [extended format] + * <tag> <type> <lineno> <file> <image> + * +---------------------------------------------------------- + * |main function 30 main.c main(argc, argv) + * |func function 21 subr.c func(arg) + * + * The following commands write this format. + * o Exuberant Ctags with -x option + * See <http://ctags.sourceforge.net> + * + * Returns 0 on success, -1 on error. + * The tag, file, and line will each be NUL-terminated pointers + * into buf. + */ + static int +getentry(buf, tag, file, line) + char *buf; /* standard or extended ctags -x format data */ + char **tag; /* name of the tag we actually found */ + char **file; /* file in which to find this tag */ + char **line; /* line number of file where this tag is found */ +{ + char *p = buf; + + for (*tag = p; *p && !IS_SPACE(*p); p++) /* tag name */ + ; + if (*p == 0) + return (-1); + *p++ = 0; + for ( ; *p && IS_SPACE(*p); p++) /* (skip blanks) */ + ; + if (*p == 0) + return (-1); + /* + * If the second part begin with other than digit, + * it is assumed tag type. Skip it. + */ + if (!IS_DIGIT(*p)) + { + for ( ; *p && !IS_SPACE(*p); p++) /* (skip tag type) */ + ; + for (; *p && IS_SPACE(*p); p++) /* (skip blanks) */ + ; + } + if (!IS_DIGIT(*p)) + return (-1); + *line = p; /* line number */ + for (*line = p; *p && !IS_SPACE(*p); p++) + ; + if (*p == 0) + return (-1); + *p++ = 0; + for ( ; *p && IS_SPACE(*p); p++) /* (skip blanks) */ + ; + if (*p == 0) + return (-1); + *file = p; /* file name */ + for (*file = p; *p && !IS_SPACE(*p); p++) + ; + if (*p == 0) + return (-1); + *p = 0; + + /* value check */ + if (strlen(*tag) && strlen(*line) && strlen(*file) && atoi(*line) > 0) + return (0); + return (-1); +} + +#endif |