From 0c498200f42036a3aad9a7c2046016a0d2de6a7f Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 10 Jul 2020 19:10:24 -0400 Subject: [PATCH 01/11] vim-patch:8.2.1169: write NUL past allocated space using corrupted spell file Problem: Write NUL past allocated space using corrupted spell file. (Markus Vervier) Solution: Init "c" every time. https://github.com/vim/vim/commit/97d2f34c8763ab3a46c9f43284cc17bad3cf9568 N/A patches for version.c: vim-patch:8.0.1531: cannot use 24 bit colors in MS-Windows console Problem: Cannot use 24 bit colors in MS-Windows console. Solution: Add support for vcon. (Nobuhiro Takasaki, Ken Takasaki, fixes vim/vim#1270, fixes vim/vim#2060) https://github.com/vim/vim/commit/cafafb381a04e33f3ce9cd15dd9f94b73226831f vim-patch:8.0.1544: when using 'termguicolors' SpellBad doesn't show Problem: When using 'termguicolors' SpellBad doesn't show. Solution: When the GUI colors are not set fall back to the cterm colors. https://github.com/vim/vim/commit/d4fc577e60d325777d38c00bd78fb9a32c7b1dfa vim-patch:8.0.1589: error for setting 'modifiable' when resetting it Problem: Error for setting 'modifiable' when resetting it. Solution: Check if 'modifiable' was actually set. https://github.com/vim/vim/commit/d7db27bafd1045281c965d9483539748a744de70 vim-patch:8.0.1591: MS-Windows: when reparsing the arguments 'wildignore' matters Problem: MS-Windows: when reparsing the arguments 'wildignore' matters. Solution: Save and reset 'wildignore'. (Yasuhiro Matsumoto, closes vim/vim#2702) https://github.com/vim/vim/commit/20586cb4f4d516a60b96cc02a94b810fea8b8cdb vim-patch:8.0.1712: terminal scrollback is not limited Problem: Terminal scrollback is not limited. Solution: Add the 'terminalscroll' option. https://github.com/vim/vim/commit/8c041b6b95f49f7383cf00e2036cf009b326fa8d Neovim has 'scrollback' option, not 'terminalscroll', to limit scrollback in terminal buffers. vim-patch:8.0.1745: build failure on MS-Windows Problem: Build failure on MS-Windows. Solution: Build job arguments for MS-Windows. Fix allocating job twice. https://github.com/vim/vim/commit/2060892028e05b1325dc0759259254180669eb5e vim-patch:8.0.1747: MS-Windows: term_start() does not set job_info() cmd Problem: MS-Windows: term_start() does not set job_info() cmd. Solution: Share the code from job_start() to set jv_argv. https://github.com/vim/vim/commit/ebe74b73677b06db7d483987a863b41cee051cc0 --- src/nvim/spellfile.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 41669789db..69c0fa51b2 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -1134,7 +1134,6 @@ static int read_sal_section(FILE *fd, slang_T *slang) salitem_T *smp; int ccnt; char_u *p; - int c = NUL; slang->sl_sofo = false; @@ -1158,7 +1157,9 @@ static int read_sal_section(FILE *fd, slang_T *slang) ga_grow(gap, cnt + 1); // : - for (; gap->ga_len < cnt; ++gap->ga_len) { + for (; gap->ga_len < cnt; gap->ga_len++) { + int c = NUL; + smp = &((salitem_T *)gap->ga_data)[gap->ga_len]; ccnt = getc(fd); // if (ccnt < 0) From be9de61012047b82e0f57742205e25a9a11ecf1c Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 10 Jul 2020 19:29:31 -0400 Subject: [PATCH 02/11] vim-patch:8.2.1170: cursor off by one with block paste while 'virtualedit' "all" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: Cursor off by one with block paste while 'virtualedit' is "all". Solution: Adjust condition. (Hugo Gualandi, closes vim/vim#6430) https://github.com/vim/vim/commit/ef85a9b2d9e992ab594e089af3883e381cfad426 N/A patches for version.c: vim-patch:8.2.1114: terminal test sometimes times out Problem: Terminal test sometimes times out. Solution: Split the test in two parts. https://github.com/vim/vim/commit/1112c0febb509d0cb219f3a2479fd36833507167 vim-patch:8.2.1171: possible crash when out of memory Problem: Possible crash when out of memory. Solution: Check for NULL pointer. (Dominique Pellé, closes vim/vim#6432) https://github.com/vim/vim/commit/58bb61cf5ee008254eb331bc3574eac87d2dcc4a vim-patch:8.2.1172: error messages when doing "make clean" in doc or tee Problem: Error messages when doing "make clean" in the runtime/doc or src/tee directories. Solution: Use "rm -f". https://github.com/vim/vim/commit/08fc48492acc07259d91293df12bf66447819443 vim-patch:8.2.1173: tee doesn't build on some systems Problem: Tee doesn't build on some systems. Solution: Include header files. (Dominique Pelle, closes vim/vim#6431) https://github.com/vim/vim/commit/40043152924827fa8c4064951065ff507c610164 vim-patch:8.2.1177: terminal2 test sometimes hangs in the GUI Problem: Terminal2 test sometimes hangs in the GUI. Solution: Move some tests to other files to further locate the problem. Set the GUI to a fixed screen size. https://github.com/vim/vim/commit/18aa13d13b69c090dbe186cd4939896488c433e3 vim-patch:8.2.1179: Test_termwinscroll() sometimes hangs in the GUI Problem: Test_termwinscroll() sometimes hangs in the GUI. Solution: Skip the test in the GUI. https://github.com/vim/vim/commit/f65927fc8d0102ef2d0fea776381caedf4c51e32 vim-patch:8.2.1180: build failure in small version Problem: Build failure in small version. Solution: Add #ifdef. https://github.com/vim/vim/commit/1e624c912dff19e889c9398b56fe537952c02fef vim-patch:8.2.1181: json code not fully tested Problem: Json code not fully tested. Solution: Add more test coverage. (Dominique Pellé, closes vim/vim#6433) https://github.com/vim/vim/commit/21e5bdd271fa4d0ff4511cf74b160315e1d17cff --- src/nvim/ops.c | 10 ++++++---- src/nvim/testdir/test_registers.vim | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/nvim/ops.c b/src/nvim/ops.c index e905029dae..595a699563 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -3080,10 +3080,12 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (ve_flags == VE_ALL && (curwin->w_cursor.coladd > 0 || endcol2 == curwin->w_cursor.col)) { - if (dir == FORWARD && c == NUL) - ++col; - if (dir != FORWARD && c != NUL) - ++curwin->w_cursor.col; + if (dir == FORWARD && c == NUL) { + col++; + } + if (dir != FORWARD && c != NUL && curwin->w_cursor.coladd > 0) { + curwin->w_cursor.col++; + } if (c == TAB) { if (dir == BACKWARD && curwin->w_cursor.col) curwin->w_cursor.col--; diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index d4f58af10a..d20f8d1eef 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -167,4 +167,22 @@ func Test_set_register() enew! endfunc +func Test_ve_blockpaste() + new + set ve=all + 0put =['QWERTZ','ASDFGH'] + call cursor(1,1) + exe ":norm! \3ljdP" + call assert_equal(1, col('.')) + call assert_equal(getline(1, 2), ['QWERTZ', 'ASDFGH']) + call cursor(1,1) + exe ":norm! \3ljd" + call cursor(1,1) + norm! $3lP + call assert_equal(5, col('.')) + call assert_equal(getline(1, 2), ['TZ QWER', 'GH ASDF']) + set ve&vim + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab From db8682b94ec92e443a3d3e093732d3174075a5ee Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 11 Jul 2020 21:10:25 -0400 Subject: [PATCH 03/11] vim-patch:8.2.0420: Vim9: cannot interrupt a loop with CTRL-C Problem: Vim9: cannot interrupt a loop with CTRL-C. Solution: Check for CTRL-C once in a while. Doesn't fully work yet. https://github.com/vim/vim/commit/f1ec378b014efb9897422c40369a6462163a512a Vim9 test, commented out in this patch, is restored in patch 8.2.0421. Vim9 tests were not ported over so skip the temporary change. Vim should stop pushing broken commits on master. --- src/nvim/misc1.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index e10770b6bd..6dafbafb3e 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -1029,6 +1029,15 @@ void fast_breakcheck(void) } } +// Like line_breakcheck() but check 100 times less often. +void veryfast_breakcheck(void) +{ + if (++breakcheck_count >= BREAKCHECK_SKIP * 100) { + breakcheck_count = 0; + os_breakcheck(); + } +} + /// os_call_shell() wrapper. Handles 'verbose', :profile, and v:shell_error. /// Invalidates cached tags. /// From 420aff590ae5f5ce7b6d807958a10b9fbd8c2594 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 11 Jul 2020 20:19:59 -0400 Subject: [PATCH 04/11] vim-patch:8.2.0894: :mkspell can take very long if the word count is high Problem: :mkspell can take very long if the word count is high. Solution: Use long to avoid negative numbers. Increase the limits by 20% if the compression did not have effect. https://github.com/vim/vim/commit/59f88fbf24b21dbae114a79a15695fa2c3a09fca --- src/nvim/spellfile.c | 62 ++++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 69c0fa51b2..7bd96b38de 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -1811,11 +1811,17 @@ spell_reload_one ( #define CONDIT_SUF 4 // add a suffix for matching flags #define CONDIT_AFF 8 // word already has an affix -// Tunable parameters for when the tree is compressed. See 'mkspellmem'. +// Tunable parameters for when the tree is compressed. Filled from the +// 'mkspellmem' option. static long compress_start = 30000; // memory / SBLOCKSIZE static long compress_inc = 100; // memory / SBLOCKSIZE static long compress_added = 500000; // word count +// Actually used values. These can change if compression doesn't result in +// reducing the size. +static long used_compress_inc; +static long used_compress_added; + // Check the 'mkspellmem' option. Return FAIL if it's wrong. // Sets "sps_flags". int spell_check_msm(void) @@ -3915,9 +3921,10 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int ++spin->si_msg_count; if (spin->si_compress_cnt > 1) { - if (--spin->si_compress_cnt == 1) + if (--spin->si_compress_cnt == 1) { // Did enough words to lower the block count limit. - spin->si_blocks_cnt += compress_inc; + spin->si_blocks_cnt += used_compress_inc; + } } // When we have allocated lots of memory we need to compress the word tree @@ -3925,9 +3932,9 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int // need that room, thus only compress in the following situations: // 1. When not compressed before (si_compress_cnt == 0): when using // "compress_start" blocks. - // 2. When compressed before and used "compress_inc" blocks before - // adding "compress_added" words (si_compress_cnt > 1). - // 3. When compressed before, added "compress_added" words + // 2. When compressed before and used "used_compress_inc" blocks before + // adding "used_compress_added" words (si_compress_cnt > 1). + // 3. When compressed before, added "used_compress_added" words // (si_compress_cnt == 1) and the number of free nodes drops below the // maximum word length. #ifndef SPELL_COMPRESS_ALLWAYS @@ -3937,11 +3944,11 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int #endif { // Decrement the block counter. The effect is that we compress again - // when the freed up room has been used and another "compress_inc" - // blocks have been allocated. Unless "compress_added" words have + // when the freed up room has been used and another "used_compress_inc" + // blocks have been allocated. Unless "used_compress_added" words have // been added, then the limit is put back again. - spin->si_blocks_cnt -= compress_inc; - spin->si_compress_cnt = compress_added; + spin->si_blocks_cnt -= used_compress_inc; + spin->si_compress_cnt = used_compress_added; if (spin->si_verbose) { msg_start(); @@ -3991,6 +3998,7 @@ static wordnode_T *get_wordnode(spellinfo_T *spin) // siblings. // Returns the number of nodes actually freed. static int deref_wordnode(spellinfo_T *spin, wordnode_T *node) + FUNC_ATTR_NONNULL_ALL { wordnode_T *np; int cnt = 0; @@ -4010,6 +4018,7 @@ static int deref_wordnode(spellinfo_T *spin, wordnode_T *node) // Free a wordnode_T for re-use later. // Only the "wn_child" field becomes invalid. static void free_wordnode(spellinfo_T *spin, wordnode_T *n) + FUNC_ATTR_NONNULL_ALL { n->wn_child = spin->si_first_free; spin->si_first_free = n; @@ -4020,15 +4029,21 @@ static void free_wordnode(spellinfo_T *spin, wordnode_T *n) static void wordtree_compress(spellinfo_T *spin, wordnode_T *root) { hashtab_T ht; - int n; - int tot = 0; - int perc; + long tot = 0; + long perc; // Skip the root itself, it's not actually used. The first sibling is the // start of the tree. if (root->wn_sibling != NULL) { hash_init(&ht); - n = node_compress(spin, root->wn_sibling, &ht, &tot); + const long n = node_compress(spin, root->wn_sibling, &ht, &tot); + + if (tot == 0) { + // Compression did not have effect. Increase the limits by 20% to + // avoid wasting time on compression, memory will be used anyway. + used_compress_inc += used_compress_inc / 5; + used_compress_added += used_compress_added / 5; + } #ifndef SPELL_PRINTTREE if (spin->si_verbose || p_verbose > 2) @@ -4041,8 +4056,8 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root) else perc = (tot - n) * 100 / tot; vim_snprintf((char *)IObuff, IOSIZE, - _("Compressed %d of %d nodes; %d (%d%%) remaining"), - n, tot, tot - n, perc); + _("Compressed %ld of %ld nodes; %ld (%ld%%) remaining"), + n, tot, tot - n, perc); spell_message(spin, IObuff); } #ifdef SPELL_PRINTTREE @@ -4054,23 +4069,23 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root) // Compress a node, its siblings and its children, depth first. // Returns the number of compressed nodes. -static int -node_compress ( +static long node_compress( spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, - int *tot // total count of nodes before compressing, + long *tot // total count of nodes before compressing, // incremented while going through the tree ) + FUNC_ATTR_NONNULL_ALL { wordnode_T *np; wordnode_T *tp; wordnode_T *child; hash_T hash; hashitem_T *hi; - int len = 0; + long len = 0; unsigned nr, n; - int compressed = 0; + long compressed = 0; // Go through the list of siblings. Compress each child and then try // finding an identical child to replace it. @@ -5094,6 +5109,8 @@ mkspell ( ga_init(&spin.si_prefcond, (int)sizeof(char_u *), 50); hash_init(&spin.si_commonwords); spin.si_newcompID = 127; // start compound ID at first maximum + used_compress_inc = compress_inc; + used_compress_added = compress_added; // default: fnames[0] is output file, following are input files // When "fcount" is 1 there is only one file. @@ -5274,7 +5291,8 @@ theend: // Display a message for spell file processing when 'verbose' is set or using // ":mkspell". "str" can be IObuff. -static void spell_message(spellinfo_T *spin, char_u *str) +static void spell_message(const spellinfo_T *spin, char_u *str) + FUNC_ATTR_NONNULL_ALL { if (spin->si_verbose || p_verbose > 2) { if (!spin->si_verbose) From 65793a90d75c59d759b32dde01c0dc6a7fb4186c Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 11 Jul 2020 20:52:56 -0400 Subject: [PATCH 05/11] vim-patch:8.2.0895: :mkspell output does not mention the tree type Problem: :mkspell output does not mention the tree type. Solution: Back out increasing the limits, it has no effect. Mention the tree being compressed. Only give a message once per second. https://github.com/vim/vim/commit/408c23b0794540ee3c568a1569f21406c5ed3ab8 --- src/nvim/spellfile.c | 80 ++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 7bd96b38de..6b9348e55d 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -1817,11 +1817,6 @@ static long compress_start = 30000; // memory / SBLOCKSIZE static long compress_inc = 100; // memory / SBLOCKSIZE static long compress_added = 500000; // word count -// Actually used values. These can change if compression doesn't result in -// reducing the size. -static long used_compress_inc; -static long used_compress_added; - // Check the 'mkspellmem' option. Return FAIL if it's wrong. // Sets "sps_flags". int spell_check_msm(void) @@ -3022,6 +3017,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) char_u message[MAXLINELEN + MAXWLEN]; int flags; int duplicate = 0; + Timestamp last_msg_time = 0; // Open the file. fd = os_fopen((char *)fname, "r"); @@ -3097,18 +3093,22 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) continue; } - // This takes time, print a message every 10000 words. + // This takes time, print a message every 10000 words, but not more + // often than once per second. if (spin->si_verbose && spin->si_msg_count > 10000) { spin->si_msg_count = 0; - vim_snprintf((char *)message, sizeof(message), - _("line %6d, word %6ld - %s"), - lnum, spin->si_foldwcount + spin->si_keepwcount, w); - msg_start(); - msg_puts_long_attr(message, 0); - msg_clr_eos(); - msg_didout = FALSE; - msg_col = 0; - ui_flush(); + if (os_time() > last_msg_time) { + last_msg_time = os_time(); + vim_snprintf((char *)message, sizeof(message), + _("line %6d, word %6ld - %s"), + lnum, spin->si_foldwcount + spin->si_keepwcount, w); + msg_start(); + msg_puts_long_attr(message, 0); + msg_clr_eos(); + msg_didout = false; + msg_col = 0; + ui_flush(); + } } // Store the word in the hashtable to be able to find duplicates. @@ -3923,7 +3923,7 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int if (spin->si_compress_cnt > 1) { if (--spin->si_compress_cnt == 1) { // Did enough words to lower the block count limit. - spin->si_blocks_cnt += used_compress_inc; + spin->si_blocks_cnt += compress_inc; } } @@ -3932,9 +3932,9 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int // need that room, thus only compress in the following situations: // 1. When not compressed before (si_compress_cnt == 0): when using // "compress_start" blocks. - // 2. When compressed before and used "used_compress_inc" blocks before - // adding "used_compress_added" words (si_compress_cnt > 1). - // 3. When compressed before, added "used_compress_added" words + // 2. When compressed before and used "compress_inc" blocks before + // adding "compress_added" words (si_compress_cnt > 1). + // 3. When compressed before, added "compress_added" words // (si_compress_cnt == 1) and the number of free nodes drops below the // maximum word length. #ifndef SPELL_COMPRESS_ALLWAYS @@ -3944,11 +3944,11 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int #endif { // Decrement the block counter. The effect is that we compress again - // when the freed up room has been used and another "used_compress_inc" - // blocks have been allocated. Unless "used_compress_added" words have + // when the freed up room has been used and another "compress_inc" + // blocks have been allocated. Unless "compress_added" words have // been added, then the limit is put back again. - spin->si_blocks_cnt -= used_compress_inc; - spin->si_compress_cnt = used_compress_added; + spin->si_blocks_cnt -= compress_inc; + spin->si_compress_cnt = compress_added; if (spin->si_verbose) { msg_start(); @@ -3963,9 +3963,10 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int // compression useful, or one of them is small, which means // compression goes fast. But when filling the soundfold word tree // there is no keep-case tree. - wordtree_compress(spin, spin->si_foldroot); - if (affixID >= 0) - wordtree_compress(spin, spin->si_keeproot); + wordtree_compress(spin, spin->si_foldroot, "case-folded"); + if (affixID >= 0) { + wordtree_compress(spin, spin->si_keeproot, "keep-case"); + } } return OK; @@ -4026,7 +4027,9 @@ static void free_wordnode(spellinfo_T *spin, wordnode_T *n) } // Compress a tree: find tails that are identical and can be shared. -static void wordtree_compress(spellinfo_T *spin, wordnode_T *root) +static void wordtree_compress(spellinfo_T *spin, wordnode_T *root, + const char *name) + FUNC_ATTR_NONNULL_ALL { hashtab_T ht; long tot = 0; @@ -4038,13 +4041,6 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root) hash_init(&ht); const long n = node_compress(spin, root->wn_sibling, &ht, &tot); - if (tot == 0) { - // Compression did not have effect. Increase the limits by 20% to - // avoid wasting time on compression, memory will be used anyway. - used_compress_inc += used_compress_inc / 5; - used_compress_added += used_compress_added / 5; - } - #ifndef SPELL_PRINTTREE if (spin->si_verbose || p_verbose > 2) #endif @@ -4056,8 +4052,8 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root) else perc = (tot - n) * 100 / tot; vim_snprintf((char *)IObuff, IOSIZE, - _("Compressed %ld of %ld nodes; %ld (%ld%%) remaining"), - n, tot, tot - n, perc); + _("Compressed %s of %ld nodes; %ld (%ld%%) remaining"), + name, tot, tot - n, perc); spell_message(spin, IObuff); } #ifdef SPELL_PRINTTREE @@ -4158,7 +4154,7 @@ static long node_compress( node->wn_u1.hashkey[5] = NUL; // Check for CTRL-C pressed now and then. - fast_breakcheck(); + veryfast_breakcheck(); return compressed; } @@ -4765,7 +4761,7 @@ static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname) // Compress the soundfold trie. spell_message(spin, (char_u *)_(msg_compressing)); - wordtree_compress(spin, spin->si_foldroot); + wordtree_compress(spin, spin->si_foldroot, "case-folded"); // Write the .sug file. // Make the file name by changing ".spl" to ".sug". @@ -5109,8 +5105,6 @@ mkspell ( ga_init(&spin.si_prefcond, (int)sizeof(char_u *), 50); hash_init(&spin.si_commonwords); spin.si_newcompID = 127; // start compound ID at first maximum - used_compress_inc = compress_inc; - used_compress_added = compress_added; // default: fnames[0] is output file, following are input files // When "fcount" is 1 there is only one file. @@ -5237,9 +5231,9 @@ mkspell ( if (!error && !got_int) { // Combine tails in the tree. spell_message(&spin, (char_u *)_(msg_compressing)); - wordtree_compress(&spin, spin.si_foldroot); - wordtree_compress(&spin, spin.si_keeproot); - wordtree_compress(&spin, spin.si_prefroot); + wordtree_compress(&spin, spin.si_foldroot, "case-folded"); + wordtree_compress(&spin, spin.si_keeproot, "keep-case"); + wordtree_compress(&spin, spin.si_prefroot, "prefixes"); } if (!error && !got_int) { From ab69ea26dccf4760a60882059a69b9245f767e2c Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 12 Jul 2020 07:40:55 -0400 Subject: [PATCH 06/11] vim-patch:8.1.0819: a failed assert with a long string is hard to read Problem: A failed assert with a long string is hard to read. Solution: Shorten the assert message. https://github.com/vim/vim/commit/865767126e97d9ac65e052a657b29bbc32dcb512 --- src/nvim/eval.c | 58 ++++++++++++++++++++------ test/functional/legacy/assert_spec.lua | 5 +++ 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e1f9fe0253..db9d0d987c 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5552,19 +5552,18 @@ void prepare_assert_error(garray_T *gap) } } -// Append "str" to "gap", escaping unprintable characters. +// Append "p[clen]" to "gap", escaping unprintable characters. // Changes NL to \n, CR to \r, etc. -static void ga_concat_esc(garray_T *gap, char_u *str) +static void ga_concat_esc(garray_T *gap, const char_u *p, int clen) + FUNC_ATTR_NONNULL_ALL { - char_u *p; char_u buf[NUMBUFLEN]; - if (str == NULL) { - ga_concat(gap, (char_u *)"NULL"); - return; - } - - for (p = str; *p != NUL; p++) { + if (clen > 1) { + memmove(buf, p, clen); + buf[clen] = NUL; + ga_concat(gap, buf); + } else { switch (*p) { case BS: ga_concat(gap, (char_u *)"\\b"); break; case ESC: ga_concat(gap, (char_u *)"\\e"); break; @@ -5585,6 +5584,41 @@ static void ga_concat_esc(garray_T *gap, char_u *str) } } +// Append "str" to "gap", escaping unprintable characters. +// Changes NL to \n, CR to \r, etc. +static void ga_concat_shorten_esc(garray_T *gap, const char_u *str) + FUNC_ATTR_NONNULL_ARG(1) +{ + char_u buf[NUMBUFLEN]; + + if (str == NULL) { + ga_concat(gap, (char_u *)"NULL"); + return; + } + + for (const char_u *p = str; *p != NUL; p++) { + int same_len = 1; + const char_u *s = p; + const int c = mb_ptr2char_adv(&s); + const int clen = s - p; + while (*s != NUL && c == utf_ptr2char(s)) { + same_len++; + s += clen; + } + if (same_len > 20) { + ga_concat(gap, (char_u *)"\\["); + ga_concat_esc(gap, p, clen); + ga_concat(gap, (char_u *)" occurs "); + vim_snprintf((char *)buf, NUMBUFLEN, "%d", same_len); + ga_concat(gap, buf); + ga_concat(gap, (char_u *)" times]"); + p = s - 1; + } else { + ga_concat_esc(gap, p, clen); + } + } +} + // Fill "gap" with information about an assert error. void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_str, typval_T *exp_tv, @@ -5609,10 +5643,10 @@ void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, if (exp_str == NULL) { tofree = (char_u *)encode_tv2string(exp_tv, NULL); - ga_concat_esc(gap, tofree); + ga_concat_shorten_esc(gap, tofree); xfree(tofree); } else { - ga_concat_esc(gap, exp_str); + ga_concat_shorten_esc(gap, exp_str); } if (atype != ASSERT_NOTEQUAL) { @@ -5624,7 +5658,7 @@ void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, ga_concat(gap, (char_u *)" but got "); } tofree = (char_u *)encode_tv2string(got_tv, NULL); - ga_concat_esc(gap, tofree); + ga_concat_shorten_esc(gap, tofree); xfree(tofree); } } diff --git a/test/functional/legacy/assert_spec.lua b/test/functional/legacy/assert_spec.lua index 3cb5d97869..dd7810bf51 100644 --- a/test/functional/legacy/assert_spec.lua +++ b/test/functional/legacy/assert_spec.lua @@ -92,6 +92,11 @@ describe('assert function:', function() call('assert_equal', 'foo', 'bar', 'testing') expected_errors({"testing: Expected 'foo' but got 'bar'"}) end) + + it('should shorten a long message', function() + call ('assert_equal', 'XxxxxxxxxxxxxxxxxxxxxxX', 'XyyyyyyyyyyyyyyyyyyyyyyyyyX') + expected_errors({"Expected 'X\\[x occurs 21 times]X' but got 'X\\[y occurs 25 times]X'"}) + end) end) -- assert_notequal({expected}, {actual}[, {msg}]) From 93f2dc0d034677a47c9ba7032b3537c91a718096 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 12 Jul 2020 10:17:30 -0400 Subject: [PATCH 07/11] vim-patch:8.2.0893: assert_equalfile() does not take a third argument Problem: Assert_equalfile() does not take a third argument. Solution: Implement the third argument. (Gary Johnson) https://github.com/vim/vim/commit/fb517bac2384798bb5142ed1f75f965f93984c0a --- runtime/doc/eval.txt | 8 ++++---- src/nvim/eval.c | 6 ++++++ src/nvim/eval.lua | 2 +- src/nvim/eval/funcs.c | 2 +- src/nvim/testdir/test_assert.vim | 4 ++++ 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 1c1baa943a..f9a5d36205 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2019,11 +2019,12 @@ argidx() Number current index in the argument list arglistid([{winnr} [, {tabnr}]]) Number argument list id argv({nr} [, {winid}]) String {nr} entry of the argument list argv([-1, {winid}]) List the argument list +asin({expr}) Float arc sine of {expr} assert_beeps({cmd}) Number assert {cmd} causes a beep assert_equal({exp}, {act} [, {msg}]) Number assert {exp} is equal to {act} -assert_equalfile({fname-one}, {fname-two}) - Number assert file contents is equal +assert_equalfile({fname-one}, {fname-two} [, {msg}]) + Number assert file contents are equal assert_exception({error} [, {msg}]) Number assert {error} is in v:exception assert_fails({cmd} [, {error}]) Number assert {cmd} fails @@ -2039,7 +2040,6 @@ assert_notmatch({pat}, {text} [, {msg}]) Number assert {pat} not matches {text} assert_report({msg}) Number report a test failure assert_true({actual} [, {msg}]) Number assert {actual} is true -asin({expr}) Float arc sine of {expr} atan({expr}) Float arc tangent of {expr} atan2({expr}, {expr}) Float arc tangent of {expr1} / {expr2} browse({save}, {title}, {initdir}, {default}) @@ -2630,7 +2630,7 @@ assert_equal({expected}, {actual}, [, {msg}]) test.vim line 12: Expected 'foo' but got 'bar' ~ *assert_equalfile()* -assert_equalfile({fname-one}, {fname-two}) +assert_equalfile({fname-one}, {fname-two} [, {msg}]) When the files {fname-one} and {fname-two} do not contain exactly the same text an error message is added to |v:errors|. Also see |assert-return|. diff --git a/src/nvim/eval.c b/src/nvim/eval.c index db9d0d987c..12c904bc1b 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5739,6 +5739,12 @@ int assert_equalfile(typval_T *argvars) } if (IObuff[0] != NUL) { prepare_assert_error(&ga); + if (argvars[2].v_type != VAR_UNKNOWN) { + char *const tofree = encode_tv2echo(&argvars[2], NULL); + ga_concat(&ga, (char_u *)tofree); + xfree(tofree); + ga_concat(&ga, (char_u *)": "); + } ga_concat(&ga, IObuff); assert_error(&ga); ga_clear(&ga); diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 51872a7ba8..d20e381481 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -28,7 +28,7 @@ return { asin={args=1, func="float_op_wrapper", data="&asin"}, -- WJMc assert_beeps={args={1, 2}}, assert_equal={args={2, 3}}, - assert_equalfile={args=2}, + assert_equalfile={args={2, 3}}, assert_exception={args={1, 2}}, assert_fails={args={1, 3}}, assert_false={args={1, 2}}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 99014d1a09..831167a489 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -415,7 +415,7 @@ static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL); } -// "assert_equalfile(fname-one, fname-two)" function +// "assert_equalfile(fname-one, fname-two[, msg])" function static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = assert_equalfile(argvars); diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim index 4cc90eca7a..86993e0acc 100644 --- a/src/nvim/testdir/test_assert.vim +++ b/src/nvim/testdir/test_assert.vim @@ -31,6 +31,10 @@ func Test_assert_equalfile() call assert_match("difference at byte 4", v:errors[0]) call remove(v:errors, 0) + call assert_equal(1, assert_equalfile('Xone', 'Xtwo', 'a message')) + call assert_match("a message: difference at byte 4", v:errors[0]) + call remove(v:errors, 0) + call delete('Xone') call delete('Xtwo') endfunc From bf882281800fe155536de487418d8052812446e5 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 12 Jul 2020 10:44:39 -0400 Subject: [PATCH 08/11] vim-patch:8.2.0899: assert_equalfile() does not give a hint about the difference Problem: Assert_equalfile() does not give a hint about the difference. Solution: Display the last seen text. https://github.com/vim/vim/commit/30cc44a97f0ba1349e1a522dab22b11f47888183 --- src/nvim/eval.c | 37 ++++++++++++++++++++++++++++---- src/nvim/testdir/test_assert.vim | 11 ++++++++-- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 12c904bc1b..0a83c3a586 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5708,6 +5708,9 @@ int assert_equalfile(typval_T *argvars) IObuff[0] = NUL; FILE *const fd1 = os_fopen(fname1, READBIN); + char line1[200]; + char line2[200]; + ptrdiff_t lineidx = 0; if (fd1 == NULL) { snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname1); } else { @@ -5716,6 +5719,7 @@ int assert_equalfile(typval_T *argvars) fclose(fd1); snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname2); } else { + int64_t linecount = 1; for (int64_t count = 0; ; count++) { const int c1 = fgetc(fd1); const int c2 = fgetc(fd2); @@ -5727,10 +5731,24 @@ int assert_equalfile(typval_T *argvars) } else if (c2 == EOF) { STRCPY(IObuff, "second file is shorter"); break; - } else if (c1 != c2) { - snprintf((char *)IObuff, IOSIZE, - "difference at byte %" PRId64, count); - break; + } else { + line1[lineidx] = c1; + line2[lineidx] = c2; + lineidx++; + if (c1 != c2) { + snprintf((char *)IObuff, IOSIZE, + "difference at byte %" PRId64 ", line %" PRId64, + count, linecount); + break; + } + } + if (c1 == NL) { + linecount++; + lineidx = 0; + } else if (lineidx + 2 == (ptrdiff_t)sizeof(line1)) { + memmove(line1, line1 + 100, lineidx - 100); + memmove(line2, line2 + 100, lineidx - 100); + lineidx -= 100; } } fclose(fd1); @@ -5746,6 +5764,17 @@ int assert_equalfile(typval_T *argvars) ga_concat(&ga, (char_u *)": "); } ga_concat(&ga, IObuff); + if (lineidx > 0) { + line1[lineidx] = NUL; + line2[lineidx] = NUL; + ga_concat(&ga, (char_u *)" after \""); + ga_concat(&ga, (char_u *)line1); + if (STRCMP(line1, line2) != 0) { + ga_concat(&ga, (char_u *)"\" vs \""); + ga_concat(&ga, (char_u *)line2); + } + ga_concat(&ga, (char_u *)"\""); + } assert_error(&ga); ga_clear(&ga); return 1; diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim index 86993e0acc..b4f7478807 100644 --- a/src/nvim/testdir/test_assert.vim +++ b/src/nvim/testdir/test_assert.vim @@ -28,11 +28,18 @@ func Test_assert_equalfile() call writefile(['1234X89'], 'Xone') call writefile(['1234Y89'], 'Xtwo') call assert_equal(1, assert_equalfile('Xone', 'Xtwo')) - call assert_match("difference at byte 4", v:errors[0]) + call assert_match('difference at byte 4, line 1 after "1234X" vs "1234Y"', v:errors[0]) + call remove(v:errors, 0) + + call writefile([repeat('x', 234) .. 'X'], 'Xone') + call writefile([repeat('x', 234) .. 'Y'], 'Xtwo') + call assert_equal(1, assert_equalfile('Xone', 'Xtwo')) + let xes = repeat('x', 134) + call assert_match('difference at byte 234, line 1 after "' .. xes .. 'X" vs "' .. xes .. 'Y"', v:errors[0]) call remove(v:errors, 0) call assert_equal(1, assert_equalfile('Xone', 'Xtwo', 'a message')) - call assert_match("a message: difference at byte 4", v:errors[0]) + call assert_match("a message: difference at byte 234, line 1 after", v:errors[0]) call remove(v:errors, 0) call delete('Xone') From e16f2cbd123b5a7bd75b5810ec7641a93c99c009 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 12 Jul 2020 19:04:35 -0400 Subject: [PATCH 09/11] vim-patch:8.2.0539: comparing two NULL list fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: Comparing two NULL list fails. Solution: Change the order of comparing two lists. https://github.com/vim/vim/commit/7b293c730b07d1586688e622b8d9cbbb4a52379b N/A patches for version.c: vim-patch:8.2.1187: terminal2 test sometimes hangs in the GUI on Travis Problem: Terminal2 test sometimes hangs in the GUI on Travis. Solution: Disable Test_zz2_terminal_guioptions_bang() for now. https://github.com/vim/vim/commit/c85156bb897085d7f5a8e4e180287f87bf19b948 vim-patch:8.2.1188: memory leak with invalid json input Problem: Memory leak with invalid json input. Solution: Free all keys at the end. (Dominique Pellé, closes vim/vim#6443, closes vim/vim#6442) https://github.com/vim/vim/commit/6d3a7213f58da834b0fc869d05f87e86010c66cf vim-patch:8.2.1196: build failure with normal features Problem: Build failure with normal features. Solution: Add #ifdef. https://github.com/vim/vim/commit/83e7450053399942e1c9efa802c568b51d948541 vim-patch:8.2.1198: terminal2 test sometimes hangs in the GUI on Travis Problem: Terminal2 test sometimes hangs in the GUI on Travis. Solution: Move test function to terminal3 to see if the problem moves too. https://github.com/vim/vim/commit/a4b442614c5ca4ebf32acf5cf0b7b718496f1c94 --- src/nvim/eval/typval.c | 8 ++++++-- test/functional/eval/null_spec.lua | 6 ++---- test/functional/legacy/assert_spec.lua | 3 +++ test/unit/eval/typval_spec.lua | 24 ++++++++++++------------ 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 89ca2db59b..2394eb8099 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -799,10 +799,14 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, if (l1 == l2) { return true; } - if (l1 == NULL || l2 == NULL) { + if (tv_list_len(l1) != tv_list_len(l2)) { return false; } - if (tv_list_len(l1) != tv_list_len(l2)) { + if (tv_list_len(l1) == 0) { + // empty and NULL list are considered equal + return true; + } + if (l1 == NULL || l2 == NULL) { return false; } diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index afe999e1fa..db0a706319 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -47,10 +47,8 @@ describe('NULL', function() -- Subjectable behaviour - -- FIXME Should return 1 - null_expr_test('is equal to empty list', 'L == []', 0, 0) - -- FIXME Should return 1 - null_expr_test('is equal to empty list (reverse order)', '[] == L', 0, 0) + null_expr_test('is equal to empty list', 'L == []', 0, 1) + null_expr_test('is equal to empty list (reverse order)', '[] == L', 0, 1) -- Correct behaviour null_expr_test('can be indexed with error message for empty list', 'L[0]', diff --git a/test/functional/legacy/assert_spec.lua b/test/functional/legacy/assert_spec.lua index dd7810bf51..d48b8882af 100644 --- a/test/functional/legacy/assert_spec.lua +++ b/test/functional/legacy/assert_spec.lua @@ -38,6 +38,9 @@ describe('assert function:', function() call assert_equal(4, n) let l = [1, 2, 3] call assert_equal([1, 2, 3], l) + call assert_equal(v:_null_list, v:_null_list) + call assert_equal(v:_null_list, []) + call assert_equal([], v:_null_list) fu Func() endfu let F1 = function('Func') diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index 06465071c5..7c03005529 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -1234,13 +1234,13 @@ describe('typval.c', function() local l = list() local l2 = list() - -- NULL lists are not equal to empty lists - eq(false, lib.tv_list_equal(l, nil, true, false)) - eq(false, lib.tv_list_equal(nil, l, false, false)) - eq(false, lib.tv_list_equal(nil, l, false, true)) - eq(false, lib.tv_list_equal(l, nil, true, true)) + -- NULL lists are equal to empty lists + eq(true, lib.tv_list_equal(l, nil, true, false)) + eq(true, lib.tv_list_equal(nil, l, false, false)) + eq(true, lib.tv_list_equal(nil, l, false, true)) + eq(true, lib.tv_list_equal(l, nil, true, true)) - -- Yet NULL lists are equal themselves + -- NULL lists are equal themselves eq(true, lib.tv_list_equal(nil, nil, true, false)) eq(true, lib.tv_list_equal(nil, nil, false, false)) eq(true, lib.tv_list_equal(nil, nil, false, true)) @@ -2648,13 +2648,13 @@ describe('typval.c', function() local l2 = lua2typvalt(empty_list) local nl = lua2typvalt(null_list) - -- NULL lists are not equal to empty lists - eq(false, lib.tv_equal(l, nl, true, false)) - eq(false, lib.tv_equal(nl, l, false, false)) - eq(false, lib.tv_equal(nl, l, false, true)) - eq(false, lib.tv_equal(l, nl, true, true)) + -- NULL lists are equal to empty lists + eq(true, lib.tv_equal(l, nl, true, false)) + eq(true, lib.tv_equal(nl, l, false, false)) + eq(true, lib.tv_equal(nl, l, false, true)) + eq(true, lib.tv_equal(l, nl, true, true)) - -- Yet NULL lists are equal themselves + -- NULL lists are equal themselves eq(true, lib.tv_equal(nl, nl, true, false)) eq(true, lib.tv_equal(nl, nl, false, false)) eq(true, lib.tv_equal(nl, nl, false, true)) From bb1f06fa82d9dd3c30b983068e51923cf50dc499 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 14 Jul 2020 19:43:09 -0400 Subject: [PATCH 10/11] vim-patch:8.2.1211: removed more than dead code Problem: Removed more than dead code. Solution: Put back the decrement. https://github.com/vim/vim/commit/8455c5ed31c9cb2919f5e62693bab6b09bf60df2 N/A patch for version.c: vim-patch:8.2.1118: condition can never be true, dead code Problem: Condition can never be true, dead code. Solution: Remove the dead code. https://github.com/vim/vim/commit/810af5ea460eab820cc5899892067d8c242be688 vim-patch:8.2.1214: MS-Windows: default _vimrc not correct in silent install mode Problem: MS-Windows: default _vimrc not correct in silent install mode. Solution: Add the LoadDefaultVimrc macro. (Ken Takata, closes vim/vim#6451) https://github.com/vim/vim/commit/ceb56ddbafbca01e52ff4291d9480bf83d739793 vim-patch:8.2.1215: Atari MiNT support is outdated Problem: Atari MiNT support is outdated. Solution: Nobody responded this code is still useful, so let's delete it. https://github.com/vim/vim/commit/e3f915d12c8fe0466918a29ab4eaef153f71a2cd --- src/nvim/testdir/test_diffmode.vim | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 42e18ed027..49bbe84869 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -800,3 +800,25 @@ func Test_diff_closeoff() diffoff! enew! endfunc + +func Test_diff_and_scroll() + " this was causing an ml_get error + set ls=2 + for i in range(winheight(0) * 2) + call setline(i, i < winheight(0) - 10 ? i : i + 10) + endfor + vnew + for i in range(winheight(0)*2 + 10) + call setline(i, i < winheight(0) - 10 ? 0 : i) + endfor + diffthis + wincmd p + diffthis + execute 'normal ' . winheight(0) . "\" + + bwipe! + bwipe! + set ls& +endfunc + +" vim: shiftwidth=2 sts=2 expandtab From 140a372017c7649e156742622c9ccc1cd8b794c1 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 15 Jul 2020 19:29:46 -0400 Subject: [PATCH 11/11] vim-patch:8.2.1222: using valgrind in Vim command started by test doesn't work Problem: When using valgrind a Vim command started by a test uses the same log file name which gets overwritten. Solution: Fix regexp to rename the log file. https://github.com/vim/vim/commit/657a826c07b4ea3f8846b6acb2c50b82c2d81e50 --- src/nvim/testdir/shared.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index b041fdedb1..41ff9b2bd6 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -275,7 +275,7 @@ func GetVimCommand(...) " If using valgrind, make sure every run uses a different log file. if cmd =~ 'valgrind.*--log-file=' - let cmd = substitute(cmd, '--log-file=\(^\s*\)', '--log-file=\1.' . g:valgrind_cnt, '') + let cmd = substitute(cmd, '--log-file=\(\S*\)', '--log-file=\1.' . g:valgrind_cnt, '') let g:valgrind_cnt += 1 endif