1" Vim completion script
2" Language:	C
3" Maintainer:	Bram Moolenaar <[email protected]>
4" Last Change:	2012 Jun 20
5
6let s:cpo_save = &cpo
7set cpo&vim
8
9" This function is used for the 'omnifunc' option.
10function! ccomplete#Complete(findstart, base)
11  if a:findstart
12    " Locate the start of the item, including ".", "->" and "[...]".
13    let line = getline('.')
14    let start = col('.') - 1
15    let lastword = -1
16    while start > 0
17      if line[start - 1] =~ '\w'
18	let start -= 1
19      elseif line[start - 1] =~ '\.'
20	if lastword == -1
21	  let lastword = start
22	endif
23	let start -= 1
24      elseif start > 1 && line[start - 2] == '-' && line[start - 1] == '>'
25	if lastword == -1
26	  let lastword = start
27	endif
28	let start -= 2
29      elseif line[start - 1] == ']'
30	" Skip over [...].
31	let n = 0
32	let start -= 1
33	while start > 0
34	  let start -= 1
35	  if line[start] == '['
36	    if n == 0
37	      break
38	    endif
39	    let n -= 1
40	  elseif line[start] == ']'  " nested []
41	    let n += 1
42	  endif
43	endwhile
44      else
45	break
46      endif
47    endwhile
48
49    " Return the column of the last word, which is going to be changed.
50    " Remember the text that comes before it in s:prepended.
51    if lastword == -1
52      let s:prepended = ''
53      return start
54    endif
55    let s:prepended = strpart(line, start, lastword - start)
56    return lastword
57  endif
58
59  " Return list of matches.
60
61  let base = s:prepended . a:base
62
63  " Don't do anything for an empty base, would result in all the tags in the
64  " tags file.
65  if base == ''
66    return []
67  endif
68
69  " init cache for vimgrep to empty
70  let s:grepCache = {}
71
72  " Split item in words, keep empty word after "." or "->".
73  " "aa" -> ['aa'], "aa." -> ['aa', ''], "aa.bb" -> ['aa', 'bb'], etc.
74  " We can't use split, because we need to skip nested [...].
75  let items = []
76  let s = 0
77  while 1
78    let e = match(base, '\.\|->\|\[', s)
79    if e < 0
80      if s == 0 || base[s - 1] != ']'
81	call add(items, strpart(base, s))
82      endif
83      break
84    endif
85    if s == 0 || base[s - 1] != ']'
86      call add(items, strpart(base, s, e - s))
87    endif
88    if base[e] == '.'
89      let s = e + 1	" skip over '.'
90    elseif base[e] == '-'
91      let s = e + 2	" skip over '->'
92    else
93      " Skip over [...].
94      let n = 0
95      let s = e
96      let e += 1
97      while e < len(base)
98	if base[e] == ']'
99	  if n == 0
100	    break
101	  endif
102	  let n -= 1
103	elseif base[e] == '['  " nested [...]
104	  let n += 1
105	endif
106	let e += 1
107      endwhile
108      let e += 1
109      call add(items, strpart(base, s, e - s))
110      let s = e
111    endif
112  endwhile
113
114  " Find the variable items[0].
115  " 1. in current function (like with "gd")
116  " 2. in tags file(s) (like with ":tag")
117  " 3. in current file (like with "gD")
118  let res = []
119  if searchdecl(items[0], 0, 1) == 0
120    " Found, now figure out the type.
121    " TODO: join previous line if it makes sense
122    let line = getline('.')
123    let col = col('.')
124    if stridx(strpart(line, 0, col), ';') != -1
125      " Handle multiple declarations on the same line.
126      let col2 = col - 1
127      while line[col2] != ';'
128	let col2 -= 1
129      endwhile
130      let line = strpart(line, col2 + 1)
131      let col -= col2
132    endif
133    if stridx(strpart(line, 0, col), ',') != -1
134      " Handle multiple declarations on the same line in a function
135      " declaration.
136      let col2 = col - 1
137      while line[col2] != ','
138	let col2 -= 1
139      endwhile
140      if strpart(line, col2 + 1, col - col2 - 1) =~ ' *[^ ][^ ]*  *[^ ]'
141	let line = strpart(line, col2 + 1)
142	let col -= col2
143      endif
144    endif
145    if len(items) == 1
146      " Completing one word and it's a local variable: May add '[', '.' or
147      " '->'.
148      let match = items[0]
149      let kind = 'v'
150      if match(line, '\<' . match . '\s*\[') > 0
151	let match .= '['
152      else
153	let res = s:Nextitem(strpart(line, 0, col), [''], 0, 1)
154	if len(res) > 0
155	  " There are members, thus add "." or "->".
156	  if match(line, '\*[ \t(]*' . match . '\>') > 0
157	    let match .= '->'
158	  else
159	    let match .= '.'
160	  endif
161	endif
162      endif
163      let res = [{'match': match, 'tagline' : '', 'kind' : kind, 'info' : line}]
164    else
165      " Completing "var.", "var.something", etc.
166      let res = s:Nextitem(strpart(line, 0, col), items[1:], 0, 1)
167    endif
168  endif
169
170  if len(items) == 1
171    " Only one part, no "." or "->": complete from tags file.
172    let tags = taglist('^' . base)
173
174    " Remove members, these can't appear without something in front.
175    call filter(tags, 'has_key(v:val, "kind") ? v:val["kind"] != "m" : 1')
176
177    " Remove static matches in other files.
178    call filter(tags, '!has_key(v:val, "static") || !v:val["static"] || bufnr("%") == bufnr(v:val["filename"])')
179
180    call extend(res, map(tags, 's:Tag2item(v:val)'))
181  endif
182
183  if len(res) == 0
184    " Find the variable in the tags file(s)
185    let diclist = taglist('^' . items[0] . '$')
186
187    " Remove members, these can't appear without something in front.
188    call filter(diclist, 'has_key(v:val, "kind") ? v:val["kind"] != "m" : 1')
189
190    let res = []
191    for i in range(len(diclist))
192      " New ctags has the "typeref" field.  Patched version has "typename".
193      if has_key(diclist[i], 'typename')
194	call extend(res, s:StructMembers(diclist[i]['typename'], items[1:], 1))
195      elseif has_key(diclist[i], 'typeref')
196	call extend(res, s:StructMembers(diclist[i]['typeref'], items[1:], 1))
197      endif
198
199      " For a variable use the command, which must be a search pattern that
200      " shows the declaration of the variable.
201      if diclist[i]['kind'] == 'v'
202	let line = diclist[i]['cmd']
203	if line[0] == '/' && line[1] == '^'
204	  let col = match(line, '\<' . items[0] . '\>')
205	  call extend(res, s:Nextitem(strpart(line, 2, col - 2), items[1:], 0, 1))
206	endif
207      endif
208    endfor
209  endif
210
211  if len(res) == 0 && searchdecl(items[0], 1) == 0
212    " Found, now figure out the type.
213    " TODO: join previous line if it makes sense
214    let line = getline('.')
215    let col = col('.')
216    let res = s:Nextitem(strpart(line, 0, col), items[1:], 0, 1)
217  endif
218
219  " If the last item(s) are [...] they need to be added to the matches.
220  let last = len(items) - 1
221  let brackets = ''
222  while last >= 0
223    if items[last][0] != '['
224      break
225    endif
226    let brackets = items[last] . brackets
227    let last -= 1
228  endwhile
229
230  return map(res, 's:Tagline2item(v:val, brackets)')
231endfunc
232
233function! s:GetAddition(line, match, memarg, bracket)
234  " Guess if the item is an array.
235  if a:bracket && match(a:line, a:match . '\s*\[') > 0
236    return '['
237  endif
238
239  " Check if the item has members.
240  if len(s:SearchMembers(a:memarg, [''], 0)) > 0
241    " If there is a '*' before the name use "->".
242    if match(a:line, '\*[ \t(]*' . a:match . '\>') > 0
243      return '->'
244    else
245      return '.'
246    endif
247  endif
248  return ''
249endfunction
250
251" Turn the tag info "val" into an item for completion.
252" "val" is is an item in the list returned by taglist().
253" If it is a variable we may add "." or "->".  Don't do it for other types,
254" such as a typedef, by not including the info that s:GetAddition() uses.
255function! s:Tag2item(val)
256  let res = {'match': a:val['name']}
257
258  let res['extra'] = s:Tagcmd2extra(a:val['cmd'], a:val['name'], a:val['filename'])
259
260  let s = s:Dict2info(a:val)
261  if s != ''
262    let res['info'] = s
263  endif
264
265  let res['tagline'] = ''
266  if has_key(a:val, "kind")
267    let kind = a:val['kind']
268    let res['kind'] = kind
269    if kind == 'v'
270      let res['tagline'] = "\t" . a:val['cmd']
271      let res['dict'] = a:val
272    elseif kind == 'f'
273      let res['match'] = a:val['name'] . '('
274    endif
275  endif
276
277  return res
278endfunction
279
280" Use all the items in dictionary for the "info" entry.
281function! s:Dict2info(dict)
282  let info = ''
283  for k in sort(keys(a:dict))
284    let info  .= k . repeat(' ', 10 - len(k))
285    if k == 'cmd'
286      let info .= substitute(matchstr(a:dict['cmd'], '/^\s*\zs.*\ze$/'), '\\\(.\)', '\1', 'g')
287    else
288      let info .= a:dict[k]
289    endif
290    let info .= "\n"
291  endfor
292  return info
293endfunc
294
295" Parse a tag line and return a dictionary with items like taglist()
296function! s:ParseTagline(line)
297  let l = split(a:line, "\t")
298  let d = {}
299  if len(l) >= 3
300    let d['name'] = l[0]
301    let d['filename'] = l[1]
302    let d['cmd'] = l[2]
303    let n = 2
304    if l[2] =~ '^/'
305      " Find end of cmd, it may contain Tabs.
306      while n < len(l) && l[n] !~ '/;"$'
307	let n += 1
308	let d['cmd'] .= "  " . l[n]
309      endwhile
310    endif
311    for i in range(n + 1, len(l) - 1)
312      if l[i] == 'file:'
313	let d['static'] = 1
314      elseif l[i] !~ ':'
315	let d['kind'] = l[i]
316      else
317	let d[matchstr(l[i], '[^:]*')] = matchstr(l[i], ':\zs.*')
318      endif
319    endfor
320  endif
321
322  return d
323endfunction
324
325" Turn a match item "val" into an item for completion.
326" "val['match']" is the matching item.
327" "val['tagline']" is the tagline in which the last part was found.
328function! s:Tagline2item(val, brackets)
329  let line = a:val['tagline']
330  let add = s:GetAddition(line, a:val['match'], [a:val], a:brackets == '')
331  let res = {'word': a:val['match'] . a:brackets . add }
332
333  if has_key(a:val, 'info')
334    " Use info from Tag2item().
335    let res['info'] = a:val['info']
336  else
337    " Parse the tag line and add each part to the "info" entry.
338    let s = s:Dict2info(s:ParseTagline(line))
339    if s != ''
340      let res['info'] = s
341    endif
342  endif
343
344  if has_key(a:val, 'kind')
345    let res['kind'] = a:val['kind']
346  elseif add == '('
347    let res['kind'] = 'f'
348  else
349    let s = matchstr(line, '\t\(kind:\)\=\zs\S\ze\(\t\|$\)')
350    if s != ''
351      let res['kind'] = s
352    endif
353  endif
354
355  if has_key(a:val, 'extra')
356    let res['menu'] = a:val['extra']
357    return res
358  endif
359
360  " Isolate the command after the tag and filename.
361  let s = matchstr(line, '[^\t]*\t[^\t]*\t\zs\(/^.*$/\|[^\t]*\)\ze\(;"\t\|\t\|$\)')
362  if s != ''
363    let res['menu'] = s:Tagcmd2extra(s, a:val['match'], matchstr(line, '[^\t]*\t\zs[^\t]*\ze\t'))
364  endif
365  return res
366endfunction
367
368" Turn a command from a tag line to something that is useful in the menu
369function! s:Tagcmd2extra(cmd, name, fname)
370  if a:cmd =~ '^/^'
371    " The command is a search command, useful to see what it is.
372    let x = matchstr(a:cmd, '^/^\s*\zs.*\ze$/')
373    let x = substitute(x, '\<' . a:name . '\>', '@@', '')
374    let x = substitute(x, '\\\(.\)', '\1', 'g')
375    let x = x . ' - ' . a:fname
376  elseif a:cmd =~ '^\d*$'
377    " The command is a line number, the file name is more useful.
378    let x = a:fname . ' - ' . a:cmd
379  else
380    " Not recognized, use command and file name.
381    let x = a:cmd . ' - ' . a:fname
382  endif
383  return x
384endfunction
385
386" Find composing type in "lead" and match items[0] with it.
387" Repeat this recursively for items[1], if it's there.
388" When resolving typedefs "depth" is used to avoid infinite recursion.
389" Return the list of matches.
390function! s:Nextitem(lead, items, depth, all)
391
392  " Use the text up to the variable name and split it in tokens.
393  let tokens = split(a:lead, '\s\+\|\<')
394
395  " Try to recognize the type of the variable.  This is rough guessing...
396  let res = []
397  for tidx in range(len(tokens))
398
399    " Skip tokens starting with a non-ID character.
400    if tokens[tidx] !~ '^\h'
401      continue
402    endif
403
404    " Recognize "struct foobar" and "union foobar".
405    " Also do "class foobar" when it's C++ after all (doesn't work very well
406    " though).
407    if (tokens[tidx] == 'struct' || tokens[tidx] == 'union' || tokens[tidx] == 'class') && tidx + 1 < len(tokens)
408      let res = s:StructMembers(tokens[tidx] . ':' . tokens[tidx + 1], a:items, a:all)
409      break
410    endif
411
412    " TODO: add more reserved words
413    if index(['int', 'short', 'char', 'float', 'double', 'static', 'unsigned', 'extern'], tokens[tidx]) >= 0
414      continue
415    endif
416
417    " Use the tags file to find out if this is a typedef.
418    let diclist = taglist('^' . tokens[tidx] . '$')
419    for tagidx in range(len(diclist))
420      let item = diclist[tagidx]
421
422      " New ctags has the "typeref" field.  Patched version has "typename".
423      if has_key(item, 'typeref')
424	call extend(res, s:StructMembers(item['typeref'], a:items, a:all))
425	continue
426      endif
427      if has_key(item, 'typename')
428	call extend(res, s:StructMembers(item['typename'], a:items, a:all))
429	continue
430      endif
431
432      " Only handle typedefs here.
433      if item['kind'] != 't'
434	continue
435      endif
436
437      " Skip matches local to another file.
438      if has_key(item, 'static') && item['static'] && bufnr('%') != bufnr(item['filename'])
439	continue
440      endif
441
442      " For old ctags we recognize "typedef struct aaa" and
443      " "typedef union bbb" in the tags file command.
444      let cmd = item['cmd']
445      let ei = matchend(cmd, 'typedef\s\+')
446      if ei > 1
447	let cmdtokens = split(strpart(cmd, ei), '\s\+\|\<')
448	if len(cmdtokens) > 1
449	  if cmdtokens[0] == 'struct' || cmdtokens[0] == 'union' || cmdtokens[0] == 'class'
450	    let name = ''
451	    " Use the first identifier after the "struct" or "union"
452	    for ti in range(len(cmdtokens) - 1)
453	      if cmdtokens[ti] =~ '^\w'
454		let name = cmdtokens[ti]
455		break
456	      endif
457	    endfor
458	    if name != ''
459	      call extend(res, s:StructMembers(cmdtokens[0] . ':' . name, a:items, a:all))
460	    endif
461	  elseif a:depth < 10
462	    " Could be "typedef other_T some_T".
463	    call extend(res, s:Nextitem(cmdtokens[0], a:items, a:depth + 1, a:all))
464	  endif
465	endif
466      endif
467    endfor
468    if len(res) > 0
469      break
470    endif
471  endfor
472
473  return res
474endfunction
475
476
477" Search for members of structure "typename" in tags files.
478" Return a list with resulting matches.
479" Each match is a dictionary with "match" and "tagline" entries.
480" When "all" is non-zero find all, otherwise just return 1 if there is any
481" member.
482function! s:StructMembers(typename, items, all)
483  " Todo: What about local structures?
484  let fnames = join(map(tagfiles(), 'escape(v:val, " \\#%")'))
485  if fnames == ''
486    return []
487  endif
488
489  let typename = a:typename
490  let qflist = []
491  let cached = 0
492  if a:all == 0
493    let n = '1'	" stop at first found match
494    if has_key(s:grepCache, a:typename)
495      let qflist = s:grepCache[a:typename]
496      let cached = 1
497    endif
498  else
499    let n = ''
500  endif
501  if !cached
502    while 1
503      exe 'silent! keepj noautocmd ' . n . 'vimgrep /\t' . typename . '\(\t\|$\)/j ' . fnames
504
505      let qflist = getqflist()
506      if len(qflist) > 0 || match(typename, "::") < 0
507	break
508      endif
509      " No match for "struct:context::name", remove "context::" and try again.
510      let typename = substitute(typename, ':[^:]*::', ':', '')
511    endwhile
512
513    if a:all == 0
514      " Store the result to be able to use it again later.
515      let s:grepCache[a:typename] = qflist
516    endif
517  endif
518
519  " Put matching members in matches[].
520  let matches = []
521  for l in qflist
522    let memb = matchstr(l['text'], '[^\t]*')
523    if memb =~ '^' . a:items[0]
524      " Skip matches local to another file.
525      if match(l['text'], "\tfile:") < 0 || bufnr('%') == bufnr(matchstr(l['text'], '\t\zs[^\t]*'))
526	let item = {'match': memb, 'tagline': l['text']}
527
528	" Add the kind of item.
529	let s = matchstr(l['text'], '\t\(kind:\)\=\zs\S\ze\(\t\|$\)')
530	if s != ''
531	  let item['kind'] = s
532	  if s == 'f'
533	    let item['match'] = memb . '('
534	  endif
535	endif
536
537	call add(matches, item)
538      endif
539    endif
540  endfor
541
542  if len(matches) > 0
543    " Skip over [...] items
544    let idx = 1
545    while 1
546      if idx >= len(a:items)
547	return matches		" No further items, return the result.
548      endif
549      if a:items[idx][0] != '['
550	break
551      endif
552      let idx += 1
553    endwhile
554
555    " More items following.  For each of the possible members find the
556    " matching following members.
557    return s:SearchMembers(matches, a:items[idx :], a:all)
558  endif
559
560  " Failed to find anything.
561  return []
562endfunction
563
564" For matching members, find matches for following items.
565" When "all" is non-zero find all, otherwise just return 1 if there is any
566" member.
567function! s:SearchMembers(matches, items, all)
568  let res = []
569  for i in range(len(a:matches))
570    let typename = ''
571    if has_key(a:matches[i], 'dict')
572      if has_key(a:matches[i].dict, 'typename')
573	let typename = a:matches[i].dict['typename']
574      elseif has_key(a:matches[i].dict, 'typeref')
575	let typename = a:matches[i].dict['typeref']
576      endif
577      let line = "\t" . a:matches[i].dict['cmd']
578    else
579      let line = a:matches[i]['tagline']
580      let e = matchend(line, '\ttypename:')
581      if e < 0
582	let e = matchend(line, '\ttyperef:')
583      endif
584      if e > 0
585	" Use typename field
586	let typename = matchstr(line, '[^\t]*', e)
587      endif
588    endif
589
590    if typename != ''
591      call extend(res, s:StructMembers(typename, a:items, a:all))
592    else
593      " Use the search command (the declaration itself).
594      let s = match(line, '\t\zs/^')
595      if s > 0
596	let e = match(line, '\<' . a:matches[i]['match'] . '\>', s)
597	if e > 0
598	  call extend(res, s:Nextitem(strpart(line, s, e - s), a:items, 0, a:all))
599	endif
600      endif
601    endif
602    if a:all == 0 && len(res) > 0
603      break
604    endif
605  endfor
606  return res
607endfunc
608
609let &cpo = s:cpo_save
610unlet s:cpo_save
611