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 / autoload / pythoncomplete.vim < prev    next >
Encoding:
Text File  |  2010-08-15  |  21.5 KB  |  626 lines

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