Rainbow
[my-vim-dotfolder.git] / plugin / clang_complete.vim
blobdb5a4a1294282a676d944e1002bd0fabf3316e90
2 " File: clang_complete.vim
3 " Author: Xavier Deguillard <deguilx@gmail.com>
5 " Description: Use of clang to complete in C/C++.
7 " Help: Use :help clang_complete
10 au FileType c,cpp,objc,objcpp call <SID>ClangCompleteInit()
12 let b:clang_parameters = ''
13 let b:clang_user_options = ''
14 let b:my_changedtick = 0
16 " Store plugin path, as this is available only when sourcing the file,
17 " not during a function call.
18 let s:plugin_path = escape(expand('<sfile>:p:h'), '\')
20 function! s:ClangCompleteInit()
21   if !exists('g:clang_auto_select')
22     let g:clang_auto_select = 0
23   endif
25   if !exists('g:clang_complete_auto')
26     let g:clang_complete_auto = 1
27   endif
29   if !exists('g:clang_complete_copen')
30     let g:clang_complete_copen = 0
31   endif
33   if !exists('g:clang_hl_errors')
34     let g:clang_hl_errors = 1
35   endif
37   if !exists('g:clang_periodic_quickfix')
38     let g:clang_periodic_quickfix = 0
39   endif
41   if !exists('g:clang_snippets')
42     let g:clang_snippets = 0
43   endif
45   if !exists('g:clang_snippets_engine')
46     let g:clang_snippets_engine = 'clang_complete'
47   endif
49   if !exists('g:clang_exec')
50     let g:clang_exec = 'clang'
51   endif
53   if !exists('g:clang_user_options')
54     let g:clang_user_options = ''
55   endif
57   " Only use libclang if the user clearly show intent to do so for now
58   if !exists('g:clang_use_library')
59     let g:clang_use_library = (has('python') && exists('g:clang_library_path'))
60   endif
62   if !exists('g:clang_complete_macros')
63     let g:clang_complete_macros = 0
64   endif
66   if !exists('g:clang_complete_patterns')
67     let g:clang_complete_patterns = 0
68   endif
70   if !exists('g:clang_debug')
71     let g:clang_debug = 0
72   endif
74   if !exists('g:clang_sort_algo')
75     let g:clang_sort_algo = 'priority'
76   endif
78   if !exists('g:clang_auto_user_options')
79     let g:clang_auto_user_options = 'path, .clang_complete'
80   endif
82   call LoadUserOptions()
84   inoremap <expr> <buffer> <C-X><C-U> <SID>LaunchCompletion()
85   inoremap <expr> <buffer> . <SID>CompleteDot()
86   inoremap <expr> <buffer> > <SID>CompleteArrow()
87   inoremap <expr> <buffer> : <SID>CompleteColon()
88   inoremap <expr> <buffer> <CR> <SID>HandlePossibleSelectionEnter()
90   if g:clang_snippets == 1
91     call g:ClangSetSnippetEngine(g:clang_snippets_engine)
92   endif
94   " Force menuone. Without it, when there's only one completion result,
95   " it can be confusing (not completing and no popup)
96   if g:clang_auto_select != 2
97     set completeopt-=menu
98     set completeopt+=menuone
99   endif
101   " Disable every autocmd that could have been set.
102   augroup ClangComplete
103     autocmd!
104   augroup end
106   let b:should_overload = 0
107   let b:my_changedtick = b:changedtick
108   let b:clang_parameters = '-x c'
110   if &filetype == 'objc'
111     let b:clang_parameters = '-x objective-c'
112   endif
114   if &filetype == 'cpp' || &filetype == 'objcpp'
115     let b:clang_parameters .= '++'
116   endif
118   if expand('%:e') =~ 'h*'
119     let b:clang_parameters .= '-header'
120   endif
122   let g:clang_complete_lib_flags = 0
124   if g:clang_complete_macros == 1
125     let b:clang_parameters .= ' -code-completion-macros'
126     let g:clang_complete_lib_flags = 1
127   endif
129   if g:clang_complete_patterns == 1
130     let b:clang_parameters .= ' -code-completion-patterns'
131     let g:clang_complete_lib_flags += 2
132   endif
134   setlocal completefunc=ClangComplete
135   setlocal omnifunc=ClangComplete
137   if g:clang_periodic_quickfix == 1
138     augroup ClangComplete
139       au CursorHold,CursorHoldI <buffer> call <SID>DoPeriodicQuickFix()
140     augroup end
141   endif
143   " Load the python bindings of libclang
144   if g:clang_use_library == 1
145     if has('python')
146       exe s:initClangCompletePython()
147     else
148       echoe 'clang_complete: No python support available.'
149       echoe 'Cannot use clang library, using executable'
150       echoe 'Compile vim with python support to use libclang'
151       let g:clang_use_library = 0
152       return
153     endif
154   endif
155 endfunction
157 function! LoadUserOptions()
158   let b:clang_user_options = ''
160   let l:option_sources = split(g:clang_auto_user_options, ',')
161   let l:remove_spaces_cmd = 'substitute(v:val, "\\s*\\(.*\\)\\s*", "\\1", "")'
162   let l:option_sources = map(l:option_sources, l:remove_spaces_cmd)
164   for l:source in l:option_sources
165     if l:source == 'path'
166       call s:parsePathOption()
167     elseif l:source == '.clang_complete'
168       call s:parseConfig()
169     endif
170   endfor
171 endfunction
173 function! s:parseConfig()
174   let l:local_conf = findfile('.clang_complete', getcwd() . ',.;')
175   if l:local_conf == '' || !filereadable(l:local_conf)
176     return
177   endif
179   let l:opts = readfile(l:local_conf)
180   for l:opt in l:opts
181     " Better handling of absolute path
182     " I don't know if those pattern will work on windows
183     " platform
184     if matchstr(l:opt, '\C-I\s*/') != ''
185       let l:opt = substitute(l:opt, '\C-I\s*\(/\%(\w\|\\\s\)*\)',
186             \ '-I' . '\1', 'g')
187     else
188       let l:opt = substitute(l:opt, '\C-I\s*\(\%(\w\|\\\s\)*\)',
189             \ '-I' . l:local_conf[:-16] . '\1', 'g')
190     endif
191     let b:clang_user_options .= ' ' . l:opt
192   endfor
193 endfunction
195 function! s:parsePathOption()
196   let l:dirs = split(&path, ',')
197   for l:dir in l:dirs
198     if len(l:dir) == 0 || !isdirectory(l:dir)
199       continue
200     endif
202     " Add only absolute paths
203     if matchstr(l:dir, '\s*/') != ''
204       let l:opt = '-I' . l:dir
205       let b:clang_user_options .= ' ' . l:opt
206     endif
207   endfor
208 endfunction
210 function! s:initClangCompletePython()
211   " Only parse the python library once
212   if !exists('s:libclang_loaded')
213     python import sys
214     if exists('g:clang_library_path')
215       " Load the library from the given library path.
216       exe 'python sys.argv = ["' . escape(g:clang_library_path, '\') . '"]'
217     else
218       " By setting argv[0] to '' force the python bindings to load the library
219       " from the normal system search path.
220       python sys.argv[0] = ''
221     endif
223     exe 'python sys.path = ["' . s:plugin_path . '"] + sys.path'
224     exe 'pyfile ' . s:plugin_path . '/libclang.py'
225     python initClangComplete(vim.eval('g:clang_complete_lib_flags'))
226     let s:libclang_loaded = 1
227   endif
228   python WarmupCache()
229 endfunction
231 function! s:GetKind(proto)
232   if a:proto == ''
233     return 't'
234   endif
235   let l:ret = match(a:proto, '^\[#')
236   let l:params = match(a:proto, '(')
237   if l:ret == -1 && l:params == -1
238     return 't'
239   endif
240   if l:ret != -1 && l:params == -1
241     return 'v'
242   endif
243   if l:params != -1
244     return 'f'
245   endif
246   return 'm'
247 endfunction
249 function! s:CallClangBinaryForDiagnostics(tempfile)
250   let l:escaped_tempfile = shellescape(a:tempfile)
251   let l:buf = getline(1, '$')
252   try
253     call writefile(l:buf, a:tempfile)
254   catch /^Vim\%((\a\+)\)\=:E482/
255     return
256   endtry
258   let l:command = g:clang_exec . ' -cc1 -fsyntax-only'
259         \ . ' -fno-caret-diagnostics -fdiagnostics-print-source-range-info'
260         \ . ' ' . l:escaped_tempfile
261         \ . ' ' . b:clang_parameters . ' ' . b:clang_user_options . ' ' . g:clang_user_options
263   let l:clang_output = split(system(l:command), "\n")
264   call delete(a:tempfile)
265   return l:clang_output
266 endfunction
268 function! s:CallClangForDiagnostics(tempfile)
269   if g:clang_use_library == 1
270     python updateCurrentDiagnostics()
271   else
272     return s:CallClangBinaryForDiagnostics(a:tempfile)
273   endif
274 endfunction
276 function! s:DoPeriodicQuickFix()
277   " Don't do any superfluous reparsing.
278   if b:my_changedtick == b:changedtick
279     return
280   endif
281   let b:my_changedtick = b:changedtick
283   " Create tempfile name for clang/clang++ executable mode
284   let b:my_changedtick = b:changedtick
285   let l:tempfile = expand('%:p:h') . '/' . localtime() . expand('%:t')
287   let l:clang_output = s:CallClangForDiagnostics(l:tempfile)
289   call s:ClangQuickFix(l:clang_output, l:tempfile)
290 endfunction
292 function! s:ClangQuickFix(clang_output, tempfname)
293   " Clear the bad spell, the user may have corrected them.
294   syntax clear SpellBad
295   syntax clear SpellLocal
297   if g:clang_use_library == 0
298     let l:list = s:ClangUpdateQuickFix(a:clang_output, a:tempfname)
299   else
300     python vim.command('let l:list = ' + str(getCurrentQuickFixList()))
301     python highlightCurrentDiagnostics()
302   endif
304   if g:clang_complete_copen == 1
305     " We should get back to the original buffer
306     let l:bufnr = bufnr('%')
308     " Workaround:
309     " http://vim.1045645.n5.nabble.com/setqflist-inconsistency-td1211423.html
310     if l:list == []
311       cclose
312     else
313       copen
314     endif
316     let l:winbufnr = bufwinnr(l:bufnr)
317     exe l:winbufnr . 'wincmd w'
318   endif
319   call setqflist(l:list)
320   silent doautocmd QuickFixCmdPost make
321 endfunction
323 function! s:ClangUpdateQuickFix(clang_output, tempfname)
324   let l:list = []
325   for l:line in a:clang_output
326     let l:erridx = match(l:line, '\%(error\|warning\|note\): ')
327     if l:erridx == -1
328       " Error are always at the beginning.
329       if l:line[:11] == 'COMPLETION: ' || l:line[:9] == 'OVERLOAD: '
330         break
331       endif
332       continue
333     endif
334     let l:pattern = '^\(.*\):\(\d*\):\(\d*\):\(\%({\d\+:\d\+-\d\+:\d\+}\)*\)'
335     let l:tmp = matchstr(l:line, l:pattern)
336     let l:fname = substitute(l:tmp, l:pattern, '\1', '')
337     if l:fname == a:tempfname
338       let l:fname = '%'
339     endif
340     let l:bufnr = bufnr(l:fname, 1)
341     let l:lnum = substitute(l:tmp, l:pattern, '\2', '')
342     let l:col = substitute(l:tmp, l:pattern, '\3', '')
343     let l:errors = substitute(l:tmp, l:pattern, '\4', '')
344     if l:line[l:erridx] == 'e'
345       let l:text = l:line[l:erridx + 7:]
346       let l:type = 'E'
347       let l:hlgroup = ' SpellBad '
348     elseif l:line[l:erridx] == 'w'
349       let l:text = l:line[l:erridx + 9:]
350       let l:type = 'W'
351       let l:hlgroup = ' SpellLocal '
352     else
353       let l:text = l:line[l:erridx + 6:]
354       let l:type = 'I'
355       let l:hlgroup = ' '
356     endif
357     let l:item = {
358           \ 'bufnr': l:bufnr,
359           \ 'lnum': l:lnum,
360           \ 'col': l:col,
361           \ 'text': l:text,
362           \ 'type': l:type }
363     let l:list = add(l:list, l:item)
365     if g:clang_hl_errors == 0 || l:fname != '%' || l:type == 'I'
366       continue
367     endif
369     " Highlighting the ^
370     let l:pat = '/\%' . l:lnum . 'l' . '\%' . l:col . 'c./'
371     exe 'syntax match' . l:hlgroup . l:pat
373     if l:errors == ''
374       continue
375     endif
376     let l:ranges = split(l:errors, '}')
377     for l:range in l:ranges
378       " Doing precise error and warning handling.
379       " The highlight will be the same as clang's carets.
380       let l:pattern = '{\%(\d\+\):\(\d\+\)-\%(\d\+\):\(\d\+\)'
381       let l:tmp = matchstr(l:range, l:pattern)
382       let l:startcol = substitute(l:tmp, l:pattern, '\1', '')
383       let l:endcol = substitute(l:tmp, l:pattern, '\2', '')
384       " Highlighting the ~~~~
385       let l:pat = '/\%' . l:lnum . 'l'
386             \ . '\%' . l:startcol . 'c'
387             \ . '.*'
388             \ . '\%' . l:endcol . 'c/'
389       exe 'syntax match' . l:hlgroup . l:pat
390     endfor
391   endfor
392   return l:list
393 endfunction
395 function! s:DemangleProto(prototype)
396   let l:proto = substitute(a:prototype, '\[#[^#]*#\]', '', 'g')
397   let l:proto = substitute(l:proto, '{#.*#}', '', 'g')
398   return l:proto
399 endfunction
401 let b:col = 0
403 function! s:ClangCompleteBinary(base)
404   let l:buf = getline(1, '$')
405   let l:tempfile = expand('%:p:h') . '/' . localtime() . expand('%:t')
406   try
407     call writefile(l:buf, l:tempfile)
408   catch /^Vim\%((\a\+)\)\=:E482/
409     return {}
410   endtry
411   let l:escaped_tempfile = shellescape(l:tempfile)
413   let l:command = g:clang_exec . ' -cc1 -fsyntax-only'
414         \ . ' -fno-caret-diagnostics -fdiagnostics-print-source-range-info'
415         \ . ' -code-completion-at=' . l:escaped_tempfile . ':'
416         \ . line('.') . ':' . b:col . ' ' . l:escaped_tempfile
417         \ . ' ' . b:clang_parameters . ' ' . b:clang_user_options . ' ' . g:clang_user_options
418   let l:clang_output = split(system(l:command), "\n")
419   call delete(l:tempfile)
421   call s:ClangQuickFix(l:clang_output, l:tempfile)
422   if v:shell_error
423     return []
424   endif
425   if l:clang_output == []
426     return []
427   endif
429   let l:filter_str = "v:val =~ '^COMPLETION: " . a:base . "\\|^OVERLOAD: '"
430   call filter(l:clang_output, l:filter_str)
432   let l:res = []
433   for l:line in l:clang_output
435     if l:line[:11] == 'COMPLETION: ' && b:should_overload != 1
437       let l:value = l:line[12:]
439       let l:colonidx = stridx(l:value, ' : ')
440       if l:colonidx == -1
441         let l:wabbr = s:DemangleProto(l:value)
442         let l:word = l:value
443         let l:proto = l:value
444       else
445         let l:word = l:value[:l:colonidx - 1]
446         " WTF is that?
447         if l:word =~ '(Hidden)'
448           let l:word = l:word[:-10]
449         endif
450         let l:wabbr = l:word
451         let l:proto = l:value[l:colonidx + 3:]
452       endif
454       let l:kind = s:GetKind(l:proto)
455       if l:kind == 't' && b:clang_complete_type == 0
456         continue
457       endif
459       let l:word = l:wabbr
461       let l:proto = s:DemangleProto(l:proto)
463     elseif l:line[:9] == 'OVERLOAD: ' && b:should_overload == 1
465       let l:value = l:line[10:]
466       if match(l:value, '<#') == -1
467         continue
468       endif
469       let l:word = substitute(l:value, '.*<#', '<#', 'g')
470       let l:word = substitute(l:word, '#>.*', '#>', 'g')
471       let l:wabbr = substitute(l:word, '<#\([^#]*\)#>', '\1', 'g')
472       let l:proto = l:value
473       let l:proto = s:DemangleProto(l:value)
474       let l:kind = ''
475     else
476       continue
477     endif
479     let l:args_pos = []
480     if g:clang_snippets == 1
481       let l:startidx = match(l:proto, '<#')
482       while l:startidx != -1
483         let l:proto = substitute(l:proto, '<#', '', '')
484         let l:endidx = match(l:proto, '#>')
485         let l:proto = substitute(l:proto, '#>', '', '')
486         let l:args_pos += [[ l:startidx, l:endidx ]]
487         let l:startidx = match(l:proto, '<#')
488       endwhile
489     endif
491     let l:item = {
492           \ 'word': l:word,
493           \ 'abbr': l:wabbr,
494           \ 'menu': l:proto,
495           \ 'info': l:proto,
496           \ 'dup': 0,
497           \ 'kind': l:kind,
498           \ 'args_pos': l:args_pos }
500     call add(l:res, l:item)
501   endfor
502   return l:res
503 endfunction
505 function! ClangComplete(findstart, base)
506   if a:findstart
507     let l:line = getline('.')
508     let l:start = col('.') - 1
509     let b:clang_complete_type = 1
510     let l:wsstart = l:start
511     if l:line[l:wsstart - 1] =~ '\s'
512       while l:wsstart > 0 && l:line[l:wsstart - 1] =~ '\s'
513         let l:wsstart -= 1
514       endwhile
515     endif
516     if l:line[l:wsstart - 1] =~ '[(,]'
517       let b:should_overload = 1
518       let b:col = l:wsstart + 1
519       return l:wsstart
520     endif
521     let b:should_overload = 0
522     while l:start > 0 && l:line[l:start - 1] =~ '\i'
523       let l:start -= 1
524     endwhile
525     if l:line[l:start - 2:] =~ '->' || l:line[l:start - 1] == '.'
526       let b:clang_complete_type = 0
527     endif
528     let b:col = l:start + 1
529     return l:start
530   else
531     if g:clang_debug == 1
532       let l:time_start = reltime()
533     endif
535     if g:clang_snippets == 1
536       call b:ResetSnip()
537     endif
539     if g:clang_use_library == 1
540       python vim.command('let l:res = ' + str(getCurrentCompletions(vim.eval('a:base'))))
541     else
542       let l:res = s:ClangCompleteBinary(a:base)
543     endif
545     for item in l:res
546       if g:clang_snippets == 1
547         let item['word'] = b:AddSnip(item['info'], item['args_pos'])
548       else
549         let item['word'] = item['abbr']
550       endif
551     endfor
552     if g:clang_snippets == 1
553       inoremap <expr> <buffer> <C-Y> <SID>HandlePossibleSelectionCtrlY()
554       augroup ClangComplete
555         au CursorMovedI <buffer> call <SID>TriggerSnippet()
556       augroup end
557       let b:snippet_chosen = 0
558     endif
560   if g:clang_debug == 1
561     echom 'clang_complete: completion time (' . (g:clang_use_library == 1 ? 'library' : 'binary') . ') '. split(reltimestr(reltime(l:time_start)))[0]
562   endif
563   return l:res
564 endif
565 endfunction
567 function! s:HandlePossibleSelectionEnter()
568   if pumvisible()
569     let b:snippet_chosen = 1
570     return "\<C-Y>"
571   end
572   return "\<CR>"
573 endfunction
575 function! s:HandlePossibleSelectionCtrlY()
576   if pumvisible()
577     let b:snippet_chosen = 1
578   end
579   return "\<C-Y>"
580 endfunction
582 function! s:TriggerSnippet()
583   " Dont bother doing anything until we're sure the user exited the menu
584   if !b:snippet_chosen
585     return
586   endif
588   " Stop monitoring as we'll trigger a snippet
589   silent! iunmap <buffer> <C-Y>
590   augroup ClangComplete
591     au! CursorMovedI <buffer>
592   augroup end
594   " Trigger the snippet
595   call b:TriggerSnip()
596 endfunction
598 function! s:ShouldComplete()
599   if (getline('.') =~ '#\s*\(include\|import\)')
600     return 0
601   else
602     if col('.') == 1
603       return 1
604     endif
605     for l:id in synstack(line('.'), col('.') - 1)
606       if match(synIDattr(l:id, 'name'), '\CComment\|String\|Number')
607             \ != -1
608         return 0
609       endif
610     endfor
611     return 1
612   endif
613 endfunction
615 function! s:LaunchCompletion()
616   let l:result = ""
617   if s:ShouldComplete()
618     let l:result = "\<C-X>\<C-U>"
619     if g:clang_auto_select != 2
620       let l:result .= "\<C-P>"
621     endif
622     if g:clang_auto_select == 1
623       let l:result .= "\<C-R>=(pumvisible() ? \"\\<Down>\" : '')\<CR>"
624     endif
625   endif
626   return l:result
627 endfunction
629 function! s:CompleteDot()
630   if g:clang_complete_auto == 1
631     return '.' . s:LaunchCompletion()
632   endif
633   return '.'
634 endfunction
636 function! s:CompleteArrow()
637   if g:clang_complete_auto != 1 || getline('.')[col('.') - 2] != '-'
638     return '>'
639   endif
640   return '>' . s:LaunchCompletion()
641 endfunction
643 function! s:CompleteColon()
644   if g:clang_complete_auto != 1 || getline('.')[col('.') - 2] != ':'
645     return ':'
646   endif
647   return ':' . s:LaunchCompletion()
648 endfunction
650 " May be used in a mapping to update the quickfix window.
651 function! g:ClangUpdateQuickFix()
652   call s:DoPeriodicQuickFix()
653   return ''
654 endfunction
656 function! g:ClangSetSnippetEngine(engine_name)
657   try
658     call eval('snippets#' . a:engine_name . '#init()')
659     let b:AddSnip = function('snippets#' . a:engine_name . '#add_snippet')
660     let b:ResetSnip = function('snippets#' . a:engine_name . '#reset')
661     let b:TriggerSnip = function('snippets#' . a:engine_name . '#trigger')
662   catch /^Vim\%((\a\+)\)\=:E117/
663     echoe 'Snippets engine ' . a:engine_name . ' not found.'
664     let g:clang_snippets = 0
665   endtry
666 endfunction
668 " vim: set ts=2 sts=2 sw=2 expandtab :