1" Vim completion script
2" Language:	C
3" Maintainer:	Bram Moolenaar <[email protected]>
4" Last Change:	2005 Dec 18
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), 'v:val["name"]')
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" Find composing type in "lead" and match items[0] with it.
120" Repeat this recursively for items[1], if it's there.
121" Return the list of matches.
122function! s:Nextitem(lead, items)
123
124  " Use the text up to the variable name and split it in tokens.
125  let tokens = split(a:lead, '\s\+\|\<')
126
127  " Try to recognize the type of the variable.  This is rough guessing...
128  let res = []
129  for tidx in range(len(tokens))
130
131    " Recognize "struct foobar" and "union foobar".
132    if (tokens[tidx] == 'struct' || tokens[tidx] == 'union') && tidx + 1 < len(tokens)
133      let res = s:StructMembers(tokens[tidx] . ':' . tokens[tidx + 1], a:items)
134      break
135    endif
136
137    " TODO: add more reserved words
138    if index(['int', 'float', 'static', 'unsigned', 'extern'], tokens[tidx]) >= 0
139      continue
140    endif
141
142    " Use the tags file to find out if this is a typedef.
143    let diclist = taglist('^' . tokens[tidx] . '$')
144    for tagidx in range(len(diclist))
145      " New ctags has the "typename" field.
146      if has_key(diclist[tagidx], 'typename')
147	call extend(res, s:StructMembers(diclist[tagidx]['typename'], a:items))
148	continue
149      endif
150
151      " Only handle typedefs here.
152      if diclist[tagidx]['kind'] != 't'
153	continue
154      endif
155
156      " For old ctags we recognize "typedef struct aaa" and
157      " "typedef union bbb" in the tags file command.
158      let cmd = diclist[tagidx]['cmd']
159      let ei = matchend(cmd, 'typedef\s\+')
160      if ei > 1
161	let cmdtokens = split(strpart(cmd, ei), '\s\+\|\<')
162	if len(cmdtokens) > 1
163	  if cmdtokens[0] == 'struct' || cmdtokens[0] == 'union'
164	    let name = ''
165	    " Use the first identifier after the "struct" or "union"
166	    for ti in range(len(cmdtokens) - 1)
167	      if cmdtokens[ti] =~ '^\w'
168		let name = cmdtokens[ti]
169		break
170	      endif
171	    endfor
172	    if name != ''
173	      call extend(res, s:StructMembers(cmdtokens[0] . ':' . name, a:items))
174	    endif
175	  else
176	    " Could be "typedef other_T some_T".
177	    call extend(res, s:Nextitem(cmdtokens[0], a:items))
178	  endif
179	endif
180      endif
181    endfor
182    if len(res) > 0
183      break
184    endif
185  endfor
186
187  return res
188endfunction
189
190
191" Return a list with resulting matches.
192" Each match is a dictionary with "match" and "tagline" entries.
193function! s:StructMembers(typename, items)
194  " Todo: What about local structures?
195  let fnames = join(map(tagfiles(), 'escape(v:val, " \\")'))
196  if fnames == ''
197    return []
198  endif
199
200  let typename = a:typename
201  let qflist = []
202  while 1
203    exe 'silent! vimgrep /\t' . typename . '\(\t\|$\)/j ' . fnames
204    let qflist = getqflist()
205    if len(qflist) > 0 || match(typename, "::") < 0
206      break
207    endif
208    " No match for "struct:context::name", remove "context::" and try again.
209    let typename = substitute(typename, ':[^:]*::', ':', '')
210  endwhile
211
212  let matches = []
213  for l in qflist
214    let memb = matchstr(l['text'], '[^\t]*')
215    if memb =~ '^' . a:items[0]
216      call add(matches, {'match': memb, 'tagline': l['text']})
217    endif
218  endfor
219
220  if len(matches) > 0
221    " No further items, return the result.
222    if len(a:items) == 1
223      return matches
224    endif
225
226    " More items following.  For each of the possible members find the
227    " matching following members.
228    return s:SearchMembers(matches, a:items[1:])
229  endif
230
231  " Failed to find anything.
232  return []
233endfunction
234
235" For matching members, find matches for following items.
236function! s:SearchMembers(matches, items)
237  let res = []
238  for i in range(len(a:matches))
239    let line = a:matches[i]['tagline']
240    let e = matchend(line, '\ttypename:')
241    if e > 0
242      " Use typename field
243      let name = matchstr(line, '[^\t]*', e)
244      call extend(res, s:StructMembers(name, a:items))
245    else
246      " Use the search command (the declaration itself).
247      let s = match(line, '\t\zs/^')
248      if s > 0
249	let e = match(line, a:matches[i]['match'], s)
250	if e > 0
251	  call extend(res, s:Nextitem(strpart(line, s, e - s), a:items))
252	endif
253      endif
254    endif
255  endfor
256  return res
257endfunc
258