1*18144c84SBram Moolenaar"pythoncomplete.vim - Omni Completion for python
2*18144c84SBram Moolenaar" Maintainer: Aaron Griffin
3*18144c84SBram Moolenaar" Version: 0.3
4*18144c84SBram Moolenaar" Last Updated: 23 January 2006
5*18144c84SBram Moolenaar"
6*18144c84SBram Moolenaar"   v0.3 Changes:
7*18144c84SBram Moolenaar"       added top level def parsing
8*18144c84SBram Moolenaar"       for safety, call returns are not evaluated
9*18144c84SBram Moolenaar"       handful of parsing changes
10*18144c84SBram Moolenaar"       trailing ( and . characters
11*18144c84SBram Moolenaar"       argument completion on open parens
12*18144c84SBram Moolenaar"       stop parsing at current line - ++performance, local var resolution
13*18144c84SBram Moolenaar"
14*18144c84SBram Moolenaar"   TODO
15*18144c84SBram Moolenaar"       RExec subclass
16*18144c84SBram Moolenaar"       Code cleanup + make class
17*18144c84SBram Moolenaar"       use internal dict, not globals()
18*18144c84SBram Moolenaar
19*18144c84SBram Moolenaarif !has('python')
20*18144c84SBram Moolenaar    echo "Error: Required vim compiled with +python"
21*18144c84SBram Moolenaar    finish
22*18144c84SBram Moolenaarendif
23*18144c84SBram Moolenaar
24*18144c84SBram Moolenaarfunction! pythoncomplete#Complete(findstart, base)
25*18144c84SBram Moolenaar    "findstart = 1 when we need to get the text length
26*18144c84SBram Moolenaar    if a:findstart
27*18144c84SBram Moolenaar        let line = getline('.')
28*18144c84SBram Moolenaar        let idx = col('.')
29*18144c84SBram Moolenaar        while idx > 0
30*18144c84SBram Moolenaar            let idx -= 1
31*18144c84SBram Moolenaar            let c = line[idx-1]
32*18144c84SBram Moolenaar            if c =~ '\w'
33*18144c84SBram Moolenaar                continue
34*18144c84SBram Moolenaar            elseif ! c =~ '\.'
35*18144c84SBram Moolenaar                idx = -1
36*18144c84SBram Moolenaar                break
37*18144c84SBram Moolenaar            else
38*18144c84SBram Moolenaar                break
39*18144c84SBram Moolenaar            endif
40*18144c84SBram Moolenaar        endwhile
41*18144c84SBram Moolenaar
42*18144c84SBram Moolenaar        return idx
43*18144c84SBram Moolenaar    "findstart = 0 when we need to return the list of completions
44*18144c84SBram Moolenaar    else
45*18144c84SBram Moolenaar        execute "python get_completions('" . a:base . "')"
46*18144c84SBram Moolenaar        return g:pythoncomplete_completions
47*18144c84SBram Moolenaar    endif
48*18144c84SBram Moolenaarendfunction
49*18144c84SBram Moolenaar
50*18144c84SBram Moolenaarfunction! s:DefPython()
51*18144c84SBram Moolenaarpython << PYTHONEOF
52*18144c84SBram Moolenaarimport vim, sys, types
53*18144c84SBram Moolenaarimport __builtin__
54*18144c84SBram Moolenaarimport tokenize, keyword, cStringIO
55*18144c84SBram Moolenaar
56*18144c84SBram MoolenaarLOCALDEFS = \
57*18144c84SBram Moolenaar	['LOCALDEFS', 'clean_up','eval_source_code', \
58*18144c84SBram Moolenaar	 'get_completions', '__builtin__', '__builtins__', \
59*18144c84SBram Moolenaar	 'dbg', '__name__', 'vim', 'sys', 'parse_to_end', \
60*18144c84SBram Moolenaar     'parse_statement', 'tokenize', 'keyword', 'cStringIO', \
61*18144c84SBram Moolenaar     'debug_level', 'safe_eval', '_ctor', 'get_arguments', \
62*18144c84SBram Moolenaar     'strip_calls', 'types', 'parse_block']
63*18144c84SBram Moolenaar
64*18144c84SBram Moolenaardef dbg(level,msg):
65*18144c84SBram Moolenaar    debug_level = 1
66*18144c84SBram Moolenaar    try:
67*18144c84SBram Moolenaar        debug_level = vim.eval("g:pythoncomplete_debug_level")
68*18144c84SBram Moolenaar    except:
69*18144c84SBram Moolenaar        pass
70*18144c84SBram Moolenaar    if level <= debug_level: print(msg)
71*18144c84SBram Moolenaar
72*18144c84SBram Moolenaardef strip_calls(stmt):
73*18144c84SBram Moolenaar    parsed=''
74*18144c84SBram Moolenaar    level = 0
75*18144c84SBram Moolenaar    for c in stmt:
76*18144c84SBram Moolenaar        if c in ['[','(']:
77*18144c84SBram Moolenaar            level += 1
78*18144c84SBram Moolenaar        elif c in [')',']']:
79*18144c84SBram Moolenaar            level -= 1
80*18144c84SBram Moolenaar        elif level == 0:
81*18144c84SBram Moolenaar            parsed += c
82*18144c84SBram Moolenaar    ##dbg(10,"stripped: %s" % parsed)
83*18144c84SBram Moolenaar    return parsed
84*18144c84SBram Moolenaar
85*18144c84SBram Moolenaardef get_completions(base):
86*18144c84SBram Moolenaar    stmt = vim.eval('expand("<cWORD>")')
87*18144c84SBram Moolenaar    #dbg(1,"statement: %s - %s" % (stmt, base))
88*18144c84SBram Moolenaar    stmt = stmt+base
89*18144c84SBram Moolenaar    eval_source_code()
90*18144c84SBram Moolenaar
91*18144c84SBram Moolenaar    try:
92*18144c84SBram Moolenaar        ridx = stmt.rfind('.')
93*18144c84SBram Moolenaar        if stmt[-1] == '(':
94*18144c84SBram Moolenaar            match = ""
95*18144c84SBram Moolenaar            stmt = strip_calls(stmt[:len(stmt)-1])
96*18144c84SBram Moolenaar            all = get_arguments(eval(stmt))
97*18144c84SBram Moolenaar        elif ridx == -1:
98*18144c84SBram Moolenaar            match = stmt
99*18144c84SBram Moolenaar            all = globals() + __builtin__.__dict__
100*18144c84SBram Moolenaar        else:
101*18144c84SBram Moolenaar            match = stmt[ridx+1:]
102*18144c84SBram Moolenaar            stmt = strip_calls(stmt[:ridx])
103*18144c84SBram Moolenaar            all = eval(stmt).__dict__
104*18144c84SBram Moolenaar
105*18144c84SBram Moolenaar        #dbg(15,"completions for: %s, match=%s" % (stmt,match))
106*18144c84SBram Moolenaar        completions = []
107*18144c84SBram Moolenaar        if type(all) == types.DictType:
108*18144c84SBram Moolenaar            for m in all:
109*18144c84SBram Moolenaar                if m.find('_') != 0 and m.find(match) == 0 and \
110*18144c84SBram Moolenaar			       m not in LOCALDEFS:
111*18144c84SBram Moolenaar                    #dbg(25,"matched... %s, %s" % (m, m.find(match)))
112*18144c84SBram Moolenaar                    typestr = str(all[m])
113*18144c84SBram Moolenaar                    if "function" in typestr: m += '('
114*18144c84SBram Moolenaar                    elif "method" in typestr: m += '('
115*18144c84SBram Moolenaar                    elif "module" in typestr: m += '.'
116*18144c84SBram Moolenaar                    elif "class" in typestr: m += '('
117*18144c84SBram Moolenaar                    completions.append(m)
118*18144c84SBram Moolenaar            completions.sort()
119*18144c84SBram Moolenaar        else:
120*18144c84SBram Moolenaar            completions.append(all)
121*18144c84SBram Moolenaar        #dbg(10,"all completions: %s" % completions)
122*18144c84SBram Moolenaar        vim.command("let g:pythoncomplete_completions = %s" % completions)
123*18144c84SBram Moolenaar    except:
124*18144c84SBram Moolenaar        vim.command("let g:pythoncomplete_completions = []")
125*18144c84SBram Moolenaar        #dbg(1,"exception: %s" % sys.exc_info()[1])
126*18144c84SBram Moolenaar    clean_up()
127*18144c84SBram Moolenaar
128*18144c84SBram Moolenaardef get_arguments(func_obj):
129*18144c84SBram Moolenaar    def _ctor(obj):
130*18144c84SBram Moolenaar        try:
131*18144c84SBram Moolenaar            return class_ob.__init__.im_func
132*18144c84SBram Moolenaar        except AttributeError:
133*18144c84SBram Moolenaar            for base in class_ob.__bases__:
134*18144c84SBram Moolenaar                rc = _find_constructor(base)
135*18144c84SBram Moolenaar                if rc is not None: return rc
136*18144c84SBram Moolenaar        return None
137*18144c84SBram Moolenaar
138*18144c84SBram Moolenaar    arg_offset = 1
139*18144c84SBram Moolenaar    if type(func_obj) == types.ClassType: func_obj = _ctor(func_obj)
140*18144c84SBram Moolenaar    elif type(func_obj) == types.MethodType: func_obj = func_obj.im_func
141*18144c84SBram Moolenaar    else: arg_offset = 0
142*18144c84SBram Moolenaar
143*18144c84SBram Moolenaar    #dbg(20,"%s, offset=%s" % (str(func_obj), arg_offset))
144*18144c84SBram Moolenaar
145*18144c84SBram Moolenaar    arg_text = ''
146*18144c84SBram Moolenaar    if type(func_obj) in [types.FunctionType, types.LambdaType]:
147*18144c84SBram Moolenaar        try:
148*18144c84SBram Moolenaar            cd = func_obj.func_code
149*18144c84SBram Moolenaar            real_args = cd.co_varnames[arg_offset:cd.co_argcount]
150*18144c84SBram Moolenaar            defaults = func_obj.func_defaults or []
151*18144c84SBram Moolenaar            defaults = list(map(lambda name: "=%s" % name, defaults))
152*18144c84SBram Moolenaar            defaults = [""] * (len(real_args)-len(defaults)) + defaults
153*18144c84SBram Moolenaar            items = map(lambda a,d: a+d, real_args, defaults)
154*18144c84SBram Moolenaar            if func_obj.func_code.co_flags & 0x4:
155*18144c84SBram Moolenaar                items.append("...")
156*18144c84SBram Moolenaar            if func_obj.func_code.co_flags & 0x8:
157*18144c84SBram Moolenaar                items.append("***")
158*18144c84SBram Moolenaar            arg_text = ", ".join(items) + ')'
159*18144c84SBram Moolenaar
160*18144c84SBram Moolenaar        except:
161*18144c84SBram Moolenaar            #dbg(1,"exception: %s" % sys.exc_info()[1])
162*18144c84SBram Moolenaar            pass
163*18144c84SBram Moolenaar    if len(arg_text) == 0:
164*18144c84SBram Moolenaar        # The doc string sometimes contains the function signature
165*18144c84SBram Moolenaar        #  this works for alot of C modules that are part of the
166*18144c84SBram Moolenaar        #  standard library
167*18144c84SBram Moolenaar        doc = getattr(func_obj, '__doc__', '')
168*18144c84SBram Moolenaar        if doc:
169*18144c84SBram Moolenaar            doc = doc.lstrip()
170*18144c84SBram Moolenaar            pos = doc.find('\n')
171*18144c84SBram Moolenaar            if pos > 0:
172*18144c84SBram Moolenaar                sigline = doc[:pos]
173*18144c84SBram Moolenaar                lidx = sigline.find('(')
174*18144c84SBram Moolenaar                ridx = sigline.find(')')
175*18144c84SBram Moolenaar                retidx = sigline.find('->')
176*18144c84SBram Moolenaar                ret = sigline[retidx+2:].strip()
177*18144c84SBram Moolenaar                if lidx > 0 and ridx > 0:
178*18144c84SBram Moolenaar                    arg_text = sigline[lidx+1:ridx] + ')'
179*18144c84SBram Moolenaar                    if len(ret) > 0: arg_text += ' #returns %s' % ret
180*18144c84SBram Moolenaar    #dbg(15,"argument completion: %s" % arg_text)
181*18144c84SBram Moolenaar    return arg_text
182*18144c84SBram Moolenaar
183*18144c84SBram Moolenaardef parse_to_end(gen):
184*18144c84SBram Moolenaar    stmt=''
185*18144c84SBram Moolenaar    level = 0
186*18144c84SBram Moolenaar    for type, str, begin, end, line in gen:
187*18144c84SBram Moolenaar        if line == vim.eval('getline(\'.\')'): break
188*18144c84SBram Moolenaar        elif str == '\\': continue
189*18144c84SBram Moolenaar        elif str == ';':
190*18144c84SBram Moolenaar            break
191*18144c84SBram Moolenaar        elif type == tokenize.NEWLINE and level == 0:
192*18144c84SBram Moolenaar            break
193*18144c84SBram Moolenaar        elif str in ['[','(']:
194*18144c84SBram Moolenaar            level += 1
195*18144c84SBram Moolenaar        elif str in [')',']']:
196*18144c84SBram Moolenaar            level -= 1
197*18144c84SBram Moolenaar        elif level == 0:
198*18144c84SBram Moolenaar            stmt += str
199*18144c84SBram Moolenaar        #dbg(10,"current statement: %s" % stmt)
200*18144c84SBram Moolenaar    return stmt
201*18144c84SBram Moolenaar
202*18144c84SBram Moolenaardef parse_block(gen):
203*18144c84SBram Moolenaar    lines = []
204*18144c84SBram Moolenaar    level = 0
205*18144c84SBram Moolenaar    for type, str, begin, end, line in gen:
206*18144c84SBram Moolenaar        if line.replace('\n','') == vim.eval('getline(\'.\')'): break
207*18144c84SBram Moolenaar        elif type == tokenize.INDENT:
208*18144c84SBram Moolenaar            level += 1
209*18144c84SBram Moolenaar        elif type == tokenize.DEDENT:
210*18144c84SBram Moolenaar            level -= 1
211*18144c84SBram Moolenaar            if level == 0: break;
212*18144c84SBram Moolenaar        else:
213*18144c84SBram Moolenaar            stmt = parse_statement(gen,str)
214*18144c84SBram Moolenaar            if len(stmt) > 0: lines.append(stmt)
215*18144c84SBram Moolenaar    return lines
216*18144c84SBram Moolenaar
217*18144c84SBram Moolenaardef parse_statement(gen,curstr=''):
218*18144c84SBram Moolenaar    var = curstr
219*18144c84SBram Moolenaar    type, str, begin, end, line = gen.next()
220*18144c84SBram Moolenaar    if str == '=':
221*18144c84SBram Moolenaar        type, str, begin, end, line = gen.next()
222*18144c84SBram Moolenaar        if type == tokenize.NEWLINE:
223*18144c84SBram Moolenaar            return ''
224*18144c84SBram Moolenaar        elif type == tokenize.STRING or str == 'str':
225*18144c84SBram Moolenaar            return '%s = str' % var
226*18144c84SBram Moolenaar        elif str == '[' or str == 'list':
227*18144c84SBram Moolenaar            return '%s= list' % var
228*18144c84SBram Moolenaar        elif str == '{' or str == 'dict':
229*18144c84SBram Moolenaar            return '%s = dict' % var
230*18144c84SBram Moolenaar        elif type == tokenize.NUMBER:
231*18144c84SBram Moolenaar            return '%s = 0' % var
232*18144c84SBram Moolenaar        elif str == 'Set':
233*18144c84SBram Moolenaar            return '%s = Set' % var
234*18144c84SBram Moolenaar        elif str == 'open' or str == 'file':
235*18144c84SBram Moolenaar            return '%s = file' % var
236*18144c84SBram Moolenaar        else:
237*18144c84SBram Moolenaar            inst = str + parse_to_end(gen)
238*18144c84SBram Moolenaar            if len(inst) > 0:
239*18144c84SBram Moolenaar                #dbg(5,"found [%s = %s]" % (var, inst))
240*18144c84SBram Moolenaar                return '%s = %s' % (var, inst)
241*18144c84SBram Moolenaar    return ''
242*18144c84SBram Moolenaar
243*18144c84SBram Moolenaardef eval_source_code():
244*18144c84SBram Moolenaar    LINE=vim.eval('getline(\'.\')')
245*18144c84SBram Moolenaar    s = cStringIO.StringIO('\n'.join(vim.current.buffer[:]) + '\n')
246*18144c84SBram Moolenaar    g = tokenize.generate_tokens(s.readline)
247*18144c84SBram Moolenaar
248*18144c84SBram Moolenaar    stmts = []
249*18144c84SBram Moolenaar    lineNo = 0
250*18144c84SBram Moolenaar    try:
251*18144c84SBram Moolenaar        for type, str, begin, end, line in g:
252*18144c84SBram Moolenaar            if line.replace('\n','') == vim.eval('getline(\'.\')'): break
253*18144c84SBram Moolenaar            elif begin[0] == lineNo: continue
254*18144c84SBram Moolenaar            #junk
255*18144c84SBram Moolenaar            elif type == tokenize.INDENT or \
256*18144c84SBram Moolenaar                 type == tokenize.DEDENT or \
257*18144c84SBram Moolenaar                 type == tokenize.ERRORTOKEN or \
258*18144c84SBram Moolenaar                 type == tokenize.ENDMARKER or \
259*18144c84SBram Moolenaar                 type == tokenize.NEWLINE or \
260*18144c84SBram Moolenaar                 type == tokenize.COMMENT:
261*18144c84SBram Moolenaar                continue
262*18144c84SBram Moolenaar            #import statement
263*18144c84SBram Moolenaar            elif str == 'import':
264*18144c84SBram Moolenaar                import_stmt=parse_to_end(g)
265*18144c84SBram Moolenaar                if len(import_stmt) > 0:
266*18144c84SBram Moolenaar                    #dbg(5,"found [import %s]" % import_stmt)
267*18144c84SBram Moolenaar                    stmts.append("import %s" % import_stmt)
268*18144c84SBram Moolenaar            #import from statement
269*18144c84SBram Moolenaar            elif str == 'from':
270*18144c84SBram Moolenaar                type, str, begin, end, line = g.next()
271*18144c84SBram Moolenaar                mod = str
272*18144c84SBram Moolenaar
273*18144c84SBram Moolenaar                type, str, begin, end, line = g.next()
274*18144c84SBram Moolenaar                if str != "import": break
275*18144c84SBram Moolenaar                from_stmt=parse_to_end(g)
276*18144c84SBram Moolenaar                if len(from_stmt) > 0:
277*18144c84SBram Moolenaar                    #dbg(5,"found [from %s import %s]" % (mod, from_stmt))
278*18144c84SBram Moolenaar                    stmts.append("from %s import %s" % (mod, from_stmt))
279*18144c84SBram Moolenaar            #def statement
280*18144c84SBram Moolenaar            elif str == 'def':
281*18144c84SBram Moolenaar                funcstr = ''
282*18144c84SBram Moolenaar                for type, str, begin, end, line in g:
283*18144c84SBram Moolenaar                    if line.replace('\n','') == vim.eval('getline(\'.\')'): break
284*18144c84SBram Moolenaar                    elif str == ':':
285*18144c84SBram Moolenaar                        stmts += parse_block(g)
286*18144c84SBram Moolenaar                        break
287*18144c84SBram Moolenaar                    funcstr += str
288*18144c84SBram Moolenaar                if len(funcstr) > 0:
289*18144c84SBram Moolenaar                    #dbg(5,"found [def %s]" % funcstr)
290*18144c84SBram Moolenaar                    stmts.append("def %s:\n   pass" % funcstr)
291*18144c84SBram Moolenaar            #class declaration
292*18144c84SBram Moolenaar            elif str == 'class':
293*18144c84SBram Moolenaar                type, str, begin, end, line = g.next()
294*18144c84SBram Moolenaar                classname = str
295*18144c84SBram Moolenaar                #dbg(5,"found [class %s]" % classname)
296*18144c84SBram Moolenaar
297*18144c84SBram Moolenaar                level = 0
298*18144c84SBram Moolenaar                members = []
299*18144c84SBram Moolenaar                for type, str, begin, end, line in g:
300*18144c84SBram Moolenaar                    if line.replace('\n','') == vim.eval('getline(\'.\')'): break
301*18144c84SBram Moolenaar                    elif type == tokenize.INDENT:
302*18144c84SBram Moolenaar                        level += 1
303*18144c84SBram Moolenaar                    elif type == tokenize.DEDENT:
304*18144c84SBram Moolenaar                        level -= 1
305*18144c84SBram Moolenaar                        if level == 0: break;
306*18144c84SBram Moolenaar                    elif str == 'def':
307*18144c84SBram Moolenaar                        memberstr = ''
308*18144c84SBram Moolenaar                        for type, str, begin, end, line in g:
309*18144c84SBram Moolenaar                            if line.replace('\n','') == vim.eval('getline(\'.\')'): break
310*18144c84SBram Moolenaar                            elif str == ':':
311*18144c84SBram Moolenaar                                stmts += parse_block(g)
312*18144c84SBram Moolenaar                                break
313*18144c84SBram Moolenaar                            memberstr += str
314*18144c84SBram Moolenaar                        #dbg(5,"   member [%s]" % memberstr)
315*18144c84SBram Moolenaar                        members.append(memberstr)
316*18144c84SBram Moolenaar                classstr = 'class %s:' % classname
317*18144c84SBram Moolenaar                for m in members:
318*18144c84SBram Moolenaar                    classstr += ("\n   def %s:\n      pass" % m)
319*18144c84SBram Moolenaar                stmts.append("%s\n" % classstr)
320*18144c84SBram Moolenaar            elif keyword.iskeyword(str) or str in globals():
321*18144c84SBram Moolenaar                #dbg(5,"keyword = %s" % str)
322*18144c84SBram Moolenaar                lineNo = begin[0]
323*18144c84SBram Moolenaar            else:
324*18144c84SBram Moolenaar                assign = parse_statement(g,str)
325*18144c84SBram Moolenaar                if len(assign) > 0: stmts.append(assign)
326*18144c84SBram Moolenaar
327*18144c84SBram Moolenaar        for s in stmts:
328*18144c84SBram Moolenaar            try:
329*18144c84SBram Moolenaar                #dbg(15,"evaluating: %s\n" % s)
330*18144c84SBram Moolenaar                exec(s) in globals()
331*18144c84SBram Moolenaar            except:
332*18144c84SBram Moolenaar                #dbg(1,"exception: %s" % sys.exc_info()[1])
333*18144c84SBram Moolenaar                pass
334*18144c84SBram Moolenaar    except:
335*18144c84SBram Moolenaar        #dbg(1,"exception: %s" % sys.exc_info()[1])
336*18144c84SBram Moolenaar        pass
337*18144c84SBram Moolenaar
338*18144c84SBram Moolenaardef clean_up():
339*18144c84SBram Moolenaar    for o in globals().keys():
340*18144c84SBram Moolenaar        if o not in LOCALDEFS:
341*18144c84SBram Moolenaar            try:
342*18144c84SBram Moolenaar                exec('del %s' % o) in globals()
343*18144c84SBram Moolenaar            except: pass
344*18144c84SBram Moolenaar
345*18144c84SBram Moolenaarsys.path.extend(['.','..'])
346*18144c84SBram MoolenaarPYTHONEOF
347*18144c84SBram Moolenaarendfunction
348*18144c84SBram Moolenaar
349*18144c84SBram Moolenaarlet g:pythoncomplete_debug_level = 0
350*18144c84SBram Moolenaarcall s:DefPython()
351*18144c84SBram Moolenaar" vim: set et ts=4:
352