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