Bram Moolenaar | 18144c8 | 2006-04-12 21:52:12 +0000 | [diff] [blame] | 1 | "pythoncomplete.vim - Omni Completion for python |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 2 | " Maintainer: Aaron Griffin |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 3 | " Version: 0.3 |
| 4 | " Last Updated: 23 January 2006 |
| 5 | " |
| 6 | " v0.3 Changes: |
| 7 | " added top level def parsing |
| 8 | " for safety, call returns are not evaluated |
| 9 | " handful of parsing changes |
| 10 | " trailing ( and . characters |
| 11 | " argument completion on open parens |
| 12 | " stop parsing at current line - ++performance, local var resolution |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 13 | " |
| 14 | " TODO |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 15 | " RExec subclass |
| 16 | " Code cleanup + make class |
| 17 | " use internal dict, not globals() |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 18 | |
| 19 | if !has('python') |
| 20 | echo "Error: Required vim compiled with +python" |
| 21 | finish |
| 22 | endif |
| 23 | |
Bram Moolenaar | 18144c8 | 2006-04-12 21:52:12 +0000 | [diff] [blame] | 24 | function! pythoncomplete#Complete(findstart, base) |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 25 | "findstart = 1 when we need to get the text length |
| 26 | if a:findstart |
| 27 | let line = getline('.') |
| 28 | let idx = col('.') |
| 29 | while idx > 0 |
| 30 | let idx -= 1 |
| 31 | let c = line[idx-1] |
| 32 | if c =~ '\w' |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 33 | continue |
| 34 | elseif ! c =~ '\.' |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 35 | idx = -1 |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 36 | break |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 37 | else |
| 38 | break |
| 39 | endif |
| 40 | endwhile |
| 41 | |
| 42 | return idx |
| 43 | "findstart = 0 when we need to return the list of completions |
| 44 | else |
| 45 | execute "python get_completions('" . a:base . "')" |
Bram Moolenaar | 18144c8 | 2006-04-12 21:52:12 +0000 | [diff] [blame] | 46 | return g:pythoncomplete_completions |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 47 | endif |
| 48 | endfunction |
| 49 | |
| 50 | function! s:DefPython() |
| 51 | python << PYTHONEOF |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 52 | import vim, sys, types |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 53 | import __builtin__ |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 54 | import tokenize, keyword, cStringIO |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 55 | |
| 56 | LOCALDEFS = \ |
| 57 | ['LOCALDEFS', 'clean_up','eval_source_code', \ |
| 58 | 'get_completions', '__builtin__', '__builtins__', \ |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 59 | 'dbg', '__name__', 'vim', 'sys', 'parse_to_end', \ |
| 60 | 'parse_statement', 'tokenize', 'keyword', 'cStringIO', \ |
| 61 | 'debug_level', 'safe_eval', '_ctor', 'get_arguments', \ |
| 62 | 'strip_calls', 'types', 'parse_block'] |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 63 | |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 64 | def dbg(level,msg): |
| 65 | debug_level = 1 |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 66 | try: |
Bram Moolenaar | 18144c8 | 2006-04-12 21:52:12 +0000 | [diff] [blame] | 67 | debug_level = vim.eval("g:pythoncomplete_debug_level") |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 68 | except: |
| 69 | pass |
| 70 | if level <= debug_level: print(msg) |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 71 | |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 72 | def strip_calls(stmt): |
| 73 | parsed='' |
| 74 | level = 0 |
| 75 | for c in stmt: |
| 76 | if c in ['[','(']: |
| 77 | level += 1 |
| 78 | elif c in [')',']']: |
| 79 | level -= 1 |
| 80 | elif level == 0: |
| 81 | parsed += c |
| 82 | ##dbg(10,"stripped: %s" % parsed) |
| 83 | return parsed |
| 84 | |
| 85 | def get_completions(base): |
| 86 | stmt = vim.eval('expand("<cWORD>")') |
| 87 | #dbg(1,"statement: %s - %s" % (stmt, base)) |
| 88 | stmt = stmt+base |
| 89 | eval_source_code() |
| 90 | |
| 91 | try: |
| 92 | ridx = stmt.rfind('.') |
| 93 | if stmt[-1] == '(': |
| 94 | match = "" |
| 95 | stmt = strip_calls(stmt[:len(stmt)-1]) |
| 96 | all = get_arguments(eval(stmt)) |
| 97 | elif ridx == -1: |
| 98 | match = stmt |
| 99 | all = globals() + __builtin__.__dict__ |
| 100 | else: |
| 101 | match = stmt[ridx+1:] |
| 102 | stmt = strip_calls(stmt[:ridx]) |
| 103 | all = eval(stmt).__dict__ |
| 104 | |
| 105 | #dbg(15,"completions for: %s, match=%s" % (stmt,match)) |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 106 | completions = [] |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 107 | if type(all) == types.DictType: |
| 108 | for m in all: |
| 109 | if m.find('_') != 0 and m.find(match) == 0 and \ |
| 110 | m not in LOCALDEFS: |
| 111 | #dbg(25,"matched... %s, %s" % (m, m.find(match))) |
| 112 | typestr = str(all[m]) |
| 113 | if "function" in typestr: m += '(' |
| 114 | elif "method" in typestr: m += '(' |
| 115 | elif "module" in typestr: m += '.' |
| 116 | elif "class" in typestr: m += '(' |
| 117 | completions.append(m) |
| 118 | completions.sort() |
| 119 | else: |
| 120 | completions.append(all) |
| 121 | #dbg(10,"all completions: %s" % completions) |
Bram Moolenaar | 18144c8 | 2006-04-12 21:52:12 +0000 | [diff] [blame] | 122 | vim.command("let g:pythoncomplete_completions = %s" % completions) |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 123 | except: |
Bram Moolenaar | 18144c8 | 2006-04-12 21:52:12 +0000 | [diff] [blame] | 124 | vim.command("let g:pythoncomplete_completions = []") |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 125 | #dbg(1,"exception: %s" % sys.exc_info()[1]) |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 126 | clean_up() |
| 127 | |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 128 | def get_arguments(func_obj): |
| 129 | def _ctor(obj): |
| 130 | try: |
| 131 | return class_ob.__init__.im_func |
| 132 | except AttributeError: |
| 133 | for base in class_ob.__bases__: |
| 134 | rc = _find_constructor(base) |
| 135 | if rc is not None: return rc |
| 136 | return None |
| 137 | |
| 138 | arg_offset = 1 |
| 139 | if type(func_obj) == types.ClassType: func_obj = _ctor(func_obj) |
| 140 | elif type(func_obj) == types.MethodType: func_obj = func_obj.im_func |
| 141 | else: arg_offset = 0 |
| 142 | |
| 143 | #dbg(20,"%s, offset=%s" % (str(func_obj), arg_offset)) |
| 144 | |
| 145 | arg_text = '' |
| 146 | if type(func_obj) in [types.FunctionType, types.LambdaType]: |
| 147 | try: |
| 148 | cd = func_obj.func_code |
| 149 | real_args = cd.co_varnames[arg_offset:cd.co_argcount] |
| 150 | defaults = func_obj.func_defaults or [] |
| 151 | defaults = list(map(lambda name: "=%s" % name, defaults)) |
| 152 | defaults = [""] * (len(real_args)-len(defaults)) + defaults |
| 153 | items = map(lambda a,d: a+d, real_args, defaults) |
| 154 | if func_obj.func_code.co_flags & 0x4: |
| 155 | items.append("...") |
| 156 | if func_obj.func_code.co_flags & 0x8: |
| 157 | items.append("***") |
| 158 | arg_text = ", ".join(items) + ')' |
| 159 | |
| 160 | except: |
| 161 | #dbg(1,"exception: %s" % sys.exc_info()[1]) |
| 162 | pass |
| 163 | if len(arg_text) == 0: |
| 164 | # The doc string sometimes contains the function signature |
| 165 | # this works for alot of C modules that are part of the |
| 166 | # standard library |
| 167 | doc = getattr(func_obj, '__doc__', '') |
| 168 | if doc: |
| 169 | doc = doc.lstrip() |
| 170 | pos = doc.find('\n') |
| 171 | if pos > 0: |
| 172 | sigline = doc[:pos] |
| 173 | lidx = sigline.find('(') |
| 174 | ridx = sigline.find(')') |
| 175 | retidx = sigline.find('->') |
| 176 | ret = sigline[retidx+2:].strip() |
| 177 | if lidx > 0 and ridx > 0: |
| 178 | arg_text = sigline[lidx+1:ridx] + ')' |
| 179 | if len(ret) > 0: arg_text += ' #returns %s' % ret |
| 180 | #dbg(15,"argument completion: %s" % arg_text) |
| 181 | return arg_text |
| 182 | |
| 183 | def parse_to_end(gen): |
| 184 | stmt='' |
| 185 | level = 0 |
| 186 | for type, str, begin, end, line in gen: |
| 187 | if line == vim.eval('getline(\'.\')'): break |
| 188 | elif str == '\\': continue |
| 189 | elif str == ';': |
| 190 | break |
| 191 | elif type == tokenize.NEWLINE and level == 0: |
| 192 | break |
| 193 | elif str in ['[','(']: |
| 194 | level += 1 |
| 195 | elif str in [')',']']: |
| 196 | level -= 1 |
| 197 | elif level == 0: |
| 198 | stmt += str |
| 199 | #dbg(10,"current statement: %s" % stmt) |
| 200 | return stmt |
| 201 | |
| 202 | def parse_block(gen): |
| 203 | lines = [] |
| 204 | level = 0 |
| 205 | for type, str, begin, end, line in gen: |
| 206 | if line.replace('\n','') == vim.eval('getline(\'.\')'): break |
| 207 | elif type == tokenize.INDENT: |
| 208 | level += 1 |
| 209 | elif type == tokenize.DEDENT: |
| 210 | level -= 1 |
| 211 | if level == 0: break; |
| 212 | else: |
| 213 | stmt = parse_statement(gen,str) |
| 214 | if len(stmt) > 0: lines.append(stmt) |
| 215 | return lines |
| 216 | |
| 217 | def parse_statement(gen,curstr=''): |
| 218 | var = curstr |
| 219 | type, str, begin, end, line = gen.next() |
| 220 | if str == '=': |
| 221 | type, str, begin, end, line = gen.next() |
| 222 | if type == tokenize.NEWLINE: |
| 223 | return '' |
| 224 | elif type == tokenize.STRING or str == 'str': |
| 225 | return '%s = str' % var |
| 226 | elif str == '[' or str == 'list': |
| 227 | return '%s= list' % var |
| 228 | elif str == '{' or str == 'dict': |
| 229 | return '%s = dict' % var |
| 230 | elif type == tokenize.NUMBER: |
| 231 | return '%s = 0' % var |
| 232 | elif str == 'Set': |
| 233 | return '%s = Set' % var |
| 234 | elif str == 'open' or str == 'file': |
| 235 | return '%s = file' % var |
| 236 | else: |
| 237 | inst = str + parse_to_end(gen) |
| 238 | if len(inst) > 0: |
| 239 | #dbg(5,"found [%s = %s]" % (var, inst)) |
| 240 | return '%s = %s' % (var, inst) |
| 241 | return '' |
| 242 | |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 243 | def eval_source_code(): |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 244 | LINE=vim.eval('getline(\'.\')') |
| 245 | s = cStringIO.StringIO('\n'.join(vim.current.buffer[:]) + '\n') |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 246 | g = tokenize.generate_tokens(s.readline) |
| 247 | |
| 248 | stmts = [] |
| 249 | lineNo = 0 |
| 250 | try: |
| 251 | for type, str, begin, end, line in g: |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 252 | if line.replace('\n','') == vim.eval('getline(\'.\')'): break |
| 253 | elif begin[0] == lineNo: continue |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 254 | #junk |
| 255 | elif type == tokenize.INDENT or \ |
| 256 | type == tokenize.DEDENT or \ |
| 257 | type == tokenize.ERRORTOKEN or \ |
| 258 | type == tokenize.ENDMARKER or \ |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 259 | type == tokenize.NEWLINE or \ |
| 260 | type == tokenize.COMMENT: |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 261 | continue |
| 262 | #import statement |
| 263 | elif str == 'import': |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 264 | import_stmt=parse_to_end(g) |
| 265 | if len(import_stmt) > 0: |
| 266 | #dbg(5,"found [import %s]" % import_stmt) |
| 267 | stmts.append("import %s" % import_stmt) |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 268 | #import from statement |
| 269 | elif str == 'from': |
| 270 | type, str, begin, end, line = g.next() |
| 271 | mod = str |
| 272 | |
| 273 | type, str, begin, end, line = g.next() |
| 274 | if str != "import": break |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 275 | from_stmt=parse_to_end(g) |
| 276 | if len(from_stmt) > 0: |
| 277 | #dbg(5,"found [from %s import %s]" % (mod, from_stmt)) |
| 278 | stmts.append("from %s import %s" % (mod, from_stmt)) |
| 279 | #def statement |
| 280 | elif str == 'def': |
| 281 | funcstr = '' |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 282 | for type, str, begin, end, line in g: |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 283 | if line.replace('\n','') == vim.eval('getline(\'.\')'): break |
| 284 | elif str == ':': |
| 285 | stmts += parse_block(g) |
| 286 | break |
| 287 | funcstr += str |
| 288 | if len(funcstr) > 0: |
| 289 | #dbg(5,"found [def %s]" % funcstr) |
| 290 | stmts.append("def %s:\n pass" % funcstr) |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 291 | #class declaration |
| 292 | elif str == 'class': |
| 293 | type, str, begin, end, line = g.next() |
| 294 | classname = str |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 295 | #dbg(5,"found [class %s]" % classname) |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 296 | |
| 297 | level = 0 |
| 298 | members = [] |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 299 | for type, str, begin, end, line in g: |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 300 | if line.replace('\n','') == vim.eval('getline(\'.\')'): break |
| 301 | elif type == tokenize.INDENT: |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 302 | level += 1 |
| 303 | elif type == tokenize.DEDENT: |
| 304 | level -= 1 |
| 305 | if level == 0: break; |
| 306 | elif str == 'def': |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 307 | memberstr = '' |
| 308 | for type, str, begin, end, line in g: |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 309 | if line.replace('\n','') == vim.eval('getline(\'.\')'): break |
| 310 | elif str == ':': |
| 311 | stmts += parse_block(g) |
| 312 | break |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 313 | memberstr += str |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 314 | #dbg(5," member [%s]" % memberstr) |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 315 | members.append(memberstr) |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 316 | classstr = 'class %s:' % classname |
| 317 | for m in members: |
| 318 | classstr += ("\n def %s:\n pass" % m) |
| 319 | stmts.append("%s\n" % classstr) |
| 320 | elif keyword.iskeyword(str) or str in globals(): |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 321 | #dbg(5,"keyword = %s" % str) |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 322 | lineNo = begin[0] |
| 323 | else: |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 324 | assign = parse_statement(g,str) |
| 325 | if len(assign) > 0: stmts.append(assign) |
| 326 | |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 327 | for s in stmts: |
| 328 | try: |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 329 | #dbg(15,"evaluating: %s\n" % s) |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 330 | exec(s) in globals() |
| 331 | except: |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 332 | #dbg(1,"exception: %s" % sys.exc_info()[1]) |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 333 | pass |
| 334 | except: |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 335 | #dbg(1,"exception: %s" % sys.exc_info()[1]) |
| 336 | pass |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 337 | |
| 338 | def clean_up(): |
| 339 | for o in globals().keys(): |
| 340 | if o not in LOCALDEFS: |
| 341 | try: |
| 342 | exec('del %s' % o) in globals() |
| 343 | except: pass |
| 344 | |
| 345 | sys.path.extend(['.','..']) |
| 346 | PYTHONEOF |
| 347 | endfunction |
| 348 | |
Bram Moolenaar | 18144c8 | 2006-04-12 21:52:12 +0000 | [diff] [blame] | 349 | let g:pythoncomplete_debug_level = 0 |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 350 | call s:DefPython() |
| 351 | " vim: set et ts=4: |