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