1" Vim completion script
2" Language:	C
3" Maintainer:	Bram Moolenaar <[email protected]>
4" Last Change:	2006 Feb 10
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  " Split item in words, keep empty word after "." or "->".
68  " "aa" -> ['aa'], "aa." -> ['aa', ''], "aa.bb" -> ['aa', 'bb'], etc.
69  " We can't use split, because we need to skip nested [...].
70  let items = []
71  let s = 0
72  while 1
73    let e = match(base, '\.\|->\|\[', s)
74    if e < 0
75      if s == 0 || base[s - 1] != ']'
76	call add(items, strpart(base, s))
77      endif
78      break
79    endif
80    if s == 0 || base[s - 1] != ']'
81      call add(items, strpart(base, s, e - s))
82    endif
83    if base[e] == '.'
84      let s = e + 1	" skip over '.'
85    elseif base[e] == '-'
86      let s = e + 2	" skip over '->'
87    else
88      " Skip over [...].
89      let n = 0
90      let s = e
91      let e += 1
92      while e < len(base)
93	if base[e] == ']'
94	  if n == 0
95	    break
96	  endif
97	  let n -= 1
98	elseif base[e] == '['  " nested [...]
99	  let n += 1
100	endif
101	let e += 1
102      endwhile
103      let e += 1
104      call add(items, strpart(base, s, e - s))
105      let s = e
106    endif
107  endwhile
108
109  " Find the variable items[0].
110  " 1. in current function (like with "gd")
111  " 2. in tags file(s) (like with ":tag")
112  " 3. in current file (like with "gD")
113  let res = []
114  if searchdecl(items[0], 0, 1) == 0
115    " Found, now figure out the type.
116    " TODO: join previous line if it makes sense
117    let line = getline('.')
118    let col = col('.')
119    if len(items) == 1
120      " Completing one word and it's a local variable: May add '[', '.' or
121      " '->'.
122      let match = items[0]
123      if match(line, match . '\s*\[') > 0
124	let match .= '['
125      else
126	let res = s:Nextitem(strpart(line, 0, col), [''], 0)
127	if len(res) > 0
128	  " There are members, thus add "." or "->".
129	  if match(line, '\*[ \t(]*' . match . '\>') > 0
130	    let match .= '->'
131	  else
132	    let match .= '.'
133	  endif
134	endif
135      endif
136      let res = [{'match': match, 'tagline' : ''}]
137    else
138      " Completing "var.", "var.something", etc.
139      let res = s:Nextitem(strpart(line, 0, col), items[1:], 0)
140    endif
141  endif
142
143  if len(items) == 1
144    " Only one part, no "." or "->": complete from tags file.
145    call extend(res, map(taglist('^' . base), 's:Tag2item(v:val)'))
146  endif
147
148  if len(res) == 0
149    " Find the variable in the tags file(s)
150    let diclist = taglist('^' . items[0] . '$')
151
152    let res = []
153    for i in range(len(diclist))
154      " New ctags has the "typename" field.
155      if has_key(diclist[i], 'typename')
156	call extend(res, s:StructMembers(diclist[i]['typename'], items[1:]))
157      endif
158
159      " For a variable use the command, which must be a search pattern that
160      " shows the declaration of the variable.
161      if diclist[i]['kind'] == 'v'
162	let line = diclist[i]['cmd']
163	if line[0] == '/' && line[1] == '^'
164	  let col = match(line, '\<' . items[0] . '\>')
165	  call extend(res, s:Nextitem(strpart(line, 2, col - 2), items[1:], 0))
166	endif
167      endif
168    endfor
169  endif
170
171  if len(res) == 0 && searchdecl(items[0], 1) == 0
172    " Found, now figure out the type.
173    " TODO: join previous line if it makes sense
174    let line = getline('.')
175    let col = col('.')
176    let res = s:Nextitem(strpart(line, 0, col), items[1:], 0)
177  endif
178
179  " If the last item(s) are [...] they need to be added to the matches.
180  let last = len(items) - 1
181  let brackets = ''
182  while last >= 0
183    if items[last][0] != '['
184      break
185    endif
186    let brackets = items[last] . brackets
187    let last -= 1
188  endwhile
189
190  return map(res, 's:Tagline2item(v:val, brackets)')
191endfunc
192
193function! s:GetAddition(line, match, memarg, bracket)
194  " Guess if the item is an array.
195  if a:bracket && match(a:line, a:match . '\s*\[') > 0
196    return '['
197  endif
198
199  " Check if the item has members.
200  if len(s:SearchMembers(a:memarg, [''])) > 0
201    " If there is a '*' before the name use "->".
202    if match(a:line, '\*[ \t(]*' . a:match . '\>') > 0
203      return '->'
204    else
205      return '.'
206    endif
207  endif
208  return ''
209endfunction
210
211" Turn the tag info "val" into an item for completion.
212" "val" is is an item in the list returned by taglist().
213" If it is a variable we may add "." or "->".  Don't do it for other types,
214" such as a typedef, by not including the info that s:GetAddition() uses.
215function! s:Tag2item(val)
216  let x = s:Tagcmd2extra(a:val['cmd'], a:val['name'], a:val['filename'])
217
218  if has_key(a:val, "kind")
219    if a:val["kind"] == 'v'
220      return {'match': a:val['name'], 'tagline': "\t" . a:val['cmd'], 'dict': a:val, 'extra': x}
221    endif
222    if a:val["kind"] == 'f'
223      return {'match': a:val['name'] . '(', 'tagline': "", 'extra': x}
224    endif
225  endif
226  return {'match': a:val['name'], 'tagline': '', 'extra': x}
227endfunction
228
229" Turn a match item "val" into an item for completion.
230" "val['match']" is the matching item.
231" "val['tagline']" is the tagline in which the last part was found.
232function! s:Tagline2item(val, brackets)
233  let line = a:val['tagline']
234  let word = a:val['match'] . a:brackets . s:GetAddition(line, a:val['match'], [a:val], a:brackets == '')
235  if has_key(a:val, 'extra')
236    return {'word': word, 'menu': a:val['extra']}
237  endif
238
239  " Isolate the command after the tag and filename.
240  let s = matchstr(line, '[^\t]*\t[^\t]*\t\zs\(/^.*$/\|[^\t]*\)\ze\(;"\t\|\t\|$\)')
241  if s != ''
242    return {'word': word, 'menu': s:Tagcmd2extra(s, a:val['match'], matchstr(line, '[^\t]*\t\zs[^\t]*\ze\t'))}
243  endif
244  return {'word': word}
245endfunction
246
247" Turn a command from a tag line to something that is useful in the menu
248function! s:Tagcmd2extra(cmd, name, fname)
249  if a:cmd =~ '^/^'
250    " The command is a search command, useful to see what it is.
251    let x = matchstr(a:cmd, '^/^\zs.*\ze$/')
252    let x = substitute(x, a:name, '@@', '')
253    let x = substitute(x, '\\\(.\)', '\1', 'g')
254    let x = x . ' - ' . a:fname
255  elseif a:cmd =~ '^\d*$'
256    " The command is a line number, the file name is more useful.
257    let x = a:fname . ' - ' . a:cmd
258  else
259    " Not recognized, use command and file name.
260    let x = a:cmd . ' - ' . a:fname
261  endif
262  return x
263endfunction
264
265" Find composing type in "lead" and match items[0] with it.
266" Repeat this recursively for items[1], if it's there.
267" When resolving typedefs "depth" is used to avoid infinite recursion.
268" Return the list of matches.
269function! s:Nextitem(lead, items, depth)
270
271  " Use the text up to the variable name and split it in tokens.
272  let tokens = split(a:lead, '\s\+\|\<')
273
274  " Try to recognize the type of the variable.  This is rough guessing...
275  let res = []
276  for tidx in range(len(tokens))
277
278    " Recognize "struct foobar" and "union foobar".
279    if (tokens[tidx] == 'struct' || tokens[tidx] == 'union') && tidx + 1 < len(tokens)
280      let res = s:StructMembers(tokens[tidx] . ':' . tokens[tidx + 1], a:items)
281      break
282    endif
283
284    " TODO: add more reserved words
285    if index(['int', 'short', 'char', 'float', 'double', 'static', 'unsigned', 'extern'], tokens[tidx]) >= 0
286      continue
287    endif
288
289    " Use the tags file to find out if this is a typedef.
290    let diclist = taglist('^' . tokens[tidx] . '$')
291    for tagidx in range(len(diclist))
292      " New ctags has the "typename" field.
293      if has_key(diclist[tagidx], 'typename')
294	call extend(res, s:StructMembers(diclist[tagidx]['typename'], a:items))
295	continue
296      endif
297
298      " Only handle typedefs here.
299      if diclist[tagidx]['kind'] != 't'
300	continue
301      endif
302
303      " For old ctags we recognize "typedef struct aaa" and
304      " "typedef union bbb" in the tags file command.
305      let cmd = diclist[tagidx]['cmd']
306      let ei = matchend(cmd, 'typedef\s\+')
307      if ei > 1
308	let cmdtokens = split(strpart(cmd, ei), '\s\+\|\<')
309	if len(cmdtokens) > 1
310	  if cmdtokens[0] == 'struct' || cmdtokens[0] == 'union'
311	    let name = ''
312	    " Use the first identifier after the "struct" or "union"
313	    for ti in range(len(cmdtokens) - 1)
314	      if cmdtokens[ti] =~ '^\w'
315		let name = cmdtokens[ti]
316		break
317	      endif
318	    endfor
319	    if name != ''
320	      call extend(res, s:StructMembers(cmdtokens[0] . ':' . name, a:items))
321	    endif
322	  elseif a:depth < 10
323	    " Could be "typedef other_T some_T".
324	    call extend(res, s:Nextitem(cmdtokens[0], a:items, a:depth + 1))
325	  endif
326	endif
327      endif
328    endfor
329    if len(res) > 0
330      break
331    endif
332  endfor
333
334  return res
335endfunction
336
337
338" Search for members of structure "typename" in tags files.
339" Return a list with resulting matches.
340" Each match is a dictionary with "match" and "tagline" entries.
341function! s:StructMembers(typename, items)
342  " Todo: What about local structures?
343  let fnames = join(map(tagfiles(), 'escape(v:val, " \\")'))
344  if fnames == ''
345    return []
346  endif
347
348  let typename = a:typename
349  let qflist = []
350  while 1
351    exe 'silent! vimgrep /\t' . typename . '\(\t\|$\)/j ' . fnames
352    let qflist = getqflist()
353    if len(qflist) > 0 || match(typename, "::") < 0
354      break
355    endif
356    " No match for "struct:context::name", remove "context::" and try again.
357    let typename = substitute(typename, ':[^:]*::', ':', '')
358  endwhile
359
360  let matches = []
361  for l in qflist
362    let memb = matchstr(l['text'], '[^\t]*')
363    if memb =~ '^' . a:items[0]
364      call add(matches, {'match': memb, 'tagline': l['text']})
365    endif
366  endfor
367
368  if len(matches) > 0
369    " Skip over [...] items
370    let idx = 1
371    while 1
372      if idx >= len(a:items)
373	return matches		" No further items, return the result.
374      endif
375      if a:items[idx][0] != '['
376	break
377      endif
378      let idx += 1
379    endwhile
380
381    " More items following.  For each of the possible members find the
382    " matching following members.
383    return s:SearchMembers(matches, a:items[idx :])
384  endif
385
386  " Failed to find anything.
387  return []
388endfunction
389
390" For matching members, find matches for following items.
391function! s:SearchMembers(matches, items)
392  let res = []
393  for i in range(len(a:matches))
394    let typename = ''
395    if has_key(a:matches[i], 'dict')
396      if has_key(a:matches[i].dict, 'typename')
397	let typename = a:matches[i].dict['typename']
398      endif
399      let line = "\t" . a:matches[i].dict['cmd']
400    else
401      let line = a:matches[i]['tagline']
402      let e = matchend(line, '\ttypename:')
403      if e > 0
404	" Use typename field
405	let typename = matchstr(line, '[^\t]*', e)
406      endif
407    endif
408    if typename != ''
409      call extend(res, s:StructMembers(typename, a:items))
410    else
411      " Use the search command (the declaration itself).
412      let s = match(line, '\t\zs/^')
413      if s > 0
414	let e = match(line, '\<' . a:matches[i]['match'] . '\>', s)
415	if e > 0
416	  call extend(res, s:Nextitem(strpart(line, s, e - s), a:items, 0))
417	endif
418      endif
419    endif
420  endfor
421  return res
422endfunc
423