1" Vim completion script 2" Language: C 3" Maintainer: Bram Moolenaar <[email protected]> 4" Last Change: 2005 Dec 18 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 else 28 break 29 endif 30 endwhile 31 32 " Return the column of the last word, which is going to be changed. 33 " Remember the text that comes before it in s:prepended. 34 if lastword == -1 35 let s:prepended = '' 36 return start 37 endif 38 let s:prepended = strpart(line, start, lastword - start) 39 return lastword 40 endif 41 42 " Return list of matches. 43 44 let base = s:prepended . a:base 45 46 " Split item in words, keep empty word after "." or "->". 47 " "aa" -> ['aa'], "aa." -> ['aa', ''], "aa.bb" -> ['aa', 'bb'], etc. 48 let items = split(base, '\.\|->', 1) 49 if len(items) <= 1 50 " Don't do anything for an empty base, would result in all the tags in the 51 " tags file. 52 if base == '' 53 return [] 54 endif 55 56 " Only one part, no "." or "->": complete from tags file. 57 " When local completion is wanted CTRL-N would have been used. 58 return map(taglist('^' . base), 'v:val["name"]') 59 endif 60 61 " Find the variable items[0]. 62 " 1. in current function (like with "gd") 63 " 2. in tags file(s) (like with ":tag") 64 " 3. in current file (like with "gD") 65 let res = [] 66 if searchdecl(items[0], 0, 1) == 0 67 " Found, now figure out the type. 68 " TODO: join previous line if it makes sense 69 let line = getline('.') 70 let col = col('.') 71 let res = s:Nextitem(strpart(line, 0, col), items[1:]) 72 endif 73 74 if len(res) == 0 75 " Find the variable in the tags file(s) 76 let diclist = taglist('^' . items[0] . '$') 77 78 let res = [] 79 for i in range(len(diclist)) 80 " New ctags has the "typename" field. 81 if has_key(diclist[i], 'typename') 82 call extend(res, s:StructMembers(diclist[i]['typename'], items[1:])) 83 endif 84 85 " For a variable use the command, which must be a search pattern that 86 " shows the declaration of the variable. 87 if diclist[i]['kind'] == 'v' 88 let line = diclist[i]['cmd'] 89 if line[0] == '/' && line[1] == '^' 90 let col = match(line, '\<' . items[0] . '\>') 91 call extend(res, s:Nextitem(strpart(line, 2, col - 2), items[1:])) 92 endif 93 endif 94 endfor 95 endif 96 97 if len(res) == 0 && searchdecl(items[0], 1) == 0 98 " Found, now figure out the type. 99 " TODO: join previous line if it makes sense 100 let line = getline('.') 101 let col = col('.') 102 let res = s:Nextitem(strpart(line, 0, col), items[1:]) 103 endif 104 105 " If the one and only match was what's already there and it is a composite 106 " type, add a "." or "->". 107 if len(res) == 1 && res[0]['match'] == items[-1] && len(s:SearchMembers(res, [''])) > 0 108 " If there is a '*' before the name use "->". 109 if match(res[0]['tagline'], '\*\s*' . res[0]['match']) > 0 110 let res[0]['match'] .= '->' 111 else 112 let res[0]['match'] .= '.' 113 endif 114 endif 115 116 return map(res, 'v:val["match"]') 117endfunc 118 119" Find composing type in "lead" and match items[0] with it. 120" Repeat this recursively for items[1], if it's there. 121" Return the list of matches. 122function! s:Nextitem(lead, items) 123 124 " Use the text up to the variable name and split it in tokens. 125 let tokens = split(a:lead, '\s\+\|\<') 126 127 " Try to recognize the type of the variable. This is rough guessing... 128 let res = [] 129 for tidx in range(len(tokens)) 130 131 " Recognize "struct foobar" and "union foobar". 132 if (tokens[tidx] == 'struct' || tokens[tidx] == 'union') && tidx + 1 < len(tokens) 133 let res = s:StructMembers(tokens[tidx] . ':' . tokens[tidx + 1], a:items) 134 break 135 endif 136 137 " TODO: add more reserved words 138 if index(['int', 'float', 'static', 'unsigned', 'extern'], tokens[tidx]) >= 0 139 continue 140 endif 141 142 " Use the tags file to find out if this is a typedef. 143 let diclist = taglist('^' . tokens[tidx] . '$') 144 for tagidx in range(len(diclist)) 145 " New ctags has the "typename" field. 146 if has_key(diclist[tagidx], 'typename') 147 call extend(res, s:StructMembers(diclist[tagidx]['typename'], a:items)) 148 continue 149 endif 150 151 " Only handle typedefs here. 152 if diclist[tagidx]['kind'] != 't' 153 continue 154 endif 155 156 " For old ctags we recognize "typedef struct aaa" and 157 " "typedef union bbb" in the tags file command. 158 let cmd = diclist[tagidx]['cmd'] 159 let ei = matchend(cmd, 'typedef\s\+') 160 if ei > 1 161 let cmdtokens = split(strpart(cmd, ei), '\s\+\|\<') 162 if len(cmdtokens) > 1 163 if cmdtokens[0] == 'struct' || cmdtokens[0] == 'union' 164 let name = '' 165 " Use the first identifier after the "struct" or "union" 166 for ti in range(len(cmdtokens) - 1) 167 if cmdtokens[ti] =~ '^\w' 168 let name = cmdtokens[ti] 169 break 170 endif 171 endfor 172 if name != '' 173 call extend(res, s:StructMembers(cmdtokens[0] . ':' . name, a:items)) 174 endif 175 else 176 " Could be "typedef other_T some_T". 177 call extend(res, s:Nextitem(cmdtokens[0], a:items)) 178 endif 179 endif 180 endif 181 endfor 182 if len(res) > 0 183 break 184 endif 185 endfor 186 187 return res 188endfunction 189 190 191" Return a list with resulting matches. 192" Each match is a dictionary with "match" and "tagline" entries. 193function! s:StructMembers(typename, items) 194 " Todo: What about local structures? 195 let fnames = join(map(tagfiles(), 'escape(v:val, " \\")')) 196 if fnames == '' 197 return [] 198 endif 199 200 let typename = a:typename 201 let qflist = [] 202 while 1 203 exe 'silent! vimgrep /\t' . typename . '\(\t\|$\)/j ' . fnames 204 let qflist = getqflist() 205 if len(qflist) > 0 || match(typename, "::") < 0 206 break 207 endif 208 " No match for "struct:context::name", remove "context::" and try again. 209 let typename = substitute(typename, ':[^:]*::', ':', '') 210 endwhile 211 212 let matches = [] 213 for l in qflist 214 let memb = matchstr(l['text'], '[^\t]*') 215 if memb =~ '^' . a:items[0] 216 call add(matches, {'match': memb, 'tagline': l['text']}) 217 endif 218 endfor 219 220 if len(matches) > 0 221 " No further items, return the result. 222 if len(a:items) == 1 223 return matches 224 endif 225 226 " More items following. For each of the possible members find the 227 " matching following members. 228 return s:SearchMembers(matches, a:items[1:]) 229 endif 230 231 " Failed to find anything. 232 return [] 233endfunction 234 235" For matching members, find matches for following items. 236function! s:SearchMembers(matches, items) 237 let res = [] 238 for i in range(len(a:matches)) 239 let line = a:matches[i]['tagline'] 240 let e = matchend(line, '\ttypename:') 241 if e > 0 242 " Use typename field 243 let name = matchstr(line, '[^\t]*', e) 244 call extend(res, s:StructMembers(name, a:items)) 245 else 246 " Use the search command (the declaration itself). 247 let s = match(line, '\t\zs/^') 248 if s > 0 249 let e = match(line, a:matches[i]['match'], s) 250 if e > 0 251 call extend(res, s:Nextitem(strpart(line, s, e - s), a:items)) 252 endif 253 endif 254 endif 255 endfor 256 return res 257endfunc 258