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