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