2 # Unit tests for Vim Language Server Protocol (LSP) clangd client
6 var lspOpts = {autoComplete: false, highlightDiagInline: true}
7 g:LspOptionsSet(lspOpts)
9 g:LSPTest_modifyDiags = false
12 filetype: ['c', 'cpp'],
13 path: (exepath('clangd-15') ?? exepath('clangd')),
14 args: ['--background-index', '--clang-tidy'],
15 initializationOptions: { clangdFileStatus: true },
16 customNotificationHandlers: {
17 'textDocument/clangd.fileStatus': (lspserver: dict<any>, reply: dict<any>) => {
18 g:LSPTest_customNotificationHandlerReplied = true
21 processDiagHandler: (diags: list<dict<any>>) => {
22 if g:LSPTest_modifyDiags != true
26 return diags->map((ix, diag) => {
27 diag.message = $'this is overridden'
32 call LspAddServer(lspServers)
34 var clangdVerDetail = systemlist($'{shellescape(lspServers[0].path)} --version')
35 var clangdVerMajor = clangdVerDetail->matchstr('.*version \d\+\..*')->substitute('.* \(\d\+\)\..*', '\1', 'g')->str2nr()
36 echomsg clangdVerDetail
39 # Test for formatting a file using LspFormat
40 def g:Test_LspFormat()
41 :silent! edit XLspFormat.c
43 setline(1, [' int i;', ' int j;'])
46 assert_equal(['int i;', 'int j;'], getline(1, '$'))
48 deletebufline('', 1, '$')
49 setline(1, ['int f1(int i)', '{', 'int j = 10; return j;', '}'])
52 assert_equal(['int f1(int i) {', ' int j = 10;', ' return j;', '}'],
55 deletebufline('', 1, '$')
56 setline(1, ['', 'int i;'])
59 assert_equal(['', 'int i;'], getline(1, '$'))
61 deletebufline('', 1, '$')
62 setline(1, [' int i;'])
65 assert_equal(['int i;'], getline(1, '$'))
67 deletebufline('', 1, '$')
68 setline(1, [' int i; '])
71 assert_equal(['int i;'], getline(1, '$'))
73 deletebufline('', 1, '$')
74 setline(1, ['int i;', '', '', ''])
77 assert_equal(['int i;'], getline(1, '$'))
79 deletebufline('', 1, '$')
80 setline(1, ['int f1(){int x;int y;x=1;y=2;return x+y;}'])
83 var expected: list<string> =<< trim END
92 assert_equal(expected, getline(1, '$'))
94 deletebufline('', 1, '$')
95 setline(1, ['', '', '', ''])
98 assert_equal([''], getline(1, '$'))
100 deletebufline('', 1, '$')
101 var lines: list<string> =<< trim END
104 for (i = 1; i < 10; i++) { j++; }
105 for (j = 1; j < 10; j++) { i++; }
111 expected =<< trim END
114 for (i = 1; i < 10; i++) { j++; }
115 for (j = 1; j < 10; j++) {
120 assert_equal(expected, getline(1, '$'))
124 assert_equal('', execute('LspFormat'))
126 # file without an LSP server
128 assert_equal('Error: Language server for "raku" file type supporting "documentFormatting" feature is not found',
129 execute('LspFormat')->split("\n")[0])
134 # Test for formatting a file using 'formatexpr'
135 def g:Test_LspFormatExpr()
136 :silent! edit XLspFormat.c
138 setlocal formatexpr=lsp#lsp#FormatExpr()
139 setline(1, [' int i;', ' int j;'])
142 assert_equal(['int i;', 'int j;'], getline(1, '$'))
145 deletebufline('', 1, '$')
149 assert_equal([''], getline(1, '$'))
155 # Test for :LspShowReferences - showing all the references to a symbol in a
157 def g:Test_LspShowReferences()
158 :silent! edit XshowRefs.c
160 var lines: list<string> =<< trim END
178 var bnr: number = bufnr()
181 assert_equal('quickfix', getwinvar(winnr('$'), '&buftype'))
182 var loclist: list<dict<any>> = getloclist(0)
183 assert_equal(bnr, loclist[0].bufnr)
184 assert_equal(3, loclist->len())
185 assert_equal([4, 6], [loclist[0].lnum, loclist[0].col])
186 assert_equal([5, 2], [loclist[1].lnum, loclist[1].col])
187 assert_equal([6, 6], [loclist[2].lnum, loclist[2].col])
191 assert_equal(1, getloclist(0)->len())
192 loclist = getloclist(0)
193 assert_equal([1, 5], [loclist[0].lnum, loclist[0].col])
196 # Test for opening in qf list
197 g:LspOptionsSet({ useQuickfixForLocations: true })
201 assert_equal('quickfix', getwinvar(winnr('$'), '&buftype'))
203 var qfl: list<dict<any>> = getqflist()
204 assert_equal(3, qfl->len())
205 assert_equal(bufnr(), qfl[0].bufnr)
206 assert_equal([4, 6], [qfl[0].lnum, qfl[0].col])
207 assert_equal([5, 2], [qfl[1].lnum, qfl[1].col])
208 assert_equal([6, 6], [qfl[2].lnum, qfl[2].col])
211 assert_equal(1, getqflist()->len())
213 assert_equal([1, 5], [qfl[0].lnum, qfl[0].col])
215 g:LspOptionsSet({ useQuickfixForLocations: false })
217 # Test for maintaining buffer focus
218 g:LspOptionsSet({ keepFocusInReferences: false })
220 assert_equal('', getwinvar(0, '&buftype'))
222 g:LspOptionsSet({ keepFocusInReferences: true })
224 # Test for LspPeekReferences
226 # Opening the preview window with an unsaved buffer displays the "E37: No
227 # write since last change" error message. To disable this message, mark the
228 # buffer as not modified.
233 var ids = popup_list()
234 assert_equal(2, ids->len())
235 var filePopupAttrs = ids[0]->popup_getoptions()
236 var refPopupAttrs = ids[1]->popup_getoptions()
237 assert_match('XshowRefs', filePopupAttrs.title)
238 assert_equal('Symbol References', refPopupAttrs.title)
239 assert_equal(10, line('.', ids[0]))
240 assert_equal(3, line('$', ids[1]))
241 feedkeys("jj\<CR>", 'xt')
242 assert_equal(12, line('.'))
243 assert_equal([], popup_list())
249 assert_equal('', execute('LspShowReferences'))
251 # file without an LSP server
253 assert_equal('Error: Language server for "raku" file type supporting "references" feature is not found',
254 execute('LspShowReferences')->split("\n")[0])
259 # Test for LSP diagnostics
261 :silent! edit XLspDiag.c
263 var lines: list<string> =<< trim END
274 g:WaitForServerFileLoad(1)
275 var bnr: number = bufnr()
278 var qfl: list<dict<any>> = getloclist(0)
279 assert_equal('quickfix', getwinvar(winnr('$'), '&buftype'))
280 assert_equal(bnr, qfl[0].bufnr)
281 assert_equal(3, qfl->len())
282 assert_equal([3, 14, 'E'], [qfl[0].lnum, qfl[0].col, qfl[0].type])
283 assert_equal([5, 2, 'W'], [qfl[1].lnum, qfl[1].col, qfl[1].type])
284 assert_equal([7, 2, 'W'], [qfl[2].lnum, qfl[2].col, qfl[2].type])
286 g:LspOptionsSet({showDiagInPopup: false})
288 var output = execute('LspDiagCurrent')->split("\n")
289 assert_equal('Warn: No diagnostic messages found for current line', output[0])
291 assert_equal([3, 14], [line('.'), col('.')])
292 output = execute('LspDiagCurrent')->split("\n")
293 assert_equal("Expected ';' at end of declaration (fix available)", output[0])
296 assert_equal([3, 14], [line('.'), col('.')])
298 assert_equal([5, 2], [line('.'), col('.')])
300 assert_equal([7, 2], [line('.'), col('.')])
301 output = execute('LspDiagNext')->split("\n")
302 assert_equal('Warn: No more diagnostics found', output[0])
306 output = execute('LspDiagPrev')->split("\n")
307 assert_equal('Warn: No more diagnostics found', output[0])
309 # Test for maintaining buffer focus
310 g:LspOptionsSet({ keepFocusInDiags: false })
312 assert_equal('', getwinvar(0, '&buftype'))
314 g:LspOptionsSet({ keepFocusInDiags: true })
316 # :[count]LspDiagNext
319 assert_equal([5, 2], [line('.'), col('.')])
321 assert_equal([7, 2], [line('.'), col('.')])
322 output = execute(':2LspDiagNext')->split("\n")
323 assert_equal('Warn: No more diagnostics found', output[0])
325 # :[count]LspDiagPrev
328 assert_equal([3, 14], [line('.'), col('.')])
329 output = execute(':4LspDiagPrev')->split("\n")
330 assert_equal('Warn: No more diagnostics found', output[0])
333 setline(1, ['void blueFunc()', '{', '}'])
335 output = execute('LspDiagShow')->split("\n")
336 assert_match('Warn: No diagnostic messages found for', output[0])
337 g:LspOptionsSet({showDiagInPopup: true})
343 # Test for LSP diagnostics handler
344 def g:Test_LspProcessDiagHandler()
345 g:LSPTest_modifyDiags = true
346 g:LspOptionsSet({showDiagInPopup: false})
348 :silent! edit XLspProcessDiag.c
350 var lines: list<string> =<< trim END
357 g:WaitForServerFileLoad(1)
362 assert_equal([3, 14], [line('.'), col('.')])
364 var output = execute('LspDiagCurrent')->split("\n")
365 assert_equal("this is overridden", output[0])
367 g:LspOptionsSet({showDiagInPopup: true})
368 g:LSPTest_modifyDiags = false
372 # Diag location list should be automatically updated when the list of diags
374 def g:Test_DiagLocListAutoUpdate()
375 :silent! edit XdiagLocListAutoUpdate.c
377 setloclist(0, [], 'f')
378 var lines: list<string> =<< trim END
384 g:WaitForServerFileLoad(1)
386 var d = lsp#diag#GetDiagsForBuf()[0]
387 assert_equal({start: {line: 0, character: 5}, end: {line: 0, character: 6}},
391 assert_equal(1, line('$'))
396 var l = lsp#diag#GetDiagsForBuf()
397 assert_equal({start: {line: 0, character: 5}, end: {line: 0, character: 6}},
399 assert_equal({start: {line: 1, character: 5}, end: {line: 1, character: 6}},
402 assert_equal(2, line('$'))
404 deletebufline('', 1, '$')
407 assert_equal([], lsp#diag#GetDiagsForBuf())
409 assert_equal([''], getline(1, '$'))
412 setloclist(0, [], 'f')
416 # Test that the client have been able to configure the server to speak utf-32
417 def g:Test_UnicodeColumnCalc()
418 :silent! edit XUnicodeColumn.c
420 var lines: list<string> =<< trim END
435 cursor(5, 1) # 😊😊😊😊 = a;
438 execute('LspGotoDefinition')->split("\n"))
439 assert_equal([2, 12], [line('.'), col('.')])
441 cursor(8, 1) # b = a;
444 execute('LspGotoDefinition')->split("\n"))
445 assert_equal([2, 12], [line('.'), col('.')])
450 # Test for multiple LSP diagnostics on the same line
451 def g:Test_LspDiag_Multi()
452 :silent! edit XLspDiagMulti.c
455 var bnr: number = bufnr()
457 var lines =<< trim END
464 # TODO: Waiting count doesn't include Warning, Info, and Hint diags
465 if clangdVerMajor > 14
466 g:WaitForServerFileLoad(3)
468 g:WaitForServerFileLoad(2)
471 var qfl: list<dict<any>> = getloclist(0)
472 assert_equal('quickfix', getwinvar(winnr('$'), '&buftype'))
473 assert_equal(bnr, qfl[0].bufnr)
474 assert_equal(3, qfl->len())
475 if clangdVerMajor > 14
476 assert_equal([1, 5, 'E'], [qfl[0].lnum, qfl[0].col, qfl[0].type])
478 assert_equal([1, 5, 'W'], [qfl[0].lnum, qfl[0].col, qfl[0].type])
480 assert_equal([1, 9, 'E'], [qfl[1].lnum, qfl[1].col, qfl[1].type])
481 assert_equal([2, 9, 'E'], [qfl[2].lnum, qfl[2].col, qfl[2].type])
486 assert_equal('', execute('LspDiagPrev'))
487 assert_equal([1, 9], [line('.'), col('.')])
489 assert_equal('', execute('LspDiagPrev'))
490 assert_equal([1, 5], [line('.'), col('.')])
492 var output = execute('LspDiagPrev')->split("\n")
493 assert_equal('Warn: No more diagnostics found', output[0])
496 assert_equal('', execute('LspDiagFirst'))
497 assert_equal([1, 5], [line('.'), col('.')])
498 assert_equal('', execute('LspDiagNext'))
499 assert_equal([1, 9], [line('.'), col('.')])
501 assert_equal('', execute('LspDiagLast'))
502 assert_equal([2, 9], [line('.'), col('.')])
505 # Test for :LspDiagHere on a line with multiple diagnostics
508 assert_equal([1, 5], [line('.'), col('.')])
509 var ids = popup_list()
510 assert_equal(1, ids->len())
511 assert_match('Incompatible pointer to integer', getbufline(ids[0]->winbufnr(), 1, '$')[0])
515 assert_equal([1, 9], [line('.'), col('.')])
517 assert_equal(1, ids->len())
518 assert_match('Initializer element is not', getbufline(ids[0]->winbufnr(), 1, '$')[0])
521 # Line without diagnostics
523 output = execute('LspDiagHere')->split("\n")
524 assert_equal('Warn: No more diagnostics found on this line', output[0])
526 g:LspOptionsSet({showDiagInPopup: false})
529 output = execute('LspDiagCurrent')->split('\n')
530 assert_match('Incompatible pointer to integer', output[0])
532 for i in range(6, 12)
534 output = execute('LspDiagCurrent')->split('\n')
535 assert_match('Initializer element is not ', output[0])
537 g:LspOptionsSet({showDiagInPopup: true})
539 # Check for exact diag ":LspDiagCurrent!"
540 g:LspOptionsSet({showDiagInPopup: false})
543 output = execute('LspDiagCurrent!')->split('\n')
544 assert_equal('Warn: No diagnostic messages found for current position', output[0])
548 output = execute('LspDiagCurrent!')->split('\n')
549 assert_match('Incompatible pointer to integer', output[0])
553 output = execute('LspDiagCurrent!')->split('\n')
554 assert_equal('Warn: No diagnostic messages found for current position', output[0])
557 for i in range(9, 11)
559 output = execute('LspDiagCurrent!')->split('\n')
560 assert_match('Initializer element is not ', output[0])
562 for i in range(12, 12)
564 output = execute('LspDiagCurrent!')->split('\n')
565 assert_equal('Warn: No diagnostic messages found for current position', output[0])
568 g:LspOptionsSet({showDiagInPopup: true})
570 # :[count]LspDiagNext
571 g:LspOptionsSet({showDiagInPopup: false})
574 assert_equal([1, 9], [line('.'), col('.')])
576 assert_equal([2, 9], [line('.'), col('.')])
577 output = execute(':2LspDiagNext')->split("\n")
578 assert_equal('Warn: No more diagnostics found', output[0])
582 assert_equal([2, 9], [line('.'), col('.')])
583 g:LspOptionsSet({showDiagInPopup: true})
585 # :[count]LspDiagPrev
586 g:LspOptionsSet({showDiagInPopup: false})
589 assert_equal('Warn: No more diagnostics found', output[0])
592 assert_equal([1, 9], [line('.'), col('.')])
594 assert_equal([1, 5], [line('.'), col('.')])
595 output = execute(':2LspDiagPrev')->split("\n")
596 assert_equal('Warn: No more diagnostics found', output[0])
600 assert_equal([1, 5], [line('.'), col('.')])
601 g:LspOptionsSet({showDiagInPopup: true})
606 # Test for highlight diag inline
607 def g:Test_LspHighlightDiagInline()
608 :silent! edit XLspHighlightDiag.c
619 # TODO: Waiting count doesn't include Warning, Info, and Hint diags
622 var props = prop_list(1)
623 assert_equal(0, props->len())
625 assert_equal(0, props->len())
627 assert_equal(2, props->len())
629 {'id': 0, 'col': 12, 'type_bufnr': 0, 'end': 1, 'type': 'LspDiagInlineInfo', 'length': 3, 'start': 1},
630 {'id': 0, 'col': 16, 'type_bufnr': 0, 'end': 1, 'type': 'LspDiagInlineError', 'length': 3, 'start': 1}
633 assert_equal(0, props->len())
635 assert_equal(1, props->len())
636 assert_equal([{'id': 0, 'col': 5, 'type_bufnr': 0, 'end': 1, 'type': 'LspDiagInlineError', 'length': 6, 'start': 1}], props)
638 assert_equal(0, props->len())
643 # Test for :LspCodeAction
644 def g:Test_LspCodeAction()
645 silent! edit XLspCodeAction.c
647 var lines: list<string> =<< trim END
655 g:WaitForServerFileLoad(0)
659 assert_equal("\tcount = 20;", getline(4))
661 setline(4, "\tcount = 20:")
665 assert_equal("\tcount = 20:", getline(4))
669 assert_equal("\tcount = 20:", getline(4))
673 assert_equal("\tcount = 20;", getline(4))
676 # pattern and string prefix
677 silent! edit XLspCodeActionPattern.c
679 var lines2: list<string> =<< trim END
688 g:WaitForServerFileLoad(0)
692 assert_equal("\tif (count == 1) {", getline(4))
694 setline(4, "\tif (count = 1) {")
697 :LspCodeAction /paren
698 assert_equal("\tif ((count = 1)) {", getline(4))
700 setline(4, "\tif (count = 1) {")
703 :LspCodeAction NON_EXISTING_PREFIX
704 assert_equal("\tif (count = 1) {", getline(4))
707 :LspCodeAction /NON_EXISTING_REGEX
708 assert_equal("\tif (count = 1) {", getline(4))
712 assert_equal('', execute('LspCodeAction'))
714 # file without an LSP server
716 assert_equal('Error: Language server for "raku" file type supporting "codeAction" feature is not found',
717 execute('LspCodeAction')->split("\n")[0])
722 # Test for :LspRename
723 def g:Test_LspRename()
724 silent! edit XLspRename.c
726 var lines: list<string> =<< trim END
740 g:WaitForServerFileLoad(0)
744 feedkeys(":LspRename\<CR>er\<CR>", "xt")
746 var expected: list<string> =<< trim END
759 assert_equal(expected, getline(1, '$'))
764 var expected2: list<string> =<< trim END
765 void F1(int countvar)
777 assert_equal(expected2, getline(1, '$'))
782 assert_equal('', execute('LspRename'))
784 # file without an LSP server
786 assert_equal('Error: Language server for "raku" file type supporting "rename" feature is not found',
787 execute('LspRename')->split("\n")[0])
792 # Test for :LspSelectionExpand and :LspSelectionShrink
793 def g:Test_LspSelection()
794 silent! edit XLspSelection.c
796 var lines: list<string> =<< trim END
797 void fnSel(int count)
800 for (i = 0; i < 10; i++) {
807 g:WaitForServerFileLoad(0)
808 # start a block-wise visual mode, LspSelectionExpand should change this to
809 # a characterwise visual mode.
810 exe "normal! 1G\<C-V>G\"_y"
816 assert_equal('v', visualmode())
817 assert_equal([2, 8], [line("'<"), line("'>")])
818 # start a linewise visual mode, LspSelectionExpand should change this to
819 # a characterwise visual mode.
820 exe "normal! 3GViB\"_y"
826 assert_equal('v', visualmode())
827 assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
829 # Expand the visual selection
830 xnoremap <silent> le <Cmd>LspSelectionExpand<CR>
831 xnoremap <silent> ls <Cmd>LspSelectionShrink<CR>
834 assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
837 assert_equal([5, 8, 5, 14], [line("'<"), col("'<"), line("'>"), col("'>")])
840 assert_equal([4, 30, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
843 assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
846 assert_equal([2, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
848 normal vleleleleleley
849 assert_equal([1, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
851 normal vleleleleleleley
852 assert_equal([1, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
854 # Shrink the visual selection
857 assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
860 assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
863 assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
866 assert_equal([5, 8, 5, 14], [line("'<"), col("'<"), line("'>"), col("'>")])
869 assert_equal([4, 30, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
871 normal vlelelelelelsy
872 assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
874 normal vlelelelelelelsy
875 assert_equal([2, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
882 assert_equal('', execute('LspSelectionExpand'))
884 # file without an LSP server
886 assert_equal('Error: Language server for "raku" file type supporting "selectionRange" feature is not found',
887 execute('LspSelectionExpand')->split("\n")[0])
892 # Test for :LspGotoDefinition, :LspGotoDeclaration and :LspGotoImpl
893 def g:Test_LspGotoSymbol()
894 settagstack(0, {items: []})
895 silent! edit XLspGotoSymbol.cpp
897 var lines: list<string> =<< trim END
900 virtual void print();
907 class derived : public base {
922 g:WaitForServerFileLoad(0)
926 assert_equal([3, 19], [line('.'), col('.')])
928 assert_equal([21, 6], [line('.'), col('.')])
929 assert_equal(1, winnr('$'))
932 assert_equal([6, 12], [line('.'), col('.')])
934 assert_equal([21, 6], [line('.'), col('.')])
935 assert_equal(1, winnr('$'))
938 :topleft LspGotoDefinition
939 assert_equal([6, 12], [line('.'), col('.')])
940 assert_equal([1, 2], [winnr(), winnr('$')])
943 assert_equal([21, 6], [line('.'), col('.')])
945 :tab LspGotoDefinition
946 assert_equal([6, 12], [line('.'), col('.')])
947 assert_equal([2, 2, 1], [tabpagenr(), tabpagenr('$'), winnr('$')])
950 assert_equal([21, 6], [line('.'), col('.')])
955 assert_equal([1, 7], [line('.'), col('.')])
957 assert_equal([21, 2], [line('.'), col('.')])
962 assert_equal([12, 11], [line('.'), col('.')])
964 assert_equal([21, 6], [line('.'), col('.')])
966 # FIXME: The following tests are failing in Github CI. Comment out for now.
972 var m = execute('messages')->split("\n")
973 assert_equal('symbol declaration is not found', m[1])
976 m = execute('messages')->split("\n")
977 assert_equal('symbol definition is not found', m[1])
980 m = execute('messages')->split("\n")
981 assert_equal('symbol implementation is not found', m[1])
985 # Test for LspPeekDeclaration
989 var plist = popup_list()
990 assert_true(1, plist->len())
991 assert_equal(bnum, plist[0]->winbufnr())
992 assert_equal(3, line('.', plist[0]))
994 # tag stack should not be changed
995 assert_fails("normal! \<C-t>", 'E555:')
997 # Test for LspPeekDefinition
1000 assert_true(1, plist->len())
1001 assert_equal(bnum, plist[0]->winbufnr())
1002 assert_equal(6, line('.', plist[0]))
1004 # tag stack should not be changed
1005 assert_fails("normal! \<C-t>", 'E555:')
1007 # FIXME: :LspPeekTypeDef and :LspPeekImpl are supported only with clang-14.
1008 # This clangd version is not available in Github CI.
1013 assert_equal('', execute('LspGotoDefinition'))
1014 assert_equal('', execute('LspGotoDeclaration'))
1015 assert_equal('', execute('LspGotoImpl'))
1017 # file without an LSP server
1019 assert_equal('Error: Language server for "raku" file type supporting "definition" feature is not found',
1020 execute('LspGotoDefinition')->split("\n")[0])
1021 assert_equal('Error: Language server for "raku" file type supporting "declaration" feature is not found',
1022 execute('LspGotoDeclaration')->split("\n")[0])
1023 assert_equal('Error: Language server for "raku" file type supporting "implementation" feature is not found',
1024 execute('LspGotoImpl')->split("\n")[0])
1029 # Test for :LspHighlight
1030 def g:Test_LspHighlight()
1031 silent! edit XLspHighlight.c
1033 var lines: list<string> =<< trim END
1044 g:WaitForServerFileLoad(0)
1047 var expected: dict<any>
1048 expected = {id: 0, col: 13, end: 1, type: 'LspTextRef', length: 3, start: 1}
1049 expected.type_bufnr = 0
1050 assert_equal([expected], prop_list(1))
1051 expected = {id: 0, col: 11, end: 1, type: 'LspReadRef', length: 3, start: 1}
1052 expected.type_bufnr = 0
1053 assert_equal([expected], prop_list(3))
1054 expected = {id: 0, col: 3, end: 1, type: 'LspWriteRef', length: 3, start: 1}
1055 expected.type_bufnr = 0
1056 assert_equal([expected], prop_list(4))
1058 assert_equal([], prop_list(1))
1059 assert_equal([], prop_list(3))
1060 assert_equal([], prop_list(4))
1062 cursor(5, 3) # if (arg == 2) {
1063 var output = execute('LspHighlight')->split("\n")
1064 assert_equal('Warn: No highlight for the current position', output[0])
1068 # Test for :LspHover
1069 def g:Test_LspHover()
1070 silent! edit XLspHover.c
1072 var lines: list<string> =<< trim END
1086 if clangdVerMajor > 14
1087 g:WaitForServerFileLoad(1)
1089 g:WaitForServerFileLoad(0)
1092 var output = execute(':LspHover')->split("\n")
1093 assert_equal([], output)
1094 var p: list<number> = popup_list()
1095 assert_equal(1, p->len())
1096 assert_equal(['### function `f1` ', '', '---', '→ `int` ', 'Parameters: ', '- `int a`', '', '---', '```cpp', 'int f1(int a)', '```'], getbufline(winbufnr(p[0]), 1, '$'))
1099 output = execute(':LspHover')->split("\n")
1100 assert_equal('Warn: No documentation found for current keyword', output[0])
1101 output = execute(':silent LspHover')->split("\n")
1102 assert_equal([], output)
1103 assert_equal([], popup_list())
1105 # Show current diagnostic as to open another popup.
1106 # Then we can test that LspHover closes all existing popups
1109 assert_equal(1, popup_list()->len())
1111 assert_equal(1, popup_list()->len())
1117 # Test for :LspShowSignature
1118 def g:Test_LspShowSignature()
1119 silent! edit XLspShowSignature.c
1121 var lines: list<string> =<< trim END
1122 int MyFunc(int a, int b)
1133 g:WaitForServerFileLoad(2)
1136 var p: list<number> = popup_list()
1137 var bnr: number = winbufnr(p[0])
1138 assert_equal(1, p->len())
1139 assert_equal(['MyFunc(int a, int b) -> int'], getbufline(bnr, 1, '$'))
1140 var expected: dict<any>
1141 expected = {id: 0, col: 8, end: 1, type: 'signature', length: 5, start: 1}
1142 expected.type_bufnr = bnr
1143 assert_equal([expected], prop_list(1, {bufnr: bnr}))
1146 setline(line('.'), ' MyFunc(10, ')
1150 bnr = winbufnr(p[0])
1151 assert_equal(1, p->len())
1152 assert_equal(['MyFunc(int a, int b) -> int'], getbufline(bnr, 1, '$'))
1153 expected = {id: 0, col: 15, end: 1, type: 'signature', length: 5, start: 1}
1154 expected.type_bufnr = bnr
1155 assert_equal([expected], prop_list(1, {bufnr: bnr}))
1160 # Test for :LspSymbolSearch
1161 def g:Test_LspSymbolSearch()
1162 silent! edit XLspSymbolSearch.c
1164 var lines: list<string> =<< trim END
1165 void lsptest_funcA()
1169 void lsptest_funcB()
1173 void lsptest_funcC()
1178 g:WaitForServerFileLoad(0)
1181 feedkeys(":LspSymbolSearch lsptest_funcB\<CR>\<CR>", "xt")
1182 assert_equal([5, 6], [line('.'), col('.')])
1185 feedkeys(":LspSymbolSearch lsptest_func\<CR>\<Down>\<Down>\<CR>", "xt")
1186 assert_equal([9, 6], [line('.'), col('.')])
1189 feedkeys(":LspSymbolSearch lsptest_funcA\<CR>\<BS>B\<CR>", "xt")
1190 assert_equal([5, 6], [line('.'), col('.')])
1192 var output = execute(':LspSymbolSearch lsptest_nonexist')->split("\n")
1193 assert_equal('Warn: Symbol "lsptest_nonexist" is not found', output[0])
1198 # Test for :LspIncomingCalls
1199 def g:Test_LspIncomingCalls()
1200 silent! edit XLspIncomingCalls.c
1202 var lines: list<string> =<< trim END
1203 void xFuncIncoming(void)
1207 void aFuncIncoming(void)
1212 void bFuncIncoming(void)
1218 g:WaitForServerFileLoad(0)
1221 assert_equal([1, 2], [winnr(), winnr('$')])
1222 var l = getline(1, '$')
1223 assert_equal('# Incoming calls to "xFuncIncoming"', l[0])
1224 assert_match('- xFuncIncoming (XLspIncomingCalls.c \[.*\])', l[1])
1225 assert_match(' + aFuncIncoming (XLspIncomingCalls.c \[.*\])', l[2])
1226 assert_match(' + bFuncIncoming (XLspIncomingCalls.c \[.*\])', l[3])
1230 # Test for :LspOutline
1231 def g:Test_LspOutline()
1232 silent! edit XLspOutline.c
1234 var lines: list<string> =<< trim END
1235 void aFuncOutline(void)
1239 void bFuncOutline(void)
1244 g:WaitForServerFileLoad(0)
1245 var winid = win_getid()
1247 assert_equal(2, winnr('$'))
1248 var bnum = winbufnr(winid + 1)
1249 assert_equal('LSP-Outline', bufname(bnum))
1250 assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
1252 # Validate position vert topleft
1253 assert_equal(['row', [['leaf', winid + 1], ['leaf', winid]]], winlayout())
1255 # Validate default width is 20
1256 assert_equal(20, winwidth(winid + 1))
1258 execute $':{bnum}bw'
1260 # Validate position vert botright
1261 g:LspOptionsSet({ outlineOnRight: true })
1263 assert_equal(2, winnr('$'))
1264 bnum = winbufnr(winid + 2)
1265 assert_equal('LSP-Outline', bufname(bnum))
1266 assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
1267 assert_equal(['row', [['leaf', winid], ['leaf', winid + 2]]], winlayout())
1268 g:LspOptionsSet({ outlineOnRight: false })
1269 execute $':{bnum}bw'
1271 # Validate <mods> position botright (below)
1272 :botright LspOutline
1273 assert_equal(2, winnr('$'))
1274 bnum = winbufnr(winid + 3)
1275 assert_equal('LSP-Outline', bufname(bnum))
1276 assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
1277 assert_equal(['col', [['leaf', winid], ['leaf', winid + 3]]], winlayout())
1278 execute $':{bnum}bw'
1280 # Validate that outlineWinSize works for LspOutline
1281 g:LspOptionsSet({ outlineWinSize: 40 })
1283 assert_equal(2, winnr('$'))
1284 bnum = winbufnr(winid + 4)
1285 assert_equal('LSP-Outline', bufname(bnum))
1286 assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
1287 assert_equal(40, winwidth(winid + 4))
1288 execute $':{bnum}bw'
1289 g:LspOptionsSet({ outlineWinSize: 20 })
1291 # Validate that <count> works for LspOutline
1293 assert_equal(2, winnr('$'))
1294 bnum = winbufnr(winid + 5)
1295 assert_equal('LSP-Outline', bufname(bnum))
1296 assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
1297 assert_equal(37, winwidth(winid + 5))
1298 execute $':{bnum}bw'
1303 # Test for setting the 'tagfunc'
1304 def g:Test_LspTagFunc()
1305 var lines: list<string> =<< trim END
1320 writefile(lines, 'Xtagfunc.c')
1321 :silent! edit Xtagfunc.c
1322 g:WaitForServerFileLoad(1)
1323 :setlocal tagfunc=lsp#lsp#TagFunc
1325 :exe "normal \<C-]>"
1326 assert_equal([11, 6], [line('.'), col('.')])
1328 assert_fails('exe "normal \<C-]>"', 'E433: No tags file')
1332 delete('Xtagfunc.c')
1335 # Test for the LspDiagsUpdated autocmd
1336 def g:Test_LspDiagsUpdated_Autocmd()
1338 autocmd_add([{event: 'User', pattern: 'LspDiagsUpdated', cmd: 'g:LspAutoCmd = g:LspAutoCmd + 1'}])
1339 silent! edit XLspDiagsAutocmd.c
1341 var lines: list<string> =<< trim END
1342 void aFuncDiag(void)
1348 g:WaitForServerFileLoad(0)
1349 setline(3, ' return:')
1352 setline(3, ' return;')
1356 autocmd_delete([{event: 'User', pattern: 'LspDiagsUpdated'}])
1357 assert_equal(5, g:LspAutoCmd)
1360 # Test custom notification handlers
1361 def g:Test_LspCustomNotificationHandlers()
1363 g:LSPTest_customNotificationHandlerReplied = false
1365 silent! edit XcustomNotification.c
1367 var lines: list<string> =<< trim END
1374 g:WaitForAssert(() => assert_equal(true, g:LSPTest_customNotificationHandlerReplied))
1378 def g:Test_ScanFindIdent()
1379 :silent! edit XscanFindIdent.c
1381 var lines: list<string> =<< trim END
1391 g:WaitForServerFileLoad(0)
1394 # LspGotoDefinition et al
1396 assert_equal([], execute('LspGotoDefinition')->split("\n"))
1397 assert_equal([2, 14], [line('.'), col('.')])
1400 assert_equal([], execute('LspGotoDefinition')->split("\n"))
1401 assert_equal([1, 5], [line('.'), col('.')])
1405 assert_equal([], execute('LspShowReferences')->split("\n"))
1410 assert_equal([], execute('LspRename counterFI')->split("\n"))
1412 assert_equal('int counterFI;', getline(1))
1413 assert_equal(' return counterFI + 1;', getline(6))
1418 # Test for doing omni completion from the first column
1419 def g:Test_OmniComplete_FirstColumn()
1420 :silent! edit XOmniCompleteFirstColumn.c
1422 var lines: list<string> =<< trim END
1423 typedef struct Foo_ {
1429 g:WaitForServerFileLoad(0)
1432 feedkeys("G0i\<C-X>\<C-O>", 'xt')
1433 assert_equal('Foo_t#define FOO 1', getline('.'))
1437 # Test for doing omni completion from the first column
1438 def g:Test_OmniComplete_Multibyte()
1439 :silent! edit XOmniCompleteMultibyte.c
1441 var lines: list<string> =<< trim END
1446 int len = strlen("©©©©©") + thisVar;
1450 g:WaitForServerFileLoad(0)
1454 feedkeys("cwthis\<C-X>\<C-O>", 'xt')
1455 assert_equal(' int len = strlen("©©©©©") + thisVar;', getline('.'))
1459 # Test for doing omni completion from the first column
1460 def g:Test_OmniComplete_Struct()
1461 :silent! edit XOmniCompleteStruct.c
1463 var lines: list<string> =<< trim END
1471 struct test_ myTest;
1472 struct test_ *pTest;
1478 g:WaitForServerFileLoad(0)
1482 feedkeys("cwb\<C-X>\<C-O>\<C-N>\<C-Y>", 'xt')
1483 assert_equal(' myTest.baz = 10;', getline('.'))
1485 feedkeys("cw\<C-X>\<C-O>\<C-N>\<C-N>\<C-Y>", 'xt')
1486 assert_equal(' pTest->foo = 20;', getline('.'))
1490 # Test for the :LspServer command.
1491 def g:Test_LspServer()
1493 assert_equal(['Warn: No Lsp servers found for "a.raku"'],
1494 execute('LspServer debug on')->split("\n"))
1495 assert_equal(['Warn: No Lsp servers found for "a.raku"'],
1496 execute('LspServer restart')->split("\n"))
1497 assert_equal(['Warn: No Lsp servers found for "a.raku"'],
1498 execute('LspServer show status')->split("\n"))
1499 assert_equal(['Warn: No Lsp servers found for "a.raku"'],
1500 execute('LspServer trace verbose')->split("\n"))
1501 assert_equal(['Error: LspServer - Unsupported argument "xyz"'],
1502 execute('LspServer xyz')->split("\n"))
1503 assert_equal(['Error: Argument required'],
1504 execute('LspServer debug')->split("\n"))
1505 assert_equal(['Error: Unsupported argument "xyz"'],
1506 execute('LspServer debug xyz')->split("\n"))
1507 assert_equal(['Error: Unsupported argument "on xyz"'],
1508 execute('LspServer debug on xyz')->split("\n"))
1509 assert_equal(['Error: Argument required'],
1510 execute('LspServer show')->split("\n"))
1511 assert_equal(['Error: Unsupported argument "xyz"'],
1512 execute('LspServer show xyz')->split("\n"))
1513 assert_equal(['Error: Unsupported argument "status xyz"'],
1514 execute('LspServer show status xyz')->split("\n"))
1515 assert_equal(['Error: Argument required'],
1516 execute('LspServer trace')->split("\n"))
1517 assert_equal(['Error: Unsupported argument "xyz"'],
1518 execute('LspServer trace xyz')->split("\n"))
1519 assert_equal(['Error: Unsupported argument "verbose xyz"'],
1520 execute('LspServer trace verbose xyz')->split("\n"))
1525 # 1. Add a test for autocompletion with a single match while ignoring case.
1526 # After the full matched name is typed, the completion popup should still
1527 # be displayed. e.g.
1530 # int abc = myvar<C-N><C-Y>
1531 # 2. Add a test for jumping to a non-existing symbol definition, declaration.
1533 # Start the C language server. Returns true on success and false on failure.
1534 def g:StartLangServer(): bool
1535 return g:StartLangServerWithFile('Xtest.c')
1538 # vim: shiftwidth=2 softtabstop=2 noexpandtab