1" Vim completion script
2" Language:	C
3" Maintainer:	Bram Moolenaar <[email protected]>
4" Last Change:	2006 Mar 11
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 "typename" field.
170      if has_key(diclist[i], 'typename')
171	call extend(res, s:StructMembers(diclist[i]['typename'], items[1:], 1))
172      endif
173
174      " For a variable use the command, which must be a search pattern that
175      " shows the declaration of the variable.
176      if diclist[i]['kind'] == 'v'
177	let line = diclist[i]['cmd']
178	if line[0] == '/' && line[1] == '^'
179	  let col = match(line, '\<' . items[0] . '\>')
180	  call extend(res, s:Nextitem(strpart(line, 2, col - 2), items[1:], 0, 1))
181	endif
182      endif
183    endfor
184  endif
185
186  if len(res) == 0 && searchdecl(items[0], 1) == 0
187    " Found, now figure out the type.
188    " TODO: join previous line if it makes sense
189    let line = getline('.')
190    let col = col('.')
191    let res = s:Nextitem(strpart(line, 0, col), items[1:], 0, 1)
192  endif
193
194  " If the last item(s) are [...] they need to be added to the matches.
195  let last = len(items) - 1
196  let brackets = ''
197  while last >= 0
198    if items[last][0] != '['
199      break
200    endif
201    let brackets = items[last] . brackets
202    let last -= 1
203  endwhile
204
205  return map(res, 's:Tagline2item(v:val, brackets)')
206endfunc
207
208function! s:GetAddition(line, match, memarg, bracket)
209  " Guess if the item is an array.
210  if a:bracket && match(a:line, a:match . '\s*\[') > 0
211    return '['
212  endif
213
214  " Check if the item has members.
215  if len(s:SearchMembers(a:memarg, [''], 0)) > 0
216    " If there is a '*' before the name use "->".
217    if match(a:line, '\*[ \t(]*' . a:match . '\>') > 0
218      return '->'
219    else
220      return '.'
221    endif
222  endif
223  return ''
224endfunction
225
226" Turn the tag info "val" into an item for completion.
227" "val" is is an item in the list returned by taglist().
228" If it is a variable we may add "." or "->".  Don't do it for other types,
229" such as a typedef, by not including the info that s:GetAddition() uses.
230function! s:Tag2item(val)
231  let res = {'match': a:val['name']}
232
233  let res['extra'] = s:Tagcmd2extra(a:val['cmd'], a:val['name'], a:val['filename'])
234
235  " Use the whole search command as the "info" entry.
236  let s = matchstr(a:val['cmd'], '/^\s*\zs.*\ze$/')
237  if s != ''
238    let res['info'] = substitute(s, '\\\(.\)', '\1', 'g')
239  endif
240
241  let res['tagline'] = ''
242  if has_key(a:val, "kind")
243    let kind = a:val['kind']
244    let res['kind'] = kind
245    if kind == 'v'
246      let res['tagline'] = "\t" . a:val['cmd']
247      let res['dict'] = a:val
248    elseif kind == 'f'
249      let res['match'] = a:val['name'] . '('
250    endif
251  endif
252
253  return res
254endfunction
255
256" Turn a match item "val" into an item for completion.
257" "val['match']" is the matching item.
258" "val['tagline']" is the tagline in which the last part was found.
259function! s:Tagline2item(val, brackets)
260  let line = a:val['tagline']
261  let add = s:GetAddition(line, a:val['match'], [a:val], a:brackets == '')
262  let res = {'word': a:val['match'] . a:brackets . add }
263
264  if has_key(a:val, 'info')
265    " Use info from Tag2item().
266    let res['info'] = a:val['info']
267  else
268    " Use the whole search command as the "info" entry.
269    let s = matchstr(line, '\t/^\s*\zs.*\ze$/')
270    if s != ''
271      let res['info'] = substitute(s, '\\\(.\)', '\1', 'g')
272    endif
273  endif
274
275  if has_key(a:val, 'kind')
276    let res['kind'] = a:val['kind']
277  elseif add == '('
278    let res['kind'] = 'f'
279  else
280    let s = matchstr(line, '\t\(kind:\)\=\zs\S\ze\(\t\|$\)')
281    if s != ''
282      let res['kind'] = s
283    endif
284  endif
285
286  if has_key(a:val, 'extra')
287    let res['menu'] = a:val['extra']
288    return res
289  endif
290
291  " Isolate the command after the tag and filename.
292  let s = matchstr(line, '[^\t]*\t[^\t]*\t\zs\(/^.*$/\|[^\t]*\)\ze\(;"\t\|\t\|$\)')
293  if s != ''
294    let res['menu'] = s:Tagcmd2extra(s, a:val['match'], matchstr(line, '[^\t]*\t\zs[^\t]*\ze\t'))
295  endif
296  return res
297endfunction
298
299" Turn a command from a tag line to something that is useful in the menu
300function! s:Tagcmd2extra(cmd, name, fname)
301  if a:cmd =~ '^/^'
302    " The command is a search command, useful to see what it is.
303    let x = matchstr(a:cmd, '^/^\s*\zs.*\ze$/')
304    let x = substitute(x, '\<' . a:name . '\>', '@@', '')
305    let x = substitute(x, '\\\(.\)', '\1', 'g')
306    let x = x . ' - ' . a:fname
307  elseif a:cmd =~ '^\d*$'
308    " The command is a line number, the file name is more useful.
309    let x = a:fname . ' - ' . a:cmd
310  else
311    " Not recognized, use command and file name.
312    let x = a:cmd . ' - ' . a:fname
313  endif
314  return x
315endfunction
316
317" Find composing type in "lead" and match items[0] with it.
318" Repeat this recursively for items[1], if it's there.
319" When resolving typedefs "depth" is used to avoid infinite recursion.
320" Return the list of matches.
321function! s:Nextitem(lead, items, depth, all)
322
323  " Use the text up to the variable name and split it in tokens.
324  let tokens = split(a:lead, '\s\+\|\<')
325
326  " Try to recognize the type of the variable.  This is rough guessing...
327  let res = []
328  for tidx in range(len(tokens))
329
330    " Skip tokens starting with a non-ID character.
331    if tokens[tidx] !~ '^\h'
332      continue
333    endif
334
335    " Recognize "struct foobar" and "union foobar".
336    if (tokens[tidx] == 'struct' || tokens[tidx] == 'union') && tidx + 1 < len(tokens)
337      let res = s:StructMembers(tokens[tidx] . ':' . tokens[tidx + 1], a:items, a:all)
338      break
339    endif
340
341    " TODO: add more reserved words
342    if index(['int', 'short', 'char', 'float', 'double', 'static', 'unsigned', 'extern'], tokens[tidx]) >= 0
343      continue
344    endif
345
346    " Use the tags file to find out if this is a typedef.
347    let diclist = taglist('^' . tokens[tidx] . '$')
348    for tagidx in range(len(diclist))
349      let item = diclist[tagidx]
350
351      " New ctags has the "typename" field.
352      if has_key(item, 'typename')
353	call extend(res, s:StructMembers(item['typename'], a:items, a:all))
354	continue
355      endif
356
357      " Only handle typedefs here.
358      if item['kind'] != 't'
359	continue
360      endif
361
362      " Skip matches local to another file.
363      if has_key(item, 'static') && item['static'] && bufnr('%') != bufnr(item['filename'])
364	continue
365      endif
366
367      " For old ctags we recognize "typedef struct aaa" and
368      " "typedef union bbb" in the tags file command.
369      let cmd = item['cmd']
370      let ei = matchend(cmd, 'typedef\s\+')
371      if ei > 1
372	let cmdtokens = split(strpart(cmd, ei), '\s\+\|\<')
373	if len(cmdtokens) > 1
374	  if cmdtokens[0] == 'struct' || cmdtokens[0] == 'union'
375	    let name = ''
376	    " Use the first identifier after the "struct" or "union"
377	    for ti in range(len(cmdtokens) - 1)
378	      if cmdtokens[ti] =~ '^\w'
379		let name = cmdtokens[ti]
380		break
381	      endif
382	    endfor
383	    if name != ''
384	      call extend(res, s:StructMembers(cmdtokens[0] . ':' . name, a:items, a:all))
385	    endif
386	  elseif a:depth < 10
387	    " Could be "typedef other_T some_T".
388	    call extend(res, s:Nextitem(cmdtokens[0], a:items, a:depth + 1, a:all))
389	  endif
390	endif
391      endif
392    endfor
393    if len(res) > 0
394      break
395    endif
396  endfor
397
398  return res
399endfunction
400
401
402" Search for members of structure "typename" in tags files.
403" Return a list with resulting matches.
404" Each match is a dictionary with "match" and "tagline" entries.
405" When "all" is non-zero find all, otherwise just return 1 if there is any
406" member.
407function! s:StructMembers(typename, items, all)
408  " Todo: What about local structures?
409  let fnames = join(map(tagfiles(), 'escape(v:val, " \\")'))
410  if fnames == ''
411    return []
412  endif
413
414  let typename = a:typename
415  let qflist = []
416  let cached = 0
417  if a:all == 0
418    let n = '1'	" stop at first found match
419    if has_key(s:grepCache, a:typename)
420      let qflist = s:grepCache[a:typename]
421      let cached = 1
422    endif
423  else
424    let n = ''
425  endif
426  if !cached
427    while 1
428      exe 'silent! ' . n . 'vimgrep /\t' . typename . '\(\t\|$\)/j ' . fnames
429
430      let qflist = getqflist()
431      if len(qflist) > 0 || match(typename, "::") < 0
432	break
433      endif
434      " No match for "struct:context::name", remove "context::" and try again.
435      let typename = substitute(typename, ':[^:]*::', ':', '')
436    endwhile
437
438    if a:all == 0
439      " Store the result to be able to use it again later.
440      let s:grepCache[a:typename] = qflist
441    endif
442  endif
443
444  " Put matching members in matches[].
445  let matches = []
446  for l in qflist
447    let memb = matchstr(l['text'], '[^\t]*')
448    if memb =~ '^' . a:items[0]
449      " Skip matches local to another file.
450      if match(l['text'], "\tfile:") < 0 || bufnr('%') == bufnr(matchstr(l['text'], '\t\zs[^\t]*'))
451	let item = {'match': memb, 'tagline': l['text']}
452
453	" Add the kind of item.
454	let s = matchstr(l['text'], '\t\(kind:\)\=\zs\S\ze\(\t\|$\)')
455	if s != ''
456	  let item['kind'] = s
457	  if s == 'f'
458	    let item['match'] = memb . '('
459	  endif
460	endif
461
462	call add(matches, item)
463      endif
464    endif
465  endfor
466
467  if len(matches) > 0
468    " Skip over [...] items
469    let idx = 1
470    while 1
471      if idx >= len(a:items)
472	return matches		" No further items, return the result.
473      endif
474      if a:items[idx][0] != '['
475	break
476      endif
477      let idx += 1
478    endwhile
479
480    " More items following.  For each of the possible members find the
481    " matching following members.
482    return s:SearchMembers(matches, a:items[idx :], a:all)
483  endif
484
485  " Failed to find anything.
486  return []
487endfunction
488
489" For matching members, find matches for following items.
490" When "all" is non-zero find all, otherwise just return 1 if there is any
491" member.
492function! s:SearchMembers(matches, items, all)
493  let res = []
494  for i in range(len(a:matches))
495    let typename = ''
496    if has_key(a:matches[i], 'dict')
497      if has_key(a:matches[i].dict, 'typename')
498	let typename = a:matches[i].dict['typename']
499      endif
500      let line = "\t" . a:matches[i].dict['cmd']
501    else
502      let line = a:matches[i]['tagline']
503      let e = matchend(line, '\ttypename:')
504      if e > 0
505	" Use typename field
506	let typename = matchstr(line, '[^\t]*', e)
507      endif
508    endif
509
510    if typename != ''
511      call extend(res, s:StructMembers(typename, a:items, a:all))
512    else
513      " Use the search command (the declaration itself).
514      let s = match(line, '\t\zs/^')
515      if s > 0
516	let e = match(line, '\<' . a:matches[i]['match'] . '\>', s)
517	if e > 0
518	  call extend(res, s:Nextitem(strpart(line, s, e - s), a:items, 0, a:all))
519	endif
520      endif
521    endif
522    if a:all == 0 && len(res) > 0
523      break
524    endif
525  endfor
526  return res
527endfunc
528