diff options
Diffstat (limited to 'usr.bin/vim/tag.c')
-rw-r--r-- | usr.bin/vim/tag.c | 1426 |
1 files changed, 1426 insertions, 0 deletions
diff --git a/usr.bin/vim/tag.c b/usr.bin/vim/tag.c new file mode 100644 index 00000000000..6880db5cbd1 --- /dev/null +++ b/usr.bin/vim/tag.c @@ -0,0 +1,1426 @@ +/* $OpenBSD: tag.c,v 1.1 1996/09/07 21:40:24 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * Code to handle tags and the tag stack + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" + +static int get_tagfname __ARGS((int, char_u *)); + +#ifdef EMACS_TAGS +static int parse_tag_line __ARGS((char_u *, int, + char_u **, char_u **, char_u **, char_u **, char_u **)); +static int jumpto_tag __ARGS((char_u *, int, char_u *, char_u *)); +#else +static int parse_tag_line __ARGS((char_u *, + char_u **, char_u **, char_u **, char_u **, char_u **)); +static int jumpto_tag __ARGS((char_u *, char_u *)); +#endif +static int test_for_static __ARGS((char_u **, char_u *, char_u *, char_u *)); +static char_u *expand_rel_name __ARGS((char_u *fname, char_u *tag_fname)); +static void simplify_filename __ARGS((char_u *filename)); +#ifdef EMACS_TAGS +static int test_for_current __ARGS((int, char_u *, char_u *, char_u *)); +#else +static int test_for_current __ARGS((char_u *, char_u *, char_u *)); +#endif + +static char_u *bottommsg = (char_u *)"at bottom of tag stack"; +static char_u *topmsg = (char_u *)"at top of tag stack"; + +/* + * Jump to tag; handling of tag stack + * + * *tag != NUL (:tag): jump to new tag, add to tag stack + * type == 1 (:pop) || type == 2 (CTRL-T): jump to old position + * type == 0 (:tag): jump to old tag + */ + void +do_tag(tag, type, count) + char_u *tag; + int type; + int count; +{ + int i; + struct taggy *tagstack = curwin->w_tagstack; + int tagstackidx = curwin->w_tagstackidx; + int tagstacklen = curwin->w_tagstacklen; + int oldtagstackidx = tagstackidx; + + if (*tag != NUL) /* new pattern, add to the stack */ + { + /* + * if last used entry is not at the top, delete all tag stack entries + * above it. + */ + while (tagstackidx < tagstacklen) + vim_free(tagstack[--tagstacklen].tagname); + + /* if tagstack is full: remove oldest entry */ + if (++tagstacklen > TAGSTACKSIZE) + { + tagstacklen = TAGSTACKSIZE; + vim_free(tagstack[0].tagname); + for (i = 1; i < tagstacklen; ++i) + tagstack[i - 1] = tagstack[i]; + --tagstackidx; + } + + /* + * put the tag name in the tag stack + * the position is added below + */ + tagstack[tagstackidx].tagname = strsave(tag); + } + else if (tagstacklen == 0) /* empty stack */ + { + EMSG("tag stack empty"); + goto end_do_tag; + } + else if (type) /* go to older position */ + { + if ((tagstackidx -= count) < 0) + { + emsg(bottommsg); + if (tagstackidx + count == 0) + { + /* We did ^T (or <num>^T) from the bottom of the stack */ + tagstackidx = 0; + goto end_do_tag; + } + /* We weren't at the bottom of the stack, so jump all the way to + * the bottom. + */ + tagstackidx = 0; + } + else if (tagstackidx >= tagstacklen) /* must have been count == 0 */ + { + emsg(topmsg); + goto end_do_tag; + } + if (tagstack[tagstackidx].fmark.fnum != curbuf->b_fnum) + { + /* + * Jump to other file. If this fails (e.g. because the file was + * changed) keep original position in tag stack. + */ + if (buflist_getfile(tagstack[tagstackidx].fmark.fnum, + tagstack[tagstackidx].fmark.mark.lnum, GETF_SETMARK) == FAIL) + { + tagstackidx = oldtagstackidx; /* back to old position */ + goto end_do_tag; + } + } + else + curwin->w_cursor.lnum = tagstack[tagstackidx].fmark.mark.lnum; + curwin->w_cursor.col = tagstack[tagstackidx].fmark.mark.col; + curwin->w_set_curswant = TRUE; + goto end_do_tag; + } + else /* go to newer pattern */ + { + if ((tagstackidx += count - 1) >= tagstacklen) + { + /* + * beyond the last one, just give an error message and go to the + * last one + */ + tagstackidx = tagstacklen - 1; + emsg(topmsg); + } + else if (tagstackidx < 0) /* must have been count == 0 */ + { + emsg(bottommsg); + tagstackidx = 0; + goto end_do_tag; + } + } + /* + * For :tag [arg], remember position before the jump + */ + if (type == 0) + { + tagstack[tagstackidx].fmark.mark = curwin->w_cursor; + tagstack[tagstackidx].fmark.fnum = curbuf->b_fnum; + } + /* curwin will change in the call to find_tags() if ^W^] was used -- webb */ + curwin->w_tagstackidx = tagstackidx; + curwin->w_tagstacklen = tagstacklen; + if (find_tags(tagstack[tagstackidx].tagname, NULL, NULL, NULL, FALSE) > 0) + ++tagstackidx; + +end_do_tag: + curwin->w_tagstackidx = tagstackidx; + curwin->w_tagstacklen = tagstacklen; +} + +/* + * Print the tag stack + */ + void +do_tags() +{ + int i; + char_u *name; + struct taggy *tagstack = curwin->w_tagstack; + int tagstackidx = curwin->w_tagstackidx; + int tagstacklen = curwin->w_tagstacklen; + + set_highlight('t'); /* Highlight title */ + start_highlight(); + MSG_OUTSTR("\n # TO tag FROM line in file"); + stop_highlight(); + for (i = 0; i < tagstacklen; ++i) + { + if (tagstack[i].tagname != NULL) + { + name = fm_getname(&(tagstack[i].fmark)); + if (name == NULL) /* file name not available */ + continue; + + msg_outchar('\n'); + sprintf((char *)IObuff, "%c%2d %-15s %4ld %s", + i == tagstackidx ? '>' : ' ', + i + 1, + tagstack[i].tagname, + tagstack[i].fmark.mark.lnum, + name); + msg_outtrans(IObuff); + } + flushbuf(); /* show one line at a time */ + } + if (tagstackidx == tagstacklen) /* idx at top of stack */ + MSG_OUTSTR("\n>"); +} + +/* + * find_tags() - goto tag or search for tags in tags files + * + * If "tag" is not NULL, search for a single tag and jump to it. + * return FAIL for failure, OK for success + * If "tag" is NULL, find all tags matching the regexp given with 'prog'. + * return FAIL if search completely failed, OK otherwise. + * + * There is a priority in which type of tag is recognized. It is computed + * from the PRI_ defines below. + * + * 6. A static or global tag with a full matching tag for the current file. + * 5. A global tag with a full matching tag for another file. + * 4. A static tag with a full matching tag for another file. + * 2. A static or global tag with an ignore-case matching tag for the + * current file. + * 1. A global tag with an ignore-case matching tag for another file. + * 0. A static tag with an ignore-case matching tag for another file. + * + * Tags in an emacs-style tags file are always global. + */ +#define PRI_GLOBAL 1 /* global or emacs tag */ +#define PRI_CURRENT 2 /* tag for current file */ +#define PRI_FULL_MATCH 4 /* case of tag matches */ + + int +find_tags(tag, prog, num_file, file, help_only) + char_u *tag; /* NULL or tag to search for */ + regexp *prog; /* regexp program or NULL */ + int *num_file; /* return value: number of matches found */ + char_u ***file; /* return value: array of matches found */ + int help_only; /* if TRUE: only tags for help files */ +{ + FILE *fp; + char_u *lbuf; /* line buffer */ + char_u *tag_fname; /* name of tag file */ + int first_file; /* trying first tag file */ + char_u *tagname, *tagname_end; /* name of tag in current line */ + char_u *fname, *fname_end; /* fname in current line */ + int did_open = FALSE; /* did open a tag file */ + int stop_searching = FALSE; /* stop when match found or error */ + int retval = FAIL; /* return value */ + int is_static; /* current tag line is static */ + int is_current; /* file name matches */ + register char_u *p; +#ifdef EMACS_TAGS + char_u *ebuf; /* aditional buffer for etag fname */ + int is_etag; /* current file is emaces style */ +#endif + +/* + * Variables used only when "tag" != NULL + */ + int taglen = 0; /* init for GCC */ + int cmplen; + int full_match; + int icase_match; + int priority; /* priority of current line */ + + char_u *bestmatch_line = NULL; /* saved line for best match found so + far */ + char_u *bestmatch_tag_fname = NULL; + /* copy of tag_fname for best match */ + int bestmatch_priority = 0; /* best match priority */ + +#ifdef EMACS_TAGS + /* + * Stack for included emacs-tags file. + * It has a fixed size, to truncate cyclic includes. jw + */ +# define INCSTACK_SIZE 42 + struct + { + FILE *fp; + char_u *tag_fname; + } incstack[INCSTACK_SIZE]; + + int incstack_idx = 0; /* index in incstack */ + char_u *bestmatch_ebuf = NULL; /* copy of ebuf for best match */ + int bestmatch_is_etag = FALSE; /* copy of is_etag for best match */ +#endif + +/* + * Variables used when "tag" == NULL + */ + char_u **matches = NULL; /* array of matches found */ + char_u **new_matches; + int match_limit = 100; /* max. number of matches stored */ + int match_count = 0; /* number of matches found */ + int i; + int help_save; + + help_save = curbuf->b_help; +/* + * Allocate memory for the buffers that are used + */ + lbuf = alloc(LSIZE); +#ifdef EMACS_TAGS + ebuf = alloc(LSIZE); +#endif + tag_fname = alloc(LSIZE + 1); + /* check for out of memory situation */ + if ((tag == NULL && prog == NULL) || lbuf == NULL || tag_fname == NULL +#ifdef EMACS_TAGS + || ebuf == NULL +#endif + ) + goto findtag_end; + if (tag == NULL) + { + matches = (char_u **)alloc((unsigned)(match_limit * sizeof(char_u *))); + if (matches == NULL) + goto findtag_end; + } + +/* + * Initialize a few variables + */ + if (tag != NULL) + { + taglen = STRLEN(tag); + if (p_tl != 0 && taglen > p_tl) /* adjust for 'taglength' */ + taglen = p_tl; + } + else if (help_only) /* want tags from help file */ + curbuf->b_help = TRUE; + +/* + * Try tag file names from tags option one by one. + */ + for (first_file = TRUE; get_tagfname(first_file, tag_fname) == OK; + first_file = FALSE) + { + /* + * A file that doesn't exist is silently ignored. + */ + if ((fp = fopen((char *)tag_fname, "r")) == NULL) + continue; + did_open = TRUE; /* remember that we found at least one file */ + +#ifdef EMACS_TAGS + is_etag = 0; /* default is: not emacs style */ +#endif + /* + * Read and parse the lines in the file one by one + */ + while (!got_int) + { + line_breakcheck(); + + if (vim_fgets(lbuf, LSIZE, fp)) +#ifdef EMACS_TAGS + if (incstack_idx) /* this was an included file */ + { + --incstack_idx; + fclose(fp); /* end of this file ... */ + fp = incstack[incstack_idx].fp; + STRCPY(tag_fname, incstack[incstack_idx].tag_fname); + vim_free(incstack[incstack_idx].tag_fname); + is_etag = 1; /* (only etags can include) */ + continue; /* ... continue with parent file */ + } + else +#endif + break; /* end of file */ + +#ifdef EMACS_TAGS + /* + * Emacs tags line with CTRL-L: New file name on next line. + * The file name is followed by a ','. + */ + if (*lbuf == Ctrl('L')) /* remember etag filename in ebuf */ + { + is_etag = 1; + if (!vim_fgets(ebuf, LSIZE, fp)) + { + for (p = ebuf; *p && *p != ','; p++) + ; + *p = NUL; + + /* + * atoi(p+1) is the number of bytes before the next ^L + * unless it is an include statement. + */ + if (STRNCMP(p + 1, "include", 7) == 0 && + incstack_idx < INCSTACK_SIZE) + { + if ((incstack[incstack_idx].tag_fname = + strsave(tag_fname)) != NULL) + { + incstack[incstack_idx].fp = fp; + if ((fp = fopen((char *)ebuf, "r")) == NULL) + { + fp = incstack[incstack_idx].fp; + vim_free(incstack[incstack_idx].tag_fname); + } + else + { + STRCPY(tag_fname, ebuf); + ++incstack_idx; + } + is_etag = 0; /* we can include anything */ + } + } + } + continue; + } +#endif + + /* + * Figure out where the different strings are in this line. + * For "normal" tags: Do a quick check if the tag matches. + * This speeds up tag searching a lot! + */ + if (tag != NULL +#ifdef EMACS_TAGS + && !is_etag +#endif + ) + { + tagname = lbuf; + fname = NULL; + for (tagname_end = lbuf; *tagname_end && + !vim_iswhite(*tagname_end); ++tagname_end) + { + if (*tagname_end == ':') + { + if (fname == NULL) + fname = skipwhite(skiptowhite(tagname_end)); + if (fnamencmp(lbuf, fname, tagname_end - lbuf) == 0 && + vim_iswhite(fname[tagname_end - lbuf])) + tagname = tagname_end + 1; + } + } + + /* + * Skip this line if the lenght of the tag is different. + */ + cmplen = tagname_end - tagname; + if (p_tl != 0 && cmplen > p_tl) /* adjust for 'taglength' */ + cmplen = p_tl; + if (taglen != cmplen) + continue; + + /* + * Skip this line if the tag does not match (ignoring case). + */ + if (vim_strnicmp(tagname, tag, (size_t)cmplen)) + continue; + + /* + * This is a useful tag, isolate the filename. + */ + if (fname == NULL) + fname = skipwhite(skiptowhite(tagname_end)); + fname_end = skiptowhite(fname); + if (*fname_end == NUL) + i = FAIL; + else + i = OK; + } + else + i = parse_tag_line(lbuf, +#ifdef EMACS_TAGS + is_etag, +#endif + &tagname, &tagname_end, &fname, &fname_end, NULL); + if (i == FAIL) + { + EMSG2("Format error in tags file \"%s\"", tag_fname); + stop_searching = TRUE; + break; + } + +#ifdef EMACS_TAGS + is_static = FALSE; + if (!is_etag) /* emacs tags are never static */ +#endif + is_static = test_for_static(&tagname, tagname_end, + fname, fname_end); +#ifdef EMACS_TAGS + if (is_etag) + fname = ebuf; +#endif + /* + * "tag" == NULL: find tags matching regexp "prog" + */ + if (tag == NULL) + { + *tagname_end = NUL; + if (vim_regexec(prog, tagname, TRUE)) + { + is_current = test_for_current( +#ifdef EMACS_TAGS + is_etag, +#endif + fname, fname_end, tag_fname); + if (!is_static || is_current) + { + /* + * Found a match, add it to matches[]. + * May need to make matches[] larger. + */ + if (match_count == match_limit) + { + match_limit += 100; + new_matches = (char_u **)alloc( + (unsigned)(match_limit * sizeof(char_u *))); + if (new_matches == NULL) + { + /* Out of memory! Just forget about the rest + * of the matches. */ + retval = OK; + stop_searching = TRUE; + break; + } + for (i = 0; i < match_count; i++) + new_matches[i] = matches[i]; + vim_free(matches); + matches = new_matches; + } + if (help_only) + { + int len; + + /* + * Append the help-heuristic number after the + * tagname, for sorting it later. + */ + len = STRLEN(tagname); + p = alloc(len + 10); + if (p != NULL) + { + STRCPY(p, tagname); + sprintf((char *)p + len + 1, "%06d", + help_heuristic(tagname, + (int)(prog->startp[0] - tagname))); + } + matches[match_count++] = p; + } + else + matches[match_count++] = strsave(tagname); + } + } + } + /* + * "tag" != NULL: find tag and jump to it + */ + else + { + /* + * If tag length does not match, skip the rest + */ + cmplen = tagname_end - tagname; + if (p_tl != 0 && cmplen > p_tl) /* adjust for 'taglength' */ + cmplen = p_tl; + if (taglen == cmplen) + { + /* + * Check for match (ignoring case). + */ + icase_match = (vim_strnicmp(tagname, tag, + (size_t)cmplen) == 0); + if (icase_match) /* Tag matches somehow */ + { + /* + * If it's a full match for the current file, jump to + * it now. + */ + full_match = (STRNCMP(tagname, tag, cmplen) == 0); + is_current = test_for_current( +#ifdef EMACS_TAGS + is_etag, +#endif + fname, fname_end, tag_fname); + if (full_match && is_current) + { + retval = jumpto_tag(lbuf, +#ifdef EMACS_TAGS + is_etag, ebuf, +#endif + tag_fname); + stop_searching = TRUE; + break; + } + + /* + * If the priority of the current line is higher than + * the best match so far, store it as the best match + */ + if (full_match) + priority = PRI_FULL_MATCH; + else + priority = 0; + if (is_current) + priority += PRI_CURRENT; + if (!is_static) + priority += PRI_GLOBAL; + + if (priority > bestmatch_priority) + { + vim_free(bestmatch_line); + bestmatch_line = strsave(lbuf); + vim_free(bestmatch_tag_fname); + bestmatch_tag_fname = strsave(tag_fname); + bestmatch_priority = priority; +#ifdef EMACS_TAGS + bestmatch_is_etag = is_etag; + if (is_etag) + { + vim_free(bestmatch_ebuf); + bestmatch_ebuf = strsave(ebuf); + } +#endif + } + } + } + } + } + fclose(fp); +#ifdef EMACS_TAGS + while (incstack_idx) + { + --incstack_idx; + fclose(incstack[incstack_idx].fp); + vim_free(incstack[incstack_idx].tag_fname); + } +#endif + if (stop_searching) + break; + + /* + * Stop searching if a tag was found in the current tags file and + * we got a global match with matching case or 'ignorecase' is set. + */ + if (tag != NULL && bestmatch_line != NULL && + bestmatch_priority >= (p_ic ? 0 : PRI_FULL_MATCH) + PRI_GLOBAL) + break; + } + + if (!stop_searching) + { + if (!did_open) /* never opened any tags file */ + EMSG("No tags file"); + else if (tag == NULL) + { + retval = OK; /* It's OK even when no tag found */ + } + else + { + /* + * If we didn't find a static full match, use the best match found. + */ + if (bestmatch_line != NULL) + { + if (bestmatch_priority < PRI_FULL_MATCH) + { + MSG("Only found tag with different case!"); + if (!msg_scrolled) + { + flushbuf(); + mch_delay(1000L, TRUE); + } + } + retval = jumpto_tag(bestmatch_line, +#ifdef EMACS_TAGS + bestmatch_is_etag, bestmatch_ebuf, +#endif + bestmatch_tag_fname); + } + else + EMSG("tag not found"); + } + } + +findtag_end: + vim_free(lbuf); + vim_free(tag_fname); + vim_free(bestmatch_line); + vim_free(bestmatch_tag_fname); +#ifdef EMACS_TAGS + vim_free(ebuf); + vim_free(bestmatch_ebuf); +#endif + + if (tag == NULL) + { + if (retval == FAIL) /* free all matches found */ + while (match_count > 0) + vim_free(matches[--match_count]); + if (match_count == 0) /* nothing found, free matches[] */ + { + vim_free(matches); + matches = NULL; + } + *file = matches; + *num_file = match_count; + } + curbuf->b_help = help_save; + + return retval; +} + +/* + * Get the next name of a tag file from the tag file list. + * For help files, use "vim_tags" file only. + * + * Return FAIL if no more tag file names, OK otherwise. + */ + static int +get_tagfname(first, buf) + int first; /* TRUE when first file name is wanted */ + char_u *buf; /* pointer to buffer of LSIZE chars */ +{ + static char_u *np = NULL; + char_u *fname; + size_t path_len, fname_len; + /* + * A list is made of the files that have been visited. + */ + struct visited + { + struct visited *v_next; +#if defined(UNIX) + struct stat v_st; +#else + char_u v_fname[1]; /* actually longer */ +#endif + }; + static struct visited *first_visited = NULL; + struct visited *vp; +#if defined(UNIX) + struct stat st; +#endif + + if (first) + { + np = p_tags; + while (first_visited != NULL) + { + vp = first_visited->v_next; + vim_free(first_visited); + first_visited = vp; + } + } + + if (np == NULL) /* tried allready (or bogus call) */ + return FAIL; + + /* + * For a help window only try the file 'vim_tags' in the same + * directory as 'helpfile'. + */ + if (curbuf->b_help) + { + path_len = gettail(p_hf) - p_hf; + if (path_len + 9 >= LSIZE) + return FAIL; + vim_memmove(buf, p_hf, path_len); + STRCPY(buf + path_len, "vim_tags"); + + np = NULL; /* try only once */ + } + + else + { + /* + * Loop until we have found a file name that can be used. + */ + for (;;) + { + if (*np == NUL) /* tried all possibilities */ + return FAIL; + + /* + * Copy next file name into buf. + */ + (void)copy_option_part(&np, buf, LSIZE, " ,"); + + /* + * Tag file name starting with "./": Replace '.' with path of + * current file. + */ + if (buf[0] == '.' && ispathsep(buf[1])) + { + if (curbuf->b_filename == NULL) /* skip if no filename */ + continue; + + path_len = gettail(curbuf->b_filename) - curbuf->b_filename; + fname = buf + 1; + while (ispathsep(*fname)) /* skip '/' and the like */ + ++fname; + fname_len = STRLEN(fname); + if (fname_len + path_len + 1 > LSIZE) + continue; + vim_memmove(buf + path_len, fname, fname_len + 1); + vim_memmove(buf, curbuf->b_filename, path_len); + } + + /* + * Check if this tags file has been used already. + * If file doesn't exist, skip it. + */ +#if defined(UNIX) + if (stat((char *)buf, &st) < 0) +#else + if (FullName(buf, NameBuff, MAXPATHL, TRUE) == FAIL) +#endif + continue; + + for (vp = first_visited; vp != NULL; vp = vp->v_next) +#if defined(UNIX) + if (vp->v_st.st_dev == st.st_dev && + vp->v_st.st_ino == st.st_ino) +#else + if (fnamecmp(vp->v_fname, NameBuff) == 0) +#endif + break; + + if (vp != NULL) /* already visited, skip it */ + continue; + + /* + * Found the next name. Add it to the list of visited files. + */ +#if defined(UNIX) + vp = (struct visited *)alloc((unsigned)sizeof(struct visited)); +#else + vp = (struct visited *)alloc((unsigned)(sizeof(struct visited) + + STRLEN(NameBuff))); +#endif + if (vp != NULL) + { +#if defined(UNIX) + vp->v_st = st; +#else + STRCPY(vp->v_fname, NameBuff); +#endif + vp->v_next = first_visited; + first_visited = vp; + } + break; + } + } + return OK; +} + +/* + * Parse one line from the tags file. Find start/end of tag name, start/end of + * file name and start of search pattern. + * + * If is_etag is TRUE, fname and fname_end are not set. + * If command == NULL it is not set. + * + * Return FAIL if there is a format error in this line, OK otherwise. + */ + static int +parse_tag_line(lbuf, +#ifdef EMACS_TAGS + is_etag, +#endif + tagname, tagname_end, fname, fname_end, command) + char_u *lbuf; +#ifdef EMACS_TAGS + int is_etag; +#endif + char_u **tagname; + char_u **tagname_end; + char_u **fname; + char_u **fname_end; + char_u **command; +{ + char_u *p; + +#ifdef EMACS_TAGS + char_u *p_7f; + + if (is_etag) + { + /* + * There are two formats for an emacs tag line: + * 1: struct EnvBase ^?EnvBase^A139,4627 + * 2: #define ARPB_WILD_WORLD ^?153,5194 + */ + p_7f = vim_strchr(lbuf, 0x7f); + if (p_7f == NULL) + return FAIL; + /* find start of line number */ + for (p = p_7f + 1; *p < '0' || *p > '9'; ++p) + if (*p == NUL) + return FAIL; + if (command != NULL) + *command = p; + + /* first format: explicit tagname given */ + if (p[-1] == Ctrl('A')) + { + *tagname = p_7f + 1; + *tagname_end = p - 1; + } + else + /* second format: isolate tagname */ + { + /* find end of tagname */ + for (p = p_7f - 1; *p == ' ' || *p == '\t' || + *p == '(' || *p == ';'; --p) + if (p == lbuf) + return FAIL; + *tagname_end = p + 1; + while (p >= lbuf && *p != ' ' && *p != '\t') + --p; + *tagname = p + 1; + } + } + else + { +#endif + /* Isolate the tagname, from lbuf up to the first white */ + *tagname = lbuf; + p = skiptowhite(lbuf); + if (*p == NUL) + return FAIL; + *tagname_end = p; + + /* Isolate file name, from first to second white space */ + p = skipwhite(p); + *fname = p; + p = skiptowhite(p); + if (*p == NUL) + return FAIL; + *fname_end = p; + + /* find start of search command, after second white space */ + if (command != NULL) + { + p = skipwhite(p); + if (*p == NUL) + return FAIL; + *command = p; + } +#ifdef EMACS_TAGS + } +#endif + + return OK; +} + +/* + * Check if tagname is a static tag + * + * Static tags produced by the ctags program have the + * format: 'file:tag file /pattern'. + * This is only recognized when both occurences of 'file' + * are the same, to avoid recognizing "string::string" or + * ":exit". + * + * Return TRUE if it is a static tag and adjust *tagname to the real tag. + * Return FALSE if it is not a static tag. + */ + static int +test_for_static(tagname, tagname_end, fname, fname_end) + char_u **tagname; + char_u *tagname_end; + char_u *fname; + char_u *fname_end; +{ + char_u *p; + + p = *tagname + (fname_end - fname); + if (p < tagname_end && *p == ':' && + fnamencmp(*tagname, fname, fname_end - fname) == 0) + { + *tagname = p + 1; + return TRUE; + } + return FALSE; +} + +/* + * Jump to a tag that has been found in one of the tag files + */ + static int +jumpto_tag(lbuf, +#ifdef EMACS_TAGS + is_etag, etag_fname, +#endif + tag_fname) + char_u *lbuf; /* line from the tags file for this tag */ +#ifdef EMACS_TAGS + int is_etag; /* TRUE if it's from an emacs tags file */ + char_u *etag_fname; /* file name for tag if is_etag is TRUE */ +#endif + char_u *tag_fname; /* file name of the tags file itself */ +{ + int save_secure; + int save_p_ws, save_p_scs, save_p_ic; + char_u *str; + char_u *pbuf; /* search pattern buffer */ + char_u *p; + char_u *expanded_fname = NULL; + char_u *tagname, *tagname_end; + char_u *fname, *fname_end; + char_u *orig_fname; + int retval = FAIL; + int getfile_result; + int search_options; + + pbuf = alloc(LSIZE); + + if (pbuf == NULL +#ifdef EMACS_TAGS + || (is_etag && etag_fname == NULL) +#endif + || tag_fname == NULL) + goto erret; + + /* + * find the search pattern (caller should check it is there) + */ + if (parse_tag_line(lbuf, +#ifdef EMACS_TAGS + is_etag, +#endif + &tagname, &tagname_end, &fname, &fname_end, &str) == FAIL) + goto erret; + orig_fname = fname; /* remember for test_for_static() below */ + +#ifdef EMACS_TAGS + if (is_etag) + fname = etag_fname; + else +#endif + *fname_end = NUL; + + /* + * If the command is a string like "/^function fname" + * scan through the search string. If we see a magic + * char, we have to quote it. This lets us use "real" + * implementations of ctags. + */ + if (*str == '/' || *str == '?') + { + p = pbuf; + *p++ = *str++; /* copy the '/' or '?' */ + if (*str == '^') + *p++ = *str++; /* copy the '^' */ + + while (*str) + { + switch (*str) + { + /* Always remove '\' before '('. + * Remove a '\' befor '*' if 'nomagic'. + * Otherwise just copy the '\' and don't look at the + * next character + */ + case '\\': if (str[1] == '(' || (!p_magic && str[1] == '*')) + ++str; + else + *p++ = *str++; + break; + + case '\r': + case '\n': *str = pbuf[0]; /* copy '/' or '?' */ + str[1] = NUL; /* delete NL after CR */ + break; + + /* + * if string ends in search character: skip it + * else escape it with '\' + */ + case '/': + case '?': if (*str != pbuf[0]) /* not the search char */ + break; + /* last char */ + if (str[1] == '\n' || str[1] == '\r') + { + ++str; + continue; + } + case '[': + if (!p_magic) + break; + case '^': + case '*': + case '~': + case '.': *p++ = '\\'; + break; + } + *p++ = *str++; + } + } + else /* not a search command, just copy it */ + { + for (p = pbuf; *str && *str != '\n'; ) + { +#ifdef EMACS_TAGS + if (is_etag && *str == ',') /* stop at ',' after line number */ + break; +#endif + *p++ = *str++; + } + } + *p = NUL; + + /* + * expand filename (for environment variables) + */ + expanded_fname = ExpandOne((char_u *)fname, NULL, WILD_LIST_NOTFOUND, + WILD_EXPAND_FREE); + if (expanded_fname != NULL) + fname = expanded_fname; + + /* + * if 'tagrelative' option set, may change file name + */ + fname = expand_rel_name(fname, tag_fname); + + /* + * check if file for tag exists before abandoning current file + */ + if (getperm(fname) < 0) + { + EMSG2("File \"%s\" does not exist", fname); + goto erret; + } + + ++RedrawingDisabled; + /* + * if it was a CTRL-W CTRL-] command split window now + */ + if (postponed_split) + win_split(0, FALSE); + /* + * A :ta from a help file will keep the b_help flag set. + */ + keep_help_flag = curbuf->b_help; + getfile_result = getfile(0, fname, NULL, TRUE, (linenr_t)0); + + if (getfile_result <= 0) /* got to the right file */ + { + curwin->w_set_curswant = TRUE; + postponed_split = FALSE; + + save_secure = secure; + secure = 1; + /* + * If 'cpoptions' contains 't', store the search pattern for the "n" + * command. If 'cpoptions' does not contain 't', the search pattern + * is not stored. + */ + if (vim_strchr(p_cpo, CPO_TAGPAT) != NULL) + search_options = 0; + else + search_options = SEARCH_KEEP; + + /* + * if the command is a search, try here + * + * Rather than starting at line one, just turn wrap-scan + * on temporarily, this ensures that tags on line 1 will + * be found, and makes sure our guess searches search the + * whole file when repeated -- webb. + * Also reset 'smartcase' for the search, since the search + * pattern was not typed by the user. + */ + if (pbuf[0] == '/' || pbuf[0] == '?') + { + save_p_ws = p_ws; + save_p_ic = p_ic; + save_p_scs = p_scs; + p_ws = TRUE; /* Switch wrap-scan on temporarily */ + p_ic = FALSE; /* don't ignore case now */ + p_scs = FALSE; + add_to_history(1, pbuf + 1); /* put pattern in search history */ + + if (do_search(pbuf[0], pbuf + 1, (long)1, search_options)) + retval = OK; + else + { + register int notfound = FALSE; + + /* + * try again, ignore case now + */ + p_ic = TRUE; + if (!do_search(pbuf[0], pbuf + 1, (long)1, search_options)) + { + /* + * Failed to find pattern, take a guess: "^func (" + */ + (void)test_for_static(&tagname, tagname_end, + orig_fname, fname_end); + *tagname_end = NUL; + sprintf((char *)pbuf, "^%s[ \t]*(", tagname); + if (!do_search('/', pbuf, (long)1, search_options)) + { + /* Guess again: "^char * func (" */ + sprintf((char *)pbuf, "^[#a-zA-Z_].*%s[ \t]*(", + tagname); + if (!do_search('/', pbuf, (long)1, search_options)) + notfound = TRUE; + } + } + if (notfound) + EMSG("Can't find tag pattern"); + else + { + MSG("Couldn't find tag, just guessing!"); + if (!msg_scrolled) + { + flushbuf(); + mch_delay(1000L, TRUE); + } + retval = OK; + } + } + p_ws = save_p_ws; + p_ic = save_p_ic; + p_scs = save_p_scs; + } + else + { /* start command in line 1 */ + curwin->w_cursor.lnum = 1; + do_cmdline(pbuf, TRUE, TRUE); + retval = OK; + } + + if (secure == 2) /* done something that is not allowed */ + wait_return(TRUE); + secure = save_secure; + + /* + * Print the file message after redraw if jumped to another file. + * Don't do this for help files (avoid a hit-return message). + */ + if (getfile_result == -1) + { + if (!curbuf->b_help) + need_fileinfo = TRUE; + retval = OK; /* always return OK if jumped to another + file (at least we found the file!) */ + } + + /* + * For a help buffer: Put the cursor line at the top of the window, + * the help subject will be below it. + */ + if (curbuf->b_help) + { + curwin->w_topline = curwin->w_cursor.lnum; + comp_Botline(curwin); + cursupdate(); /* take care of 'scrolloff' */ + updateScreen(NOT_VALID); + } + --RedrawingDisabled; + } + else + { + --RedrawingDisabled; + if (postponed_split) /* close the window */ + { + close_window(curwin, FALSE); + postponed_split = FALSE; + } + } + +erret: + vim_free(pbuf); + vim_free(expanded_fname); + + return retval; +} + +/* + * If 'tagrelative' option set, change fname (name of file containing tag) + * according to tag_fname (name of tag file containing fname). + */ + static char_u * +expand_rel_name(fname, tag_fname) + char_u *fname; + char_u *tag_fname; +{ + char_u *p; + + if ((p_tr || curbuf->b_help) && !isFullName(fname) && + (p = gettail(tag_fname)) != tag_fname) + { + STRCPY(NameBuff, tag_fname); + STRNCPY(NameBuff + (p - tag_fname), fname, + MAXPATHL - (p - tag_fname)); + /* + * Translate names like "src/a/../b/file.c" into "src/b/file.c". + */ + simplify_filename(NameBuff); + fname = NameBuff; + } + return fname; +} + +/* + * Moves the tail part of the path (including the terminating NUL) pointed to + * by "tail" to the new location pointed to by "here". This should accomodate + * an overlapping move. + */ +#define movetail(here, tail) vim_memmove(here, tail, STRLEN(tail) + (size_t)1) + +/* + * For MS-DOS we should check for backslash too, but that is complicated. + */ +#define DIR_SEP '/' /* the directory separator character */ + +/* + * Converts a filename into a canonical form. It simplifies a filename into + * its simplest form by stripping out unneeded components, if any. The + * resulting filename is simplified in place and will either be the same + * length as that supplied, or shorter. + */ + static void +simplify_filename(filename) + char_u *filename; +{ + int absolute = FALSE; + int components = 0; + char_u *p, *tail; + + p = filename; + if (*p == DIR_SEP) + { + absolute = TRUE; + ++p; + } + do + { + /* Always leave "p" pointing to character following next "/". */ + if (*p == DIR_SEP) + movetail(p, p+1); /* strip duplicate "/" */ + else if (STRNCMP(p, "./", 2) == 0) + movetail(p, p+2); /* strip "./" */ + else if (STRNCMP(p, "../", 3) == 0) + { + if (components > 0) /* strip any prev. component */ + { + *(p - 1) = 0; /* delete "/" before "../" */ + tail = p + 2; /* skip to "/" of "../" */ + p = vim_strrchr(filename, DIR_SEP); /* find preceding sep. */ + if (p != NULL) /* none found */ + ++p; /* skip to char after "/" */ + else + { + ++tail; /* strip leading "/" from tail*/ + p = filename; /* go back to beginning */ + if (absolute) /* skip over any leading "/" */ + ++p; + } + movetail(p, tail); /* strip previous component */ + --components; + } + else if (absolute) /* no parent to root... */ + movetail(p, p+3); /* so strip "../" */ + else /* leading series of "../" */ + { + p = vim_strchr(p, DIR_SEP); /* skip to next "/" */ + if (p != NULL) + ++p; /* skip to char after "/" */ + } + } + else + { + ++components; /* simple path component */ + p = vim_strchr(p, DIR_SEP); /* skip to next "/" */ + if (p != NULL) + ++p; /* skip to char after "/" */ + } + } while (p != NULL && *p != NUL); +} + +/* + * Check if we have a tag for the current file. + * This is a bit slow, because of the full path compare in fullpathcmp(). + * Return TRUE if tag for file "fname" if tag file "tag_fname" is for current + * file. + */ + static int +#ifdef EMACS_TAGS +test_for_current(is_etag, fname, fname_end, tag_fname) + int is_etag; +#else +test_for_current(fname, fname_end, tag_fname) +#endif + char_u *fname; + char_u *fname_end; + char_u *tag_fname; +{ + int c; + int retval; + + if (curbuf->b_filename == NULL) + retval = FALSE; + else + { +#ifdef EMACS_TAGS + if (is_etag) + c = 0; /* to shut up GCC */ + else +#endif + { + c = *fname_end; + *fname_end = NUL; + } + retval = (fullpathcmp(expand_rel_name(fname, tag_fname), + curbuf->b_xfilename) == FPC_SAME); +#ifdef EMACS_TAGS + if (!is_etag) +#endif + *fname_end = c; + } + + return retval; +} |