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