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