blob: 6850e19a0b9cb90e81d094811e416a753b3fb0fe [file] [log] [blame]
Abaakouk Mehdi5fb87e72010-07-21 12:51:38 +02001" Vim completion script
2" Language: C
3" Maintainer: Bram Moolenaar <Bram@vim.org>
4" Last Change: 2007 Aug 30
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 stridx(strpart(line, 0, col), ';') != -1
123 " Handle multiple declarations on the same line.
124 let col2 = col - 1
125 while line[col2] != ';'
126 let col2 -= 1
127 endwhile
128 let line = strpart(line, col2 + 1)
129 let col -= col2
130 endif
131 if stridx(strpart(line, 0, col), ',') != -1
132 " Handle multiple declarations on the same line in a function
133 " declaration.
134 let col2 = col - 1
135 while line[col2] != ','
136 let col2 -= 1
137 endwhile
138 if strpart(line, col2 + 1, col - col2 - 1) =~ ' *[^ ][^ ]* *[^ ]'
139 let line = strpart(line, col2 + 1)
140 let col -= col2
141 endif
142 endif
143 if len(items) == 1
144 " Completing one word and it's a local variable: May add '[', '.' or
145 " '->'.
146 let match = items[0]
147 let kind = 'v'
148 if match(line, '\<' . match . '\s*\[') > 0
149 let match .= '['
150 else
151 let res = s:Nextitem(strpart(line, 0, col), [''], 0, 1)
152 if len(res) > 0
153 " There are members, thus add "." or "->".
154 if match(line, '\*[ \t(]*' . match . '\>') > 0
155 let match .= '->'
156 else
157 let match .= '.'
158 endif
159 endif
160 endif
161 let res = [{'match': match, 'tagline' : '', 'kind' : kind, 'info' : line}]
162 else
163 " Completing "var.", "var.something", etc.
164 let res = s:Nextitem(strpart(line, 0, col), items[-1], 0, 1)
165 endif
166 endif
167
168 if len(items) == 1
169 " Only one part, no "." or "->": complete from tags file.
170 let tags = taglist('^' . base)
171
172 " Remove members, these can't appear without something in front.
173 call filter(tags, 'has_key(v:val, "kind") ? v:val["kind"] != "m" : 1')
174
175 " Remove static matches in other files.
176 call filter(tags, '!has_key(v:val, "static") || !v:val["static"] || bufnr("%") == bufnr(v:val["filename"])')
177
178 call extend(res, map(tags, 's:Tag2item(v:val)'))
179 endif
180
181 if len(res) == 0
182 " Find the variable in the tags file(s)
183 let diclist = taglist('^' . items[0] . '$')
184
185 " Remove members, these can't appear without something in front.
186 call filter(diclist, 'has_key(v:val, "kind") ? v:val["kind"] != "m" : 1')
187
188 let res = []
189 for i in range(len(diclist))
190 " New ctags has the "typeref" field. Patched version has "typename".
191 if has_key(diclist[i], 'typename')
192 call extend(res, s:StructMembers(diclist[i]['typename'], items[1:], 1))
193 elseif has_key(diclist[i], 'typeref')
194 call extend(res, s:StructMembers(diclist[i]['typeref'], items[1:], 1))
195 endif
196
197 " For a variable use the command, which must be a search pattern that
198 " shows the declaration of the variable.
199 if diclist[i]['kind'] == 'v'
200 let line = diclist[i]['cmd']
201 if line[0] == '/' && line[1] == '^'
202 let col = match(line, '\<' . items[0] . '\>')
203 call extend(res, s:Nextitem(strpart(line, 2, col - 2), items[1:], 0, 1))
204 endif
205 endif
206 endfor
207 endif
208
209 if len(res) == 0 && searchdecl(items[0], 1) == 0
210 " Found, now figure out the type.
211 " TODO: join previous line if it makes sense
212 let line = getline('.')
213 let col = col('.')
214 let res = s:Nextitem(strpart(line, 0, col), items[1:], 0, 1)
215 endif
216
217 " If the last item(s) are [...] they need to be added to the matches.
218 let last = len(items) - 1
219 let brackets = ''
220 while last >= 0
221 if items[last][0] != '['
222 break
223 endif
224 let brackets = items[last] . brackets
225 let last -= 1
226 endwhile
227
228 return map(res, 's:Tagline2item(v:val, brackets)')
229endfunc
230
231function! s:GetAddition(line, match, memarg, bracket)
232 " Guess if the item is an array.
233 if a:bracket && match(a:line, a:match . '\s*\[') > 0
234 return '['
235 endif
236
237 " Check if the item has members.
238 if len(s:SearchMembers(a:memarg, [''], 0)) > 0
239 " If there is a '*' before the name use "->".
240 if match(a:line, '\*[ \t(]*' . a:match . '\>') > 0
241 return '->'
242 else
243 return '.'
244 endif
245 endif
246 return ''
247endfunction
248
249" Turn the tag info "val" into an item for completion.
250" "val" is is an item in the list returned by taglist().
251" If it is a variable we may add "." or "->". Don't do it for other types,
252" such as a typedef, by not including the info that s:GetAddition() uses.
253function! s:Tag2item(val)
254 let res = {'match': a:val['name']}
255
256 let res['extra'] = s:Tagcmd2extra(a:val['cmd'], a:val['name'], a:val['filename'])
257
258 let s = s:Dict2info(a:val)
259 if s != ''
260 let res['info'] = s
261 endif
262
263 let res['tagline'] = ''
264 if has_key(a:val, "kind")
265 let kind = a:val['kind']
266 let res['kind'] = kind
267 if kind == 'v'
268 let res['tagline'] = "\t" . a:val['cmd']
269 let res['dict'] = a:val
270 elseif kind == 'f'
271 let res['match'] = a:val['name'] . '('
272 endif
273 endif
274
275 return res
276endfunction
277
278" Use all the items in dictionary for the "info" entry.
279function! s:Dict2info(dict)
280 let info = ''
281 for k in sort(keys(a:dict))
282 let info .= k . repeat(' ', 10 - len(k))
283 if k == 'cmd'
284 let info .= substitute(matchstr(a:dict['cmd'], '/^\s*\zs.*\ze$/'), '\\\(.\)', '\1', 'g')
285 else
286 let info .= a:dict[k]
287 endif
288 let info .= "\n"
289 endfor
290 return info
291endfunc
292
293" Parse a tag line and return a dictionary with items like taglist()
294function! s:ParseTagline(line)
295 let l = split(a:line, "\t")
296 let d = {}
297 if len(l) >= 3
298 let d['name'] = l[0]
299 let d['filename'] = l[1]
300 let d['cmd'] = l[2]
301 let n = 2
302 if l[2] =~ '^/'
303 " Find end of cmd, it may contain Tabs.
304 while n < len(l) && l[n] !~ '/;"$'
305 let n += 1
306 let d['cmd'] .= " " . l[n]
307 endwhile
308 endif
309 for i in range(n + 1, len(l) - 1)
310 if l[i] == 'file:'
311 let d['static'] = 1
312 elseif l[i] !~ ':'
313 let d['kind'] = l[i]
314 else
315 let d[matchstr(l[i], '[^:]*')] = matchstr(l[i], ':\zs.*')
316 endif
317 endfor
318 endif
319
320 return d
321endfunction
322
323" Turn a match item "val" into an item for completion.
324" "val['match']" is the matching item.
325" "val['tagline']" is the tagline in which the last part was found.
326function! s:Tagline2item(val, brackets)
327 let line = a:val['tagline']
328 let add = s:GetAddition(line, a:val['match'], [a:val], a:brackets == '')
329 let res = {'word': a:val['match'] . a:brackets . add }
330
331 if has_key(a:val, 'info')
332 " Use info from Tag2item().
333 let res['info'] = a:val['info']
334 else
335 " Parse the tag line and add each part to the "info" entry.
336 let s = s:Dict2info(s:ParseTagline(line))
337 if s != ''
338 let res['info'] = s
339 endif
340 endif
341
342 if has_key(a:val, 'kind')
343 let res['kind'] = a:val['kind']
344 elseif add == '('
345 let res['kind'] = 'f'
346 else
347 let s = matchstr(line, '\t\(kind:\)\=\zs\S\ze\(\t\|$\)')
348 if s != ''
349 let res['kind'] = s
350 endif
351 endif
352
353 if has_key(a:val, 'extra')
354 let res['menu'] = a:val['extra']
355 return res
356 endif
357
358 " Isolate the command after the tag and filename.
359 let s = matchstr(line, '[^\t]*\t[^\t]*\t\zs\(/^.*$/\|[^\t]*\)\ze\(;"\t\|\t\|$\)')
360 if s != ''
361 let res['menu'] = s:Tagcmd2extra(s, a:val['match'], matchstr(line, '[^\t]*\t\zs[^\t]*\ze\t'))
362 endif
363 return res
364endfunction
365
366" Turn a command from a tag line to something that is useful in the menu
367function! s:Tagcmd2extra(cmd, name, fname)
368 if a:cmd =~ '^/^'
369 " The command is a search command, useful to see what it is.
370 let x = matchstr(a:cmd, '^/^\s*\zs.*\ze$/')
371 let x = substitute(x, '\<' . a:name . '\>', '@@', '')
372 let x = substitute(x, '\\\(.\)', '\1', 'g')
373 let x = x . ' - ' . a:fname
374 elseif a:cmd =~ '^\d*$'
375 " The command is a line number, the file name is more useful.
376 let x = a:fname . ' - ' . a:cmd
377 else
378 " Not recognized, use command and file name.
379 let x = a:cmd . ' - ' . a:fname
380 endif
381 return x
382endfunction
383
384" Find composing type in "lead" and match items[0] with it.
385" Repeat this recursively for items[1], if it's there.
386" When resolving typedefs "depth" is used to avoid infinite recursion.
387" Return the list of matches.
388function! s:Nextitem(lead, items, depth, all)
389
390 " Use the text up to the variable name and split it in tokens.
391 let tokens = split(a:lead, '\s\+\|\<')
392
393 " Try to recognize the type of the variable. This is rough guessing...
394 let res = []
395 for tidx in range(len(tokens))
396
397 " Skip tokens starting with a non-ID character.
398 if tokens[tidx] !~ '^\h'
399 continue
400 endif
401
402 " Recognize "struct foobar" and "union foobar".
403 " Also do "class foobar" when it's C++ after all (doesn't work very well
404 " though).
405 if (tokens[tidx] == 'struct' || tokens[tidx] == 'union' || tokens[tidx] == 'class') && tidx + 1 < len(tokens)
406 let res = s:StructMembers(tokens[tidx] . ':' . tokens[tidx + 1], a:items, a:all)
407 break
408 endif
409
410 " TODO: add more reserved words
411 if index(['int', 'short', 'char', 'float', 'double', 'static', 'unsigned', 'extern'], tokens[tidx]) >= 0
412 continue
413 endif
414
415 " Use the tags file to find out if this is a typedef.
416 let diclist = taglist('^' . tokens[tidx] . '$')
417 for tagidx in range(len(diclist))
418 let item = diclist[tagidx]
419
420 " New ctags has the "typeref" field. Patched version has "typename".
421 if has_key(item, 'typeref')
422 call extend(res, s:StructMembers(item['typeref'], a:items, a:all))
423 continue
424 endif
425 if has_key(item, 'typename')
426 call extend(res, s:StructMembers(item['typename'], a:items, a:all))
427 continue
428 endif
429
430 " Only handle typedefs here.
431 if item['kind'] != 't'
432 continue
433 endif
434
435 " Skip matches local to another file.
436 if has_key(item, 'static') && item['static'] && bufnr('%') != bufnr(item['filename'])
437 continue
438 endif
439
440 " For old ctags we recognize "typedef struct aaa" and
441 " "typedef union bbb" in the tags file command.
442 let cmd = item['cmd']
443 let ei = matchend(cmd, 'typedef\s\+')
444 if ei > 1
445 let cmdtokens = split(strpart(cmd, ei), '\s\+\|\<')
446 if len(cmdtokens) > 1
447 if cmdtokens[0] == 'struct' || cmdtokens[0] == 'union' || cmdtokens[0] == 'class'
448 let name = ''
449 " Use the first identifier after the "struct" or "union"
450 for ti in range(len(cmdtokens) - 1)
451 if cmdtokens[ti] =~ '^\w'
452 let name = cmdtokens[ti]
453 break
454 endif
455 endfor
456 if name != ''
457 call extend(res, s:StructMembers(cmdtokens[0] . ':' . name, a:items, a:all))
458 endif
459 elseif a:depth < 10
460 " Could be "typedef other_T some_T".
461 call extend(res, s:Nextitem(cmdtokens[0], a:items, a:depth + 1, a:all))
462 endif
463 endif
464 endif
465 endfor
466 if len(res) > 0
467 break
468 endif
469 endfor
470
471 return res
472endfunction
473
474
475" Search for members of structure "typename" in tags files.
476" Return a list with resulting matches.
477" Each match is a dictionary with "match" and "tagline" entries.
478" When "all" is non-zero find all, otherwise just return 1 if there is any
479" member.
480function! s:StructMembers(typename, items, all)
481 " Todo: What about local structures?
482 let fnames = join(map(tagfiles(), 'escape(v:val, " \\#%")'))
483 if fnames == ''
484 return []
485 endif
486
487 let typename = a:typename
488 let qflist = []
489 let cached = 0
490 if a:all == 0
491 let n = '1' " stop at first found match
492 if has_key(s:grepCache, a:typename)
493 let qflist = s:grepCache[a:typename]
494 let cached = 1
495 endif
496 else
497 let n = ''
498 endif
499 if !cached
500 while 1
501 exe 'silent! ' . n . 'vimgrep /\t' . typename . '\(\t\|$\)/j ' . fnames
502
503 let qflist = getqflist()
504 if len(qflist) > 0 || match(typename, "::") < 0
505 break
506 endif
507 " No match for "struct:context::name", remove "context::" and try again.
508 let typename = substitute(typename, ':[^:]*::', ':', '')
509 endwhile
510
511 if a:all == 0
512 " Store the result to be able to use it again later.
513 let s:grepCache[a:typename] = qflist
514 endif
515 endif
516
517 " Put matching members in matches[].
518 let matches = []
519 for l in qflist
520 let memb = matchstr(l['text'], '[^\t]*')
521 if memb =~ '^' . a:items[0]
522 " Skip matches local to another file.
523 if match(l['text'], "\tfile:") < 0 || bufnr('%') == bufnr(matchstr(l['text'], '\t\zs[^\t]*'))
524 let item = {'match': memb, 'tagline': l['text']}
525
526 " Add the kind of item.
527 let s = matchstr(l['text'], '\t\(kind:\)\=\zs\S\ze\(\t\|$\)')
528 if s != ''
529 let item['kind'] = s
530 if s == 'f'
531 let item['match'] = memb . '('
532 endif
533 endif
534
535 call add(matches, item)
536 endif
537 endif
538 endfor
539
540 if len(matches) > 0
541 " Skip over [...] items
542 let idx = 1
543 while 1
544 if idx >= len(a:items)
545 return matches " No further items, return the result.
546 endif
547 if a:items[idx][0] != '['
548 break
549 endif
550 let idx += 1
551 endwhile
552
553 " More items following. For each of the possible members find the
554 " matching following members.
555 return s:SearchMembers(matches, a:items[idx :], a:all)
556 endif
557
558 " Failed to find anything.
559 return []
560endfunction
561
562" For matching members, find matches for following items.
563" When "all" is non-zero find all, otherwise just return 1 if there is any
564" member.
565function! s:SearchMembers(matches, items, all)
566 let res = []
567 for i in range(len(a:matches))
568 let typename = ''
569 if has_key(a:matches[i], 'dict')
570 if has_key(a:matches[i].dict, 'typename')
571 let typename = a:matches[i].dict['typename']
572 elseif has_key(a:matches[i].dict, 'typeref')
573 let typename = a:matches[i].dict['typeref']
574 endif
575 let line = "\t" . a:matches[i].dict['cmd']
576 else
577 let line = a:matches[i]['tagline']
578 let e = matchend(line, '\ttypename:')
579 if e < 0
580 let e = matchend(line, '\ttyperef:')
581 endif
582 if e > 0
583 " Use typename field
584 let typename = matchstr(line, '[^\t]*', e)
585 endif
586 endif
587
588 if typename != ''
589 call extend(res, s:StructMembers(typename, a:items, a:all))
590 else
591 " Use the search command (the declaration itself).
592 let s = match(line, '\t\zs/^')
593 if s > 0
594 let e = match(line, '\<' . a:matches[i]['match'] . '\>', s)
595 if e > 0
596 call extend(res, s:Nextitem(strpart(line, s, e - s), a:items, 0, a:all))
597 endif
598 endif
599 endif
600 if a:all == 0 && len(res) > 0
601 break
602 endif
603 endfor
604 return res
605endfunc