History

initial import

phil9 committed 5 years ago

Commit dd360bcNo parent (first commit)1336 files · +557163 −-5Browse files

Showing the changes to lib/vimfiles/autoload/pythoncomplete.vim (1 of 1336 files). Show all

Unified · Split

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