]> Sergey Matveev's repositories - vim-lsp.git/blob - test/clangd_tests.vim
Diags are not highlighted after a buffer is reloaded
[vim-lsp.git] / test / clangd_tests.vim
1 vim9script
2 # Unit tests for Vim Language Server Protocol (LSP) clangd client
3
4 source common.vim
5
6 var lspOpts = {autoComplete: false, highlightDiagInline: true}
7 g:LspOptionsSet(lspOpts)
8
9 g:LSPTest_modifyDiags = false
10
11 var lspServers = [{
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
19         }
20       },
21       processDiagHandler: (diags: list<dict<any>>) => {
22         if g:LSPTest_modifyDiags != true
23           return diags
24         endif
25
26         return diags->map((ix, diag) => {
27           diag.message = $'this is overridden'
28           return diag
29         })
30       }
31   }]
32 call LspAddServer(lspServers)
33
34 var clangdVerDetail = systemlist($'{shellescape(lspServers[0].path)} --version')
35 var clangdVerMajor = clangdVerDetail->matchstr('.*version \d\+\..*')->substitute('.* \(\d\+\)\..*', '\1', 'g')->str2nr()
36 echomsg clangdVerDetail
37
38
39 # Test for formatting a file using LspFormat
40 def g:Test_LspFormat()
41   :silent! edit XLspFormat.c
42   sleep 200m
43   setline(1, ['  int i;', '  int j;'])
44   :redraw!
45   :LspFormat
46   assert_equal(['int i;', 'int j;'], getline(1, '$'))
47
48   deletebufline('', 1, '$')
49   setline(1, ['int f1(int i)', '{', 'int j = 10; return j;', '}'])
50   :redraw!
51   :LspFormat
52   assert_equal(['int f1(int i) {', '  int j = 10;', '  return j;', '}'],
53                                                         getline(1, '$'))
54
55   deletebufline('', 1, '$')
56   setline(1, ['', 'int     i;'])
57   :redraw!
58   :LspFormat
59   assert_equal(['', 'int i;'], getline(1, '$'))
60
61   deletebufline('', 1, '$')
62   setline(1, [' int i;'])
63   :redraw!
64   :LspFormat
65   assert_equal(['int i;'], getline(1, '$'))
66
67   deletebufline('', 1, '$')
68   setline(1, ['  int  i; '])
69   :redraw!
70   :LspFormat
71   assert_equal(['int i;'], getline(1, '$'))
72
73   deletebufline('', 1, '$')
74   setline(1, ['int  i;', '', '', ''])
75   :redraw!
76   :LspFormat
77   assert_equal(['int i;'], getline(1, '$'))
78
79   deletebufline('', 1, '$')
80   setline(1, ['int f1(){int x;int y;x=1;y=2;return x+y;}'])
81   :redraw!
82   :LspFormat
83   var expected: list<string> =<< trim END
84     int f1() {
85       int x;
86       int y;
87       x = 1;
88       y = 2;
89       return x + y;
90     }
91   END
92   assert_equal(expected, getline(1, '$'))
93
94   deletebufline('', 1, '$')
95   setline(1, ['', '', '', ''])
96   :redraw!
97   :LspFormat
98   assert_equal([''], getline(1, '$'))
99
100   deletebufline('', 1, '$')
101   var lines: list<string> =<< trim END
102     int f1() {
103       int i, j;
104         for (i = 1; i < 10; i++) { j++; }
105         for (j = 1; j < 10; j++) { i++; }
106     }
107   END
108   setline(1, lines)
109   :redraw!
110   :4LspFormat
111   expected =<< trim END
112     int f1() {
113       int i, j;
114         for (i = 1; i < 10; i++) { j++; }
115         for (j = 1; j < 10; j++) {
116           i++;
117         }
118     }
119   END
120   assert_equal(expected, getline(1, '$'))
121   bw!
122
123   # empty file
124   assert_equal('', execute('LspFormat'))
125
126   # file without an LSP server
127   edit a.raku
128   assert_equal('Error: Language server for "raku" file type supporting "documentFormatting" feature is not found',
129                execute('LspFormat')->split("\n")[0])
130
131   :%bw!
132 enddef
133
134 # Test for formatting a file using 'formatexpr'
135 def g:Test_LspFormatExpr()
136   :silent! edit XLspFormat.c
137   sleep 200m
138   setlocal formatexpr=lsp#lsp#FormatExpr()
139   setline(1, ['  int i;', '  int j;'])
140   :redraw!
141   normal! ggVGgq
142   assert_equal(['int i;', 'int j;'], getline(1, '$'))
143
144   # empty line/file
145   deletebufline('', 1, '$')
146   setline(1, [''])
147   redraw!
148   normal! ggVGgq
149   assert_equal([''], getline(1, '$'))
150
151   setlocal formatexpr&
152   :%bw!
153 enddef
154
155 # Test for :LspShowReferences - showing all the references to a symbol in a
156 # file using LSP
157 def g:Test_LspShowReferences()
158   :silent! edit XshowRefs.c
159   sleep 200m
160   var lines: list<string> =<< trim END
161     int count;
162     void redFunc()
163     {
164         int count, i;
165         count = 10;
166         i = count;
167     }
168     void blueFunc()
169     {
170         int count, j;
171         count = 20;
172         j = count;
173     }
174   END
175   setline(1, lines)
176   :redraw!
177   cursor(5, 2)
178   var bnr: number = bufnr()
179   :LspShowReferences
180   sleep 100m
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])
188   :lclose
189   cursor(1, 5)
190   :LspShowReferences
191   assert_equal(1, getloclist(0)->len())
192   loclist = getloclist(0)
193   assert_equal([1, 5], [loclist[0].lnum, loclist[0].col])
194   :lclose
195
196   # Test for opening in qf list
197   g:LspOptionsSet({ useQuickfixForLocations: true })
198   cursor(5, 2)
199   :LspShowReferences
200   sleep 100m
201   assert_equal('quickfix', getwinvar(winnr('$'), '&buftype'))
202   :cclose
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])
209   cursor(1, 5)
210   :LspShowReferences
211   assert_equal(1, getqflist()->len())
212   qfl = getqflist()
213   assert_equal([1, 5], [qfl[0].lnum, qfl[0].col])
214   :cclose
215   g:LspOptionsSet({ useQuickfixForLocations: false })
216
217   # Test for maintaining buffer focus
218   g:LspOptionsSet({ keepFocusInReferences: false })
219   :LspShowReferences
220   assert_equal('', getwinvar(0, '&buftype'))
221   :lclose
222   g:LspOptionsSet({ keepFocusInReferences: true })
223
224   # Test for LspPeekReferences
225
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.
229   setlocal nomodified
230   cursor(10, 6)
231   :LspPeekReferences
232   sleep 50m
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())
245   popup_clear()
246
247   # LspShowReferences should start with the current symbol
248   cursor(12, 6)
249   :LspPeekReferences
250   sleep 50m
251   ids = popup_list()
252   assert_equal(2, ids->len())
253   assert_equal(12, line('.', ids[0]))
254   assert_equal(3, line('.', ids[1]))
255   feedkeys("\<CR>", 'xt')
256   popup_clear()
257
258   bw!
259
260   # empty file
261   assert_equal('', execute('LspShowReferences'))
262
263   # file without an LSP server
264   edit a.raku
265   assert_equal('Error: Language server for "raku" file type supporting "references" feature is not found',
266                execute('LspShowReferences')->split("\n")[0])
267
268   :%bw!
269 enddef
270
271 # Test for LSP diagnostics
272 def g:Test_LspDiag()
273   :silent! edit XLspDiag.c
274   sleep 200m
275   var lines: list<string> =<< trim END
276     void blueFunc()
277     {
278         int count, j:
279         count = 20;
280         j <= count;
281         j = 10;
282         MyFunc();
283     }
284   END
285   setline(1, lines)
286   g:WaitForServerFileLoad(1)
287   var bnr: number = bufnr()
288   :redraw!
289   :LspDiagShow
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])
297   close
298   g:LspOptionsSet({showDiagInPopup: false})
299   normal gg
300   var output = execute('LspDiagCurrent')->split("\n")
301   assert_equal('Warn: No diagnostic messages found for current line', output[0])
302   :LspDiagFirst
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])
306   :normal! 0
307   :LspDiagHere
308   assert_equal([3, 14], [line('.'), col('.')])
309   :LspDiagNext
310   assert_equal([5, 2], [line('.'), col('.')])
311   :LspDiagNext
312   assert_equal([7, 2], [line('.'), col('.')])
313   output = execute('LspDiagNext')->split("\n")
314   assert_equal('Warn: No more diagnostics found', output[0])
315   :LspDiagPrev
316   :LspDiagPrev
317   :LspDiagPrev
318   output = execute('LspDiagPrev')->split("\n")
319   assert_equal('Warn: No more diagnostics found', output[0])
320
321   # Test for maintaining buffer focus
322   g:LspOptionsSet({ keepFocusInDiags: false })
323   :LspDiagShow
324   assert_equal('', getwinvar(0, '&buftype'))
325   :lclose
326   g:LspOptionsSet({ keepFocusInDiags: true })
327
328   # :[count]LspDiagNext
329   cursor(3, 1)
330   :2LspDiagNext
331   assert_equal([5, 2], [line('.'), col('.')])
332   :2LspDiagNext
333   assert_equal([7, 2], [line('.'), col('.')])
334   output = execute(':2LspDiagNext')->split("\n")
335   assert_equal('Warn: No more diagnostics found', output[0])
336
337   # :[count]LspDiagPrev
338   cursor(7, 2)
339   :4LspDiagPrev
340   assert_equal([3, 14], [line('.'), col('.')])
341   output = execute(':4LspDiagPrev')->split("\n")
342   assert_equal('Warn: No more diagnostics found', output[0])
343
344   :%d
345   setline(1, ['void blueFunc()', '{', '}'])
346   g:WaitForDiags(0)
347   output = execute('LspDiagShow')->split("\n")
348   assert_match('Warn: No diagnostic messages found for', output[0])
349   g:LspOptionsSet({showDiagInPopup: true})
350
351   popup_clear()
352   :%bw!
353 enddef
354
355 # Test for LSP diagnostics handler
356 def g:Test_LspProcessDiagHandler()
357   g:LSPTest_modifyDiags = true
358   g:LspOptionsSet({showDiagInPopup: false})
359
360   :silent! edit XLspProcessDiag.c
361   sleep 200m
362   var lines: list<string> =<< trim END
363     void blueFunc()
364     {
365         int count, j:
366     }
367   END
368   setline(1, lines)
369   g:WaitForServerFileLoad(1)
370   :redraw!
371   normal gg
372
373   :LspDiagFirst
374   assert_equal([3, 14], [line('.'), col('.')])
375
376   var output = execute('LspDiagCurrent')->split("\n")
377   assert_equal("this is overridden", output[0])
378
379   g:LspOptionsSet({showDiagInPopup: true})
380   g:LSPTest_modifyDiags = false
381   :%bw!
382 enddef
383
384 # Diag location list should be automatically updated when the list of diags
385 # changes.
386 def g:Test_DiagLocListAutoUpdate()
387   :silent! edit XdiagLocListAutoUpdate.c
388   :sleep 200m
389   setloclist(0, [], 'f')
390   var lines: list<string> =<< trim END
391     int i:
392     int j;
393   END
394   setline(1, lines)
395   var bnr = bufnr()
396   g:WaitForServerFileLoad(1)
397   :redraw!
398   var d = lsp#diag#GetDiagsForBuf()[0]
399   assert_equal({start: {line: 0, character: 5}, end: {line: 0, character: 6}},
400                d.range)
401
402   :LspDiagShow
403   assert_equal(1, line('$'))
404   wincmd w
405   setline(2, 'int j:')
406   redraw!
407   g:WaitForDiags(2)
408   var l = lsp#diag#GetDiagsForBuf()
409   assert_equal({start: {line: 0, character: 5}, end: {line: 0, character: 6}},
410                l[0].range)
411   assert_equal({start: {line: 1, character: 5}, end: {line: 1, character: 6}},
412                l[1].range)
413   wincmd w
414   assert_equal(2, line('$'))
415   wincmd w
416   deletebufline('', 1, '$')
417   redraw!
418   g:WaitForDiags(0)
419   assert_equal([], lsp#diag#GetDiagsForBuf())
420   wincmd w
421   assert_equal([''], getline(1, '$'))
422   :lclose
423
424   setloclist(0, [], 'f')
425   :%bw!
426 enddef
427
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
431   sleep 200m
432   var lines: list<string> =<< trim END
433     int count;
434     int fn(int a)
435     {
436       int 😊😊😊😊;
437       😊😊😊😊 = a;
438
439       int b;
440       b = a;
441       return    count + 1;
442     }
443   END
444   setline(1, lines)
445   :redraw!
446
447   cursor(5, 1) # 😊😊😊😊 = a;
448   search('a')
449   assert_equal([],
450               execute('LspGotoDefinition')->split("\n"))
451   assert_equal([2, 12], [line('.'), col('.')])
452
453   cursor(8, 1) # b = a;
454   search('a')
455   assert_equal([],
456               execute('LspGotoDefinition')->split("\n"))
457   assert_equal([2, 12], [line('.'), col('.')])
458
459   :%bw!
460 enddef
461
462 # Test for multiple LSP diagnostics on the same line
463 def g:Test_LspDiag_Multi()
464   :silent! edit XLspDiagMulti.c
465   sleep 200m
466
467   var bnr: number = bufnr()
468
469   var lines =<< trim END
470     int i = "a";
471     int j = i;
472     int y = 0;
473   END
474   setline(1, lines)
475   :redraw!
476   # TODO: Waiting count doesn't include Warning, Info, and Hint diags
477   if clangdVerMajor > 14
478         g:WaitForServerFileLoad(3)
479   else
480         g:WaitForServerFileLoad(2)
481   endif
482   :LspDiagShow
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])
489   else
490         assert_equal([1, 5, 'W'], [qfl[0].lnum, qfl[0].col, qfl[0].type])
491   endif
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])
494   close
495
496   :sleep 100m
497   cursor(2, 1)
498   assert_equal('', execute('LspDiagPrev'))
499   assert_equal([1, 9], [line('.'), col('.')])
500
501   assert_equal('', execute('LspDiagPrev'))
502   assert_equal([1, 5], [line('.'), col('.')])
503
504   var output = execute('LspDiagPrev')->split("\n")
505   assert_equal('Warn: No more diagnostics found', output[0])
506
507   cursor(2, 1)
508   assert_equal('', execute('LspDiagFirst'))
509   assert_equal([1, 5], [line('.'), col('.')])
510   assert_equal('', execute('LspDiagNext'))
511   assert_equal([1, 9], [line('.'), col('.')])
512   cursor(1, 1)
513   assert_equal('', execute('LspDiagLast'))
514   assert_equal([2, 9], [line('.'), col('.')])
515   popup_clear()
516
517   # Test for :LspDiagHere on a line with multiple diagnostics
518   cursor(1, 1)
519   :LspDiagHere
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])
524   popup_clear()
525   cursor(1, 6)
526   :LspDiagHere
527   assert_equal([1, 9], [line('.'), col('.')])
528   ids = popup_list()
529   assert_equal(1, ids->len())
530   assert_match('Initializer element is not', getbufline(ids[0]->winbufnr(), 1, '$')[0])
531   popup_clear()
532
533   # Line without diagnostics
534   cursor(3, 1)
535   output = execute('LspDiagHere')->split("\n")
536   assert_equal('Warn: No more diagnostics found on this line', output[0])
537
538   g:LspOptionsSet({showDiagInPopup: false})
539   for i in range(1, 5)
540     cursor(1, i)
541     output = execute('LspDiagCurrent')->split('\n')
542     assert_match('Incompatible pointer to integer', output[0])
543   endfor
544   for i in range(6, 12)
545     cursor(1, i)
546     output = execute('LspDiagCurrent')->split('\n')
547     assert_match('Initializer element is not ', output[0])
548   endfor
549   g:LspOptionsSet({showDiagInPopup: true})
550
551   # Check for exact diag ":LspDiagCurrent!"
552   g:LspOptionsSet({showDiagInPopup: false})
553   for i in range(1, 4)
554     cursor(1, i)
555     output = execute('LspDiagCurrent!')->split('\n')
556     assert_equal('Warn: No diagnostic messages found for current position', output[0])
557   endfor
558
559   cursor(1, 5)
560   output = execute('LspDiagCurrent!')->split('\n')
561   assert_match('Incompatible pointer to integer', output[0])
562
563   for i in range(6, 8)
564     cursor(1, i)
565     output = execute('LspDiagCurrent!')->split('\n')
566     assert_equal('Warn: No diagnostic messages found for current position', output[0])
567   endfor
568
569   for i in range(9, 11)
570     cursor(1, i)
571     output = execute('LspDiagCurrent!')->split('\n')
572     assert_match('Initializer element is not ', output[0])
573   endfor
574   for i in range(12, 12)
575     cursor(1, i)
576     output = execute('LspDiagCurrent!')->split('\n')
577     assert_equal('Warn: No diagnostic messages found for current position', output[0])
578   endfor
579
580   g:LspOptionsSet({showDiagInPopup: true})
581
582   # :[count]LspDiagNext
583   g:LspOptionsSet({showDiagInPopup: false})
584   cursor(1, 1)
585   :2LspDiagNext
586   assert_equal([1, 9], [line('.'), col('.')])
587   :2LspDiagNext
588   assert_equal([2, 9], [line('.'), col('.')])
589   output = execute(':2LspDiagNext')->split("\n")
590   assert_equal('Warn: No more diagnostics found', output[0])
591
592   cursor(1, 1)
593   :99LspDiagNext
594   assert_equal([2, 9], [line('.'), col('.')])
595   g:LspOptionsSet({showDiagInPopup: true})
596
597   # :[count]LspDiagPrev
598   g:LspOptionsSet({showDiagInPopup: false})
599   cursor(1, 1)
600   :2LspDiagPrev
601   assert_equal('Warn: No more diagnostics found', output[0])
602   cursor(3, 3)
603   :2LspDiagPrev
604   assert_equal([1, 9], [line('.'), col('.')])
605   :2LspDiagPrev
606   assert_equal([1, 5], [line('.'), col('.')])
607   output = execute(':2LspDiagPrev')->split("\n")
608   assert_equal('Warn: No more diagnostics found', output[0])
609
610   cursor(3, 3)
611   :99LspDiagPrev
612   assert_equal([1, 5], [line('.'), col('.')])
613   g:LspOptionsSet({showDiagInPopup: true})
614
615   :%bw!
616 enddef
617
618 # Test for highlight diag inline
619 def g:Test_LspHighlightDiagInline()
620   :silent! edit XLspHighlightDiag.c
621   sleep 200m
622   setline(1, [
623     'int main()',
624     '{',
625       '    struct obj obj',
626       '',
627       '    return 1;',
628     '}',
629   ])
630
631   # TODO: Waiting count doesn't include Warning, Info, and Hint diags
632   g:WaitForDiags(2)
633
634   var props = prop_list(1)
635   assert_equal(0, props->len())
636   props = prop_list(2)
637   assert_equal(0, props->len())
638   props = prop_list(3)
639   assert_equal(2, props->len())
640   assert_equal([
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}
643   ], props)
644   props = prop_list(4)
645   assert_equal(0, props->len())
646   props = prop_list(5)
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)
649   props = prop_list(6)
650   assert_equal(0, props->len())
651
652   :%bw!
653 enddef
654
655 # Test for :LspCodeAction
656 def g:Test_LspCodeAction()
657   silent! edit XLspCodeAction.c
658   sleep 200m
659   var lines: list<string> =<< trim END
660     void testFunc()
661     {
662         int count;
663         count == 20;
664     }
665   END
666   setline(1, lines)
667   g:WaitForServerFileLoad(0)
668   cursor(4, 1)
669   redraw!
670   :LspCodeAction 1
671   assert_equal("\tcount = 20;", getline(4))
672
673   setline(4, "\tcount = 20:")
674   cursor(4, 1)
675   sleep 500m
676   :LspCodeAction 0
677   assert_equal("\tcount = 20:", getline(4))
678
679   cursor(4, 1)
680   :LspCodeAction 2
681   assert_equal("\tcount = 20:", getline(4))
682
683   cursor(4, 1)
684   :LspCodeAction 1
685   assert_equal("\tcount = 20;", getline(4))
686   bw!
687
688   # pattern and string prefix
689   silent! edit XLspCodeActionPattern.c
690   sleep 200m
691   var lines2: list<string> =<< trim END
692     void testFunc()
693     {
694         int count;
695         if (count = 1) {
696         }
697     }
698   END
699   setline(1, lines2)
700   g:WaitForServerFileLoad(0)
701   cursor(4, 1)
702   redraw!
703   :LspCodeAction use
704   assert_equal("\tif (count == 1) {", getline(4))
705
706   setline(4, "\tif (count = 1) {")
707   cursor(4, 1)
708   sleep 500m
709   :LspCodeAction /paren
710   assert_equal("\tif ((count = 1)) {", getline(4))
711
712   setline(4, "\tif (count = 1) {")
713   cursor(4, 1)
714   sleep 500m
715   :LspCodeAction NON_EXISTING_PREFIX
716   assert_equal("\tif (count = 1) {", getline(4))
717
718   cursor(4, 1)
719   :LspCodeAction /NON_EXISTING_REGEX
720   assert_equal("\tif (count = 1) {", getline(4))
721   bw!
722
723   # empty file
724   assert_equal('', execute('LspCodeAction'))
725
726   # file without an LSP server
727   edit a.raku
728   assert_equal('Error: Language server for "raku" file type supporting "codeAction" feature is not found',
729                execute('LspCodeAction')->split("\n")[0])
730
731   :%bw!
732 enddef
733
734 # Test for :LspRename
735 def g:Test_LspRename()
736   silent! edit XLspRename.c
737   sleep 200m
738   var lines: list<string> =<< trim END
739     void F1(int count)
740     {
741         count = 20;
742
743         ++count;
744     }
745
746     void F2(int count)
747     {
748         count = 5;
749     }
750   END
751   setline(1, lines)
752   g:WaitForServerFileLoad(0)
753   cursor(1, 1)
754   search('count')
755   redraw!
756   feedkeys(":LspRename\<CR>er\<CR>", "xt")
757   redraw!
758   var expected: list<string> =<< trim END
759     void F1(int counter)
760     {
761         counter = 20;
762
763         ++counter;
764     }
765
766     void F2(int count)
767     {
768         count = 5;
769     }
770   END
771   assert_equal(expected, getline(1, '$'))
772
773   cursor(1, 1)
774   search('counter')
775   LspRename countvar
776   var expected2: list<string> =<< trim END
777     void F1(int countvar)
778     {
779         countvar = 20;
780
781         ++countvar;
782     }
783
784     void F2(int count)
785     {
786         count = 5;
787     }
788   END
789   assert_equal(expected2, getline(1, '$'))
790   sleep 100m
791   bw!
792
793   # empty file
794   assert_equal('', execute('LspRename'))
795
796   # file without an LSP server
797   edit a.raku
798   assert_equal('Error: Language server for "raku" file type supporting "rename" feature is not found',
799                execute('LspRename')->split("\n")[0])
800
801   :%bw!
802 enddef
803
804 # Test for :LspSelectionExpand and :LspSelectionShrink
805 def g:Test_LspSelection()
806   silent! edit XLspSelection.c
807   sleep 200m
808   var lines: list<string> =<< trim END
809     void fnSel(int count)
810     {
811         int i;
812         for (i = 0; i < 10; i++) {
813            count++;
814         }
815         count = 20;
816     }
817   END
818   setline(1, lines)
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"
823   cursor(2, 1)
824   redraw!
825   :LspSelectionExpand
826   redraw!
827   normal! 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"
833   cursor(4, 29)
834   redraw!
835   :LspSelectionExpand
836   redraw!
837   normal! y
838   assert_equal('v', visualmode())
839   assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
840
841   # Expand the visual selection
842   xnoremap <silent> le <Cmd>LspSelectionExpand<CR>
843   xnoremap <silent> ls <Cmd>LspSelectionShrink<CR>
844   cursor(5, 8)
845   normal vley
846   assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
847   cursor(5, 8)
848   normal vleley
849   assert_equal([5, 8, 5, 14], [line("'<"), col("'<"), line("'>"), col("'>")])
850   cursor(5, 8)
851   normal vleleley
852   assert_equal([4, 30, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
853   cursor(5, 8)
854   normal vleleleley
855   assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
856   cursor(5, 8)
857   normal vleleleleley
858   assert_equal([2, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
859   cursor(5, 8)
860   normal vleleleleleley
861   assert_equal([1, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
862   cursor(5, 8)
863   normal vleleleleleleley
864   assert_equal([1, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
865
866   # Shrink the visual selection
867   cursor(5, 8)
868   normal vlsy
869   assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
870   cursor(5, 8)
871   normal vlelsy
872   assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
873   cursor(5, 8)
874   normal vlelelsy
875   assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
876   cursor(5, 8)
877   normal vlelelelsy
878   assert_equal([5, 8, 5, 14], [line("'<"), col("'<"), line("'>"), col("'>")])
879   cursor(5, 8)
880   normal vlelelelelsy
881   assert_equal([4, 30, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
882   cursor(5, 8)
883   normal vlelelelelelsy
884   assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
885   cursor(5, 8)
886   normal vlelelelelelelsy
887   assert_equal([2, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
888
889   xunmap le
890   xunmap ls
891   bw!
892
893   # empty file
894   assert_equal('', execute('LspSelectionExpand'))
895
896   # file without an LSP server
897   edit a.raku
898   assert_equal('Error: Language server for "raku" file type supporting "selectionRange" feature is not found',
899                execute('LspSelectionExpand')->split("\n")[0])
900
901   :%bw!
902 enddef
903
904 # Test for :LspGotoDefinition, :LspGotoDeclaration and :LspGotoImpl
905 def g:Test_LspGotoSymbol()
906   settagstack(0, {items: []})
907   silent! edit XLspGotoSymbol.cpp
908   sleep 600m
909   var lines: list<string> =<< trim END
910     class base {
911         public:
912             virtual void print();
913     };
914
915     void base::print()
916     {
917     }
918
919     class derived : public base {
920         public:
921             void print() {}
922     };
923
924     void f1(void)
925     {
926         base *bp;
927         derived d;
928         bp = &d;
929
930         bp->print();
931     }
932   END
933   setline(1, lines)
934   g:WaitForServerFileLoad(0)
935
936   cursor(21, 6)
937   :LspGotoDeclaration
938   assert_equal([3, 19], [line('.'), col('.')])
939   exe "normal! \<C-t>"
940   assert_equal([21, 6], [line('.'), col('.')])
941   assert_equal(1, winnr('$'))
942
943   :LspGotoDefinition
944   assert_equal([6, 12], [line('.'), col('.')])
945   exe "normal! \<C-t>"
946   assert_equal([21, 6], [line('.'), col('.')])
947   assert_equal(1, winnr('$'))
948
949   # Command modifiers
950   :topleft LspGotoDefinition
951   assert_equal([6, 12], [line('.'), col('.')])
952   assert_equal([1, 2], [winnr(), winnr('$')])
953   close
954   exe "normal! \<C-t>"
955   assert_equal([21, 6], [line('.'), col('.')])
956
957   :tab LspGotoDefinition
958   assert_equal([6, 12], [line('.'), col('.')])
959   assert_equal([2, 2, 1], [tabpagenr(), tabpagenr('$'), winnr('$')])
960   tabclose
961   exe "normal! \<C-t>"
962   assert_equal([21, 6], [line('.'), col('.')])
963
964   # :LspGotoTypeDef
965   cursor(21, 2)
966   :LspGotoTypeDef
967   assert_equal([1, 7], [line('.'), col('.')])
968   exe "normal! \<C-t>"
969   assert_equal([21, 2], [line('.'), col('.')])
970
971   # :LspGotoImpl
972   cursor(21, 6)
973   :LspGotoImpl
974   assert_equal([12, 11], [line('.'), col('.')])
975   exe "normal! \<C-t>"
976   assert_equal([21, 6], [line('.'), col('.')])
977
978   # FIXME: The following tests are failing in Github CI. Comment out for now.
979   if 0
980   # Error cases
981   :messages clear
982   cursor(11, 5)
983   :LspGotoDeclaration
984   var m = execute('messages')->split("\n")
985   assert_equal('symbol declaration is not found', m[1])
986   :messages clear
987   :LspGotoDefinition
988   m = execute('messages')->split("\n")
989   assert_equal('symbol definition is not found', m[1])
990   :messages clear
991   :LspGotoImpl
992   m = execute('messages')->split("\n")
993   assert_equal('symbol implementation is not found', m[1])
994   :messages clear
995   endif
996
997   # Test for LspPeekDeclaration
998   cursor(21, 6)
999   var bnum = bufnr()
1000   :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]))
1005   popup_clear()
1006   # tag stack should not be changed
1007   assert_fails("normal! \<C-t>", 'E555:')
1008
1009   # Test for LspPeekDefinition
1010   :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]))
1015   popup_clear()
1016   # tag stack should not be changed
1017   assert_fails("normal! \<C-t>", 'E555:')
1018
1019   # FIXME: :LspPeekTypeDef and :LspPeekImpl are supported only with clang-14.
1020   # This clangd version is not available in Github CI.
1021
1022   :%bw!
1023
1024   # empty file
1025   assert_equal('', execute('LspGotoDefinition'))
1026   assert_equal('', execute('LspGotoDeclaration'))
1027   assert_equal('', execute('LspGotoImpl'))
1028
1029   # file without an LSP server
1030   edit a.raku
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])
1037
1038   :%bw!
1039 enddef
1040
1041 # Test for :LspHighlight
1042 def g:Test_LspHighlight()
1043   silent! edit XLspHighlight.c
1044   sleep 200m
1045   var lines: list<string> =<< trim END
1046     void f1(int arg)
1047     {
1048       int i = arg;
1049       arg = 2;
1050       if (arg == 2) {
1051         arg = 3;
1052       }
1053     }
1054   END
1055   setline(1, lines)
1056   g:WaitForServerFileLoad(0)
1057   cursor(1, 13)
1058   :LspHighlight
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))
1069   :LspHighlightClear
1070   assert_equal([], prop_list(1))
1071   assert_equal([], prop_list(3))
1072   assert_equal([], prop_list(4))
1073
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])
1077   :%bw!
1078 enddef
1079
1080 # Test for :LspHover
1081 def g:Test_LspHover()
1082   silent! edit XLspHover.c
1083   sleep 200m
1084   var lines: list<string> =<< trim END
1085     int f1(int a)
1086     {
1087       return 0;
1088     }
1089
1090     void f2(void)
1091     {
1092       f1(5);
1093       char *z = "z";
1094       f1(z);
1095     }
1096   END
1097   setline(1, lines)
1098   if clangdVerMajor > 14
1099         g:WaitForServerFileLoad(1)
1100   else
1101         g:WaitForServerFileLoad(0)
1102   endif
1103   cursor(8, 4)
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, '$'))
1109   popup_close(p[0])
1110   cursor(7, 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())
1116
1117   # Show current diagnostic as to open another popup.
1118   # Then we can test that LspHover closes all existing popups
1119   cursor(10, 6)
1120   :LspDiagCurrent
1121   assert_equal(1, popup_list()->len())
1122   :LspHover
1123   assert_equal(1, popup_list()->len())
1124   popup_clear()
1125
1126   :%bw!
1127 enddef
1128
1129 # Test for :LspShowSignature
1130 def g:Test_LspShowSignature()
1131   silent! edit XLspShowSignature.c
1132   sleep 200m
1133   var lines: list<string> =<< trim END
1134     int MyFunc(int a, int b)
1135     {
1136       return 0;
1137     }
1138
1139     void f2(void)
1140     {
1141       MyFunc( 
1142     }
1143   END
1144   setline(1, lines)
1145   g:WaitForServerFileLoad(2)
1146   cursor(8, 10)
1147   :LspShowSignature
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}))
1156   popup_close(p[0])
1157
1158   setline(line('.'), '  MyFunc(10, ')
1159   cursor(8, 13)
1160   :LspShowSignature
1161   p = popup_list()
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}))
1168   popup_close(p[0])
1169   :%bw!
1170 enddef
1171
1172 # Test for :LspSymbolSearch
1173 def g:Test_LspSymbolSearch()
1174   silent! edit XLspSymbolSearch.c
1175   sleep 200m
1176   var lines: list<string> =<< trim END
1177     void lsptest_funcA()
1178     {
1179     }
1180
1181     void lsptest_funcB()
1182     {
1183     }
1184
1185     void lsptest_funcC()
1186     {
1187     }
1188   END
1189   setline(1, lines)
1190   g:WaitForServerFileLoad(0)
1191
1192   cursor(1, 1)
1193   feedkeys(":LspSymbolSearch lsptest_funcB\<CR>", "xt")
1194   assert_equal([5, 6], [line('.'), col('.')])
1195
1196   cursor(1, 1)
1197   feedkeys(":LspSymbolSearch lsptest_func\<CR>\<Down>\<Down>\<CR>", "xt")
1198   assert_equal([9, 6], [line('.'), col('.')])
1199
1200   cursor(1, 1)
1201   feedkeys(":LspSymbolSearch lsptest_func\<CR>A\<BS>B\<CR>", "xt")
1202   assert_equal([5, 6], [line('.'), col('.')])
1203
1204   var output = execute(':LspSymbolSearch lsptest_nonexist')->split("\n")
1205   assert_equal('Warn: Symbol "lsptest_nonexist" is not found', output[0])
1206
1207   :%bw!
1208 enddef
1209
1210 # Test for :LspIncomingCalls
1211 def g:Test_LspIncomingCalls()
1212   silent! edit XLspIncomingCalls.c
1213   sleep 200m
1214   var lines: list<string> =<< trim END
1215     void xFuncIncoming(void)
1216     {
1217     }
1218
1219     void aFuncIncoming(void)
1220     {
1221       xFuncIncoming();
1222     }
1223
1224     void bFuncIncoming(void)
1225     {
1226       xFuncIncoming();
1227     }
1228   END
1229   setline(1, lines)
1230   g:WaitForServerFileLoad(0)
1231   cursor(1, 6)
1232   :LspIncomingCalls
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])
1239   :%bw!
1240 enddef
1241
1242 # Test for :LspOutline
1243 def g:Test_LspOutline()
1244   silent! edit XLspOutline.c
1245   sleep 200m
1246   var lines: list<string> =<< trim END
1247     void aFuncOutline(void)
1248     {
1249     }
1250
1251     void bFuncOutline(void)
1252     {
1253     }
1254   END
1255   setline(1, lines)
1256   g:WaitForServerFileLoad(0)
1257   var winid = win_getid()
1258   :LspOutline
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, '$'))
1263
1264   # Validate position vert topleft
1265   assert_equal(['row', [['leaf', winid + 1], ['leaf', winid]]], winlayout())
1266
1267   # Validate default width is 20
1268   assert_equal(20, winwidth(winid + 1))
1269
1270   execute $':{bnum}bw'
1271
1272   # Validate position vert botright
1273   g:LspOptionsSet({ outlineOnRight: true })
1274   :LspOutline
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'
1282
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'
1291
1292   # Validate that outlineWinSize works for LspOutline
1293   g:LspOptionsSet({ outlineWinSize: 40 })
1294   :LspOutline
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 })
1302
1303   # Validate that <count> works for LspOutline
1304   :37LspOutline
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'
1311
1312   :%bw!
1313 enddef
1314
1315 # Test for setting the 'tagfunc'
1316 def g:Test_LspTagFunc()
1317   var lines: list<string> =<< trim END
1318     void aFuncTag(void)
1319     {
1320       xFuncTag();
1321     }
1322
1323     void bFuncTag(void)
1324     {
1325       xFuncTag();
1326     }
1327
1328     void xFuncTag(void)
1329     {
1330     }
1331   END
1332   writefile(lines, 'Xtagfunc.c')
1333   :silent! edit Xtagfunc.c
1334   g:WaitForServerFileLoad(1)
1335   :setlocal tagfunc=lsp#lsp#TagFunc
1336   cursor(3, 4)
1337   :exe "normal \<C-]>"
1338   assert_equal([11, 6], [line('.'), col('.')])
1339   cursor(1, 1)
1340   assert_fails('exe "normal \<C-]>"', 'E433: No tags file')
1341
1342   :set tagfunc&
1343   :%bw!
1344   delete('Xtagfunc.c')
1345 enddef
1346
1347 # Test for the LspDiagsUpdated autocmd
1348 def g:Test_LspDiagsUpdated_Autocmd()
1349   g:LspAutoCmd = 0
1350   autocmd_add([{event: 'User', pattern: 'LspDiagsUpdated', cmd: 'g:LspAutoCmd = g:LspAutoCmd + 1'}])
1351   silent! edit XLspDiagsAutocmd.c
1352   sleep 200m
1353   var lines: list<string> =<< trim END
1354     void aFuncDiag(void)
1355     {
1356         return;
1357     }
1358   END
1359   setline(1, lines)
1360   g:WaitForServerFileLoad(0)
1361   setline(3, '    return:')
1362   redraw!
1363   g:WaitForDiags(1)
1364   setline(3, '    return;')
1365   redraw!
1366   g:WaitForDiags(0)
1367   :%bw!
1368   autocmd_delete([{event: 'User', pattern: 'LspDiagsUpdated'}])
1369   assert_equal(5, g:LspAutoCmd)
1370 enddef
1371
1372 # Test custom notification handlers
1373 def g:Test_LspCustomNotificationHandlers()
1374
1375   g:LSPTest_customNotificationHandlerReplied = false
1376
1377   silent! edit XcustomNotification.c
1378   sleep 200m
1379   var lines: list<string> =<< trim END
1380     int a = 1;
1381     int main(void) {
1382       return a;
1383     }
1384   END
1385   setline(1, lines)
1386   g:WaitForAssert(() => assert_equal(true, g:LSPTest_customNotificationHandlerReplied))
1387   :%bw!
1388 enddef
1389
1390 def g:Test_ScanFindIdent()
1391   :silent! edit XscanFindIdent.c
1392   sleep 200m
1393   var lines: list<string> =<< trim END
1394     int countFI;
1395     int fnFI(int a)
1396     {
1397       int hello;
1398       hello =    a;
1399       return    countFI + 1;
1400     }
1401   END
1402   setline(1, lines)
1403   g:WaitForServerFileLoad(0)
1404   :redraw!
1405
1406   # LspGotoDefinition et al
1407   cursor(5, 10)
1408   assert_equal([], execute('LspGotoDefinition')->split("\n"))
1409   assert_equal([2, 14], [line('.'), col('.')])
1410
1411   cursor(6, 10)
1412   assert_equal([], execute('LspGotoDefinition')->split("\n"))
1413   assert_equal([1, 5], [line('.'), col('.')])
1414
1415   # LspShowReferences
1416   cursor(6, 10)
1417   assert_equal([], execute('LspShowReferences')->split("\n"))
1418   :lclose
1419
1420   # LspRename
1421   cursor(6, 10)
1422   assert_equal([], execute('LspRename counterFI')->split("\n"))
1423   sleep 100m
1424   assert_equal('int counterFI;', getline(1))
1425   assert_equal('  return    counterFI + 1;', getline(6))
1426
1427   :%bw!
1428 enddef
1429
1430 # Test for doing omni completion from the first column
1431 def g:Test_OmniComplete_FirstColumn()
1432   :silent! edit XOmniCompleteFirstColumn.c
1433   sleep 200m
1434   var lines: list<string> =<< trim END
1435     typedef struct Foo_ {
1436     } Foo_t;
1437
1438     #define FOO 1
1439   END
1440   setline(1, lines)
1441   g:WaitForServerFileLoad(0)
1442   redraw!
1443
1444   feedkeys("G0i\<C-X>\<C-O>", 'xt')
1445   assert_equal('Foo_t#define FOO 1', getline('.'))
1446   :%bw!
1447 enddef
1448
1449 # Test for doing omni completion from the first column
1450 def g:Test_OmniComplete_Multibyte()
1451   :silent! edit XOmniCompleteMultibyte.c
1452   sleep 200m
1453   var lines: list<string> =<< trim END
1454     #include <string.h>
1455     void Fn(void)
1456     {
1457       int thisVar = 1;
1458       int len = strlen("©©©©©") + thisVar;
1459     }
1460   END
1461   setline(1, lines)
1462   g:WaitForServerFileLoad(0)
1463   redraw!
1464
1465   cursor(5, 36)
1466   feedkeys("cwthis\<C-X>\<C-O>", 'xt')
1467   assert_equal('  int len = strlen("©©©©©") + thisVar;', getline('.'))
1468   :%bw!
1469 enddef
1470
1471 # Test for doing omni completion from the first column
1472 def g:Test_OmniComplete_Struct()
1473   :silent! edit XOmniCompleteStruct.c
1474   sleep 200m
1475   var lines: list<string> =<< trim END
1476     struct test_ {
1477         int foo;
1478         int bar;
1479         int baz;
1480     };
1481     void Fn(void)
1482     {
1483         struct test_ myTest;
1484         struct test_ *pTest;
1485         myTest.bar = 10;
1486         pTest->bar = 20;
1487     }
1488   END
1489   setline(1, lines)
1490   g:WaitForServerFileLoad(0)
1491   redraw!
1492
1493   cursor(10, 12)
1494   feedkeys("cwb\<C-X>\<C-O>\<C-N>\<C-Y>", 'xt')
1495   assert_equal('    myTest.baz = 10;', getline('.'))
1496   cursor(11, 12)
1497   feedkeys("cw\<C-X>\<C-O>\<C-N>\<C-N>\<C-Y>", 'xt')
1498   assert_equal('    pTest->foo = 20;', getline('.'))
1499   :%bw!
1500 enddef
1501
1502 # Test for inlay hints
1503 def g:Test_InlayHints()
1504   :silent! edit XinlayHints.c
1505   sleep 200m
1506   var lines: list<string> =<< trim END
1507     void func1(int a, int b)
1508     {
1509     }
1510
1511     void func2()
1512     {
1513       func1(10, 20);
1514     }
1515   END
1516   setline(1, lines)
1517   g:WaitForServerFileLoad(0)
1518   redraw!
1519
1520   assert_equal([], prop_list(7))
1521
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])
1526
1527   :LspInlayHints disable
1528   assert_equal([], prop_list(7))
1529
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])
1533
1534   g:LspOptionsSet({showInlayHints: false})
1535   assert_equal([], prop_list(7))
1536
1537   :hide enew
1538   :LspInlayHints enable
1539   :bprev
1540   assert_equal([9, 'LspInlayHintsParam'], [p[0].col, p[0].type])
1541   assert_equal([13, 'LspInlayHintsParam'], [p[1].col, p[1].type])
1542
1543   :hide enew
1544   :LspInlayHints disable
1545   :bprev
1546   assert_equal([], prop_list(7))
1547
1548   :%bw!
1549 enddef
1550
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)
1555     {
1556       int a:
1557     }
1558   END
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)
1564   append(0, ['', ''])
1565   signs = sign_getplaced('%', {group: '*'})[0].signs
1566   assert_equal(5, signs[0].lnum)
1567   :edit!
1568   sleep 200m
1569   signs = sign_getplaced('%', {group: '*'})[0].signs
1570   assert_equal(3, signs[0].lnum)
1571
1572   :%bw!
1573   delete('Xreloadbuffer.c')
1574 enddef
1575
1576 # Test for the :LspServer command.
1577 def g:Test_LspServer()
1578   new a.raku
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"))
1607   :%bw!
1608 enddef
1609
1610 # TODO:
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.
1614 #
1615 #      int MyVar = 1;
1616 #      int abc = myvar<C-N><C-Y>
1617 # 2. Add a test for jumping to a non-existing symbol definition, declaration.
1618
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')
1622 enddef
1623
1624 # vim: shiftwidth=2 softtabstop=2 noexpandtab