From dd49a130ff0cd7a51cec3a7bae1ecda3708f8eb2 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Fri, 11 Oct 2019 18:32:15 +0100 Subject: vim-patch:8.1.1099: the do_tag() function is too long Problem: The do_tag() function is too long. Solution: Factor parts out to separate functions. Move simplify_filename() to a file where it fits better. (Andy Massimino, closes vim/vim#4195) https://github.com/vim/vim/commit/b4a6020ac6a0638167013f1e45ff440ddc8a1671 --- src/nvim/tag.c | 800 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 427 insertions(+), 373 deletions(-) (limited to 'src') diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 6fe3efbaae..880c467d30 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -131,13 +131,13 @@ static taggy_T ptag_entry = { NULL, { { 0, 0, 0 }, 0, 0, NULL }, 0, 0 }; * * for cscope, returns TRUE if we jumped to tag or aborted, FALSE otherwise */ -int -do_tag ( - char_u *tag, /* tag (pattern) to jump to */ +int +do_tag( + char_u *tag, // tag (pattern) to jump to int type, int count, - int forceit, /* :ta with ! */ - int verbose /* print "tag not found" message */ + int forceit, // :ta with ! + int verbose // print "tag not found" message ) { taggy_T *tagstack = curwin->w_tagstack; @@ -148,28 +148,19 @@ do_tag ( int oldtagstackidx = tagstackidx; int prevtagstackidx = tagstackidx; int prev_num_matches; - int new_tag = FALSE; - int other_name; - int i, j, k; - int idx; + int new_tag = false; + int i; int ic; - char_u *p; - char_u *name; - int no_regexp = FALSE; + int no_regexp = false; int error_cur_match = 0; - char_u *command_end; - int save_pos = FALSE; + int save_pos = false; fmark_T saved_fmark; - int taglen; - int jumped_to_tag = FALSE; - tagptrs_T tagp, tagp2; + int jumped_to_tag = false; int new_num_matches; char_u **new_matches; - int attr; int use_tagstack; - int skip_msg = FALSE; - char_u *buf_ffname = curbuf->b_ffname; /* name to use for - priority computation */ + int skip_msg = false; + char_u *buf_ffname = curbuf->b_ffname; // name for priority computation /* remember the matches for the last used tag */ static int num_matches = 0; @@ -183,13 +174,13 @@ do_tag ( FreeWild(num_matches, matches); cs_free_tags(); num_matches = 0; - return FALSE; + return false; } #endif if (type == DT_HELP) { type = DT_TAG; - no_regexp = TRUE; + no_regexp = true; } prev_num_matches = num_matches; @@ -209,10 +200,11 @@ do_tag ( ptag_entry.tagname = vim_strsave(tag); } } else { - if (g_do_tagpreview != 0) - use_tagstack = FALSE; - else - use_tagstack = TRUE; + if (g_do_tagpreview != 0) { + use_tagstack = false; + } else { + use_tagstack = true; + } /* new pattern, add to the tag stack */ if (*tag != NUL @@ -253,15 +245,15 @@ do_tag ( curwin->w_tagstacklen = tagstacklen; - save_pos = TRUE; /* save the cursor position below */ + save_pos = true; // save the cursor position below } - new_tag = TRUE; + new_tag = true; } else { if ( - g_do_tagpreview != 0 ? ptag_entry.tagname == NULL : - tagstacklen == 0) { - /* empty stack */ + g_do_tagpreview != 0 ? ptag_entry.tagname == NULL : + tagstacklen == 0) { + // empty stack EMSG(_(e_tagstack)); goto end_do_tag; } @@ -271,7 +263,7 @@ do_tag ( if ((tagstackidx -= count) < 0) { EMSG(_(bottommsg)); if (tagstackidx + count == 0) { - /* We did [num]^T from the bottom of the stack */ + // We did [num]^T from the bottom of the stack tagstackidx = 0; goto end_do_tag; } @@ -279,7 +271,7 @@ do_tag ( * way to the bottom now. */ tagstackidx = 0; - } else if (tagstackidx >= tagstacklen) { /* count == 0? */ + } else if (tagstackidx >= tagstacklen) { // count == 0? EMSG(_(topmsg)); goto end_do_tag; } @@ -293,8 +285,8 @@ do_tag ( * file was changed) keep original position in tag stack. */ if (buflist_getfile(saved_fmark.fnum, saved_fmark.mark.lnum, - GETF_SETMARK, forceit) == FAIL) { - tagstackidx = oldtagstackidx; /* back to old posn */ + GETF_SETMARK, forceit) == FAIL) { + tagstackidx = oldtagstackidx; // back to old posn goto end_do_tag; } /* A BufReadPost autocommand may jump to the '" mark, but @@ -305,12 +297,12 @@ do_tag ( curwin->w_cursor.lnum = saved_fmark.mark.lnum; } curwin->w_cursor.col = saved_fmark.mark.col; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; check_cursor(); if ((fdo_flags & FDO_TAG) && old_KeyTyped) foldOpenCursor(); - /* remove the old list of matches */ + // remove the old list of matches FreeWild(num_matches, matches); cs_free_tags(); num_matches = 0; @@ -325,8 +317,8 @@ do_tag ( cur_match = ptag_entry.cur_match; cur_fnum = ptag_entry.cur_fnum; } else { - /* ":tag" (no argument): go to newer pattern */ - save_pos = TRUE; /* save the cursor position below */ + // ":tag" (no argument): go to newer pattern + save_pos = true; // save the cursor position below if ((tagstackidx += count - 1) >= tagstacklen) { /* * Beyond the last one, just give an error message and @@ -335,8 +327,8 @@ do_tag ( */ tagstackidx = tagstacklen - 1; EMSG(_(topmsg)); - save_pos = FALSE; - } else if (tagstackidx < 0) { /* must have been count == 0 */ + save_pos = false; + } else if (tagstackidx < 0) { // must have been count == 0 EMSG(_(bottommsg)); tagstackidx = 0; goto end_do_tag; @@ -344,9 +336,9 @@ do_tag ( cur_match = tagstack[tagstackidx].cur_match; cur_fnum = tagstack[tagstackidx].cur_fnum; } - new_tag = TRUE; - } else { /* go to other matching tag */ - /* Save index for when selection is cancelled. */ + new_tag = true; + } else { // go to other matching tag + // Save index for when selection is cancelled. prevtagstackidx = tagstackidx; if (g_do_tagpreview != 0) { @@ -371,7 +363,7 @@ do_tag ( cur_match = MAXCOL - 1; else if (cur_match < 0) { EMSG(_("E425: Cannot go before first matching tag")); - skip_msg = TRUE; + skip_msg = true; cur_match = 0; cur_fnum = curbuf->b_fnum; } @@ -418,15 +410,17 @@ do_tag ( * Repeat searching for tags, when a file has not been found. */ for (;; ) { - /* - * When desired match not found yet, try to find it (and others). - */ - if (use_tagstack) + int other_name; + char_u *name; + + // When desired match not found yet, try to find it (and others). + if (use_tagstack) { name = tagstack[tagstackidx].tagname; - else if (g_do_tagpreview != 0) + } else if (g_do_tagpreview != 0) { name = ptag_entry.tagname; - else + } else { name = tag; + } other_name = (tagmatchname == NULL || STRCMP(tagmatchname, name) != 0); if (new_tag || (cur_match >= num_matches && max_num_matches != MAXCOL) @@ -446,7 +440,7 @@ do_tag ( max_num_matches = cur_match + 1; } - /* when the argument starts with '/', use it as a regexp */ + // when the argument starts with '/', use it as a regexp if (!no_regexp && *name == '/') { flags = TAG_REGEXP; ++name; @@ -467,17 +461,21 @@ do_tag ( * to the start. Avoids that the order changes when using * ":tnext" and jumping to another file. */ if (!new_tag && !other_name) { - /* Find the position of each old match in the new list. Need - * to use parse_match() to find the tag line. */ - idx = 0; - for (j = 0; j < num_matches; ++j) { + int j, k; + int idx = 0; + tagptrs_T tagp, tagp2; + + // Find the position of each old match in the new list. Need + // to use parse_match() to find the tag line. + for (j = 0; j < num_matches; j++) { parse_match(matches[j], &tagp); for (i = idx; i < new_num_matches; ++i) { parse_match(new_matches[i], &tagp2); if (STRCMP(tagp.tagname, tagp2.tagname) == 0) { - p = new_matches[i]; - for (k = i; k > idx; --k) + char_u *p = new_matches[i]; + for (k = i; k > idx; k--) { new_matches[k] = new_matches[k - 1]; + } new_matches[idx++] = p; break; } @@ -504,304 +502,27 @@ do_tag ( // jump to count'th matching tag. cur_match = count > 0 ? count - 1 : 0; } else if (type == DT_SELECT || (type == DT_JUMP && num_matches > 1)) { - // List all the matching tags. - // Assume that the first match indicates how long the tags can - // be, and align the file names to that. - parse_match(matches[0], &tagp); - taglen = (int)(tagp.tagname_end - tagp.tagname + 2); - if (taglen < 18) - taglen = 18; - if (taglen > Columns - 25) - taglen = MAXCOL; - if (msg_col == 0) - msg_didout = FALSE; /* overwrite previous message */ - msg_start(); - MSG_PUTS_ATTR(_(" # pri kind tag"), HL_ATTR(HLF_T)); - msg_clr_eos(); - taglen_advance(taglen); - MSG_PUTS_ATTR(_("file\n"), HL_ATTR(HLF_T)); - - for (i = 0; i < num_matches && !got_int; i++) { - parse_match(matches[i], &tagp); - if (!new_tag && ((g_do_tagpreview != 0 && i == ptag_entry.cur_match) - || (use_tagstack - && i == tagstack[tagstackidx].cur_match))) { - *IObuff = '>'; - } else { - *IObuff = ' '; - } - vim_snprintf((char *)IObuff + 1, IOSIZE - 1, "%2d %s ", i + 1, - mt_names[matches[i][0] & MT_MASK]); - msg_puts((const char *)IObuff); - if (tagp.tagkind != NULL) { - msg_outtrans_len(tagp.tagkind, - (int)(tagp.tagkind_end - tagp.tagkind)); - } - msg_advance(13); - msg_outtrans_len_attr(tagp.tagname, - (int)(tagp.tagname_end - tagp.tagname), - HL_ATTR(HLF_T)); - msg_putchar(' '); - taglen_advance(taglen); - - /* Find out the actual file name. If it is long, truncate - * it and put "..." in the middle */ - p = tag_full_fname(&tagp); - msg_puts_long_attr(p, HL_ATTR(HLF_D)); - xfree(p); - - if (msg_col > 0) - msg_putchar('\n'); - if (got_int) - break; - msg_advance(15); - - /* print any extra fields */ - command_end = tagp.command_end; - if (command_end != NULL) { - p = command_end + 3; - while (*p && *p != '\r' && *p != '\n') { - while (*p == TAB) - ++p; - - /* skip "file:" without a value (static tag) */ - if (STRNCMP(p, "file:", 5) == 0 - && ascii_isspace(p[5])) { - p += 5; - continue; - } - /* skip "kind:" and "" */ - if (p == tagp.tagkind - || (p + 5 == tagp.tagkind - && STRNCMP(p, "kind:", 5) == 0)) { - p = tagp.tagkind_end; - continue; - } - // print all other extra fields - attr = HL_ATTR(HLF_CM); - while (*p && *p != '\r' && *p != '\n') { - if (msg_col + ptr2cells(p) >= Columns) { - msg_putchar('\n'); - if (got_int) - break; - msg_advance(15); - } - p = msg_outtrans_one(p, attr); - if (*p == TAB) { - msg_puts_attr(" ", attr); - break; - } - if (*p == ':') - attr = 0; - } - } - if (msg_col > 15) { - msg_putchar('\n'); - if (got_int) - break; - msg_advance(15); - } - } else { - for (p = tagp.command; - *p && *p != '\r' && *p != '\n'; ++p) - ; - command_end = p; - } - - /* - * Put the info (in several lines) at column 15. - * Don't display "/^" and "?^". - */ - p = tagp.command; - if (*p == '/' || *p == '?') { - ++p; - if (*p == '^') - ++p; - } - /* Remove leading whitespace from pattern */ - while (p != command_end && ascii_isspace(*p)) - ++p; - - while (p != command_end) { - if (msg_col + (*p == TAB ? 1 : ptr2cells(p)) > Columns) - msg_putchar('\n'); - if (got_int) - break; - msg_advance(15); - - // Skip backslash used for escaping a command char or a backslash. - if (*p == '\\' && (*(p + 1) == *tagp.command - || *(p + 1) == '\\')) { - ++p; - } - - if (*p == TAB) { - msg_putchar(' '); - ++p; - } else - p = msg_outtrans_one(p, 0); - - /* don't display the "$/;\"" and "$?;\"" */ - if (p == command_end - 2 && *p == '$' - && *(p + 1) == *tagp.command) - break; - /* don't display matching '/' or '?' */ - if (p == command_end - 1 && *p == *tagp.command - && (*p == '/' || *p == '?')) - break; - } - if (msg_col) - msg_putchar('\n'); - os_breakcheck(); - } - if (got_int) { - got_int = false; // only stop the listing - } + print_tag_list(new_tag, use_tagstack, num_matches, matches); ask_for_selection = true; } else if (type == DT_LTAG) { - list_T *list; - char_u tag_name[128 + 1]; - char_u *fname; - char_u *cmd; - - /* - * Add the matching tags to the location list for the current - * window. - */ - - fname = xmalloc(MAXPATHL + 1); - cmd = xmalloc(CMDBUFFSIZE + 1); - list = tv_list_alloc(num_matches); - - for (i = 0; i < num_matches; ++i) { - int len, cmd_len; - long lnum; - dict_T *dict; - - parse_match(matches[i], &tagp); - - /* Save the tag name */ - len = (int)(tagp.tagname_end - tagp.tagname); - if (len > 128) - len = 128; - STRLCPY(tag_name, tagp.tagname, len + 1); - - /* Save the tag file name */ - p = tag_full_fname(&tagp); - STRLCPY(fname, p, MAXPATHL + 1); - xfree(p); - - /* - * Get the line number or the search pattern used to locate - * the tag. - */ - lnum = 0; - if (isdigit(*tagp.command)) - /* Line number is used to locate the tag */ - lnum = atol((char *)tagp.command); - else { - char_u *cmd_start, *cmd_end; - - /* Search pattern is used to locate the tag */ - - /* Locate the end of the command */ - cmd_start = tagp.command; - cmd_end = tagp.command_end; - if (cmd_end == NULL) { - for (p = tagp.command; - *p && *p != '\r' && *p != '\n'; ++p) - ; - cmd_end = p; - } - - /* - * Now, cmd_end points to the character after the - * command. Adjust it to point to the last - * character of the command. - */ - cmd_end--; - - /* - * Skip the '/' and '?' characters at the - * beginning and end of the search pattern. - */ - if (*cmd_start == '/' || *cmd_start == '?') - cmd_start++; - - if (*cmd_end == '/' || *cmd_end == '?') - cmd_end--; - - len = 0; - cmd[0] = NUL; - - /* - * If "^" is present in the tag search pattern, then - * copy it first. - */ - if (*cmd_start == '^') { - STRCPY(cmd, "^"); - cmd_start++; - len++; - } - - /* - * Precede the tag pattern with \V to make it very - * nomagic. - */ - STRCAT(cmd, "\\V"); - len += 2; - - cmd_len = (int)(cmd_end - cmd_start + 1); - if (cmd_len > (CMDBUFFSIZE - 5)) - cmd_len = CMDBUFFSIZE - 5; - STRNCAT(cmd, cmd_start, cmd_len); - len += cmd_len; - - if (cmd[len - 1] == '$') { - /* - * Replace '$' at the end of the search pattern - * with '\$' - */ - cmd[len - 1] = '\\'; - cmd[len] = '$'; - len++; - } - - cmd[len] = NUL; - } - - dict = tv_dict_alloc(); - tv_list_append_dict(list, dict); - - tv_dict_add_str(dict, S_LEN("text"), (const char *)tag_name); - tv_dict_add_str(dict, S_LEN("filename"), (const char *)fname); - tv_dict_add_nr(dict, S_LEN("lnum"), lnum); - if (lnum == 0) { - tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cmd); - } + if (add_llist_tags(tag, num_matches, matches) == FAIL) { + goto end_do_tag; } - vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag); - set_errorlist(curwin, list, ' ', IObuff, NULL); - - tv_list_free(list); - xfree(fname); - xfree(cmd); - - cur_match = 0; /* Jump to the first tag */ + cur_match = 0; // Jump to the first tag } if (ask_for_selection) { // Ask to select a tag from the list. i = prompt_for_number(NULL); if (i <= 0 || i > num_matches || got_int) { - /* no valid choice: don't change anything */ + // no valid choice: don't change anything if (use_tagstack) { tagstack[tagstackidx].fmark = saved_fmark; tagstackidx = prevtagstackidx; } cs_free_tags(); - jumped_to_tag = TRUE; + jumped_to_tag = true; break; } cur_match = i - 1; @@ -817,7 +538,7 @@ do_tag ( EMSG(_("E427: There is only one matching tag")); else EMSG(_("E428: Cannot go beyond last matching tag")); - skip_msg = TRUE; + skip_msg = true; } cur_match = num_matches - 1; } @@ -843,13 +564,14 @@ do_tag ( && type != DT_CSCOPE && (num_matches > 1 || ic) && !skip_msg) { - /* Give an indication of the number of matching tags */ - sprintf((char *)IObuff, _("tag %d of %d%s"), - cur_match + 1, - num_matches, - max_num_matches != MAXCOL ? _(" or more") : ""); - if (ic) + // Give an indication of the number of matching tags + snprintf((char *)IObuff, sizeof(IObuff), _("tag %d of %d%s"), + cur_match + 1, + num_matches, + max_num_matches != MAXCOL ? _(" or more") : ""); + if (ic) { STRCAT(IObuff, _(" Using tag with different case!")); + } if ((num_matches > prev_num_matches || new_tag) && num_matches > 1) { if (ic) { @@ -867,7 +589,7 @@ do_tag ( } } - /* Let the SwapExists event know what tag we are jumping to. */ + // Let the SwapExists event know what tag we are jumping to. vim_snprintf((char *)IObuff, IOSIZE, ":ta %s\r", name); set_vim_var_string(VV_SWAPCOMMAND, (char *) IObuff, -1); @@ -879,7 +601,7 @@ do_tag ( set_vim_var_string(VV_SWAPCOMMAND, NULL, -1); if (i == NOTAGFILE) { - /* File not found: try again with another matching tag */ + // File not found: try again with another matching tag if ((type == DT_PREV && cur_match > 0) || ((type == DT_TAG || type == DT_NEXT || type == DT_FIRST) @@ -902,22 +624,354 @@ do_tag ( * tagstackidx is still valid. */ if (use_tagstack && tagstackidx > curwin->w_tagstacklen) tagstackidx = curwin->w_tagstackidx; - jumped_to_tag = TRUE; + jumped_to_tag = true; } } break; } end_do_tag: - /* Only store the new index when using the tagstack and it's valid. */ - if (use_tagstack && tagstackidx <= curwin->w_tagstacklen) + // Only store the new index when using the tagstack and it's valid. + if (use_tagstack && tagstackidx <= curwin->w_tagstacklen) { curwin->w_tagstackidx = tagstackidx; + } postponed_split = 0; // don't split next time g_do_tagpreview = 0; // don't do tag preview next time return jumped_to_tag; } +// +// List all the matching tags. +// +static void +print_tag_list( + int new_tag, + int use_tagstack, + int num_matches, + char_u **matches) +{ + taggy_T *tagstack = curwin->w_tagstack; + int tagstackidx = curwin->w_tagstackidx; + int i; + char_u *p; + char_u *command_end; + tagptrs_T tagp; + int taglen; + int attr; + + // Assume that the first match indicates how long the tags can + // be, and align the file names to that. + parse_match(matches[0], &tagp); + taglen = (int)(tagp.tagname_end - tagp.tagname + 2); + if (taglen < 18) { + taglen = 18; + } + if (taglen > Columns - 25) { + taglen = MAXCOL; + } + if (msg_col == 0) { + msg_didout = false; // overwrite previous message + } + msg_start(); + msg_puts_attr(_(" # pri kind tag"), HL_ATTR(HLF_T)); + msg_clr_eos(); + taglen_advance(taglen); + msg_puts_attr(_("file\n"), HL_ATTR(HLF_T)); + + for (i = 0; i < num_matches && !got_int; i++) { + parse_match(matches[i], &tagp); + if (!new_tag && ( + (g_do_tagpreview != 0 + && i == ptag_entry.cur_match) + || (use_tagstack + && i == tagstack[tagstackidx].cur_match))) { + *IObuff = '>'; + } else { + *IObuff = ' '; + } + vim_snprintf((char *)IObuff + 1, IOSIZE - 1, + "%2d %s ", i + 1, + mt_names[matches[i][0] & MT_MASK]); + msg_puts((char *)IObuff); + if (tagp.tagkind != NULL) { + msg_outtrans_len(tagp.tagkind, + (int)(tagp.tagkind_end - tagp.tagkind)); + } + msg_advance(13); + msg_outtrans_len_attr(tagp.tagname, + (int)(tagp.tagname_end - tagp.tagname), + HL_ATTR(HLF_T)); + msg_putchar(' '); + taglen_advance(taglen); + + // Find out the actual file name. If it is long, truncate + // it and put "..." in the middle + p = tag_full_fname(&tagp); + if (p != NULL) { + msg_outtrans_attr(p, HL_ATTR(HLF_D)); + XFREE_CLEAR(p); + } + if (msg_col > 0) { + msg_putchar('\n'); + } + if (got_int) { + break; + } + msg_advance(15); + + // print any extra fields + command_end = tagp.command_end; + if (command_end != NULL) { + p = command_end + 3; + while (*p && *p != '\r' && *p != '\n') { + while (*p == TAB) { + p++; + } + + // skip "file:" without a value (static tag) + if (STRNCMP(p, "file:", 5) == 0 && ascii_isspace(p[5])) { + p += 5; + continue; + } + // skip "kind:" and "" + if (p == tagp.tagkind + || (p + 5 == tagp.tagkind + && STRNCMP(p, "kind:", 5) == 0)) { + p = tagp.tagkind_end; + continue; + } + // print all other extra fields + attr = HL_ATTR(HLF_CM); + while (*p && *p != '\r' && *p != '\n') { + if (msg_col + ptr2cells(p) >= Columns) { + msg_putchar('\n'); + if (got_int) { + break; + } + msg_advance(15); + } + p = msg_outtrans_one(p, attr); + if (*p == TAB) { + msg_puts_attr(" ", attr); + break; + } + if (*p == ':') { + attr = 0; + } + } + } + if (msg_col > 15) { + msg_putchar('\n'); + if (got_int) { + break; + } + msg_advance(15); + } + } else { + for (p = tagp.command; + *p && *p != '\r' && *p != '\n'; + p++) { + } + command_end = p; + } + + // Put the info (in several lines) at column 15. + // Don't display "/^" and "?^". + p = tagp.command; + if (*p == '/' || *p == '?') { + p++; + if (*p == '^') { + p++; + } + } + // Remove leading whitespace from pattern + while (p != command_end && ascii_isspace(*p)) { + p++; + } + + while (p != command_end) { + if (msg_col + (*p == TAB ? 1 : ptr2cells(p)) > Columns) { + msg_putchar('\n'); + } + if (got_int) { + break; + } + msg_advance(15); + + // skip backslash used for escaping a command char or + // a backslash + if (*p == '\\' && (*(p + 1) == *tagp.command + || *(p + 1) == '\\')) { + p++; + } + + if (*p == TAB) { + msg_putchar(' '); + p++; + } else { + p = msg_outtrans_one(p, 0); + } + + // don't display the "$/;\"" and "$?;\"" + if (p == command_end - 2 && *p == '$' + && *(p + 1) == *tagp.command) { + break; + } + // don't display matching '/' or '?' + if (p == command_end - 1 && *p == *tagp.command + && (*p == '/' || *p == '?')) { + break; + } + } + if (msg_col) { + msg_putchar('\n'); + } + os_breakcheck(); + } + if (got_int) { + got_int = false; // only stop the listing + } +} + +// +// Add the matching tags to the location list for the current +// window. +// +static int +add_llist_tags( + char_u *tag, + int num_matches, + char_u **matches) +{ + list_T *list; + char_u tag_name[128 + 1]; + char_u *fname; + char_u *cmd; + int i; + char_u *p; + tagptrs_T tagp; + + fname = xmalloc(MAXPATHL + 1); + cmd = xmalloc(CMDBUFFSIZE + 1); + list = tv_list_alloc(0); + + for (i = 0; i < num_matches; i++) { + int len, cmd_len; + long lnum; + dict_T *dict; + + parse_match(matches[i], &tagp); + + // Save the tag name + len = (int)(tagp.tagname_end - tagp.tagname); + if (len > 128) { + len = 128; + } + xstrlcpy((char *)tag_name, (const char *)tagp.tagname, len); + tag_name[len] = NUL; + + // Save the tag file name + p = tag_full_fname(&tagp); + if (p == NULL) { + continue; + } + xstrlcpy((char *)fname, (const char *)p, MAXPATHL); + XFREE_CLEAR(p); + + // Get the line number or the search pattern used to locate + // the tag. + lnum = 0; + if (isdigit(*tagp.command)) { + // Line number is used to locate the tag + lnum = atol((char *)tagp.command); + } else { + char_u *cmd_start, *cmd_end; + + // Search pattern is used to locate the tag + + // Locate the end of the command + cmd_start = tagp.command; + cmd_end = tagp.command_end; + if (cmd_end == NULL) { + for (p = tagp.command; + *p && *p != '\r' && *p != '\n'; p++) { + } + cmd_end = p; + } + + // Now, cmd_end points to the character after the + // command. Adjust it to point to the last + // character of the command. + cmd_end--; + + // Skip the '/' and '?' characters at the + // beginning and end of the search pattern. + if (*cmd_start == '/' || *cmd_start == '?') { + cmd_start++; + } + + if (*cmd_end == '/' || *cmd_end == '?') { + cmd_end--; + } + + len = 0; + cmd[0] = NUL; + + // If "^" is present in the tag search pattern, then + // copy it first. + if (*cmd_start == '^') { + STRCPY(cmd, "^"); + cmd_start++; + len++; + } + + // Precede the tag pattern with \V to make it very + // nomagic. + STRCAT(cmd, "\\V"); + len += 2; + + cmd_len = (int)(cmd_end - cmd_start + 1); + if (cmd_len > (CMDBUFFSIZE - 5)) { + cmd_len = CMDBUFFSIZE - 5; + } + xstrlcat((char *)cmd, (char *)cmd_start, cmd_len); + len += cmd_len; + + if (cmd[len - 1] == '$') { + // Replace '$' at the end of the search pattern + // with '\$' + cmd[len - 1] = '\\'; + cmd[len] = '$'; + len++; + } + + cmd[len] = NUL; + } + + if ((dict = tv_dict_alloc()) == NULL) { + continue; + } + tv_list_append_dict(list, dict); + + tv_dict_add_str(dict, S_LEN("text"), (const char *)tag_name); + tv_dict_add_str(dict, S_LEN("filename"), (const char *)fname); + tv_dict_add_nr(dict, S_LEN("lnum"), lnum); + if (lnum == 0) { + tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cmd); + } + } + + vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag); + set_errorlist(curwin, list, ' ', IObuff, NULL); + + tv_list_free(list); + XFREE_CLEAR(fname); + XFREE_CLEAR(cmd); + + return OK; +} + /* * Free cached tags. */ @@ -1055,11 +1109,11 @@ static void prepare_pats(pat_T *pats, int has_re) * TAG_KEEP_LANG keep language * TAG_CSCOPE use cscope results for tags */ -int -find_tags ( - char_u *pat, /* pattern to search for */ - int *num_matches, /* return: number of matches found */ - char_u ***matchesp, /* return: array of matches found */ +int +find_tags( + char_u *pat, // pattern to search for + int *num_matches, // return: number of matches found + char_u ***matchesp, // return: array of matches found int flags, int mincount, /* MAXCOL: find all matches other: minimal number of matches */ @@ -1999,11 +2053,11 @@ void free_tag_stuff(void) * * Return FAIL if no more tag file names, OK otherwise. */ -int -get_tagfname ( - tagname_T *tnp, /* holds status info */ - int first, /* TRUE when first file name is wanted */ - char_u *buf /* pointer to buffer of MAXPATHL chars */ +int +get_tagfname( + tagname_T *tnp, // holds status info + int first, // TRUE when first file name is wanted + char_u *buf // pointer to buffer of MAXPATHL chars ) { char_u *fname = NULL; @@ -2128,9 +2182,9 @@ void tagname_free(tagname_T *tnp) * * Return FAIL if there is a format error in this line, OK otherwise. */ -static int -parse_tag_line ( - char_u *lbuf, /* line to be parsed */ +static int +parse_tag_line( + char_u *lbuf, // line to be parsed tagptrs_T *tagp ) { @@ -2211,10 +2265,10 @@ static size_t matching_line_len(const char_u *const lbuf) * * Return OK or FAIL. */ -static int -parse_match ( - char_u *lbuf, /* input: matching line */ - tagptrs_T *tagp /* output: pointers into the line */ +static int +parse_match( + char_u *lbuf, // input: matching line + tagptrs_T *tagp // output: pointers into the line ) { int retval; @@ -2768,8 +2822,8 @@ expand_tags ( * Add a tag field to the dictionary "dict". * Return OK or FAIL. */ -static int -add_tag_field ( +static int +add_tag_field( dict_T *dict, const char *field_name, const char_u *start, // start of the value -- cgit From e284b7233fb459a7a6d4ce0f98371b34b3639d2b Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 22 Oct 2019 19:55:55 +0100 Subject: Perform HASHTAB_ITER bookkeeping before user-code The `HASHTAB_ITER` logic keeps track of how many entries in the hash table are left to visit, decrementing this on each iteration of the loop. This was previously decremented at the end of the loop body: ```c size_t hi##todo_ = hi##ht_->ht_used; for (hashitem_T *hi = hi##ht_->ht_array; hi##todo_; hi++) { if (!HASHITEM_EMPTY(hi)) { { } hi##todo_--; // <--- important decrement here } } ``` This meant that if the body of the loop (substituted in via macro expansion) contained a `continue` statement, we'd skip decrementing our counter, meaning we'd iterate too many times over the hash table, usually leading to an out of bounds read beyond the hash table's memory, or uninitialised/null pointers from unused hash table slots. Decrementing `hi##todo` before the arbitrary loop body protects us from this, and has no adverse side-effects since only the macro code can (or should) use this variable. Before this commit, no code within `HASHTAB_ITER()` contained a `continue`, meaning this bug was left dormant and the fix has a very minimal chance of introducing any bugs. --- src/nvim/hashtab.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/hashtab.h b/src/nvim/hashtab.h index a70a8bea63..19633d455f 100644 --- a/src/nvim/hashtab.h +++ b/src/nvim/hashtab.h @@ -81,10 +81,10 @@ typedef struct hashtable_S { size_t hi##todo_ = hi##ht_->ht_used; \ for (hashitem_T *hi = hi##ht_->ht_array; hi##todo_; hi++) { \ if (!HASHITEM_EMPTY(hi)) { \ + hi##todo_--; \ { \ code \ } \ - hi##todo_--; \ } \ } \ } while (0) -- cgit From 194f7bfacea934177d524197127242947bd28471 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 10 Oct 2019 22:06:45 +0100 Subject: vim-patch:8.1.1228: not possible to process tags with a function Problem: Not possible to process tags with a function. Solution: Add tagfunc() (Christian Brabandt, Andy Massimino, closes vim/vim#4010) https://github.com/vim/vim/commit/45e18cbdc40afd8144d20dcc07ad2d981636f4c9 --- src/nvim/buffer.c | 1 + src/nvim/buffer_defs.h | 10 +- src/nvim/edit.c | 2 + src/nvim/ex_cmds.c | 2 +- src/nvim/globals.h | 6 +- src/nvim/normal.c | 2 + src/nvim/option.c | 5 + src/nvim/option_defs.h | 1 + src/nvim/options.lua | 8 + src/nvim/tag.c | 386 +++++++++++++++++++++++++++++++++----- src/nvim/tag.h | 27 +-- src/nvim/testdir/test_alot.vim | 1 + src/nvim/testdir/test_tagfunc.vim | 84 +++++++++ src/nvim/window.c | 16 +- 14 files changed, 482 insertions(+), 69 deletions(-) create mode 100644 src/nvim/testdir/test_tagfunc.vim (limited to 'src') diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index b81ffd09e1..1d5aa8ba9b 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1947,6 +1947,7 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_path); clear_string_option(&buf->b_p_tags); clear_string_option(&buf->b_p_tc); + clear_string_option(&buf->b_p_tfu); clear_string_option(&buf->b_p_dict); clear_string_option(&buf->b_p_tsr); clear_string_option(&buf->b_p_qe); diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 16c7804be0..ca740dea21 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -119,10 +119,11 @@ typedef uint16_t disptick_T; // display tick type * The taggy struct is used to store the information about a :tag command. */ typedef struct taggy { - char_u *tagname; /* tag name */ - fmark_T fmark; /* cursor position BEFORE ":tag" */ - int cur_match; /* match number */ - int cur_fnum; /* buffer number used for cur_match */ + char_u *tagname; // tag name + fmark_T fmark; // cursor position BEFORE ":tag" + int cur_match; // match number + int cur_fnum; // buffer number used for cur_match + char_u *user_data; // used with tagfunc } taggy_T; typedef struct buffblock buffblock_T; @@ -647,6 +648,7 @@ struct file_buffer { char_u *b_p_cpt; ///< 'complete' char_u *b_p_cfu; ///< 'completefunc' char_u *b_p_ofu; ///< 'omnifunc' + char_u *b_p_tfu; ///< 'tagfunc' int b_p_eol; ///< 'endofline' int b_p_fixeol; ///< 'fixendofline' int b_p_et; ///< 'expandtab' diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 16c4882975..a4b4e0d980 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -4047,12 +4047,14 @@ static int ins_compl_get_exp(pos_T *ini) // Find up to TAG_MANY matches. Avoids that an enormous number // of matches is found when compl_pattern is empty + g_tag_at_cursor = true; if (find_tags(compl_pattern, &num_matches, &matches, TAG_REGEXP | TAG_NAMES | TAG_NOIC | TAG_INS_COMP | (l_ctrl_x_mode != CTRL_X_NORMAL ? TAG_VERBOSE : 0), TAG_MANY, curbuf->b_ffname) == OK && num_matches > 0) { ins_compl_add_matches(num_matches, matches, p_ic); } + g_tag_at_cursor = false; p_ic = save_p_ic; break; diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 00de7a3d66..2e8bd79c81 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -4905,7 +4905,7 @@ int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches, *matches = NULL; *num_matches = 0; - int flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE; + int flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE | TAG_NO_TAGFUNC; if (keep_lang) { flags |= TAG_KEEP_LANG; } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 5237c621f9..c3d1a4d40b 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -787,7 +787,11 @@ EXTERN int postponed_split_flags INIT(= 0); /* args for win_split() */ EXTERN int postponed_split_tab INIT(= 0); /* cmdmod.tab */ EXTERN int g_do_tagpreview INIT(= 0); /* for tag preview commands: height of preview window */ -EXTERN int replace_offset INIT(= 0); /* offset for replace_push() */ +EXTERN int g_tag_at_cursor INIT(= false); // whether the tag command comes + // from the command line (0) or was + // invoked as a normal command (1) + +EXTERN int replace_offset INIT(= 0); // offset for replace_push() EXTERN char_u *escape_chars INIT(= (char_u *)" \t\\\"|"); /* need backslash in cmd line */ diff --git a/src/nvim/normal.c b/src/nvim/normal.c index e0dc9d4f23..28183ffa1d 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -4929,7 +4929,9 @@ static void nv_ident(cmdarg_T *cap) add_to_history(HIST_SEARCH, (char_u *)buf, true, NUL); (void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0); } else { + g_tag_at_cursor = true; do_cmdline_cmd(buf); + g_tag_at_cursor = false; } xfree(buf); diff --git a/src/nvim/option.c b/src/nvim/option.c index 22f7b85133..20351d3908 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -133,6 +133,7 @@ static char_u *p_cms; static char_u *p_cpt; static char_u *p_cfu; static char_u *p_ofu; +static char_u *p_tfu; static int p_eol; static int p_fixeol; static int p_et; @@ -2273,6 +2274,7 @@ void check_buf_options(buf_T *buf) check_string_option(&buf->b_p_ep); check_string_option(&buf->b_p_path); check_string_option(&buf->b_p_tags); + check_string_option(&buf->b_p_tfu); check_string_option(&buf->b_p_tc); check_string_option(&buf->b_p_dict); check_string_option(&buf->b_p_tsr); @@ -5590,6 +5592,7 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) case PV_INC: return (char_u *)&(curbuf->b_p_inc); case PV_DICT: return (char_u *)&(curbuf->b_p_dict); case PV_TSR: return (char_u *)&(curbuf->b_p_tsr); + case PV_TFU: return (char_u *)&(curbuf->b_p_tfu); case PV_STL: return (char_u *)&(curwin->w_p_stl); case PV_UL: return (char_u *)&(curbuf->b_p_ul); case PV_LW: return (char_u *)&(curbuf->b_p_lw); @@ -5742,6 +5745,7 @@ static char_u *get_varp(vimoption_T *p) case PV_SPF: return (char_u *)&(curwin->w_s->b_p_spf); case PV_SPL: return (char_u *)&(curwin->w_s->b_p_spl); case PV_SW: return (char_u *)&(curbuf->b_p_sw); + case PV_TFU: return (char_u *)&(curbuf->b_p_tfu); case PV_TS: return (char_u *)&(curbuf->b_p_ts); case PV_TW: return (char_u *)&(curbuf->b_p_tw); case PV_UDF: return (char_u *)&(curbuf->b_p_udf); @@ -6004,6 +6008,7 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_cpt = vim_strsave(p_cpt); buf->b_p_cfu = vim_strsave(p_cfu); buf->b_p_ofu = vim_strsave(p_ofu); + buf->b_p_tfu = vim_strsave(p_tfu); buf->b_p_sts = p_sts; buf->b_p_sts_nopaste = p_sts_nopaste; buf->b_p_com = vim_strsave(p_com); diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 67cb53ce02..e5a3c0bd95 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -780,6 +780,7 @@ enum { , BV_SUA , BV_SW , BV_SWF + , BV_TFU , BV_TAGS , BV_TC , BV_TS diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 52e788944b..e96b3f8e02 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2388,6 +2388,14 @@ return { varname='p_syn', defaults={if_true={vi=""}} }, + { + full_name='tagfunc', abbreviation='tfu', + type='string', scope={'buffer'}, + vim=true, + vi_def=true, + varname='p_tfu', + defaults={if_true={vi=""}} + }, { full_name='tabline', abbreviation='tal', type='string', scope={'global'}, diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 880c467d30..872fc0f279 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -51,17 +51,20 @@ * Structure to hold pointers to various items in a tag line. */ typedef struct tag_pointers { - /* filled in by parse_tag_line(): */ - char_u *tagname; /* start of tag name (skip "file:") */ - char_u *tagname_end; /* char after tag name */ - char_u *fname; /* first char of file name */ - char_u *fname_end; /* char after file name */ - char_u *command; /* first char of command */ - /* filled in by parse_match(): */ - char_u *command_end; /* first char after command */ - char_u *tag_fname; /* file name of the tags file */ - char_u *tagkind; /* "kind:" value */ - char_u *tagkind_end; /* end of tagkind */ + // filled in by parse_tag_line(): + char_u *tagname; // start of tag name (skip "file:") + char_u *tagname_end; // char after tag name + char_u *fname; // first char of file name + char_u *fname_end; // char after file name + char_u *command; // first char of command + // filled in by parse_match(): + char_u *command_end; // first char after command + char_u *tag_fname; // file name of the tags file. This is used + // when 'tr' is set. + char_u *tagkind; // "kind:" value + char_u *tagkind_end; // end of tagkind + char_u *user_data; // user_data string + char_u *user_data_end; // end of user_data } tagptrs_T; /* @@ -102,6 +105,10 @@ static char_u *nofile_fname = NULL; /* fname for NOTAGFILE error */ static char_u *bottommsg = (char_u *)N_("E555: at bottom of tag stack"); static char_u *topmsg = (char_u *)N_("E556: at top of tag stack"); +static char_u *recurmsg + = (char_u *)N_("E986: cannot modify the tag stack within tagfunc"); +static char_u *tfu_inv_ret_msg + = (char_u *)N_("E987: invalid return value from tagfunc"); static char_u *tagmatchname = NULL; /* name of last used tag */ @@ -109,7 +116,12 @@ static char_u *tagmatchname = NULL; /* name of last used tag */ * Tag for preview window is remembered separately, to avoid messing up the * normal tagstack. */ -static taggy_T ptag_entry = { NULL, { { 0, 0, 0 }, 0, 0, NULL }, 0, 0 }; +static taggy_T ptag_entry = { NULL, { { 0, 0, 0 }, 0, 0, NULL }, 0, 0, NULL }; + +static int tfu_in_use = false; // disallow recursive call of tagfunc + +// Used instead of NUL to separate tag fields in the growarrays. +#define TAG_SEP 0x02 /* * Jump to tag; handling of tag commands and tag stack @@ -161,6 +173,7 @@ do_tag( int use_tagstack; int skip_msg = false; char_u *buf_ffname = curbuf->b_ffname; // name for priority computation + int use_tfu = 1; /* remember the matches for the last used tag */ static int num_matches = 0; @@ -168,6 +181,11 @@ do_tag( static char_u **matches = NULL; static int flags; + if (tfu_in_use) { + EMSG(_(recurmsg)); + return false; + } + #ifdef EXITFREE if (type == DT_FREE) { /* remove the list of matches */ @@ -181,6 +199,7 @@ do_tag( if (type == DT_HELP) { type = DT_TAG; no_regexp = true; + use_tfu = 0; } prev_num_matches = num_matches; @@ -196,7 +215,7 @@ do_tag( use_tagstack = false; new_tag = true; if (g_do_tagpreview != 0) { - xfree(ptag_entry.tagname); + tagstack_clear_entry(&ptag_entry); ptag_entry.tagname = vim_strsave(tag); } } else { @@ -220,7 +239,7 @@ do_tag( cur_match = ptag_entry.cur_match; cur_fnum = ptag_entry.cur_fnum; } else { - xfree(ptag_entry.tagname); + tagstack_clear_entry(&ptag_entry); ptag_entry.tagname = vim_strsave(tag); } } else { @@ -228,16 +247,18 @@ do_tag( * If the last used entry is not at the top, delete all tag * stack entries above it. */ - while (tagstackidx < tagstacklen) - xfree(tagstack[--tagstacklen].tagname); + while (tagstackidx < tagstacklen) { + tagstack_clear_entry(&tagstack[--tagstacklen]); + } /* if the tagstack is full: remove oldest entry */ if (++tagstacklen > TAGSTACKSIZE) { tagstacklen = TAGSTACKSIZE; - xfree(tagstack[0].tagname); - for (i = 1; i < tagstacklen; ++i) + tagstack_clear_entry(&tagstack[0]); + for (i = 1; i < tagstacklen; i++) { tagstack[i - 1] = tagstack[i]; - --tagstackidx; + } + tagstackidx--; } // put the tag name in the tag stack @@ -447,15 +468,22 @@ do_tag( } else flags = TAG_NOIC; - if (type == DT_CSCOPE) + if (type == DT_CSCOPE) { flags = TAG_CSCOPE; - if (verbose) + } + if (verbose) { flags |= TAG_VERBOSE; + } + if (!use_tfu) { + flags |= TAG_NO_TAGFUNC; + } + if (find_tags(name, &new_num_matches, &new_matches, flags, - max_num_matches, buf_ffname) == OK - && new_num_matches < max_num_matches) - max_num_matches = MAXCOL; /* If less than max_num_matches - found: all matches found. */ + max_num_matches, buf_ffname) == OK + && new_num_matches < max_num_matches) { + max_num_matches = MAXCOL; // If less than max_num_matches + // found: all matches found. + } /* If there already were some matches for the same name, move them * to the start. Avoids that the order changes when using @@ -543,9 +571,20 @@ do_tag( cur_match = num_matches - 1; } if (use_tagstack) { + tagptrs_T tagp2; + tagstack[tagstackidx].cur_match = cur_match; tagstack[tagstackidx].cur_fnum = cur_fnum; - ++tagstackidx; + + // store user-provided data originating from tagfunc + if (use_tfu && parse_match(matches[cur_match], &tagp2) == OK + && tagp2.user_data) { + XFREE_CLEAR(tagstack[tagstackidx].user_data); + tagstack[tagstackidx].user_data = vim_strnsave( + tagp2.user_data, tagp2.user_data_end - tagp2.user_data); + } + + tagstackidx++; } else if (g_do_tagpreview != 0) { ptag_entry.cur_match = cur_match; ptag_entry.cur_fnum = cur_fnum; @@ -1083,6 +1122,220 @@ static void prepare_pats(pat_T *pats, int has_re) pats->regmatch.regprog = NULL; } +// +// Call the user-defined function to generate a list of tags used by +// find_tags(). +// +// Return OK if at least 1 tag has been successfully found, +// NOTDONE if the function returns v:null, and FAIL otherwise. +// +static int find_tagfunc_tags( + char_u *pat, // pattern supplied to the user-defined function + garray_T *ga, // the tags will be placed here + int *match_count, // here the number of tags found will be placed + int flags, // flags from find_tags (TAG_*) + char_u *buf_ffname) // name of buffer for priority +{ + pos_T save_pos; + list_T *taglist; + int ntags = 0; + int result = FAIL; + typval_T args[4]; + typval_T rettv; + char_u flagString[3]; + dict_T *d; + taggy_T *tag = &curwin->w_tagstack[curwin->w_tagstackidx]; + + if (*curbuf->b_p_tfu == NUL) { + return FAIL; + } + + args[0].v_type = VAR_STRING; + args[0].vval.v_string = pat; + args[1].v_type = VAR_STRING; + args[1].vval.v_string = flagString; + + // create 'info' dict argument + d = tv_dict_alloc(); + if (tag->user_data != NULL) { + tv_dict_add_str(d, S_LEN("user_data"), (const char *)tag->user_data); + } + if (buf_ffname != NULL) { + tv_dict_add_str(d, S_LEN("buf_ffname"), (const char *)buf_ffname); + } + + d->dv_refcount++; + args[2].v_type = VAR_DICT; + args[2].vval.v_dict = d; + + args[3].v_type = VAR_UNKNOWN; + + vim_snprintf((char *)flagString, sizeof(flagString), + "%s%s", + g_tag_at_cursor ? "c": "", + flags & TAG_INS_COMP ? "i": ""); + + save_pos = curwin->w_cursor; + result = call_vim_function(curbuf->b_p_tfu, 3, args, &rettv); + curwin->w_cursor = save_pos; // restore the cursor position + d->dv_refcount--; + + if (result == FAIL) { + return FAIL; + } + if (rettv.v_type == VAR_SPECIAL && rettv.vval.v_number == VV_NULL) { + tv_clear(&rettv); + return NOTDONE; + } + if (rettv.v_type != VAR_LIST || !rettv.vval.v_list) { + tv_clear(&rettv); + EMSG(_(tfu_inv_ret_msg)); + return FAIL; + } + taglist = rettv.vval.v_list; + + TV_LIST_ITER_CONST(taglist, li, { + char_u *mfp; + char_u *res_name; + char_u *res_fname; + char_u *res_cmd; + char_u *res_kind; + int len; + int has_extra = 0; + int name_only = flags & TAG_NAMES; + + if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT) { + EMSG(_(tfu_inv_ret_msg)); + break; + } + + len = 2; + res_name = NULL; + res_fname = NULL; + res_cmd = NULL; + res_kind = NULL; + + TV_DICT_ITER(TV_LIST_ITEM_TV(li)->vval.v_dict, di, { + const char_u *dict_key = di->di_key; + typval_T *tv = &di->di_tv; + + if (tv->v_type != VAR_STRING || tv->vval.v_string == NULL) { + continue; + } + + len += STRLEN(tv->vval.v_string) + 1; // Space for "\tVALUE" + if (!STRCMP(dict_key, "name")) { + res_name = tv->vval.v_string; + continue; + } + if (!STRCMP(dict_key, "filename")) { + res_fname = tv->vval.v_string; + continue; + } + if (!STRCMP(dict_key, "cmd")) { + res_cmd = tv->vval.v_string; + continue; + } + has_extra = 1; + if (!STRCMP(dict_key, "kind")) { + res_kind = tv->vval.v_string; + continue; + } + // Other elements will be stored as "\tKEY:VALUE" + // Allocate space for the key and the colon + len += STRLEN(dict_key) + 1; + }); + + if (has_extra) { + len += 2; // need space for ;" + } + + if (!res_name || !res_fname || !res_cmd) { + EMSG(_(tfu_inv_ret_msg)); + break; + } + + if (name_only) { + mfp = vim_strsave(res_name); + } else { + mfp = (char_u *)xmalloc((int)sizeof(char_u) + len + 1); + } + + if (mfp == NULL) { + continue; + } + + if (!name_only) { + char_u *p = mfp; + + *p++ = MT_GL_OTH + 1; // mtt + *p++ = TAG_SEP; // no tag file name + + STRCPY(p, res_name); + p += STRLEN(p); + + *p++ = TAB; + STRCPY(p, res_fname); + p += STRLEN(p); + + *p++ = TAB; + STRCPY(p, res_cmd); + p += STRLEN(p); + + if (has_extra) { + STRCPY(p, ";\""); + p += STRLEN(p); + + if (res_kind) { + *p++ = TAB; + STRCPY(p, res_kind); + p += STRLEN(p); + } + + TV_DICT_ITER(TV_LIST_ITEM_TV(li)->vval.v_dict, di, { + const char_u *dict_key = di->di_key; + typval_T *tv = &di->di_tv; + if (tv->v_type != VAR_STRING || tv->vval.v_string == NULL) { + continue; + } + + if (!STRCMP(dict_key, "name")) { + continue; + } + if (!STRCMP(dict_key, "filename")) { + continue; + } + if (!STRCMP(dict_key, "cmd")) { + continue; + } + if (!STRCMP(dict_key, "kind")) { + continue; + } + + *p++ = TAB; + STRCPY(p, dict_key); + p += STRLEN(p); + STRCPY(p, ":"); + p += STRLEN(p); + STRCPY(p, tv->vval.v_string); + p += STRLEN(p); + }); + } + } + + // Add all matches because tagfunc should do filtering. + ga_grow(ga, 1); + ((char_u **)(ga->ga_data))[ga->ga_len++] = mfp; + ntags++; + result = OK; + }); + + tv_clear(&rettv); + + *match_count = ntags; + return result; +} + /* * find_tags() - search for tags in tags files * @@ -1108,6 +1361,7 @@ static void prepare_pats(pat_T *pats, int has_re) * TAG_NOIC don't always ignore case * TAG_KEEP_LANG keep language * TAG_CSCOPE use cscope results for tags + * TAG_NO_TAGFUNC do not call the 'tagfunc' function */ int find_tags( @@ -1198,6 +1452,7 @@ find_tags( int get_it_again = FALSE; int use_cscope = (flags & TAG_CSCOPE); int verbose = (flags & TAG_VERBOSE); + int use_tfu = ((flags & TAG_NO_TAGFUNC) == 0); int save_p_ic = p_ic; // Change the value of 'ignorecase' according to 'tagcase' for the @@ -1275,6 +1530,16 @@ find_tags( // uninitialised. memset(&search_info, 0, 1); // -V512 + if (*curbuf->b_p_tfu != NUL && use_tfu && !tfu_in_use) { + tfu_in_use = true; + retval = find_tagfunc_tags(pat, &ga_match[0], &match_count, + flags, buf_ffname); + tfu_in_use = false; + if (retval != NOTDONE) { + goto findtag_end; + } + } + /* * When finding a specified number of matches, first try with matching * case, so binary search can be used, and try ignore-case matches in a @@ -1856,7 +2121,6 @@ parse_line: } } } else { -#define TAG_SEP 0x02 size_t tag_fname_len = STRLEN(tag_fname); // Save the tag in a buffer. // Use 0x02 to separate fields (Can't use NUL, because the @@ -2040,9 +2304,7 @@ void free_tag_stuff(void) do_tag(NULL, DT_FREE, 0, 0, 0); tag_freematch(); - if (ptag_entry.tagname) { - XFREE_CLEAR(ptag_entry.tagname); - } + tagstack_clear_entry(&ptag_entry); } #endif @@ -2283,6 +2545,7 @@ parse_match( tagp); tagp->tagkind = NULL; + tagp->user_data = NULL; tagp->command_end = NULL; if (retval == OK) { @@ -2300,13 +2563,17 @@ parse_match( while (ASCII_ISALPHA(*p)) { if (STRNCMP(p, "kind:", 5) == 0) { tagp->tagkind = p + 5; + } else if (STRNCMP(p, "user_data:", 10) == 0) { + tagp->user_data = p + 10; + } + if (tagp->tagkind != NULL && tagp->user_data != NULL) { break; } + pc = vim_strchr(p, ':'); pt = vim_strchr(p, '\t'); if (pc == NULL || (pt != NULL && pc > pt)) { tagp->tagkind = p; - break; } if (pt == NULL) break; @@ -2320,6 +2587,12 @@ parse_match( ; tagp->tagkind_end = p; } + if (tagp->user_data != NULL) { + for (p = tagp->user_data; + *p && *p != '\t' && *p != '\r' && *p != '\n'; p++) { + } + tagp->user_data_end = p; + } } return retval; } @@ -2770,6 +3043,15 @@ static int find_extra(char_u **pp) return FAIL; } +// +// Free a single entry in a tag stack +// +static void tagstack_clear_entry(taggy_T *item) +{ + XFREE_CLEAR(item->tagname); + XFREE_CLEAR(item->user_data); +} + int expand_tags ( int tagnames, /* expand tag names */ @@ -2789,14 +3071,16 @@ expand_tags ( tagnmflag = TAG_NAMES; else tagnmflag = 0; - if (pat[0] == '/') + if (pat[0] == '/') { ret = find_tags(pat + 1, num_file, file, - TAG_REGEXP | tagnmflag | TAG_VERBOSE, - TAG_MANY, curbuf->b_ffname); - else + TAG_REGEXP | tagnmflag | TAG_VERBOSE | TAG_NO_TAGFUNC, + TAG_MANY, curbuf->b_ffname); + } else { ret = find_tags(pat, num_file, file, - TAG_REGEXP | tagnmflag | TAG_VERBOSE | TAG_NOIC, - TAG_MANY, curbuf->b_ffname); + TAG_REGEXP | tagnmflag | TAG_VERBOSE + | TAG_NO_TAGFUNC | TAG_NOIC, + TAG_MANY, curbuf->b_ffname); + } if (ret == OK && !tagnames) { /* Reorganize the tags for display and matching as strings of: * "\0\0\0" @@ -2958,6 +3242,9 @@ static void get_tag_details(taggy_T *tag, dict_T *retdict) tv_dict_add_str(retdict, S_LEN("tagname"), (const char *)tag->tagname); tv_dict_add_nr(retdict, S_LEN("matchnr"), tag->cur_match + 1); tv_dict_add_nr(retdict, S_LEN("bufnr"), tag->cur_fnum); + if (tag->user_data) { + tv_dict_add_str(retdict, S_LEN("user_data"), (const char *)tag->user_data); + } pos = tv_list_alloc(4); tv_dict_add_list(retdict, S_LEN("from"), pos); @@ -2996,7 +3283,7 @@ static void tagstack_clear(win_T *wp) { // Free the current tag stack for (int i = 0; i < wp->w_tagstacklen; i++) { - xfree(wp->w_tagstack[i].tagname); + tagstack_clear_entry(&wp->w_tagstack[i]); } wp->w_tagstacklen = 0; wp->w_tagstackidx = 0; @@ -3007,7 +3294,7 @@ static void tagstack_clear(win_T *wp) static void tagstack_shift(win_T *wp) { taggy_T *tagstack = wp->w_tagstack; - xfree(tagstack[0].tagname); + tagstack_clear_entry(&tagstack[0]); for (int i = 1; i < wp->w_tagstacklen; i++) { tagstack[i - 1] = tagstack[i]; } @@ -3021,7 +3308,8 @@ static void tagstack_push_item( int cur_fnum, int cur_match, pos_T mark, - int fnum) + int fnum, + char_u *user_data) { taggy_T *tagstack = wp->w_tagstack; int idx = wp->w_tagstacklen; // top of the stack @@ -3041,6 +3329,7 @@ static void tagstack_push_item( } tagstack[idx].fmark.mark = mark; tagstack[idx].fmark.fnum = fnum; + tagstack[idx].user_data = user_data; } // Add a list of items to the tag stack in the specified window @@ -3076,10 +3365,13 @@ static void tagstack_push_items(win_T *wp, list_T *l) if (mark.col > 0) { mark.col--; } - tagstack_push_item(wp, tagname, - (int)tv_dict_get_number(itemdict, "bufnr"), - (int)tv_dict_get_number(itemdict, "matchnr") - 1, - mark, fnum); + tagstack_push_item( + wp, + tagname, + (int)tv_dict_get_number(itemdict, "bufnr"), + (int)tv_dict_get_number(itemdict, "matchnr") - 1, + mark, fnum, + (char_u *)tv_dict_get_string(itemdict, "user_data", true)); } } @@ -3103,6 +3395,12 @@ int set_tagstack(win_T *wp, dict_T *d, int action) dictitem_T *di; list_T *l; + // not allowed to alter the tag stack entries from inside tagfunc + if (tfu_in_use) { + EMSG(_(recurmsg)); + return FAIL; + } + if ((di = tv_dict_find(d, "items", -1)) != NULL) { if (di->di_tv.v_type != VAR_LIST) { return FAIL; diff --git a/src/nvim/tag.h b/src/nvim/tag.h index a8fddd05da..9f671043b3 100644 --- a/src/nvim/tag.h +++ b/src/nvim/tag.h @@ -20,20 +20,21 @@ #define DT_LTAG 11 /* tag using location list */ #define DT_FREE 99 /* free cached matches */ -/* - * flags for find_tags(). - */ -#define TAG_HELP 1 /* only search for help tags */ -#define TAG_NAMES 2 /* only return name of tag */ -#define TAG_REGEXP 4 /* use tag pattern as regexp */ -#define TAG_NOIC 8 /* don't always ignore case */ -#define TAG_CSCOPE 16 /* cscope tag */ -#define TAG_VERBOSE 32 /* message verbosity */ -#define TAG_INS_COMP 64 /* Currently doing insert completion */ -#define TAG_KEEP_LANG 128 /* keep current language */ +// +// flags for find_tags(). +// +#define TAG_HELP 1 // only search for help tags +#define TAG_NAMES 2 // only return name of tag +#define TAG_REGEXP 4 // use tag pattern as regexp +#define TAG_NOIC 8 // don't always ignore case +#define TAG_CSCOPE 16 // cscope tag +#define TAG_VERBOSE 32 // message verbosity +#define TAG_INS_COMP 64 // Currently doing insert completion +#define TAG_KEEP_LANG 128 // keep current language +#define TAG_NO_TAGFUNC 256 // do not use 'tagfunc' -#define TAG_MANY 300 /* When finding many tags (for completion), - find up to this many tags */ +#define TAG_MANY 300 // When finding many tags (for completion), + // find up to this many tags /* * Structure used for get_tagfname(). diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 6bf2e8329c..a871924d32 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -45,6 +45,7 @@ source test_syn_attr.vim source test_tabline.vim source test_tabpage.vim source test_tagcase.vim +source test_tagfunc.vim source test_tagjump.vim source test_taglist.vim source test_true_false.vim diff --git a/src/nvim/testdir/test_tagfunc.vim b/src/nvim/testdir/test_tagfunc.vim new file mode 100644 index 0000000000..242aa3a235 --- /dev/null +++ b/src/nvim/testdir/test_tagfunc.vim @@ -0,0 +1,84 @@ +" Test 'tagfunc' + +func TagFunc(pat, flag, info) + let g:tagfunc_args = [a:pat, a:flag, a:info] + let tags = [] + for num in range(1,10) + let tags += [{ + \ 'cmd': '2', 'name': 'nothing'.num, 'kind': 'm', + \ 'filename': 'Xfile1', 'user_data': 'somedata'.num, + \}] + endfor + return tags +endfunc + +func Test_tagfunc() + set tagfunc=TagFunc + new Xfile1 + call setline(1, ['empty', 'one()', 'empty']) + write + + call assert_equal({'cmd': '2', 'static': 0, + \ 'name': 'nothing2', 'user_data': 'somedata2', + \ 'kind': 'm', 'filename': 'Xfile1'}, taglist('.')[1]) + + call settagstack(win_getid(), {'items': []}) + + tag arbitrary + call assert_equal('arbitrary', g:tagfunc_args[0]) + call assert_equal('', g:tagfunc_args[1]) + call assert_equal('somedata1', gettagstack().items[0].user_data) + 5tag arbitrary + call assert_equal('arbitrary', g:tagfunc_args[0]) + call assert_equal('', g:tagfunc_args[1]) + call assert_equal('somedata5', gettagstack().items[1].user_data) + pop + tag + call assert_equal('arbitrary', g:tagfunc_args[0]) + call assert_equal('', g:tagfunc_args[1]) + call assert_equal('somedata5', gettagstack().items[1].user_data) + + let g:tagfunc_args=[] + execute "normal! \" + call assert_equal('one', g:tagfunc_args[0]) + call assert_equal('c', g:tagfunc_args[1]) + + set cpt=t + let g:tagfunc_args=[] + execute "normal! i\\" + call assert_equal('ci', g:tagfunc_args[1]) + call assert_equal('nothing1', getline('.')[0:7]) + + func BadTagFunc1(...) + return 0 + endfunc + func BadTagFunc2(...) + return [1] + endfunc + func BadTagFunc3(...) + return [{'name': 'foo'}] + endfunc + + for &tagfunc in ['BadTagFunc1', 'BadTagFunc2', 'BadTagFunc3'] + try + tag nothing + call assert_false(1, 'tag command should have failed') + catch + call assert_exception('E987:') + endtry + exe 'delf' &tagfunc + endfor + + func NullTagFunc(...) + return v:null + endfunc + set tags= tfu=NullTagFunc + call assert_fails('tag nothing', 'E426') + delf NullTagFunc + + bwipe! + set tags& tfu& cpt& + call delete('Xfile1') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/window.c b/src/nvim/window.c index 4d8eaa9dcc..976f1d8ff0 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -70,8 +70,8 @@ static char *m_onlyone = N_("Already only one window"); /* * all CTRL-W window commands are handled here, called from normal_cmd(). */ -void -do_window ( +void +do_window( int nchar, long Prenum, int xchar /* extra char from ":wincmd gx" or NUL */ @@ -1537,10 +1537,14 @@ static void win_init(win_T *newp, win_T *oldp, int flags) /* copy tagstack and folds */ for (i = 0; i < oldp->w_tagstacklen; i++) { - newp->w_tagstack[i] = oldp->w_tagstack[i]; - if (newp->w_tagstack[i].tagname != NULL) - newp->w_tagstack[i].tagname = - vim_strsave(newp->w_tagstack[i].tagname); + taggy_T *tag = &newp->w_tagstack[i]; + *tag = oldp->w_tagstack[i]; + if (tag->tagname != NULL) { + tag->tagname = vim_strsave(tag->tagname); + } + if (tag->user_data != NULL) { + tag->user_data = vim_strsave(tag->user_data); + } } newp->w_tagstackidx = oldp->w_tagstackidx; newp->w_tagstacklen = oldp->w_tagstacklen; -- cgit From 3b6b528ea98ca7bf8cd5ae1cf103203e3ca67814 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 10 Oct 2019 23:40:57 +0100 Subject: vim-patch:8.1.1962: leaking memory when using tagfunc() Problem: Leaking memory when using tagfunc(). Solution: Free the user_data. (Dominique Pelle, closes vim/vim#4886) https://github.com/vim/vim/commit/55008aad50601cae079037fda8fb434cde70c0f4 --- src/nvim/window.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/window.c b/src/nvim/window.c index 976f1d8ff0..ce5be8e904 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -4642,8 +4642,10 @@ win_free ( xfree(wp->w_lines); - for (i = 0; i < wp->w_tagstacklen; ++i) + for (i = 0; i < wp->w_tagstacklen; i++) { xfree(wp->w_tagstack[i].tagname); + xfree(wp->w_tagstack[i].user_data); + } xfree(wp->w_localdir); -- cgit