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(1, line('.', ids[1]))
241 assert_equal(3, line('$', ids[1]))
242 feedkeys("jj\<CR>", 'xt')
243 assert_equal(12, line('.'))
244 assert_equal([], popup_list())
247 # LspShowReferences should start with the current symbol
252 assert_equal(2, ids->len())
253 assert_equal(12, line('.', ids[0]))
254 assert_equal(3, line('.', ids[1]))
255 feedkeys("\<CR>", 'xt')
261 assert_equal('', execute('LspShowReferences'))
263 # file without an LSP server
265 assert_equal('Error: Language server for "raku" file type supporting "references" feature is not found',
266 execute('LspShowReferences')->split("\n")[0])
271 # Test for LSP diagnostics
273 :silent! edit XLspDiag.c
275 var lines: list<string> =<< trim END
286 g:WaitForServerFileLoad(1)
287 var bnr: number = bufnr()
290 var qfl: list<dict<any>> = getloclist(0)
291 assert_equal('quickfix', getwinvar(winnr('$'), '&buftype'))
292 assert_equal(bnr, qfl[0].bufnr)
293 assert_equal(3, qfl->len())
294 assert_equal([3, 14, 'E'], [qfl[0].lnum, qfl[0].col, qfl[0].type])
295 assert_equal([5, 2, 'W'], [qfl[1].lnum, qfl[1].col, qfl[1].type])
296 assert_equal([7, 2, 'W'], [qfl[2].lnum, qfl[2].col, qfl[2].type])
298 g:LspOptionsSet({showDiagInPopup: false})
300 var output = execute('LspDiagCurrent')->split("\n")
301 assert_equal('Warn: No diagnostic messages found for current line', output[0])
303 assert_equal([3, 14], [line('.'), col('.')])
304 output = execute('LspDiagCurrent')->split("\n")
305 assert_equal("Expected ';' at end of declaration (fix available)", output[0])
308 assert_equal([3, 14], [line('.'), col('.')])
310 assert_equal([5, 2], [line('.'), col('.')])
312 assert_equal([7, 2], [line('.'), col('.')])
313 output = execute('LspDiagNext')->split("\n")
314 assert_equal('Warn: No more diagnostics found', output[0])
318 output = execute('LspDiagPrev')->split("\n")
319 assert_equal('Warn: No more diagnostics found', output[0])
321 # Test for maintaining buffer focus
322 g:LspOptionsSet({ keepFocusInDiags: false })
324 assert_equal('', getwinvar(0, '&buftype'))
326 g:LspOptionsSet({ keepFocusInDiags: true })
328 # :[count]LspDiagNext
331 assert_equal([5, 2], [line('.'), col('.')])
333 assert_equal([7, 2], [line('.'), col('.')])
334 output = execute(':2LspDiagNext')->split("\n")
335 assert_equal('Warn: No more diagnostics found', output[0])
337 # :[count]LspDiagPrev
340 assert_equal([3, 14], [line('.'), col('.')])
341 output = execute(':4LspDiagPrev')->split("\n")
342 assert_equal('Warn: No more diagnostics found', output[0])
345 setline(1, ['void blueFunc()', '{', '}'])
347 output = execute('LspDiagShow')->split("\n")
348 assert_match('Warn: No diagnostic messages found for', output[0])
349 g:LspOptionsSet({showDiagInPopup: true})
355 # Test for LSP diagnostics handler
356 def g:Test_LspProcessDiagHandler()
357 g:LSPTest_modifyDiags = true
358 g:LspOptionsSet({showDiagInPopup: false})
360 :silent! edit XLspProcessDiag.c
362 var lines: list<string> =<< trim END
369 g:WaitForServerFileLoad(1)
374 assert_equal([3, 14], [line('.'), col('.')])
376 var output = execute('LspDiagCurrent')->split("\n")
377 assert_equal("this is overridden", output[0])
379 g:LspOptionsSet({showDiagInPopup: true})
380 g:LSPTest_modifyDiags = false
384 # Diag location list should be automatically updated when the list of diags
386 def g:Test_DiagLocListAutoUpdate()
387 :silent! edit XdiagLocListAutoUpdate.c
389 setloclist(0, [], 'f')
390 var lines: list<string> =<< trim END
396 g:WaitForServerFileLoad(1)
398 var d = lsp#diag#GetDiagsForBuf()[0]
399 assert_equal({start: {line: 0, character: 5}, end: {line: 0, character: 6}},
403 assert_equal(1, line('$'))
408 var l = lsp#diag#GetDiagsForBuf()
409 assert_equal({start: {line: 0, character: 5}, end: {line: 0, character: 6}},
411 assert_equal({start: {line: 1, character: 5}, end: {line: 1, character: 6}},
414 assert_equal(2, line('$'))
416 deletebufline('', 1, '$')
419 assert_equal([], lsp#diag#GetDiagsForBuf())
421 assert_equal([''], getline(1, '$'))
424 setloclist(0, [], 'f')
428 # Test that the client have been able to configure the server to speak utf-32
429 def g:Test_UnicodeColumnCalc()
430 :silent! edit XUnicodeColumn.c
432 var lines: list<string> =<< trim END
447 cursor(5, 1) # 😊😊😊😊 = a;
450 execute('LspGotoDefinition')->split("\n"))
451 assert_equal([2, 12], [line('.'), col('.')])
453 cursor(8, 1) # b = a;
456 execute('LspGotoDefinition')->split("\n"))
457 assert_equal([2, 12], [line('.'), col('.')])
462 # Test for multiple LSP diagnostics on the same line
463 def g:Test_LspDiag_Multi()
464 :silent! edit XLspDiagMulti.c
467 var bnr: number = bufnr()
469 var lines =<< trim END
476 # TODO: Waiting count doesn't include Warning, Info, and Hint diags
477 if clangdVerMajor > 14
478 g:WaitForServerFileLoad(3)
480 g:WaitForServerFileLoad(2)
483 var qfl: list<dict<any>> = getloclist(0)
484 assert_equal('quickfix', getwinvar(winnr('$'), '&buftype'))
485 assert_equal(bnr, qfl[0].bufnr)
486 assert_equal(3, qfl->len())
487 if clangdVerMajor > 14
488 assert_equal([1, 5, 'E'], [qfl[0].lnum, qfl[0].col, qfl[0].type])
490 assert_equal([1, 5, 'W'], [qfl[0].lnum, qfl[0].col, qfl[0].type])
492 assert_equal([1, 9, 'E'], [qfl[1].lnum, qfl[1].col, qfl[1].type])
493 assert_equal([2, 9, 'E'], [qfl[2].lnum, qfl[2].col, qfl[2].type])
498 assert_equal('', execute('LspDiagPrev'))
499 assert_equal([1, 9], [line('.'), col('.')])
501 assert_equal('', execute('LspDiagPrev'))
502 assert_equal([1, 5], [line('.'), col('.')])
504 var output = execute('LspDiagPrev')->split("\n")
505 assert_equal('Warn: No more diagnostics found', output[0])
508 assert_equal('', execute('LspDiagFirst'))
509 assert_equal([1, 5], [line('.'), col('.')])
510 assert_equal('', execute('LspDiagNext'))
511 assert_equal([1, 9], [line('.'), col('.')])
513 assert_equal('', execute('LspDiagLast'))
514 assert_equal([2, 9], [line('.'), col('.')])
517 # Test for :LspDiagHere on a line with multiple diagnostics
520 assert_equal([1, 5], [line('.'), col('.')])
521 var ids = popup_list()
522 assert_equal(1, ids->len())
523 assert_match('Incompatible pointer to integer', getbufline(ids[0]->winbufnr(), 1, '$')[0])
527 assert_equal([1, 9], [line('.'), col('.')])
529 assert_equal(1, ids->len())
530 assert_match('Initializer element is not', getbufline(ids[0]->winbufnr(), 1, '$')[0])
533 # Line without diagnostics
535 output = execute('LspDiagHere')->split("\n")
536 assert_equal('Warn: No more diagnostics found on this line', output[0])
538 g:LspOptionsSet({showDiagInPopup: false})
541 output = execute('LspDiagCurrent')->split('\n')
542 assert_match('Incompatible pointer to integer', output[0])
544 for i in range(6, 12)
546 output = execute('LspDiagCurrent')->split('\n')
547 assert_match('Initializer element is not ', output[0])
549 g:LspOptionsSet({showDiagInPopup: true})
551 # Check for exact diag ":LspDiagCurrent!"
552 g:LspOptionsSet({showDiagInPopup: false})
555 output = execute('LspDiagCurrent!')->split('\n')
556 assert_equal('Warn: No diagnostic messages found for current position', output[0])
560 output = execute('LspDiagCurrent!')->split('\n')
561 assert_match('Incompatible pointer to integer', output[0])
565 output = execute('LspDiagCurrent!')->split('\n')
566 assert_equal('Warn: No diagnostic messages found for current position', output[0])
569 for i in range(9, 11)
571 output = execute('LspDiagCurrent!')->split('\n')
572 assert_match('Initializer element is not ', output[0])
574 for i in range(12, 12)
576 output = execute('LspDiagCurrent!')->split('\n')
577 assert_equal('Warn: No diagnostic messages found for current position', output[0])
580 g:LspOptionsSet({showDiagInPopup: true})
582 # :[count]LspDiagNext
583 g:LspOptionsSet({showDiagInPopup: false})
586 assert_equal([1, 9], [line('.'), col('.')])
588 assert_equal([2, 9], [line('.'), col('.')])
589 output = execute(':2LspDiagNext')->split("\n")
590 assert_equal('Warn: No more diagnostics found', output[0])
594 assert_equal([2, 9], [line('.'), col('.')])
595 g:LspOptionsSet({showDiagInPopup: true})
597 # :[count]LspDiagPrev
598 g:LspOptionsSet({showDiagInPopup: false})
601 assert_equal('Warn: No more diagnostics found', output[0])
604 assert_equal([1, 9], [line('.'), col('.')])
606 assert_equal([1, 5], [line('.'), col('.')])
607 output = execute(':2LspDiagPrev')->split("\n")
608 assert_equal('Warn: No more diagnostics found', output[0])
612 assert_equal([1, 5], [line('.'), col('.')])
613 g:LspOptionsSet({showDiagInPopup: true})
618 # Test for highlight diag inline
619 def g:Test_LspHighlightDiagInline()
620 :silent! edit XLspHighlightDiag.c
631 # TODO: Waiting count doesn't include Warning, Info, and Hint diags
634 var props = prop_list(1)
635 assert_equal(0, props->len())
637 assert_equal(0, props->len())
639 assert_equal(2, props->len())
641 {'id': 0, 'col': 12, 'type_bufnr': 0, 'end': 1, 'type': 'LspDiagInlineInfo', 'length': 3, 'start': 1},
642 {'id': 0, 'col': 16, 'type_bufnr': 0, 'end': 1, 'type': 'LspDiagInlineError', 'length': 3, 'start': 1}
645 assert_equal(0, props->len())
647 assert_equal(1, props->len())
648 assert_equal([{'id': 0, 'col': 5, 'type_bufnr': 0, 'end': 1, 'type': 'LspDiagInlineError', 'length': 6, 'start': 1}], props)
650 assert_equal(0, props->len())
655 # Test for :LspCodeAction
656 def g:Test_LspCodeAction()
657 silent! edit XLspCodeAction.c
659 var lines: list<string> =<< trim END
667 g:WaitForServerFileLoad(0)
671 assert_equal("\tcount = 20;", getline(4))
673 setline(4, "\tcount = 20:")
677 assert_equal("\tcount = 20:", getline(4))
681 assert_equal("\tcount = 20:", getline(4))
685 assert_equal("\tcount = 20;", getline(4))
688 # pattern and string prefix
689 silent! edit XLspCodeActionPattern.c
691 var lines2: list<string> =<< trim END
700 g:WaitForServerFileLoad(0)
704 assert_equal("\tif (count == 1) {", getline(4))
706 setline(4, "\tif (count = 1) {")
709 :LspCodeAction /paren
710 assert_equal("\tif ((count = 1)) {", getline(4))
712 setline(4, "\tif (count = 1) {")
715 :LspCodeAction NON_EXISTING_PREFIX
716 assert_equal("\tif (count = 1) {", getline(4))
719 :LspCodeAction /NON_EXISTING_REGEX
720 assert_equal("\tif (count = 1) {", getline(4))
724 assert_equal('', execute('LspCodeAction'))
726 # file without an LSP server
728 assert_equal('Error: Language server for "raku" file type supporting "codeAction" feature is not found',
729 execute('LspCodeAction')->split("\n")[0])
734 # Test for :LspRename
735 def g:Test_LspRename()
736 silent! edit XLspRename.c
738 var lines: list<string> =<< trim END
752 g:WaitForServerFileLoad(0)
756 feedkeys(":LspRename\<CR>er\<CR>", "xt")
758 var expected: list<string> =<< trim END
771 assert_equal(expected, getline(1, '$'))
776 var expected2: list<string> =<< trim END
777 void F1(int countvar)
789 assert_equal(expected2, getline(1, '$'))
794 assert_equal('', execute('LspRename'))
796 # file without an LSP server
798 assert_equal('Error: Language server for "raku" file type supporting "rename" feature is not found',
799 execute('LspRename')->split("\n")[0])
804 # Test for :LspSelectionExpand and :LspSelectionShrink
805 def g:Test_LspSelection()
806 silent! edit XLspSelection.c
808 var lines: list<string> =<< trim END
809 void fnSel(int count)
812 for (i = 0; i < 10; i++) {
819 g:WaitForServerFileLoad(0)
820 # start a block-wise visual mode, LspSelectionExpand should change this to
821 # a characterwise visual mode.
822 exe "normal! 1G\<C-V>G\"_y"
828 assert_equal('v', visualmode())
829 assert_equal([2, 8], [line("'<"), line("'>")])
830 # start a linewise visual mode, LspSelectionExpand should change this to
831 # a characterwise visual mode.
832 exe "normal! 3GViB\"_y"
838 assert_equal('v', visualmode())
839 assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
841 # Expand the visual selection
842 xnoremap <silent> le <Cmd>LspSelectionExpand<CR>
843 xnoremap <silent> ls <Cmd>LspSelectionShrink<CR>
846 assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
849 assert_equal([5, 8, 5, 14], [line("'<"), col("'<"), line("'>"), col("'>")])
852 assert_equal([4, 30, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
855 assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
858 assert_equal([2, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
860 normal vleleleleleley
861 assert_equal([1, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
863 normal vleleleleleleley
864 assert_equal([1, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
866 # Shrink the visual selection
869 assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
872 assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
875 assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
878 assert_equal([5, 8, 5, 14], [line("'<"), col("'<"), line("'>"), col("'>")])
881 assert_equal([4, 30, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
883 normal vlelelelelelsy
884 assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
886 normal vlelelelelelelsy
887 assert_equal([2, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
894 assert_equal('', execute('LspSelectionExpand'))
896 # file without an LSP server
898 assert_equal('Error: Language server for "raku" file type supporting "selectionRange" feature is not found',
899 execute('LspSelectionExpand')->split("\n")[0])
904 # Test for :LspGotoDefinition, :LspGotoDeclaration and :LspGotoImpl
905 def g:Test_LspGotoSymbol()
906 settagstack(0, {items: []})
907 silent! edit XLspGotoSymbol.cpp
909 var lines: list<string> =<< trim END
912 virtual void print();
919 class derived : public base {
934 g:WaitForServerFileLoad(0)
938 assert_equal([3, 19], [line('.'), col('.')])
940 assert_equal([21, 6], [line('.'), col('.')])
941 assert_equal(1, winnr('$'))
944 assert_equal([6, 12], [line('.'), col('.')])
946 assert_equal([21, 6], [line('.'), col('.')])
947 assert_equal(1, winnr('$'))
950 :topleft LspGotoDefinition
951 assert_equal([6, 12], [line('.'), col('.')])
952 assert_equal([1, 2], [winnr(), winnr('$')])
955 assert_equal([21, 6], [line('.'), col('.')])
957 :tab LspGotoDefinition
958 assert_equal([6, 12], [line('.'), col('.')])
959 assert_equal([2, 2, 1], [tabpagenr(), tabpagenr('$'), winnr('$')])
962 assert_equal([21, 6], [line('.'), col('.')])
967 assert_equal([1, 7], [line('.'), col('.')])
969 assert_equal([21, 2], [line('.'), col('.')])
974 assert_equal([12, 11], [line('.'), col('.')])
976 assert_equal([21, 6], [line('.'), col('.')])
978 # FIXME: The following tests are failing in Github CI. Comment out for now.
984 var m = execute('messages')->split("\n")
985 assert_equal('symbol declaration is not found', m[1])
988 m = execute('messages')->split("\n")
989 assert_equal('symbol definition is not found', m[1])
992 m = execute('messages')->split("\n")
993 assert_equal('symbol implementation is not found', m[1])
997 # Test for LspPeekDeclaration
1001 var plist = popup_list()
1002 assert_true(1, plist->len())
1003 assert_equal(bnum, plist[0]->winbufnr())
1004 assert_equal(3, line('.', plist[0]))
1006 # tag stack should not be changed
1007 assert_fails("normal! \<C-t>", 'E555:')
1009 # Test for LspPeekDefinition
1011 plist = popup_list()
1012 assert_true(1, plist->len())
1013 assert_equal(bnum, plist[0]->winbufnr())
1014 assert_equal(6, line('.', plist[0]))
1016 # tag stack should not be changed
1017 assert_fails("normal! \<C-t>", 'E555:')
1019 # FIXME: :LspPeekTypeDef and :LspPeekImpl are supported only with clang-14.
1020 # This clangd version is not available in Github CI.
1025 assert_equal('', execute('LspGotoDefinition'))
1026 assert_equal('', execute('LspGotoDeclaration'))
1027 assert_equal('', execute('LspGotoImpl'))
1029 # file without an LSP server
1031 assert_equal('Error: Language server for "raku" file type supporting "definition" feature is not found',
1032 execute('LspGotoDefinition')->split("\n")[0])
1033 assert_equal('Error: Language server for "raku" file type supporting "declaration" feature is not found',
1034 execute('LspGotoDeclaration')->split("\n")[0])
1035 assert_equal('Error: Language server for "raku" file type supporting "implementation" feature is not found',
1036 execute('LspGotoImpl')->split("\n")[0])
1041 # Test for :LspHighlight
1042 def g:Test_LspHighlight()
1043 silent! edit XLspHighlight.c
1045 var lines: list<string> =<< trim END
1056 g:WaitForServerFileLoad(0)
1059 var expected: dict<any>
1060 expected = {id: 0, col: 13, end: 1, type: 'LspTextRef', length: 3, start: 1}
1061 expected.type_bufnr = 0
1062 assert_equal([expected], prop_list(1))
1063 expected = {id: 0, col: 11, end: 1, type: 'LspReadRef', length: 3, start: 1}
1064 expected.type_bufnr = 0
1065 assert_equal([expected], prop_list(3))
1066 expected = {id: 0, col: 3, end: 1, type: 'LspWriteRef', length: 3, start: 1}
1067 expected.type_bufnr = 0
1068 assert_equal([expected], prop_list(4))
1070 assert_equal([], prop_list(1))
1071 assert_equal([], prop_list(3))
1072 assert_equal([], prop_list(4))
1074 cursor(5, 3) # if (arg == 2) {
1075 var output = execute('LspHighlight')->split("\n")
1076 assert_equal('Warn: No highlight for the current position', output[0])
1080 # Test for :LspHover
1081 def g:Test_LspHover()
1082 silent! edit XLspHover.c
1084 var lines: list<string> =<< trim END
1098 if clangdVerMajor > 14
1099 g:WaitForServerFileLoad(1)
1101 g:WaitForServerFileLoad(0)
1104 var output = execute(':LspHover')->split("\n")
1105 assert_equal([], output)
1106 var p: list<number> = popup_list()
1107 assert_equal(1, p->len())
1108 assert_equal(['### function `f1` ', '', '---', '→ `int` ', 'Parameters: ', '- `int a`', '', '---', '```cpp', 'int f1(int a)', '```'], getbufline(winbufnr(p[0]), 1, '$'))
1111 output = execute(':LspHover')->split("\n")
1112 assert_equal('Warn: No documentation found for current keyword', output[0])
1113 output = execute(':silent LspHover')->split("\n")
1114 assert_equal([], output)
1115 assert_equal([], popup_list())
1117 # Show current diagnostic as to open another popup.
1118 # Then we can test that LspHover closes all existing popups
1121 assert_equal(1, popup_list()->len())
1123 assert_equal(1, popup_list()->len())
1129 # Test for :LspShowSignature
1130 def g:Test_LspShowSignature()
1131 silent! edit XLspShowSignature.c
1133 var lines: list<string> =<< trim END
1134 int MyFunc(int a, int b)
1145 g:WaitForServerFileLoad(2)
1148 var p: list<number> = popup_list()
1149 var bnr: number = winbufnr(p[0])
1150 assert_equal(1, p->len())
1151 assert_equal(['MyFunc(int a, int b) -> int'], getbufline(bnr, 1, '$'))
1152 var expected: dict<any>
1153 expected = {id: 0, col: 8, end: 1, type: 'signature', length: 5, start: 1}
1154 expected.type_bufnr = bnr
1155 assert_equal([expected], prop_list(1, {bufnr: bnr}))
1158 setline(line('.'), ' MyFunc(10, ')
1162 bnr = winbufnr(p[0])
1163 assert_equal(1, p->len())
1164 assert_equal(['MyFunc(int a, int b) -> int'], getbufline(bnr, 1, '$'))
1165 expected = {id: 0, col: 15, end: 1, type: 'signature', length: 5, start: 1}
1166 expected.type_bufnr = bnr
1167 assert_equal([expected], prop_list(1, {bufnr: bnr}))
1172 # Test for :LspSymbolSearch
1173 def g:Test_LspSymbolSearch()
1174 silent! edit XLspSymbolSearch.c
1176 var lines: list<string> =<< trim END
1177 void lsptest_funcA()
1181 void lsptest_funcB()
1185 void lsptest_funcC()
1190 g:WaitForServerFileLoad(0)
1193 feedkeys(":LspSymbolSearch lsptest_funcB\<CR>", "xt")
1194 assert_equal([5, 6], [line('.'), col('.')])
1197 feedkeys(":LspSymbolSearch lsptest_func\<CR>\<Down>\<Down>\<CR>", "xt")
1198 assert_equal([9, 6], [line('.'), col('.')])
1201 feedkeys(":LspSymbolSearch lsptest_func\<CR>A\<BS>B\<CR>", "xt")
1202 assert_equal([5, 6], [line('.'), col('.')])
1204 var output = execute(':LspSymbolSearch lsptest_nonexist')->split("\n")
1205 assert_equal('Warn: Symbol "lsptest_nonexist" is not found', output[0])
1210 # Test for :LspIncomingCalls
1211 def g:Test_LspIncomingCalls()
1212 silent! edit XLspIncomingCalls.c
1214 var lines: list<string> =<< trim END
1215 void xFuncIncoming(void)
1219 void aFuncIncoming(void)
1224 void bFuncIncoming(void)
1230 g:WaitForServerFileLoad(0)
1233 assert_equal([1, 2], [winnr(), winnr('$')])
1234 var l = getline(1, '$')
1235 assert_equal('# Incoming calls to "xFuncIncoming"', l[0])
1236 assert_match('- xFuncIncoming (XLspIncomingCalls.c \[.*\])', l[1])
1237 assert_match(' + aFuncIncoming (XLspIncomingCalls.c \[.*\])', l[2])
1238 assert_match(' + bFuncIncoming (XLspIncomingCalls.c \[.*\])', l[3])
1242 # Test for :LspOutline
1243 def g:Test_LspOutline()
1244 silent! edit XLspOutline.c
1246 var lines: list<string> =<< trim END
1247 void aFuncOutline(void)
1251 void bFuncOutline(void)
1256 g:WaitForServerFileLoad(0)
1257 var winid = win_getid()
1259 assert_equal(2, winnr('$'))
1260 var bnum = winbufnr(winid + 1)
1261 assert_equal('LSP-Outline', bufname(bnum))
1262 assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
1264 # Validate position vert topleft
1265 assert_equal(['row', [['leaf', winid + 1], ['leaf', winid]]], winlayout())
1267 # Validate default width is 20
1268 assert_equal(20, winwidth(winid + 1))
1270 execute $':{bnum}bw'
1272 # Validate position vert botright
1273 g:LspOptionsSet({ outlineOnRight: true })
1275 assert_equal(2, winnr('$'))
1276 bnum = winbufnr(winid + 2)
1277 assert_equal('LSP-Outline', bufname(bnum))
1278 assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
1279 assert_equal(['row', [['leaf', winid], ['leaf', winid + 2]]], winlayout())
1280 g:LspOptionsSet({ outlineOnRight: false })
1281 execute $':{bnum}bw'
1283 # Validate <mods> position botright (below)
1284 :botright LspOutline
1285 assert_equal(2, winnr('$'))
1286 bnum = winbufnr(winid + 3)
1287 assert_equal('LSP-Outline', bufname(bnum))
1288 assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
1289 assert_equal(['col', [['leaf', winid], ['leaf', winid + 3]]], winlayout())
1290 execute $':{bnum}bw'
1292 # Validate that outlineWinSize works for LspOutline
1293 g:LspOptionsSet({ outlineWinSize: 40 })
1295 assert_equal(2, winnr('$'))
1296 bnum = winbufnr(winid + 4)
1297 assert_equal('LSP-Outline', bufname(bnum))
1298 assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
1299 assert_equal(40, winwidth(winid + 4))
1300 execute $':{bnum}bw'
1301 g:LspOptionsSet({ outlineWinSize: 20 })
1303 # Validate that <count> works for LspOutline
1305 assert_equal(2, winnr('$'))
1306 bnum = winbufnr(winid + 5)
1307 assert_equal('LSP-Outline', bufname(bnum))
1308 assert_equal(['Function', ' aFuncOutline', ' bFuncOutline'], getbufline(bnum, 4, '$'))
1309 assert_equal(37, winwidth(winid + 5))
1310 execute $':{bnum}bw'
1315 # Test for setting the 'tagfunc'
1316 def g:Test_LspTagFunc()
1317 var lines: list<string> =<< trim END
1332 writefile(lines, 'Xtagfunc.c')
1333 :silent! edit Xtagfunc.c
1334 g:WaitForServerFileLoad(1)
1335 :setlocal tagfunc=lsp#lsp#TagFunc
1337 :exe "normal \<C-]>"
1338 assert_equal([11, 6], [line('.'), col('.')])
1340 assert_fails('exe "normal \<C-]>"', 'E433: No tags file')
1344 delete('Xtagfunc.c')
1347 # Test for the LspDiagsUpdated autocmd
1348 def g:Test_LspDiagsUpdated_Autocmd()
1350 autocmd_add([{event: 'User', pattern: 'LspDiagsUpdated', cmd: 'g:LspAutoCmd = g:LspAutoCmd + 1'}])
1351 silent! edit XLspDiagsAutocmd.c
1353 var lines: list<string> =<< trim END
1354 void aFuncDiag(void)
1360 g:WaitForServerFileLoad(0)
1361 setline(3, ' return:')
1364 setline(3, ' return;')
1368 autocmd_delete([{event: 'User', pattern: 'LspDiagsUpdated'}])
1369 assert_equal(5, g:LspAutoCmd)
1372 # Test custom notification handlers
1373 def g:Test_LspCustomNotificationHandlers()
1375 g:LSPTest_customNotificationHandlerReplied = false
1377 silent! edit XcustomNotification.c
1379 var lines: list<string> =<< trim END
1386 g:WaitForAssert(() => assert_equal(true, g:LSPTest_customNotificationHandlerReplied))
1390 def g:Test_ScanFindIdent()
1391 :silent! edit XscanFindIdent.c
1393 var lines: list<string> =<< trim END
1403 g:WaitForServerFileLoad(0)
1406 # LspGotoDefinition et al
1408 assert_equal([], execute('LspGotoDefinition')->split("\n"))
1409 assert_equal([2, 14], [line('.'), col('.')])
1412 assert_equal([], execute('LspGotoDefinition')->split("\n"))
1413 assert_equal([1, 5], [line('.'), col('.')])
1417 assert_equal([], execute('LspShowReferences')->split("\n"))
1422 assert_equal([], execute('LspRename counterFI')->split("\n"))
1424 assert_equal('int counterFI;', getline(1))
1425 assert_equal(' return counterFI + 1;', getline(6))
1430 # Test for doing omni completion from the first column
1431 def g:Test_OmniComplete_FirstColumn()
1432 :silent! edit XOmniCompleteFirstColumn.c
1434 var lines: list<string> =<< trim END
1435 typedef struct Foo_ {
1441 g:WaitForServerFileLoad(0)
1444 feedkeys("G0i\<C-X>\<C-O>", 'xt')
1445 assert_equal('Foo_t#define FOO 1', getline('.'))
1449 # Test for doing omni completion from the first column
1450 def g:Test_OmniComplete_Multibyte()
1451 :silent! edit XOmniCompleteMultibyte.c
1453 var lines: list<string> =<< trim END
1458 int len = strlen("©©©©©") + thisVar;
1462 g:WaitForServerFileLoad(0)
1466 feedkeys("cwthis\<C-X>\<C-O>", 'xt')
1467 assert_equal(' int len = strlen("©©©©©") + thisVar;', getline('.'))
1471 # Test for doing omni completion from the first column
1472 def g:Test_OmniComplete_Struct()
1473 :silent! edit XOmniCompleteStruct.c
1475 var lines: list<string> =<< trim END
1483 struct test_ myTest;
1484 struct test_ *pTest;
1490 g:WaitForServerFileLoad(0)
1494 feedkeys("cwb\<C-X>\<C-O>\<C-N>\<C-Y>", 'xt')
1495 assert_equal(' myTest.baz = 10;', getline('.'))
1497 feedkeys("cw\<C-X>\<C-O>\<C-N>\<C-N>\<C-Y>", 'xt')
1498 assert_equal(' pTest->foo = 20;', getline('.'))
1502 # Test for inlay hints
1503 def g:Test_InlayHints()
1504 :silent! edit XinlayHints.c
1506 var lines: list<string> =<< trim END
1507 void func1(int a, int b)
1517 g:WaitForServerFileLoad(0)
1520 assert_equal([], prop_list(7))
1522 :LspInlayHints enable
1523 var p = prop_list(7)
1524 assert_equal([9, 'LspInlayHintsParam'], [p[0].col, p[0].type])
1525 assert_equal([13, 'LspInlayHintsParam'], [p[1].col, p[1].type])
1527 :LspInlayHints disable
1528 assert_equal([], prop_list(7))
1530 g:LspOptionsSet({showInlayHints: true})
1531 assert_equal([9, 'LspInlayHintsParam'], [p[0].col, p[0].type])
1532 assert_equal([13, 'LspInlayHintsParam'], [p[1].col, p[1].type])
1534 g:LspOptionsSet({showInlayHints: false})
1535 assert_equal([], prop_list(7))
1538 :LspInlayHints enable
1540 assert_equal([9, 'LspInlayHintsParam'], [p[0].col, p[0].type])
1541 assert_equal([13, 'LspInlayHintsParam'], [p[1].col, p[1].type])
1544 :LspInlayHints disable
1546 assert_equal([], prop_list(7))
1551 # Test for reloading a modified buffer with diags
1552 def g:Test_ReloadBufferWithDiags()
1553 var lines: list<string> =<< trim END
1554 void ReloadBufferFunc1(void)
1559 writefile(lines, 'Xreloadbuffer.c')
1560 :silent! edit Xreloadbuffer.c
1561 g:WaitForServerFileLoad(1)
1562 var signs = sign_getplaced('%', {group: '*'})[0].signs
1563 assert_equal(3, signs[0].lnum)
1565 signs = sign_getplaced('%', {group: '*'})[0].signs
1566 assert_equal(5, signs[0].lnum)
1569 signs = sign_getplaced('%', {group: '*'})[0].signs
1570 assert_equal(3, signs[0].lnum)
1573 delete('Xreloadbuffer.c')
1576 # Test for the :LspServer command.
1577 def g:Test_LspServer()
1579 assert_equal(['Warn: No Lsp servers found for "a.raku"'],
1580 execute('LspServer debug on')->split("\n"))
1581 assert_equal(['Warn: No Lsp servers found for "a.raku"'],
1582 execute('LspServer restart')->split("\n"))
1583 assert_equal(['Warn: No Lsp servers found for "a.raku"'],
1584 execute('LspServer show status')->split("\n"))
1585 assert_equal(['Warn: No Lsp servers found for "a.raku"'],
1586 execute('LspServer trace verbose')->split("\n"))
1587 assert_equal(['Error: LspServer - Unsupported argument "xyz"'],
1588 execute('LspServer xyz')->split("\n"))
1589 assert_equal(['Error: Argument required'],
1590 execute('LspServer debug')->split("\n"))
1591 assert_equal(['Error: Unsupported argument "xyz"'],
1592 execute('LspServer debug xyz')->split("\n"))
1593 assert_equal(['Error: Unsupported argument "on xyz"'],
1594 execute('LspServer debug on xyz')->split("\n"))
1595 assert_equal(['Error: Argument required'],
1596 execute('LspServer show')->split("\n"))
1597 assert_equal(['Error: Unsupported argument "xyz"'],
1598 execute('LspServer show xyz')->split("\n"))
1599 assert_equal(['Error: Unsupported argument "status xyz"'],
1600 execute('LspServer show status xyz')->split("\n"))
1601 assert_equal(['Error: Argument required'],
1602 execute('LspServer trace')->split("\n"))
1603 assert_equal(['Error: Unsupported argument "xyz"'],
1604 execute('LspServer trace xyz')->split("\n"))
1605 assert_equal(['Error: Unsupported argument "verbose xyz"'],
1606 execute('LspServer trace verbose xyz')->split("\n"))
1611 # 1. Add a test for autocompletion with a single match while ignoring case.
1612 # After the full matched name is typed, the completion popup should still
1613 # be displayed. e.g.
1616 # int abc = myvar<C-N><C-Y>
1617 # 2. Add a test for jumping to a non-existing symbol definition, declaration.
1619 # Start the C language server. Returns true on success and false on failure.
1620 def g:StartLangServer(): bool
1621 return g:StartLangServerWithFile('Xtest.c')
1624 # vim: shiftwidth=2 softtabstop=2 noexpandtab