Bram Moolenaar | 18144c8 | 2006-04-12 21:52:12 +0000 | [diff] [blame] | 1 | "pythoncomplete.vim - Omni Completion for python |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 2 | " Maintainer: Aaron Griffin <aaronmgriffin@gmail.com> |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 3 | " Version: 0.7 |
| 4 | " Last Updated: 19 Oct 2006 |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 5 | " |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 6 | " Changes |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 7 | " TODO: |
| 8 | " User defined docstrings aren't handled right... |
| 9 | " 'info' item output can use some formatting work |
| 10 | " Add an "unsafe eval" mode, to allow for return type evaluation |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 11 | " Complete basic syntax along with import statements |
| 12 | " i.e. "import url<c-x,c-o>" |
| 13 | " Continue parsing on invalid line?? |
| 14 | " |
| 15 | " v 0.7 |
| 16 | " * Fixed function list sorting (_ and __ at the bottom) |
| 17 | " * Removed newline removal from docs. It appears vim handles these better in |
| 18 | " recent patches |
| 19 | " |
| 20 | " v 0.6: |
| 21 | " * Fixed argument completion |
| 22 | " * Removed the 'kind' completions, as they are better indicated |
| 23 | " with real syntax |
| 24 | " * Added tuple assignment parsing (whoops, that was forgotten) |
| 25 | " * Fixed import handling when flattening scope |
| 26 | " |
| 27 | " v 0.5: |
| 28 | " Yeah, I skipped a version number - 0.4 was never public. |
| 29 | " It was a bugfix version on top of 0.3. This is a complete |
| 30 | " rewrite. |
| 31 | " |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 32 | |
| 33 | if !has('python') |
| 34 | echo "Error: Required vim compiled with +python" |
| 35 | finish |
| 36 | endif |
| 37 | |
Bram Moolenaar | 18144c8 | 2006-04-12 21:52:12 +0000 | [diff] [blame] | 38 | function! pythoncomplete#Complete(findstart, base) |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 39 | "findstart = 1 when we need to get the text length |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 40 | if a:findstart == 1 |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 41 | let line = getline('.') |
| 42 | let idx = col('.') |
| 43 | while idx > 0 |
| 44 | let idx -= 1 |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 45 | let c = line[idx] |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 46 | if c =~ '\w' |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 47 | continue |
| 48 | elseif ! c =~ '\.' |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 49 | let idx = -1 |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 50 | break |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 51 | else |
| 52 | break |
| 53 | endif |
| 54 | endwhile |
| 55 | |
| 56 | return idx |
| 57 | "findstart = 0 when we need to return the list of completions |
| 58 | else |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 59 | "vim no longer moves the cursor upon completion... fix that |
| 60 | let line = getline('.') |
| 61 | let idx = col('.') |
| 62 | let cword = '' |
| 63 | while idx > 0 |
| 64 | let idx -= 1 |
| 65 | let c = line[idx] |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 66 | if c =~ '\w' || c =~ '\.' || c == '(' |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 67 | let cword = c . cword |
| 68 | continue |
| 69 | elseif strlen(cword) > 0 || idx == 0 |
| 70 | break |
| 71 | endif |
| 72 | endwhile |
| 73 | execute "python vimcomplete('" . cword . "', '" . a:base . "')" |
Bram Moolenaar | 18144c8 | 2006-04-12 21:52:12 +0000 | [diff] [blame] | 74 | return g:pythoncomplete_completions |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 75 | endif |
| 76 | endfunction |
| 77 | |
| 78 | function! s:DefPython() |
| 79 | python << PYTHONEOF |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 80 | import sys, tokenize, cStringIO, types |
| 81 | from token import NAME, DEDENT, NEWLINE, STRING |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 82 | |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 83 | debugstmts=[] |
| 84 | def dbg(s): debugstmts.append(s) |
| 85 | def showdbg(): |
| 86 | for d in debugstmts: print "DBG: %s " % d |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 87 | |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 88 | def vimcomplete(context,match): |
| 89 | global debugstmts |
| 90 | debugstmts = [] |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 91 | try: |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 92 | import vim |
| 93 | def complsort(x,y): |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 94 | try: |
| 95 | xa = x['abbr'] |
| 96 | ya = y['abbr'] |
| 97 | if xa[0] == '_': |
| 98 | if xa[1] == '_' and ya[0:2] == '__': |
| 99 | return xa > ya |
| 100 | elif ya[0:2] == '__': |
| 101 | return -1 |
| 102 | elif y[0] == '_': |
| 103 | return xa > ya |
| 104 | else: |
| 105 | return 1 |
| 106 | elif ya[0] == '_': |
| 107 | return -1 |
| 108 | else: |
| 109 | return xa > ya |
| 110 | except: |
| 111 | return 0 |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 112 | cmpl = Completer() |
| 113 | cmpl.evalsource('\n'.join(vim.current.buffer),vim.eval("line('.')")) |
| 114 | all = cmpl.get_completions(context,match) |
| 115 | all.sort(complsort) |
| 116 | dictstr = '[' |
| 117 | # have to do this for double quoting |
| 118 | for cmpl in all: |
| 119 | dictstr += '{' |
| 120 | for x in cmpl: dictstr += '"%s":"%s",' % (x,cmpl[x]) |
| 121 | dictstr += '"icase":0},' |
| 122 | if dictstr[-1] == ',': dictstr = dictstr[:-1] |
| 123 | dictstr += ']' |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 124 | #dbg("dict: %s" % dictstr) |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 125 | vim.command("silent let g:pythoncomplete_completions = %s" % dictstr) |
| 126 | #dbg("Completion dict:\n%s" % all) |
| 127 | except vim.error: |
| 128 | dbg("VIM Error: %s" % vim.error) |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 129 | |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 130 | class Completer(object): |
| 131 | def __init__(self): |
| 132 | self.compldict = {} |
| 133 | self.parser = PyParser() |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 134 | |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 135 | def evalsource(self,text,line=0): |
| 136 | sc = self.parser.parse(text,line) |
| 137 | src = sc.get_code() |
| 138 | dbg("source: %s" % src) |
| 139 | try: exec(src) in self.compldict |
| 140 | except: dbg("parser: %s, %s" % (sys.exc_info()[0],sys.exc_info()[1])) |
| 141 | for l in sc.locals: |
| 142 | try: exec(l) in self.compldict |
| 143 | except: dbg("locals: %s, %s [%s]" % (sys.exc_info()[0],sys.exc_info()[1],l)) |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 144 | |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 145 | def _cleanstr(self,doc): |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 146 | return doc.replace('"',' ').replace("'",' ') |
Bram Moolenaar | d12f5c1 | 2006-01-25 22:10:52 +0000 | [diff] [blame] | 147 | |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 148 | def get_arguments(self,func_obj): |
| 149 | def _ctor(obj): |
| 150 | try: return class_ob.__init__.im_func |
| 151 | except AttributeError: |
| 152 | for base in class_ob.__bases__: |
| 153 | rc = _find_constructor(base) |
| 154 | if rc is not None: return rc |
| 155 | return None |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 156 | |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 157 | arg_offset = 1 |
| 158 | if type(func_obj) == types.ClassType: func_obj = _ctor(func_obj) |
| 159 | elif type(func_obj) == types.MethodType: func_obj = func_obj.im_func |
| 160 | else: arg_offset = 0 |
| 161 | |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 162 | arg_text='' |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 163 | if type(func_obj) in [types.FunctionType, types.LambdaType]: |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 164 | try: |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 165 | cd = func_obj.func_code |
| 166 | real_args = cd.co_varnames[arg_offset:cd.co_argcount] |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 167 | defaults = func_obj.func_defaults or '' |
| 168 | defaults = map(lambda name: "=%s" % name, defaults) |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 169 | defaults = [""] * (len(real_args)-len(defaults)) + defaults |
| 170 | items = map(lambda a,d: a+d, real_args, defaults) |
| 171 | if func_obj.func_code.co_flags & 0x4: |
| 172 | items.append("...") |
| 173 | if func_obj.func_code.co_flags & 0x8: |
| 174 | items.append("***") |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 175 | arg_text = (','.join(items)) + ')' |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 176 | |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 177 | except: |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 178 | dbg("arg completion: %s: %s" % (sys.exc_info()[0],sys.exc_info()[1])) |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 179 | pass |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 180 | if len(arg_text) == 0: |
| 181 | # The doc string sometimes contains the function signature |
| 182 | # this works for alot of C modules that are part of the |
| 183 | # standard library |
| 184 | doc = func_obj.__doc__ |
| 185 | if doc: |
| 186 | doc = doc.lstrip() |
| 187 | pos = doc.find('\n') |
| 188 | if pos > 0: |
| 189 | sigline = doc[:pos] |
| 190 | lidx = sigline.find('(') |
| 191 | ridx = sigline.find(')') |
| 192 | if lidx > 0 and ridx > 0: |
| 193 | arg_text = sigline[lidx+1:ridx] + ')' |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 194 | if len(arg_text) == 0: arg_text = ')' |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 195 | return arg_text |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 196 | |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 197 | def get_completions(self,context,match): |
| 198 | dbg("get_completions('%s','%s')" % (context,match)) |
| 199 | stmt = '' |
| 200 | if context: stmt += str(context) |
| 201 | if match: stmt += str(match) |
| 202 | try: |
| 203 | result = None |
| 204 | all = {} |
| 205 | ridx = stmt.rfind('.') |
| 206 | if len(stmt) > 0 and stmt[-1] == '(': |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 207 | result = eval(_sanitize(stmt[:-1]), self.compldict) |
| 208 | doc = result.__doc__ |
| 209 | if doc == None: doc = '' |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 210 | args = self.get_arguments(result) |
| 211 | return [{'word':self._cleanstr(args),'info':self._cleanstr(doc)}] |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 212 | elif ridx == -1: |
| 213 | match = stmt |
| 214 | all = self.compldict |
| 215 | else: |
| 216 | match = stmt[ridx+1:] |
| 217 | stmt = _sanitize(stmt[:ridx]) |
| 218 | result = eval(stmt, self.compldict) |
| 219 | all = dir(result) |
| 220 | |
| 221 | dbg("completing: stmt:%s" % stmt) |
| 222 | completions = [] |
| 223 | |
| 224 | try: maindoc = result.__doc__ |
| 225 | except: maindoc = ' ' |
| 226 | if maindoc == None: maindoc = ' ' |
| 227 | for m in all: |
| 228 | if m == "_PyCmplNoType": continue #this is internal |
| 229 | try: |
| 230 | dbg('possible completion: %s' % m) |
| 231 | if m.find(match) == 0: |
| 232 | if result == None: inst = all[m] |
| 233 | else: inst = getattr(result,m) |
| 234 | try: doc = inst.__doc__ |
| 235 | except: doc = maindoc |
| 236 | typestr = str(inst) |
| 237 | if doc == None or doc == '': doc = maindoc |
| 238 | |
| 239 | wrd = m[len(match):] |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 240 | c = {'word':wrd, 'abbr':m, 'info':self._cleanstr(doc)} |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 241 | if "function" in typestr: |
| 242 | c['word'] += '(' |
| 243 | c['abbr'] += '(' + self._cleanstr(self.get_arguments(inst)) |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 244 | elif "method" in typestr: |
| 245 | c['word'] += '(' |
| 246 | c['abbr'] += '(' + self._cleanstr(self.get_arguments(inst)) |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 247 | elif "module" in typestr: |
| 248 | c['word'] += '.' |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 249 | elif "class" in typestr: |
| 250 | c['word'] += '(' |
| 251 | c['abbr'] += '(' |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 252 | completions.append(c) |
| 253 | except: |
| 254 | i = sys.exc_info() |
| 255 | dbg("inner completion: %s,%s [stmt='%s']" % (i[0],i[1],stmt)) |
| 256 | return completions |
| 257 | except: |
| 258 | i = sys.exc_info() |
| 259 | dbg("completion: %s,%s [stmt='%s']" % (i[0],i[1],stmt)) |
| 260 | return [] |
| 261 | |
| 262 | class Scope(object): |
| 263 | def __init__(self,name,indent): |
| 264 | self.subscopes = [] |
| 265 | self.docstr = '' |
| 266 | self.locals = [] |
| 267 | self.parent = None |
| 268 | self.name = name |
| 269 | self.indent = indent |
| 270 | |
| 271 | def add(self,sub): |
| 272 | #print 'push scope: [%s@%s]' % (sub.name,sub.indent) |
| 273 | sub.parent = self |
| 274 | self.subscopes.append(sub) |
| 275 | return sub |
| 276 | |
| 277 | def doc(self,str): |
| 278 | """ Clean up a docstring """ |
| 279 | d = str.replace('\n',' ') |
| 280 | d = d.replace('\t',' ') |
| 281 | while d.find(' ') > -1: d = d.replace(' ',' ') |
| 282 | while d[0] in '"\'\t ': d = d[1:] |
| 283 | while d[-1] in '"\'\t ': d = d[:-1] |
| 284 | self.docstr = d |
| 285 | |
| 286 | def local(self,loc): |
| 287 | if not self._hasvaralready(loc): |
| 288 | self.locals.append(loc) |
| 289 | |
| 290 | def copy_decl(self,indent=0): |
| 291 | """ Copy a scope's declaration only, at the specified indent level - not local variables """ |
| 292 | return Scope(self.name,indent) |
| 293 | |
| 294 | def _hasvaralready(self,test): |
| 295 | "Convienance function... keep out duplicates" |
| 296 | if test.find('=') > -1: |
| 297 | var = test.split('=')[0].strip() |
| 298 | for l in self.locals: |
| 299 | if l.find('=') > -1 and var == l.split('=')[0].strip(): |
| 300 | return True |
| 301 | return False |
| 302 | |
| 303 | def get_code(self): |
| 304 | # we need to start with this, to fix up broken completions |
| 305 | # hopefully this name is unique enough... |
| 306 | str = '"""'+self.docstr+'"""\n' |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 307 | for l in self.locals: |
| 308 | if l.startswith('import'): str += l+'\n' |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 309 | str += 'class _PyCmplNoType:\n def __getattr__(self,name):\n return None\n' |
| 310 | for sub in self.subscopes: |
| 311 | str += sub.get_code() |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 312 | for l in self.locals: |
| 313 | if not l.startswith('import'): str += l+'\n' |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 314 | |
| 315 | return str |
| 316 | |
| 317 | def pop(self,indent): |
| 318 | #print 'pop scope: [%s] to [%s]' % (self.indent,indent) |
| 319 | outer = self |
| 320 | while outer.parent != None and outer.indent >= indent: |
| 321 | outer = outer.parent |
| 322 | return outer |
| 323 | |
| 324 | def currentindent(self): |
| 325 | #print 'parse current indent: %s' % self.indent |
| 326 | return ' '*self.indent |
| 327 | |
| 328 | def childindent(self): |
| 329 | #print 'parse child indent: [%s]' % (self.indent+1) |
| 330 | return ' '*(self.indent+1) |
| 331 | |
| 332 | class Class(Scope): |
| 333 | def __init__(self, name, supers, indent): |
| 334 | Scope.__init__(self,name,indent) |
| 335 | self.supers = supers |
| 336 | def copy_decl(self,indent=0): |
| 337 | c = Class(self.name,self.supers,indent) |
| 338 | for s in self.subscopes: |
| 339 | c.add(s.copy_decl(indent+1)) |
| 340 | return c |
| 341 | def get_code(self): |
| 342 | str = '%sclass %s' % (self.currentindent(),self.name) |
| 343 | if len(self.supers) > 0: str += '(%s)' % ','.join(self.supers) |
| 344 | str += ':\n' |
| 345 | if len(self.docstr) > 0: str += self.childindent()+'"""'+self.docstr+'"""\n' |
| 346 | if len(self.subscopes) > 0: |
| 347 | for s in self.subscopes: str += s.get_code() |
| 348 | else: |
| 349 | str += '%spass\n' % self.childindent() |
| 350 | return str |
| 351 | |
| 352 | |
| 353 | class Function(Scope): |
| 354 | def __init__(self, name, params, indent): |
| 355 | Scope.__init__(self,name,indent) |
| 356 | self.params = params |
| 357 | def copy_decl(self,indent=0): |
| 358 | return Function(self.name,self.params,indent) |
| 359 | def get_code(self): |
| 360 | str = "%sdef %s(%s):\n" % \ |
| 361 | (self.currentindent(),self.name,','.join(self.params)) |
| 362 | if len(self.docstr) > 0: str += self.childindent()+'"""'+self.docstr+'"""\n' |
| 363 | str += "%spass\n" % self.childindent() |
| 364 | return str |
| 365 | |
| 366 | class PyParser: |
| 367 | def __init__(self): |
| 368 | self.top = Scope('global',0) |
| 369 | self.scope = self.top |
| 370 | |
| 371 | def _parsedotname(self,pre=None): |
| 372 | #returns (dottedname, nexttoken) |
| 373 | name = [] |
| 374 | if pre == None: |
| 375 | tokentype, token, indent = self.next() |
| 376 | if tokentype != NAME and token != '*': |
| 377 | return ('', token) |
| 378 | else: token = pre |
| 379 | name.append(token) |
| 380 | while True: |
| 381 | tokentype, token, indent = self.next() |
| 382 | if token != '.': break |
| 383 | tokentype, token, indent = self.next() |
| 384 | if tokentype != NAME: break |
| 385 | name.append(token) |
| 386 | return (".".join(name), token) |
| 387 | |
| 388 | def _parseimportlist(self): |
| 389 | imports = [] |
| 390 | while True: |
| 391 | name, token = self._parsedotname() |
| 392 | if not name: break |
| 393 | name2 = '' |
| 394 | if token == 'as': name2, token = self._parsedotname() |
| 395 | imports.append((name, name2)) |
| 396 | while token != "," and "\n" not in token: |
| 397 | tokentype, token, indent = self.next() |
| 398 | if token != ",": break |
| 399 | return imports |
| 400 | |
| 401 | def _parenparse(self): |
| 402 | name = '' |
| 403 | names = [] |
| 404 | level = 1 |
| 405 | while True: |
| 406 | tokentype, token, indent = self.next() |
| 407 | if token in (')', ',') and level == 1: |
| 408 | names.append(name) |
| 409 | name = '' |
| 410 | if token == '(': |
| 411 | level += 1 |
| 412 | elif token == ')': |
| 413 | level -= 1 |
| 414 | if level == 0: break |
| 415 | elif token == ',' and level == 1: |
| 416 | pass |
| 417 | else: |
| 418 | name += str(token) |
| 419 | return names |
| 420 | |
| 421 | def _parsefunction(self,indent): |
| 422 | self.scope=self.scope.pop(indent) |
| 423 | tokentype, fname, ind = self.next() |
| 424 | if tokentype != NAME: return None |
| 425 | |
| 426 | tokentype, open, ind = self.next() |
| 427 | if open != '(': return None |
| 428 | params=self._parenparse() |
| 429 | |
| 430 | tokentype, colon, ind = self.next() |
| 431 | if colon != ':': return None |
| 432 | |
| 433 | return Function(fname,params,indent) |
| 434 | |
| 435 | def _parseclass(self,indent): |
| 436 | self.scope=self.scope.pop(indent) |
| 437 | tokentype, cname, ind = self.next() |
| 438 | if tokentype != NAME: return None |
| 439 | |
| 440 | super = [] |
| 441 | tokentype, next, ind = self.next() |
| 442 | if next == '(': |
| 443 | super=self._parenparse() |
| 444 | elif next != ':': return None |
| 445 | |
| 446 | return Class(cname,super,indent) |
| 447 | |
| 448 | def _parseassignment(self): |
| 449 | assign='' |
| 450 | tokentype, token, indent = self.next() |
| 451 | if tokentype == tokenize.STRING or token == 'str': |
| 452 | return '""' |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 453 | elif token == '(' or token == 'tuple': |
| 454 | return '()' |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 455 | elif token == '[' or token == 'list': |
| 456 | return '[]' |
| 457 | elif token == '{' or token == 'dict': |
| 458 | return '{}' |
| 459 | elif tokentype == tokenize.NUMBER: |
| 460 | return '0' |
| 461 | elif token == 'open' or token == 'file': |
| 462 | return 'file' |
| 463 | elif token == 'None': |
| 464 | return '_PyCmplNoType()' |
| 465 | elif token == 'type': |
| 466 | return 'type(_PyCmplNoType)' #only for method resolution |
| 467 | else: |
| 468 | assign += token |
| 469 | level = 0 |
| 470 | while True: |
| 471 | tokentype, token, indent = self.next() |
| 472 | if token in ('(','{','['): |
| 473 | level += 1 |
| 474 | elif token in (']','}',')'): |
| 475 | level -= 1 |
| 476 | if level == 0: break |
| 477 | elif level == 0: |
| 478 | if token in (';','\n'): break |
| 479 | assign += token |
| 480 | return "%s" % assign |
| 481 | |
| 482 | def next(self): |
| 483 | type, token, (lineno, indent), end, self.parserline = self.gen.next() |
| 484 | if lineno == self.curline: |
| 485 | #print 'line found [%s] scope=%s' % (line.replace('\n',''),self.scope.name) |
| 486 | self.currentscope = self.scope |
| 487 | return (type, token, indent) |
| 488 | |
| 489 | def _adjustvisibility(self): |
| 490 | newscope = Scope('result',0) |
| 491 | scp = self.currentscope |
| 492 | while scp != None: |
| 493 | if type(scp) == Function: |
| 494 | slice = 0 |
| 495 | #Handle 'self' params |
| 496 | if scp.parent != None and type(scp.parent) == Class: |
| 497 | slice = 1 |
| 498 | p = scp.params[0] |
| 499 | i = p.find('=') |
| 500 | if i != -1: p = p[:i] |
| 501 | newscope.local('%s = %s' % (scp.params[0],scp.parent.name)) |
| 502 | for p in scp.params[slice:]: |
| 503 | i = p.find('=') |
| 504 | if i == -1: |
| 505 | newscope.local('%s = _PyCmplNoType()' % p) |
| 506 | else: |
| 507 | newscope.local('%s = %s' % (p[:i],_sanitize(p[i+1]))) |
| 508 | |
| 509 | for s in scp.subscopes: |
| 510 | ns = s.copy_decl(0) |
| 511 | newscope.add(ns) |
| 512 | for l in scp.locals: newscope.local(l) |
| 513 | scp = scp.parent |
| 514 | |
| 515 | self.currentscope = newscope |
| 516 | return self.currentscope |
| 517 | |
| 518 | #p.parse(vim.current.buffer[:],vim.eval("line('.')")) |
| 519 | def parse(self,text,curline=0): |
| 520 | self.curline = int(curline) |
| 521 | buf = cStringIO.StringIO(''.join(text) + '\n') |
| 522 | self.gen = tokenize.generate_tokens(buf.readline) |
| 523 | self.currentscope = self.scope |
| 524 | |
| 525 | try: |
| 526 | freshscope=True |
| 527 | while True: |
| 528 | tokentype, token, indent = self.next() |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 529 | #dbg( 'main: token=[%s] indent=[%s]' % (token,indent)) |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 530 | |
Bram Moolenaar | 9964e46 | 2007-05-05 17:54:07 +0000 | [diff] [blame] | 531 | if tokentype == DEDENT or token == "pass": |
Bram Moolenaar | fc1421e | 2006-04-20 22:17:20 +0000 | [diff] [blame] | 532 | self.scope = self.scope.pop(indent) |
| 533 | elif token == 'def': |
| 534 | func = self._parsefunction(indent) |
| 535 | if func == None: |
| 536 | print "function: syntax error..." |
| 537 | continue |
| 538 | freshscope = True |
| 539 | self.scope = self.scope.add(func) |
| 540 | elif token == 'class': |
| 541 | cls = self._parseclass(indent) |
| 542 | if cls == None: |
| 543 | print "class: syntax error..." |
| 544 | continue |
| 545 | freshscope = True |
| 546 | self.scope = self.scope.add(cls) |
| 547 | |
| 548 | elif token == 'import': |
| 549 | imports = self._parseimportlist() |
| 550 | for mod, alias in imports: |
| 551 | loc = "import %s" % mod |
| 552 | if len(alias) > 0: loc += " as %s" % alias |
| 553 | self.scope.local(loc) |
| 554 | freshscope = False |
| 555 | elif token == 'from': |
| 556 | mod, token = self._parsedotname() |
| 557 | if not mod or token != "import": |
| 558 | print "from: syntax error..." |
| 559 | continue |
| 560 | names = self._parseimportlist() |
| 561 | for name, alias in names: |
| 562 | loc = "from %s import %s" % (mod,name) |
| 563 | if len(alias) > 0: loc += " as %s" % alias |
| 564 | self.scope.local(loc) |
| 565 | freshscope = False |
| 566 | elif tokentype == STRING: |
| 567 | if freshscope: self.scope.doc(token) |
| 568 | elif tokentype == NAME: |
| 569 | name,token = self._parsedotname(token) |
| 570 | if token == '=': |
| 571 | stmt = self._parseassignment() |
| 572 | if stmt != None: |
| 573 | self.scope.local("%s = %s" % (name,stmt)) |
| 574 | freshscope = False |
| 575 | except StopIteration: #thrown on EOF |
| 576 | pass |
| 577 | except: |
| 578 | dbg("parse error: %s, %s @ %s" % |
| 579 | (sys.exc_info()[0], sys.exc_info()[1], self.parserline)) |
| 580 | return self._adjustvisibility() |
| 581 | |
| 582 | def _sanitize(str): |
| 583 | val = '' |
| 584 | level = 0 |
| 585 | for c in str: |
| 586 | if c in ('(','{','['): |
| 587 | level += 1 |
| 588 | elif c in (']','}',')'): |
| 589 | level -= 1 |
| 590 | elif level == 0: |
| 591 | val += c |
| 592 | return val |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 593 | |
| 594 | sys.path.extend(['.','..']) |
| 595 | PYTHONEOF |
| 596 | endfunction |
| 597 | |
Bram Moolenaar | a40ceaf | 2006-01-13 22:35:40 +0000 | [diff] [blame] | 598 | call s:DefPython() |
| 599 | " vim: set et ts=4: |