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