home *** CD-ROM | disk | FTP | other *** search
/ vim.ftp.fu-berlin.de / 2015-02-03.vim.ftp.fu-berlin.de.tar / vim.ftp.fu-berlin.de / runtime / dos / autoload / ccomplete.vim < prev    next >
Encoding:
Text File  |  2012-05-31  |  17.2 KB  |  611 lines

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