1" Vim completion script 2" Language: C 3" Maintainer: Bram Moolenaar <[email protected]> 4" Last Change: 2006 Mar 11 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 "typename" field. 170 if has_key(diclist[i], 'typename') 171 call extend(res, s:StructMembers(diclist[i]['typename'], items[1:], 1)) 172 endif 173 174 " For a variable use the command, which must be a search pattern that 175 " shows the declaration of the variable. 176 if diclist[i]['kind'] == 'v' 177 let line = diclist[i]['cmd'] 178 if line[0] == '/' && line[1] == '^' 179 let col = match(line, '\<' . items[0] . '\>') 180 call extend(res, s:Nextitem(strpart(line, 2, col - 2), items[1:], 0, 1)) 181 endif 182 endif 183 endfor 184 endif 185 186 if len(res) == 0 && searchdecl(items[0], 1) == 0 187 " Found, now figure out the type. 188 " TODO: join previous line if it makes sense 189 let line = getline('.') 190 let col = col('.') 191 let res = s:Nextitem(strpart(line, 0, col), items[1:], 0, 1) 192 endif 193 194 " If the last item(s) are [...] they need to be added to the matches. 195 let last = len(items) - 1 196 let brackets = '' 197 while last >= 0 198 if items[last][0] != '[' 199 break 200 endif 201 let brackets = items[last] . brackets 202 let last -= 1 203 endwhile 204 205 return map(res, 's:Tagline2item(v:val, brackets)') 206endfunc 207 208function! s:GetAddition(line, match, memarg, bracket) 209 " Guess if the item is an array. 210 if a:bracket && match(a:line, a:match . '\s*\[') > 0 211 return '[' 212 endif 213 214 " Check if the item has members. 215 if len(s:SearchMembers(a:memarg, [''], 0)) > 0 216 " If there is a '*' before the name use "->". 217 if match(a:line, '\*[ \t(]*' . a:match . '\>') > 0 218 return '->' 219 else 220 return '.' 221 endif 222 endif 223 return '' 224endfunction 225 226" Turn the tag info "val" into an item for completion. 227" "val" is is an item in the list returned by taglist(). 228" If it is a variable we may add "." or "->". Don't do it for other types, 229" such as a typedef, by not including the info that s:GetAddition() uses. 230function! s:Tag2item(val) 231 let res = {'match': a:val['name']} 232 233 let res['extra'] = s:Tagcmd2extra(a:val['cmd'], a:val['name'], a:val['filename']) 234 235 " Use the whole search command as the "info" entry. 236 let s = matchstr(a:val['cmd'], '/^\s*\zs.*\ze$/') 237 if s != '' 238 let res['info'] = substitute(s, '\\\(.\)', '\1', 'g') 239 endif 240 241 let res['tagline'] = '' 242 if has_key(a:val, "kind") 243 let kind = a:val['kind'] 244 let res['kind'] = kind 245 if kind == 'v' 246 let res['tagline'] = "\t" . a:val['cmd'] 247 let res['dict'] = a:val 248 elseif kind == 'f' 249 let res['match'] = a:val['name'] . '(' 250 endif 251 endif 252 253 return res 254endfunction 255 256" Turn a match item "val" into an item for completion. 257" "val['match']" is the matching item. 258" "val['tagline']" is the tagline in which the last part was found. 259function! s:Tagline2item(val, brackets) 260 let line = a:val['tagline'] 261 let add = s:GetAddition(line, a:val['match'], [a:val], a:brackets == '') 262 let res = {'word': a:val['match'] . a:brackets . add } 263 264 if has_key(a:val, 'info') 265 " Use info from Tag2item(). 266 let res['info'] = a:val['info'] 267 else 268 " Use the whole search command as the "info" entry. 269 let s = matchstr(line, '\t/^\s*\zs.*\ze$/') 270 if s != '' 271 let res['info'] = substitute(s, '\\\(.\)', '\1', 'g') 272 endif 273 endif 274 275 if has_key(a:val, 'kind') 276 let res['kind'] = a:val['kind'] 277 elseif add == '(' 278 let res['kind'] = 'f' 279 else 280 let s = matchstr(line, '\t\(kind:\)\=\zs\S\ze\(\t\|$\)') 281 if s != '' 282 let res['kind'] = s 283 endif 284 endif 285 286 if has_key(a:val, 'extra') 287 let res['menu'] = a:val['extra'] 288 return res 289 endif 290 291 " Isolate the command after the tag and filename. 292 let s = matchstr(line, '[^\t]*\t[^\t]*\t\zs\(/^.*$/\|[^\t]*\)\ze\(;"\t\|\t\|$\)') 293 if s != '' 294 let res['menu'] = s:Tagcmd2extra(s, a:val['match'], matchstr(line, '[^\t]*\t\zs[^\t]*\ze\t')) 295 endif 296 return res 297endfunction 298 299" Turn a command from a tag line to something that is useful in the menu 300function! s:Tagcmd2extra(cmd, name, fname) 301 if a:cmd =~ '^/^' 302 " The command is a search command, useful to see what it is. 303 let x = matchstr(a:cmd, '^/^\s*\zs.*\ze$/') 304 let x = substitute(x, '\<' . a:name . '\>', '@@', '') 305 let x = substitute(x, '\\\(.\)', '\1', 'g') 306 let x = x . ' - ' . a:fname 307 elseif a:cmd =~ '^\d*$' 308 " The command is a line number, the file name is more useful. 309 let x = a:fname . ' - ' . a:cmd 310 else 311 " Not recognized, use command and file name. 312 let x = a:cmd . ' - ' . a:fname 313 endif 314 return x 315endfunction 316 317" Find composing type in "lead" and match items[0] with it. 318" Repeat this recursively for items[1], if it's there. 319" When resolving typedefs "depth" is used to avoid infinite recursion. 320" Return the list of matches. 321function! s:Nextitem(lead, items, depth, all) 322 323 " Use the text up to the variable name and split it in tokens. 324 let tokens = split(a:lead, '\s\+\|\<') 325 326 " Try to recognize the type of the variable. This is rough guessing... 327 let res = [] 328 for tidx in range(len(tokens)) 329 330 " Skip tokens starting with a non-ID character. 331 if tokens[tidx] !~ '^\h' 332 continue 333 endif 334 335 " Recognize "struct foobar" and "union foobar". 336 if (tokens[tidx] == 'struct' || tokens[tidx] == 'union') && tidx + 1 < len(tokens) 337 let res = s:StructMembers(tokens[tidx] . ':' . tokens[tidx + 1], a:items, a:all) 338 break 339 endif 340 341 " TODO: add more reserved words 342 if index(['int', 'short', 'char', 'float', 'double', 'static', 'unsigned', 'extern'], tokens[tidx]) >= 0 343 continue 344 endif 345 346 " Use the tags file to find out if this is a typedef. 347 let diclist = taglist('^' . tokens[tidx] . '$') 348 for tagidx in range(len(diclist)) 349 let item = diclist[tagidx] 350 351 " New ctags has the "typename" field. 352 if has_key(item, 'typename') 353 call extend(res, s:StructMembers(item['typename'], a:items, a:all)) 354 continue 355 endif 356 357 " Only handle typedefs here. 358 if item['kind'] != 't' 359 continue 360 endif 361 362 " Skip matches local to another file. 363 if has_key(item, 'static') && item['static'] && bufnr('%') != bufnr(item['filename']) 364 continue 365 endif 366 367 " For old ctags we recognize "typedef struct aaa" and 368 " "typedef union bbb" in the tags file command. 369 let cmd = item['cmd'] 370 let ei = matchend(cmd, 'typedef\s\+') 371 if ei > 1 372 let cmdtokens = split(strpart(cmd, ei), '\s\+\|\<') 373 if len(cmdtokens) > 1 374 if cmdtokens[0] == 'struct' || cmdtokens[0] == 'union' 375 let name = '' 376 " Use the first identifier after the "struct" or "union" 377 for ti in range(len(cmdtokens) - 1) 378 if cmdtokens[ti] =~ '^\w' 379 let name = cmdtokens[ti] 380 break 381 endif 382 endfor 383 if name != '' 384 call extend(res, s:StructMembers(cmdtokens[0] . ':' . name, a:items, a:all)) 385 endif 386 elseif a:depth < 10 387 " Could be "typedef other_T some_T". 388 call extend(res, s:Nextitem(cmdtokens[0], a:items, a:depth + 1, a:all)) 389 endif 390 endif 391 endif 392 endfor 393 if len(res) > 0 394 break 395 endif 396 endfor 397 398 return res 399endfunction 400 401 402" Search for members of structure "typename" in tags files. 403" Return a list with resulting matches. 404" Each match is a dictionary with "match" and "tagline" entries. 405" When "all" is non-zero find all, otherwise just return 1 if there is any 406" member. 407function! s:StructMembers(typename, items, all) 408 " Todo: What about local structures? 409 let fnames = join(map(tagfiles(), 'escape(v:val, " \\")')) 410 if fnames == '' 411 return [] 412 endif 413 414 let typename = a:typename 415 let qflist = [] 416 let cached = 0 417 if a:all == 0 418 let n = '1' " stop at first found match 419 if has_key(s:grepCache, a:typename) 420 let qflist = s:grepCache[a:typename] 421 let cached = 1 422 endif 423 else 424 let n = '' 425 endif 426 if !cached 427 while 1 428 exe 'silent! ' . n . 'vimgrep /\t' . typename . '\(\t\|$\)/j ' . fnames 429 430 let qflist = getqflist() 431 if len(qflist) > 0 || match(typename, "::") < 0 432 break 433 endif 434 " No match for "struct:context::name", remove "context::" and try again. 435 let typename = substitute(typename, ':[^:]*::', ':', '') 436 endwhile 437 438 if a:all == 0 439 " Store the result to be able to use it again later. 440 let s:grepCache[a:typename] = qflist 441 endif 442 endif 443 444 " Put matching members in matches[]. 445 let matches = [] 446 for l in qflist 447 let memb = matchstr(l['text'], '[^\t]*') 448 if memb =~ '^' . a:items[0] 449 " Skip matches local to another file. 450 if match(l['text'], "\tfile:") < 0 || bufnr('%') == bufnr(matchstr(l['text'], '\t\zs[^\t]*')) 451 let item = {'match': memb, 'tagline': l['text']} 452 453 " Add the kind of item. 454 let s = matchstr(l['text'], '\t\(kind:\)\=\zs\S\ze\(\t\|$\)') 455 if s != '' 456 let item['kind'] = s 457 if s == 'f' 458 let item['match'] = memb . '(' 459 endif 460 endif 461 462 call add(matches, item) 463 endif 464 endif 465 endfor 466 467 if len(matches) > 0 468 " Skip over [...] items 469 let idx = 1 470 while 1 471 if idx >= len(a:items) 472 return matches " No further items, return the result. 473 endif 474 if a:items[idx][0] != '[' 475 break 476 endif 477 let idx += 1 478 endwhile 479 480 " More items following. For each of the possible members find the 481 " matching following members. 482 return s:SearchMembers(matches, a:items[idx :], a:all) 483 endif 484 485 " Failed to find anything. 486 return [] 487endfunction 488 489" For matching members, find matches for following items. 490" When "all" is non-zero find all, otherwise just return 1 if there is any 491" member. 492function! s:SearchMembers(matches, items, all) 493 let res = [] 494 for i in range(len(a:matches)) 495 let typename = '' 496 if has_key(a:matches[i], 'dict') 497 if has_key(a:matches[i].dict, 'typename') 498 let typename = a:matches[i].dict['typename'] 499 endif 500 let line = "\t" . a:matches[i].dict['cmd'] 501 else 502 let line = a:matches[i]['tagline'] 503 let e = matchend(line, '\ttypename:') 504 if e > 0 505 " Use typename field 506 let typename = matchstr(line, '[^\t]*', e) 507 endif 508 endif 509 510 if typename != '' 511 call extend(res, s:StructMembers(typename, a:items, a:all)) 512 else 513 " Use the search command (the declaration itself). 514 let s = match(line, '\t\zs/^') 515 if s > 0 516 let e = match(line, '\<' . a:matches[i]['match'] . '\>', s) 517 if e > 0 518 call extend(res, s:Nextitem(strpart(line, s, e - s), a:items, 0, a:all)) 519 endif 520 endif 521 endif 522 if a:all == 0 && len(res) > 0 523 break 524 endif 525 endfor 526 return res 527endfunc 528