diff options
author | Marc Coquand <marc@mccd.space> | 2024-07-01 21:26:30 -0500 |
---|---|---|
committer | Marc Coquand <marc@mccd.space> | 2024-07-01 21:26:30 -0500 |
commit | 0a20357d4585da91d92252972f3eb7b715ff64ab (patch) | |
tree | c1e228d72b6331e89f72d99a1ba4ba1807d60381 /vim/pack/downloads/opt/lsp/test | |
download | bsd-0a20357d4585da91d92252972f3eb7b715ff64ab.tar.gz bsd-0a20357d4585da91d92252972f3eb7b715ff64ab.tar.bz2 bsd-0a20357d4585da91d92252972f3eb7b715ff64ab.zip |
initial commit
Diffstat (limited to 'vim/pack/downloads/opt/lsp/test')
16 files changed, 3441 insertions, 0 deletions
diff --git a/vim/pack/downloads/opt/lsp/test/clangd_offsetencoding.vim b/vim/pack/downloads/opt/lsp/test/clangd_offsetencoding.vim new file mode 100644 index 0000000..397f459 --- /dev/null +++ b/vim/pack/downloads/opt/lsp/test/clangd_offsetencoding.vim @@ -0,0 +1,478 @@ +vim9script +# Unit tests for language server protocol offset encoding using clangd + +source common.vim + +# Start the C language server. Returns true on success and false on failure. +def g:StartLangServer(): bool + if has('patch-9.0.1629') + return g:StartLangServerWithFile('Xtest.c') + endif + return false +enddef + +if !has('patch-9.0.1629') + # Need patch 9.0.1629 to properly encode/decode the UTF-16 offsets + finish +endif + +var lspOpts = {autoComplete: false} +g:LspOptionsSet(lspOpts) + +var lspServers = [{ + filetype: ['c', 'cpp'], + path: (exepath('clangd-15') ?? exepath('clangd')), + args: ['--background-index', + '--clang-tidy', + $'--offset-encoding={$LSP_OFFSET_ENCODING}'] + }] +call LspAddServer(lspServers) + +# Test for :LspCodeAction with symbols containing multibyte and composing +# characters +def g:Test_LspCodeAction_multibyte() + silent! edit XLspCodeAction_mb.c + sleep 200m + var lines =<< trim END + #include <stdio.h> + void fn(int aVar) + { + printf("aVar = %d\n", aVar); + printf("😊😊😊😊 = %d\n", aVar): + printf("áb́áb́ = %d\n", aVar): + printf("ą́ą́ą́ą́ = %d\n", aVar): + } + END + setline(1, lines) + g:WaitForServerFileLoad(3) + :redraw! + cursor(5, 5) + redraw! + :LspCodeAction 1 + assert_equal(' printf("😊😊😊😊 = %d\n", aVar);', getline(5)) + cursor(6, 5) + redraw! + :LspCodeAction 1 + assert_equal(' printf("áb́áb́ = %d\n", aVar);', getline(6)) + cursor(7, 5) + redraw! + :LspCodeAction 1 + assert_equal(' printf("ą́ą́ą́ą́ = %d\n", aVar);', getline(7)) + + :%bw! +enddef + +# Test for ":LspDiag show" when using multibyte and composing characters +def g:Test_LspDiagShow_multibyte() + :silent! edit XLspDiagShow_mb.c + sleep 200m + var lines =<< trim END + #include <stdio.h> + void fn(int aVar) + { + printf("aVar = %d\n", aVar); + printf("😊😊😊😊 = %d\n". aVar); + printf("áb́áb́ = %d\n". aVar); + printf("ą́ą́ą́ą́ = %d\n". aVar); + } + END + setline(1, lines) + g:WaitForServerFileLoad(3) + :redraw! + :LspDiag show + var qfl: list<dict<any>> = getloclist(0) + assert_equal([5, 37], [qfl[0].lnum, qfl[0].col]) + assert_equal([6, 33], [qfl[1].lnum, qfl[1].col]) + assert_equal([7, 41], [qfl[2].lnum, qfl[2].col]) + :lclose + :%bw! +enddef + +# Test for :LspFormat when using multibyte and composing characters +def g:Test_LspFormat_multibyte() + :silent! edit XLspFormat_mb.c + sleep 200m + var lines =<< trim END + void fn(int aVar) + { + int 😊😊😊😊 = aVar + 1; + int áb́áb́ = aVar + 1; + int ą́ą́ą́ą́ = aVar + 1; + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + :redraw! + :LspFormat + var expected =<< trim END + void fn(int aVar) { + int 😊😊😊😊 = aVar + 1; + int áb́áb́ = aVar + 1; + int ą́ą́ą́ą́ = aVar + 1; + } + END + assert_equal(expected, getline(1, '$')) + :%bw! +enddef + +# Test for :LspGotoDefinition when using multibyte and composing characters +def g:Test_LspGotoDefinition_multibyte() + :silent! edit XLspGotoDefinition_mb.c + sleep 200m + var lines: list<string> =<< trim END + #include <stdio.h> + void fn(int aVar) + { + printf("aVar = %d\n", aVar); + printf("😊😊😊😊 = %d\n", aVar); + printf("áb́áb́ = %d\n", aVar); + printf("ą́ą́ą́ą́ = %d\n", aVar); + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + redraw! + + for [lnum, colnr] in [[4, 27], [5, 39], [6, 35], [7, 43]] + cursor(lnum, colnr) + :LspGotoDefinition + assert_equal([2, 13], [line('.'), col('.')]) + endfor + + :%bw! +enddef + +# Test for :LspGotoDefinition when using multibyte and composing characters +def g:Test_LspGotoDefinition_after_multibyte() + :silent! edit XLspGotoDef_after_mb.c + sleep 200m + var lines =<< trim END + void fn(int aVar) + { + /* αβγδ, 😊😊😊😊, áb́áb́, ą́ą́ą́ą́ */ int αβγδ, bVar; + /* αβγδ, 😊😊😊😊, áb́áb́, ą́ą́ą́ą́ */ int 😊😊😊😊, cVar; + /* αβγδ, 😊😊😊😊, áb́áb́, ą́ą́ą́ą́ */ int áb́áb́, dVar; + /* αβγδ, 😊😊😊😊, áb́áb́, ą́ą́ą́ą́ */ int ą́ą́ą́ą́, eVar; + bVar = 1; + cVar = 2; + dVar = 3; + eVar = 4; + aVar = αβγδ + 😊😊😊😊 + áb́áb́ + ą́ą́ą́ą́ + bVar; + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + :redraw! + cursor(7, 5) + :LspGotoDefinition + assert_equal([3, 88], [line('.'), col('.')]) + cursor(8, 5) + :LspGotoDefinition + assert_equal([4, 96], [line('.'), col('.')]) + cursor(9, 5) + :LspGotoDefinition + assert_equal([5, 92], [line('.'), col('.')]) + cursor(10, 5) + :LspGotoDefinition + assert_equal([6, 100], [line('.'), col('.')]) + cursor(11, 12) + :LspGotoDefinition + assert_equal([3, 78], [line('.'), col('.')]) + cursor(11, 23) + :LspGotoDefinition + assert_equal([4, 78], [line('.'), col('.')]) + cursor(11, 42) + :LspGotoDefinition + assert_equal([5, 78], [line('.'), col('.')]) + cursor(11, 57) + :LspGotoDefinition + assert_equal([6, 78], [line('.'), col('.')]) + + :%bw! +enddef + +# Test for doing omni completion for symbols with multibyte and composing +# characters +def g:Test_OmniComplete_multibyte() + :silent! edit XOmniComplete_mb.c + sleep 200m + var lines: list<string> =<< trim END + void Func1(void) + { + int 😊😊😊😊, aVar; + int áb́áb́, bVar; + int ą́ą́ą́ą́, cVar; + + + + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + redraw! + + cursor(6, 4) + feedkeys("aaV\<C-X>\<C-O> = 😊😊\<C-X>\<C-O>;", 'xt') + assert_equal(' aVar = 😊😊😊😊;', getline('.')) + cursor(7, 4) + feedkeys("abV\<C-X>\<C-O> = áb́\<C-X>\<C-O>;", 'xt') + assert_equal(' bVar = áb́áb́;', getline('.')) + cursor(8, 4) + feedkeys("acV\<C-X>\<C-O> = ą́ą́\<C-X>\<C-O>;", 'xt') + assert_equal(' cVar = ą́ą́ą́ą́;', getline('.')) + feedkeys("oáb́\<C-X>\<C-O> = ą́ą́\<C-X>\<C-O>;", 'xt') + assert_equal(' áb́áb́ = ą́ą́ą́ą́;', getline('.')) + feedkeys("oą́ą́\<C-X>\<C-O> = áb́\<C-X>\<C-O>;", 'xt') + assert_equal(' ą́ą́ą́ą́ = áb́áb́;', getline('.')) + :%bw! +enddef + +# Test for :LspOutline with multibyte and composing characters +def g:Test_Outline_multibyte() + silent! edit XLspOutline_mb.c + sleep 200m + var lines: list<string> =<< trim END + typedef void 😊😊😊😊; + typedef void áb́áb́; + typedef void ą́ą́ą́ą́; + + 😊😊😊😊 Func1() + { + } + + áb́áb́ Func2() + { + } + + ą́ą́ą́ą́ Func3() + { + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + redraw! + + cursor(1, 1) + :LspOutline + assert_equal(2, winnr('$')) + + :wincmd w + cursor(5, 1) + feedkeys("\<CR>", 'xt') + assert_equal([2, 5, 18], [winnr(), line('.'), col('.')]) + + :wincmd w + cursor(6, 1) + feedkeys("\<CR>", 'xt') + assert_equal([2, 9, 14], [winnr(), line('.'), col('.')]) + + :wincmd w + cursor(7, 1) + feedkeys("\<CR>", 'xt') + assert_equal([2, 13, 22], [winnr(), line('.'), col('.')]) + + :wincmd w + cursor(10, 1) + feedkeys("\<CR>", 'xt') + assert_equal([2, 1, 14], [winnr(), line('.'), col('.')]) + + :wincmd w + cursor(11, 1) + feedkeys("\<CR>", 'xt') + assert_equal([2, 2, 14], [winnr(), line('.'), col('.')]) + + :wincmd w + cursor(12, 1) + feedkeys("\<CR>", 'xt') + assert_equal([2, 3, 14], [winnr(), line('.'), col('.')]) + + :%bw! +enddef + +# Test for :LspRename with multibyte and composing characters +def g:Test_LspRename_multibyte() + silent! edit XLspRename_mb.c + sleep 200m + var lines: list<string> =<< trim END + #include <stdio.h> + void fn(int aVar) + { + printf("aVar = %d\n", aVar); + printf("😊😊😊😊 = %d\n", aVar); + printf("áb́áb́ = %d\n", aVar); + printf("ą́ą́ą́ą́ = %d\n", aVar); + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + redraw! + cursor(2, 12) + :LspRename bVar + redraw! + var expected: list<string> =<< trim END + #include <stdio.h> + void fn(int bVar) + { + printf("aVar = %d\n", bVar); + printf("😊😊😊😊 = %d\n", bVar); + printf("áb́áb́ = %d\n", bVar); + printf("ą́ą́ą́ą́ = %d\n", bVar); + } + END + assert_equal(expected, getline(1, '$')) + :%bw! +enddef + +# Test for :LspShowReferences when using multibyte and composing characters +def g:Test_LspShowReferences_multibyte() + :silent! edit XLspShowReferences_mb.c + sleep 200m + var lines: list<string> =<< trim END + #include <stdio.h> + void fn(int aVar) + { + printf("aVar = %d\n", aVar); + printf("😊😊😊😊 = %d\n", aVar); + printf("áb́áb́ = %d\n", aVar); + printf("ą́ą́ą́ą́ = %d\n", aVar); + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + redraw! + cursor(4, 27) + :LspShowReferences + var qfl: list<dict<any>> = getloclist(0) + assert_equal([2, 13], [qfl[0].lnum, qfl[0].col]) + assert_equal([4, 27], [qfl[1].lnum, qfl[1].col]) + assert_equal([5, 39], [qfl[2].lnum, qfl[2].col]) + assert_equal([6, 35], [qfl[3].lnum, qfl[3].col]) + assert_equal([7, 43], [qfl[4].lnum, qfl[4].col]) + :lclose + + :%bw! +enddef + +# Test for :LspSymbolSearch when using multibyte and composing characters +def g:Test_LspSymbolSearch_multibyte() + silent! edit XLspSymbolSearch_mb.c + sleep 200m + var lines: list<string> =<< trim END + typedef void 😊😊😊😊; + typedef void áb́áb́; + typedef void ą́ą́ą́ą́; + + 😊😊😊😊 Func1() + { + } + + áb́áb́ Func2() + { + } + + ą́ą́ą́ą́ Func3() + { + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + + cursor(1, 1) + feedkeys(":LspSymbolSearch Func1\<CR>", "xt") + assert_equal([5, 18], [line('.'), col('.')]) + cursor(1, 1) + feedkeys(":LspSymbolSearch Func2\<CR>", "xt") + assert_equal([9, 14], [line('.'), col('.')]) + cursor(1, 1) + feedkeys(":LspSymbolSearch Func3\<CR>", "xt") + assert_equal([13, 22], [line('.'), col('.')]) + + :%bw! +enddef + +# Test for setting the 'tagfunc' with multibyte and composing characters in +# symbols +def g:Test_LspTagFunc_multibyte() + var lines =<< trim END + void fn(int aVar) + { + int 😊😊😊😊, bVar; + int áb́áb́, cVar; + int ą́ą́ą́ą́, dVar; + bVar = 10; + cVar = 10; + dVar = 10; + } + END + writefile(lines, 'Xtagfunc_mb.c') + :silent! edit! Xtagfunc_mb.c + g:WaitForServerFileLoad(0) + :setlocal tagfunc=lsp#lsp#TagFunc + cursor(6, 5) + :exe "normal \<C-]>" + assert_equal([3, 27], [line('.'), col('.')]) + cursor(7, 5) + :exe "normal \<C-]>" + assert_equal([4, 23], [line('.'), col('.')]) + cursor(8, 5) + :exe "normal \<C-]>" + assert_equal([5, 31], [line('.'), col('.')]) + :set tagfunc& + + :%bw! + delete('Xtagfunc_mb.c') +enddef + +# Test for the :LspSuperTypeHierarchy and :LspSubTypeHierarchy commands with +# multibyte and composing characters +def g:Test_LspTypeHier_multibyte() + silent! edit XLspTypeHier_mb.cpp + sleep 200m + var lines =<< trim END + /* αβ😊😊ááą́ą́ */ class parent { + }; + + /* αβ😊😊ááą́ą́ */ class child : public parent { + }; + + /* αβ😊😊ááą́ą́ */ class grandchild : public child { + }; + END + setline(1, lines) + g:WaitForServerFileLoad(0) + redraw! + + cursor(1, 42) + :LspSubTypeHierarchy + call feedkeys("\<CR>", 'xt') + assert_equal([1, 36], [line('.'), col('.')]) + cursor(1, 42) + + :LspSubTypeHierarchy + call feedkeys("\<Down>\<CR>", 'xt') + assert_equal([4, 42], [line('.'), col('.')]) + + cursor(1, 42) + :LspSubTypeHierarchy + call feedkeys("\<Down>\<Down>\<CR>", 'xt') + assert_equal([7, 42], [line('.'), col('.')]) + + cursor(7, 42) + :LspSuperTypeHierarchy + call feedkeys("\<CR>", 'xt') + assert_equal([7, 36], [line('.'), col('.')]) + + cursor(7, 42) + :LspSuperTypeHierarchy + call feedkeys("\<Down>\<CR>", 'xt') + assert_equal([4, 36], [line('.'), col('.')]) + + cursor(7, 42) + :LspSuperTypeHierarchy + call feedkeys("\<Down>\<Down>\<CR>", 'xt') + assert_equal([1, 36], [line('.'), col('.')]) + + :%bw! +enddef + +# vim: shiftwidth=2 softtabstop=2 noexpandtab diff --git a/vim/pack/downloads/opt/lsp/test/clangd_tests.vim b/vim/pack/downloads/opt/lsp/test/clangd_tests.vim new file mode 100644 index 0000000..b084e86 --- /dev/null +++ b/vim/pack/downloads/opt/lsp/test/clangd_tests.vim @@ -0,0 +1,1782 @@ +vim9script +# Unit tests for Vim Language Server Protocol (LSP) clangd client + +source common.vim + +var lspOpts = {autoComplete: false} +g:LspOptionsSet(lspOpts) + +g:LSPTest_modifyDiags = false + +var lspServers = [{ + filetype: ['c', 'cpp'], + path: (exepath('clangd-15') ?? exepath('clangd')), + args: ['--background-index', '--clang-tidy'], + initializationOptions: { clangdFileStatus: true }, + customNotificationHandlers: { + 'textDocument/clangd.fileStatus': (lspserver: dict<any>, reply: dict<any>) => { + g:LSPTest_customNotificationHandlerReplied = true + } + }, + processDiagHandler: (diags: list<dict<any>>) => { + if g:LSPTest_modifyDiags != true + return diags + endif + + return diags->map((ix, diag) => { + diag.message = $'this is overridden' + return diag + }) + } + }] +call LspAddServer(lspServers) + +var clangdVerDetail = systemlist($'{shellescape(lspServers[0].path)} --version') +var clangdVerMajor = clangdVerDetail->matchstr('.*version \d\+\..*')->substitute('.* \(\d\+\)\..*', '\1', 'g')->str2nr() +echomsg clangdVerDetail + + +# Test for formatting a file using LspFormat +def g:Test_LspFormat() + :silent! edit XLspFormat.c + sleep 200m + setline(1, [' int i;', ' int j;']) + :redraw! + :LspFormat + assert_equal(['int i;', 'int j;'], getline(1, '$')) + + deletebufline('', 1, '$') + setline(1, ['int f1(int i)', '{', 'int j = 10; return j;', '}']) + :redraw! + :LspFormat + assert_equal(['int f1(int i) {', ' int j = 10;', ' return j;', '}'], + getline(1, '$')) + + deletebufline('', 1, '$') + setline(1, ['', 'int i;']) + :redraw! + :LspFormat + assert_equal(['', 'int i;'], getline(1, '$')) + + deletebufline('', 1, '$') + setline(1, [' int i;']) + :redraw! + :LspFormat + assert_equal(['int i;'], getline(1, '$')) + + deletebufline('', 1, '$') + setline(1, [' int i; ']) + :redraw! + :LspFormat + assert_equal(['int i;'], getline(1, '$')) + + deletebufline('', 1, '$') + setline(1, ['int i;', '', '', '']) + :redraw! + :LspFormat + assert_equal(['int i;'], getline(1, '$')) + + deletebufline('', 1, '$') + setline(1, ['int f1(){int x;int y;x=1;y=2;return x+y;}']) + :redraw! + :LspFormat + var expected: list<string> =<< trim END + int f1() { + int x; + int y; + x = 1; + y = 2; + return x + y; + } + END + assert_equal(expected, getline(1, '$')) + + deletebufline('', 1, '$') + setline(1, ['', '', '', '']) + :redraw! + :LspFormat + assert_equal([''], getline(1, '$')) + + deletebufline('', 1, '$') + var lines: list<string> =<< trim END + int f1() { + int i, j; + for (i = 1; i < 10; i++) { j++; } + for (j = 1; j < 10; j++) { i++; } + } + END + setline(1, lines) + :redraw! + :4LspFormat + expected =<< trim END + int f1() { + int i, j; + for (i = 1; i < 10; i++) { j++; } + for (j = 1; j < 10; j++) { + i++; + } + } + END + assert_equal(expected, getline(1, '$')) + + deletebufline('', 1, '$') + # shrinking multiple lines into a single one works + setline(1, ['int \', 'i \', '= \', '42;']) + :redraw! + :4LspFormat + assert_equal(['int i = 42;'], getline(1, '$')) + bw! + + # empty file + assert_equal('', execute('LspFormat')) + + # file without an LSP server + edit a.raku + assert_equal('Error: Language server for "raku" file type supporting "documentFormatting" feature is not found', + execute('LspFormat')->split("\n")[0]) + + :%bw! +enddef + +# Test for formatting a file using 'formatexpr' +def g:Test_LspFormatExpr() + :silent! edit XLspFormat.c + sleep 200m + setlocal formatexpr=lsp#lsp#FormatExpr() + setline(1, [' int i;', ' int j;']) + :redraw! + normal! ggVGgq + assert_equal(['int i;', 'int j;'], getline(1, '$')) + + # empty line/file + deletebufline('', 1, '$') + setline(1, ['']) + redraw! + normal! ggVGgq + assert_equal([''], getline(1, '$')) + + setlocal formatexpr& + :%bw! +enddef + +# Test for :LspShowReferences - showing all the references to a symbol in a +# file using LSP +def g:Test_LspShowReferences() + :silent! edit XshowRefs.c + sleep 200m + var lines: list<string> =<< trim END + int count; + void redFunc() + { + int count, i; + count = 10; + i = count; + } + void blueFunc() + { + int count, j; + count = 20; + j = count; + } + END + setline(1, lines) + :redraw! + cursor(5, 2) + var bnr: number = bufnr() + :LspShowReferences + sleep 100m + assert_equal('quickfix', getwinvar(winnr('$'), '&buftype')) + var loclist: list<dict<any>> = getloclist(0) + assert_equal(bnr, loclist[0].bufnr) + assert_equal(3, loclist->len()) + assert_equal([4, 6], [loclist[0].lnum, loclist[0].col]) + assert_equal([5, 2], [loclist[1].lnum, loclist[1].col]) + assert_equal([6, 6], [loclist[2].lnum, loclist[2].col]) + :lclose + cursor(1, 5) + :LspShowReferences + assert_equal(1, getloclist(0)->len()) + loclist = getloclist(0) + assert_equal([1, 5], [loclist[0].lnum, loclist[0].col]) + :lclose + + # Test for opening in qf list + g:LspOptionsSet({useQuickfixForLocations: true}) + cursor(5, 2) + :LspShowReferences + sleep 100m + assert_equal('quickfix', getwinvar(winnr('$'), '&buftype')) + :cclose + var qfl: list<dict<any>> = getqflist() + assert_equal(3, qfl->len()) + assert_equal(bufnr(), qfl[0].bufnr) + assert_equal([4, 6], [qfl[0].lnum, qfl[0].col]) + assert_equal([5, 2], [qfl[1].lnum, qfl[1].col]) + assert_equal([6, 6], [qfl[2].lnum, qfl[2].col]) + cursor(1, 5) + :LspShowReferences + assert_equal(1, getqflist()->len()) + qfl = getqflist() + assert_equal([1, 5], [qfl[0].lnum, qfl[0].col]) + :cclose + g:LspOptionsSet({useQuickfixForLocations: false}) + + # Test for maintaining buffer focus + g:LspOptionsSet({keepFocusInReferences: false}) + :LspShowReferences + assert_equal('', getwinvar(0, '&buftype')) + :lclose + g:LspOptionsSet({keepFocusInReferences: true}) + + # Test for LspPeekReferences + + # Opening the preview window with an unsaved buffer displays the "E37: No + # write since last change" error message. To disable this message, mark the + # buffer as not modified. + setlocal nomodified + cursor(10, 6) + :LspPeekReferences + sleep 50m + var ids = popup_list() + assert_equal(2, ids->len()) + var filePopupAttrs = ids[0]->popup_getoptions() + var refPopupAttrs = ids[1]->popup_getoptions() + assert_match('XshowRefs', filePopupAttrs.title) + assert_equal('Symbol References', refPopupAttrs.title) + assert_equal(10, line('.', ids[0])) + assert_equal(1, line('.', ids[1])) + assert_equal(3, line('$', ids[1])) + feedkeys("jj\<CR>", 'xt') + assert_equal(12, line('.')) + assert_equal([], popup_list()) + popup_clear() + + # LspShowReferences should start with the current symbol + cursor(12, 6) + :LspPeekReferences + sleep 50m + ids = popup_list() + assert_equal(2, ids->len()) + assert_equal(12, line('.', ids[0])) + assert_equal(3, line('.', ids[1])) + feedkeys("\<CR>", 'xt') + popup_clear() + + bw! + + # empty file + assert_equal('', execute('LspShowReferences')) + + # file without an LSP server + edit a.raku + assert_equal('Error: Language server for "raku" file type supporting "references" feature is not found', + execute('LspShowReferences')->split("\n")[0]) + + :%bw! +enddef + +# Test for LSP diagnostics +def g:Test_LspDiag() + :silent! edit XLspDiag.c + sleep 200m + var lines: list<string> =<< trim END + void blueFunc() + { + int count, j: + count = 20; + j <= count; + j = 10; + MyFunc(); + } + END + setline(1, lines) + g:WaitForServerFileLoad(1) + var bnr: number = bufnr() + :redraw! + :LspDiag show + var qfl: list<dict<any>> = getloclist(0) + assert_equal('quickfix', getwinvar(winnr('$'), '&buftype')) + assert_equal(bnr, qfl[0].bufnr) + assert_equal(3, qfl->len()) + assert_equal([3, 14, 'E'], [qfl[0].lnum, qfl[0].col, qfl[0].type]) + assert_equal([5, 2, 'W'], [qfl[1].lnum, qfl[1].col, qfl[1].type]) + assert_equal([7, 2, 'W'], [qfl[2].lnum, qfl[2].col, qfl[2].type]) + close + g:LspOptionsSet({showDiagInPopup: false}) + normal gg + var output = execute('LspDiag current')->split("\n") + assert_equal('Warn: No diagnostic messages found for current line', output[0]) + :LspDiag first + assert_equal([3, 14], [line('.'), col('.')]) + output = execute('LspDiag current')->split("\n") + assert_equal("Expected ';' at end of declaration (fix available)", output[0]) + :normal! 0 + :LspDiag here + assert_equal([3, 14], [line('.'), col('.')]) + :LspDiag next + assert_equal([5, 2], [line('.'), col('.')]) + :LspDiag next + assert_equal([7, 2], [line('.'), col('.')]) + output = execute('LspDiag next')->split("\n") + assert_equal('Warn: No more diagnostics found', output[0]) + :LspDiag prev + :LspDiag prev + :LspDiag prev + output = execute('LspDiag prev')->split("\n") + assert_equal('Warn: No more diagnostics found', output[0]) + + # Test for maintaining buffer focus + g:LspOptionsSet({keepFocusInDiags: false}) + :LspDiag show + assert_equal('', getwinvar(0, '&buftype')) + :lclose + g:LspOptionsSet({keepFocusInDiags: true}) + + # :[count]LspDiag next + cursor(3, 1) + :2LspDiag next + assert_equal([5, 2], [line('.'), col('.')]) + :2LspDiag next + assert_equal([7, 2], [line('.'), col('.')]) + output = execute(':2LspDiag next')->split("\n") + assert_equal('Warn: No more diagnostics found', output[0]) + + # :[count]LspDiag prev + cursor(7, 2) + :4LspDiag prev + assert_equal([3, 14], [line('.'), col('.')]) + output = execute(':4LspDiag prev')->split("\n") + assert_equal('Warn: No more diagnostics found', output[0]) + + :%d + setline(1, ['void blueFunc()', '{', '}']) + g:WaitForDiags(0) + output = execute('LspDiag show')->split("\n") + assert_match('Warn: No diagnostic messages found for', output[0]) + g:LspOptionsSet({showDiagInPopup: true}) + + popup_clear() + :%bw! +enddef + +# Test for LSP diagnostics handler +def g:Test_LspProcessDiagHandler() + g:LSPTest_modifyDiags = true + g:LspOptionsSet({showDiagInPopup: false}) + + :silent! edit XLspProcessDiag.c + sleep 200m + var lines: list<string> =<< trim END + void blueFunc() + { + int count, j: + } + END + setline(1, lines) + g:WaitForServerFileLoad(1) + :redraw! + normal gg + + :LspDiag first + assert_equal([3, 14], [line('.'), col('.')]) + + var output = execute('LspDiag current')->split("\n") + assert_equal("this is overridden", output[0]) + + g:LspOptionsSet({showDiagInPopup: true}) + g:LSPTest_modifyDiags = false + :%bw! +enddef + +# Diag location list should be automatically updated when the list of diags +# changes. +def g:Test_DiagLocListAutoUpdate() + :silent! edit XdiagLocListAutoUpdate.c + :sleep 200m + setloclist(0, [], 'f') + var lines: list<string> =<< trim END + int i: + int j; + END + setline(1, lines) + var bnr = bufnr() + g:WaitForServerFileLoad(1) + :redraw! + var d = lsp#diag#GetDiagsForBuf()[0] + assert_equal({start: {line: 0, character: 5}, end: {line: 0, character: 6}}, + d.range) + + :LspDiag show + assert_equal(1, line('$')) + wincmd w + setline(2, 'int j:') + redraw! + g:WaitForDiags(2) + var l = lsp#diag#GetDiagsForBuf() + assert_equal({start: {line: 0, character: 5}, end: {line: 0, character: 6}}, + l[0].range) + assert_equal({start: {line: 1, character: 5}, end: {line: 1, character: 6}}, + l[1].range) + wincmd w + assert_equal(2, line('$')) + wincmd w + deletebufline('', 1, '$') + redraw! + g:WaitForDiags(0) + assert_equal([], lsp#diag#GetDiagsForBuf()) + wincmd w + assert_equal([''], getline(1, '$')) + :lclose + + setloclist(0, [], 'f') + :%bw! +enddef + +# Test that the client have been able to configure the server to speak utf-32 +def g:Test_UnicodeColumnCalc() + :silent! edit XUnicodeColumn.c + sleep 200m + var lines: list<string> =<< trim END + int count; + int fn(int a) + { + int 😊😊😊😊; + 😊😊😊😊 = a; + + int b; + b = a; + return count + 1; + } + END + setline(1, lines) + :redraw! + + cursor(5, 1) # 😊😊😊😊 = a; + search('a') + assert_equal([], + execute('LspGotoDefinition')->split("\n")) + assert_equal([2, 12], [line('.'), col('.')]) + + cursor(8, 1) # b = a; + search('a') + assert_equal([], + execute('LspGotoDefinition')->split("\n")) + assert_equal([2, 12], [line('.'), col('.')]) + + :%bw! +enddef + +# Test for multiple LSP diagnostics on the same line +def g:Test_LspDiag_Multi() + :silent! edit XLspDiagMulti.c + sleep 200m + + var bnr: number = bufnr() + + var lines =<< trim END + int i = "a"; + int j = i; + int y = 0; + END + setline(1, lines) + :redraw! + # TODO: Waiting count doesn't include Warning, Info, and Hint diags + if clangdVerMajor > 14 + g:WaitForServerFileLoad(3) + else + g:WaitForServerFileLoad(2) + endif + :LspDiag show + var qfl: list<dict<any>> = getloclist(0) + assert_equal('quickfix', getwinvar(winnr('$'), '&buftype')) + assert_equal(bnr, qfl[0].bufnr) + assert_equal(3, qfl->len()) + if clangdVerMajor > 14 + assert_equal([1, 5, 'E'], [qfl[0].lnum, qfl[0].col, qfl[0].type]) + else + assert_equal([1, 5, 'W'], [qfl[0].lnum, qfl[0].col, qfl[0].type]) + endif + assert_equal([1, 9, 'E'], [qfl[1].lnum, qfl[1].col, qfl[1].type]) + assert_equal([2, 9, 'E'], [qfl[2].lnum, qfl[2].col, qfl[2].type]) + close + + :sleep 100m + cursor(2, 1) + assert_equal('', execute('LspDiag prev')) + assert_equal([1, 9], [line('.'), col('.')]) + + assert_equal('', execute('LspDiag prev')) + assert_equal([1, 5], [line('.'), col('.')]) + + var output = execute('LspDiag prev')->split("\n") + assert_equal('Warn: No more diagnostics found', output[0]) + + assert_equal('', execute('LspDiag prevWrap')) + assert_equal([2, 9], [line('.'), col('.')]) + + cursor(2, 1) + assert_equal('', execute('LspDiag first')) + assert_equal([1, 5], [line('.'), col('.')]) + assert_equal('', execute('LspDiag next')) + assert_equal([1, 9], [line('.'), col('.')]) + cursor(1, 1) + assert_equal('', execute('LspDiag last')) + assert_equal([2, 9], [line('.'), col('.')]) + assert_equal('', execute('LspDiag nextWrap')) + assert_equal([1, 5], [line('.'), col('.')]) + assert_equal('', execute('LspDiag nextWrap')) + assert_equal([1, 9], [line('.'), col('.')]) + popup_clear() + + # Test for :LspDiag here on a line with multiple diagnostics + cursor(1, 1) + :LspDiag here + assert_equal([1, 5], [line('.'), col('.')]) + var ids = popup_list() + assert_equal(1, ids->len()) + assert_match('Incompatible pointer to integer', getbufline(ids[0]->winbufnr(), 1, '$')[0]) + popup_clear() + cursor(1, 6) + :LspDiag here + assert_equal([1, 9], [line('.'), col('.')]) + ids = popup_list() + assert_equal(1, ids->len()) + assert_match('Initializer element is not', getbufline(ids[0]->winbufnr(), 1, '$')[0]) + popup_clear() + + # Line without diagnostics + cursor(3, 1) + output = execute('LspDiag here')->split("\n") + assert_equal('Warn: No more diagnostics found on this line', output[0]) + + g:LspOptionsSet({showDiagInPopup: false}) + for i in range(1, 5) + cursor(1, i) + output = execute('LspDiag current')->split('\n') + assert_match('Incompatible pointer to integer', output[0]) + endfor + for i in range(6, 12) + cursor(1, i) + output = execute('LspDiag current')->split('\n') + assert_match('Initializer element is not ', output[0]) + endfor + g:LspOptionsSet({showDiagInPopup: true}) + + # Check for exact diag ":LspDiag current!" + g:LspOptionsSet({showDiagInPopup: false}) + for i in range(1, 4) + cursor(1, i) + output = execute('LspDiag! current')->split('\n') + assert_equal('Warn: No diagnostic messages found for current position', output[0]) + endfor + + cursor(1, 5) + output = execute('LspDiag! current')->split('\n') + assert_match('Incompatible pointer to integer', output[0]) + + for i in range(6, 8) + cursor(1, i) + output = execute('LspDiag! current')->split('\n') + assert_equal('Warn: No diagnostic messages found for current position', output[0]) + endfor + + for i in range(9, 11) + cursor(1, i) + output = execute('LspDiag! current')->split('\n') + assert_match('Initializer element is not ', output[0]) + endfor + for i in range(12, 12) + cursor(1, i) + output = execute('LspDiag! current')->split('\n') + assert_equal('Warn: No diagnostic messages found for current position', output[0]) + endfor + + g:LspOptionsSet({showDiagInPopup: true}) + + # :[count]LspDiag next + g:LspOptionsSet({showDiagInPopup: false}) + cursor(1, 1) + :2LspDiag next + assert_equal([1, 9], [line('.'), col('.')]) + :2LspDiag next + assert_equal([2, 9], [line('.'), col('.')]) + output = execute(':2LspDiag next')->split("\n") + assert_equal('Warn: No more diagnostics found', output[0]) + + cursor(1, 1) + :99LspDiag next + assert_equal([2, 9], [line('.'), col('.')]) + g:LspOptionsSet({showDiagInPopup: true}) + + # :[count]LspDiag prev + g:LspOptionsSet({showDiagInPopup: false}) + cursor(1, 1) + :2LspDiag prev + assert_equal('Warn: No more diagnostics found', output[0]) + cursor(3, 3) + :2LspDiag prev + assert_equal([1, 9], [line('.'), col('.')]) + :2LspDiag prev + assert_equal([1, 5], [line('.'), col('.')]) + output = execute(':2LspDiag prev')->split("\n") + assert_equal('Warn: No more diagnostics found', output[0]) + + cursor(3, 3) + :99LspDiag prev + assert_equal([1, 5], [line('.'), col('.')]) + g:LspOptionsSet({showDiagInPopup: true}) + + :%bw! +enddef + +# Test for highlight diag inline +def g:Test_LspHighlightDiagInline() + :silent! edit XLspHighlightDiag.c + sleep 200m + setline(1, [ + 'int main()', + '{', + ' struct obj obj', + '', + ' return 1;', + '}', + ]) + + # TODO: Waiting count doesn't include Warning, Info, and Hint diags + g:WaitForDiags(2) + + g:LspOptionsSet({highlightDiagInline: true}) + + var props = prop_list(1) + assert_equal(0, props->len()) + props = prop_list(2) + assert_equal(0, props->len()) + props = prop_list(3) + assert_equal(2, props->len()) + assert_equal([ + {'id': 0, 'col': 12, 'type_bufnr': 0, 'end': 1, 'type': 'LspDiagInlineInfo', 'length': 3, 'start': 1}, + {'id': 0, 'col': 16, 'type_bufnr': 0, 'end': 1, 'type': 'LspDiagInlineError', 'length': 3, 'start': 1} + ], props) + props = prop_list(4) + assert_equal(0, props->len()) + props = prop_list(5) + assert_equal(1, props->len()) + assert_equal([{'id': 0, 'col': 5, 'type_bufnr': 0, 'end': 1, 'type': 'LspDiagInlineError', 'length': 6, 'start': 1}], props) + props = prop_list(6) + assert_equal(0, props->len()) + + g:LspOptionsSet({highlightDiagInline: false}) + props = prop_list(1, {end_lnum: line('$')}) + assert_equal(0, props->len()) + + :%bw! +enddef + +# Test for :LspCodeAction +def g:Test_LspCodeAction() + silent! edit XLspCodeAction.c + sleep 200m + var lines: list<string> =<< trim END + void testFunc() + { + int count; + count == 20; + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + cursor(4, 1) + redraw! + :LspCodeAction 1 + assert_equal("\tcount = 20;", getline(4)) + + setline(4, "\tcount = 20:") + cursor(4, 1) + sleep 500m + :LspCodeAction 0 + assert_equal("\tcount = 20:", getline(4)) + + cursor(4, 1) + :LspCodeAction 2 + assert_equal("\tcount = 20:", getline(4)) + + cursor(4, 1) + :LspCodeAction 1 + assert_equal("\tcount = 20;", getline(4)) + bw! + + # pattern and string prefix + silent! edit XLspCodeActionPattern.c + sleep 200m + var lines2: list<string> =<< trim END + void testFunc() + { + int count; + if (count = 1) { + } + } + END + setline(1, lines2) + g:WaitForServerFileLoad(0) + cursor(4, 1) + redraw! + :LspCodeAction use + assert_equal("\tif (count == 1) {", getline(4)) + + setline(4, "\tif (count = 1) {") + cursor(4, 1) + sleep 500m + :LspCodeAction /paren + assert_equal("\tif ((count = 1)) {", getline(4)) + + setline(4, "\tif (count = 1) {") + cursor(4, 1) + sleep 500m + :LspCodeAction NON_EXISTING_PREFIX + assert_equal("\tif (count = 1) {", getline(4)) + + cursor(4, 1) + :LspCodeAction /NON_EXISTING_REGEX + assert_equal("\tif (count = 1) {", getline(4)) + bw! + + # empty file + assert_equal('', execute('LspCodeAction')) + + # file without an LSP server + edit a.raku + assert_equal('Error: Language server for "raku" file type supporting "codeAction" feature is not found', + execute('LspCodeAction')->split("\n")[0]) + + :%bw! +enddef + +# Test for :LspRename +def g:Test_LspRename() + silent! edit XLspRename.c + sleep 200m + var lines: list<string> =<< trim END + void F1(int count) + { + count = 20; + + ++count; + } + + void F2(int count) + { + count = 5; + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + cursor(1, 1) + search('count') + redraw! + feedkeys(":LspRename\<CR>er\<CR>", "xt") + redraw! + var expected: list<string> =<< trim END + void F1(int counter) + { + counter = 20; + + ++counter; + } + + void F2(int count) + { + count = 5; + } + END + assert_equal(expected, getline(1, '$')) + + cursor(1, 1) + search('counter') + LspRename countvar + var expected2: list<string> =<< trim END + void F1(int countvar) + { + countvar = 20; + + ++countvar; + } + + void F2(int count) + { + count = 5; + } + END + assert_equal(expected2, getline(1, '$')) + sleep 100m + bw! + + # empty file + assert_equal('', execute('LspRename')) + + # file without an LSP server + edit a.raku + assert_equal('Error: Language server for "raku" file type supporting "rename" feature is not found', + execute('LspRename')->split("\n")[0]) + + :%bw! +enddef + +# Test for :LspSelectionExpand and :LspSelectionShrink +def g:Test_LspSelection() + silent! edit XLspSelection.c + sleep 200m + var lines: list<string> =<< trim END + void fnSel(int count) + { + int i; + for (i = 0; i < 10; i++) { + count++; + } + count = 20; + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + # start a block-wise visual mode, LspSelectionExpand should change this to + # a characterwise visual mode. + exe "normal! 1G\<C-V>G\"_y" + cursor(2, 1) + redraw! + :LspSelectionExpand + redraw! + normal! y + assert_equal('v', visualmode()) + assert_equal([2, 8], [line("'<"), line("'>")]) + # start a linewise visual mode, LspSelectionExpand should change this to + # a characterwise visual mode. + exe "normal! 3GViB\"_y" + cursor(4, 29) + redraw! + :LspSelectionExpand + redraw! + normal! y + assert_equal('v', visualmode()) + assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")]) + + # Expand the visual selection + xnoremap <silent> le <Cmd>LspSelectionExpand<CR> + xnoremap <silent> ls <Cmd>LspSelectionShrink<CR> + cursor(5, 8) + normal vley + assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")]) + cursor(5, 8) + normal vleley + assert_equal([5, 8, 5, 14], [line("'<"), col("'<"), line("'>"), col("'>")]) + cursor(5, 8) + normal vleleley + assert_equal([4, 30, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")]) + cursor(5, 8) + normal vleleleley + assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")]) + cursor(5, 8) + normal vleleleleley + assert_equal([2, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")]) + cursor(5, 8) + normal vleleleleleley + assert_equal([1, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")]) + cursor(5, 8) + normal vleleleleleleley + assert_equal([1, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")]) + + # Shrink the visual selection + cursor(5, 8) + normal vlsy + assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")]) + cursor(5, 8) + normal vlelsy + assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")]) + cursor(5, 8) + normal vlelelsy + assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")]) + cursor(5, 8) + normal vlelelelsy + assert_equal([5, 8, 5, 14], [line("'<"), col("'<"), line("'>"), col("'>")]) + cursor(5, 8) + normal vlelelelelsy + assert_equal([4, 30, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")]) + cursor(5, 8) + normal vlelelelelelsy + assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")]) + cursor(5, 8) + normal vlelelelelelelsy + assert_equal([2, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")]) + + xunmap le + xunmap ls + bw! + + # empty file + assert_equal('', execute('LspSelectionExpand')) + + # file without an LSP server + edit a.raku + assert_equal('Error: Language server for "raku" file type supporting "selectionRange" feature is not found', + execute('LspSelectionExpand')->split("\n")[0]) + + :%bw! +enddef + +# Test for :LspGotoDefinition, :LspGotoDeclaration and :LspGotoImpl +def g:Test_LspGotoSymbol() + settagstack(0, {items: []}) + silent! edit XLspGotoSymbol.cpp + sleep 600m + var lines: list<string> =<< trim END + class base { + public: + virtual void print(); + }; + + void base::print() + { + } + + class derived : public base { + public: + void print() {} + }; + + void f1(void) + { + base *bp; + derived d; + bp = &d; + + bp->print(); + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + + cursor(21, 6) + :LspGotoDeclaration + assert_equal([3, 19], [line('.'), col('.')]) + exe "normal! \<C-t>" + assert_equal([21, 6], [line('.'), col('.')]) + assert_equal(1, winnr('$')) + + :LspGotoDefinition + assert_equal([6, 12], [line('.'), col('.')]) + exe "normal! \<C-t>" + assert_equal([21, 6], [line('.'), col('.')]) + assert_equal(1, winnr('$')) + + # Command modifiers + :topleft LspGotoDefinition + assert_equal([6, 12], [line('.'), col('.')]) + assert_equal([1, 2], [winnr(), winnr('$')]) + close + exe "normal! \<C-t>" + assert_equal([21, 6], [line('.'), col('.')]) + + :tab LspGotoDefinition + assert_equal([6, 12], [line('.'), col('.')]) + assert_equal([2, 2, 1], [tabpagenr(), tabpagenr('$'), winnr('$')]) + tabclose + exe "normal! \<C-t>" + assert_equal([21, 6], [line('.'), col('.')]) + + # :LspGotoTypeDef + cursor(21, 2) + :LspGotoTypeDef + assert_equal([1, 7], [line('.'), col('.')]) + exe "normal! \<C-t>" + assert_equal([21, 2], [line('.'), col('.')]) + + # :LspGotoImpl + cursor(21, 6) + :LspGotoImpl + assert_equal([12, 11], [line('.'), col('.')]) + exe "normal! \<C-t>" + assert_equal([21, 6], [line('.'), col('.')]) + + # FIXME: The following tests are failing in Github CI. Comment out for now. + if 0 + # Error cases + :messages clear + cursor(11, 5) + :LspGotoDeclaration + var m = execute('messages')->split("\n") + assert_equal('symbol declaration is not found', m[1]) + :messages clear + :LspGotoDefinition + m = execute('messages')->split("\n") + assert_equal('symbol definition is not found', m[1]) + :messages clear + :LspGotoImpl + m = execute('messages')->split("\n") + assert_equal('symbol implementation is not found', m[1]) + :messages clear + endif + + # Test for LspPeekDeclaration + cursor(21, 6) + var bnum = bufnr() + :LspPeekDeclaration + var plist = popup_list() + assert_true(1, plist->len()) + assert_equal(bnum, plist[0]->winbufnr()) + assert_equal(3, line('.', plist[0])) + popup_clear() + # tag stack should not be changed + assert_fails("normal! \<C-t>", 'E555:') + + # Test for LspPeekDefinition + :LspPeekDefinition + plist = popup_list() + assert_true(1, plist->len()) + assert_equal(bnum, plist[0]->winbufnr()) + assert_equal(6, line('.', plist[0])) + popup_clear() + # tag stack should not be changed + assert_fails("normal! \<C-t>", 'E555:') + + # FIXME: :LspPeekTypeDef and :LspPeekImpl are supported only with clang-14. + # This clangd version is not available in Github CI. + + :%bw! + + # empty file + assert_equal('', execute('LspGotoDefinition')) + assert_equal('', execute('LspGotoDeclaration')) + assert_equal('', execute('LspGotoImpl')) + + # file without an LSP server + edit a.raku + assert_equal('Error: Language server for "raku" file type supporting "definition" feature is not found', + execute('LspGotoDefinition')->split("\n")[0]) + assert_equal('Error: Language server for "raku" file type supporting "declaration" feature is not found', + execute('LspGotoDeclaration')->split("\n")[0]) + assert_equal('Error: Language server for "raku" file type supporting "implementation" feature is not found', + execute('LspGotoImpl')->split("\n")[0]) + + :%bw! +enddef + +# Test for :LspHighlight +def g:Test_LspHighlight() + silent! edit XLspHighlight.c + sleep 200m + var lines: list<string> =<< trim END + void f1(int arg) + { + int i = arg; + arg = 2; + if (arg == 2) { + arg = 3; + } + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + cursor(1, 13) + :LspHighlight + var expected: dict<any> + expected = {id: 0, col: 13, end: 1, type: 'LspTextRef', length: 3, start: 1} + expected.type_bufnr = 0 + assert_equal([expected], prop_list(1)) + expected = {id: 0, col: 11, end: 1, type: 'LspReadRef', length: 3, start: 1} + expected.type_bufnr = 0 + assert_equal([expected], prop_list(3)) + expected = {id: 0, col: 3, end: 1, type: 'LspWriteRef', length: 3, start: 1} + expected.type_bufnr = 0 + assert_equal([expected], prop_list(4)) + :LspHighlightClear + assert_equal([], prop_list(1)) + assert_equal([], prop_list(3)) + assert_equal([], prop_list(4)) + + cursor(5, 3) # if (arg == 2) { + var output = execute('LspHighlight')->split("\n") + assert_equal('Warn: No highlight for the current position', output[0]) + :%bw! +enddef + +# Test for :LspHover +def g:Test_LspHover() + silent! edit XLspHover.c + sleep 200m + var lines: list<string> =<< trim END + int f1(int a) + { + return 0; + } + + void f2(void) + { + f1(5); + char *z = "z"; + f1(z); + } + END + setline(1, lines) + if clangdVerMajor > 14 + g:WaitForServerFileLoad(1) + else + g:WaitForServerFileLoad(0) + endif + cursor(8, 4) + var output = execute(':LspHover')->split("\n") + assert_equal([], output) + var p: list<number> = popup_list() + assert_equal(1, p->len()) + assert_equal(['### function `f1` ', '', '---', '→ `int` ', 'Parameters: ', '- `int a`', '', '---', '```cpp', 'int f1(int a)', '```'], getbufline(winbufnr(p[0]), 1, '$')) + popup_close(p[0]) + cursor(7, 1) + output = execute(':LspHover')->split("\n") + assert_equal('Warn: No documentation found for current keyword', output[0]) + output = execute(':silent LspHover')->split("\n") + assert_equal([], output) + assert_equal([], popup_list()) + + # Show current diagnostic as to open another popup. + # Then we can test that LspHover closes all existing popups + cursor(10, 6) + :LspDiag current + assert_equal(1, popup_list()->len()) + :LspHover + assert_equal(1, popup_list()->len()) + popup_clear() + + # Show hover information in a preview window + g:LspOptionsSet({hoverInPreview: true}) + cursor(8, 4) + :LspHover + assert_equal([2, 2, 'preview'], [winnr('$'), winnr(), win_gettype(1)]) + assert_equal('LspHover', winbufnr(1)->bufname()) + cursor(9, 9) + :LspHover + assert_equal([2, 2, 'preview'], [winnr('$'), winnr(), win_gettype(1)]) + g:LspOptionsSet({hoverInPreview: false}) + :pclose + + :%bw! +enddef + +# Test for :LspShowSignature +def g:Test_LspShowSignature() + silent! edit XLspShowSignature.c + sleep 200m + var lines: list<string> =<< trim END + int MyFunc(int a, int b) + { + return 0; + } + + void f2(void) + { + MyFunc( + } + END + setline(1, lines) + g:WaitForServerFileLoad(2) + cursor(8, 10) + :LspShowSignature + var p: list<number> = popup_list() + var bnr: number = winbufnr(p[0]) + assert_equal(1, p->len()) + assert_equal(['MyFunc(int a, int b) -> int'], getbufline(bnr, 1, '$')) + var expected: dict<any> + expected = {id: 0, col: 8, end: 1, type: 'signature', length: 5, start: 1} + expected.type_bufnr = bnr + assert_equal([expected], prop_list(1, {bufnr: bnr})) + popup_close(p[0]) + + setline(line('.'), ' MyFunc(10, ') + cursor(8, 13) + :LspShowSignature + p = popup_list() + bnr = winbufnr(p[0]) + assert_equal(1, p->len()) + assert_equal(['MyFunc(int a, int b) -> int'], getbufline(bnr, 1, '$')) + expected = {id: 0, col: 15, end: 1, type: 'signature', length: 5, start: 1} + expected.type_bufnr = bnr + assert_equal([expected], prop_list(1, {bufnr: bnr})) + popup_close(p[0]) + :%bw! +enddef + +# Test for :LspSymbolSearch +def g:Test_LspSymbolSearch() + silent! edit XLspSymbolSearch.c + sleep 200m + var lines: list<string> =<< trim END + void lsptest_funcA() + { + } + + void lsptest_funcB() + { + } + + void lsptest_funcC() + { + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + + cursor(1, 1) + feedkeys(":LspSymbolSearch lsptest_funcB\<CR>", "xt") + assert_equal([5, 6], [line('.'), col('.')]) + + cursor(1, 1) + feedkeys(":LspSymbolSearch lsptest_func\<CR>\<Down>\<Down>\<CR>", "xt") + assert_equal([9, 6], [line('.'), col('.')]) + + cursor(1, 1) + feedkeys(":LspSymbolSearch lsptest_func\<CR>A\<BS>B\<CR>", "xt") + assert_equal([5, 6], [line('.'), col('.')]) + + var output = execute(':LspSymbolSearch lsptest_nonexist')->split("\n") + assert_equal('Warn: Symbol "lsptest_nonexist" is not found', output[0]) + + :%bw! +enddef + +# Test for :LspIncomingCalls +def g:Test_LspIncomingCalls() + silent! edit XLspIncomingCalls.c + sleep 200m + var lines: list<string> =<< trim END + void xFuncIncoming(void) + { + } + + void aFuncIncoming(void) + { + xFuncIncoming(); + } + + void bFuncIncoming(void) + { + xFuncIncoming(); + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + cursor(1, 6) + :LspIncomingCalls + assert_equal([1, 2], [winnr(), winnr('$')]) + var l = getline(1, '$') + assert_equal('# Incoming calls to "xFuncIncoming"', l[0]) + assert_match('- xFuncIncoming (XLspIncomingCalls.c \[.*\])', l[1]) + assert_match(' + aFuncIncoming (XLspIncomingCalls.c \[.*\])', l[2]) + assert_match(' + bFuncIncoming (XLspIncomingCalls.c \[.*\])', l[3]) + :%bw! +enddef + +# Test for :LspOutline +def g:Test_LspOutline() + silent! edit XLspOutline.c + sleep 200m + var lines: list<string> =<< trim END + void aFuncOutline(void) + { + } + + void bFuncOutline(void) + { + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + var winid = win_getid() + :LspOutline + assert_equal(2, winnr('$')) + var bnum = winbufnr(winid + 1) + assert_equal('LSP-Outline', bufname(bnum)) + assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$')) + + # Validate position vert topleft + assert_equal(['row', [['leaf', winid + 1], ['leaf', winid]]], winlayout()) + + # Validate default width is 20 + assert_equal(20, winwidth(winid + 1)) + + execute $':{bnum}bw' + + # Validate position vert botright + g:LspOptionsSet({outlineOnRight: true}) + :LspOutline + assert_equal(2, winnr('$')) + bnum = winbufnr(winid + 2) + assert_equal('LSP-Outline', bufname(bnum)) + assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$')) + assert_equal(['row', [['leaf', winid], ['leaf', winid + 2]]], winlayout()) + g:LspOptionsSet({outlineOnRight: false}) + execute $':{bnum}bw' + + # Validate <mods> position botright (below) + :botright LspOutline + assert_equal(2, winnr('$')) + bnum = winbufnr(winid + 3) + assert_equal('LSP-Outline', bufname(bnum)) + assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$')) + assert_equal(['col', [['leaf', winid], ['leaf', winid + 3]]], winlayout()) + execute $':{bnum}bw' + + # Validate that outlineWinSize works for LspOutline + g:LspOptionsSet({outlineWinSize: 40}) + :LspOutline + assert_equal(2, winnr('$')) + bnum = winbufnr(winid + 4) + assert_equal('LSP-Outline', bufname(bnum)) + assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$')) + assert_equal(40, winwidth(winid + 4)) + execute $':{bnum}bw' + g:LspOptionsSet({outlineWinSize: 20}) + + # Validate that <count> works for LspOutline + :37LspOutline + assert_equal(2, winnr('$')) + bnum = winbufnr(winid + 5) + assert_equal('LSP-Outline', bufname(bnum)) + assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$')) + assert_equal(37, winwidth(winid + 5)) + execute $':{bnum}bw' + + :%bw! +enddef + +# Test for setting the 'tagfunc' +def g:Test_LspTagFunc() + var lines: list<string> =<< trim END + void aFuncTag(void) + { + xFuncTag(); + } + + void bFuncTag(void) + { + xFuncTag(); + } + + void xFuncTag(void) + { + } + END + writefile(lines, 'Xtagfunc.c') + :silent! edit Xtagfunc.c + g:WaitForServerFileLoad(1) + :setlocal tagfunc=lsp#lsp#TagFunc + cursor(3, 4) + :exe "normal \<C-]>" + assert_equal([11, 6], [line('.'), col('.')]) + cursor(1, 1) + assert_fails('exe "normal \<C-]>"', 'E433:') + + :set tagfunc& + :%bw! + delete('Xtagfunc.c') +enddef + +# Test for the LspDiagsUpdated autocmd +def g:Test_LspDiagsUpdated_Autocmd() + g:LspAutoCmd = 0 + autocmd_add([{event: 'User', pattern: 'LspDiagsUpdated', cmd: 'g:LspAutoCmd = g:LspAutoCmd + 1'}]) + silent! edit XLspDiagsAutocmd.c + sleep 200m + var lines: list<string> =<< trim END + void aFuncDiag(void) + { + return; + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + setline(3, ' return:') + redraw! + g:WaitForDiags(1) + setline(3, ' return;') + redraw! + g:WaitForDiags(0) + :%bw! + autocmd_delete([{event: 'User', pattern: 'LspDiagsUpdated'}]) + assert_equal(5, g:LspAutoCmd) +enddef + +# Test custom notification handlers +def g:Test_LspCustomNotificationHandlers() + + g:LSPTest_customNotificationHandlerReplied = false + + silent! edit XcustomNotification.c + sleep 200m + var lines: list<string> =<< trim END + int a = 1; + int main(void) { + return a; + } + END + setline(1, lines) + g:WaitForAssert(() => assert_equal(true, g:LSPTest_customNotificationHandlerReplied)) + :%bw! +enddef + +def g:Test_ScanFindIdent() + :silent! edit XscanFindIdent.c + sleep 200m + var lines: list<string> =<< trim END + int countFI; + int fnFI(int a) + { + int hello; + hello = a; + return countFI + 1; + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + :redraw! + + # LspGotoDefinition et al + cursor(5, 10) + assert_equal([], execute('LspGotoDefinition')->split("\n")) + assert_equal([2, 14], [line('.'), col('.')]) + + cursor(6, 10) + assert_equal([], execute('LspGotoDefinition')->split("\n")) + assert_equal([1, 5], [line('.'), col('.')]) + + # LspShowReferences + cursor(6, 10) + assert_equal([], execute('LspShowReferences')->split("\n")) + :lclose + + # LspRename + cursor(6, 10) + assert_equal([], execute('LspRename counterFI')->split("\n")) + sleep 100m + assert_equal('int counterFI;', getline(1)) + assert_equal(' return counterFI + 1;', getline(6)) + + :%bw! +enddef + +# Test for doing omni completion from the first column +def g:Test_OmniComplete_FirstColumn() + :silent! edit XOmniCompleteFirstColumn.c + sleep 200m + var lines: list<string> =<< trim END + typedef struct Foo_ { + } Foo_t; + + #define FOO 1 + END + setline(1, lines) + g:WaitForServerFileLoad(0) + redraw! + + feedkeys("G0i\<C-X>\<C-O>", 'xt') + assert_equal('Foo_t#define FOO 1', getline('.')) + :%bw! +enddef + +# Test for doing omni completion with a multibyte character +def g:Test_OmniComplete_Multibyte() + :silent! edit XOmniCompleteMultibyte.c + sleep 200m + var lines: list<string> =<< trim END + #include <string.h> + void Fn(void) + { + int thisVar = 1; + int len = strlen("©©©©©") + thisVar; + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + redraw! + + cursor(5, 36) + feedkeys("cwthis\<C-X>\<C-O>", 'xt') + assert_equal(' int len = strlen("©©©©©") + thisVar;', getline('.')) + :%bw! +enddef + +# Test for doing omni completion for a struct field +def g:Test_OmniComplete_Struct() + :silent! edit XOmniCompleteStruct.c + sleep 200m + var lines: list<string> =<< trim END + struct test_ { + int foo; + int bar; + int baz; + }; + void Fn(void) + { + struct test_ myTest; + struct test_ *pTest; + myTest.bar = 10; + pTest->bar = 20; + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + redraw! + + cursor(10, 12) + feedkeys("cwb\<C-X>\<C-O>\<C-N>\<C-Y>", 'xt') + assert_equal(' myTest.baz = 10;', getline('.')) + cursor(11, 12) + feedkeys("cw\<C-X>\<C-O>\<C-N>\<C-Y>", 'xt') + assert_equal(' pTest->baz = 20;', getline('.')) + :%bw! +enddef + +# Test for doing omni completion after an opening parenthesis. +# This used to result in an error message. +def g:Test_OmniComplete_AfterParen() + :silent! edit XOmniCompleteAfterParen.c + sleep 200m + var lines: list<string> =<< trim END + #include <stdio.h> + void Fn(void) + { + printf( + } + END + setline(1, lines) + g:WaitForServerFileLoad(2) + redraw! + + cursor(4, 1) + feedkeys("A\<C-X>\<C-O>\<C-Y>", 'xt') + assert_equal(' printf(', getline('.')) + :%bw! +enddef + +# Test for inlay hints +def g:Test_InlayHints() + :silent! edit XinlayHints.c + sleep 200m + var lines: list<string> =<< trim END + void func1(int a, int b) + { + } + + void func2() + { + func1(10, 20); + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + redraw! + + assert_equal([], prop_list(7)) + + :LspInlayHints enable + var p = prop_list(7) + assert_equal([9, 'LspInlayHintsParam'], [p[0].col, p[0].type]) + assert_equal([13, 'LspInlayHintsParam'], [p[1].col, p[1].type]) + + :LspInlayHints disable + assert_equal([], prop_list(7)) + + g:LspOptionsSet({showInlayHints: true}) + assert_equal([9, 'LspInlayHintsParam'], [p[0].col, p[0].type]) + assert_equal([13, 'LspInlayHintsParam'], [p[1].col, p[1].type]) + + g:LspOptionsSet({showInlayHints: false}) + assert_equal([], prop_list(7)) + + :hide enew + :LspInlayHints enable + :bprev + assert_equal([9, 'LspInlayHintsParam'], [p[0].col, p[0].type]) + assert_equal([13, 'LspInlayHintsParam'], [p[1].col, p[1].type]) + + :hide enew + :LspInlayHints disable + :bprev + assert_equal([], prop_list(7)) + + :%bw! +enddef + +# Test for reloading a modified buffer with diags +def g:Test_ReloadBufferWithDiags() + var lines: list<string> =<< trim END + void ReloadBufferFunc1(void) + { + int a: + } + END + writefile(lines, 'Xreloadbuffer.c') + :silent! edit Xreloadbuffer.c + g:WaitForServerFileLoad(1) + var signs = sign_getplaced('%', {group: '*'})[0].signs + assert_equal(3, signs[0].lnum) + append(0, ['', '']) + signs = sign_getplaced('%', {group: '*'})[0].signs + assert_equal(5, signs[0].lnum) + :edit! + sleep 200m + signs = sign_getplaced('%', {group: '*'})[0].signs + assert_equal(3, signs[0].lnum) + + :%bw! + delete('Xreloadbuffer.c') +enddef + +# Test for ":LspDiag" sub commands +def g:Test_LspDiagsSubcmd() + new XLspDiagsSubCmd.raku + + feedkeys(":LspDiag \<C-A>\<CR>", 'xt') + assert_equal('LspDiag first current here highlight last next nextWrap prev prevWrap show', @:) + feedkeys(":LspDiag highlight \<C-A>\<CR>", 'xt') + assert_equal('LspDiag highlight enable disable', @:) + assert_equal(['Error: :LspDiag - Unsupported argument "xyz"'], + execute('LspDiag xyz')->split("\n")) + assert_equal(['Error: :LspDiag - Unsupported argument "first xyz"'], + execute('LspDiag first xyz')->split("\n")) + assert_equal(['Error: :LspDiag - Unsupported argument "current xyz"'], + execute('LspDiag current xyz')->split("\n")) + assert_equal(['Error: :LspDiag - Unsupported argument "here xyz"'], + execute('LspDiag here xyz')->split("\n")) + assert_equal(['Error: Argument required for ":LspDiag highlight"'], + execute('LspDiag highlight')->split("\n")) + assert_equal(['Error: :LspDiag highlight - Unsupported argument "xyz"'], + execute('LspDiag highlight xyz')->split("\n")) + assert_equal(['Error: :LspDiag highlight - Unsupported argument "enable xyz"'], + execute('LspDiag highlight enable xyz')->split("\n")) + assert_equal(['Error: :LspDiag - Unsupported argument "last xyz"'], + execute('LspDiag last xyz')->split("\n")) + assert_equal(['Error: :LspDiag - Unsupported argument "next xyz"'], + execute('LspDiag next xyz')->split("\n")) + assert_equal(['Error: :LspDiag - Unsupported argument "prev xyz"'], + execute('LspDiag prev xyz')->split("\n")) + assert_equal(['Error: :LspDiag - Unsupported argument "show xyz"'], + execute('LspDiag show xyz')->split("\n")) + + :%bw! +enddef + +# Test for the :LspServer command. +def g:Test_LspServer() + new a.raku + assert_equal(['Warn: No Lsp servers found for "a.raku"'], + execute('LspServer debug on')->split("\n")) + assert_equal(['Warn: No Lsp servers found for "a.raku"'], + execute('LspServer restart')->split("\n")) + assert_equal(['Warn: No Lsp servers found for "a.raku"'], + execute('LspServer show status')->split("\n")) + assert_equal(['Warn: No Lsp servers found for "a.raku"'], + execute('LspServer trace verbose')->split("\n")) + assert_equal(['Error: LspServer - Unsupported argument "xyz"'], + execute('LspServer xyz')->split("\n")) + assert_equal(['Error: Argument required for ":LspServer debug"'], + execute('LspServer debug')->split("\n")) + assert_equal(['Error: Unsupported argument "xyz"'], + execute('LspServer debug xyz')->split("\n")) + assert_equal(['Error: Unsupported argument "on xyz"'], + execute('LspServer debug on xyz')->split("\n")) + assert_equal(['Error: Argument required for ":LspServer show"'], + execute('LspServer show')->split("\n")) + assert_equal(['Error: Unsupported argument "xyz"'], + execute('LspServer show xyz')->split("\n")) + assert_equal(['Error: Unsupported argument "status xyz"'], + execute('LspServer show status xyz')->split("\n")) + assert_equal(['Error: Argument required for ":LspServer trace"'], + execute('LspServer trace')->split("\n")) + assert_equal(['Error: Unsupported argument "xyz"'], + execute('LspServer trace xyz')->split("\n")) + assert_equal(['Error: Unsupported argument "verbose xyz"'], + execute('LspServer trace verbose xyz')->split("\n")) + :%bw! +enddef + +# Test for the diagnostics virtual text text property +def g:Test_DiagVirtualText() + if !has('patch-9.0.1157') + # Doesn't support virtual text + return + endif + g:LspOptionsSet({highlightDiagInline: false}) + :silent! edit XdiagVirtualText.c + sleep 200m + var lines: list<string> =<< trim END + void DiagVirtualTextFunc1() + { + int i: + } + END + setline(1, lines) + g:WaitForServerFileLoad(1) + redraw! + + var p = prop_list(1, {end_lnum: line('$')}) + assert_equal(0, p->len()) + + g:LspOptionsSet({showDiagWithVirtualText: true}) + p = prop_list(1, {end_lnum: line('$')}) + assert_equal(1, p->len()) + assert_equal([3, 'LspDiagVirtualTextError'], [p[0].lnum, p[0].type]) + + g:LspOptionsSet({showDiagWithVirtualText: false}) + p = prop_list(1, {end_lnum: line('$')}) + assert_equal(0, p->len()) + + g:LspOptionsSet({highlightDiagInline: true}) + :%bw! +enddef + +# Test for enabling and disabling the "showDiagWithSign" option. +def g:Test_DiagSigns() + :silent! edit Xdiagsigns.c + sleep 200m + var lines: list<string> =<< trim END + void DiagSignsFunc1(void) + { + int a: + } + END + setline(1, lines) + g:WaitForServerFileLoad(1) + redraw! + + var signs = sign_getplaced('%', {group: '*'})[0].signs + assert_equal([1, 3], [signs->len(), signs[0].lnum]) + + g:LspOptionsSet({showDiagWithSign: false}) + signs = sign_getplaced('%', {group: '*'})[0].signs + assert_equal([], signs) + g:LspOptionsSet({showDiagWithSign: true}) + signs = sign_getplaced('%', {group: '*'})[0].signs + assert_equal([1, 3], [signs->len(), signs[0].lnum]) + + # Test for enabling/disabling "autoHighlightDiags" + g:LspOptionsSet({autoHighlightDiags: false}) + signs = sign_getplaced('%', {group: '*'})[0].signs + assert_equal([], signs) + g:LspOptionsSet({autoHighlightDiags: true}) + signs = sign_getplaced('%', {group: '*'})[0].signs + assert_equal([1, 3], [signs->len(), signs[0].lnum]) + + :%bw! +enddef + +# TODO: +# 1. Add a test for autocompletion with a single match while ignoring case. +# After the full matched name is typed, the completion popup should still +# be displayed. e.g. +# +# int MyVar = 1; +# int abc = myvar<C-N><C-Y> +# 2. Add a test for jumping to a non-existing symbol definition, declaration. + +# Start the C language server. Returns true on success and false on failure. +def g:StartLangServer(): bool + return g:StartLangServerWithFile('Xtest.c') +enddef + +# vim: shiftwidth=2 softtabstop=2 noexpandtab diff --git a/vim/pack/downloads/opt/lsp/test/common.vim b/vim/pack/downloads/opt/lsp/test/common.vim new file mode 100644 index 0000000..eeb852c --- /dev/null +++ b/vim/pack/downloads/opt/lsp/test/common.vim @@ -0,0 +1,166 @@ +vim9script +# Common routines used for running the unit tests + +# Load the LSP plugin. Also enable syntax, file type detection. +def g:LoadLspPlugin() + syntax on + filetype on + filetype plugin on + filetype indent on + + # Set the $LSP_PROFILE environment variable to profile the LSP plugin + var do_profile: bool = false + if exists('$LSP_PROFILE') + do_profile = true + endif + + if do_profile + # profile the LSP plugin + profile start lsp_profile.txt + profile! file */lsp/* + endif + + g:LSPTest = true + source ../plugin/lsp.vim +enddef + +# The WaitFor*() functions are reused from the Vim test suite. +# +# Wait for up to five seconds for "assert" to return zero. "assert" must be a +# (lambda) function containing one assert function. Example: +# call WaitForAssert({-> assert_equal("dead", job_status(job)}) +# +# A second argument can be used to specify a different timeout in msec. +# +# Return zero for success, one for failure (like the assert function). +func g:WaitForAssert(assert, ...) + let timeout = get(a:000, 0, 5000) + if g:WaitForCommon(v:null, a:assert, timeout) < 0 + return 1 + endif + return 0 +endfunc + +# Either "expr" or "assert" is not v:null +# Return the waiting time for success, -1 for failure. +func g:WaitForCommon(expr, assert, timeout) + " using reltime() is more accurate, but not always available + let slept = 0 + if exists('*reltimefloat') + let start = reltime() + endif + + while 1 + if type(a:expr) == v:t_func + let success = a:expr() + elseif type(a:assert) == v:t_func + let success = a:assert() == 0 + else + let success = eval(a:expr) + endif + if success + return slept + endif + + if slept >= a:timeout + break + endif + if type(a:assert) == v:t_func + " Remove the error added by the assert function. + call remove(v:errors, -1) + endif + + sleep 10m + if exists('*reltimefloat') + let slept = float2nr(reltimefloat(reltime(start)) * 1000) + else + let slept += 10 + endif + endwhile + + return -1 " timed out +endfunc + +# Wait for up to five seconds for "expr" to become true. "expr" can be a +# stringified expression to evaluate, or a funcref without arguments. +# Using a lambda works best. Example: +# call WaitFor({-> status == "ok"}) +# +# A second argument can be used to specify a different timeout in msec. +# +# When successful the time slept is returned. +# When running into the timeout an exception is thrown, thus the function does +# not return. +func g:WaitFor(expr, ...) + let timeout = get(a:000, 0, 5000) + let slept = g:WaitForCommon(a:expr, v:null, timeout) + if slept < 0 + throw 'WaitFor() timed out after ' .. timeout .. ' msec' + endif + return slept +endfunc + +# Wait for diagnostic messages from the LSP server. +# Waits for a maximum of (150 * 200) / 1000 = 30 seconds +def g:WaitForDiags(errCount: number) + var retries = 0 + while retries < 200 + var d = lsp#lsp#ErrorCount() + if d.Error == errCount + break + endif + retries += 1 + :sleep 150m + endwhile + + assert_equal(errCount, lsp#lsp#ErrorCount().Error) + if lsp#lsp#ErrorCount().Error != errCount + :LspDiag show + assert_report(getloclist(0)->string()) + :lclose + endif +enddef + +# Wait for the LSP server to load and process a file. This works by waiting +# for a certain number of diagnostic messages from the server. +def g:WaitForServerFileLoad(diagCount: number) + :redraw! + var waitCount = diagCount + if waitCount == 0 + # Introduce a temporary diagnostic + append('$', '-') + redraw! + waitCount = 1 + endif + g:WaitForDiags(waitCount) + if waitCount != diagCount + # Remove the temporary line + deletebufline('%', '$') + redraw! + g:WaitForDiags(0) + endif +enddef + +# Start the language server. Returns true on success and false on failure. +# 'fname' is the name of a dummy file to start the server. +def g:StartLangServerWithFile(fname: string): bool + # Edit a dummy file to start the LSP server + exe ':silent! edit ' .. fname + # Wait for the LSP server to become ready (max 10 seconds) + var maxcount = 100 + while maxcount > 0 && !g:LspServerReady() + :sleep 100m + maxcount -= 1 + endwhile + var serverStatus: bool = g:LspServerReady() + :bw! + + if !serverStatus + writefile(['FAIL: Not able to start the language server'], 'results.txt') + qall! + endif + + return serverStatus +enddef + +# vim: shiftwidth=2 softtabstop=2 noexpandtab diff --git a/vim/pack/downloads/opt/lsp/test/dumps/Test_tsserver_completion_1.dump b/vim/pack/downloads/opt/lsp/test/dumps/Test_tsserver_completion_1.dump new file mode 100644 index 0000000..175c1dd --- /dev/null +++ b/vim/pack/downloads/opt/lsp/test/dumps/Test_tsserver_completion_1.dump @@ -0,0 +1,10 @@ +|H+0#00e0003#ffffff0|>|c+0#af5f00255&|o|n|s|t| +0#0000000&|h|t@1|p| |=| |r|e|q|u|i|r|e|(|'+0#e000002&|h|t@1|p|'|)+0#0000000&| @44 +| +0#0000e05#a8a8a8255@1|h+0#0000000#ffffff0|t@1|p|.|c|r|e> @64 +|~+0#4040ff13&| @4| +0#0000001#ffd7ff255|c|r|e|a|t|e|S|e|r|v|e|r| |f| | +0#4040ff13#ffffff0@52 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|-+2#0000000&@1| |I|N|S|E|R|T| |-@1| +0&&@62 diff --git a/vim/pack/downloads/opt/lsp/test/dumps/Test_tsserver_completion_2.dump b/vim/pack/downloads/opt/lsp/test/dumps/Test_tsserver_completion_2.dump new file mode 100644 index 0000000..3b6ecab --- /dev/null +++ b/vim/pack/downloads/opt/lsp/test/dumps/Test_tsserver_completion_2.dump @@ -0,0 +1,10 @@ +|H+0#00e0003#ffffff0|>|c+0#af5f00255&|o|n|s|t| +0#0000000&|h|t@1|p| |=| |r|e|q|u|i|r|e|(|'+0#e000002&|h|t@1|p|'|)+0#0000000&| @44 +| +0#0000e05#a8a8a8255@1|h+0#0000000#ffffff0|t@1|p|.|c|r> @65 +|~+0#4040ff13&| @4| +0#0000001#ffd7ff255|c|r|e|a|t|e|S|e|r|v|e|r| |f| | +0#4040ff13#ffffff0@52 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|-+2#0000000&@1| |I|N|S|E|R|T| |-@1| +0&&@62 diff --git a/vim/pack/downloads/opt/lsp/test/gopls_tests.vim b/vim/pack/downloads/opt/lsp/test/gopls_tests.vim new file mode 100644 index 0000000..bf00c89 --- /dev/null +++ b/vim/pack/downloads/opt/lsp/test/gopls_tests.vim @@ -0,0 +1,121 @@ +vim9script +# Unit tests for Vim Language Server Protocol (LSP) golang client + +source common.vim + +var lspServers = [{ + filetype: ['go'], + path: exepath('gopls'), + args: ['serve'] + }] +call LspAddServer(lspServers) +echomsg systemlist($'{lspServers[0].path} version') + +# Test for :LspGotoDefinition, :LspGotoDeclaration, etc. +# This test also tests that multiple locations will be +# shown in a list or popup +def g:Test_LspGoto() + :silent! edit Xtest.go + var bnr = bufnr() + + sleep 200m + + var lines =<< trim END + package main + + type A/*goto implementation*/ interface { + Hello() + } + + type B struct{} + + func (b *B) Hello() {} + + type C struct{} + + func (c *C) Hello() {} + + func main() { + } + END + + setline(1, lines) + :redraw! + g:WaitForServerFileLoad(0) + + cursor(9, 10) + :LspGotoDefinition + assert_equal([7, 6], [line('.'), col('.')]) + exe "normal! \<C-t>" + assert_equal([9, 10], [line('.'), col('.')]) + + cursor(9, 13) + :LspGotoImpl + assert_equal([4, 9], [line('.'), col('.')]) + + cursor(13, 13) + :LspGotoImpl + assert_equal([4, 9], [line('.'), col('.')]) + + # Two implementions needs to be shown in a location list + cursor(4, 9) + assert_equal('', execute('LspGotoImpl')) + sleep 200m + var loclist: list<dict<any>> = getloclist(0) + assert_equal('quickfix', getwinvar(winnr('$'), '&buftype')) + assert_equal(2, loclist->len()) + assert_equal(bnr, loclist[0].bufnr) + assert_equal([9, 13, ''], [loclist[0].lnum, loclist[0].col, loclist[0].type]) + assert_equal([13, 13, ''], [loclist[1].lnum, loclist[1].col, loclist[1].type]) + lclose + + # Two implementions needs to be shown in a quickfix list + g:LspOptionsSet({ useQuickfixForLocations: true }) + cursor(4, 9) + assert_equal('', execute('LspGotoImpl')) + sleep 200m + var qfl: list<dict<any>> = getqflist() + assert_equal('quickfix', getwinvar(winnr('$'), '&buftype')) + assert_equal(2, qfl->len()) + assert_equal(bnr, qfl[0].bufnr) + assert_equal([9, 13, ''], [qfl[0].lnum, qfl[0].col, qfl[0].type]) + assert_equal([13, 13, ''], [qfl[1].lnum, qfl[1].col, qfl[1].type]) + cclose + g:LspOptionsSet({ useQuickfixForLocations: false }) + + # Two implementions needs to be peeked in a popup + cursor(4, 9) + :LspPeekImpl + sleep 10m + var ids = popup_list() + assert_equal(2, ids->len()) + var filePopupAttrs = ids[0]->popup_getoptions() + var refPopupAttrs = ids[1]->popup_getoptions() + assert_match('Xtest', filePopupAttrs.title) + assert_match('Implementation', refPopupAttrs.title) + assert_equal(9, line('.', ids[0])) # current line in left panel + assert_equal(2, line('$', ids[1])) # last line in right panel + feedkeys("j\<CR>", 'xt') + assert_equal(13, line('.')) + assert_equal([], popup_list()) + popup_clear() + + # Jump to the first implementation + cursor(4, 9) + assert_equal('', execute(':1LspGotoImpl')) + assert_equal([9, 13], [line('.'), col('.')]) + + # Jump to the second implementation + cursor(4, 9) + assert_equal('', execute(':2LspGotoImpl')) + assert_equal([13, 13], [line('.'), col('.')]) + bw! +enddef + +# Start the gopls language server. Returns true on success and false on +# failure. +def g:StartLangServer(): bool + return g:StartLangServerWithFile('Xtest.go') +enddef + +# vim: shiftwidth=2 softtabstop=2 noexpandtab diff --git a/vim/pack/downloads/opt/lsp/test/markdown_tests.vim b/vim/pack/downloads/opt/lsp/test/markdown_tests.vim new file mode 100644 index 0000000..59abfb6 --- /dev/null +++ b/vim/pack/downloads/opt/lsp/test/markdown_tests.vim @@ -0,0 +1,305 @@ +vim9script + +# Unit tests for the Github Flavored Markdown parser + +import '../autoload/lsp/markdown.vim' as md + +# Test for different markdowns +def g:Test_Markdown() + var tests: list<list<list<any>>> = [ + [ + # Different headings + # Input text + [ + '# First level heading', + '## Second level heading', + '### Third level heading', + '# Heading with leading and trailing whitespaces ', + 'Multiline setext heading ', + 'of level 1', + '===', + 'Multiline setext heading\', + 'of level 2', + '---' + ], + # Expected text + [ + 'First level heading', + '', + 'Second level heading', + '', + 'Third level heading', + '', + 'Heading with leading and trailing whitespaces', + '', + 'Multiline setext heading', + 'of level 1', + '', + 'Multiline setext heading', + 'of level 2' + ], + # Expected text properties + [ + [{'col': 1, 'type': 'LspMarkdownHeading', 'length': 19}], + [], + [{'col': 1, 'type': 'LspMarkdownHeading', 'length': 20}], + [], + [{'col': 1, 'type': 'LspMarkdownHeading', 'length': 19}], + [], + [{'col': 1, 'type': 'LspMarkdownHeading', 'length': 45}], + [], + [{'col': 1, 'type': 'LspMarkdownHeading', 'length': 24}], + [{'col': 1, 'type': 'LspMarkdownHeading', 'length': 10}], + [], + [{'col': 1, 'type': 'LspMarkdownHeading', 'length': 24}], + [{'col': 1, 'type': 'LspMarkdownHeading', 'length': 10}], + ] + ], + [ + # Bold text style + # Input text + [ + 'This **word** should be bold', + '', + '**This line should be bold**', + '', + 'This __word__ should be bold', + '', + '__This line should be bold__' + ], + # Expected text + [ + 'This word should be bold', + '', + 'This line should be bold', + '', + 'This word should be bold', + '', + 'This line should be bold' + ], + # Expected text properties + [ + [{'col': 6, 'type': 'LspMarkdownBold', 'length': 4}], + [], + [{'col': 1, 'type': 'LspMarkdownBold', 'length': 24}], + [], + [{'col': 6, 'type': 'LspMarkdownBold', 'length': 4}], + [], + [{'col': 1, 'type': 'LspMarkdownBold', 'length': 24}] + ] + ], + [ + # Italic text style + # Input text + [ + 'This *word* should be italic', + '', + '*This line should be italic*', + '', + 'This _word_ should be italic', + '', + '_This line should be italic_' + ], + # Expected text + [ + 'This word should be italic', + '', + 'This line should be italic', + '', + 'This word should be italic', + '', + 'This line should be italic' + ], + # Expected text properties + [ + [{'col': 6, 'type': 'LspMarkdownItalic', 'length': 4}], + [], + [{'col': 1, 'type': 'LspMarkdownItalic', 'length': 26}], + [], + [{'col': 6, 'type': 'LspMarkdownItalic', 'length': 4}], + [], + [{'col': 1, 'type': 'LspMarkdownItalic', 'length': 26}] + ], + ], + [ + # strikethrough text style + # Input text + [ + 'This ~word~ should be strikethrough', + '', + '~This line should be strikethrough~' + ], + # Expected text + [ + 'This word should be strikethrough', + '', + 'This line should be strikethrough' + ], + # Expected text properties + [ + [{'col': 6, 'type': 'LspMarkdownStrikeThrough', 'length': 4}], + [], + [{'col': 1, 'type': 'LspMarkdownStrikeThrough', 'length': 33}] + ] + ], + [ + # bold and nested italic text style + # Input text + [ + '**This _word_ should be bold and italic**', + ], + # Expected text + [ + 'This word should be bold and italic', + ], + # Expected text properties + [ + [ + {'col': 1, 'type': 'LspMarkdownBold', 'length': 35}, + {'col': 6, 'type': 'LspMarkdownItalic', 'length': 4} + ] + ] + ], + [ + # all bold and italic text style + # Input text + [ + '***This line should be all bold and italic***', + ], + # Expected text + [ + 'This line should be all bold and italic', + ], + # Expected text properties + [ + [ + {'col': 1, 'type': 'LspMarkdownItalic', 'length': 39}, + {'col': 1, 'type': 'LspMarkdownBold', 'length': 39} + ] + ] + ], + [ + # quoted text + # FIXME: The text is not quoted + # Input text + [ + 'Text that is not quoted', + '> quoted text' + ], + # Expected text + [ + 'Text that is not quoted', + '', + 'quoted text' + ], + # Expected text properties + [ + [], [], [] + ] + ], + [ + # line breaks + # Input text + [ + 'This paragraph contains ', + 'a soft line break', + '', + 'This paragraph contains ', + 'an hard line break', + '', + 'This paragraph contains an emphasis _before_\', + 'an hard line break', + '', + 'This paragraph contains an emphasis ', + '_after_ an hard line break', + '', + 'This paragraph _contains\', + 'an emphasis_ with an hard line break in the middle', + '', + '→ This paragraph contains an hard line break ', + 'and starts with the multibyte character "\u2192"', + '', + 'Line breaks `', + 'do\', + 'not ', + 'occur', + '` inside code spans' + ], + # Expected text + [ + 'This paragraph contains a soft line break', + '', + 'This paragraph contains', + 'an hard line break', + '', + 'This paragraph contains an emphasis before', + 'an hard line break', + '', + 'This paragraph contains an emphasis', + 'after an hard line break', + '', + 'This paragraph contains', + 'an emphasis with an hard line break in the middle', + '', + '→ This paragraph contains an hard line break', + 'and starts with the multibyte character "\u2192"', + '', + 'Line breaks do\ not occur inside code spans' + ], + # Expected text properties + [ + [], + [], + [], + [], + [], + [{'col': 37, 'type': 'LspMarkdownItalic', 'length': 6}], + [], + [], + [], + [{'col': 1, 'type': 'LspMarkdownItalic', 'length': 5}], + [], + [{'col': 16, 'type': 'LspMarkdownItalic', 'length': 8}], + [{'col': 1, 'type': 'LspMarkdownItalic', 'length': 11}], + [], + [], + [], + [], + [{'col': 13, 'type': 'LspMarkdownCode', 'length': 15}] + ] + ], + [ + # non-breaking space characters + # Input text + [ + ' This is text.', + ], + # Expected text + [ + ' This is text.', + ], + # Expected text properties + [ + [] + ] + ], + ] + + var doc: dict<list<any>> + var text_result: list<string> + var props_result: list<list<dict<any>>> + for t in tests + doc = md.ParseMarkdown(t[0]) + text_result = doc.content->deepcopy()->map((_, v) => v.text) + props_result = doc.content->deepcopy()->map((_, v) => v.props) + assert_equal(t[1], text_result, t[0]->string()) + assert_equal(t[2], props_result, t[0]->string()) + endfor +enddef + +# Only here to because the test runner needs it +def g:StartLangServer(): bool + return true +enddef + +# vim: tabstop=8 shiftwidth=2 softtabstop=2 noexpandtab diff --git a/vim/pack/downloads/opt/lsp/test/not_lspserver_related_tests.vim b/vim/pack/downloads/opt/lsp/test/not_lspserver_related_tests.vim new file mode 100644 index 0000000..57c7525 --- /dev/null +++ b/vim/pack/downloads/opt/lsp/test/not_lspserver_related_tests.vim @@ -0,0 +1,14 @@ +vim9script +# Unit tests for Vim Language Server Protocol (LSP) for various functionality + +# Test for no duplicates in helptags +def g:Test_Helptags() + :helptags ../doc +enddef + +# Only here to because the test runner needs it +def g:StartLangServer(): bool + return true +enddef + +# vim: shiftwidth=2 softtabstop=2 noexpandtab diff --git a/vim/pack/downloads/opt/lsp/test/run_tests.cmd b/vim/pack/downloads/opt/lsp/test/run_tests.cmd new file mode 100644 index 0000000..863d06d --- /dev/null +++ b/vim/pack/downloads/opt/lsp/test/run_tests.cmd @@ -0,0 +1,17 @@ +@echo off + +REM Script to run the unit-tests for the LSP Vim plugin on MS-Windows + +SETLOCAL +SET VIMPRG="vim.exe" +SET VIM_CMD=%VIMPRG% -u NONE -U NONE -i NONE --noplugin -N --not-a-term + +%VIM_CMD% -c "let g:TestName='clangd_tests.vim'" -S runner.vim + +echo LSP unit test results +type results.txt + +findstr /I FAIL results.txt > nul 2>&1 +if %ERRORLEVEL% EQU 0 echo ERROR: Some test failed. +if %ERRORLEVEL% NEQ 0 echo SUCCESS: All the tests passed. + diff --git a/vim/pack/downloads/opt/lsp/test/run_tests.sh b/vim/pack/downloads/opt/lsp/test/run_tests.sh new file mode 100755 index 0000000..1a1e69b --- /dev/null +++ b/vim/pack/downloads/opt/lsp/test/run_tests.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +# Script to run the unit-tests for the LSP Vim plugin + +VIMPRG=${VIMPRG:=$(which vim)} +if [ -z "$VIMPRG" ]; then + echo "ERROR: vim (\$VIMPRG) is not found in PATH" + exit 1 +fi + +VIM_CMD="$VIMPRG -u NONE -U NONE -i NONE --noplugin -N --not-a-term" + +TESTS="clangd_tests.vim tsserver_tests.vim gopls_tests.vim not_lspserver_related_tests.vim markdown_tests.vim rust_tests.vim" + +RunTestsInFile() { + testfile=$1 + echo "Running tests in $testfile" + $VIM_CMD -c "let g:TestName='$testfile'" -S runner.vim + + if ! [ -f results.txt ]; then + echo "ERROR: Test results file 'results.txt' is not found." + exit 2 + fi + + cat results.txt + + if grep -qw FAIL results.txt; then + echo "ERROR: Some test(s) in $testfile failed." + exit 3 + fi + + echo "SUCCESS: All the tests in $testfile passed." + echo +} + +for testfile in $TESTS +do + RunTestsInFile $testfile +done + +for encoding in "utf-8" "utf-16" "utf-32" +do + export LSP_OFFSET_ENCODING=$encoding + echo "LSP offset encoding: $LSP_OFFSET_ENCODING" + RunTestsInFile clangd_offsetencoding.vim +done + +echo "SUCCESS: All the tests passed." +exit 0 + +# vim: shiftwidth=2 softtabstop=2 noexpandtab diff --git a/vim/pack/downloads/opt/lsp/test/runner.vim b/vim/pack/downloads/opt/lsp/test/runner.vim new file mode 100644 index 0000000..14c849a --- /dev/null +++ b/vim/pack/downloads/opt/lsp/test/runner.vim @@ -0,0 +1,55 @@ +vim9script +# Script to run a language server unit tests +# The global variable TestName should be set to the name of the file +# containing the tests. + +source common.vim + +def LspRunTests() + :set nomore + :set debug=beep + delete('results.txt') + + # Get the list of test functions in this file and call them + var fns: list<string> = execute('function /^Test_') + ->split("\n") + ->map("v:val->substitute('^def ', '', '')") + ->sort() + if fns->empty() + # No tests are found + writefile(['No tests are found'], 'results.txt') + return + endif + for f in fns + v:errors = [] + v:errmsg = '' + try + :%bw! + exe $'g:{f}' + catch + call add(v:errors, $'Error: Test {f} failed with exception {v:exception} at {v:throwpoint}') + endtry + if v:errmsg != '' + call add(v:errors, $'Error: Test {f} generated error {v:errmsg}') + endif + if !v:errors->empty() + writefile(v:errors, 'results.txt', 'a') + writefile([$'{f}: FAIL'], 'results.txt', 'a') + else + writefile([$'{f}: pass'], 'results.txt', 'a') + endif + endfor +enddef + +try + g:LoadLspPlugin() + exe $'source {g:TestName}' + g:StartLangServer() + LspRunTests() +catch + writefile(['FAIL: Tests in ' .. g:TestName .. ' failed with exception ' .. v:exception .. ' at ' .. v:throwpoint], 'results.txt', 'a') +endtry + +qall! + +# vim: shiftwidth=2 softtabstop=2 noexpandtab diff --git a/vim/pack/downloads/opt/lsp/test/rust_tests.vim b/vim/pack/downloads/opt/lsp/test/rust_tests.vim new file mode 100644 index 0000000..6dc2277 --- /dev/null +++ b/vim/pack/downloads/opt/lsp/test/rust_tests.vim @@ -0,0 +1,137 @@ +vim9script +# Unit tests for LSP rust-analyzer client + +source common.vim +source term_util.vim +source screendump.vim + +var lspServers = [{ + filetype: ['rust'], + path: exepath('rust-analyzer'), + args: [] + }] +call LspAddServer(lspServers) +echomsg systemlist($'{lspServers[0].path} --version') + +def g:Test_LspGotoDef() + settagstack(0, {items: []}) + :cd xrust_tests/src + try + silent! edit ./main.rs + deletebufline('%', 1, '$') + g:WaitForServerFileLoad(0) + var lines: list<string> =<< trim END + fn main() { + } + fn foo() { + } + fn bar() { + foo(); + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + cursor(6, 5) + :LspGotoDefinition + assert_equal([3, 4], [line('.'), col('.')]) + :%bw! + finally + :cd ../.. + endtry +enddef + +# Test for :LspCodeAction creating a file in the current directory +def g:Test_LspCodeAction_CreateFile() + :cd xrust_tests/src + try + silent! edit ./main.rs + deletebufline('%', 1, '$') + g:WaitForServerFileLoad(0) + var lines: list<string> =<< trim END + mod foo; + fn main() { + } + END + setline(1, lines) + g:WaitForServerFileLoad(1) + cursor(1, 1) + :LspCodeAction 1 + g:WaitForServerFileLoad(0) + assert_true(filereadable('foo.rs')) + :%bw! + delete('foo.rs') + finally + :cd ../.. + endtry +enddef + +# Test for :LspCodeAction creating a file in a subdirectory +def g:Test_LspCodeAction_CreateFile_Subdir() + :cd xrust_tests/src + try + silent! edit ./main.rs + deletebufline('%', 1, '$') + g:WaitForServerFileLoad(0) + var lines: list<string> =<< trim END + mod baz; + fn main() { + } + END + setline(1, lines) + g:WaitForServerFileLoad(1) + cursor(1, 1) + :LspCodeAction 2 + g:WaitForServerFileLoad(0) + assert_true(filereadable('baz/mod.rs')) + :%bw! + delete('baz', 'rf') + finally + :cd ../.. + endtry +enddef + +# Test for :LspCodeAction renaming a file +def g:Test_LspCodeAction_RenameFile() + :cd xrust_tests/src + try + silent! edit ./main.rs + deletebufline('%', 1, '$') + g:WaitForServerFileLoad(0) + writefile([], 'foobar.rs') + var lines: list<string> =<< trim END + mod foobar; + fn main() { + } + END + setline(1, lines) + g:WaitForServerFileLoad(0) + cursor(1, 5) + :LspRename foobaz + g:WaitForServerFileLoad(0) + assert_true(filereadable('foobaz.rs')) + :%bw! + delete('foobaz.rs') + finally + :cd ../.. + endtry +enddef + +def g:Test_ZZZ_Cleanup() + delete('./xrust_tests', 'rf') +enddef + +# Start the rust-analyzer language server. Returns true on success and false +# on failure. +def g:StartLangServer(): bool + system('cargo new xrust_tests') + :cd xrust_tests/src + var status = false + try + status = g:StartLangServerWithFile('./main.rs') + finally + :cd ../.. + endtry + return status +enddef + +# vim: shiftwidth=2 softtabstop=2 noexpandtab diff --git a/vim/pack/downloads/opt/lsp/test/screendump.vim b/vim/pack/downloads/opt/lsp/test/screendump.vim new file mode 100644 index 0000000..68d3c3f --- /dev/null +++ b/vim/pack/downloads/opt/lsp/test/screendump.vim @@ -0,0 +1,117 @@ +" Functions shared by tests making screen dumps. + +source term_util.vim + +" Skip the rest if there is no terminal feature at all. +if !has('terminal') + finish +endif + +" Read a dump file "fname" and if "filter" exists apply it to the text. +def ReadAndFilter(fname: string, filter: string): list<string> + var contents = readfile(fname) + + if filereadable(filter) + # do this in the bottom window so that the terminal window is unaffected + wincmd j + enew + setline(1, contents) + exe "source " .. filter + contents = getline(1, '$') + enew! + wincmd k + redraw + endif + + return contents +enddef + + +" Verify that Vim running in terminal buffer "buf" matches the screen dump. +" "options" is passed to term_dumpwrite(). +" Additionally, the "wait" entry can specify the maximum time to wait for the +" screen dump to match in msec (default 1000 msec). +" The file name used is "dumps/{filename}.dump". +" +" To ignore part of the dump, provide a "dumps/{filename}.vim" file with +" Vim commands to be applied to both the reference and the current dump, so +" that parts that are irrelevant are not used for the comparison. The result +" is NOT written, thus "term_dumpdiff()" shows the difference anyway. +" +" Optionally an extra argument can be passed which is prepended to the error +" message. Use this when using the same dump file with different options. +" Returns non-zero when verification fails. +func VerifyScreenDump(buf, filename, options, ...) + let reference = 'dumps/' . a:filename . '.dump' + let filter = 'dumps/' . a:filename . '.vim' + let testfile = 'failed/' . a:filename . '.dump' + + let max_loops = get(a:options, 'wait', 1000) / 10 + + " Starting a terminal to make a screendump is always considered flaky. + let g:test_is_flaky = 1 + + " wait for the pending updates to be handled. + call TermWait(a:buf) + + " Redraw to execute the code that updates the screen. Otherwise we get the + " text and attributes only from the internal buffer. + redraw + + if filereadable(reference) + let refdump = ReadAndFilter(reference, filter) + else + " Must be a new screendump, always fail + let refdump = [] + endif + + let did_mkdir = 0 + if !isdirectory('failed') + let did_mkdir = 1 + call mkdir('failed') + endif + + let i = 0 + while 1 + " leave some time for updating the original window + sleep 10m + call delete(testfile) + call term_dumpwrite(a:buf, testfile, a:options) + let testdump = ReadAndFilter(testfile, filter) + if refdump == testdump + call delete(testfile) + if did_mkdir + call delete('failed', 'd') + endif + break + endif + if i == max_loops + " Leave the failed dump around for inspection. + if filereadable(reference) + let msg = 'See dump file difference: call term_dumpdiff("testdir/' .. testfile .. '", "testdir/' .. reference .. '")' + if a:0 == 1 + let msg = a:1 . ': ' . msg + endif + if len(testdump) != len(refdump) + let msg = msg . '; line count is ' . len(testdump) . ' instead of ' . len(refdump) + endif + else + let msg = 'See new dump file: call term_dumpload("testdir/' .. testfile .. '")' + " no point in retrying + let g:run_nr = 10 + endif + for i in range(len(refdump)) + if i >= len(testdump) + break + endif + if testdump[i] != refdump[i] + let msg = msg . '; difference in line ' . (i + 1) . ': "' . testdump[i] . '"' + endif + endfor + call assert_report(msg) + return 1 + endif + let i += 1 + endwhile + return 0 +endfunc diff --git a/vim/pack/downloads/opt/lsp/test/start_tsserver.vim b/vim/pack/downloads/opt/lsp/test/start_tsserver.vim new file mode 100644 index 0000000..3896c70 --- /dev/null +++ b/vim/pack/downloads/opt/lsp/test/start_tsserver.vim @@ -0,0 +1,10 @@ +vim9script +source common.vim +g:LoadLspPlugin() +var lspServers = [{ +filetype: ['typescript', 'javascript'], + path: exepath('typescript-language-server'), + args: ['--stdio'] +}] +g:LspAddServer(lspServers) +g:StartLangServerWithFile('Xtest.ts') diff --git a/vim/pack/downloads/opt/lsp/test/term_util.vim b/vim/pack/downloads/opt/lsp/test/term_util.vim new file mode 100644 index 0000000..7b1779f --- /dev/null +++ b/vim/pack/downloads/opt/lsp/test/term_util.vim @@ -0,0 +1,125 @@ +" Functions about terminal shared by several tests + +" Wrapper around term_wait(). +" The second argument is the minimum time to wait in msec, 10 if omitted. +func TermWait(buf, ...) + let wait_time = a:0 ? a:1 : 10 + call term_wait(a:buf, wait_time) +endfunc + +" Run Vim with "arguments" in a new terminal window. +" By default uses a size of 20 lines and 75 columns. +" Returns the buffer number of the terminal. +" +" Options is a dictionary, these items are recognized: +" "keep_t_u7" - when 1 do not make t_u7 empty (resetting t_u7 avoids clearing +" parts of line 2 and 3 on the display) +" "rows" - height of the terminal window (max. 20) +" "cols" - width of the terminal window (max. 78) +" "statusoff" - number of lines the status is offset from default +" "wait_for_ruler" - if zero then don't wait for ruler to show +" "no_clean" - if non-zero then remove "--clean" from the command +func RunVimInTerminal(arguments, options) + " If Vim doesn't exit a swap file remains, causing other tests to fail. + " Remove it here. + call delete(".swp") + + if exists('$COLORFGBG') + " Clear $COLORFGBG to avoid 'background' being set to "dark", which will + " only be corrected if the response to t_RB is received, which may be too + " late. + let $COLORFGBG = '' + endif + + " Make a horizontal and vertical split, so that we can get exactly the right + " size terminal window. Works only when the current window is full width. + call assert_equal(&columns, winwidth(0)) + split + vsplit + + " Always do this with 256 colors and a light background. + set t_Co=256 background=light + hi Normal ctermfg=NONE ctermbg=NONE + + " Make the window 20 lines high and 75 columns, unless told otherwise or + " 'termwinsize' is set. + let rows = get(a:options, 'rows', 20) + let cols = get(a:options, 'cols', 75) + let statusoff = get(a:options, 'statusoff', 1) + + if get(a:options, 'keep_t_u7', 0) + let reset_u7 = '' + else + let reset_u7 = ' --cmd "set t_u7=" ' + endif + + let cmd = exepath('vim') .. ' -u NONE --clean --not-a-term --cmd "set enc=utf8"'.. reset_u7 .. a:arguments + + if get(a:options, 'no_clean', 0) + let cmd = substitute(cmd, '--clean', '', '') + endif + + let options = #{curwin: 1} + if &termwinsize == '' + let options.term_rows = rows + let options.term_cols = cols + endif + + " Accept other options whose name starts with 'term_'. + call extend(options, filter(copy(a:options), 'v:key =~# "^term_"')) + + let buf = term_start(cmd, options) + + if &termwinsize == '' + " in the GUI we may end up with a different size, try to set it. + if term_getsize(buf) != [rows, cols] + call term_setsize(buf, rows, cols) + endif + call assert_equal([rows, cols], term_getsize(buf)) + else + let rows = term_getsize(buf)[0] + let cols = term_getsize(buf)[1] + endif + + call TermWait(buf) + + if get(a:options, 'wait_for_ruler', 1) + " Wait for "All" or "Top" of the ruler to be shown in the last line or in + " the status line of the last window. This can be quite slow (e.g. when + " using valgrind). + " If it fails then show the terminal contents for debugging. + try + call g:WaitFor({-> len(term_getline(buf, rows)) >= cols - 1 || len(term_getline(buf, rows - statusoff)) >= cols - 1}) + catch /timed out after/ + let lines = map(range(1, rows), {key, val -> term_getline(buf, val)}) + call assert_report('RunVimInTerminal() failed, screen contents: ' . join(lines, "<NL>")) + endtry + endif + + return buf +endfunc + +" Stop a Vim running in terminal buffer "buf". +func StopVimInTerminal(buf, kill = 1) + call assert_equal("running", term_getstatus(a:buf)) + + " Wait for all the pending updates to terminal to complete + call TermWait(a:buf) + + " CTRL-O : works both in Normal mode and Insert mode to start a command line. + " In Command-line it's inserted, the CTRL-U removes it again. + call term_sendkeys(a:buf, "\<C-O>:\<C-U>qa!\<cr>") + + " Wait for all the pending updates to terminal to complete + call TermWait(a:buf) + + " Wait for the terminal to end. + call WaitForAssert({-> assert_equal("finished", term_getstatus(a:buf))}) + + " If the buffer still exists forcefully wipe it. + if a:kill && bufexists(a:buf) + exe a:buf .. 'bwipe!' + endif +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/vim/pack/downloads/opt/lsp/test/tsserver_tests.vim b/vim/pack/downloads/opt/lsp/test/tsserver_tests.vim new file mode 100644 index 0000000..99e2836 --- /dev/null +++ b/vim/pack/downloads/opt/lsp/test/tsserver_tests.vim @@ -0,0 +1,43 @@ +vim9script +# Unit tests for Vim Language Server Protocol (LSP) typescript client + +source common.vim +source term_util.vim +source screendump.vim + +var lspServers = [{ + filetype: ['typescript', 'javascript'], + path: exepath('typescript-language-server'), + args: ['--stdio'] + }] +call LspAddServer(lspServers) +echomsg systemlist($'{lspServers[0].path} --version') + +# Test for auto-completion. Make sure that only keywords that matches with the +# keyword before the cursor are shown. +# def g:Test_LspCompletion1() +# var lines =<< trim END +# const http = require('http') +# http.cr +# END +# writefile(lines, 'Xcompletion1.js') +# var buf = g:RunVimInTerminal('--cmd "silent so start_tsserver.vim" Xcompletion1.js', {rows: 10, wait_for_ruler: 1}) +# sleep 5 +# term_sendkeys(buf, "GAe") +# g:TermWait(buf) +# g:VerifyScreenDump(buf, 'Test_tsserver_completion_1', {}) +# term_sendkeys(buf, "\<BS>") +# g:TermWait(buf) +# g:VerifyScreenDump(buf, 'Test_tsserver_completion_2', {}) +# +# g:StopVimInTerminal(buf) +# delete('Xcompletion1.js') +# enddef + +# Start the typescript language server. Returns true on success and false on +# failure. +def g:StartLangServer(): bool + return g:StartLangServerWithFile('Xtest.ts') +enddef + +# vim: shiftwidth=2 softtabstop=2 noexpandtab |