118144c84SBram Moolenaar"pythoncomplete.vim - Omni Completion for python
2fc1421ebSBram Moolenaar" Maintainer: Aaron Griffin <[email protected]>
3*b52073acSBram Moolenaar" Version: 0.9
4*b52073acSBram Moolenaar" Last Updated: 18 Jun 2009
518144c84SBram Moolenaar"
69964e468SBram Moolenaar" Changes
7fc1421ebSBram Moolenaar" TODO:
8fc1421ebSBram Moolenaar" 'info' item output can use some formatting work
9fc1421ebSBram Moolenaar" Add an "unsafe eval" mode, to allow for return type evaluation
109964e468SBram Moolenaar" Complete basic syntax along with import statements
119964e468SBram Moolenaar"   i.e. "import url<c-x,c-o>"
129964e468SBram Moolenaar" Continue parsing on invalid line??
139964e468SBram Moolenaar"
14*b52073acSBram Moolenaar" v 0.9
15*b52073acSBram Moolenaar"   * Fixed docstring parsing for classes and functions
16*b52073acSBram Moolenaar"   * Fixed parsing of *args and **kwargs type arguments
17*b52073acSBram Moolenaar"   * Better function param parsing to handle things like tuples and
18*b52073acSBram Moolenaar"     lambda defaults args
19*b52073acSBram Moolenaar"
20*b52073acSBram Moolenaar" v 0.8
21*b52073acSBram Moolenaar"   * Fixed an issue where the FIRST assignment was always used instead of
22*b52073acSBram Moolenaar"   using a subsequent assignment for a variable
23*b52073acSBram Moolenaar"   * Fixed a scoping issue when working inside a parameterless function
24*b52073acSBram Moolenaar"
25*b52073acSBram Moolenaar"
269964e468SBram Moolenaar" v 0.7
279964e468SBram Moolenaar"   * Fixed function list sorting (_ and __ at the bottom)
289964e468SBram Moolenaar"   * Removed newline removal from docs.  It appears vim handles these better in
299964e468SBram Moolenaar"   recent patches
309964e468SBram Moolenaar"
319964e468SBram Moolenaar" v 0.6:
329964e468SBram Moolenaar"   * Fixed argument completion
339964e468SBram Moolenaar"   * Removed the 'kind' completions, as they are better indicated
349964e468SBram Moolenaar"   with real syntax
359964e468SBram Moolenaar"   * Added tuple assignment parsing (whoops, that was forgotten)
369964e468SBram Moolenaar"   * Fixed import handling when flattening scope
379964e468SBram Moolenaar"
389964e468SBram Moolenaar" v 0.5:
399964e468SBram Moolenaar" Yeah, I skipped a version number - 0.4 was never public.
409964e468SBram Moolenaar"  It was a bugfix version on top of 0.3.  This is a complete
419964e468SBram Moolenaar"  rewrite.
429964e468SBram Moolenaar"
4318144c84SBram Moolenaar
4418144c84SBram Moolenaarif !has('python')
4518144c84SBram Moolenaar    echo "Error: Required vim compiled with +python"
4618144c84SBram Moolenaar    finish
4718144c84SBram Moolenaarendif
4818144c84SBram Moolenaar
4918144c84SBram Moolenaarfunction! pythoncomplete#Complete(findstart, base)
5018144c84SBram Moolenaar    "findstart = 1 when we need to get the text length
51fc1421ebSBram Moolenaar    if a:findstart == 1
5218144c84SBram Moolenaar        let line = getline('.')
5318144c84SBram Moolenaar        let idx = col('.')
5418144c84SBram Moolenaar        while idx > 0
5518144c84SBram Moolenaar            let idx -= 1
56fc1421ebSBram Moolenaar            let c = line[idx]
5718144c84SBram Moolenaar            if c =~ '\w'
5818144c84SBram Moolenaar                continue
5918144c84SBram Moolenaar            elseif ! c =~ '\.'
609964e468SBram Moolenaar                let idx = -1
6118144c84SBram Moolenaar                break
6218144c84SBram Moolenaar            else
6318144c84SBram Moolenaar                break
6418144c84SBram Moolenaar            endif
6518144c84SBram Moolenaar        endwhile
6618144c84SBram Moolenaar
6718144c84SBram Moolenaar        return idx
6818144c84SBram Moolenaar    "findstart = 0 when we need to return the list of completions
6918144c84SBram Moolenaar    else
70fc1421ebSBram Moolenaar        "vim no longer moves the cursor upon completion... fix that
71fc1421ebSBram Moolenaar        let line = getline('.')
72fc1421ebSBram Moolenaar        let idx = col('.')
73fc1421ebSBram Moolenaar        let cword = ''
74fc1421ebSBram Moolenaar        while idx > 0
75fc1421ebSBram Moolenaar            let idx -= 1
76fc1421ebSBram Moolenaar            let c = line[idx]
77*b52073acSBram Moolenaar            if c =~ '\w' || c =~ '\.'
78fc1421ebSBram Moolenaar                let cword = c . cword
79fc1421ebSBram Moolenaar                continue
80fc1421ebSBram Moolenaar            elseif strlen(cword) > 0 || idx == 0
81fc1421ebSBram Moolenaar                break
82fc1421ebSBram Moolenaar            endif
83fc1421ebSBram Moolenaar        endwhile
84fc1421ebSBram Moolenaar        execute "python vimcomplete('" . cword . "', '" . a:base . "')"
8518144c84SBram Moolenaar        return g:pythoncomplete_completions
8618144c84SBram Moolenaar    endif
8718144c84SBram Moolenaarendfunction
8818144c84SBram Moolenaar
8918144c84SBram Moolenaarfunction! s:DefPython()
9018144c84SBram Moolenaarpython << PYTHONEOF
91fc1421ebSBram Moolenaarimport sys, tokenize, cStringIO, types
92fc1421ebSBram Moolenaarfrom token import NAME, DEDENT, NEWLINE, STRING
9318144c84SBram Moolenaar
94fc1421ebSBram Moolenaardebugstmts=[]
95fc1421ebSBram Moolenaardef dbg(s): debugstmts.append(s)
96fc1421ebSBram Moolenaardef showdbg():
97fc1421ebSBram Moolenaar    for d in debugstmts: print "DBG: %s " % d
9818144c84SBram Moolenaar
99fc1421ebSBram Moolenaardef vimcomplete(context,match):
100fc1421ebSBram Moolenaar    global debugstmts
101fc1421ebSBram Moolenaar    debugstmts = []
10218144c84SBram Moolenaar    try:
103fc1421ebSBram Moolenaar        import vim
104fc1421ebSBram Moolenaar        def complsort(x,y):
1059964e468SBram Moolenaar            try:
1069964e468SBram Moolenaar                xa = x['abbr']
1079964e468SBram Moolenaar                ya = y['abbr']
1089964e468SBram Moolenaar                if xa[0] == '_':
1099964e468SBram Moolenaar                    if xa[1] == '_' and ya[0:2] == '__':
1109964e468SBram Moolenaar                        return xa > ya
1119964e468SBram Moolenaar                    elif ya[0:2] == '__':
1129964e468SBram Moolenaar                        return -1
1139964e468SBram Moolenaar                    elif y[0] == '_':
1149964e468SBram Moolenaar                        return xa > ya
1159964e468SBram Moolenaar                    else:
1169964e468SBram Moolenaar                        return 1
1179964e468SBram Moolenaar                elif ya[0] == '_':
1189964e468SBram Moolenaar                    return -1
1199964e468SBram Moolenaar                else:
1209964e468SBram Moolenaar                   return xa > ya
1219964e468SBram Moolenaar            except:
1229964e468SBram Moolenaar                return 0
123fc1421ebSBram Moolenaar        cmpl = Completer()
124fc1421ebSBram Moolenaar        cmpl.evalsource('\n'.join(vim.current.buffer),vim.eval("line('.')"))
125fc1421ebSBram Moolenaar        all = cmpl.get_completions(context,match)
126fc1421ebSBram Moolenaar        all.sort(complsort)
127fc1421ebSBram Moolenaar        dictstr = '['
128fc1421ebSBram Moolenaar        # have to do this for double quoting
129fc1421ebSBram Moolenaar        for cmpl in all:
130fc1421ebSBram Moolenaar            dictstr += '{'
131fc1421ebSBram Moolenaar            for x in cmpl: dictstr += '"%s":"%s",' % (x,cmpl[x])
132fc1421ebSBram Moolenaar            dictstr += '"icase":0},'
133fc1421ebSBram Moolenaar        if dictstr[-1] == ',': dictstr = dictstr[:-1]
134fc1421ebSBram Moolenaar        dictstr += ']'
1359964e468SBram Moolenaar        #dbg("dict: %s" % dictstr)
136fc1421ebSBram Moolenaar        vim.command("silent let g:pythoncomplete_completions = %s" % dictstr)
137fc1421ebSBram Moolenaar        #dbg("Completion dict:\n%s" % all)
138fc1421ebSBram Moolenaar    except vim.error:
139fc1421ebSBram Moolenaar        dbg("VIM Error: %s" % vim.error)
14018144c84SBram Moolenaar
141fc1421ebSBram Moolenaarclass Completer(object):
142fc1421ebSBram Moolenaar    def __init__(self):
143fc1421ebSBram Moolenaar       self.compldict = {}
144fc1421ebSBram Moolenaar       self.parser = PyParser()
14518144c84SBram Moolenaar
146fc1421ebSBram Moolenaar    def evalsource(self,text,line=0):
147fc1421ebSBram Moolenaar        sc = self.parser.parse(text,line)
148fc1421ebSBram Moolenaar        src = sc.get_code()
149fc1421ebSBram Moolenaar        dbg("source: %s" % src)
150fc1421ebSBram Moolenaar        try: exec(src) in self.compldict
151fc1421ebSBram Moolenaar        except: dbg("parser: %s, %s" % (sys.exc_info()[0],sys.exc_info()[1]))
152fc1421ebSBram Moolenaar        for l in sc.locals:
153fc1421ebSBram Moolenaar            try: exec(l) in self.compldict
154fc1421ebSBram Moolenaar            except: dbg("locals: %s, %s [%s]" % (sys.exc_info()[0],sys.exc_info()[1],l))
15518144c84SBram Moolenaar
156fc1421ebSBram Moolenaar    def _cleanstr(self,doc):
1579964e468SBram Moolenaar        return doc.replace('"',' ').replace("'",' ')
15818144c84SBram Moolenaar
159fc1421ebSBram Moolenaar    def get_arguments(self,func_obj):
16018144c84SBram Moolenaar        def _ctor(obj):
161fc1421ebSBram Moolenaar            try: return class_ob.__init__.im_func
16218144c84SBram Moolenaar            except AttributeError:
16318144c84SBram Moolenaar                for base in class_ob.__bases__:
16418144c84SBram Moolenaar                    rc = _find_constructor(base)
16518144c84SBram Moolenaar                    if rc is not None: return rc
16618144c84SBram Moolenaar            return None
16718144c84SBram Moolenaar
16818144c84SBram Moolenaar        arg_offset = 1
16918144c84SBram Moolenaar        if type(func_obj) == types.ClassType: func_obj = _ctor(func_obj)
17018144c84SBram Moolenaar        elif type(func_obj) == types.MethodType: func_obj = func_obj.im_func
17118144c84SBram Moolenaar        else: arg_offset = 0
17218144c84SBram Moolenaar
1739964e468SBram Moolenaar        arg_text=''
17418144c84SBram Moolenaar        if type(func_obj) in [types.FunctionType, types.LambdaType]:
17518144c84SBram Moolenaar            try:
17618144c84SBram Moolenaar                cd = func_obj.func_code
17718144c84SBram Moolenaar                real_args = cd.co_varnames[arg_offset:cd.co_argcount]
1789964e468SBram Moolenaar                defaults = func_obj.func_defaults or ''
1799964e468SBram Moolenaar                defaults = map(lambda name: "=%s" % name, defaults)
18018144c84SBram Moolenaar                defaults = [""] * (len(real_args)-len(defaults)) + defaults
18118144c84SBram Moolenaar                items = map(lambda a,d: a+d, real_args, defaults)
18218144c84SBram Moolenaar                if func_obj.func_code.co_flags & 0x4:
18318144c84SBram Moolenaar                    items.append("...")
18418144c84SBram Moolenaar                if func_obj.func_code.co_flags & 0x8:
18518144c84SBram Moolenaar                    items.append("***")
1869964e468SBram Moolenaar                arg_text = (','.join(items)) + ')'
18718144c84SBram Moolenaar
18818144c84SBram Moolenaar            except:
1899964e468SBram Moolenaar                dbg("arg completion: %s: %s" % (sys.exc_info()[0],sys.exc_info()[1]))
19018144c84SBram Moolenaar                pass
19118144c84SBram Moolenaar        if len(arg_text) == 0:
19218144c84SBram Moolenaar            # The doc string sometimes contains the function signature
19318144c84SBram Moolenaar            #  this works for alot of C modules that are part of the
19418144c84SBram Moolenaar            #  standard library
195fc1421ebSBram Moolenaar            doc = func_obj.__doc__
19618144c84SBram Moolenaar            if doc:
19718144c84SBram Moolenaar                doc = doc.lstrip()
19818144c84SBram Moolenaar                pos = doc.find('\n')
19918144c84SBram Moolenaar                if pos > 0:
20018144c84SBram Moolenaar                    sigline = doc[:pos]
20118144c84SBram Moolenaar                    lidx = sigline.find('(')
20218144c84SBram Moolenaar                    ridx = sigline.find(')')
20318144c84SBram Moolenaar                    if lidx > 0 and ridx > 0:
20418144c84SBram Moolenaar                        arg_text = sigline[lidx+1:ridx] + ')'
2059964e468SBram Moolenaar        if len(arg_text) == 0: arg_text = ')'
20618144c84SBram Moolenaar        return arg_text
20718144c84SBram Moolenaar
208fc1421ebSBram Moolenaar    def get_completions(self,context,match):
209fc1421ebSBram Moolenaar        dbg("get_completions('%s','%s')" % (context,match))
21018144c84SBram Moolenaar        stmt = ''
211fc1421ebSBram Moolenaar        if context: stmt += str(context)
212fc1421ebSBram Moolenaar        if match: stmt += str(match)
213fc1421ebSBram Moolenaar        try:
214fc1421ebSBram Moolenaar            result = None
215fc1421ebSBram Moolenaar            all = {}
216fc1421ebSBram Moolenaar            ridx = stmt.rfind('.')
217fc1421ebSBram Moolenaar            if len(stmt) > 0 and stmt[-1] == '(':
218fc1421ebSBram Moolenaar                result = eval(_sanitize(stmt[:-1]), self.compldict)
219fc1421ebSBram Moolenaar                doc = result.__doc__
220*b52073acSBram Moolenaar                if doc is None: doc = ''
2219964e468SBram Moolenaar                args = self.get_arguments(result)
2229964e468SBram Moolenaar                return [{'word':self._cleanstr(args),'info':self._cleanstr(doc)}]
223fc1421ebSBram Moolenaar            elif ridx == -1:
224fc1421ebSBram Moolenaar                match = stmt
225fc1421ebSBram Moolenaar                all = self.compldict
226fc1421ebSBram Moolenaar            else:
227fc1421ebSBram Moolenaar                match = stmt[ridx+1:]
228fc1421ebSBram Moolenaar                stmt = _sanitize(stmt[:ridx])
229fc1421ebSBram Moolenaar                result = eval(stmt, self.compldict)
230fc1421ebSBram Moolenaar                all = dir(result)
231fc1421ebSBram Moolenaar
232fc1421ebSBram Moolenaar            dbg("completing: stmt:%s" % stmt)
233fc1421ebSBram Moolenaar            completions = []
234fc1421ebSBram Moolenaar
235fc1421ebSBram Moolenaar            try: maindoc = result.__doc__
236fc1421ebSBram Moolenaar            except: maindoc = ' '
237*b52073acSBram Moolenaar            if maindoc is None: maindoc = ' '
238fc1421ebSBram Moolenaar            for m in all:
239fc1421ebSBram Moolenaar                if m == "_PyCmplNoType": continue #this is internal
240fc1421ebSBram Moolenaar                try:
241fc1421ebSBram Moolenaar                    dbg('possible completion: %s' % m)
242fc1421ebSBram Moolenaar                    if m.find(match) == 0:
243*b52073acSBram Moolenaar                        if result is None: inst = all[m]
244fc1421ebSBram Moolenaar                        else: inst = getattr(result,m)
245fc1421ebSBram Moolenaar                        try: doc = inst.__doc__
246fc1421ebSBram Moolenaar                        except: doc = maindoc
247fc1421ebSBram Moolenaar                        typestr = str(inst)
248*b52073acSBram Moolenaar                        if doc is None or doc == '': doc = maindoc
249fc1421ebSBram Moolenaar
250fc1421ebSBram Moolenaar                        wrd = m[len(match):]
2519964e468SBram Moolenaar                        c = {'word':wrd, 'abbr':m,  'info':self._cleanstr(doc)}
252fc1421ebSBram Moolenaar                        if "function" in typestr:
253fc1421ebSBram Moolenaar                            c['word'] += '('
254fc1421ebSBram Moolenaar                            c['abbr'] += '(' + self._cleanstr(self.get_arguments(inst))
255fc1421ebSBram Moolenaar                        elif "method" in typestr:
256fc1421ebSBram Moolenaar                            c['word'] += '('
257fc1421ebSBram Moolenaar                            c['abbr'] += '(' + self._cleanstr(self.get_arguments(inst))
258fc1421ebSBram Moolenaar                        elif "module" in typestr:
259fc1421ebSBram Moolenaar                            c['word'] += '.'
260fc1421ebSBram Moolenaar                        elif "class" in typestr:
261fc1421ebSBram Moolenaar                            c['word'] += '('
262fc1421ebSBram Moolenaar                            c['abbr'] += '('
263fc1421ebSBram Moolenaar                        completions.append(c)
264fc1421ebSBram Moolenaar                except:
265fc1421ebSBram Moolenaar                    i = sys.exc_info()
266fc1421ebSBram Moolenaar                    dbg("inner completion: %s,%s [stmt='%s']" % (i[0],i[1],stmt))
267fc1421ebSBram Moolenaar            return completions
268fc1421ebSBram Moolenaar        except:
269fc1421ebSBram Moolenaar            i = sys.exc_info()
270fc1421ebSBram Moolenaar            dbg("completion: %s,%s [stmt='%s']" % (i[0],i[1],stmt))
271fc1421ebSBram Moolenaar            return []
272fc1421ebSBram Moolenaar
273fc1421ebSBram Moolenaarclass Scope(object):
274*b52073acSBram Moolenaar    def __init__(self,name,indent,docstr=''):
275fc1421ebSBram Moolenaar        self.subscopes = []
276*b52073acSBram Moolenaar        self.docstr = docstr
277fc1421ebSBram Moolenaar        self.locals = []
278fc1421ebSBram Moolenaar        self.parent = None
279fc1421ebSBram Moolenaar        self.name = name
280fc1421ebSBram Moolenaar        self.indent = indent
281fc1421ebSBram Moolenaar
282fc1421ebSBram Moolenaar    def add(self,sub):
283fc1421ebSBram Moolenaar        #print 'push scope: [%s@%s]' % (sub.name,sub.indent)
284fc1421ebSBram Moolenaar        sub.parent = self
285fc1421ebSBram Moolenaar        self.subscopes.append(sub)
286fc1421ebSBram Moolenaar        return sub
287fc1421ebSBram Moolenaar
288fc1421ebSBram Moolenaar    def doc(self,str):
289fc1421ebSBram Moolenaar        """ Clean up a docstring """
290fc1421ebSBram Moolenaar        d = str.replace('\n',' ')
291fc1421ebSBram Moolenaar        d = d.replace('\t',' ')
292fc1421ebSBram Moolenaar        while d.find('  ') > -1: d = d.replace('  ',' ')
293fc1421ebSBram Moolenaar        while d[0] in '"\'\t ': d = d[1:]
294fc1421ebSBram Moolenaar        while d[-1] in '"\'\t ': d = d[:-1]
295*b52073acSBram Moolenaar        dbg("Scope(%s)::docstr = %s" % (self,d))
296fc1421ebSBram Moolenaar        self.docstr = d
297fc1421ebSBram Moolenaar
298fc1421ebSBram Moolenaar    def local(self,loc):
299*b52073acSBram Moolenaar        self._checkexisting(loc)
300fc1421ebSBram Moolenaar        self.locals.append(loc)
301fc1421ebSBram Moolenaar
302fc1421ebSBram Moolenaar    def copy_decl(self,indent=0):
303fc1421ebSBram Moolenaar        """ Copy a scope's declaration only, at the specified indent level - not local variables """
304*b52073acSBram Moolenaar        return Scope(self.name,indent,self.docstr)
305fc1421ebSBram Moolenaar
306*b52073acSBram Moolenaar    def _checkexisting(self,test):
307fc1421ebSBram Moolenaar        "Convienance function... keep out duplicates"
308fc1421ebSBram Moolenaar        if test.find('=') > -1:
309fc1421ebSBram Moolenaar            var = test.split('=')[0].strip()
310fc1421ebSBram Moolenaar            for l in self.locals:
311fc1421ebSBram Moolenaar                if l.find('=') > -1 and var == l.split('=')[0].strip():
312*b52073acSBram Moolenaar                    self.locals.remove(l)
313fc1421ebSBram Moolenaar
314fc1421ebSBram Moolenaar    def get_code(self):
315*b52073acSBram Moolenaar        str = ""
316*b52073acSBram Moolenaar        if len(self.docstr) > 0: str += '"""'+self.docstr+'"""\n'
3179964e468SBram Moolenaar        for l in self.locals:
3189964e468SBram Moolenaar            if l.startswith('import'): str += l+'\n'
319fc1421ebSBram Moolenaar        str += 'class _PyCmplNoType:\n    def __getattr__(self,name):\n        return None\n'
320fc1421ebSBram Moolenaar        for sub in self.subscopes:
321fc1421ebSBram Moolenaar            str += sub.get_code()
3229964e468SBram Moolenaar        for l in self.locals:
3239964e468SBram Moolenaar            if not l.startswith('import'): str += l+'\n'
324fc1421ebSBram Moolenaar
325fc1421ebSBram Moolenaar        return str
326fc1421ebSBram Moolenaar
327fc1421ebSBram Moolenaar    def pop(self,indent):
328fc1421ebSBram Moolenaar        #print 'pop scope: [%s] to [%s]' % (self.indent,indent)
329fc1421ebSBram Moolenaar        outer = self
330fc1421ebSBram Moolenaar        while outer.parent != None and outer.indent >= indent:
331fc1421ebSBram Moolenaar            outer = outer.parent
332fc1421ebSBram Moolenaar        return outer
333fc1421ebSBram Moolenaar
334fc1421ebSBram Moolenaar    def currentindent(self):
335fc1421ebSBram Moolenaar        #print 'parse current indent: %s' % self.indent
336fc1421ebSBram Moolenaar        return '    '*self.indent
337fc1421ebSBram Moolenaar
338fc1421ebSBram Moolenaar    def childindent(self):
339fc1421ebSBram Moolenaar        #print 'parse child indent: [%s]' % (self.indent+1)
340fc1421ebSBram Moolenaar        return '    '*(self.indent+1)
341fc1421ebSBram Moolenaar
342fc1421ebSBram Moolenaarclass Class(Scope):
343*b52073acSBram Moolenaar    def __init__(self, name, supers, indent, docstr=''):
344*b52073acSBram Moolenaar        Scope.__init__(self,name,indent, docstr)
345fc1421ebSBram Moolenaar        self.supers = supers
346fc1421ebSBram Moolenaar    def copy_decl(self,indent=0):
347*b52073acSBram Moolenaar        c = Class(self.name,self.supers,indent, self.docstr)
348fc1421ebSBram Moolenaar        for s in self.subscopes:
349fc1421ebSBram Moolenaar            c.add(s.copy_decl(indent+1))
350fc1421ebSBram Moolenaar        return c
351fc1421ebSBram Moolenaar    def get_code(self):
352fc1421ebSBram Moolenaar        str = '%sclass %s' % (self.currentindent(),self.name)
353fc1421ebSBram Moolenaar        if len(self.supers) > 0: str += '(%s)' % ','.join(self.supers)
354fc1421ebSBram Moolenaar        str += ':\n'
355fc1421ebSBram Moolenaar        if len(self.docstr) > 0: str += self.childindent()+'"""'+self.docstr+'"""\n'
356fc1421ebSBram Moolenaar        if len(self.subscopes) > 0:
357fc1421ebSBram Moolenaar            for s in self.subscopes: str += s.get_code()
358fc1421ebSBram Moolenaar        else:
359fc1421ebSBram Moolenaar            str += '%spass\n' % self.childindent()
360fc1421ebSBram Moolenaar        return str
361fc1421ebSBram Moolenaar
362fc1421ebSBram Moolenaar
363fc1421ebSBram Moolenaarclass Function(Scope):
364*b52073acSBram Moolenaar    def __init__(self, name, params, indent, docstr=''):
365*b52073acSBram Moolenaar        Scope.__init__(self,name,indent, docstr)
366fc1421ebSBram Moolenaar        self.params = params
367fc1421ebSBram Moolenaar    def copy_decl(self,indent=0):
368*b52073acSBram Moolenaar        return Function(self.name,self.params,indent, self.docstr)
369fc1421ebSBram Moolenaar    def get_code(self):
370fc1421ebSBram Moolenaar        str = "%sdef %s(%s):\n" % \
371fc1421ebSBram Moolenaar            (self.currentindent(),self.name,','.join(self.params))
372fc1421ebSBram Moolenaar        if len(self.docstr) > 0: str += self.childindent()+'"""'+self.docstr+'"""\n'
373fc1421ebSBram Moolenaar        str += "%spass\n" % self.childindent()
374fc1421ebSBram Moolenaar        return str
375fc1421ebSBram Moolenaar
376fc1421ebSBram Moolenaarclass PyParser:
377fc1421ebSBram Moolenaar    def __init__(self):
378fc1421ebSBram Moolenaar        self.top = Scope('global',0)
379fc1421ebSBram Moolenaar        self.scope = self.top
380fc1421ebSBram Moolenaar
381fc1421ebSBram Moolenaar    def _parsedotname(self,pre=None):
382fc1421ebSBram Moolenaar        #returns (dottedname, nexttoken)
383fc1421ebSBram Moolenaar        name = []
384*b52073acSBram Moolenaar        if pre is None:
385fc1421ebSBram Moolenaar            tokentype, token, indent = self.next()
386fc1421ebSBram Moolenaar            if tokentype != NAME and token != '*':
387fc1421ebSBram Moolenaar                return ('', token)
388fc1421ebSBram Moolenaar        else: token = pre
389fc1421ebSBram Moolenaar        name.append(token)
390fc1421ebSBram Moolenaar        while True:
391fc1421ebSBram Moolenaar            tokentype, token, indent = self.next()
392fc1421ebSBram Moolenaar            if token != '.': break
393fc1421ebSBram Moolenaar            tokentype, token, indent = self.next()
394fc1421ebSBram Moolenaar            if tokentype != NAME: break
395fc1421ebSBram Moolenaar            name.append(token)
396fc1421ebSBram Moolenaar        return (".".join(name), token)
397fc1421ebSBram Moolenaar
398fc1421ebSBram Moolenaar    def _parseimportlist(self):
399fc1421ebSBram Moolenaar        imports = []
400fc1421ebSBram Moolenaar        while True:
401fc1421ebSBram Moolenaar            name, token = self._parsedotname()
402fc1421ebSBram Moolenaar            if not name: break
403fc1421ebSBram Moolenaar            name2 = ''
404fc1421ebSBram Moolenaar            if token == 'as': name2, token = self._parsedotname()
405fc1421ebSBram Moolenaar            imports.append((name, name2))
406fc1421ebSBram Moolenaar            while token != "," and "\n" not in token:
407fc1421ebSBram Moolenaar                tokentype, token, indent = self.next()
408fc1421ebSBram Moolenaar            if token != ",": break
409fc1421ebSBram Moolenaar        return imports
410fc1421ebSBram Moolenaar
411fc1421ebSBram Moolenaar    def _parenparse(self):
412fc1421ebSBram Moolenaar        name = ''
413fc1421ebSBram Moolenaar        names = []
414fc1421ebSBram Moolenaar        level = 1
415fc1421ebSBram Moolenaar        while True:
416fc1421ebSBram Moolenaar            tokentype, token, indent = self.next()
417fc1421ebSBram Moolenaar            if token in (')', ',') and level == 1:
418*b52073acSBram Moolenaar                if '=' not in name: name = name.replace(' ', '')
419*b52073acSBram Moolenaar                names.append(name.strip())
420fc1421ebSBram Moolenaar                name = ''
421fc1421ebSBram Moolenaar            if token == '(':
42218144c84SBram Moolenaar                level += 1
423*b52073acSBram Moolenaar                name += "("
424fc1421ebSBram Moolenaar            elif token == ')':
425fc1421ebSBram Moolenaar                level -= 1
426fc1421ebSBram Moolenaar                if level == 0: break
427*b52073acSBram Moolenaar                else: name += ")"
428fc1421ebSBram Moolenaar            elif token == ',' and level == 1:
429fc1421ebSBram Moolenaar                pass
430fc1421ebSBram Moolenaar            else:
431*b52073acSBram Moolenaar                name += "%s " % str(token)
432fc1421ebSBram Moolenaar        return names
433fc1421ebSBram Moolenaar
434fc1421ebSBram Moolenaar    def _parsefunction(self,indent):
435fc1421ebSBram Moolenaar        self.scope=self.scope.pop(indent)
436fc1421ebSBram Moolenaar        tokentype, fname, ind = self.next()
437fc1421ebSBram Moolenaar        if tokentype != NAME: return None
438fc1421ebSBram Moolenaar
439fc1421ebSBram Moolenaar        tokentype, open, ind = self.next()
440fc1421ebSBram Moolenaar        if open != '(': return None
441fc1421ebSBram Moolenaar        params=self._parenparse()
442fc1421ebSBram Moolenaar
443fc1421ebSBram Moolenaar        tokentype, colon, ind = self.next()
444fc1421ebSBram Moolenaar        if colon != ':': return None
445fc1421ebSBram Moolenaar
446fc1421ebSBram Moolenaar        return Function(fname,params,indent)
447fc1421ebSBram Moolenaar
448fc1421ebSBram Moolenaar    def _parseclass(self,indent):
449fc1421ebSBram Moolenaar        self.scope=self.scope.pop(indent)
450fc1421ebSBram Moolenaar        tokentype, cname, ind = self.next()
451fc1421ebSBram Moolenaar        if tokentype != NAME: return None
452fc1421ebSBram Moolenaar
453fc1421ebSBram Moolenaar        super = []
454fc1421ebSBram Moolenaar        tokentype, next, ind = self.next()
455fc1421ebSBram Moolenaar        if next == '(':
456fc1421ebSBram Moolenaar            super=self._parenparse()
457fc1421ebSBram Moolenaar        elif next != ':': return None
458fc1421ebSBram Moolenaar
459fc1421ebSBram Moolenaar        return Class(cname,super,indent)
460fc1421ebSBram Moolenaar
461fc1421ebSBram Moolenaar    def _parseassignment(self):
462fc1421ebSBram Moolenaar        assign=''
463fc1421ebSBram Moolenaar        tokentype, token, indent = self.next()
464fc1421ebSBram Moolenaar        if tokentype == tokenize.STRING or token == 'str':
465fc1421ebSBram Moolenaar            return '""'
4669964e468SBram Moolenaar        elif token == '(' or token == 'tuple':
4679964e468SBram Moolenaar            return '()'
468fc1421ebSBram Moolenaar        elif token == '[' or token == 'list':
469fc1421ebSBram Moolenaar            return '[]'
470fc1421ebSBram Moolenaar        elif token == '{' or token == 'dict':
471fc1421ebSBram Moolenaar            return '{}'
472fc1421ebSBram Moolenaar        elif tokentype == tokenize.NUMBER:
473fc1421ebSBram Moolenaar            return '0'
474fc1421ebSBram Moolenaar        elif token == 'open' or token == 'file':
475fc1421ebSBram Moolenaar            return 'file'
476fc1421ebSBram Moolenaar        elif token == 'None':
477fc1421ebSBram Moolenaar            return '_PyCmplNoType()'
478fc1421ebSBram Moolenaar        elif token == 'type':
479fc1421ebSBram Moolenaar            return 'type(_PyCmplNoType)' #only for method resolution
480fc1421ebSBram Moolenaar        else:
481fc1421ebSBram Moolenaar            assign += token
482fc1421ebSBram Moolenaar            level = 0
483fc1421ebSBram Moolenaar            while True:
484fc1421ebSBram Moolenaar                tokentype, token, indent = self.next()
485fc1421ebSBram Moolenaar                if token in ('(','{','['):
486fc1421ebSBram Moolenaar                    level += 1
487fc1421ebSBram Moolenaar                elif token in (']','}',')'):
488fc1421ebSBram Moolenaar                    level -= 1
489fc1421ebSBram Moolenaar                    if level == 0: break
490fc1421ebSBram Moolenaar                elif level == 0:
491fc1421ebSBram Moolenaar                    if token in (';','\n'): break
492fc1421ebSBram Moolenaar                    assign += token
493fc1421ebSBram Moolenaar        return "%s" % assign
494fc1421ebSBram Moolenaar
495fc1421ebSBram Moolenaar    def next(self):
496fc1421ebSBram Moolenaar        type, token, (lineno, indent), end, self.parserline = self.gen.next()
497fc1421ebSBram Moolenaar        if lineno == self.curline:
498fc1421ebSBram Moolenaar            #print 'line found [%s] scope=%s' % (line.replace('\n',''),self.scope.name)
499fc1421ebSBram Moolenaar            self.currentscope = self.scope
500fc1421ebSBram Moolenaar        return (type, token, indent)
501fc1421ebSBram Moolenaar
502fc1421ebSBram Moolenaar    def _adjustvisibility(self):
503fc1421ebSBram Moolenaar        newscope = Scope('result',0)
504fc1421ebSBram Moolenaar        scp = self.currentscope
505fc1421ebSBram Moolenaar        while scp != None:
506fc1421ebSBram Moolenaar            if type(scp) == Function:
507fc1421ebSBram Moolenaar                slice = 0
508fc1421ebSBram Moolenaar                #Handle 'self' params
509fc1421ebSBram Moolenaar                if scp.parent != None and type(scp.parent) == Class:
510fc1421ebSBram Moolenaar                    slice = 1
511fc1421ebSBram Moolenaar                    newscope.local('%s = %s' % (scp.params[0],scp.parent.name))
512fc1421ebSBram Moolenaar                for p in scp.params[slice:]:
513fc1421ebSBram Moolenaar                    i = p.find('=')
514*b52073acSBram Moolenaar                    if len(p) == 0: continue
515*b52073acSBram Moolenaar                    pvar = ''
516*b52073acSBram Moolenaar                    ptype = ''
517fc1421ebSBram Moolenaar                    if i == -1:
518*b52073acSBram Moolenaar                        pvar = p
519*b52073acSBram Moolenaar                        ptype = '_PyCmplNoType()'
520fc1421ebSBram Moolenaar                    else:
521*b52073acSBram Moolenaar                        pvar = p[:i]
522*b52073acSBram Moolenaar                        ptype = _sanitize(p[i+1:])
523*b52073acSBram Moolenaar                    if pvar.startswith('**'):
524*b52073acSBram Moolenaar                        pvar = pvar[2:]
525*b52073acSBram Moolenaar                        ptype = '{}'
526*b52073acSBram Moolenaar                    elif pvar.startswith('*'):
527*b52073acSBram Moolenaar                        pvar = pvar[1:]
528*b52073acSBram Moolenaar                        ptype = '[]'
529*b52073acSBram Moolenaar
530*b52073acSBram Moolenaar                    newscope.local('%s = %s' % (pvar,ptype))
531fc1421ebSBram Moolenaar
532fc1421ebSBram Moolenaar            for s in scp.subscopes:
533fc1421ebSBram Moolenaar                ns = s.copy_decl(0)
534fc1421ebSBram Moolenaar                newscope.add(ns)
535fc1421ebSBram Moolenaar            for l in scp.locals: newscope.local(l)
536fc1421ebSBram Moolenaar            scp = scp.parent
537fc1421ebSBram Moolenaar
538fc1421ebSBram Moolenaar        self.currentscope = newscope
539fc1421ebSBram Moolenaar        return self.currentscope
540fc1421ebSBram Moolenaar
541fc1421ebSBram Moolenaar    #p.parse(vim.current.buffer[:],vim.eval("line('.')"))
542fc1421ebSBram Moolenaar    def parse(self,text,curline=0):
543fc1421ebSBram Moolenaar        self.curline = int(curline)
544fc1421ebSBram Moolenaar        buf = cStringIO.StringIO(''.join(text) + '\n')
545fc1421ebSBram Moolenaar        self.gen = tokenize.generate_tokens(buf.readline)
546fc1421ebSBram Moolenaar        self.currentscope = self.scope
547fc1421ebSBram Moolenaar
548fc1421ebSBram Moolenaar        try:
549fc1421ebSBram Moolenaar            freshscope=True
550fc1421ebSBram Moolenaar            while True:
551fc1421ebSBram Moolenaar                tokentype, token, indent = self.next()
5529964e468SBram Moolenaar                #dbg( 'main: token=[%s] indent=[%s]' % (token,indent))
553fc1421ebSBram Moolenaar
5549964e468SBram Moolenaar                if tokentype == DEDENT or token == "pass":
555fc1421ebSBram Moolenaar                    self.scope = self.scope.pop(indent)
556fc1421ebSBram Moolenaar                elif token == 'def':
557fc1421ebSBram Moolenaar                    func = self._parsefunction(indent)
558*b52073acSBram Moolenaar                    if func is None:
559fc1421ebSBram Moolenaar                        print "function: syntax error..."
560fc1421ebSBram Moolenaar                        continue
561*b52073acSBram Moolenaar                    dbg("new scope: function")
562fc1421ebSBram Moolenaar                    freshscope = True
563fc1421ebSBram Moolenaar                    self.scope = self.scope.add(func)
564fc1421ebSBram Moolenaar                elif token == 'class':
565fc1421ebSBram Moolenaar                    cls = self._parseclass(indent)
566*b52073acSBram Moolenaar                    if cls is None:
567fc1421ebSBram Moolenaar                        print "class: syntax error..."
568fc1421ebSBram Moolenaar                        continue
569fc1421ebSBram Moolenaar                    freshscope = True
570*b52073acSBram Moolenaar                    dbg("new scope: class")
571fc1421ebSBram Moolenaar                    self.scope = self.scope.add(cls)
572fc1421ebSBram Moolenaar
573fc1421ebSBram Moolenaar                elif token == 'import':
574fc1421ebSBram Moolenaar                    imports = self._parseimportlist()
575fc1421ebSBram Moolenaar                    for mod, alias in imports:
576fc1421ebSBram Moolenaar                        loc = "import %s" % mod
577fc1421ebSBram Moolenaar                        if len(alias) > 0: loc += " as %s" % alias
578fc1421ebSBram Moolenaar                        self.scope.local(loc)
579fc1421ebSBram Moolenaar                    freshscope = False
580fc1421ebSBram Moolenaar                elif token == 'from':
581fc1421ebSBram Moolenaar                    mod, token = self._parsedotname()
582fc1421ebSBram Moolenaar                    if not mod or token != "import":
583fc1421ebSBram Moolenaar                        print "from: syntax error..."
584fc1421ebSBram Moolenaar                        continue
585fc1421ebSBram Moolenaar                    names = self._parseimportlist()
586fc1421ebSBram Moolenaar                    for name, alias in names:
587fc1421ebSBram Moolenaar                        loc = "from %s import %s" % (mod,name)
588fc1421ebSBram Moolenaar                        if len(alias) > 0: loc += " as %s" % alias
589fc1421ebSBram Moolenaar                        self.scope.local(loc)
590fc1421ebSBram Moolenaar                    freshscope = False
591fc1421ebSBram Moolenaar                elif tokentype == STRING:
592fc1421ebSBram Moolenaar                    if freshscope: self.scope.doc(token)
593fc1421ebSBram Moolenaar                elif tokentype == NAME:
594fc1421ebSBram Moolenaar                    name,token = self._parsedotname(token)
595fc1421ebSBram Moolenaar                    if token == '=':
596fc1421ebSBram Moolenaar                        stmt = self._parseassignment()
597*b52073acSBram Moolenaar                        dbg("parseassignment: %s = %s" % (name, stmt))
598fc1421ebSBram Moolenaar                        if stmt != None:
599fc1421ebSBram Moolenaar                            self.scope.local("%s = %s" % (name,stmt))
600fc1421ebSBram Moolenaar                    freshscope = False
601fc1421ebSBram Moolenaar        except StopIteration: #thrown on EOF
602fc1421ebSBram Moolenaar            pass
603fc1421ebSBram Moolenaar        except:
604fc1421ebSBram Moolenaar            dbg("parse error: %s, %s @ %s" %
605fc1421ebSBram Moolenaar                (sys.exc_info()[0], sys.exc_info()[1], self.parserline))
606fc1421ebSBram Moolenaar        return self._adjustvisibility()
607fc1421ebSBram Moolenaar
608fc1421ebSBram Moolenaardef _sanitize(str):
609fc1421ebSBram Moolenaar    val = ''
610fc1421ebSBram Moolenaar    level = 0
611fc1421ebSBram Moolenaar    for c in str:
612fc1421ebSBram Moolenaar        if c in ('(','{','['):
613fc1421ebSBram Moolenaar            level += 1
614fc1421ebSBram Moolenaar        elif c in (']','}',')'):
61518144c84SBram Moolenaar            level -= 1
61618144c84SBram Moolenaar        elif level == 0:
617fc1421ebSBram Moolenaar            val += c
618fc1421ebSBram Moolenaar    return val
61918144c84SBram Moolenaar
62018144c84SBram Moolenaarsys.path.extend(['.','..'])
62118144c84SBram MoolenaarPYTHONEOF
62218144c84SBram Moolenaarendfunction
62318144c84SBram Moolenaar
62418144c84SBram Moolenaarcall s:DefPython()
62518144c84SBram Moolenaar" vim: set et ts=4:
626