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