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