summaryrefslogtreecommitdiff
path: root/vim/pack/downloads/opt/lsp/test
diff options
context:
space:
mode:
Diffstat (limited to 'vim/pack/downloads/opt/lsp/test')
-rw-r--r--vim/pack/downloads/opt/lsp/test/clangd_offsetencoding.vim478
-rw-r--r--vim/pack/downloads/opt/lsp/test/clangd_tests.vim1782
-rw-r--r--vim/pack/downloads/opt/lsp/test/common.vim166
-rw-r--r--vim/pack/downloads/opt/lsp/test/dumps/Test_tsserver_completion_1.dump10
-rw-r--r--vim/pack/downloads/opt/lsp/test/dumps/Test_tsserver_completion_2.dump10
-rw-r--r--vim/pack/downloads/opt/lsp/test/gopls_tests.vim121
-rw-r--r--vim/pack/downloads/opt/lsp/test/markdown_tests.vim305
-rw-r--r--vim/pack/downloads/opt/lsp/test/not_lspserver_related_tests.vim14
-rw-r--r--vim/pack/downloads/opt/lsp/test/run_tests.cmd17
-rwxr-xr-xvim/pack/downloads/opt/lsp/test/run_tests.sh51
-rw-r--r--vim/pack/downloads/opt/lsp/test/runner.vim55
-rw-r--r--vim/pack/downloads/opt/lsp/test/rust_tests.vim137
-rw-r--r--vim/pack/downloads/opt/lsp/test/screendump.vim117
-rw-r--r--vim/pack/downloads/opt/lsp/test/start_tsserver.vim10
-rw-r--r--vim/pack/downloads/opt/lsp/test/term_util.vim125
-rw-r--r--vim/pack/downloads/opt/lsp/test/tsserver_tests.vim43
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
+ [
+ '&nbsp;&nbsp;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