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