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