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 # Test that the client have been able to configure the server to speak utf-32
373 def g:Test_UnicodeColumnCalc()
374 :silent! edit XUnicodeColumn.c
376 var lines: list<string> =<< trim END
391 cursor(5, 1) # 😊😊😊😊 = a;
394 execute('LspGotoDefinition')->split("\n"))
395 assert_equal([2, 12], [line('.'), col('.')])
397 cursor(8, 1) # b = a;
400 execute('LspGotoDefinition')->split("\n"))
401 assert_equal([2, 12], [line('.'), col('.')])
406 # Test for multiple LSP diagnostics on the same line
407 def g:Test_LspDiag_Multi()
408 :silent! edit XLspDiagMulti.c
411 var bnr: number = bufnr()
413 var lines =<< trim END
420 # TODO: Waiting count doesn't include Warning, Info, and Hint diags
421 if clangdVerMajor > 14
422 g:WaitForServerFileLoad(3)
424 g:WaitForServerFileLoad(2)
427 var qfl: list<dict<any>> = getloclist(0)
428 assert_equal('quickfix', getwinvar(winnr('$'), '&buftype'))
429 assert_equal(bnr, qfl[0].bufnr)
430 assert_equal(3, qfl->len())
431 if clangdVerMajor > 14
432 assert_equal([1, 5, 'E'], [qfl[0].lnum, qfl[0].col, qfl[0].type])
434 assert_equal([1, 5, 'W'], [qfl[0].lnum, qfl[0].col, qfl[0].type])
436 assert_equal([1, 9, 'E'], [qfl[1].lnum, qfl[1].col, qfl[1].type])
437 assert_equal([2, 9, 'E'], [qfl[2].lnum, qfl[2].col, qfl[2].type])
442 assert_equal('', execute('LspDiagPrev'))
443 assert_equal([1, 9], [line('.'), col('.')])
445 assert_equal('', execute('LspDiagPrev'))
446 assert_equal([1, 5], [line('.'), col('.')])
448 var output = execute('LspDiagPrev')->split("\n")
449 assert_equal('Warn: No more diagnostics found', output[0])
452 assert_equal('', execute('LspDiagFirst'))
453 assert_equal([1, 5], [line('.'), col('.')])
454 assert_equal('', execute('LspDiagNext'))
455 assert_equal([1, 9], [line('.'), col('.')])
457 assert_equal('', execute('LspDiagLast'))
458 assert_equal([2, 9], [line('.'), col('.')])
461 # Test for :LspDiagHere on a line with multiple diagnostics
464 assert_equal([1, 5], [line('.'), col('.')])
465 var ids = popup_list()
466 assert_equal(1, ids->len())
467 assert_match('Incompatible pointer to integer', getbufline(ids[0]->winbufnr(), 1, '$')[0])
471 assert_equal([1, 9], [line('.'), col('.')])
473 assert_equal(1, ids->len())
474 assert_match('Initializer element is not', getbufline(ids[0]->winbufnr(), 1, '$')[0])
477 # Line without diagnostics
479 output = execute('LspDiagHere')->split("\n")
480 assert_equal('Warn: No more diagnostics found on this line', output[0])
482 g:LspOptionsSet({showDiagInPopup: false})
485 output = execute('LspDiagCurrent')->split('\n')
486 assert_match('Incompatible pointer to integer', output[0])
488 for i in range(6, 12)
490 output = execute('LspDiagCurrent')->split('\n')
491 assert_match('Initializer element is not ', output[0])
493 g:LspOptionsSet({showDiagInPopup: true})
495 # Check for exact diag ":LspDiagCurrent!"
496 g:LspOptionsSet({showDiagInPopup: false})
499 output = execute('LspDiagCurrent!')->split('\n')
500 assert_equal('Warn: No diagnostic messages found for current position', output[0])
504 output = execute('LspDiagCurrent!')->split('\n')
505 assert_match('Incompatible pointer to integer', output[0])
509 output = execute('LspDiagCurrent!')->split('\n')
510 assert_equal('Warn: No diagnostic messages found for current position', output[0])
513 for i in range(9, 11)
515 output = execute('LspDiagCurrent!')->split('\n')
516 assert_match('Initializer element is not ', output[0])
518 for i in range(12, 12)
520 output = execute('LspDiagCurrent!')->split('\n')
521 assert_equal('Warn: No diagnostic messages found for current position', output[0])
524 g:LspOptionsSet({showDiagInPopup: true})
526 # :[count]LspDiagNext
527 g:LspOptionsSet({showDiagInPopup: false})
530 assert_equal([1, 9], [line('.'), col('.')])
532 assert_equal([2, 9], [line('.'), col('.')])
533 output = execute(':2LspDiagNext')->split("\n")
534 assert_equal('Warn: No more diagnostics found', output[0])
538 assert_equal([2, 9], [line('.'), col('.')])
539 g:LspOptionsSet({showDiagInPopup: true})
541 # :[count]LspDiagPrev
542 g:LspOptionsSet({showDiagInPopup: false})
545 assert_equal('Warn: No more diagnostics found', output[0])
548 assert_equal([1, 9], [line('.'), col('.')])
550 assert_equal([1, 5], [line('.'), col('.')])
551 output = execute(':2LspDiagPrev')->split("\n")
552 assert_equal('Warn: No more diagnostics found', output[0])
556 assert_equal([1, 5], [line('.'), col('.')])
557 g:LspOptionsSet({showDiagInPopup: true})
562 # Test for highlight diag inline
563 def g:Test_LspHighlightDiagInline()
564 :silent! edit XLspHighlightDiag.c
575 # TODO: Waiting count doesn't include Warning, Info, and Hint diags
578 var props = prop_list(1)
579 assert_equal(0, props->len())
581 assert_equal(0, props->len())
583 assert_equal(2, props->len())
585 {'id': 0, 'col': 12, 'type_bufnr': 0, 'end': 1, 'type': 'LspDiagInlineInfo', 'length': 3, 'start': 1},
586 {'id': 0, 'col': 16, 'type_bufnr': 0, 'end': 1, 'type': 'LspDiagInlineError', 'length': 3, 'start': 1}
589 assert_equal(0, props->len())
591 assert_equal(1, props->len())
592 assert_equal([{'id': 0, 'col': 5, 'type_bufnr': 0, 'end': 1, 'type': 'LspDiagInlineError', 'length': 6, 'start': 1}], props)
594 assert_equal(0, props->len())
599 # Test for :LspCodeAction
600 def g:Test_LspCodeAction()
601 silent! edit XLspCodeAction.c
603 var lines: list<string> =<< trim END
611 g:WaitForServerFileLoad(0)
615 assert_equal("\tcount = 20;", getline(4))
617 setline(4, "\tcount = 20:")
621 assert_equal("\tcount = 20:", getline(4))
625 assert_equal("\tcount = 20:", getline(4))
629 assert_equal("\tcount = 20;", getline(4))
632 # pattern and string prefix
633 silent! edit XLspCodeActionPattern.c
635 var lines2: list<string> =<< trim END
644 g:WaitForServerFileLoad(0)
648 assert_equal("\tif (count == 1) {", getline(4))
650 setline(4, "\tif (count = 1) {")
653 :LspCodeAction /paren
654 assert_equal("\tif ((count = 1)) {", getline(4))
656 setline(4, "\tif (count = 1) {")
659 :LspCodeAction NON_EXISTING_PREFIX
660 assert_equal("\tif (count = 1) {", getline(4))
663 :LspCodeAction /NON_EXISTING_REGEX
664 assert_equal("\tif (count = 1) {", getline(4))
668 assert_equal('', execute('LspCodeAction'))
670 # file without an LSP server
672 assert_equal('Error: Language server for "raku" file type supporting "codeAction" feature is not found',
673 execute('LspCodeAction')->split("\n")[0])
678 # Test for :LspRename
679 def g:Test_LspRename()
680 silent! edit XLspRename.c
682 var lines: list<string> =<< trim END
696 g:WaitForServerFileLoad(0)
700 feedkeys(":LspRename\<CR>er\<CR>", "xt")
702 var expected: list<string> =<< trim END
715 assert_equal(expected, getline(1, '$'))
720 var expected2: list<string> =<< trim END
721 void F1(int countvar)
733 assert_equal(expected2, getline(1, '$'))
738 assert_equal('', execute('LspRename'))
740 # file without an LSP server
742 assert_equal('Error: Language server for "raku" file type supporting "rename" feature is not found',
743 execute('LspRename')->split("\n")[0])
748 # Test for :LspSelectionExpand and :LspSelectionShrink
749 def g:Test_LspSelection()
750 silent! edit XLspSelection.c
752 var lines: list<string> =<< trim END
753 void fnSel(int count)
756 for (i = 0; i < 10; i++) {
763 g:WaitForServerFileLoad(0)
764 # start a block-wise visual mode, LspSelectionExpand should change this to
765 # a characterwise visual mode.
766 exe "normal! 1G\<C-V>G\"_y"
772 assert_equal('v', visualmode())
773 assert_equal([2, 8], [line("'<"), line("'>")])
774 # start a linewise visual mode, LspSelectionExpand should change this to
775 # a characterwise visual mode.
776 exe "normal! 3GViB\"_y"
782 assert_equal('v', visualmode())
783 assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
785 # Expand the visual selection
786 xnoremap <silent> le <Cmd>LspSelectionExpand<CR>
787 xnoremap <silent> ls <Cmd>LspSelectionShrink<CR>
790 assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
793 assert_equal([5, 8, 5, 14], [line("'<"), col("'<"), line("'>"), col("'>")])
796 assert_equal([4, 30, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
799 assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
802 assert_equal([2, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
804 normal vleleleleleley
805 assert_equal([1, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
807 normal vleleleleleleley
808 assert_equal([1, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
810 # Shrink the visual selection
813 assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
816 assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
819 assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
822 assert_equal([5, 8, 5, 14], [line("'<"), col("'<"), line("'>"), col("'>")])
825 assert_equal([4, 30, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
827 normal vlelelelelelsy
828 assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
830 normal vlelelelelelelsy
831 assert_equal([2, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
838 assert_equal('', execute('LspSelectionExpand'))
840 # file without an LSP server
842 assert_equal('Error: Language server for "raku" file type supporting "selectionRange" feature is not found',
843 execute('LspSelectionExpand')->split("\n")[0])
848 # Test for :LspGotoDefinition, :LspGotoDeclaration and :LspGotoImpl
849 def g:Test_LspGotoSymbol()
850 settagstack(0, {items: []})
851 silent! edit XLspGotoSymbol.cpp
853 var lines: list<string> =<< trim END
856 virtual void print();
863 class derived : public base {
878 g:WaitForServerFileLoad(0)
882 assert_equal([3, 19], [line('.'), col('.')])
884 assert_equal([21, 6], [line('.'), col('.')])
885 assert_equal(1, winnr('$'))
888 assert_equal([6, 12], [line('.'), col('.')])
890 assert_equal([21, 6], [line('.'), col('.')])
891 assert_equal(1, winnr('$'))
894 :topleft LspGotoDefinition
895 assert_equal([6, 12], [line('.'), col('.')])
896 assert_equal([1, 2], [winnr(), winnr('$')])
899 assert_equal([21, 6], [line('.'), col('.')])
901 :tab LspGotoDefinition
902 assert_equal([6, 12], [line('.'), col('.')])
903 assert_equal([2, 2, 1], [tabpagenr(), tabpagenr('$'), winnr('$')])
906 assert_equal([21, 6], [line('.'), col('.')])
911 assert_equal([1, 7], [line('.'), col('.')])
913 assert_equal([21, 2], [line('.'), col('.')])
918 assert_equal([12, 11], [line('.'), col('.')])
920 assert_equal([21, 6], [line('.'), col('.')])
922 # FIXME: The following tests are failing in Github CI. Comment out for now.
928 var m = execute('messages')->split("\n")
929 assert_equal('symbol declaration is not found', m[1])
932 m = execute('messages')->split("\n")
933 assert_equal('symbol definition is not found', m[1])
936 m = execute('messages')->split("\n")
937 assert_equal('symbol implementation is not found', m[1])
941 # Test for LspPeekDeclaration
945 var plist = popup_list()
946 assert_true(1, plist->len())
947 assert_equal(bnum, plist[0]->winbufnr())
948 assert_equal(3, line('.', plist[0]))
950 # tag stack should not be changed
951 assert_fails("normal! \<C-t>", 'E555:')
953 # Test for LspPeekDefinition
956 assert_true(1, plist->len())
957 assert_equal(bnum, plist[0]->winbufnr())
958 assert_equal(6, line('.', plist[0]))
960 # tag stack should not be changed
961 assert_fails("normal! \<C-t>", 'E555:')
963 # FIXME: :LspPeekTypeDef and :LspPeekImpl are supported only with clang-14.
964 # This clangd version is not available in Github CI.
969 assert_equal('', execute('LspGotoDefinition'))
970 assert_equal('', execute('LspGotoDeclaration'))
971 assert_equal('', execute('LspGotoImpl'))
973 # file without an LSP server
975 assert_equal('Error: Language server for "raku" file type supporting "definition" feature is not found',
976 execute('LspGotoDefinition')->split("\n")[0])
977 assert_equal('Error: Language server for "raku" file type supporting "declaration" feature is not found',
978 execute('LspGotoDeclaration')->split("\n")[0])
979 assert_equal('Error: Language server for "raku" file type supporting "implementation" feature is not found',
980 execute('LspGotoImpl')->split("\n")[0])
985 # Test for :LspHighlight
986 def g:Test_LspHighlight()
987 silent! edit XLspHighlight.c
989 var lines: list<string> =<< trim END
1000 g:WaitForServerFileLoad(0)
1003 var expected: dict<any>
1004 expected = {id: 0, col: 13, end: 1, type: 'LspTextRef', length: 3, start: 1}
1005 expected.type_bufnr = 0
1006 assert_equal([expected], prop_list(1))
1007 expected = {id: 0, col: 11, end: 1, type: 'LspReadRef', length: 3, start: 1}
1008 expected.type_bufnr = 0
1009 assert_equal([expected], prop_list(3))
1010 expected = {id: 0, col: 3, end: 1, type: 'LspWriteRef', length: 3, start: 1}
1011 expected.type_bufnr = 0
1012 assert_equal([expected], prop_list(4))
1014 assert_equal([], prop_list(1))
1015 assert_equal([], prop_list(3))
1016 assert_equal([], prop_list(4))
1018 cursor(5, 3) # if (arg == 2) {
1019 var output = execute('LspHighlight')->split("\n")
1020 assert_equal('Warn: No highlight for the current position', output[0])
1024 # Test for :LspHover
1025 def g:Test_LspHover()
1026 silent! edit XLspHover.c
1028 var lines: list<string> =<< trim END
1042 if clangdVerMajor > 14
1043 g:WaitForServerFileLoad(1)
1045 g:WaitForServerFileLoad(0)
1048 var output = execute(':LspHover')->split("\n")
1049 assert_equal([], output)
1050 var p: list<number> = popup_list()
1051 assert_equal(1, p->len())
1052 assert_equal(['### function `f1` ', '', '---', '→ `int` ', 'Parameters: ', '- `int a`', '', '---', '```cpp', 'int f1(int a)', '```'], getbufline(winbufnr(p[0]), 1, '$'))
1055 output = execute(':LspHover')->split("\n")
1056 assert_equal('Warn: No documentation found for current keyword', output[0])
1057 output = execute(':silent LspHover')->split("\n")
1058 assert_equal([], output)
1059 assert_equal([], popup_list())
1061 # Show current diagnostic as to open another popup.
1062 # Then we can test that LspHover closes all existing popups
1065 assert_equal(1, popup_list()->len())
1067 assert_equal(1, popup_list()->len())
1075 # Test for :LspShowSignature
1076 def g:Test_LspShowSignature()
1077 silent! edit XLspShowSignature.c
1079 var lines: list<string> =<< trim END
1080 int MyFunc(int a, int b)
1091 g:WaitForServerFileLoad(2)
1094 var p: list<number> = popup_list()
1095 var bnr: number = winbufnr(p[0])
1096 assert_equal(1, p->len())
1097 assert_equal(['MyFunc(int a, int b) -> int'], getbufline(bnr, 1, '$'))
1098 var expected: dict<any>
1099 expected = {id: 0, col: 8, end: 1, type: 'signature', length: 5, start: 1}
1100 expected.type_bufnr = bnr
1101 assert_equal([expected], prop_list(1, {bufnr: bnr}))
1104 setline(line('.'), ' MyFunc(10, ')
1108 bnr = winbufnr(p[0])
1109 assert_equal(1, p->len())
1110 assert_equal(['MyFunc(int a, int b) -> int'], getbufline(bnr, 1, '$'))
1111 expected = {id: 0, col: 15, end: 1, type: 'signature', length: 5, start: 1}
1112 expected.type_bufnr = bnr
1113 assert_equal([expected], prop_list(1, {bufnr: bnr}))
1118 # Test for :LspSymbolSearch
1119 def g:Test_LspSymbolSearch()
1120 silent! edit XLspSymbolSearch.c
1122 var lines: list<string> =<< trim END
1123 void lsptest_funcA()
1127 void lsptest_funcB()
1131 void lsptest_funcC()
1136 g:WaitForServerFileLoad(0)
1139 feedkeys(":LspSymbolSearch lsptest_funcB\<CR>\<CR>", "xt")
1140 assert_equal([5, 6], [line('.'), col('.')])
1143 feedkeys(":LspSymbolSearch lsptest_func\<CR>\<Down>\<Down>\<CR>", "xt")
1144 assert_equal([9, 6], [line('.'), col('.')])
1147 feedkeys(":LspSymbolSearch lsptest_funcA\<CR>\<BS>B\<CR>", "xt")
1148 assert_equal([5, 6], [line('.'), col('.')])
1150 var output = execute(':LspSymbolSearch lsptest_nonexist')->split("\n")
1151 assert_equal('Warn: Symbol "lsptest_nonexist" is not found', output[0])
1156 # Test for :LspIncomingCalls
1157 def g:Test_LspIncomingCalls()
1158 silent! edit XLspIncomingCalls.c
1160 var lines: list<string> =<< trim END
1161 void xFuncIncoming(void)
1165 void aFuncIncoming(void)
1170 void bFuncIncoming(void)
1176 g:WaitForServerFileLoad(0)
1179 assert_equal([1, 2], [winnr(), winnr('$')])
1180 var l = getline(1, '$')
1181 assert_equal('# Incoming calls to "xFuncIncoming"', l[0])
1182 assert_match('- xFuncIncoming (XLspIncomingCalls.c \[.*\])', l[1])
1183 assert_match(' + aFuncIncoming (XLspIncomingCalls.c \[.*\])', l[2])
1184 assert_match(' + bFuncIncoming (XLspIncomingCalls.c \[.*\])', l[3])
1188 # Test for :LspOutline
1189 def g:Test_LspOutline()
1190 silent! edit XLspOutline.c
1192 var lines: list<string> =<< trim END
1193 void aFuncOutline(void)
1197 void bFuncOutline(void)
1202 g:WaitForServerFileLoad(0)
1203 var winid = win_getid()
1205 assert_equal(2, winnr('$'))
1206 var bnum = winbufnr(winid + 1)
1207 assert_equal('LSP-Outline', bufname(bnum))
1208 assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
1210 # Validate position vert topleft
1211 assert_equal(['row', [['leaf', winid + 1], ['leaf', winid]]], winlayout())
1213 # Validate default width is 20
1214 assert_equal(20, winwidth(winid + 1))
1216 execute $':{bnum}bw'
1218 # Validate position vert botright
1219 g:LspOptionsSet({ outlineOnRight: true })
1221 assert_equal(2, winnr('$'))
1222 bnum = winbufnr(winid + 2)
1223 assert_equal('LSP-Outline', bufname(bnum))
1224 assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
1225 assert_equal(['row', [['leaf', winid], ['leaf', winid + 2]]], winlayout())
1226 g:LspOptionsSet({ outlineOnRight: false })
1227 execute $':{bnum}bw'
1229 # Validate <mods> position botright (below)
1230 :botright LspOutline
1231 assert_equal(2, winnr('$'))
1232 bnum = winbufnr(winid + 3)
1233 assert_equal('LSP-Outline', bufname(bnum))
1234 assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
1235 assert_equal(['col', [['leaf', winid], ['leaf', winid + 3]]], winlayout())
1236 execute $':{bnum}bw'
1238 # Validate that outlineWinSize works for LspOutline
1239 g:LspOptionsSet({ outlineWinSize: 40 })
1241 assert_equal(2, winnr('$'))
1242 bnum = winbufnr(winid + 4)
1243 assert_equal('LSP-Outline', bufname(bnum))
1244 assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
1245 assert_equal(40, winwidth(winid + 4))
1246 execute $':{bnum}bw'
1247 g:LspOptionsSet({ outlineWinSize: 20 })
1249 # Validate that <count> works for LspOutline
1251 assert_equal(2, winnr('$'))
1252 bnum = winbufnr(winid + 5)
1253 assert_equal('LSP-Outline', bufname(bnum))
1254 assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
1255 assert_equal(37, winwidth(winid + 5))
1256 execute $':{bnum}bw'
1261 # Test for setting the 'tagfunc'
1262 def g:Test_LspTagFunc()
1263 var lines: list<string> =<< trim END
1278 writefile(lines, 'Xtagfunc.c')
1279 :silent! edit Xtagfunc.c
1280 g:WaitForServerFileLoad(1)
1281 :setlocal tagfunc=lsp#lsp#TagFunc
1283 :exe "normal \<C-]>"
1284 assert_equal([11, 6], [line('.'), col('.')])
1286 assert_fails('exe "normal \<C-]>"', 'E433: No tags file')
1290 delete('Xtagfunc.c')
1293 # Test for the LspDiagsUpdated autocmd
1294 def g:Test_LspDiagsUpdated_Autocmd()
1296 autocmd_add([{event: 'User', pattern: 'LspDiagsUpdated', cmd: 'g:LspAutoCmd = g:LspAutoCmd + 1'}])
1297 silent! edit XLspDiagsAutocmd.c
1299 var lines: list<string> =<< trim END
1300 void aFuncDiag(void)
1306 g:WaitForServerFileLoad(0)
1307 setline(3, ' return:')
1310 setline(3, ' return;')
1314 autocmd_delete([{event: 'User', pattern: 'LspDiagsUpdated'}])
1315 assert_equal(5, g:LspAutoCmd)
1318 # Test custom notification handlers
1319 def g:Test_LspCustomNotificationHandlers()
1321 g:LSPTest_customNotificationHandlerReplied = false
1323 silent! edit XcustomNotification.c
1325 var lines: list<string> =<< trim END
1332 g:WaitForAssert(() => assert_equal(true, g:LSPTest_customNotificationHandlerReplied))
1336 def g:Test_ScanFindIdent()
1337 :silent! edit XscanFindIdent.c
1339 var lines: list<string> =<< trim END
1349 g:WaitForServerFileLoad(0)
1352 # LspGotoDefinition et al
1354 assert_equal([], execute('LspGotoDefinition')->split("\n"))
1355 assert_equal([2, 14], [line('.'), col('.')])
1358 assert_equal([], execute('LspGotoDefinition')->split("\n"))
1359 assert_equal([1, 5], [line('.'), col('.')])
1363 assert_equal([], execute('LspShowReferences')->split("\n"))
1368 assert_equal([], execute('LspRename counterFI')->split("\n"))
1370 assert_equal('int counterFI;', getline(1))
1371 assert_equal(' return counterFI + 1;', getline(6))
1376 # Test for doing omni completion from the first column
1377 def g:Test_OmniComplete_FirstColumn()
1378 :silent! edit XOmniCompleteFirstColumn.c
1380 var lines: list<string> =<< trim END
1381 typedef struct Foo_ {
1387 g:WaitForServerFileLoad(0)
1390 feedkeys("G0i\<C-X>\<C-O>", 'xt')
1391 assert_equal('Foo_t#define FOO 1', getline('.'))
1395 # Test for doing omni completion from the first column
1396 def g:Test_OmniComplete_Multibyte()
1397 :silent! edit XOmniCompleteMultibyte.c
1399 var lines: list<string> =<< trim END
1404 int len = strlen("©©©©©") + thisVar;
1408 g:WaitForServerFileLoad(0)
1412 feedkeys("cwthis\<C-X>\<C-O>", 'xt')
1413 assert_equal(' int len = strlen("©©©©©") + thisVar;', getline('.'))
1417 # Test for doing omni completion from the first column
1418 def g:Test_OmniComplete_Struct()
1419 :silent! edit XOmniCompleteStruct.c
1421 var lines: list<string> =<< trim END
1429 struct test_ myTest;
1430 struct test_ *pTest;
1436 g:WaitForServerFileLoad(0)
1440 feedkeys("cwb\<C-X>\<C-O>\<C-N>\<C-Y>", 'xt')
1441 assert_equal(' myTest.baz = 10;', getline('.'))
1443 feedkeys("cw\<C-X>\<C-O>\<C-N>\<C-N>\<C-Y>", 'xt')
1444 assert_equal(' pTest->foo = 20;', getline('.'))
1448 # Test for the :LspServer command.
1449 def g:Test_LspServer()
1451 assert_equal(['Warn: No Lsp servers found for "a.raku"'],
1452 execute('LspServer debug on')->split("\n"))
1453 assert_equal(['Warn: No Lsp servers found for "a.raku"'],
1454 execute('LspServer restart')->split("\n"))
1455 assert_equal(['Warn: No Lsp servers found for "a.raku"'],
1456 execute('LspServer show status')->split("\n"))
1457 assert_equal(['Warn: No Lsp servers found for "a.raku"'],
1458 execute('LspServer trace verbose')->split("\n"))
1459 assert_equal(['Error: LspServer - Unsupported argument "xyz"'],
1460 execute('LspServer xyz')->split("\n"))
1461 assert_equal(['Error: Argument required'],
1462 execute('LspServer debug')->split("\n"))
1463 assert_equal(['Error: Unsupported argument "xyz"'],
1464 execute('LspServer debug xyz')->split("\n"))
1465 assert_equal(['Error: Unsupported argument "on xyz"'],
1466 execute('LspServer debug on xyz')->split("\n"))
1467 assert_equal(['Error: Argument required'],
1468 execute('LspServer show')->split("\n"))
1469 assert_equal(['Error: Unsupported argument "xyz"'],
1470 execute('LspServer show xyz')->split("\n"))
1471 assert_equal(['Error: Unsupported argument "status xyz"'],
1472 execute('LspServer show status xyz')->split("\n"))
1473 assert_equal(['Error: Argument required'],
1474 execute('LspServer trace')->split("\n"))
1475 assert_equal(['Error: Unsupported argument "xyz"'],
1476 execute('LspServer trace xyz')->split("\n"))
1477 assert_equal(['Error: Unsupported argument "verbose xyz"'],
1478 execute('LspServer trace verbose xyz')->split("\n"))
1483 # 1. Add a test for autocompletion with a single match while ignoring case.
1484 # After the full matched name is typed, the completion popup should still
1485 # be displayed. e.g.
1488 # int abc = myvar<C-N><C-Y>
1489 # 2. Add a test for jumping to a non-existing symbol definition, declaration.
1491 # Start the C language server. Returns true on success and false on failure.
1492 def g:StartLangServer(): bool
1493 return g:StartLangServerWithFile('Xtest.c')
1496 # vim: shiftwidth=2 softtabstop=2 noexpandtab