]> Sergey Matveev's repositories - vim-lsp.git/blob - test/clangd_tests.vim
62a832e4e590eec2ae3e75ead77121de226bd091
[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(3, line('$', ids[1]))
241   feedkeys("jj\<CR>", 'xt')
242   assert_equal(12, line('.'))
243   assert_equal([], popup_list())
244   popup_clear()
245
246   bw!
247
248   # empty file
249   assert_equal('', execute('LspShowReferences'))
250
251   # file without an LSP server
252   edit a.raku
253   assert_equal('Error: Language server for "raku" file type supporting "references" feature is not found',
254                execute('LspShowReferences')->split("\n")[0])
255
256   :%bw!
257 enddef
258
259 # Test for LSP diagnostics
260 def g:Test_LspDiag()
261   :silent! edit XLspDiag.c
262   sleep 200m
263   var lines: list<string> =<< trim END
264     void blueFunc()
265     {
266         int count, j:
267         count = 20;
268         j <= count;
269         j = 10;
270         MyFunc();
271     }
272   END
273   setline(1, lines)
274   g:WaitForServerFileLoad(1)
275   var bnr: number = bufnr()
276   :redraw!
277   :LspDiagShow
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])
285   close
286   g:LspOptionsSet({showDiagInPopup: false})
287   normal gg
288   var output = execute('LspDiagCurrent')->split("\n")
289   assert_equal('Warn: No diagnostic messages found for current line', output[0])
290   :LspDiagFirst
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])
294   :normal! 0
295   :LspDiagHere
296   assert_equal([3, 14], [line('.'), col('.')])
297   :LspDiagNext
298   assert_equal([5, 2], [line('.'), col('.')])
299   :LspDiagNext
300   assert_equal([7, 2], [line('.'), col('.')])
301   output = execute('LspDiagNext')->split("\n")
302   assert_equal('Warn: No more diagnostics found', output[0])
303   :LspDiagPrev
304   :LspDiagPrev
305   :LspDiagPrev
306   output = execute('LspDiagPrev')->split("\n")
307   assert_equal('Warn: No more diagnostics found', output[0])
308
309   # Test for maintaining buffer focus
310   g:LspOptionsSet({ keepFocusInDiags: false })
311   :LspDiagShow
312   assert_equal('', getwinvar(0, '&buftype'))
313   :lclose
314   g:LspOptionsSet({ keepFocusInDiags: true })
315
316   # :[count]LspDiagNext
317   cursor(3, 1)
318   :2LspDiagNext
319   assert_equal([5, 2], [line('.'), col('.')])
320   :2LspDiagNext
321   assert_equal([7, 2], [line('.'), col('.')])
322   output = execute(':2LspDiagNext')->split("\n")
323   assert_equal('Warn: No more diagnostics found', output[0])
324
325   # :[count]LspDiagPrev
326   cursor(7, 2)
327   :4LspDiagPrev
328   assert_equal([3, 14], [line('.'), col('.')])
329   output = execute(':4LspDiagPrev')->split("\n")
330   assert_equal('Warn: No more diagnostics found', output[0])
331
332   :%d
333   setline(1, ['void blueFunc()', '{', '}'])
334   g:WaitForDiags(0)
335   output = execute('LspDiagShow')->split("\n")
336   assert_match('Warn: No diagnostic messages found for', output[0])
337   g:LspOptionsSet({showDiagInPopup: true})
338
339   popup_clear()
340   :%bw!
341 enddef
342
343 # Test for LSP diagnostics handler
344 def g:Test_LspProcessDiagHandler()
345   g:LSPTest_modifyDiags = true
346   g:LspOptionsSet({showDiagInPopup: false})
347
348   :silent! edit XLspProcessDiag.c
349   sleep 200m
350   var lines: list<string> =<< trim END
351     void blueFunc()
352     {
353         int count, j:
354     }
355   END
356   setline(1, lines)
357   g:WaitForServerFileLoad(1)
358   :redraw!
359   normal gg
360
361   :LspDiagFirst
362   assert_equal([3, 14], [line('.'), col('.')])
363
364   var output = execute('LspDiagCurrent')->split("\n")
365   assert_equal("this is overridden", output[0])
366
367   g:LspOptionsSet({showDiagInPopup: true})
368   g:LSPTest_modifyDiags = false
369   :%bw!
370 enddef
371
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
375   sleep 200m
376   var lines: list<string> =<< trim END
377     int count;
378     int fn(int a)
379     {
380       int 😊😊😊😊;
381       😊😊😊😊 = a;
382
383       int b;
384       b = a;
385       return    count + 1;
386     }
387   END
388   setline(1, lines)
389   :redraw!
390
391   cursor(5, 1) # 😊😊😊😊 = a;
392   search('a')
393   assert_equal([],
394               execute('LspGotoDefinition')->split("\n"))
395   assert_equal([2, 12], [line('.'), col('.')])
396
397   cursor(8, 1) # b = a;
398   search('a')
399   assert_equal([],
400               execute('LspGotoDefinition')->split("\n"))
401   assert_equal([2, 12], [line('.'), col('.')])
402
403   :%bw!
404 enddef
405
406 # Test for multiple LSP diagnostics on the same line
407 def g:Test_LspDiag_Multi()
408   :silent! edit XLspDiagMulti.c
409   sleep 200m
410
411   var bnr: number = bufnr()
412
413   var lines =<< trim END
414     int i = "a";
415     int j = i;
416     int y = 0;
417   END
418   setline(1, lines)
419   :redraw!
420   # TODO: Waiting count doesn't include Warning, Info, and Hint diags
421   if clangdVerMajor > 14
422         g:WaitForServerFileLoad(3)
423   else
424         g:WaitForServerFileLoad(2)
425   endif
426   :LspDiagShow
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])
433   else
434         assert_equal([1, 5, 'W'], [qfl[0].lnum, qfl[0].col, qfl[0].type])
435   endif
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])
438   close
439
440   :sleep 100m
441   cursor(2, 1)
442   assert_equal('', execute('LspDiagPrev'))
443   assert_equal([1, 9], [line('.'), col('.')])
444
445   assert_equal('', execute('LspDiagPrev'))
446   assert_equal([1, 5], [line('.'), col('.')])
447
448   var output = execute('LspDiagPrev')->split("\n")
449   assert_equal('Warn: No more diagnostics found', output[0])
450
451   cursor(2, 1)
452   assert_equal('', execute('LspDiagFirst'))
453   assert_equal([1, 5], [line('.'), col('.')])
454   assert_equal('', execute('LspDiagNext'))
455   assert_equal([1, 9], [line('.'), col('.')])
456   cursor(1, 1)
457   assert_equal('', execute('LspDiagLast'))
458   assert_equal([2, 9], [line('.'), col('.')])
459   popup_clear()
460
461   # Test for :LspDiagHere on a line with multiple diagnostics
462   cursor(1, 1)
463   :LspDiagHere
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])
468   popup_clear()
469   cursor(1, 6)
470   :LspDiagHere
471   assert_equal([1, 9], [line('.'), col('.')])
472   ids = popup_list()
473   assert_equal(1, ids->len())
474   assert_match('Initializer element is not', getbufline(ids[0]->winbufnr(), 1, '$')[0])
475   popup_clear()
476
477   # Line without diagnostics
478   cursor(3, 1)
479   output = execute('LspDiagHere')->split("\n")
480   assert_equal('Warn: No more diagnostics found on this line', output[0])
481
482   g:LspOptionsSet({showDiagInPopup: false})
483   for i in range(1, 5)
484     cursor(1, i)
485     output = execute('LspDiagCurrent')->split('\n')
486     assert_match('Incompatible pointer to integer', output[0])
487   endfor
488   for i in range(6, 12)
489     cursor(1, i)
490     output = execute('LspDiagCurrent')->split('\n')
491     assert_match('Initializer element is not ', output[0])
492   endfor
493   g:LspOptionsSet({showDiagInPopup: true})
494
495   # Check for exact diag ":LspDiagCurrent!"
496   g:LspOptionsSet({showDiagInPopup: false})
497   for i in range(1, 4)
498     cursor(1, i)
499     output = execute('LspDiagCurrent!')->split('\n')
500     assert_equal('Warn: No diagnostic messages found for current position', output[0])
501   endfor
502
503   cursor(1, 5)
504   output = execute('LspDiagCurrent!')->split('\n')
505   assert_match('Incompatible pointer to integer', output[0])
506
507   for i in range(6, 8)
508     cursor(1, i)
509     output = execute('LspDiagCurrent!')->split('\n')
510     assert_equal('Warn: No diagnostic messages found for current position', output[0])
511   endfor
512
513   for i in range(9, 11)
514     cursor(1, i)
515     output = execute('LspDiagCurrent!')->split('\n')
516     assert_match('Initializer element is not ', output[0])
517   endfor
518   for i in range(12, 12)
519     cursor(1, i)
520     output = execute('LspDiagCurrent!')->split('\n')
521     assert_equal('Warn: No diagnostic messages found for current position', output[0])
522   endfor
523
524   g:LspOptionsSet({showDiagInPopup: true})
525
526   # :[count]LspDiagNext
527   g:LspOptionsSet({showDiagInPopup: false})
528   cursor(1, 1)
529   :2LspDiagNext
530   assert_equal([1, 9], [line('.'), col('.')])
531   :2LspDiagNext
532   assert_equal([2, 9], [line('.'), col('.')])
533   output = execute(':2LspDiagNext')->split("\n")
534   assert_equal('Warn: No more diagnostics found', output[0])
535
536   cursor(1, 1)
537   :99LspDiagNext
538   assert_equal([2, 9], [line('.'), col('.')])
539   g:LspOptionsSet({showDiagInPopup: true})
540
541   # :[count]LspDiagPrev
542   g:LspOptionsSet({showDiagInPopup: false})
543   cursor(1, 1)
544   :2LspDiagPrev
545   assert_equal('Warn: No more diagnostics found', output[0])
546   cursor(3, 3)
547   :2LspDiagPrev
548   assert_equal([1, 9], [line('.'), col('.')])
549   :2LspDiagPrev
550   assert_equal([1, 5], [line('.'), col('.')])
551   output = execute(':2LspDiagPrev')->split("\n")
552   assert_equal('Warn: No more diagnostics found', output[0])
553
554   cursor(3, 3)
555   :99LspDiagPrev
556   assert_equal([1, 5], [line('.'), col('.')])
557   g:LspOptionsSet({showDiagInPopup: true})
558
559   :%bw!
560 enddef
561
562 # Test for highlight diag inline
563 def g:Test_LspHighlightDiagInline()
564   :silent! edit XLspHighlightDiag.c
565   sleep 200m
566   setline(1, [
567     'int main()',
568     '{',
569       '    struct obj obj',
570       '',
571       '    return 1;',
572     '}',
573   ])
574
575   # TODO: Waiting count doesn't include Warning, Info, and Hint diags
576   g:WaitForDiags(2)
577
578   var props = prop_list(1)
579   assert_equal(0, props->len())
580   props = prop_list(2)
581   assert_equal(0, props->len())
582   props = prop_list(3)
583   assert_equal(2, props->len())
584   assert_equal([
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}
587   ], props)
588   props = prop_list(4)
589   assert_equal(0, props->len())
590   props = prop_list(5)
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)
593   props = prop_list(6)
594   assert_equal(0, props->len())
595
596   :%bw!
597 enddef
598
599 # Test for :LspCodeAction
600 def g:Test_LspCodeAction()
601   silent! edit XLspCodeAction.c
602   sleep 200m
603   var lines: list<string> =<< trim END
604     void testFunc()
605     {
606         int count;
607         count == 20;
608     }
609   END
610   setline(1, lines)
611   g:WaitForServerFileLoad(0)
612   cursor(4, 1)
613   redraw!
614   :LspCodeAction 1
615   assert_equal("\tcount = 20;", getline(4))
616
617   setline(4, "\tcount = 20:")
618   cursor(4, 1)
619   sleep 500m
620   :LspCodeAction 0
621   assert_equal("\tcount = 20:", getline(4))
622
623   cursor(4, 1)
624   :LspCodeAction 2
625   assert_equal("\tcount = 20:", getline(4))
626
627   cursor(4, 1)
628   :LspCodeAction 1
629   assert_equal("\tcount = 20;", getline(4))
630   bw!
631
632   # pattern and string prefix
633   silent! edit XLspCodeActionPattern.c
634   sleep 200m
635   var lines2: list<string> =<< trim END
636     void testFunc()
637     {
638         int count;
639         if (count = 1) {
640         }
641     }
642   END
643   setline(1, lines2)
644   g:WaitForServerFileLoad(0)
645   cursor(4, 1)
646   redraw!
647   :LspCodeAction use
648   assert_equal("\tif (count == 1) {", getline(4))
649
650   setline(4, "\tif (count = 1) {")
651   cursor(4, 1)
652   sleep 500m
653   :LspCodeAction /paren
654   assert_equal("\tif ((count = 1)) {", getline(4))
655
656   setline(4, "\tif (count = 1) {")
657   cursor(4, 1)
658   sleep 500m
659   :LspCodeAction NON_EXISTING_PREFIX
660   assert_equal("\tif (count = 1) {", getline(4))
661
662   cursor(4, 1)
663   :LspCodeAction /NON_EXISTING_REGEX
664   assert_equal("\tif (count = 1) {", getline(4))
665   bw!
666
667   # empty file
668   assert_equal('', execute('LspCodeAction'))
669
670   # file without an LSP server
671   edit a.raku
672   assert_equal('Error: Language server for "raku" file type supporting "codeAction" feature is not found',
673                execute('LspCodeAction')->split("\n")[0])
674
675   :%bw!
676 enddef
677
678 # Test for :LspRename
679 def g:Test_LspRename()
680   silent! edit XLspRename.c
681   sleep 200m
682   var lines: list<string> =<< trim END
683     void F1(int count)
684     {
685         count = 20;
686
687         ++count;
688     }
689
690     void F2(int count)
691     {
692         count = 5;
693     }
694   END
695   setline(1, lines)
696   g:WaitForServerFileLoad(0)
697   cursor(1, 1)
698   search('count')
699   redraw!
700   feedkeys(":LspRename\<CR>er\<CR>", "xt")
701   redraw!
702   var expected: list<string> =<< trim END
703     void F1(int counter)
704     {
705         counter = 20;
706
707         ++counter;
708     }
709
710     void F2(int count)
711     {
712         count = 5;
713     }
714   END
715   assert_equal(expected, getline(1, '$'))
716
717   cursor(1, 1)
718   search('counter')
719   LspRename countvar
720   var expected2: list<string> =<< trim END
721     void F1(int countvar)
722     {
723         countvar = 20;
724
725         ++countvar;
726     }
727
728     void F2(int count)
729     {
730         count = 5;
731     }
732   END
733   assert_equal(expected2, getline(1, '$'))
734   sleep 100m
735   bw!
736
737   # empty file
738   assert_equal('', execute('LspRename'))
739
740   # file without an LSP server
741   edit a.raku
742   assert_equal('Error: Language server for "raku" file type supporting "rename" feature is not found',
743                execute('LspRename')->split("\n")[0])
744
745   :%bw!
746 enddef
747
748 # Test for :LspSelectionExpand and :LspSelectionShrink
749 def g:Test_LspSelection()
750   silent! edit XLspSelection.c
751   sleep 200m
752   var lines: list<string> =<< trim END
753     void fnSel(int count)
754     {
755         int i;
756         for (i = 0; i < 10; i++) {
757            count++;
758         }
759         count = 20;
760     }
761   END
762   setline(1, lines)
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"
767   cursor(2, 1)
768   redraw!
769   :LspSelectionExpand
770   redraw!
771   normal! 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"
777   cursor(4, 29)
778   redraw!
779   :LspSelectionExpand
780   redraw!
781   normal! y
782   assert_equal('v', visualmode())
783   assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
784
785   # Expand the visual selection
786   xnoremap <silent> le <Cmd>LspSelectionExpand<CR>
787   xnoremap <silent> ls <Cmd>LspSelectionShrink<CR>
788   cursor(5, 8)
789   normal vley
790   assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
791   cursor(5, 8)
792   normal vleley
793   assert_equal([5, 8, 5, 14], [line("'<"), col("'<"), line("'>"), col("'>")])
794   cursor(5, 8)
795   normal vleleley
796   assert_equal([4, 30, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
797   cursor(5, 8)
798   normal vleleleley
799   assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
800   cursor(5, 8)
801   normal vleleleleley
802   assert_equal([2, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
803   cursor(5, 8)
804   normal vleleleleleley
805   assert_equal([1, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
806   cursor(5, 8)
807   normal vleleleleleleley
808   assert_equal([1, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
809
810   # Shrink the visual selection
811   cursor(5, 8)
812   normal vlsy
813   assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
814   cursor(5, 8)
815   normal vlelsy
816   assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
817   cursor(5, 8)
818   normal vlelelsy
819   assert_equal([5, 8, 5, 12], [line("'<"), col("'<"), line("'>"), col("'>")])
820   cursor(5, 8)
821   normal vlelelelsy
822   assert_equal([5, 8, 5, 14], [line("'<"), col("'<"), line("'>"), col("'>")])
823   cursor(5, 8)
824   normal vlelelelelsy
825   assert_equal([4, 30, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
826   cursor(5, 8)
827   normal vlelelelelelsy
828   assert_equal([4, 5, 6, 5], [line("'<"), col("'<"), line("'>"), col("'>")])
829   cursor(5, 8)
830   normal vlelelelelelelsy
831   assert_equal([2, 1, 8, 1], [line("'<"), col("'<"), line("'>"), col("'>")])
832
833   xunmap le
834   xunmap ls
835   bw!
836
837   # empty file
838   assert_equal('', execute('LspSelectionExpand'))
839
840   # file without an LSP server
841   edit a.raku
842   assert_equal('Error: Language server for "raku" file type supporting "selectionRange" feature is not found',
843                execute('LspSelectionExpand')->split("\n")[0])
844
845   :%bw!
846 enddef
847
848 # Test for :LspGotoDefinition, :LspGotoDeclaration and :LspGotoImpl
849 def g:Test_LspGotoSymbol()
850   settagstack(0, {items: []})
851   silent! edit XLspGotoSymbol.cpp
852   sleep 600m
853   var lines: list<string> =<< trim END
854     class base {
855         public:
856             virtual void print();
857     };
858
859     void base::print()
860     {
861     }
862
863     class derived : public base {
864         public:
865             void print() {}
866     };
867
868     void f1(void)
869     {
870         base *bp;
871         derived d;
872         bp = &d;
873
874         bp->print();
875     }
876   END
877   setline(1, lines)
878   g:WaitForServerFileLoad(0)
879
880   cursor(21, 6)
881   :LspGotoDeclaration
882   assert_equal([3, 19], [line('.'), col('.')])
883   exe "normal! \<C-t>"
884   assert_equal([21, 6], [line('.'), col('.')])
885   assert_equal(1, winnr('$'))
886
887   :LspGotoDefinition
888   assert_equal([6, 12], [line('.'), col('.')])
889   exe "normal! \<C-t>"
890   assert_equal([21, 6], [line('.'), col('.')])
891   assert_equal(1, winnr('$'))
892
893   # Command modifiers
894   :topleft LspGotoDefinition
895   assert_equal([6, 12], [line('.'), col('.')])
896   assert_equal([1, 2], [winnr(), winnr('$')])
897   close
898   exe "normal! \<C-t>"
899   assert_equal([21, 6], [line('.'), col('.')])
900
901   :tab LspGotoDefinition
902   assert_equal([6, 12], [line('.'), col('.')])
903   assert_equal([2, 2, 1], [tabpagenr(), tabpagenr('$'), winnr('$')])
904   tabclose
905   exe "normal! \<C-t>"
906   assert_equal([21, 6], [line('.'), col('.')])
907
908   # :LspGotoTypeDef
909   cursor(21, 2)
910   :LspGotoTypeDef
911   assert_equal([1, 7], [line('.'), col('.')])
912   exe "normal! \<C-t>"
913   assert_equal([21, 2], [line('.'), col('.')])
914
915   # :LspGotoImpl
916   cursor(21, 6)
917   :LspGotoImpl
918   assert_equal([12, 11], [line('.'), col('.')])
919   exe "normal! \<C-t>"
920   assert_equal([21, 6], [line('.'), col('.')])
921
922   # FIXME: The following tests are failing in Github CI. Comment out for now.
923   if 0
924   # Error cases
925   :messages clear
926   cursor(11, 5)
927   :LspGotoDeclaration
928   var m = execute('messages')->split("\n")
929   assert_equal('symbol declaration is not found', m[1])
930   :messages clear
931   :LspGotoDefinition
932   m = execute('messages')->split("\n")
933   assert_equal('symbol definition is not found', m[1])
934   :messages clear
935   :LspGotoImpl
936   m = execute('messages')->split("\n")
937   assert_equal('symbol implementation is not found', m[1])
938   :messages clear
939   endif
940
941   # Test for LspPeekDeclaration
942   cursor(21, 6)
943   var bnum = bufnr()
944   :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]))
949   popup_clear()
950   # tag stack should not be changed
951   assert_fails("normal! \<C-t>", 'E555:')
952
953   # Test for LspPeekDefinition
954   :LspPeekDefinition
955   plist = popup_list()
956   assert_true(1, plist->len())
957   assert_equal(bnum, plist[0]->winbufnr())
958   assert_equal(6, line('.', plist[0]))
959   popup_clear()
960   # tag stack should not be changed
961   assert_fails("normal! \<C-t>", 'E555:')
962
963   # FIXME: :LspPeekTypeDef and :LspPeekImpl are supported only with clang-14.
964   # This clangd version is not available in Github CI.
965
966   :%bw!
967
968   # empty file
969   assert_equal('', execute('LspGotoDefinition'))
970   assert_equal('', execute('LspGotoDeclaration'))
971   assert_equal('', execute('LspGotoImpl'))
972
973   # file without an LSP server
974   edit a.raku
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])
981
982   :%bw!
983 enddef
984
985 # Test for :LspHighlight
986 def g:Test_LspHighlight()
987   silent! edit XLspHighlight.c
988   sleep 200m
989   var lines: list<string> =<< trim END
990     void f1(int arg)
991     {
992       int i = arg;
993       arg = 2;
994       if (arg == 2) {
995         arg = 3;
996       }
997     }
998   END
999   setline(1, lines)
1000   g:WaitForServerFileLoad(0)
1001   cursor(1, 13)
1002   :LspHighlight
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))
1013   :LspHighlightClear
1014   assert_equal([], prop_list(1))
1015   assert_equal([], prop_list(3))
1016   assert_equal([], prop_list(4))
1017
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])
1021   :%bw!
1022 enddef
1023
1024 # Test for :LspHover
1025 def g:Test_LspHover()
1026   silent! edit XLspHover.c
1027   sleep 200m
1028   var lines: list<string> =<< trim END
1029     int f1(int a)
1030     {
1031       return 0;
1032     }
1033
1034     void f2(void)
1035     {
1036       f1(5);
1037       char *z = "z";
1038       f1(z);
1039     }
1040   END
1041   setline(1, lines)
1042   if clangdVerMajor > 14
1043         g:WaitForServerFileLoad(1)
1044   else
1045         g:WaitForServerFileLoad(0)
1046   endif
1047   cursor(8, 4)
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, '$'))
1053   popup_close(p[0])
1054   cursor(7, 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())
1060
1061   # Show current diagnostic as to open another popup.
1062   # Then we can test that LspHover closes all existing popups
1063   cursor(10, 6)
1064   :LspDiagCurrent
1065   assert_equal(1, popup_list()->len())
1066   :LspHover
1067   assert_equal(1, popup_list()->len())
1068   popup_clear()
1069
1070   :%bw!
1071
1072
1073 enddef
1074
1075 # Test for :LspShowSignature
1076 def g:Test_LspShowSignature()
1077   silent! edit XLspShowSignature.c
1078   sleep 200m
1079   var lines: list<string> =<< trim END
1080     int MyFunc(int a, int b)
1081     {
1082       return 0;
1083     }
1084
1085     void f2(void)
1086     {
1087       MyFunc( 
1088     }
1089   END
1090   setline(1, lines)
1091   g:WaitForServerFileLoad(2)
1092   cursor(8, 10)
1093   :LspShowSignature
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}))
1102   popup_close(p[0])
1103
1104   setline(line('.'), '  MyFunc(10, ')
1105   cursor(8, 13)
1106   :LspShowSignature
1107   p = popup_list()
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}))
1114   popup_close(p[0])
1115   :%bw!
1116 enddef
1117
1118 # Test for :LspSymbolSearch
1119 def g:Test_LspSymbolSearch()
1120   silent! edit XLspSymbolSearch.c
1121   sleep 200m
1122   var lines: list<string> =<< trim END
1123     void lsptest_funcA()
1124     {
1125     }
1126
1127     void lsptest_funcB()
1128     {
1129     }
1130
1131     void lsptest_funcC()
1132     {
1133     }
1134   END
1135   setline(1, lines)
1136   g:WaitForServerFileLoad(0)
1137
1138   cursor(1, 1)
1139   feedkeys(":LspSymbolSearch lsptest_funcB\<CR>\<CR>", "xt")
1140   assert_equal([5, 6], [line('.'), col('.')])
1141
1142   cursor(1, 1)
1143   feedkeys(":LspSymbolSearch lsptest_func\<CR>\<Down>\<Down>\<CR>", "xt")
1144   assert_equal([9, 6], [line('.'), col('.')])
1145
1146   cursor(1, 1)
1147   feedkeys(":LspSymbolSearch lsptest_funcA\<CR>\<BS>B\<CR>", "xt")
1148   assert_equal([5, 6], [line('.'), col('.')])
1149
1150   var output = execute(':LspSymbolSearch lsptest_nonexist')->split("\n")
1151   assert_equal('Warn: Symbol "lsptest_nonexist" is not found', output[0])
1152
1153   :%bw!
1154 enddef
1155
1156 # Test for :LspIncomingCalls
1157 def g:Test_LspIncomingCalls()
1158   silent! edit XLspIncomingCalls.c
1159   sleep 200m
1160   var lines: list<string> =<< trim END
1161     void xFuncIncoming(void)
1162     {
1163     }
1164
1165     void aFuncIncoming(void)
1166     {
1167       xFuncIncoming();
1168     }
1169
1170     void bFuncIncoming(void)
1171     {
1172       xFuncIncoming();
1173     }
1174   END
1175   setline(1, lines)
1176   g:WaitForServerFileLoad(0)
1177   cursor(1, 6)
1178   :LspIncomingCalls
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])
1185   :%bw!
1186 enddef
1187
1188 # Test for :LspOutline
1189 def g:Test_LspOutline()
1190   silent! edit XLspOutline.c
1191   sleep 200m
1192   var lines: list<string> =<< trim END
1193     void aFuncOutline(void)
1194     {
1195     }
1196
1197     void bFuncOutline(void)
1198     {
1199     }
1200   END
1201   setline(1, lines)
1202   g:WaitForServerFileLoad(0)
1203   var winid = win_getid()
1204   :LspOutline
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, '$'))
1209
1210   # Validate position vert topleft
1211   assert_equal(['row', [['leaf', winid + 1], ['leaf', winid]]], winlayout())
1212
1213   # Validate default width is 20
1214   assert_equal(20, winwidth(winid + 1))
1215
1216   execute $':{bnum}bw'
1217
1218   # Validate position vert botright
1219   g:LspOptionsSet({ outlineOnRight: true })
1220   :LspOutline
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'
1228
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'
1237
1238   # Validate that outlineWinSize works for LspOutline
1239   g:LspOptionsSet({ outlineWinSize: 40 })
1240   :LspOutline
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 })
1248
1249   # Validate that <count> works for LspOutline
1250   :37LspOutline
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'
1257
1258   :%bw!
1259 enddef
1260
1261 # Test for setting the 'tagfunc'
1262 def g:Test_LspTagFunc()
1263   var lines: list<string> =<< trim END
1264     void aFuncTag(void)
1265     {
1266       xFuncTag();
1267     }
1268
1269     void bFuncTag(void)
1270     {
1271       xFuncTag();
1272     }
1273
1274     void xFuncTag(void)
1275     {
1276     }
1277   END
1278   writefile(lines, 'Xtagfunc.c')
1279   :silent! edit Xtagfunc.c
1280   g:WaitForServerFileLoad(1)
1281   :setlocal tagfunc=lsp#lsp#TagFunc
1282   cursor(3, 4)
1283   :exe "normal \<C-]>"
1284   assert_equal([11, 6], [line('.'), col('.')])
1285   cursor(1, 1)
1286   assert_fails('exe "normal \<C-]>"', 'E433: No tags file')
1287
1288   :set tagfunc&
1289   :%bw!
1290   delete('Xtagfunc.c')
1291 enddef
1292
1293 # Test for the LspDiagsUpdated autocmd
1294 def g:Test_LspDiagsUpdated_Autocmd()
1295   g:LspAutoCmd = 0
1296   autocmd_add([{event: 'User', pattern: 'LspDiagsUpdated', cmd: 'g:LspAutoCmd = g:LspAutoCmd + 1'}])
1297   silent! edit XLspDiagsAutocmd.c
1298   sleep 200m
1299   var lines: list<string> =<< trim END
1300     void aFuncDiag(void)
1301     {
1302         return;
1303     }
1304   END
1305   setline(1, lines)
1306   g:WaitForServerFileLoad(0)
1307   setline(3, '    return:')
1308   redraw!
1309   g:WaitForDiags(1)
1310   setline(3, '    return;')
1311   redraw!
1312   g:WaitForDiags(0)
1313   :%bw!
1314   autocmd_delete([{event: 'User', pattern: 'LspDiagsUpdated'}])
1315   assert_equal(5, g:LspAutoCmd)
1316 enddef
1317
1318 # Test custom notification handlers
1319 def g:Test_LspCustomNotificationHandlers()
1320
1321   g:LSPTest_customNotificationHandlerReplied = false
1322
1323   silent! edit XcustomNotification.c
1324   sleep 200m
1325   var lines: list<string> =<< trim END
1326     int a = 1;
1327     int main(void) {
1328       return a;
1329     }
1330   END
1331   setline(1, lines)
1332   g:WaitForAssert(() => assert_equal(true, g:LSPTest_customNotificationHandlerReplied))
1333   :%bw!
1334 enddef
1335
1336 def g:Test_ScanFindIdent()
1337   :silent! edit XscanFindIdent.c
1338   sleep 200m
1339   var lines: list<string> =<< trim END
1340     int countFI;
1341     int fnFI(int a)
1342     {
1343       int hello;
1344       hello =    a;
1345       return    countFI + 1;
1346     }
1347   END
1348   setline(1, lines)
1349   g:WaitForServerFileLoad(0)
1350   :redraw!
1351
1352   # LspGotoDefinition et al
1353   cursor(5, 10)
1354   assert_equal([], execute('LspGotoDefinition')->split("\n"))
1355   assert_equal([2, 14], [line('.'), col('.')])
1356
1357   cursor(6, 10)
1358   assert_equal([], execute('LspGotoDefinition')->split("\n"))
1359   assert_equal([1, 5], [line('.'), col('.')])
1360
1361   # LspShowReferences
1362   cursor(6, 10)
1363   assert_equal([], execute('LspShowReferences')->split("\n"))
1364   :lclose
1365
1366   # LspRename
1367   cursor(6, 10)
1368   assert_equal([], execute('LspRename counterFI')->split("\n"))
1369   sleep 100m
1370   assert_equal('int counterFI;', getline(1))
1371   assert_equal('  return    counterFI + 1;', getline(6))
1372
1373   :%bw!
1374 enddef
1375
1376 # Test for doing omni completion from the first column
1377 def g:Test_OmniComplete_FirstColumn()
1378   :silent! edit XOmniCompleteFirstColumn.c
1379   sleep 200m
1380   var lines: list<string> =<< trim END
1381     typedef struct Foo_ {
1382     } Foo_t;
1383
1384     #define FOO 1
1385   END
1386   setline(1, lines)
1387   g:WaitForServerFileLoad(0)
1388   redraw!
1389
1390   feedkeys("G0i\<C-X>\<C-O>", 'xt')
1391   assert_equal('Foo_t#define FOO 1', getline('.'))
1392   :%bw!
1393 enddef
1394
1395 # Test for doing omni completion from the first column
1396 def g:Test_OmniComplete_Multibyte()
1397   :silent! edit XOmniCompleteMultibyte.c
1398   sleep 200m
1399   var lines: list<string> =<< trim END
1400     #include <string.h>
1401     void Fn(void)
1402     {
1403       int thisVar = 1;
1404       int len = strlen("©©©©©") + thisVar;
1405     }
1406   END
1407   setline(1, lines)
1408   g:WaitForServerFileLoad(0)
1409   redraw!
1410
1411   cursor(5, 36)
1412   feedkeys("cwthis\<C-X>\<C-O>", 'xt')
1413   assert_equal('  int len = strlen("©©©©©") + thisVar;', getline('.'))
1414   :%bw!
1415 enddef
1416
1417 # Test for doing omni completion from the first column
1418 def g:Test_OmniComplete_Struct()
1419   :silent! edit XOmniCompleteStruct.c
1420   sleep 200m
1421   var lines: list<string> =<< trim END
1422     struct test_ {
1423         int foo;
1424         int bar;
1425         int baz;
1426     };
1427     void Fn(void)
1428     {
1429         struct test_ myTest;
1430         struct test_ *pTest;
1431         myTest.bar = 10;
1432         pTest->bar = 20;
1433     }
1434   END
1435   setline(1, lines)
1436   g:WaitForServerFileLoad(0)
1437   redraw!
1438
1439   cursor(10, 12)
1440   feedkeys("cwb\<C-X>\<C-O>\<C-N>\<C-Y>", 'xt')
1441   assert_equal('    myTest.baz = 10;', getline('.'))
1442   cursor(11, 12)
1443   feedkeys("cw\<C-X>\<C-O>\<C-N>\<C-N>\<C-Y>", 'xt')
1444   assert_equal('    pTest->foo = 20;', getline('.'))
1445   :%bw!
1446 enddef
1447
1448 # Test for the :LspServer command.
1449 def g:Test_LspServer()
1450   new a.raku
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"))
1479   :%bw!
1480 enddef
1481
1482 # TODO:
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.
1486 #
1487 #      int MyVar = 1;
1488 #      int abc = myvar<C-N><C-Y>
1489 # 2. Add a test for jumping to a non-existing symbol definition, declaration.
1490
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')
1494 enddef
1495
1496 # vim: shiftwidth=2 softtabstop=2 noexpandtab