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