From bad5b2f8cf03c6cec690e29b937aac2bb7f2d1e0 Mon Sep 17 00:00:00 2001 From: ZyX Date: Sun, 18 Jun 2017 15:53:53 +0300 Subject: [PATCH 1/7] eval: Error out when there is something after :endfunction Ref #6844 --- src/nvim/eval.c | 10 +++++++++- src/nvim/globals.h | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index baa61a26bc..c424cfcd80 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19842,8 +19842,16 @@ void ex_function(exarg_T *eap) /* Check for "endfunction". */ if (checkforcmd(&p, "endfunction", 4) && nesting-- == 0) { - if (line_arg == NULL) + if (*p == '!') { + p++; + } + p += strspn((const char *)p, " \t\r\n"); + if (*p != NUL && *p != '"') { + emsgf(_(e_trailing2), p); + } + if (line_arg == NULL) { xfree(theline); + } break; } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 840be928c8..6d1bd1de12 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1131,6 +1131,7 @@ EXTERN char_u e_longname[] INIT(= N_("E75: Name too long")); EXTERN char_u e_toomsbra[] INIT(= N_("E76: Too many [")); EXTERN char_u e_toomany[] INIT(= N_("E77: Too many file names")); EXTERN char_u e_trailing[] INIT(= N_("E488: Trailing characters")); +EXTERN char_u e_trailing2[] INIT(= N_("E488: Trailing characters: %s")); EXTERN char_u e_umark[] INIT(= N_("E78: Unknown mark")); EXTERN char_u e_wildexpand[] INIT(= N_("E79: Cannot expand wildcards")); EXTERN char_u e_winheight[] INIT(= N_( From e57d4eef88de620656ecf2ad8de86fe42604c3cb Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 20 Jun 2017 17:14:48 +0300 Subject: [PATCH 2/7] functests: Move function_spec to eval --- test/functional/{viml => eval}/function_spec.lua | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/functional/{viml => eval}/function_spec.lua (100%) diff --git a/test/functional/viml/function_spec.lua b/test/functional/eval/function_spec.lua similarity index 100% rename from test/functional/viml/function_spec.lua rename to test/functional/eval/function_spec.lua From d5839770eee97a6f41c3b165a34a97127c7a6169 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 20 Jun 2017 17:27:06 +0300 Subject: [PATCH 3/7] functests: Refactor redir_exec --- test/functional/helpers.lua | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index b8512c0ad6..5b641b5054 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -492,17 +492,6 @@ local exc_exec = function(cmd) return ret end -local function redir_exec(cmd) - nvim_command(([[ - redir => g:__output - silent! execute "%s" - redir END - ]]):format(cmd:gsub('\n', '\\n'):gsub('[\\"]', '\\%0'))) - local ret = nvim_eval('get(g:, "__output", 0)') - nvim_command('unlet! g:__output') - return ret -end - local function create_callindex(func) local table = {} setmetatable(table, { @@ -562,6 +551,19 @@ local curbufmeths = create_callindex(curbuf) local curwinmeths = create_callindex(curwin) local curtabmeths = create_callindex(curtab) +local function redir_exec(cmd) + meths.set_var('__redir_exec_cmd', cmd) + nvim_command([[ + redir => g:__redir_exec_output + silent! execute g:__redir_exec_cmd + redir END + ]]) + local ret = meths.get_var('__redir_exec_output') + meths.del_var('__redir_exec_output') + meths.del_var('__redir_exec_cmd') + return ret +end + local function get_pathsep() return funcs.fnamemodify('.', ':p'):sub(-1) end From 60c025267265ba4bfc2abd34ea02b13bd5c0e63f Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 20 Jun 2017 17:36:56 +0300 Subject: [PATCH 4/7] eval: Allow running next command after :endfunction This will still error out on `:endfunction | next`, but defining many functions in one `:execute` should be possible. --- src/nvim/eval.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index c424cfcd80..18aa5bf763 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -19765,10 +19765,12 @@ void ex_function(exarg_T *eap) /* When there is a line break use what follows for the function body. * Makes 'exe "func Test()\n...\nendfunc"' work. */ - if (*p == '\n') + const char *const end = (const char *)p + STRLEN(p); + if (*p == '\n') { line_arg = p + 1; - else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) - EMSG(_(e_trailing)); + } else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) { + emsgf(_(e_trailing)); + } /* * Read the body of the function, until ":endfunction" is found. @@ -19845,12 +19847,26 @@ void ex_function(exarg_T *eap) if (*p == '!') { p++; } - p += strspn((const char *)p, " \t\r\n"); - if (*p != NUL && *p != '"') { + const char *const comment_start = strchr((const char *)p, '"'); + const char *const endfunc_end = (comment_start + ? strchr(comment_start, '\n') + : strpbrk((const char *)p, "\n|")); + p = (endfunc_end + ? (char_u *)endfunc_end + : p + STRLEN(p)); + if (*p == '|') { emsgf(_(e_trailing2), p); + if (line_arg == NULL) { + xfree(theline); + } + goto erret; } if (line_arg == NULL) { xfree(theline); + } else { + if ((const char *)p < end) { + eap->nextcmd = p + 1; + } } break; } From 607dc3e0f9119fa4939d9cfa8ee1f7f764c6e530 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 20 Jun 2017 18:17:19 +0300 Subject: [PATCH 5/7] functests: Add tests --- test/functional/viml/function_spec.lua | 176 +++++++++++++++++++++++++ test/helpers.lua | 7 +- 2 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 test/functional/viml/function_spec.lua diff --git a/test/functional/viml/function_spec.lua b/test/functional/viml/function_spec.lua new file mode 100644 index 0000000000..c5458e38ea --- /dev/null +++ b/test/functional/viml/function_spec.lua @@ -0,0 +1,176 @@ +local helpers = require('test.functional.helpers')(after_each) + +local eq = helpers.eq +local clear = helpers.clear +local funcs = helpers.funcs +local dedent = helpers.dedent +local redir_exec = helpers.redir_exec + +before_each(clear) + +local function check_nofunc(fname) + eq(0, funcs.exists('*' .. fname)) +end + +local function check_func(fname, body) + if type(body) == 'number' then + body = ('return %i'):format(body) + end + eq(dedent(([[ + + function %s()%s + endfunction]] + ), 3):format(fname, body and ('\n1 ' .. body) or ''), + redir_exec('function ' .. fname)) +end + +describe(':endfunction', function() + it('accepts bang', function() + eq('', redir_exec([[ + function F() + endfunction! + ]])) + check_func('F') + eq('', redir_exec([[ + function! F() + return 1 + endfunction! + ]])) + check_func('F', 1) + end) + it('accepts comments', function() + eq('', redir_exec([[ + function F1() + endfunction " Comment + ]])) + check_func('F1') + eq('', redir_exec([[ + function F2() + endfunction " }}} + ]])) + check_func('F2') + eq('', redir_exec([[ + function F3() + endfunction " F3 + ]])) + check_func('F3') + eq('', redir_exec([[ + function F4() + endfunction! " F4 + ]])) + check_func('F4') + eq('', redir_exec([[ + function! F4() + return 2 + endfunction! " F4 + ]])) + check_func('F4', 2) + end) + it('accepts function name', function() + eq('', redir_exec([[ + function F0() + endfunction F0 + ]])) + check_func('F0') + eq('', redir_exec([[ + function F1() + endfunction! F1 + ]])) + check_func('F1') + eq('', redir_exec([[ + function! F2() + endfunction! F2 + ]])) + check_func('F2') + eq('', redir_exec([[ + function! F2() + return 3 + endfunction! F2 + ]])) + check_func('F2', 3) + end) + it('accepts weird characters', function() + eq('', redir_exec([[ + function F1() + endfunction: }}} + ]])) + check_func('F1') + -- From accurev + eq('', redir_exec([[ + function F2() + endfunction :}}} + ]])) + check_func('F2') + -- From cream-vimabbrev + eq('', redir_exec([[ + function F3() + endfunction 1}}} + ]])) + check_func('F3') + -- From pyunit + eq('', redir_exec([[ + function F4() + endfunction # }}} + ]])) + check_func('F4') + -- From vim-lldb + eq('', redir_exec([[ + function F5() + endfunction() + ]])) + check_func('F5') + -- From vim-mail + eq('', redir_exec([[ + function F6() + endfunction; + ]])) + check_func('F6') + end) + it('accepts commented bar', function() + eq('', redir_exec([[ + function F1() + endfunction " F1 | echo 42 + ]])) + check_func('F1') + eq('', redir_exec([[ + function! F1() + return 42 + endfunction! " F1 | echo 42 + ]])) + check_func('F1', 42) + end) + it('errors out on an uncommented bar', function() + eq('\nE488: Trailing characters: | echo 42', redir_exec([[ + function F1() + endfunction | echo 42 + ]])) + check_nofunc('F1') + end) + it('allows running multiple commands', function() + eq('\n2', redir_exec([[ + function F1() + echo 2 + endfunction + call F1() + ]])) + check_func('F1', 'echo 2') + eq('\n2\n3\n4', redir_exec([[ + function F2() + echo 2 + endfunction F2 + function F3() + echo 3 + endfunction " F3 + function! F4() + echo 4 + endfunction! + call F2() + call F3() + call F4() + ]])) + check_func('F2', 'echo 2') + check_func('F3', 'echo 3') + check_func('F4', 'echo 4') + end) +end) +-- vim: foldmarker=▶,▲ diff --git a/test/helpers.lua b/test/helpers.lua index 7a0e4b8c3c..260f10002e 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -284,7 +284,7 @@ local function concat_tables(...) return ret end -local function dedent(str) +local function dedent(str, leave_indent) -- find minimum common indent across lines local indent = nil for line in str:gmatch('[^\n]+') do @@ -297,12 +297,13 @@ local function dedent(str) -- no minimum common indent return str end + local left_indent = (' '):rep(leave_indent or 0) -- create a pattern for the indent indent = indent:gsub('%s', '[ \t]') -- strip it from the first line - str = str:gsub('^'..indent, '') + str = str:gsub('^'..indent, left_indent) -- strip it from the remaining lines - str = str:gsub('[\n]'..indent, '\n') + str = str:gsub('[\n]'..indent, '\n' .. left_indent) return str end From ae457ff64a8d6ad322b2d44bde05b28bf14931d9 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 20 Jun 2017 18:31:27 +0300 Subject: [PATCH 6/7] functests: Check that minimal distance between commands works --- test/functional/viml/function_spec.lua | 49 ++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/test/functional/viml/function_spec.lua b/test/functional/viml/function_spec.lua index c5458e38ea..0cf92f7d40 100644 --- a/test/functional/viml/function_spec.lua +++ b/test/functional/viml/function_spec.lua @@ -12,7 +12,7 @@ local function check_nofunc(fname) eq(0, funcs.exists('*' .. fname)) end -local function check_func(fname, body) +local function check_func(fname, body, indent) if type(body) == 'number' then body = ('return %i'):format(body) end @@ -20,7 +20,9 @@ local function check_func(fname, body) function %s()%s endfunction]] - ), 3):format(fname, body and ('\n1 ' .. body) or ''), + ), 3):format( + fname, + body and ('\n1' .. (' '):rep(2 + (indent or 8)) .. body) or ''), redir_exec('function ' .. fname)) end @@ -172,5 +174,48 @@ describe(':endfunction', function() check_func('F3', 'echo 3') check_func('F4', 'echo 4') end) + it('allows running multiple commands with only one character in between', + function() + eq('\n3', redir_exec(dedent([[ + function! F1() + echo 3 + endfunction! + call F1()]]))) + check_func('F1', 'echo 3', 2) + eq('\n4', redir_exec(dedent([[ + function F5() + echo 4 + endfunction + call F5()]]))) + check_func('F5', 'echo 4', 2) + eq('\n5', redir_exec(dedent([[ + function F6() + echo 5 + endfunction " TEST + call F6()]]))) + check_func('F6', 'echo 5', 2) + eq('\n6', redir_exec(dedent([[ + function F7() + echo 6 + endfunction F7 + call F7()]]))) + check_func('F7', 'echo 6', 2) + eq('\n2\n3\n4', redir_exec(dedent([[ + function F2() + echo 2 + endfunction F2 + function F3() + echo 3 + endfunction " F3 + function! F4() + echo 4 + endfunction! + call F2() + call F3() + call F4()]]))) + check_func('F2', 'echo 2', 2) + check_func('F3', 'echo 3', 2) + check_func('F4', 'echo 4', 2) + end) end) -- vim: foldmarker=▶,▲ From 476c28f433ebd7e3c8dfc2d31c59b3fc4c1c02e5 Mon Sep 17 00:00:00 2001 From: ZyX Date: Tue, 20 Jun 2017 20:52:43 +0300 Subject: [PATCH 7/7] functests: Fix screen.lua supplying dedent additional argument --- test/functional/ui/screen.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 7d9cd6c026..5408e1e195 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -198,8 +198,9 @@ function Screen:expect(expected, attr_ids, attr_ignore, condition, any) condition = expected expected = nil else - -- Remove the last line and dedent. - expected = dedent(expected:gsub('\n[ ]+$', '')) + -- Remove the last line and dedent. Note that gsub returns more then one + -- value. + expected = dedent(expected:gsub('\n[ ]+$', ''), 0) for row in expected:gmatch('[^\n]+') do row = row:sub(1, #row - 1) -- Last char must be the screen delimiter. table.insert(expected_rows, row)