diff options
-rw-r--r-- | runtime/doc/eval.txt | 26 | ||||
-rw-r--r-- | src/nvim/eval.lua | 2 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 10 | ||||
-rw-r--r-- | src/nvim/testdir/runtest.vim | 86 | ||||
-rw-r--r-- | src/nvim/testdir/test_functions.vim | 4 | ||||
-rw-r--r-- | src/nvim/testdir/test_mksession.vim | 45 |
6 files changed, 122 insertions, 51 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 448885296d..ca03ee0374 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2409,7 +2409,8 @@ str2list({expr} [, {utf8}]) List convert each character of {expr} to str2nr({expr} [, {base}]) Number convert String to Number strchars({expr} [, {skipcc}]) Number character length of the String {expr} strcharpart({str}, {start} [, {len}]) - String {len} characters of {str} at {start} + String {len} characters of {str} at + character {start} strdisplaywidth({expr} [, {col}]) Number display length of the String {expr} strftime({format} [, {time}]) String time in specified format strgetchar({str}, {index}) Number get char {index} from {str} @@ -2417,8 +2418,9 @@ stridx({haystack}, {needle} [, {start}]) Number index of {needle} in {haystack} string({expr}) String String representation of {expr} value strlen({expr}) Number length of the String {expr} -strpart({str}, {start} [, {len}]) - String {len} characters of {str} at {start} +strpart({str}, {start} [, {len} [, {chars}]]) + String {len} bytes/chars of {str} at + byte {start} strridx({haystack}, {needle} [, {start}]) Number last index of {needle} in {haystack} strtrans({expr}) String translate string to make it printable @@ -2906,7 +2908,8 @@ byte2line({byte}) *byte2line()* byteidx({expr}, {nr}) *byteidx()* Return byte index of the {nr}'th character in the string - {expr}. Use zero for the first character, it returns zero. + {expr}. Use zero for the first character, it then returns + zero. This function is only useful when there are multibyte characters, otherwise the returned value is equal to {nr}. Composing characters are not counted separately, their byte @@ -8438,14 +8441,19 @@ strlen({expr}) The result is a Number, which is the length of the String {expr} in bytes. If the argument is a Number it is first converted to a String. For other types an error is given. - If you want to count the number of multi-byte characters use + If you want to count the number of multibyte characters use |strchars()|. Also see |len()|, |strdisplaywidth()| and |strwidth()|. -strpart({src}, {start} [, {len}]) *strpart()* +strpart({src}, {start} [, {len} [, {chars}]]) *strpart()* The result is a String, which is part of {src}, starting from byte {start}, with the byte length {len}. - To count characters instead of bytes use |strcharpart()|. + When {chars} is present and TRUE then {len} is the number of + characters positions (composing characters are not counted + separately, thus "1" means one base character and any + following composing characters). + To count {start} as characters instead of bytes use + |strcharpart()|. When bytes are selected which do not exist, this doesn't result in an error, the bytes are simply omitted. @@ -8457,8 +8465,8 @@ strpart({src}, {start} [, {len}]) *strpart()* strpart("abcdefg", 3) == "defg" < Note: To get the first character, {start} must be 0. For - example, to get three bytes under and after the cursor: > - strpart(getline("."), col(".") - 1, 3) + example, to get the character under the cursor: > + strpart(getline("."), col(".") - 1, 1, v:true) < strridx({haystack}, {needle} [, {start}]) *strridx()* The result is a Number, which gives the byte index in diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index a5544435d2..be16ddd7f6 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -336,7 +336,7 @@ return { stridx={args={2, 3}}, string={args=1}, strlen={args=1}, - strpart={args={2, 3}}, + strpart={args={2, 4}}, strridx={args={2, 3}}, strtrans={args=1}, strwidth={args=1}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 18e514b9a0..8e0e420eb0 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -9946,6 +9946,16 @@ static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) len = slen - n; } + if (argvars[2].v_type != VAR_UNKNOWN && argvars[3].v_type != VAR_UNKNOWN) { + int off; + + // length in characters + for (off = n; off < (int)slen && len > 0; len--) { + off += utfc_ptr2len((char_u *)p + off); + } + len = off - n; + } + rettv->v_type = VAR_STRING; rettv->vval.v_string = (char_u *)xmemdupz(p + n, (size_t)len); } diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 2bf61b0719..4f16aa807c 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -7,6 +7,19 @@ " ../vim -u NONE -S runtest.vim test_channel.vim open_delay " The output can be found in the "messages" file. " +" If the environment variable $TEST_FILTER is set then only test functions +" matching this pattern are executed. E.g. for sh/bash: +" export TEST_FILTER=Test_channel +" For csh: +" setenv TEST_FILTER Test_channel +" +" To ignore failure for tests that are known to fail in a certain environment, +" set $TEST_MAY_FAIL to a comma separated list of function names. E.g. for +" sh/bash: +" export TEST_MAY_FAIL=Test_channel_one,Test_channel_other +" The failure report will then not be included in the test.log file and +" "make test" will not fail. +" " The test script may contain anything, only functions that start with " "Test_" are special. These will be invoked and should contain assert " functions. See test_assert.vim for an example. @@ -65,10 +78,14 @@ set encoding=utf-8 let s:test_script_fname = expand('%') au! SwapExists * call HandleSwapExists() func HandleSwapExists() - " Only ignore finding a swap file for the test script (the user might be + " Ignore finding a swap file for the test script (the user might be " editing it and do ":make test_name") and the output file. + " Report finding another swap file and chose 'q' to avoid getting stuck. if expand('<afile>') == 'messages' || expand('<afile>') =~ s:test_script_fname let v:swapchoice = 'e' + else + call assert_report('Unexpected swap file: ' .. v:swapname) + let v:swapchoice = 'q' endif endfunc @@ -136,13 +153,6 @@ func RunTheTest(test) endtry endif - let message = 'Executed ' . a:test - if has('reltime') - let message ..= ' in ' .. reltimestr(reltime(func_start)) .. ' seconds' - endif - call add(s:messages, message) - let s:done += 1 - if a:test =~ 'Test_nocatch_' " Function handles errors itself. This avoids skipping commands after the " error. @@ -196,13 +206,26 @@ func RunTheTest(test) endwhile exe 'cd ' . save_cwd + + let message = 'Executed ' . a:test + if has('reltime') + let message ..= ' in ' .. reltimestr(reltime(func_start)) .. ' seconds' + endif + call add(s:messages, message) + let s:done += 1 endfunc -func AfterTheTest() +func AfterTheTest(func_name) if len(v:errors) > 0 - let s:fail += 1 - call add(s:errors, 'Found errors in ' . s:test . ':') - call extend(s:errors, v:errors) + if match(s:may_fail_list, '^' .. a:func_name) >= 0 + let s:fail_expected += 1 + call add(s:errors_expected, 'Found errors in ' . s:test . ':') + call extend(s:errors_expected, v:errors) + else + let s:fail += 1 + call add(s:errors, 'Found errors in ' . s:test . ':') + call extend(s:errors, v:errors) + endif let v:errors = [] endif endfunc @@ -218,7 +241,7 @@ endfunc " This function can be called by a test if it wants to abort testing. func FinishTesting() - call AfterTheTest() + call AfterTheTest('') " Don't write viminfo on exit. set viminfo= @@ -226,7 +249,7 @@ func FinishTesting() " Clean up files created by setup.vim call delete('XfakeHOME', 'rf') - if s:fail == 0 + if s:fail == 0 && s:fail_expected == 0 " Success, create the .res file so that make knows it's done. exe 'split ' . fnamemodify(g:testname, ':r') . '.res' write @@ -242,11 +265,18 @@ func FinishTesting() endif if s:done == 0 - let message = 'NO tests executed' + if s:filtered > 0 + let message = "NO tests match $TEST_FILTER: '" .. $TEST_FILTER .. "'" + else + let message = 'NO tests executed' + endif else + if s:filtered > 0 + call add(s:messages, "Filtered " .. s:filtered .. " tests with $TEST_FILTER") + endif let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test') endif - if has('reltime') + if s:done > 0 && has('reltime') let message ..= ' in ' .. reltimestr(reltime(s:start_time)) .. ' seconds' endif echo message @@ -257,6 +287,12 @@ func FinishTesting() call add(s:messages, message) call extend(s:messages, s:errors) endif + if s:fail_expected > 0 + let message = s:fail_expected . ' FAILED (matching $TEST_MAY_FAIL):' + echo message + call add(s:messages, message) + call extend(s:messages, s:errors_expected) + endif " Add SKIPPED messages call extend(s:messages, s:skipped) @@ -276,11 +312,13 @@ endfunc let g:testname = expand('%') let s:done = 0 let s:fail = 0 +let s:fail_expected = 0 let s:errors = [] +let s:errors_expected = [] let s:messages = [] let s:skipped = [] if expand('%') =~ 'test_vimscript.vim' - " this test has intentional s:errors, don't use try/catch. + " this test has intentional errors, don't use try/catch. source % else try @@ -311,11 +349,12 @@ let s:flaky_tests = [ \ 'Test_repeat_three()', \ 'Test_state()', \ 'Test_stop_all_in_callback()', - \ 'Test_term_mouse_double_click_to_create_tab', + \ 'Test_term_mouse_double_click_to_create_tab()', \ 'Test_term_mouse_multiple_clicks_to_visually_select()', \ 'Test_terminal_composing_unicode()', \ 'Test_terminal_redir_file()', \ 'Test_terminal_tmap()', + \ 'Test_termwinscroll()', \ 'Test_with_partial_callback()', \ ] @@ -335,8 +374,17 @@ endif " If the environment variable $TEST_FILTER is set then filter the function " names against it. +let s:filtered = 0 if $TEST_FILTER != '' + let s:filtered = len(s:tests) let s:tests = filter(s:tests, 'v:val =~ $TEST_FILTER') + let s:filtered -= len(s:tests) +endif + +let s:may_fail_list = [] +if $TEST_MAY_FAIL != '' + " Split the list at commas and add () to make it match s:test. + let s:may_fail_list = split($TEST_MAY_FAIL, ',')->map({i, v -> v .. '()'}) endif " Execute the tests in alphabetical order. @@ -388,7 +436,7 @@ for s:test in sort(s:tests) endwhile endif - call AfterTheTest() + call AfterTheTest(s:test) endfor call FinishTesting() diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 51689db9c4..6b45ac61d1 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -334,6 +334,10 @@ func Test_strpart() call assert_equal('lép', strpart('éléphant', 2, 4)) call assert_equal('léphant', strpart('éléphant', 2)) + + call assert_equal('é', strpart('éléphant', 0, 1, 1)) + call assert_equal('ép', strpart('éléphant', 3, 2, v:true)) + call assert_equal('ó', strpart('cómposed', 1, 1, 1)) endfunc func Test_tolower() diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index 1e22f7e9c9..3243edbf55 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -9,6 +9,29 @@ endif source shared.vim source term_util.vim +" Test for storing global and local argument list in a session file +" This one must be done first. +func Test__mksession_arglocal() + enew | only + n a b c + new + arglocal + mksession! Xtest_mks.out + + %bwipe! + %argdelete + argglobal + source Xtest_mks.out + call assert_equal(2, winnr('$')) + call assert_equal(2, arglistid(1)) + call assert_equal(0, arglistid(2)) + + %bwipe! + %argdelete + argglobal + call delete('Xtest_mks.out') +endfunc + func Test_mksession() tabnew let wrap_save = &wrap @@ -359,28 +382,6 @@ func Test_mksession_slash() set sessionoptions& endfunc -" Test for storing global and local argument list in a session file -func Test_mkseesion_arglocal() - enew | only - n a b c - new - arglocal - mksession! Xtest_mks.out - - %bwipe! - %argdelete - argglobal - source Xtest_mks.out - call assert_equal(2, winnr('$')) - call assert_equal(2, arglistid(1)) - call assert_equal(0, arglistid(2)) - - %bwipe! - %argdelete - argglobal - call delete('Xtest_mks.out') -endfunc - " Test for changing directory to the session file directory func Test_mksession_sesdir() call mkdir('Xproj') |