pythoncomplete.vim 22 KB

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