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