1" Vim completion script
2" Language:	C
3" Maintainer:	Bram Moolenaar <[email protected]>
4" Last Change:	2006 Jan 29
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      else
28	break
29      endif
30    endwhile
31
32    " Return the column of the last word, which is going to be changed.
33    " Remember the text that comes before it in s:prepended.
34    if lastword == -1
35      let s:prepended = ''
36      return start
37    endif
38    let s:prepended = strpart(line, start, lastword - start)
39    return lastword
40  endif
41
42  " Return list of matches.
43
44  let base = s:prepended . a:base
45
46  " Split item in words, keep empty word after "." or "->".
47  " "aa" -> ['aa'], "aa." -> ['aa', ''], "aa.bb" -> ['aa', 'bb'], etc.
48  let items = split(base, '\.\|->', 1)
49  if len(items) <= 1
50    " Don't do anything for an empty base, would result in all the tags in the
51    " tags file.
52    if base == ''
53      return []
54    endif
55
56    " Only one part, no "." or "->": complete from tags file.
57    " When local completion is wanted CTRL-N would have been used.
58    return map(taglist('^' . base), 's:Tag2item(v:val)')
59  endif
60
61  " Find the variable items[0].
62  " 1. in current function (like with "gd")
63  " 2. in tags file(s) (like with ":tag")
64  " 3. in current file (like with "gD")
65  let res = []
66  if searchdecl(items[0], 0, 1) == 0
67    " Found, now figure out the type.
68    " TODO: join previous line if it makes sense
69    let line = getline('.')
70    let col = col('.')
71    let res = s:Nextitem(strpart(line, 0, col), items[1:])
72  endif
73
74  if len(res) == 0
75    " Find the variable in the tags file(s)
76    let diclist = taglist('^' . items[0] . '$')
77
78    let res = []
79    for i in range(len(diclist))
80      " New ctags has the "typename" field.
81      if has_key(diclist[i], 'typename')
82	call extend(res, s:StructMembers(diclist[i]['typename'], items[1:]))
83      endif
84
85      " For a variable use the command, which must be a search pattern that
86      " shows the declaration of the variable.
87      if diclist[i]['kind'] == 'v'
88	let line = diclist[i]['cmd']
89	if line[0] == '/' && line[1] == '^'
90	  let col = match(line, '\<' . items[0] . '\>')
91	  call extend(res, s:Nextitem(strpart(line, 2, col - 2), items[1:]))
92	endif
93      endif
94    endfor
95  endif
96
97  if len(res) == 0 && searchdecl(items[0], 1) == 0
98    " Found, now figure out the type.
99    " TODO: join previous line if it makes sense
100    let line = getline('.')
101    let col = col('.')
102    let res = s:Nextitem(strpart(line, 0, col), items[1:])
103  endif
104
105  " If the one and only match was what's already there and it is a composite
106  " type, add a "." or "->".
107  if len(res) == 1 && res[0]['match'] == items[-1] && len(s:SearchMembers(res, [''])) > 0
108    " If there is a '*' before the name use "->".
109    if match(res[0]['tagline'], '\*\s*' . res[0]['match'] . '\>') > 0
110      let res[0]['match'] .= '->'
111    else
112      let res[0]['match'] .= '.'
113    endif
114  endif
115
116  return map(res, 'v:val["match"]')
117endfunc
118
119"
120" Turn the tag info "val" into an item for completion.
121" "val" is is an item in the list returned by taglist().
122function! s:Tag2item(val)
123  if has_key(a:val, "kind") && a:val["kind"] == 'v'
124    if len(s:SearchMembers([{'match': a:val["name"], 'dict': a:val}], [''])) > 0
125      " If there is a '*' before the name use "->".  This assumes the command
126      " is a search pattern!
127      if match(a:val['cmd'], '\*\s*' . a:val['name'] . '\>') > 0
128        return a:val["name"] . '->'
129      else
130        return a:val["name"] . '.'
131      endif
132    endif
133  endif
134  return a:val["name"]
135endfunction
136
137
138" Find composing type in "lead" and match items[0] with it.
139" Repeat this recursively for items[1], if it's there.
140" Return the list of matches.
141function! s:Nextitem(lead, items)
142
143  " Use the text up to the variable name and split it in tokens.
144  let tokens = split(a:lead, '\s\+\|\<')
145
146  " Try to recognize the type of the variable.  This is rough guessing...
147  let res = []
148  for tidx in range(len(tokens))
149
150    " Recognize "struct foobar" and "union foobar".
151    if (tokens[tidx] == 'struct' || tokens[tidx] == 'union') && tidx + 1 < len(tokens)
152      let res = s:StructMembers(tokens[tidx] . ':' . tokens[tidx + 1], a:items)
153      break
154    endif
155
156    " TODO: add more reserved words
157    if index(['int', 'float', 'static', 'unsigned', 'extern'], tokens[tidx]) >= 0
158      continue
159    endif
160
161    " Use the tags file to find out if this is a typedef.
162    let diclist = taglist('^' . tokens[tidx] . '$')
163    for tagidx in range(len(diclist))
164      " New ctags has the "typename" field.
165      if has_key(diclist[tagidx], 'typename')
166	call extend(res, s:StructMembers(diclist[tagidx]['typename'], a:items))
167	continue
168      endif
169
170      " Only handle typedefs here.
171      if diclist[tagidx]['kind'] != 't'
172	continue
173      endif
174
175      " For old ctags we recognize "typedef struct aaa" and
176      " "typedef union bbb" in the tags file command.
177      let cmd = diclist[tagidx]['cmd']
178      let ei = matchend(cmd, 'typedef\s\+')
179      if ei > 1
180	let cmdtokens = split(strpart(cmd, ei), '\s\+\|\<')
181	if len(cmdtokens) > 1
182	  if cmdtokens[0] == 'struct' || cmdtokens[0] == 'union'
183	    let name = ''
184	    " Use the first identifier after the "struct" or "union"
185	    for ti in range(len(cmdtokens) - 1)
186	      if cmdtokens[ti] =~ '^\w'
187		let name = cmdtokens[ti]
188		break
189	      endif
190	    endfor
191	    if name != ''
192	      call extend(res, s:StructMembers(cmdtokens[0] . ':' . name, a:items))
193	    endif
194	  else
195	    " Could be "typedef other_T some_T".
196	    call extend(res, s:Nextitem(cmdtokens[0], a:items))
197	  endif
198	endif
199      endif
200    endfor
201    if len(res) > 0
202      break
203    endif
204  endfor
205
206  return res
207endfunction
208
209
210" Return a list with resulting matches.
211" Each match is a dictionary with "match" and "tagline" entries.
212function! s:StructMembers(typename, items)
213  " Todo: What about local structures?
214  let fnames = join(map(tagfiles(), 'escape(v:val, " \\")'))
215  if fnames == ''
216    return []
217  endif
218
219  let typename = a:typename
220  let qflist = []
221  while 1
222    exe 'silent! vimgrep /\t' . typename . '\(\t\|$\)/j ' . fnames
223    let qflist = getqflist()
224    if len(qflist) > 0 || match(typename, "::") < 0
225      break
226    endif
227    " No match for "struct:context::name", remove "context::" and try again.
228    let typename = substitute(typename, ':[^:]*::', ':', '')
229  endwhile
230
231  let matches = []
232  for l in qflist
233    let memb = matchstr(l['text'], '[^\t]*')
234    if memb =~ '^' . a:items[0]
235      call add(matches, {'match': memb, 'tagline': l['text']})
236    endif
237  endfor
238
239  if len(matches) > 0
240    " No further items, return the result.
241    if len(a:items) == 1
242      return matches
243    endif
244
245    " More items following.  For each of the possible members find the
246    " matching following members.
247    return s:SearchMembers(matches, a:items[1:])
248  endif
249
250  " Failed to find anything.
251  return []
252endfunction
253
254" For matching members, find matches for following items.
255function! s:SearchMembers(matches, items)
256  let res = []
257  for i in range(len(a:matches))
258    let typename = ''
259    if has_key(a:matches[i], 'dict')
260      "if a:matches[i].dict['name'] == "gui"
261	"echomsg string(a:matches[i].dict)
262      "endif
263      if has_key(a:matches[i].dict, 'typename')
264	let typename = a:matches[i].dict['typename']
265      endif
266      let line = "\t" . a:matches[i].dict['cmd']
267    else
268      let line = a:matches[i]['tagline']
269      let e = matchend(line, '\ttypename:')
270      if e > 0
271	" Use typename field
272	let typename = matchstr(line, '[^\t]*', e)
273      endif
274    endif
275    if typename != ''
276      call extend(res, s:StructMembers(name, a:items))
277    else
278      " Use the search command (the declaration itself).
279      let s = match(line, '\t\zs/^')
280      if s > 0
281	let e = match(line, '\<' . a:matches[i]['match'] . '\>', s)
282	if e > 0
283	  "if a:matches[i].dict['name'] == "gui"
284	    "echomsg strpart(line, s, e - s)
285	  "endif
286	  call extend(res, s:Nextitem(strpart(line, s, e - s), a:items))
287	endif
288      endif
289    endif
290  endfor
291  return res
292endfunc
293