1" Vim completion script 2" Language: C 3" Maintainer: Bram Moolenaar <[email protected]> 4" Last Change: 2006 Mar 24 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 if (tokens[tidx] == 'struct' || tokens[tidx] == 'union') && tidx + 1 < len(tokens) 383 let res = s:StructMembers(tokens[tidx] . ':' . tokens[tidx + 1], a:items, a:all) 384 break 385 endif 386 387 " TODO: add more reserved words 388 if index(['int', 'short', 'char', 'float', 'double', 'static', 'unsigned', 'extern'], tokens[tidx]) >= 0 389 continue 390 endif 391 392 " Use the tags file to find out if this is a typedef. 393 let diclist = taglist('^' . tokens[tidx] . '$') 394 for tagidx in range(len(diclist)) 395 let item = diclist[tagidx] 396 397 " New ctags has the "typeref" field. Patched version has "typename". 398 if has_key(item, 'typeref') 399 call extend(res, s:StructMembers(item['typeref'], a:items, a:all)) 400 continue 401 endif 402 if has_key(item, 'typename') 403 call extend(res, s:StructMembers(item['typename'], a:items, a:all)) 404 continue 405 endif 406 407 " Only handle typedefs here. 408 if item['kind'] != 't' 409 continue 410 endif 411 412 " Skip matches local to another file. 413 if has_key(item, 'static') && item['static'] && bufnr('%') != bufnr(item['filename']) 414 continue 415 endif 416 417 " For old ctags we recognize "typedef struct aaa" and 418 " "typedef union bbb" in the tags file command. 419 let cmd = item['cmd'] 420 let ei = matchend(cmd, 'typedef\s\+') 421 if ei > 1 422 let cmdtokens = split(strpart(cmd, ei), '\s\+\|\<') 423 if len(cmdtokens) > 1 424 if cmdtokens[0] == 'struct' || cmdtokens[0] == 'union' 425 let name = '' 426 " Use the first identifier after the "struct" or "union" 427 for ti in range(len(cmdtokens) - 1) 428 if cmdtokens[ti] =~ '^\w' 429 let name = cmdtokens[ti] 430 break 431 endif 432 endfor 433 if name != '' 434 call extend(res, s:StructMembers(cmdtokens[0] . ':' . name, a:items, a:all)) 435 endif 436 elseif a:depth < 10 437 " Could be "typedef other_T some_T". 438 call extend(res, s:Nextitem(cmdtokens[0], a:items, a:depth + 1, a:all)) 439 endif 440 endif 441 endif 442 endfor 443 if len(res) > 0 444 break 445 endif 446 endfor 447 448 return res 449endfunction 450 451 452" Search for members of structure "typename" in tags files. 453" Return a list with resulting matches. 454" Each match is a dictionary with "match" and "tagline" entries. 455" When "all" is non-zero find all, otherwise just return 1 if there is any 456" member. 457function! s:StructMembers(typename, items, all) 458 " Todo: What about local structures? 459 let fnames = join(map(tagfiles(), 'escape(v:val, " \\")')) 460 if fnames == '' 461 return [] 462 endif 463 464 let typename = a:typename 465 let qflist = [] 466 let cached = 0 467 if a:all == 0 468 let n = '1' " stop at first found match 469 if has_key(s:grepCache, a:typename) 470 let qflist = s:grepCache[a:typename] 471 let cached = 1 472 endif 473 else 474 let n = '' 475 endif 476 if !cached 477 while 1 478 exe 'silent! ' . n . 'vimgrep /\t' . typename . '\(\t\|$\)/j ' . fnames 479 480 let qflist = getqflist() 481 if len(qflist) > 0 || match(typename, "::") < 0 482 break 483 endif 484 " No match for "struct:context::name", remove "context::" and try again. 485 let typename = substitute(typename, ':[^:]*::', ':', '') 486 endwhile 487 488 if a:all == 0 489 " Store the result to be able to use it again later. 490 let s:grepCache[a:typename] = qflist 491 endif 492 endif 493 494 " Put matching members in matches[]. 495 let matches = [] 496 for l in qflist 497 let memb = matchstr(l['text'], '[^\t]*') 498 if memb =~ '^' . a:items[0] 499 " Skip matches local to another file. 500 if match(l['text'], "\tfile:") < 0 || bufnr('%') == bufnr(matchstr(l['text'], '\t\zs[^\t]*')) 501 let item = {'match': memb, 'tagline': l['text']} 502 503 " Add the kind of item. 504 let s = matchstr(l['text'], '\t\(kind:\)\=\zs\S\ze\(\t\|$\)') 505 if s != '' 506 let item['kind'] = s 507 if s == 'f' 508 let item['match'] = memb . '(' 509 endif 510 endif 511 512 call add(matches, item) 513 endif 514 endif 515 endfor 516 517 if len(matches) > 0 518 " Skip over [...] items 519 let idx = 1 520 while 1 521 if idx >= len(a:items) 522 return matches " No further items, return the result. 523 endif 524 if a:items[idx][0] != '[' 525 break 526 endif 527 let idx += 1 528 endwhile 529 530 " More items following. For each of the possible members find the 531 " matching following members. 532 return s:SearchMembers(matches, a:items[idx :], a:all) 533 endif 534 535 " Failed to find anything. 536 return [] 537endfunction 538 539" For matching members, find matches for following items. 540" When "all" is non-zero find all, otherwise just return 1 if there is any 541" member. 542function! s:SearchMembers(matches, items, all) 543 let res = [] 544 for i in range(len(a:matches)) 545 let typename = '' 546 if has_key(a:matches[i], 'dict') 547 if has_key(a:matches[i].dict, 'typename') 548 let typename = a:matches[i].dict['typename'] 549 elseif has_key(a:matches[i].dict, 'typeref') 550 let typename = a:matches[i].dict['typeref'] 551 endif 552 let line = "\t" . a:matches[i].dict['cmd'] 553 else 554 let line = a:matches[i]['tagline'] 555 let e = matchend(line, '\ttypename:') 556 if e < 0 557 let e = matchend(line, '\ttyperef:') 558 endif 559 if e > 0 560 " Use typename field 561 let typename = matchstr(line, '[^\t]*', e) 562 endif 563 endif 564 565 if typename != '' 566 call extend(res, s:StructMembers(typename, a:items, a:all)) 567 else 568 " Use the search command (the declaration itself). 569 let s = match(line, '\t\zs/^') 570 if s > 0 571 let e = match(line, '\<' . a:matches[i]['match'] . '\>', s) 572 if e > 0 573 call extend(res, s:Nextitem(strpart(line, s, e - s), a:items, 0, a:all)) 574 endif 575 endif 576 endif 577 if a:all == 0 && len(res) > 0 578 break 579 endif 580 endfor 581 return res 582endfunc 583