1" Vim completion script 2" Language: C 3" Maintainer: Bram Moolenaar <[email protected]> 4" Last Change: 2006 Jan 29 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), 's:Tag2item(v:val)') 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" 120" Turn the tag info "val" into an item for completion. 121" "val" is is an item in the list returned by taglist(). 122function! s:Tag2item(val) 123 if has_key(a:val, "kind") && a:val["kind"] == 'v' 124 if len(s:SearchMembers([{'match': a:val["name"], 'dict': a:val}], [''])) > 0 125 " If there is a '*' before the name use "->". This assumes the command 126 " is a search pattern! 127 if match(a:val['cmd'], '\*\s*' . a:val['name'] . '\>') > 0 128 return a:val["name"] . '->' 129 else 130 return a:val["name"] . '.' 131 endif 132 endif 133 endif 134 return a:val["name"] 135endfunction 136 137 138" Find composing type in "lead" and match items[0] with it. 139" Repeat this recursively for items[1], if it's there. 140" Return the list of matches. 141function! s:Nextitem(lead, items) 142 143 " Use the text up to the variable name and split it in tokens. 144 let tokens = split(a:lead, '\s\+\|\<') 145 146 " Try to recognize the type of the variable. This is rough guessing... 147 let res = [] 148 for tidx in range(len(tokens)) 149 150 " Recognize "struct foobar" and "union foobar". 151 if (tokens[tidx] == 'struct' || tokens[tidx] == 'union') && tidx + 1 < len(tokens) 152 let res = s:StructMembers(tokens[tidx] . ':' . tokens[tidx + 1], a:items) 153 break 154 endif 155 156 " TODO: add more reserved words 157 if index(['int', 'float', 'static', 'unsigned', 'extern'], tokens[tidx]) >= 0 158 continue 159 endif 160 161 " Use the tags file to find out if this is a typedef. 162 let diclist = taglist('^' . tokens[tidx] . '$') 163 for tagidx in range(len(diclist)) 164 " New ctags has the "typename" field. 165 if has_key(diclist[tagidx], 'typename') 166 call extend(res, s:StructMembers(diclist[tagidx]['typename'], a:items)) 167 continue 168 endif 169 170 " Only handle typedefs here. 171 if diclist[tagidx]['kind'] != 't' 172 continue 173 endif 174 175 " For old ctags we recognize "typedef struct aaa" and 176 " "typedef union bbb" in the tags file command. 177 let cmd = diclist[tagidx]['cmd'] 178 let ei = matchend(cmd, 'typedef\s\+') 179 if ei > 1 180 let cmdtokens = split(strpart(cmd, ei), '\s\+\|\<') 181 if len(cmdtokens) > 1 182 if cmdtokens[0] == 'struct' || cmdtokens[0] == 'union' 183 let name = '' 184 " Use the first identifier after the "struct" or "union" 185 for ti in range(len(cmdtokens) - 1) 186 if cmdtokens[ti] =~ '^\w' 187 let name = cmdtokens[ti] 188 break 189 endif 190 endfor 191 if name != '' 192 call extend(res, s:StructMembers(cmdtokens[0] . ':' . name, a:items)) 193 endif 194 else 195 " Could be "typedef other_T some_T". 196 call extend(res, s:Nextitem(cmdtokens[0], a:items)) 197 endif 198 endif 199 endif 200 endfor 201 if len(res) > 0 202 break 203 endif 204 endfor 205 206 return res 207endfunction 208 209 210" Return a list with resulting matches. 211" Each match is a dictionary with "match" and "tagline" entries. 212function! s:StructMembers(typename, items) 213 " Todo: What about local structures? 214 let fnames = join(map(tagfiles(), 'escape(v:val, " \\")')) 215 if fnames == '' 216 return [] 217 endif 218 219 let typename = a:typename 220 let qflist = [] 221 while 1 222 exe 'silent! vimgrep /\t' . typename . '\(\t\|$\)/j ' . fnames 223 let qflist = getqflist() 224 if len(qflist) > 0 || match(typename, "::") < 0 225 break 226 endif 227 " No match for "struct:context::name", remove "context::" and try again. 228 let typename = substitute(typename, ':[^:]*::', ':', '') 229 endwhile 230 231 let matches = [] 232 for l in qflist 233 let memb = matchstr(l['text'], '[^\t]*') 234 if memb =~ '^' . a:items[0] 235 call add(matches, {'match': memb, 'tagline': l['text']}) 236 endif 237 endfor 238 239 if len(matches) > 0 240 " No further items, return the result. 241 if len(a:items) == 1 242 return matches 243 endif 244 245 " More items following. For each of the possible members find the 246 " matching following members. 247 return s:SearchMembers(matches, a:items[1:]) 248 endif 249 250 " Failed to find anything. 251 return [] 252endfunction 253 254" For matching members, find matches for following items. 255function! s:SearchMembers(matches, items) 256 let res = [] 257 for i in range(len(a:matches)) 258 let typename = '' 259 if has_key(a:matches[i], 'dict') 260 "if a:matches[i].dict['name'] == "gui" 261 "echomsg string(a:matches[i].dict) 262 "endif 263 if has_key(a:matches[i].dict, 'typename') 264 let typename = a:matches[i].dict['typename'] 265 endif 266 let line = "\t" . a:matches[i].dict['cmd'] 267 else 268 let line = a:matches[i]['tagline'] 269 let e = matchend(line, '\ttypename:') 270 if e > 0 271 " Use typename field 272 let typename = matchstr(line, '[^\t]*', e) 273 endif 274 endif 275 if typename != '' 276 call extend(res, s:StructMembers(name, a:items)) 277 else 278 " Use the search command (the declaration itself). 279 let s = match(line, '\t\zs/^') 280 if s > 0 281 let e = match(line, '\<' . a:matches[i]['match'] . '\>', s) 282 if e > 0 283 "if a:matches[i].dict['name'] == "gui" 284 "echomsg strpart(line, s, e - s) 285 "endif 286 call extend(res, s:Nextitem(strpart(line, s, e - s), a:items)) 287 endif 288 endif 289 endif 290 endfor 291 return res 292endfunc 293