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