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