From 170b10b421f0c9fda08b7cfd3bb043c064f3659a Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Fri, 29 Jul 2016 16:15:27 +0200 Subject: [PATCH 1/9] patch 7.4.2113 Problem: Test for undo is flaky. Solution: Turn it into a new style test. Use test_settime() to avoid flakyness. --- src/Makefile | 6 +- src/testdir/Make_all.mak | 2 +- src/testdir/test61.in | 113 ------------------ src/testdir/test61.ok | 49 -------- src/testdir/test_alot.vim | 1 - src/testdir/test_undo.vim | 204 ++++++++++++++++++++++++++++++++ src/testdir/test_undolevels.vim | 48 -------- src/undo.c | 10 +- src/version.c | 2 + 9 files changed, 215 insertions(+), 220 deletions(-) delete mode 100644 src/testdir/test61.in delete mode 100644 src/testdir/test61.ok create mode 100644 src/testdir/test_undo.vim delete mode 100644 src/testdir/test_undolevels.vim diff --git a/src/Makefile b/src/Makefile index ea28eb715d..6aa1308015 100644 --- a/src/Makefile +++ b/src/Makefile @@ -2044,7 +2044,7 @@ test1 \ test30 test31 test32 test33 test34 test36 test37 test38 test39 \ test40 test41 test42 test43 test44 test45 test46 test47 test48 test49 \ test50 test51 test52 test53 test54 test55 test56 test57 test58 test59 \ - test60 test61 test62 test63 test64 test65 test66 test67 test68 test69 \ + test60 test62 test63 test64 test65 test66 test67 test68 test69 \ test70 test71 test72 test73 test74 test75 test76 test77 test78 test79 \ test80 test81 test82 test83 test84 test85 test86 test87 test88 test89 \ test90 test91 test92 test93 test94 test95 test97 test98 test99 \ @@ -2119,9 +2119,9 @@ test_arglist \ test_textobjects \ test_timers \ test_true_false \ - test_undolevels \ - test_usercommands \ + test_undo \ test_unlet \ + test_usercommands \ test_viminfo \ test_viml \ test_visual \ diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak index 193679810f..78f0b0b503 100644 --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -51,7 +51,6 @@ SCRIPTS_ALL = \ test56.out \ test57.out \ test60.out \ - test61.out \ test62.out \ test63.out \ test64.out \ @@ -188,6 +187,7 @@ NEW_TESTS = test_arglist.res \ test_stat.res \ test_syntax.res \ test_textobjects.res \ + test_undo.res \ test_usercommands.res \ test_viminfo.res \ test_viml.res \ diff --git a/src/testdir/test61.in b/src/testdir/test61.in deleted file mode 100644 index b18bbb52cb..0000000000 --- a/src/testdir/test61.in +++ /dev/null @@ -1,113 +0,0 @@ -Tests for undo tree. -Since this script is sourced we need to explicitly break changes up in -undo-able pieces. Do that by setting 'undolevels'. -Also tests :earlier and :later. - -STARTTEST -:echo undotree().entries -ENDTEST - -STARTTEST -:" Delete three characters and undo -Gx:set ul=100 -x:set ul=100 -x:.w! test.out -g-:.w >>test.out -g-:.w >>test.out -g-:.w >>test.out -g-:.w >>test.out -:" -:/^111/w >>test.out -:" Delete three other characters and go back in time step by step -$x:set ul=100 -x:set ul=100 -x:.w >>test.out -:sleep 1 -g-:.w >>test.out -g-:.w >>test.out -g-:.w >>test.out -g-:.w >>test.out -g-:.w >>test.out -g-:.w >>test.out -g-:.w >>test.out -g-:.w >>test.out -10g+:.w >>test.out -:" -:/^222/w >>test.out -:" Delay for three seconds and go some seconds forward and backward -:sleep 2 -Aa:set ul=100 -Ab:set ul=100 -Ac:set ul=100 -:.w >>test.out -:ear 1s -:.w >>test.out -:ear 3s -:.w >>test.out -:later 1s -:.w >>test.out -:later 1h -:.w >>test.out -:" -:" test undojoin -Goaaaa:set ul=100 -obbbbu:.w >>test.out -obbbb:set ul=100 -:undojoin -occccu:.w >>test.out -:e! Xtest -ione one one:set ul=100 -:w! -otwo:set ul=100 -otwo:set ul=100 -:w -othree:earlier 1f -:" expect "one one one\ntwo\ntwo" -:%yank a -:earlier 1f -:" expect "one one one" -:%yank b -:earlier 1f -:" expect empty line -:%yank c -:later 1f -:" expect "one one one" -:%yank d -:later 1f -:" expect "one one one\ntwo\ntwo" -:%yank e -:later 1f -:" expect "one one one\ntwo\ntwo\nthree" -ggO---:0put e -ggO---:0put d -ggO---:0put c -ggO---:0put b -ggO---:0put a -ggO---:w >>test.out -:so small.vim -:set nocp viminfo+=nviminfo -:enew! -oa: -:set ul=100 -ob: -:set ul=100 -o1a2=setline('.','1234') - -uu:" -oc: -:set ul=100 -o1a2=setline('.','1234') - -u:" -od: -:set ul=100 -o1a2=string(123) -u:" -:%w >>test.out -:qa! -ENDTEST - -1111 ----- -2222 ----- - -123456789 diff --git a/src/testdir/test61.ok b/src/testdir/test61.ok deleted file mode 100644 index ea4b473ad7..0000000000 --- a/src/testdir/test61.ok +++ /dev/null @@ -1,49 +0,0 @@ -456789 -3456789 -23456789 -123456789 -123456789 -1111 ----- -123456 -1234567 -12345678 -456789 -3456789 -23456789 -123456789 -123456789 -123456789 -123456 -2222 ----- -123456abc -123456 -123456789 -123456 -123456abc -aaaa -aaaa ---- -one one one -two -two ---- -one one one ---- - ---- -one one one ---- -one one one -two -two ---- -one one one -two -two -three - -a -b -c -12 -d diff --git a/src/testdir/test_alot.vim b/src/testdir/test_alot.vim index e9c84398e8..c7d651bd96 100644 --- a/src/testdir/test_alot.vim +++ b/src/testdir/test_alot.vim @@ -36,6 +36,5 @@ source test_tabline.vim source test_tagjump.vim source test_timers.vim source test_true_false.vim -source test_undolevels.vim source test_unlet.vim source test_window_cmd.vim diff --git a/src/testdir/test_undo.vim b/src/testdir/test_undo.vim new file mode 100644 index 0000000000..e521e35407 --- /dev/null +++ b/src/testdir/test_undo.vim @@ -0,0 +1,204 @@ +" Tests for the undo tree. +" Since this script is sourced we need to explicitly break changes up in +" undo-able pieces. Do that by setting 'undolevels'. +" Also tests :earlier and :later. + +func Test_undotree() + exe "normal Aabc\" + set ul=100 + exe "normal Adef\" + set ul=100 + undo + let d = undotree() + call assert_true(d.seq_last > 0) + call assert_true(d.seq_cur > 0) + call assert_true(d.seq_cur < d.seq_last) + call assert_true(len(d.entries) > 0) + " TODO: check more members of d + + w! Xtest + call assert_equal(d.save_last + 1, undotree().save_last) + call delete('Xtest') + bwipe Xtest +endfunc + +func FillBuffer() + for i in range(1,13) + put=i + " Set 'undolevels' to split undo. + exe "setg ul=" . &g:ul + endfor +endfunc + +func Test_global_local_undolevels() + new one + set undolevels=5 + call FillBuffer() + " will only undo the last 5 changes, end up with 13 - (5 + 1) = 7 lines + earlier 10 + call assert_equal(5, &g:undolevels) + call assert_equal(-123456, &l:undolevels) + call assert_equal('7', getline('$')) + + new two + setlocal undolevels=2 + call FillBuffer() + " will only undo the last 2 changes, end up with 13 - (2 + 1) = 10 lines + earlier 10 + call assert_equal(5, &g:undolevels) + call assert_equal(2, &l:undolevels) + call assert_equal('10', getline('$')) + + setlocal ul=10 + call assert_equal(5, &g:undolevels) + call assert_equal(10, &l:undolevels) + + " Setting local value in "two" must not change local value in "one" + wincmd p + call assert_equal(5, &g:undolevels) + call assert_equal(-123456, &l:undolevels) + + new three + setglobal ul=50 + call assert_equal(50, &g:undolevels) + call assert_equal(-123456, &l:undolevels) + + " Drop created windows + set ul& + new + only! +endfunc + +func BackOne(expected) + call feedkeys('g-', 'xt') + call assert_equal(a:expected, getline(1)) +endfunc + +func Test_undo_del_chars() + " Setup a buffer without creating undo entries + new + set ul=-1 + call setline(1, ['123-456']) + set ul=100 + 1 + call test_settime(100) + + " Delete three characters and undo with g- + call feedkeys('x', 'xt') + call feedkeys('x', 'xt') + call feedkeys('x', 'xt') + call assert_equal('-456', getline(1)) + call BackOne('3-456') + call BackOne('23-456') + call BackOne('123-456') + call assert_fails("BackOne('123-456')") + + :" Delete three other characters and go back in time with g- + call feedkeys('$x', 'xt') + call feedkeys('x', 'xt') + call feedkeys('x', 'xt') + call assert_equal('123-', getline(1)) + call test_settime(101) + + call BackOne('123-4') + call BackOne('123-45') + " skips '123-456' because it's older + call BackOne('-456') + call BackOne('3-456') + call BackOne('23-456') + call BackOne('123-456') + call assert_fails("BackOne('123-456')") + normal 10g+ + call assert_equal('123-', getline(1)) + + :" Jump two seconds and go some seconds forward and backward + call test_settime(103) + call feedkeys("Aa\", 'xt') + call feedkeys("Ab\", 'xt') + call feedkeys("Ac\", 'xt') + call assert_equal('123-abc', getline(1)) + earlier 1s + call assert_equal('123-', getline(1)) + earlier 3s + call assert_equal('123-456', getline(1)) + later 1s + call assert_equal('123-', getline(1)) + later 1h + call assert_equal('123-abc', getline(1)) + + close! +endfunc + +func Test_undojoin() + new + call feedkeys("Goaaaa\", 'xt') + call feedkeys("obbbb\", 'xt') + call assert_equal(['aaaa', 'bbbb'], getline(2, '$')) + call feedkeys("u", 'xt') + call assert_equal(['aaaa'], getline(2, '$')) + call feedkeys("obbbb\", 'xt') + undojoin + " Note: next change must not be as if typed + call feedkeys("occcc\", 'x') + call assert_equal(['aaaa', 'bbbb', 'cccc'], getline(2, '$')) + call feedkeys("u", 'xt') + call assert_equal(['aaaa'], getline(2, '$')) + close! +endfunc + +func Test_undo_write() + split Xtest + call feedkeys("ione one one\", 'xt') + w! + call feedkeys("otwo\", 'xt') + call feedkeys("otwo\", 'xt') + w + call feedkeys("othree\", 'xt') + call assert_equal(['one one one', 'two', 'two', 'three'], getline(1, '$')) + earlier 1f + call assert_equal(['one one one', 'two', 'two'], getline(1, '$')) + earlier 1f + call assert_equal(['one one one'], getline(1, '$')) + earlier 1f + call assert_equal([''], getline(1, '$')) + later 1f + call assert_equal(['one one one'], getline(1, '$')) + later 1f + call assert_equal(['one one one', 'two', 'two'], getline(1, '$')) + later 1f + call assert_equal(['one one one', 'two', 'two', 'three'], getline(1, '$')) + + close! + call delete('Xtest') + bwipe! Xtest +endfunc + +func Test_insert_expr() + new + " calling setline() triggers undo sync + call feedkeys("oa\", 'xt') + call feedkeys("ob\", 'xt') + set ul=100 + call feedkeys("o1\a2\=setline('.','1234')\\\", 'x') + call assert_equal(['a', 'b', '120', '34'], getline(2, '$')) + call feedkeys("u", 'x') + call assert_equal(['a', 'b', '12'], getline(2, '$')) + call feedkeys("u", 'x') + call assert_equal(['a', 'b'], getline(2, '$')) + + call feedkeys("oc\", 'xt') + set ul=100 + call feedkeys("o1\a2\=setline('.','1234')\\\", 'x') + call assert_equal(['a', 'b', 'c', '120', '34'], getline(2, '$')) + call feedkeys("u", 'x') + call assert_equal(['a', 'b', 'c', '12'], getline(2, '$')) + + call feedkeys("od\", 'xt') + set ul=100 + call feedkeys("o1\a2\=string(123)\\", 'x') + call assert_equal(['a', 'b', 'c', '12', 'd', '12123'], getline(2, '$')) + call feedkeys("u", 'x') + call assert_equal(['a', 'b', 'c', '12', 'd'], getline(2, '$')) + + close! +endfunc diff --git a/src/testdir/test_undolevels.vim b/src/testdir/test_undolevels.vim deleted file mode 100644 index b96b6934b0..0000000000 --- a/src/testdir/test_undolevels.vim +++ /dev/null @@ -1,48 +0,0 @@ -" Tests for 'undolevels' - -func FillBuffer() - for i in range(1,13) - put=i - " Set 'undolevels' to split undo. - exe "setg ul=" . &g:ul - endfor -endfunc - -func Test_global_local_undolevels() - new one - set undolevels=5 - call FillBuffer() - " will only undo the last 5 changes, end up with 13 - (5 + 1) = 7 lines - earlier 10 - call assert_equal(5, &g:undolevels) - call assert_equal(-123456, &l:undolevels) - call assert_equal('7', getline('$')) - - new two - setlocal undolevels=2 - call FillBuffer() - " will only undo the last 2 changes, end up with 13 - (2 + 1) = 10 lines - earlier 10 - call assert_equal(5, &g:undolevels) - call assert_equal(2, &l:undolevels) - call assert_equal('10', getline('$')) - - setlocal ul=10 - call assert_equal(5, &g:undolevels) - call assert_equal(10, &l:undolevels) - - " Setting local value in "two" must not change local value in "one" - wincmd p - call assert_equal(5, &g:undolevels) - call assert_equal(-123456, &l:undolevels) - - new three - setglobal ul=50 - call assert_equal(50, &g:undolevels) - call assert_equal(-123456, &l:undolevels) - - " Drop created windows - set ul& - new - only! -endfunc diff --git a/src/undo.c b/src/undo.c index 71e62a1995..bb5c73da24 100644 --- a/src/undo.c +++ b/src/undo.c @@ -534,7 +534,7 @@ u_savecommon( uhp->uh_seq = ++curbuf->b_u_seq_last; curbuf->b_u_seq_cur = uhp->uh_seq; - uhp->uh_time = time(NULL); + uhp->uh_time = vim_time(); uhp->uh_save_nr = 0; curbuf->b_u_time_cur = uhp->uh_time + 1; @@ -2350,7 +2350,7 @@ undo_time( else { if (dosec) - closest = (long)(time(NULL) - starttime + 1); + closest = (long)(vim_time() - starttime + 1); else if (dofile) closest = curbuf->b_u_save_nr_last + 2; else @@ -3104,10 +3104,10 @@ u_add_time(char_u *buf, size_t buflen, time_t tt) #ifdef HAVE_STRFTIME struct tm *curtime; - if (time(NULL) - tt >= 100) + if (vim_time() - tt >= 100) { curtime = localtime(&tt); - if (time(NULL) - tt < (60L * 60L * 12L)) + if (vim_time() - tt < (60L * 60L * 12L)) /* within 12 hours */ (void)strftime((char *)buf, buflen, "%H:%M:%S", curtime); else @@ -3117,7 +3117,7 @@ u_add_time(char_u *buf, size_t buflen, time_t tt) else #endif vim_snprintf((char *)buf, buflen, _("%ld seconds ago"), - (long)(time(NULL) - tt)); + (long)(vim_time() - tt)); } /* diff --git a/src/version.c b/src/version.c index 53597e9de8..76bca7a412 100644 --- a/src/version.c +++ b/src/version.c @@ -758,6 +758,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2113, /**/ 2112, /**/ From d05b191b91c4e16d6887bf781832d135d2a8fae5 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Fri, 29 Jul 2016 17:03:54 +0200 Subject: [PATCH 2/9] patch 7.4.2114 Problem: Tiny build fails. Solution: Always include vim_time(). --- src/ex_cmds.c | 2 -- src/version.c | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ex_cmds.c b/src/ex_cmds.c index 2afa5e733a..860d3dc984 100644 --- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -2867,7 +2867,6 @@ write_viminfo_barlines(vir_T *virp, FILE *fp_out) } #endif /* FEAT_VIMINFO */ -#if defined(FEAT_CMDHIST) || defined(FEAT_VIMINFO) || defined(PROTO) /* * Return the current time in seconds. Calls time(), unless test_settime() * was used. @@ -2881,7 +2880,6 @@ vim_time(void) return time(NULL); # endif } -#endif /* * Implementation of ":fixdel", also used by get_stty(). diff --git a/src/version.c b/src/version.c index 76bca7a412..b036bfd470 100644 --- a/src/version.c +++ b/src/version.c @@ -758,6 +758,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2114, /**/ 2113, /**/ From b9a46fec3e79d1fc8c406084a41733c647a5e535 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Fri, 29 Jul 2016 18:13:42 +0200 Subject: [PATCH 3/9] patch 7.4.2115 Problem: Loading defaults.vim with -C argument. Solution: Don't load the defaults script with -C argument. Test sourcing the defaults script. Set 'display' to "truncate". --- runtime/defaults.vim | 5 ++++- src/Makefile | 1 + src/main.c | 5 ++++- src/testdir/Make_all.mak | 1 + src/testdir/test_startup.vim | 8 ++++++++ src/version.c | 2 ++ 6 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 src/testdir/test_startup.vim diff --git a/runtime/defaults.vim b/runtime/defaults.vim index f83e8bb55a..1968ce9be7 100644 --- a/runtime/defaults.vim +++ b/runtime/defaults.vim @@ -1,7 +1,7 @@ " The default vimrc file. " " Maintainer: Bram Moolenaar -" Last change: 2016 Jul 28 +" Last change: 2016 Jul 29 " " This is loaded if no vimrc file was found. " Except when Vim is run with "-u NONE" or "-C". @@ -25,6 +25,9 @@ set ruler " show the cursor position all the time set showcmd " display incomplete commands set wildmenu " display completion matches in a status line +" Show @@@ in the last line if it is truncated. +set display=truncate + " Do incremental searching when it's possible to timeout. if has('reltime') set incsearch diff --git a/src/Makefile b/src/Makefile index 6aa1308015..7ef6e90c2d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -2107,6 +2107,7 @@ test_arglist \ test_regexp_utf8 \ test_reltime \ test_ruby \ + test_startup \ test_searchpos \ test_set \ test_sort \ diff --git a/src/main.c b/src/main.c index f24bda2387..51e4483e3e 100644 --- a/src/main.c +++ b/src/main.c @@ -90,6 +90,8 @@ static char *(main_errors[]) = static char_u *start_dir = NULL; /* current working dir on startup */ +static int has_dash_c_arg = FALSE; + int # ifdef VIMDLL _export @@ -1928,6 +1930,7 @@ command_line_scan(mparm_T *parmp) case 'C': /* "-C" Compatible */ change_compatible(TRUE); + has_dash_c_arg = TRUE; break; case 'e': /* "-e" Ex mode */ @@ -3001,7 +3004,7 @@ source_startup_scripts(mparm_T *parmp) #ifdef USR_EXRC_FILE2 && do_source((char_u *)USR_EXRC_FILE2, FALSE, DOSO_NONE) == FAIL #endif - ) + && !has_dash_c_arg) { /* When no .vimrc file was found: source defaults.vim. */ do_source((char_u *)VIM_DEFAULTS_FILE, FALSE, DOSO_NONE); diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak index 78f0b0b503..ced52e0a35 100644 --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -184,6 +184,7 @@ NEW_TESTS = test_arglist.res \ test_perl.res \ test_quickfix.res \ test_ruby.res \ + test_startup.res \ test_stat.res \ test_syntax.res \ test_textobjects.res \ diff --git a/src/testdir/test_startup.vim b/src/testdir/test_startup.vim new file mode 100644 index 0000000000..0630b2a841 --- /dev/null +++ b/src/testdir/test_startup.vim @@ -0,0 +1,8 @@ +" Check that loading startup.vim works. + +func Test_startup_script() + set compatible + source $VIMRUNTIME/defaults.vim + + call assert_equal(0, &compatible) +endfunc diff --git a/src/version.c b/src/version.c index b036bfd470..37aa255ce5 100644 --- a/src/version.c +++ b/src/version.c @@ -758,6 +758,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2115, /**/ 2114, /**/ From c73e4474b1f1b5b18a8d504eec5305e0c77981f7 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Fri, 29 Jul 2016 18:33:38 +0200 Subject: [PATCH 4/9] patch 7.4.2116 Problem: The default vimrc for Windows is very conservative. Solution: Use the defaults.vim in the Windows installer. --- src/dosinst.c | 3 +-- src/version.c | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dosinst.c b/src/dosinst.c index 2362418a6b..9a50cdbec4 100644 --- a/src/dosinst.c +++ b/src/dosinst.c @@ -1153,10 +1153,9 @@ install_vimrc(int idx) fprintf(fd, "set compatible\n"); break; case compat_some_enhancements: - fprintf(fd, "set nocompatible\n"); + fprintf(fd, "source $VIMRUNTIME/defaults.vim\n"); break; case compat_all_enhancements: - fprintf(fd, "set nocompatible\n"); fprintf(fd, "source $VIMRUNTIME/vimrc_example.vim\n"); break; } diff --git a/src/version.c b/src/version.c index 37aa255ce5..f797f41b64 100644 --- a/src/version.c +++ b/src/version.c @@ -758,6 +758,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2116, /**/ 2115, /**/ From f2c4c391192cab6e923b1a418d4af09106fba25f Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Fri, 29 Jul 2016 20:50:24 +0200 Subject: [PATCH 5/9] patch 7.4.2117 Problem: Deleting an augroup that still has autocmds does not give a warning. The next defined augroup takes its place. Solution: Give a warning and prevent the index being used for another group name. --- src/fileio.c | 49 +++++++++++++++++++++++++++++++----- src/testdir/test_autocmd.vim | 17 +++++++++++++ src/version.c | 2 ++ 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/src/fileio.c b/src/fileio.c index 4542f6a194..042bbec6e1 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -7758,6 +7758,7 @@ static AutoPatCmd *active_apc_list = NULL; /* stack of active autocommands */ */ static garray_T augroups = {0, 0, sizeof(char_u *), 10, NULL}; #define AUGROUP_NAME(i) (((char_u **)augroups.ga_data)[i]) +static char_u *deleted_augroup = NULL; /* * The ID of the current group. Group 0 is the default one. @@ -7812,7 +7813,7 @@ show_autocmd(AutoPat *ap, event_T event) if (ap->group != AUGROUP_DEFAULT) { if (AUGROUP_NAME(ap->group) == NULL) - msg_puts_attr((char_u *)_("--Deleted--"), hl_attr(HLF_E)); + msg_puts_attr(deleted_augroup, hl_attr(HLF_E)); else msg_puts_attr(AUGROUP_NAME(ap->group), hl_attr(HLF_T)); msg_puts((char_u *)" "); @@ -8009,8 +8010,31 @@ au_del_group(char_u *name) EMSG2(_("E367: No such group: \"%s\""), name); else { + event_T event; + AutoPat *ap; + int in_use = FALSE; + + for (event = (event_T)0; (int)event < (int)NUM_EVENTS; + event = (event_T)((int)event + 1)) + { + for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) + if (ap->group == i) + { + give_warning((char_u *)_("W19: Deleting augroup that is still in use"), TRUE); + in_use = TRUE; + event = NUM_EVENTS; + break; + } + } vim_free(AUGROUP_NAME(i)); - AUGROUP_NAME(i) = NULL; + if (in_use) + { + if (deleted_augroup == NULL) + deleted_augroup = (char_u *)_("--Deleted--"); + AUGROUP_NAME(i) = deleted_augroup; + } + else + AUGROUP_NAME(i) = NULL; } } @@ -8024,7 +8048,8 @@ au_find_group(char_u *name) int i; for (i = 0; i < augroups.ga_len; ++i) - if (AUGROUP_NAME(i) != NULL && STRCMP(AUGROUP_NAME(i), name) == 0) + if (AUGROUP_NAME(i) != NULL && AUGROUP_NAME(i) != deleted_augroup + && STRCMP(AUGROUP_NAME(i), name) == 0) return i; return AUGROUP_ERROR; } @@ -8081,10 +8106,20 @@ do_augroup(char_u *arg, int del_group) void free_all_autocmds(void) { + int i; + char_u *s; + for (current_augroup = -1; current_augroup < augroups.ga_len; ++current_augroup) do_autocmd((char_u *)"", TRUE); - ga_clear_strings(&augroups); + + for (i = 0; i < augroups.ga_len; ++i) + { + s = ((char_u **)(augroups.ga_data))[i]; + if (s != deleted_augroup) + vim_free(s); + } + ga_clear(&augroups); } #endif @@ -9830,7 +9865,8 @@ get_augroup_name(expand_T *xp UNUSED, int idx) return (char_u *)"END"; if (idx >= augroups.ga_len) /* end of list */ return NULL; - if (AUGROUP_NAME(idx) == NULL) /* skip deleted entries */ + if (AUGROUP_NAME(idx) == NULL || AUGROUP_NAME(idx) == deleted_augroup) + /* skip deleted entries */ return (char_u *)""; return AUGROUP_NAME(idx); /* return a name */ } @@ -9894,7 +9930,8 @@ get_event_name(expand_T *xp UNUSED, int idx) { if (idx < augroups.ga_len) /* First list group names, if wanted */ { - if (!include_groups || AUGROUP_NAME(idx) == NULL) + if (!include_groups || AUGROUP_NAME(idx) == NULL + || AUGROUP_NAME(idx) == deleted_augroup) return (char_u *)""; /* skip deleted entries */ return AUGROUP_NAME(idx); /* return a name */ } diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim index b9d5cfe27b..d856d3296a 100644 --- a/src/testdir/test_autocmd.vim +++ b/src/testdir/test_autocmd.vim @@ -151,3 +151,20 @@ func Test_early_bar() au! vimBarTest|echo 'hello' call assert_equal(1, len(split(execute('au vimBarTest'), "\n"))) endfunc + +func Test_augroup_warning() + augroup TheWarning + au VimEnter * echo 'entering' + augroup END + call assert_true(match(execute('au VimEnter'), "TheWarning.*VimEnter") >= 0) + redir => res + augroup! TheWarning + redir END + call assert_true(match(res, "W19:") >= 0) + call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0) + + " check "Another" does not take the pace of the deleted entry + augroup Another + augroup END + call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0) +endfunc diff --git a/src/version.c b/src/version.c index f797f41b64..962bae7c5e 100644 --- a/src/version.c +++ b/src/version.c @@ -758,6 +758,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2117, /**/ 2116, /**/ From 83a2a80d6f699ad9a236431170038698e355c025 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Fri, 29 Jul 2016 21:01:10 +0200 Subject: [PATCH 6/9] patch 7.4.2118 Problem: Mac: can't build with tiny features. Solution: Don't define FEAT_CLIPBOARD unconditionally. (Kazunobu Kuriyama) --- src/version.c | 2 ++ src/vim.h | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/version.c b/src/version.c index 962bae7c5e..153b3f5f89 100644 --- a/src/version.c +++ b/src/version.c @@ -758,6 +758,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2118, /**/ 2117, /**/ diff --git a/src/vim.h b/src/vim.h index 498078a4d8..b7855270ec 100644 --- a/src/vim.h +++ b/src/vim.h @@ -98,11 +98,11 @@ # ifndef HAVE_CONFIG_H # define UNIX # endif -# ifndef FEAT_CLIPBOARD +# if defined(FEAT_SMALL) && !defined(FEAT_CLIPBOARD) # define FEAT_CLIPBOARD -# if defined(FEAT_SMALL) && !defined(FEAT_MOUSE) -# define FEAT_MOUSE -# endif +# endif +# if defined(FEAT_SMALL) && !defined(FEAT_MOUSE) +# define FEAT_MOUSE # endif #endif #if defined(MACOS_X) || defined(MACOS_CLASSIC) From 1e96d9bf98f9ab84d5af7f98d6a961d91b17364f Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Fri, 29 Jul 2016 22:15:09 +0200 Subject: [PATCH 7/9] patch 7.4.2119 Problem: Closures are not supported. Solution: Capture variables in lambdas from the outer scope. (Yasuhiro Matsumoto, Ken Takata) --- runtime/doc/eval.txt | 27 ++++- src/eval.c | 57 ++++++++- src/ex_cmds2.c | 12 +- src/globals.h | 3 + src/proto/eval.pro | 1 + src/proto/userfunc.pro | 2 + src/testdir/test_lambda.vim | 160 ++++++++++++++++++++++++- src/userfunc.c | 233 ++++++++++++++++++++++++++++++------ src/version.c | 2 + 9 files changed, 450 insertions(+), 47 deletions(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 7b421aa112..196e71cf0e 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1,4 +1,4 @@ -*eval.txt* For Vim version 7.4. Last change: 2016 Jul 24 +*eval.txt* For Vim version 7.4. Last change: 2016 Jul 29 VIM REFERENCE MANUAL by Bram Moolenaar @@ -40,7 +40,7 @@ done, the features in this document are not available. See |+eval| and There are nine types of variables: Number A 32 or 64 bit signed number. |expr-number| *Number* - 64-bit Number is available only when compiled with the + 64-bit Numbers are available only when compiled with the |+num64| feature. Examples: -123 0x10 0177 @@ -1219,7 +1219,7 @@ the following ways: 1. The body of the lambda expression is an |expr1| and not a sequence of |Ex| commands. -2. The prefix "a:" is optional for arguments. E.g.: > +2. The prefix "a:" should not be used for arguments. E.g.: > :let F = {arg1, arg2 -> arg1 - arg2} :echo F(5, 2) < 3 @@ -1228,6 +1228,18 @@ The arguments are optional. Example: > :let F = {-> 'error function'} :echo F() < error function + *closure* +Lambda expressions can access outer scope variables and arguments. This is +often called a closure. Example where "i" a and "a:arg" are used in a lambda +while they exists in the function scope. They remain valid even after the +function returns: > + :function Foo(arg) + : let i = 3 + : return {x -> x + i - a:arg} + :endfunction + :let Bar = Foo(4) + :echo Bar(6) +< 5 Examples for using a lambda expression with |sort()|, |map()| and |filter()|: > :echo map([1, 2, 3], {idx, val -> val + 1}) @@ -1245,6 +1257,12 @@ The lambda expression is also useful for Channel, Job and timer: > Note how execute() is used to execute an Ex command. That's ugly though. + +Lambda expressions have internal names like '42'. If you get an error +for a lambda expression, you can find what it is with the following command: > + :function {'42'} +See also: |numbered-function| + ============================================================================== 3. Internal variable *internal-variables* *E461* @@ -7494,7 +7512,8 @@ test_null_string() *test_null_string()* test_settime({expr}) *test_settime()* Set the time Vim uses internally. Currently only used for - timestamps in the history, as they are used in viminfo. + timestamps in the history, as they are used in viminfo, and + for undo. {expr} must evaluate to a number. When the value is zero the normal behavior is restored. diff --git a/src/eval.c b/src/eval.c index 6f10756abf..463c8927ec 100644 --- a/src/eval.c +++ b/src/eval.c @@ -237,8 +237,8 @@ static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate); static int get_env_len(char_u **arg); static char_u * make_expanded_name(char_u *in_start, char_u *expr_start, char_u *expr_end, char_u *in_end); +static void check_vars(char_u *name, int len); static typval_T *alloc_string_tv(char_u *string); -static hashtab_T *find_var_ht(char_u *name, char_u **varname); static void delete_var(hashtab_T *ht, hashitem_T *hi); static void list_one_var(dictitem_T *v, char_u *prefix, int *first); static void list_one_var_a(char_u *prefix, char_u *name, int type, char_u *string, int *first); @@ -4332,6 +4332,9 @@ eval7( { partial_T *partial; + if (!evaluate) + check_vars(s, len); + /* If "s" is the name of a variable of type VAR_FUNC * use its contents. */ s = deref_func_name(s, &len, &partial, !evaluate); @@ -4363,7 +4366,10 @@ eval7( else if (evaluate) ret = get_var_tv(s, len, rettv, NULL, TRUE, FALSE); else + { + check_vars(s, len); ret = OK; + } } vim_free(alias); } @@ -5540,6 +5546,10 @@ set_ref_in_item( } } } + else if (tv->v_type == VAR_FUNC) + { + abort = set_ref_in_func(tv->vval.v_string, copyID); + } else if (tv->v_type == VAR_PARTIAL) { partial_T *pt = tv->vval.v_partial; @@ -5549,6 +5559,8 @@ set_ref_in_item( */ if (pt != NULL) { + abort = set_ref_in_func(pt->pt_name, copyID); + if (pt->pt_dict != NULL) { typval_T dtv; @@ -6790,6 +6802,34 @@ get_var_tv( return ret; } +/* + * Check if variable "name[len]" is a local variable or an argument. + * If so, "*eval_lavars_used" is set to TRUE. + */ + static void +check_vars(char_u *name, int len) +{ + int cc; + char_u *varname; + hashtab_T *ht; + + if (eval_lavars_used == NULL) + return; + + /* truncate the name, so that we can use strcmp() */ + cc = name[len]; + name[len] = NUL; + + ht = find_var_ht(name, &varname); + if (ht == get_funccal_local_ht() || ht == get_funccal_args_ht()) + { + if (find_var(name, NULL, TRUE) != NULL) + *eval_lavars_used = TRUE; + } + + name[len] = cc; +} + /* * Handle expr[expr], expr[expr:expr] subscript and .name lookup. * Also handle function call with Funcref variable: func(expr) @@ -7274,13 +7314,20 @@ find_var(char_u *name, hashtab_T **htp, int no_autoload) { char_u *varname; hashtab_T *ht; + dictitem_T *ret = NULL; ht = find_var_ht(name, &varname); if (htp != NULL) *htp = ht; if (ht == NULL) return NULL; - return find_var_in_ht(ht, *name, varname, no_autoload || htp != NULL); + ret = find_var_in_ht(ht, *name, varname, no_autoload || htp != NULL); + if (ret != NULL) + return ret; + + /* Search in parent scope for lambda */ + return find_var_in_scoped_ht(name, varname ? &varname : NULL, + no_autoload || htp != NULL); } /* @@ -7341,7 +7388,7 @@ find_var_in_ht( * Return NULL if the name is not valid. * Set "varname" to the start of name without ':'. */ - static hashtab_T * + hashtab_T * find_var_ht(char_u *name, char_u **varname) { hashitem_T *hi; @@ -7617,6 +7664,10 @@ set_var( } v = find_var_in_ht(ht, 0, varname, TRUE); + /* Search in parent scope which is possible to reference from lambda */ + if (v == NULL) + v = find_var_in_scoped_ht(name, varname ? &varname : NULL, TRUE); + if ((tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL) && var_check_func_name(name, v == NULL)) return; diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c index 83305b2407..ec9f50a527 100644 --- a/src/ex_cmds2.c +++ b/src/ex_cmds2.c @@ -1265,8 +1265,16 @@ set_ref_in_timer(int copyID) for (timer = first_timer; timer != NULL; timer = timer->tr_next) { - tv.v_type = VAR_PARTIAL; - tv.vval.v_partial = timer->tr_partial; + if (timer->tr_partial != NULL) + { + tv.v_type = VAR_PARTIAL; + tv.vval.v_partial = timer->tr_partial; + } + else + { + tv.v_type = VAR_FUNC; + tv.vval.v_string = timer->tr_callback; + } abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL); } return abort; diff --git a/src/globals.h b/src/globals.h index 7ca3ecd3a0..ec6e152911 100644 --- a/src/globals.h +++ b/src/globals.h @@ -1658,6 +1658,9 @@ EXTERN time_T time_for_testing INIT(= 0); /* Abort conversion to string after a recursion error. */ EXTERN int did_echo_string_emsg INIT(= FALSE); + +/* Used for checking if local variables or arguments used in a lambda. */ +EXTERN int *eval_lavars_used INIT(= NULL); #endif /* diff --git a/src/proto/eval.pro b/src/proto/eval.pro index 5d9b35c625..56f0b49278 100644 --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -87,6 +87,7 @@ char_u *get_tv_string_chk(typval_T *varp); char_u *get_tv_string_buf_chk(typval_T *varp, char_u *buf); dictitem_T *find_var(char_u *name, hashtab_T **htp, int no_autoload); dictitem_T *find_var_in_ht(hashtab_T *ht, int htname, char_u *varname, int no_autoload); +hashtab_T *find_var_ht(char_u *name, char_u **varname); char_u *get_var_value(char_u *name); void new_script_vars(scid_T id); void init_var_dict(dict_T *dict, dictitem_T *dict_var, int scope); diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro index feacd4c004..e503bcdcce 100644 --- a/src/proto/userfunc.pro +++ b/src/proto/userfunc.pro @@ -46,7 +46,9 @@ void *clear_current_funccal(void); void restore_current_funccal(void *f); void list_func_vars(int *first); dict_T *get_current_funccal_dict(hashtab_T *ht); +dictitem_T *find_var_in_scoped_ht(char_u *name, char_u **varname, int no_autoload); int set_ref_in_previous_funccal(int copyID); int set_ref_in_call_stack(int copyID); int set_ref_in_func_args(int copyID); +int set_ref_in_func(char_u *name, int copyID); /* vim: set ft=c : */ diff --git a/src/testdir/test_lambda.vim b/src/testdir/test_lambda.vim index cbd4addce6..02face8a74 100644 --- a/src/testdir/test_lambda.vim +++ b/src/testdir/test_lambda.vim @@ -21,7 +21,7 @@ function! Test_lambda_with_timer() let s:timer_id = 0 function! s:Foo() "let n = 0 - let s:timer_id = timer_start(50, {-> execute("let s:n += 1 | echo s:n")}, {"repeat": -1}) + let s:timer_id = timer_start(50, {-> execute("let s:n += 1 | echo s:n", "")}, {"repeat": -1}) endfunction call s:Foo() @@ -51,3 +51,161 @@ func Test_not_lamda() let x = {'>' : 'foo'} call assert_equal('foo', x['>']) endfunc + +function! Test_lambda_capture_by_reference() + let v = 1 + let l:F = {x -> x + v} + let v = 2 + call assert_equal(12, l:F(10)) +endfunction + +function! Test_lambda_side_effect() + function! s:update_and_return(arr) + let a:arr[1] = 5 + return a:arr + endfunction + + function! s:foo(arr) + return {-> s:update_and_return(a:arr)} + endfunction + + let arr = [3,2,1] + call assert_equal([3, 5, 1], s:foo(arr)()) +endfunction + +function! Test_lambda_refer_local_variable_from_other_scope() + function! s:foo(X) + return a:X() " refer l:x in s:bar() + endfunction + + function! s:bar() + let x = 123 + return s:foo({-> x}) + endfunction + + call assert_equal(123, s:bar()) +endfunction + +function! Test_lambda_do_not_share_local_variable() + function! s:define_funcs() + let l:One = {-> split(execute("let a = 'abc' | echo a"))[0]} + let l:Two = {-> exists("a") ? a : "no"} + return [l:One, l:Two] + endfunction + + let l:F = s:define_funcs() + + call assert_equal('no', l:F[1]()) + call assert_equal('abc', l:F[0]()) + call assert_equal('no', l:F[1]()) +endfunction + +function! Test_lambda_closure() + function! s:foo() + let x = 0 + return {-> [execute("let x += 1"), x][-1]} + endfunction + + let l:F = s:foo() + call test_garbagecollect_now() + call assert_equal(1, l:F()) + call assert_equal(2, l:F()) + call assert_equal(3, l:F()) + call assert_equal(4, l:F()) +endfunction + +function! Test_lambda_with_a_var() + function! s:foo() + let x = 2 + return {... -> a:000 + [x]} + endfunction + function! s:bar() + return s:foo()(1) + endfunction + + call assert_equal([1, 2], s:bar()) +endfunction + +function! Test_lambda_call_lambda_from_lambda() + function! s:foo(x) + let l:F1 = {-> {-> a:x}} + return {-> l:F1()} + endfunction + + let l:F = s:foo(1) + call assert_equal(1, l:F()()) +endfunction + +function! Test_lambda_delfunc() + function! s:gen() + let pl = l: + let l:Foo = {-> get(pl, "Foo", get(pl, "Bar", {-> 0}))} + let l:Bar = l:Foo + delfunction l:Foo + return l:Bar + endfunction + + let l:F = s:gen() + call assert_fails(':call l:F()', 'E117:') +endfunction + +function! Test_lambda_scope() + function! s:NewCounter() + let c = 0 + return {-> [execute('let c += 1'), c][-1]} + endfunction + + function! s:NewCounter2() + return {-> [execute('let c += 100'), c][-1]} + endfunction + + let l:C = s:NewCounter() + let l:D = s:NewCounter2() + + call assert_equal(1, l:C()) + call assert_fails(':call l:D()', 'E15:') " E121: then E15: + call assert_equal(2, l:C()) +endfunction + +function! Test_lambda_share_scope() + function! s:New() + let c = 0 + let l:Inc0 = {-> [execute('let c += 1'), c][-1]} + let l:Dec0 = {-> [execute('let c -= 1'), c][-1]} + return [l:Inc0, l:Dec0] + endfunction + + let [l:Inc, l:Dec] = s:New() + + call assert_equal(1, l:Inc()) + call assert_equal(2, l:Inc()) + call assert_equal(1, l:Dec()) +endfunction + +function! Test_lambda_circular_reference() + function! s:Foo() + let d = {} + let d.f = {-> d} + return d.f + endfunction + + call s:Foo() + call test_garbagecollect_now() + let i = 0 | while i < 10000 | call s:Foo() | let i+= 1 | endwhile + call test_garbagecollect_now() +endfunction + +function! Test_lambda_combination() + call assert_equal(2, {x -> {x -> x}}(1)(2)) + call assert_equal(10, {y -> {x -> x(y)(10)}({y -> y})}({z -> z})) + call assert_equal(5.0, {x -> {y -> x / y}}(10)(2.0)) + call assert_equal(6, {x -> {y -> {z -> x + y + z}}}(1)(2)(3)) + + call assert_equal(6, {x -> {f -> f(x)}}(3)({x -> x * 2})) + call assert_equal(6, {f -> {x -> f(x)}}({x -> x * 2})(3)) + + " Z combinator + let Z = {f -> {x -> f({y -> x(x)(y)})}({x -> f({y -> x(x)(y)})})} + let Fact = {f -> {x -> x == 0 ? 1 : x * f(x - 1)}} + call assert_equal(120, Z(Fact)(5)) +endfunction diff --git a/src/userfunc.c b/src/userfunc.c index 30eeb347fb..caa0cfd660 100644 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -15,6 +15,8 @@ #if defined(FEAT_EVAL) || defined(PROTO) +typedef struct funccall_S funccall_T; + /* * Structure to hold info for a user function. */ @@ -47,6 +49,7 @@ struct ufunc scid_T uf_script_ID; /* ID of script where function was defined, used for s: variables */ int uf_refcount; /* for numbered function: reference count */ + funccall_T *uf_scoped; /* l: local variables for closure */ char_u uf_name[1]; /* name of function (actually longer); can start with 123_ ( is K_SPECIAL KS_EXTRA KE_SNR) */ @@ -70,8 +73,6 @@ struct ufunc #define FIXVAR_CNT 12 /* number of fixed variables */ /* structure to hold info for a function that is currently being executed. */ -typedef struct funccall_S funccall_T; - struct funccall_S { ufunc_T *func; /* function being called */ @@ -96,6 +97,11 @@ struct funccall_S proftime_T prof_child; /* time spent in a child */ #endif funccall_T *caller; /* calling function or NULL */ + + /* for closure */ + int fc_refcount; + int fc_copyID; /* for garbage collection */ + garray_T fc_funcs; /* list of ufunc_T* which refer this */ }; /* @@ -259,6 +265,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) { garray_T newargs; garray_T newlines; + garray_T *pnewargs; ufunc_T *fp = NULL; int varargs; int ret; @@ -266,6 +273,8 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) char_u *start = skipwhite(*arg + 1); char_u *s, *e; static int lambda_no = 0; + int *old_eval_lavars = eval_lavars_used; + int eval_lavars = FALSE; ga_init(&newargs); ga_init(&newlines); @@ -276,11 +285,19 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) return NOTDONE; /* Parse the arguments again. */ + if (evaluate) + pnewargs = &newargs; + else + pnewargs = NULL; *arg = skipwhite(*arg + 1); - ret = get_function_args(arg, '-', &newargs, &varargs, FALSE); + ret = get_function_args(arg, '-', pnewargs, &varargs, FALSE); if (ret == FAIL || **arg != '>') goto errret; + /* Set up dictionaries for checking local variables and arguments. */ + if (evaluate) + eval_lavars_used = &eval_lavars; + /* Get the start and the end of the expression. */ *arg = skipwhite(*arg + 1); s = *arg; @@ -298,32 +315,42 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) int len; char_u *p; - fp = (ufunc_T *)alloc((unsigned)(sizeof(ufunc_T) + 20)); + sprintf((char*)name, "%d", ++lambda_no); + + fp = (ufunc_T *)alloc((unsigned)(sizeof(ufunc_T) + STRLEN(name))); if (fp == NULL) goto errret; - sprintf((char*)name, "%d", ++lambda_no); - ga_init2(&newlines, (int)sizeof(char_u *), 1); if (ga_grow(&newlines, 1) == FAIL) goto errret; - /* Add "return " before the expression. - * TODO: Support multiple expressions. */ + /* Add "return " before the expression. */ len = 7 + e - s + 1; p = (char_u *)alloc(len); if (p == NULL) goto errret; ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p; STRCPY(p, "return "); - STRNCPY(p + 7, s, e - s); - p[7 + e - s] = NUL; + vim_strncpy(p + 7, s, e - s); fp->uf_refcount = 1; STRCPY(fp->uf_name, name); hash_add(&func_hashtab, UF2HIKEY(fp)); fp->uf_args = newargs; fp->uf_lines = newlines; + if (current_funccal != NULL && eval_lavars) + { + fp->uf_scoped = current_funccal; + current_funccal->fc_refcount++; + if (ga_grow(¤t_funccal->fc_funcs, 1) == FAIL) + goto errret; + ((ufunc_T **)current_funccal->fc_funcs.ga_data) + [current_funccal->fc_funcs.ga_len++] = fp; + func_ref(current_funccal->func->uf_name); + } + else + fp->uf_scoped = NULL; #ifdef FEAT_PROFILE fp->uf_tml_count = NULL; @@ -341,15 +368,15 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) rettv->vval.v_string = vim_strsave(name); rettv->v_type = VAR_FUNC; } - else - ga_clear_strings(&newargs); + eval_lavars_used = old_eval_lavars; return OK; errret: ga_clear_strings(&newargs); ga_clear_strings(&newlines); vim_free(fp); + eval_lavars_used = old_eval_lavars; return FAIL; } @@ -624,6 +651,15 @@ free_funccal( int free_val) /* a: vars were allocated */ { listitem_T *li; + int i; + + for (i = 0; i < fc->fc_funcs.ga_len; ++i) + { + ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; + + if (fp != NULL) + fp->uf_scoped = NULL; + } /* The a: variables typevals may not have been allocated, only free the * allocated variables. */ @@ -637,6 +673,16 @@ free_funccal( for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) clear_tv(&li->li_tv); + for (i = 0; i < fc->fc_funcs.ga_len; ++i) + { + ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; + + if (fp != NULL) + func_unref(fc->func->uf_name); + } + ga_clear(&fc->fc_funcs); + + func_unref(fc->func->uf_name); vim_free(fc); } @@ -696,6 +742,11 @@ call_user_func( /* Check if this function has a breakpoint. */ fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0); fc->dbg_tick = debug_tick; + /* Set up fields for closure. */ + fc->fc_refcount = 0; + fc->fc_copyID = 0; + ga_init2(&fc->fc_funcs, sizeof(ufunc_T *), 1); + func_ref(fp->uf_name); if (STRNCMP(fp->uf_name, "", 8) == 0) islambda = TRUE; @@ -758,7 +809,6 @@ call_user_func( for (i = 0; i < argcount; ++i) { int addlocal = FALSE; - dictitem_T *v2; ai = i - fp->uf_args.ga_len; if (ai < 0) @@ -778,9 +828,6 @@ call_user_func( { v = &fc->fixvar[fixvar_idx++].var; v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - - if (addlocal) - v2 = v; } else { @@ -789,36 +836,23 @@ call_user_func( if (v == NULL) break; v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; - - if (addlocal) - { - v2 = (dictitem_T *)alloc((unsigned)(sizeof(dictitem_T) - + STRLEN(name))); - if (v2 == NULL) - { - vim_free(v); - break; - } - v2->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; - } } STRCPY(v->di_key, name); - hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v)); /* Note: the values are copied directly to avoid alloc/free. * "argvars" must have VAR_FIXED for v_lock. */ v->di_tv = argvars[i]; v->di_tv.v_lock = VAR_FIXED; - /* Named arguments can be accessed without the "a:" prefix in lambda - * expressions. Add to the l: dict. */ if (addlocal) { - STRCPY(v2->di_key, name); - copy_tv(&v->di_tv, &v2->di_tv); - v2->di_tv.v_lock = VAR_FIXED; - hash_add(&fc->l_vars.dv_hashtab, DI2HIKEY(v2)); + /* Named arguments should be accessed without the "a:" prefix in + * lambda expressions. Add to the l: dict. */ + copy_tv(&v->di_tv, &v->di_tv); + hash_add(&fc->l_vars.dv_hashtab, DI2HIKEY(v)); } + else + hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v)); if (ai >= 0 && ai < MAX_FUNC_ARGS) { @@ -1014,7 +1048,8 @@ call_user_func( * free the funccall_T and what's in it. */ if (fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT - && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT) + && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT + && fc->fc_refcount <= 0) { free_funccal(fc, FALSE); } @@ -1048,6 +1083,52 @@ call_user_func( } } +/* + * Unreference "fc": decrement the reference count and free it when it + * becomes zero. If "fp" is not NULL, "fp" is detached from "fc". + */ + static void +funccal_unref(funccall_T *fc, ufunc_T *fp) +{ + funccall_T **pfc; + int i; + int freed = FALSE; + + if (fc == NULL) + return; + + if (--fc->fc_refcount <= 0) + { + for (pfc = &previous_funccal; *pfc != NULL; ) + { + if (fc == *pfc + && fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT + && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT + && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT) + { + *pfc = fc->caller; + free_funccal(fc, TRUE); + freed = TRUE; + } + else + pfc = &(*pfc)->caller; + } + } + if (!freed) + { + func_unref(fc->func->uf_name); + + if (fp != NULL) + { + for (i = 0; i < fc->fc_funcs.ga_len; ++i) + { + if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) + ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL; + } + } + } +} + /* * Free a function and remove it from the list of functions. */ @@ -1072,6 +1153,8 @@ func_free(ufunc_T *fp) else hash_remove(&func_hashtab, hi); + funccal_unref(fp->uf_scoped, fp); + vim_free(fp); } @@ -2216,6 +2299,7 @@ ex_function(exarg_T *eap) } fp->uf_args = newargs; fp->uf_lines = newlines; + fp->uf_scoped = NULL; #ifdef FEAT_PROFILE fp->uf_tml_count = NULL; fp->uf_tml_total = NULL; @@ -2705,7 +2789,8 @@ can_free_funccal(funccall_T *fc, int copyID) { return (fc->l_varlist.lv_copyID != copyID && fc->l_vars.dv_copyID != copyID - && fc->l_avars.dv_copyID != copyID); + && fc->l_avars.dv_copyID != copyID + && fc->fc_copyID != copyID); } /* @@ -3450,6 +3535,40 @@ get_current_funccal_dict(hashtab_T *ht) return NULL; } +/* + * Search variable in parent scope. + */ + dictitem_T * +find_var_in_scoped_ht(char_u *name, char_u **varname, int no_autoload) +{ + dictitem_T *v = NULL; + funccall_T *old_current_funccal = current_funccal; + hashtab_T *ht; + + if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) + return NULL; + + /* Search in parent scope which is possible to reference from lambda */ + current_funccal = current_funccal->func->uf_scoped; + while (current_funccal) + { + ht = find_var_ht(name, varname ? &(*varname) : NULL); + if (ht != NULL) + { + v = find_var_in_ht(ht, *name, + varname ? *varname : NULL, no_autoload); + if (v != NULL) + break; + } + if (current_funccal == current_funccal->func->uf_scoped) + break; + current_funccal = current_funccal->func->uf_scoped; + } + current_funccal = old_current_funccal; + + return v; +} + /* * Set "copyID + 1" in previous_funccal and callers. */ @@ -3461,6 +3580,7 @@ set_ref_in_previous_funccal(int copyID) for (fc = previous_funccal; fc != NULL; fc = fc->caller) { + fc->fc_copyID = copyID + 1; abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL); abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, @@ -3480,6 +3600,7 @@ set_ref_in_call_stack(int copyID) for (fc = current_funccal; fc != NULL; fc = fc->caller) { + fc->fc_copyID = copyID; abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); } @@ -3501,4 +3622,42 @@ set_ref_in_func_args(int copyID) return abort; } +/* + * Mark all lists and dicts referenced through function "name" with "copyID". + * "list_stack" is used to add lists to be marked. Can be NULL. + * "ht_stack" is used to add hashtabs to be marked. Can be NULL. + * + * Returns TRUE if setting references failed somehow. + */ + int +set_ref_in_func(char_u *name, int copyID) +{ + ufunc_T *fp; + funccall_T *fc; + int error = ERROR_NONE; + char_u fname_buf[FLEN_FIXED + 1]; + char_u *tofree = NULL; + char_u *fname; + + if (name == NULL) + return FALSE; + + fname = fname_trans_sid(name, fname_buf, &tofree, &error); + fp = find_func(fname); + if (fp != NULL) + { + for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) + { + if (fc->fc_copyID != copyID) + { + fc->fc_copyID = copyID; + set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); + set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL); + } + } + } + vim_free(tofree); + return FALSE; +} + #endif /* FEAT_EVAL */ diff --git a/src/version.c b/src/version.c index 153b3f5f89..865ac929b6 100644 --- a/src/version.c +++ b/src/version.c @@ -758,6 +758,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2119, /**/ 2118, /**/ From 10ce39a0d52272a3dfff2feb8c631529f29e6740 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Fri, 29 Jul 2016 22:37:06 +0200 Subject: [PATCH 8/9] patch 7.4.2120 Problem: User defined functions can't be a closure. Solution: Add the "closure" argument. Allow using :unlet on a bound variable. (Yasuhiro Matsumoto, Ken Takata) --- runtime/doc/eval.txt | 25 ++++++++++++- src/eval.c | 4 ++- src/proto/userfunc.pro | 1 + src/testdir/test_lambda.vim | 36 ++++++++++++++++++- src/userfunc.c | 71 ++++++++++++++++++++++++++++++++++--- src/version.c | 2 ++ 6 files changed, 132 insertions(+), 7 deletions(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 196e71cf0e..8dfbb3a28e 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1240,6 +1240,7 @@ function returns: > :let Bar = Foo(4) :echo Bar(6) < 5 +See also |:func-closure|. Examples for using a lambda expression with |sort()|, |map()| and |filter()|: > :echo map([1, 2, 3], {idx, val -> val + 1}) @@ -8217,7 +8218,7 @@ last defined. Example: > See |:verbose-cmd| for more information. *E124* *E125* *E853* *E884* -:fu[nction][!] {name}([arguments]) [range] [abort] [dict] +:fu[nction][!] {name}([arguments]) [range] [abort] [dict] [closure] Define a new function by the name {name}. The name must be made of alphanumeric characters and '_', and must start with a capital or "s:" (see above). Note @@ -8260,6 +8261,28 @@ See |:verbose-cmd| for more information. be invoked through an entry in a |Dictionary|. The local variable "self" will then be set to the dictionary. See |Dictionary-function|. + *:func-closure* *E932* + When the [closure] argument is added, the function + can access variables and arguments from the outer + scope. This is usually called a closure. In this + example Bar() uses "x" from the scope of Foo(). It + remains referenced even after Foo() returns: > + :function! Foo() + : let x = 0 + : function! Bar() closure + : let x += 1 + : return x + : endfunction + : return function('Bar') + :endfunction + + :let F = Foo() + :echo F() +< 1 > + :echo F() +< 2 > + :echo F() +< 3 *function-search-undo* The last used search pattern and the redo command "." diff --git a/src/eval.c b/src/eval.c index 463c8927ec..956f05cb1f 100644 --- a/src/eval.c +++ b/src/eval.c @@ -2837,7 +2837,9 @@ do_unlet(char_u *name, int forceit) } } hi = hash_find(ht, varname); - if (!HASHITEM_EMPTY(hi)) + if (HASHITEM_EMPTY(hi)) + hi = find_hi_in_scoped_ht(name, &varname, &ht); + if (hi != NULL && !HASHITEM_EMPTY(hi)) { di = HI2DI(hi); if (var_check_fixed(di->di_flags, name, FALSE) diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro index e503bcdcce..42c5883d01 100644 --- a/src/proto/userfunc.pro +++ b/src/proto/userfunc.pro @@ -46,6 +46,7 @@ void *clear_current_funccal(void); void restore_current_funccal(void *f); void list_func_vars(int *first); dict_T *get_current_funccal_dict(hashtab_T *ht); +hashitem_T *find_hi_in_scoped_ht(char_u *name, char_u **varname, hashtab_T **pht); dictitem_T *find_var_in_scoped_ht(char_u *name, char_u **varname, int no_autoload); int set_ref_in_previous_funccal(int copyID); int set_ref_in_call_stack(int copyID); diff --git a/src/testdir/test_lambda.vim b/src/testdir/test_lambda.vim index 02face8a74..9eb34e434e 100644 --- a/src/testdir/test_lambda.vim +++ b/src/testdir/test_lambda.vim @@ -1,3 +1,5 @@ +" Test for lambda and closure + function! Test_lambda_with_filter() let s:x = 2 call assert_equal([2, 3], filter([1, 2, 3], {i, v -> v >= s:x})) @@ -100,7 +102,7 @@ function! Test_lambda_do_not_share_local_variable() call assert_equal('no', l:F[1]()) endfunction -function! Test_lambda_closure() +function! Test_lambda_closure_counter() function! s:foo() let x = 0 return {-> [execute("let x += 1"), x][-1]} @@ -209,3 +211,35 @@ function! Test_lambda_combination() let Fact = {f -> {x -> x == 0 ? 1 : x * f(x - 1)}} call assert_equal(120, Z(Fact)(5)) endfunction + +function! Test_closure_counter() + function! s:foo() + let x = 0 + function! s:bar() closure + let x += 1 + return x + endfunction + return function('s:bar') + endfunction + + let l:F = s:foo() + call test_garbagecollect_now() + call assert_equal(1, l:F()) + call assert_equal(2, l:F()) + call assert_equal(3, l:F()) + call assert_equal(4, l:F()) +endfunction + +function! Test_closure_unlet() + function! s:foo() + let x = 1 + function! s:bar() closure + unlet x + endfunction + call s:bar() + return l: + endfunction + + call assert_false(has_key(s:foo(), 'x')) + call test_garbagecollect_now() +endfunction diff --git a/src/userfunc.c b/src/userfunc.c index caa0cfd660..0c6c613e3e 100644 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -59,6 +59,7 @@ struct ufunc #define FC_ABORT 1 /* abort function on error */ #define FC_RANGE 2 /* function accepts range */ #define FC_DICT 4 /* Dict function, uses "self" */ +#define FC_CLOSURE 8 /* closure, uses outer scope variables */ /* From user function to hashitem and back. */ #define UF2HIKEY(fp) ((fp)->uf_name) @@ -312,7 +313,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) if (evaluate) { - int len; + int len, flags = 0; char_u *p; sprintf((char*)name, "%d", ++lambda_no); @@ -341,6 +342,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) fp->uf_lines = newlines; if (current_funccal != NULL && eval_lavars) { + flags |= FC_CLOSURE; fp->uf_scoped = current_funccal; current_funccal->fc_refcount++; if (ga_grow(¤t_funccal->fc_funcs, 1) == FAIL) @@ -361,7 +363,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) func_do_profile(fp); #endif fp->uf_varargs = TRUE; - fp->uf_flags = 0; + fp->uf_flags = flags; fp->uf_calls = 0; fp->uf_script_ID = current_SID; @@ -1487,6 +1489,8 @@ list_func_head(ufunc_T *fp, int indent) MSG_PUTS(" range"); if (fp->uf_flags & FC_DICT) MSG_PUTS(" dict"); + if (fp->uf_flags & FC_CLOSURE) + MSG_PUTS(" closure"); msg_clr_eos(); if (p_verbose > 0) last_set_msg(fp->uf_script_ID); @@ -1948,7 +1952,7 @@ ex_function(exarg_T *eap) if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) goto errret_2; - /* find extra arguments "range", "dict" and "abort" */ + /* find extra arguments "range", "dict", "abort" and "closure" */ for (;;) { p = skipwhite(p); @@ -1967,6 +1971,11 @@ ex_function(exarg_T *eap) flags |= FC_ABORT; p += 5; } + else if (STRNCMP(p, "closure", 7) == 0) + { + flags |= FC_CLOSURE; + p += 7; + } else break; } @@ -2299,7 +2308,25 @@ ex_function(exarg_T *eap) } fp->uf_args = newargs; fp->uf_lines = newlines; - fp->uf_scoped = NULL; + if ((flags & FC_CLOSURE) != 0) + { + if (current_funccal == NULL) + { + emsg_funcname(N_("E932 Closure function should not be at top level: %s"), + name); + goto erret; + } + fp->uf_scoped = current_funccal; + current_funccal->fc_refcount++; + if (ga_grow(¤t_funccal->fc_funcs, 1) == FAIL) + goto erret; + ((ufunc_T **)current_funccal->fc_funcs.ga_data) + [current_funccal->fc_funcs.ga_len++] = fp; + func_ref(current_funccal->func->uf_name); + } + else + fp->uf_scoped = NULL; + #ifdef FEAT_PROFILE fp->uf_tml_count = NULL; fp->uf_tml_total = NULL; @@ -3535,6 +3562,42 @@ get_current_funccal_dict(hashtab_T *ht) return NULL; } +/* + * Search hashitem in parent scope. + */ + hashitem_T * +find_hi_in_scoped_ht(char_u *name, char_u **varname, hashtab_T **pht) +{ + funccall_T *old_current_funccal = current_funccal; + hashtab_T *ht; + hashitem_T *hi = NULL; + + if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) + return NULL; + + /* Search in parent scope which is possible to reference from lambda */ + current_funccal = current_funccal->func->uf_scoped; + while (current_funccal) + { + ht = find_var_ht(name, varname); + if (ht != NULL && **varname != NUL) + { + hi = hash_find(ht, *varname); + if (!HASHITEM_EMPTY(hi)) + { + *pht = ht; + break; + } + } + if (current_funccal == current_funccal->func->uf_scoped) + break; + current_funccal = current_funccal->func->uf_scoped; + } + current_funccal = old_current_funccal; + + return hi; +} + /* * Search variable in parent scope. */ diff --git a/src/version.c b/src/version.c index 865ac929b6..7e8ca166e1 100644 --- a/src/version.c +++ b/src/version.c @@ -758,6 +758,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2120, /**/ 2119, /**/ From 9532fe7fbe1b14531931e83bd9f8054efdcf7509 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Fri, 29 Jul 2016 22:50:35 +0200 Subject: [PATCH 9/9] patch 7.4.2121 Problem: No easy way to check if lambda and closure are supported. Solution: Add the +lambda feature. --- src/evalfunc.c | 1 + src/testdir/test_lambda.vim | 4 ++++ src/version.c | 7 +++++++ 3 files changed, 12 insertions(+) diff --git a/src/evalfunc.c b/src/evalfunc.c index 53783af740..f665842428 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -5205,6 +5205,7 @@ f_has(typval_T *argvars, typval_T *rettv) #ifdef FEAT_KEYMAP "keymap", #endif + "lambda", /* always with FEAT_EVAL, since 7.4.2120 with closure */ #ifdef FEAT_LANGMAP "langmap", #endif diff --git a/src/testdir/test_lambda.vim b/src/testdir/test_lambda.vim index 9eb34e434e..5118738076 100644 --- a/src/testdir/test_lambda.vim +++ b/src/testdir/test_lambda.vim @@ -1,5 +1,9 @@ " Test for lambda and closure +function! Test_lambda_feature() + call assert_equal(1, has('lambda')) +endfunction + function! Test_lambda_with_filter() let s:x = 2 call assert_equal([2, 3], filter([1, 2, 3], {i, v -> v >= s:x})) diff --git a/src/version.c b/src/version.c index 7e8ca166e1..4af1468a7d 100644 --- a/src/version.c +++ b/src/version.c @@ -304,6 +304,11 @@ static char *(features[]) = #else "-keymap", #endif +#ifdef FEAT_EVAL + "+lambda", +#else + "-lambda", +#endif #ifdef FEAT_LANGMAP "+langmap", #else @@ -758,6 +763,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2121, /**/ 2120, /**/