Mercurial > vim
view src/testdir/test_tagjump.vim @ 33815:08f9e1eac4cf v9.0.2123
patch 9.0.2123: Problem with initializing the length of range() lists
Commit: https://github.com/vim/vim/commit/df63da98d8dc284b1c76cfe1b17fa0acbd6094d8
Author: Christian Brabandt <cb@256bit.org>
Date: Thu Nov 23 20:14:28 2023 +0100
patch 9.0.2123: Problem with initializing the length of range() lists
Problem: Problem with initializing the length of range() lists
Solution: Set length explicitly when it shouldn't contain any items
range() may cause a wrong calculation of list length, which may later
then cause a segfault in list_find(). This is usually not a problem,
because range_list_materialize() calculates the length, when it
materializes the list.
In addition, in list_find() when the length of the range was wrongly
initialized, it may seem to be valid, so the check for list index
out-of-bounds will not be true, because it is called before the list is
actually materialized. And so we may eventually try to access a null
pointer, causing a segfault.
So this patch does 3 things:
- In f_range(), when we know that the list should be empty, explicitly
set the list->lv_len value to zero. This should happen, when
start is larger than end (in case the stride is positive) or
end is larger than start when the stride is negative.
This should fix the underlying issue properly. However,
- as a safety measure, let's check that the requested index is not
out of range one more time, after the list has been materialized
and return NULL in case it suddenly is.
- add a few more tests to verify the behaviour.
fixes: #13557
closes: #13563
Co-authored-by: Tim Pope <tpope@github.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Thu, 23 Nov 2023 20:30:07 +0100 |
parents | dbec60b8c253 |
children | d8c69a773456 |
line wrap: on
line source
" Tests for tagjump (tags and special searches) source check.vim source screendump.vim " SEGV occurs in older versions. (At least 7.4.1748 or older) func Test_ptag_with_notagstack() CheckFeature quickfix set notagstack call assert_fails('ptag does_not_exist_tag_name', 'E433:') set tagstack&vim endfunc func Test_ptjump() CheckFeature quickfix set tags=Xpttags call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "one\tXptfile\t1", \ "three\tXptfile\t3", \ "two\tXptfile\t2"], \ 'Xpttags', 'D') call writefile(['one', 'two', 'three'], 'Xptfile', 'D') %bw! ptjump two call assert_equal(2, winnr()) wincmd p call assert_equal(1, &previewwindow) call assert_equal('Xptfile', expand("%:p:t")) call assert_equal(2, line('.')) call assert_equal(2, winnr('$')) call assert_equal(1, winnr()) close call setline(1, ['one', 'two', 'three']) exe "normal 3G\<C-W>g}" call assert_equal(2, winnr()) wincmd p call assert_equal(1, &previewwindow) call assert_equal('Xptfile', expand("%:p:t")) call assert_equal(3, line('.')) call assert_equal(2, winnr('$')) call assert_equal(1, winnr()) close exe "normal 3G5\<C-W>\<C-G>}" wincmd p call assert_equal(5, winheight(0)) close set tags& endfunc func Test_cancel_ptjump() CheckFeature quickfix set tags=Xtags call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "word\tfile1\tcmd1", \ "word\tfile2\tcmd2"], \ 'Xtags', 'D') only! call feedkeys(":ptjump word\<CR>\<CR>", "xt") help call assert_equal(2, winnr('$')) set tags& quit endfunc func Test_static_tagjump() set tags=Xtjtags call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "one\tXtjfile1\t/^one/;\"\tf\tfile:\tsignature:(void)", \ "word\tXtjfile2\tcmd2"], \ 'Xtjtags', 'D') new Xtjfile1 call setline(1, ['empty', 'one()', 'empty']) write tag one call assert_equal(2, line('.')) bwipe! set tags& call delete('Xtjfile1') endfunc func Test_duplicate_tagjump() set tags=Xdttags call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "thesame\tXdtfile1\t1;\"\td\tfile:", \ "thesame\tXdtfile1\t2;\"\td\tfile:", \ "thesame\tXdtfile1\t3;\"\td\tfile:", \ ], \ 'Xdttags', 'D') new Xdtfile1 call setline(1, ['thesame one', 'thesame two', 'thesame three']) write tag thesame call assert_equal(1, line('.')) tnext call assert_equal(2, line('.')) tnext call assert_equal(3, line('.')) bwipe! set tags& call delete('Xdtfile1') endfunc func Test_tagjump_switchbuf() CheckFeature quickfix set tags=Xswtags call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "second\tXsbfile1\t2", \ "third\tXsbfile1\t3",], \ 'Xswtags', 'D') call writefile(['first', 'second', 'third'], 'Xsbfile1', 'D') enew | only set switchbuf= stag second call assert_equal(2, winnr('$')) call assert_equal(2, line('.')) stag third call assert_equal(3, winnr('$')) call assert_equal(3, line('.')) enew | only set switchbuf=useopen stag second call assert_equal(2, winnr('$')) call assert_equal(2, line('.')) stag third call assert_equal(2, winnr('$')) call assert_equal(3, line('.')) enew | only set switchbuf=usetab tab stag second call assert_equal(2, tabpagenr('$')) call assert_equal(2, line('.')) 1tabnext | stag third call assert_equal(2, tabpagenr('$')) call assert_equal(3, line('.')) tabclose! enew | only set tags& set switchbuf&vim endfunc " Tests for [ CTRL-I and CTRL-W CTRL-I commands function Test_keyword_jump() call writefile(["#include Xinclude", "", \ "", \ "/* test text test tex start here", \ " some text", \ " test text", \ " start OK if found this line", \ " start found wrong line", \ "test text"], 'Xtestfile', 'D') call writefile(["/* test text test tex start here", \ " some text", \ " test text", \ " start OK if found this line", \ " start found wrong line", \ "test text"], 'Xinclude', 'D') new Xtestfile call cursor(1,1) call search("start") exe "normal! 5[\<C-I>" call assert_equal(" start OK if found this line", getline('.')) call cursor(1,1) call search("start") exe "normal! 5\<C-W>\<C-I>" call assert_equal(" start OK if found this line", getline('.')) " invalid tag search pattern call assert_fails('tag /\%(/', 'E426:') enew! | only endfunction " Test for jumping to a tag with 'hidden' set, with symbolic link in path of " tag. This only works for Unix, because of the symbolic link. func Test_tag_symbolic() CheckUnix set hidden call delete("Xtest.dir", "rf") call system("ln -s . Xtest.dir") " Create a tags file with the current directory name inserted. call writefile([ \ "SECTION_OFF " . getcwd() . "/Xtest.dir/Xtest.c /^#define SECTION_OFF 3$/", \ '', \ ], 'Xsymtags', 'D') call writefile(['#define SECTION_OFF 3', \ '#define NUM_SECTIONS 3'], 'Xtest.c', 'D') " Try jumping to a tag, but with a path that contains a symbolic link. When " wrong, this will give the ATTENTION message. The next space will then be " eaten by hit-return, instead of moving the cursor to 'd'. set tags=Xsymtags enew! call append(0, 'SECTION_OFF') call cursor(1,1) exe "normal \<C-]> " call assert_equal('Xtest.c', expand('%:t')) call assert_equal(2, col('.')) set hidden& set tags& enew! call delete("Xtest.dir", "rf") %bwipe! endfunc " Tests for tag search with !_TAG_FILE_ENCODING. func Test_tag_file_encoding() if has('vms') throw 'Skipped: does not work on VMS' endif if !has('iconv') || iconv("\x82\x60", "cp932", "utf-8") != "\uff21" throw 'Skipped: iconv does not work' endif let save_enc = &encoding set encoding=utf8 let content = ['text for tags1', 'abcdefghijklmnopqrs'] call writefile(content, 'Xtags1.txt', 'D') let content = ['text for tags2', 'ABC'] call writefile(content, 'Xtags2.txt', 'D') let content = ['text for tags3', 'ABC'] call writefile(content, 'Xtags3.txt', 'D') let content = ['!_TAG_FILE_ENCODING utf-8 //', 'abcdefghijklmnopqrs Xtags1.txt /abcdefghijklmnopqrs'] call writefile(content, 'Xtags1', 'D') " case1: new set tags=Xtags1 tag abcdefghijklmnopqrs call assert_equal('Xtags1.txt', expand('%:t')) call assert_equal('abcdefghijklmnopqrs', getline('.')) close " case2: new let content = ['!_TAG_FILE_ENCODING cp932 //', \ "\x82`\x82a\x82b Xtags2.txt /\x82`\x82a\x82b"] call writefile(content, 'Xenctags') set tags=Xenctags tag /.BC call assert_equal('Xtags2.txt', expand('%:t')) call assert_equal('ABC', getline('.')) call delete('Xenctags') close " case3: new let contents = [ \ "!_TAG_FILE_SORTED 1 //", \ "!_TAG_FILE_ENCODING cp932 //"] for i in range(1, 100) call add(contents, 'abc' .. i \ .. " Xtags3.txt /\x82`\x82a\x82b") endfor call writefile(contents, 'Xenctags', 'D') set tags=Xenctags tag abc50 call assert_equal('Xtags3.txt', expand('%:t')) call assert_equal('ABC', getline('.')) close set tags& let &encoding = save_enc endfunc " Test for emacs-style tags file (TAGS) func Test_tagjump_etags() CheckFeature emacs_tags call writefile([ \ "void foo() {}", \ "int main(int argc, char **argv)", \ "{", \ "\tfoo();", \ "\treturn 0;", \ "}", \ ], 'Xmain.c', 'D') call writefile([ \ "\x0c", \ "Xmain.c,64", \ "void foo() {}\x7ffoo\x011,0", \ "int main(int argc, char **argv)\x7fmain\x012,14", \ ], 'Xtetags', 'D') set tags=Xtetags ta foo call assert_equal('void foo() {}', getline('.')) " Test for including another tags file call writefile([ \ "\x0c", \ "Xmain.c,64", \ "void foo() {}\x7ffoo\x011,0", \ "\x0c", \ "Xnonexisting,include", \ "\x0c", \ "Xtags2,include" \ ], 'Xtetags') call writefile([ \ "\x0c", \ "Xmain.c,64", \ "int main(int argc, char **argv)\x7fmain\x012,14", \ ], 'Xtags2', 'D') tag main call assert_equal(2, line('.')) call assert_fails('tag bar', 'E426:') " corrupted tag line call writefile([ \ "\x0c", \ "Xmain.c,8", \ "int main" \ ], 'Xtetags', 'b') call assert_fails('tag foo', 'E426:') " invalid line number call writefile([ \ "\x0c", \ "Xmain.c,64", \ "void foo() {}\x7ffoo\x0abc,0", \ ], 'Xtetags') call assert_fails('tag foo', 'E426:') " invalid tag name call writefile([ \ "\x0c", \ "Xmain.c,64", \ ";;;;\x7f1,0", \ ], 'Xtetags') call assert_fails('tag foo', 'E431:') " end of file after a CTRL-L line call writefile([ \ "\x0c", \ "Xmain.c,64", \ "void foo() {}\x7ffoo\x011,0", \ "\x0c", \ ], 'Xtetags') call assert_fails('tag main', 'E426:') " error in an included tags file call writefile([ \ "\x0c", \ "Xtags2,include" \ ], 'Xtetags') call writefile([ \ "\x0c", \ "Xmain.c,64", \ "void foo() {}", \ ], 'Xtags2') call assert_fails('tag foo', 'E431:') set tags& bwipe! endfunc " Test for getting and modifying the tag stack func Test_getsettagstack() call writefile(['line1', 'line2', 'line3'], 'Xstsfile1', 'D') call writefile(['line1', 'line2', 'line3'], 'Xstsfile2', 'D') call writefile(['line1', 'line2', 'line3'], 'Xstsfile3', 'D') enew | only call settagstack(1, {'items' : []}) call assert_equal(0, gettagstack(1).length) call assert_equal([], 1->gettagstack().items) " Error cases call assert_equal({}, gettagstack(100)) call assert_equal(-1, settagstack(100, {'items' : []})) call assert_fails('call settagstack(1, [1, 10])', 'E1206:') call assert_fails("call settagstack(1, {'items' : 10})", 'E714:') call assert_fails("call settagstack(1, {'items' : []}, 10)", 'E1174:') call assert_fails("call settagstack(1, {'items' : []}, 'b')", 'E962:') call assert_equal(-1, settagstack(0, test_null_dict())) set tags=Xtsttags call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "one\tXstsfile1\t1", \ "three\tXstsfile3\t3", \ "two\tXstsfile2\t2"], \ 'Xtsttags', 'D') let stk = [] call add(stk, {'bufnr' : bufnr('%'), 'tagname' : 'one', \ 'from' : [bufnr('%'), line('.'), col('.'), 0], 'matchnr' : 1}) tag one call add(stk, {'bufnr' : bufnr('%'), 'tagname' : 'two', \ 'from' : [bufnr('%'), line('.'), col('.'), 0], 'matchnr' : 1}) tag two call add(stk, {'bufnr' : bufnr('%'), 'tagname' : 'three', \ 'from' : [bufnr('%'), line('.'), col('.'), 0], 'matchnr' : 1}) tag three call assert_equal(3, gettagstack(1).length) call assert_equal(stk, gettagstack(1).items) " Check for default - current window call assert_equal(3, gettagstack().length) call assert_equal(stk, gettagstack().items) " Try to set current index to invalid values call settagstack(1, {'curidx' : -1}) call assert_equal(1, gettagstack().curidx) eval {'curidx' : 50}->settagstack(1) call assert_equal(4, gettagstack().curidx) " Try pushing invalid items onto the stack call settagstack(1, {'items' : []}) call settagstack(1, {'items' : ["plate"]}, 'a') call assert_equal(0, gettagstack().length) call assert_equal([], gettagstack().items) call settagstack(1, {'items' : [{"tagname" : "abc"}]}, 'a') call assert_equal(0, gettagstack().length) call assert_equal([], gettagstack().items) call settagstack(1, {'items' : [{"from" : 100}]}, 'a') call assert_equal(0, gettagstack().length) call assert_equal([], gettagstack().items) call settagstack(1, {'items' : [{"from" : [2, 1, 0, 0]}]}, 'a') call assert_equal(0, gettagstack().length) call assert_equal([], gettagstack().items) " Push one item at a time to the stack call settagstack(1, {'items' : []}) call settagstack(1, {'items' : [stk[0]]}, 'a') call settagstack(1, {'items' : [stk[1]]}, 'a') call settagstack(1, {'items' : [stk[2]]}, 'a') call settagstack(1, {'curidx' : 4}) call assert_equal({'length' : 3, 'curidx' : 4, 'items' : stk}, \ gettagstack(1)) " Try pushing items onto a full stack for i in range(7) call settagstack(1, {'items' : stk}, 'a') endfor call assert_equal(20, gettagstack().length) call settagstack(1, \ {'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 'a') call assert_equal('abc', gettagstack().items[19].tagname) " truncate the tag stack call settagstack(1, \ {'curidx' : 9, \ 'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 't') let t = gettagstack() call assert_equal(9, t.length) call assert_equal(10, t.curidx) " truncate the tag stack without pushing any new items call settagstack(1, {'curidx' : 5}, 't') let t = gettagstack() call assert_equal(4, t.length) call assert_equal(5, t.curidx) " truncate an empty tag stack and push new items call settagstack(1, {'items' : []}) call settagstack(1, \ {'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 't') let t = gettagstack() call assert_equal(1, t.length) call assert_equal(2, t.curidx) " Tag with multiple matches call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "two\tXstsfile1\t1", \ "two\tXstsfile2\t3", \ "two\tXstsfile3\t2"], \ 'Xtsttags') call settagstack(1, {'items' : []}) tag two tnext tnext call assert_equal(1, gettagstack().length) call assert_equal(3, gettagstack().items[0].matchnr) " Memory allocation failures call test_alloc_fail(GetAllocId('tagstack_items'), 0, 0) call assert_fails('call gettagstack()', 'E342:') call test_alloc_fail(GetAllocId('tagstack_from'), 0, 0) call assert_fails('call gettagstack()', 'E342:') call test_alloc_fail(GetAllocId('tagstack_details'), 0, 0) call assert_fails('call gettagstack()', 'E342:') call settagstack(1, {'items' : []}) set tags& endfunc func Test_tag_with_count() call writefile([ \ 'test Xtest.h /^void test();$/;" p typeref:typename:void signature:()', \ ], 'Xtags', 'D') call writefile([ \ 'main Xtest.c /^int main()$/;" f typeref:typename:int signature:()', \ 'test Xtest.c /^void test()$/;" f typeref:typename:void signature:()', \ ], 'Ytags', 'D') cal writefile([ \ 'int main()', \ 'void test()', \ ], 'Xtest.c', 'D') cal writefile([ \ 'void test();', \ ], 'Xtest.h', 'D') set tags=Xtags,Ytags new Xtest.c let tl = taglist('test', 'Xtest.c') call assert_equal(tl[0].filename, 'Xtest.c') call assert_equal(tl[1].filename, 'Xtest.h') tag test call assert_equal(bufname('%'), 'Xtest.c') 1tag test call assert_equal(bufname('%'), 'Xtest.c') 2tag test call assert_equal(bufname('%'), 'Xtest.h') set tags& bwipe Xtest.h bwipe Xtest.c endfunc func Test_tagnr_recall() call writefile([ \ 'test Xtest.h /^void test();$/;" p', \ 'main Xtest.c /^int main()$/;" f', \ 'test Xtest.c /^void test()$/;" f', \ ], 'Xtags', 'D') cal writefile([ \ 'int main()', \ 'void test()', \ ], 'Xtest.c', 'D') cal writefile([ \ 'void test();', \ ], 'Xtest.h', 'D') set tags=Xtags new Xtest.c let tl = taglist('test', 'Xtest.c') call assert_equal(tl[0].filename, 'Xtest.c') call assert_equal(tl[1].filename, 'Xtest.h') 2tag test call assert_equal(bufname('%'), 'Xtest.h') pop call assert_equal(bufname('%'), 'Xtest.c') tag call assert_equal(bufname('%'), 'Xtest.h') set tags& bwipe Xtest.h bwipe Xtest.c endfunc func Test_tag_line_toolong() call writefile([ \ '1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 django/contrib/admin/templates/admin/edit_inline/stacked.html 16;" j line:16 language:HTML' \ ], 'Xtags', 'D') set tags=Xtags let old_vbs = &verbose set verbose=5 " ":tjump" should give "tag not found" not "Format error in tags file" call assert_fails('tj /foo', 'E426:') try tj /foo catch /^Vim\%((\a\+)\)\=:E431/ call assert_report(v:exception) catch /.*/ endtry call assert_equal('Searching tags file Xtags', split(execute('messages'), '\n')[-1]) call writefile([ \ '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567 django/contrib/admin/templates/admin/edit_inline/stacked.html 16;" j line:16 language:HTML' \ ], 'Xtags') call assert_fails('tj /foo', 'E426:') try tj /foo catch /^Vim\%((\a\+)\)\=:E431/ call assert_report(v:exception) catch /.*/ endtry call assert_equal('Searching tags file Xtags', split(execute('messages'), '\n')[-1]) " binary search works in file with long line call writefile([ \ 'asdfasfd nowhere 16', \ 'foobar Xsomewhere 3; " 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567', \ 'zasdfasfd nowhere 16', \ ], 'Xtags') call writefile([ \ 'one', \ 'two', \ 'trhee', \ 'four', \ ], 'Xsomewhere', 'D') tag foobar call assert_equal('Xsomewhere', expand('%')) call assert_equal(3, getcurpos()[1]) " expansion on command line works with long lines when &wildoptions contains " 'tagfile' set wildoptions=tagfile call writefile([ \ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa file /^pattern$/;" f' \ ], 'Xtags') call feedkeys(":tag \<Tab>", 'tx') " Should not crash call assert_true(v:true) set tags& let &verbose = old_vbs endfunc " Check that using :tselect does not run into the hit-enter prompt. " Requires a terminal to trigger that prompt. func Test_tselect() CheckScreendump call writefile([ \ 'main Xtest.h /^void test();$/;" f', \ 'main Xtest.c /^int main()$/;" f', \ 'main Xtest.x /^void test()$/;" f', \ ], 'Xtags', 'D') cal writefile([ \ 'int main()', \ 'void test()', \ ], 'Xtest.c', 'D') let lines =<< trim [SCRIPT] set tags=Xtags [SCRIPT] call writefile(lines, 'XTest_tselect', 'D') let buf = RunVimInTerminal('-S XTest_tselect', {'rows': 10, 'cols': 50}) call TermWait(buf, 50) call term_sendkeys(buf, ":tselect main\<CR>2\<CR>") call VerifyScreenDump(buf, 'Test_tselect_1', {}) call StopVimInTerminal(buf) endfunc func Test_tagline() call writefile([ \ 'provision Xtest.py /^ def provision(self, **kwargs):$/;" m line:1 language:Python class:Foo', \ 'provision Xtest.py /^ def provision(self, **kwargs):$/;" m line:3 language:Python class:Bar', \], 'Xtags', 'D') call writefile([ \ ' def provision(self, **kwargs):', \ ' pass', \ ' def provision(self, **kwargs):', \ ' pass', \], 'Xtest.py', 'D') set tags=Xtags 1tag provision call assert_equal(line('.'), 1) 2tag provision call assert_equal(line('.'), 3) set tags& endfunc " Test for expanding environment variable in a tag file name func Test_tag_envvar() call writefile(["Func1\t$FOO\t/^Func1/"], 'Xtags', 'D') set tags=Xtags let $FOO='TagTestEnv' let caught_exception = v:false try tag Func1 catch /E429:/ call assert_match('E429:.*"TagTestEnv".*', v:exception) let caught_exception = v:true endtry call assert_true(caught_exception) set tags& unlet $FOO endfunc " Test for :ptag func Test_tag_preview() call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "second\tXtpfile1\t2", \ "third\tXtpfile1\t3",], \ 'Xtags', 'D') set tags=Xtags call writefile(['first', 'second', 'third'], 'Xtpfile1', 'D') enew | only ptag third call assert_equal(2, winnr()) call assert_equal(2, winnr('$')) call assert_equal(1, getwinvar(1, '&previewwindow')) call assert_equal(0, getwinvar(2, '&previewwindow')) wincmd P call assert_equal(3, line('.')) " jump to the tag again wincmd w ptag third wincmd P call assert_equal(3, line('.')) " jump to the newer tag wincmd w ptag wincmd P call assert_equal(3, line('.')) " close the preview window pclose call assert_equal(1, winnr('$')) set tags& endfunc " Tests for guessing the tag location func Test_tag_guess() call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "func1\tXfoo\t/^int func1(int x)/", \ "func2\tXfoo\t/^int func2(int y)/", \ "func3\tXfoo\t/^func3/", \ "func4\tXfoo\t/^func4/"], \ 'Xtags', 'D') set tags=Xtags let code =<< trim [CODE] int FUNC1 (int x) { } int func2 (int y) { } int * func3 () { } [CODE] call writefile(code, 'Xfoo', 'D') let v:statusmsg = '' ta func1 call assert_match('E435:', v:statusmsg) call assert_equal(2, line('.')) let v:statusmsg = '' ta func2 call assert_match('E435:', v:statusmsg) call assert_equal(4, line('.')) let v:statusmsg = '' ta func3 call assert_match('E435:', v:statusmsg) call assert_equal(5, line('.')) call assert_fails('ta func4', 'E434:') set tags& endfunc " Test for an unsorted tags file func Test_tag_sort() let l = [ \ "first\tXfoo\t1", \ "ten\tXfoo\t3", \ "six\tXfoo\t2"] call writefile(l, 'Xtags', 'D') set tags=Xtags let code =<< trim [CODE] int first() {} int six() {} int ten() {} [CODE] call writefile(code, 'Xfoo', 'D') call assert_fails('tag first', 'E432:') " When multiple tag files are not sorted, then message should be displayed " multiple times call writefile(l, 'Xtags2', 'D') set tags=Xtags,Xtags2 call assert_fails('tag first', ['E432:', 'E432:']) set tags& %bwipe endfunc " Test for an unsorted tags file func Test_tag_fold() call writefile([ \ "!_TAG_FILE_ENCODING\tutf-8\t//", \ "!_TAG_FILE_SORTED\t2\t/0=unsorted, 1=sorted, 2=foldcase/", \ "first\tXfoo\t1", \ "second\tXfoo\t2", \ "third\tXfoo\t3"], \ 'Xtags', 'D') set tags=Xtags let code =<< trim [CODE] int first() {} int second() {} int third() {} [CODE] call writefile(code, 'Xfoo', 'D') enew tag second call assert_equal('Xfoo', bufname('')) call assert_equal(2, line('.')) set tags& %bwipe endfunc " Test for the :ltag command func Test_ltag() call writefile([ \ "!_TAG_FILE_ENCODING\tutf-8\t//", \ "first\tXfoo\t1", \ "second\tXfoo\t/^int second() {}$/", \ "third\tXfoo\t3"], \ 'Xtags', 'D') set tags=Xtags let code =<< trim [CODE] int first() {} int second() {} int third() {} [CODE] call writefile(code, 'Xfoo', 'D') enew call setloclist(0, [], 'f') ltag third call assert_equal('Xfoo', bufname('')) call assert_equal(3, line('.')) call assert_equal([{'lnum': 3, 'end_lnum': 0, 'bufnr': bufnr('Xfoo'), \ 'col': 0, 'end_col': 0, 'pattern': '', 'valid': 1, 'vcol': 0, \ 'nr': 0, 'type': '', 'module': '', 'text': 'third'}], getloclist(0)) ltag second call assert_equal(2, line('.')) call assert_equal([{'lnum': 0, 'end_lnum': 0, 'bufnr': bufnr('Xfoo'), \ 'col': 0, 'end_col': 0, 'pattern': '^\Vint second() {}\$', \ 'valid': 1, 'vcol': 0, 'nr': 0, 'type': '', 'module': '', \ 'text': 'second'}], getloclist(0)) set tags& %bwipe endfunc " Test for setting the last search pattern to the tag search pattern " when cpoptions has 't' func Test_tag_last_search_pat() call writefile([ \ "!_TAG_FILE_ENCODING\tutf-8\t//", \ "first\tXfoo\t/^int first() {}/", \ "second\tXfoo\t/^int second() {}/", \ "third\tXfoo\t/^int third() {}/"], \ 'Xtags', 'D') set tags=Xtags let code =<< trim [CODE] int first() {} int second() {} int third() {} [CODE] call writefile(code, 'Xfoo', 'D') enew let save_cpo = &cpo set cpo+=t let @/ = '' tag second call assert_equal('^int second() {}', @/) let &cpo = save_cpo set tags& %bwipe endfunc " Tag stack tests func Test_tag_stack() let l = [] for i in range(10, 31) let l += ["var" .. i .. "\tXfoo\t/^int var" .. i .. ";$/"] endfor call writefile(l, 'Xtags', 'D') set tags=Xtags let l = [] for i in range(10, 31) let l += ["int var" .. i .. ";"] endfor call writefile(l, 'Xfoo', 'D') " Jump to a tag when the tag stack is full. Oldest entry should be removed. enew for i in range(10, 30) exe "tag var" .. i endfor let l = gettagstack() call assert_equal(20, l.length) call assert_equal('var11', l.items[0].tagname) tag var31 let l = gettagstack() call assert_equal('var12', l.items[0].tagname) call assert_equal('var31', l.items[19].tagname) " Use tnext with a single match call assert_fails('tnext', 'E427:') " Jump to newest entry from the top of the stack call assert_fails('tag', 'E556:') " Pop with zero count from the top of the stack call assert_fails('0pop', 'E556:') " Pop from an unsaved buffer enew! call append(1, "sample text") call assert_fails('pop', 'E37:') call assert_equal(21, gettagstack().curidx) enew! " Pop all the entries in the tag stack call assert_fails('30pop', 'E555:') " Pop with a count when already at the bottom of the stack call assert_fails('exe "normal 4\<C-T>"', 'E555:') call assert_equal(1, gettagstack().curidx) " Jump to newest entry from the bottom of the stack with zero count call assert_fails('0tag', 'E555:') " Pop the tag stack when it is empty call settagstack(1, {'items' : []}) call assert_fails('pop', 'E73:') set tags& %bwipe endfunc " Test for browsing multiple matching tags func Test_tag_multimatch() call writefile([ \ "!_TAG_FILE_ENCODING\tutf-8\t//", \ "first\tXfoo\t1", \ "first\tXfoo\t2", \ "first\tXfoo\t3"], \ 'Xtags', 'D') set tags=Xtags let code =<< trim [CODE] int first() {} int first() {} int first() {} [CODE] call writefile(code, 'Xfoo', 'D') call settagstack(1, {'items' : []}) tag first tlast call assert_equal(3, line('.')) call assert_fails('tnext', 'E428:') tfirst call assert_equal(1, line('.')) call assert_fails('tprev', 'E425:') tlast call feedkeys("5\<CR>", 't') tselect first call assert_equal(2, gettagstack().curidx) set ignorecase tag FIRST tnext call assert_equal(2, line('.')) tlast tprev call assert_equal(2, line('.')) tNext call assert_equal(1, line('.')) set ignorecase& set tags& %bwipe endfunc " Test for previewing multiple matching tags func Test_preview_tag_multimatch() call writefile([ \ "!_TAG_FILE_ENCODING\tutf-8\t//", \ "first\tXfoo\t1", \ "first\tXfoo\t2", \ "first\tXfoo\t3"], \ 'Xtags', 'D') set tags=Xtags let code =<< trim [CODE] int first() {} int first() {} int first() {} [CODE] call writefile(code, 'Xfoo', 'D') enew | only ptag first ptlast wincmd P call assert_equal(3, line('.')) wincmd w call assert_fails('ptnext', 'E428:') ptprev wincmd P call assert_equal(2, line('.')) wincmd w ptfirst wincmd P call assert_equal(1, line('.')) wincmd w call assert_fails('ptprev', 'E425:') ptnext wincmd P call assert_equal(2, line('.')) wincmd w ptlast call feedkeys("5\<CR>", 't') ptselect first wincmd P call assert_equal(3, line('.')) pclose set tags& %bwipe endfunc " Test for jumping to multiple matching tags across multiple :tags commands func Test_tnext_multimatch() call writefile([ \ "!_TAG_FILE_ENCODING\tutf-8\t//", \ "first\tXfoo1\t1", \ "first\tXfoo2\t1", \ "first\tXfoo3\t1"], \ 'Xtags', 'D') set tags=Xtags let code =<< trim [CODE] int first() {} [CODE] call writefile(code, 'Xfoo1', 'D') call writefile(code, 'Xfoo2', 'D') call writefile(code, 'Xfoo3', 'D') tag first tag first pop tnext tnext call assert_fails('tnext', 'E428:') set tags& %bwipe endfunc " Test for jumping to multiple matching tags in non-existing files func Test_multimatch_non_existing_files() call writefile([ \ "!_TAG_FILE_ENCODING\tutf-8\t//", \ "first\tXfoo1\t1", \ "first\tXfoo2\t1", \ "first\tXfoo3\t1"], \ 'Xtags', 'D') set tags=Xtags call settagstack(1, {'items' : []}) call assert_fails('tag first', 'E429:') call assert_equal(3, gettagstack().items[0].matchnr) set tags& %bwipe endfunc func Test_tselect_listing() call writefile([ \ "!_TAG_FILE_ENCODING\tutf-8\t//", \ "first\tXfoo\t1" .. ';"' .. "\tv\ttyperef:typename:int\tfile:", \ "first\tXfoo\t2" .. ';"' .. "\tkind:v\ttyperef:typename:char\tfile:"], \ 'Xtags', 'D') set tags=Xtags let code =<< trim [CODE] static int first; static char first; [CODE] call writefile(code, 'Xfoo', 'D') call feedkeys("\<CR>", "t") let l = split(execute("tselect first"), "\n") let expected =<< [DATA] # pri kind tag file 1 FS v first Xfoo typeref:typename:int 1 2 FS v first Xfoo typeref:typename:char 2 Type number and <Enter> (q or empty cancels): [DATA] call assert_equal(expected, l) set tags& %bwipe endfunc " Test for :isearch, :ilist, :ijump and :isplit commands " Test for [i, ]i, [I, ]I, [ CTRL-I, ] CTRL-I and CTRL-W i commands func Test_inc_search() new call setline(1, ['1:foo', '2:foo', 'foo', '3:foo', '4:foo', '===']) call cursor(3, 1) " Test for [i and ]i call assert_equal('1:foo', execute('normal [i')) call assert_equal('2:foo', execute('normal 2[i')) call assert_fails('normal 3[i', 'E387:') call assert_equal('3:foo', execute('normal ]i')) call assert_equal('4:foo', execute('normal 2]i')) call assert_fails('normal 3]i', 'E389:') call assert_fails('normal G]i', 'E349:') call assert_fails('normal [i', 'E349:') call cursor(3, 1) " Test for :isearch call assert_equal('1:foo', execute('isearch foo')) call assert_equal('3:foo', execute('isearch 4 /foo/')) call assert_fails('isearch 3 foo', 'E387:') call assert_equal('3:foo', execute('+1,$isearch foo')) call assert_fails('1,.-1isearch 3 foo', 'E389:') call assert_fails('isearch bar', 'E389:') call assert_fails('isearch /foo/3', 'E488:') " Test for [I and ]I call assert_equal([ \ ' 1: 1 1:foo', \ ' 2: 2 2:foo', \ ' 3: 3 foo', \ ' 4: 4 3:foo', \ ' 5: 5 4:foo'], split(execute('normal [I'), "\n")) call assert_equal([ \ ' 1: 4 3:foo', \ ' 2: 5 4:foo'], split(execute('normal ]I'), "\n")) call assert_fails('normal G]I', 'E349:') call assert_fails('normal [I', 'E349:') call cursor(3, 1) " Test for :ilist call assert_equal([ \ ' 1: 1 1:foo', \ ' 2: 2 2:foo', \ ' 3: 3 foo', \ ' 4: 4 3:foo', \ ' 5: 5 4:foo'], split(execute('ilist foo'), "\n")) call assert_equal([ \ ' 1: 4 3:foo', \ ' 2: 5 4:foo'], split(execute('+1,$ilist /foo/'), "\n")) call assert_fails('ilist bar', 'E389:') " Test for [ CTRL-I and ] CTRL-I exe "normal [\t" call assert_equal([1, 3], [line('.'), col('.')]) exe "normal 2j4[\t" call assert_equal([4, 3], [line('.'), col('.')]) call assert_fails("normal k3[\t", 'E387:') call assert_fails("normal 6[\t", 'E389:') exe "normal ]\t" call assert_equal([4, 3], [line('.'), col('.')]) exe "normal k2]\t" call assert_equal([5, 3], [line('.'), col('.')]) call assert_fails("normal 2k3]\t", 'E389:') call assert_fails("normal G[\t", 'E349:') call assert_fails("normal ]\t", 'E349:') call cursor(3, 1) " Test for :ijump call cursor(3, 1) ijump foo call assert_equal([1, 3], [line('.'), col('.')]) call cursor(3, 1) ijump 4 /foo/ call assert_equal([4, 3], [line('.'), col('.')]) call cursor(3, 1) call assert_fails('ijump 3 foo', 'E387:') +,$ijump 2 foo call assert_equal([5, 3], [line('.'), col('.')]) call assert_fails('ijump bar', 'E389:') " Test for CTRL-W i call cursor(3, 1) wincmd i call assert_equal([1, 3, 3], [line('.'), col('.'), winnr('$')]) close 5wincmd i call assert_equal([5, 3, 3], [line('.'), col('.'), winnr('$')]) close call assert_fails('3wincmd i', 'E387:') call assert_fails('6wincmd i', 'E389:') call assert_fails("normal G\<C-W>i", 'E349:') call cursor(3, 1) " Test for :isplit isplit foo call assert_equal([1, 3, 3], [line('.'), col('.'), winnr('$')]) close isplit 5 /foo/ call assert_equal([5, 3, 3], [line('.'), col('.'), winnr('$')]) close call assert_fails('isplit 3 foo', 'E387:') call assert_fails('isplit 6 foo', 'E389:') call assert_fails('isplit bar', 'E389:') close! endfunc " this was using a line from ml_get() freed by the regexp func Test_isearch_copy_line() new norm o norm 0 0norm o sil! norm bc0 sil! isearch \%') bwipe! endfunc " Test for :dsearch, :dlist, :djump and :dsplit commands " Test for [d, ]d, [D, ]D, [ CTRL-D, ] CTRL-D and CTRL-W d commands func Test_macro_search() new call setline(1, ['#define FOO 1', '#define FOO 2', '#define FOO 3', \ '#define FOO 4', '#define FOO 5']) call cursor(3, 9) " Test for [d and ]d call assert_equal('#define FOO 1', execute('normal [d')) call assert_equal('#define FOO 2', execute('normal 2[d')) call assert_fails('normal 3[d', 'E387:') call assert_equal('#define FOO 4', execute('normal ]d')) call assert_equal('#define FOO 5', execute('normal 2]d')) call assert_fails('normal 3]d', 'E388:') " Test for :dsearch call assert_equal('#define FOO 1', execute('dsearch FOO')) call assert_equal('#define FOO 5', execute('dsearch 5 /FOO/')) call assert_fails('dsearch 3 FOO', 'E387:') call assert_equal('#define FOO 4', execute('+1,$dsearch FOO')) call assert_fails('1,.-1dsearch 3 FOO', 'E388:') call assert_fails('dsearch BAR', 'E388:') " Test for [D and ]D call assert_equal([ \ ' 1: 1 #define FOO 1', \ ' 2: 2 #define FOO 2', \ ' 3: 3 #define FOO 3', \ ' 4: 4 #define FOO 4', \ ' 5: 5 #define FOO 5'], split(execute('normal [D'), "\n")) call assert_equal([ \ ' 1: 4 #define FOO 4', \ ' 2: 5 #define FOO 5'], split(execute('normal ]D'), "\n")) " Test for :dlist call assert_equal([ \ ' 1: 1 #define FOO 1', \ ' 2: 2 #define FOO 2', \ ' 3: 3 #define FOO 3', \ ' 4: 4 #define FOO 4', \ ' 5: 5 #define FOO 5'], split(execute('dlist FOO'), "\n")) call assert_equal([ \ ' 1: 4 #define FOO 4', \ ' 2: 5 #define FOO 5'], split(execute('+1,$dlist /FOO/'), "\n")) call assert_fails('dlist BAR', 'E388:') " Test for [ CTRL-D and ] CTRL-D exe "normal [\<C-D>" call assert_equal([1, 9], [line('.'), col('.')]) exe "normal 2j4[\<C-D>" call assert_equal([4, 9], [line('.'), col('.')]) call assert_fails("normal k3[\<C-D>", 'E387:') call assert_fails("normal 6[\<C-D>", 'E388:') exe "normal ]\<C-D>" call assert_equal([4, 9], [line('.'), col('.')]) exe "normal k2]\<C-D>" call assert_equal([5, 9], [line('.'), col('.')]) call assert_fails("normal 2k3]\<C-D>", 'E388:') " Test for :djump call cursor(3, 9) djump FOO call assert_equal([1, 9], [line('.'), col('.')]) call cursor(3, 9) djump 4 /FOO/ call assert_equal([4, 9], [line('.'), col('.')]) call cursor(3, 9) call assert_fails('djump 3 FOO', 'E387:') +,$djump 2 FOO call assert_equal([5, 9], [line('.'), col('.')]) call assert_fails('djump BAR', 'E388:') " Test for CTRL-W d call cursor(3, 9) wincmd d call assert_equal([1, 9, 3], [line('.'), col('.'), winnr('$')]) close 5wincmd d call assert_equal([5, 9, 3], [line('.'), col('.'), winnr('$')]) close call assert_fails('3wincmd d', 'E387:') call assert_fails('6wincmd d', 'E388:') new call assert_fails("normal \<C-W>d", 'E349:') call assert_fails("normal \<C-W>\<C-D>", 'E349:') close " Test for :dsplit dsplit FOO call assert_equal([1, 9, 3], [line('.'), col('.'), winnr('$')]) close dsplit 5 /FOO/ call assert_equal([5, 9, 3], [line('.'), col('.'), winnr('$')]) close call assert_fails('dsplit 3 FOO', 'E387:') call assert_fails('dsplit 6 FOO', 'E388:') call assert_fails('dsplit BAR', 'E388:') close! endfunc func Test_define_search() " this was accessing freed memory new call setline(1, ['first line', '', '#define something 0']) sil norm o0 sil! norm bwipe! new somefile call setline(1, ['first line', '', '#define something 0']) sil norm 0o0 sil! norm ]d bwipe! endfunc " Test for [*, [/, ]* and ]/ func Test_comment_search() new call setline(1, ['', '/*', ' *', ' *', ' */']) normal! 4gg[/ call assert_equal([2, 1], [line('.'), col('.')]) normal! 3gg[* call assert_equal([2, 1], [line('.'), col('.')]) normal! 3gg]/ call assert_equal([5, 3], [line('.'), col('.')]) normal! 3gg]* call assert_equal([5, 3], [line('.'), col('.')]) %d call setline(1, ['', '/*', ' *', ' *']) call assert_beeps('normal! 3gg]/') %d call setline(1, ['', ' *', ' *', ' */']) call assert_beeps('normal! 4gg[/') %d call setline(1, ' /* comment */') normal! 15|[/ call assert_equal(9, col('.')) normal! 15|]/ call assert_equal(21, col('.')) call setline(1, ' comment */') call assert_beeps('normal! 15|[/') call setline(1, ' /* comment') call assert_beeps('normal! 15|]/') close! endfunc " Test for the 'taglength' option func Test_tag_length() set tags=Xtags call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "tame\tXtlfile1\t1;", \ "tape\tXtlfile2\t1;"], 'Xtags', 'D') call writefile(['tame'], 'Xtlfile1', 'D') call writefile(['tape'], 'Xtlfile2', 'D') " Jumping to the tag 'tape', should instead jump to 'tame' new set taglength=2 tag tape call assert_equal('Xtlfile1', @%) " Tag search should jump to the right tag enew tag /^tape$ call assert_equal('Xtlfile2', @%) set tags& taglength& endfunc " Tests for errors in a tags file func Test_tagfile_errors() set tags=Xtags " missing search pattern or line number for a tag call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "foo\tXfile\t"], 'Xtags', 'bD') call writefile(['foo'], 'Xfile', 'D') enew tag foo call assert_equal('', @%) let caught_431 = v:false try eval taglist('.*') catch /:E431:/ let caught_431 = v:true endtry call assert_equal(v:true, caught_431) " tag name and file name are not separated by a tab call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "foo Xfile 1"], 'Xtags') call assert_fails('tag foo', 'E431:') " file name and search pattern are not separated by a tab call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "foo\tXfile 1;"], 'Xtags') call assert_fails('tag foo', 'E431:') set tags& endfunc " When :stag fails to open the file, should close the new window func Test_stag_close_window_on_error() new | only set tags=Xtags call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "foo\tXfile\t1"], 'Xtags', 'D') call writefile(['foo'], 'Xfile', 'D') call writefile([], '.Xfile.swp', 'D') " Remove the catch-all that runtest.vim adds au! SwapExists augroup StagTest au! autocmd SwapExists Xfile let v:swapchoice='q' augroup END stag foo call assert_equal(1, winnr('$')) call assert_equal('', @%) augroup StagTest au! augroup END set tags& endfunc " Test for 'tagbsearch' (binary search) func Test_tagbsearch() " If a tags file header says the tags are sorted, but the tags are actually " unsorted, then binary search should fail and linear search should work. call writefile([ \ "!_TAG_FILE_ENCODING\tutf-8\t//", \ "!_TAG_FILE_SORTED\t1\t/0=unsorted, 1=sorted, 2=foldcase/", \ "third\tXfoo\t3", \ "second\tXfoo\t2", \ "first\tXfoo\t1"], \ 'Xtags', 'D') set tags=Xtags let code =<< trim [CODE] int first() {} int second() {} int third() {} [CODE] call writefile(code, 'Xfoo', 'D') enew set tagbsearch call assert_fails('tag first', 'E426:') call assert_equal('', bufname()) call assert_fails('tag second', 'E426:') call assert_equal('', bufname()) tag third call assert_equal('Xfoo', bufname()) call assert_equal(3, line('.')) %bw! set notagbsearch tag first call assert_equal('Xfoo', bufname()) call assert_equal(1, line('.')) enew tag second call assert_equal('Xfoo', bufname()) call assert_equal(2, line('.')) enew tag third call assert_equal('Xfoo', bufname()) call assert_equal(3, line('.')) %bw! " If a tags file header says the tags are unsorted, but the tags are " actually sorted, then binary search should work. call writefile([ \ "!_TAG_FILE_ENCODING\tutf-8\t//", \ "!_TAG_FILE_SORTED\t0\t/0=unsorted, 1=sorted, 2=foldcase/", \ "first\tXfoo\t1", \ "second\tXfoo\t2", \ "third\tXfoo\t3"], \ 'Xtags') set tagbsearch tag first call assert_equal('Xfoo', bufname()) call assert_equal(1, line('.')) enew tag second call assert_equal('Xfoo', bufname()) call assert_equal(2, line('.')) enew tag third call assert_equal('Xfoo', bufname()) call assert_equal(3, line('.')) %bw! " Binary search fails on EOF call writefile([ \ "!_TAG_FILE_ENCODING\tutf-8\t//", \ "!_TAG_FILE_SORTED\t1\t/0=unsorted, 1=sorted, 2=foldcase/", \ "bar\tXfoo\t1", \ "foo\tXfoo\t2"], \ 'Xtags') call assert_fails('tag bbb', 'E426:') set tags& tagbsearch& endfunc " vim: shiftwidth=2 sts=2 expandtab