jedi_vim.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. """
  2. The Python parts of the Jedi library for VIM. It is mostly about communicating
  3. with VIM.
  4. """
  5. import traceback # for exception output
  6. import re
  7. import os
  8. import sys
  9. from shlex import split as shsplit
  10. try:
  11. from itertools import zip_longest
  12. except ImportError:
  13. from itertools import izip_longest as zip_longest # Python 2
  14. def no_jedi_warning():
  15. vim.command('echohl WarningMsg | echom "Please install Jedi if you want to use jedi_vim." | echohl None')
  16. def echo_highlight(msg):
  17. vim_command('echohl WarningMsg | echom "%s" | echohl None' % msg)
  18. import vim
  19. try:
  20. import jedi
  21. except ImportError:
  22. no_jedi_warning()
  23. jedi = None
  24. else:
  25. version = jedi.__version__
  26. if isinstance(version, str):
  27. # the normal use case, now.
  28. from jedi import utils
  29. version = utils.version_info()
  30. if version < (0, 7):
  31. echo_highlight('Please update your Jedi version, it is to old.')
  32. is_py3 = sys.version_info[0] >= 3
  33. if is_py3:
  34. unicode = str
  35. def catch_and_print_exceptions(func):
  36. def wrapper(*args, **kwargs):
  37. try:
  38. return func(*args, **kwargs)
  39. except (Exception, vim.error):
  40. print(traceback.format_exc())
  41. return None
  42. return wrapper
  43. def _check_jedi_availability(show_error=False):
  44. def func_receiver(func):
  45. def wrapper(*args, **kwargs):
  46. if jedi is None:
  47. if show_error:
  48. no_jedi_warning()
  49. return
  50. else:
  51. return func(*args, **kwargs)
  52. return wrapper
  53. return func_receiver
  54. class VimError(Exception):
  55. def __init__(self, message, throwpoint, executing):
  56. super(type(self), self).__init__(message)
  57. self.throwpoint = throwpoint
  58. self.executing = executing
  59. def __str__(self):
  60. return self.message + '; created by: ' + repr(self.executing)
  61. def _catch_exception(string, is_eval):
  62. """
  63. Interface between vim and python calls back to it.
  64. Necessary, because the exact error message is not given by `vim.error`.
  65. """
  66. e = 'jedi#_vim_exceptions(%s, %s)'
  67. result = vim.eval(e % (repr(PythonToVimStr(string, 'UTF-8')), is_eval))
  68. if 'exception' in result:
  69. raise VimError(result['exception'], result['throwpoint'], string)
  70. return result['result']
  71. def vim_eval(string):
  72. return _catch_exception(string, 1)
  73. def vim_command(string):
  74. _catch_exception(string, 0)
  75. class PythonToVimStr(unicode):
  76. """ Vim has a different string implementation of single quotes """
  77. __slots__ = []
  78. def __new__(cls, obj, encoding='UTF-8'):
  79. if is_py3 or isinstance(obj, unicode):
  80. return unicode.__new__(cls, obj)
  81. else:
  82. return unicode.__new__(cls, obj, encoding)
  83. def __repr__(self):
  84. # this is totally stupid and makes no sense but vim/python unicode
  85. # support is pretty bad. don't ask how I came up with this... It just
  86. # works...
  87. # It seems to be related to that bug: http://bugs.python.org/issue5876
  88. if unicode is str:
  89. s = self
  90. else:
  91. s = self.encode('UTF-8')
  92. return '"%s"' % s.replace('\\', '\\\\').replace('"', r'\"')
  93. @catch_and_print_exceptions
  94. def get_script(source=None, column=None):
  95. jedi.settings.additional_dynamic_modules = \
  96. [b.name for b in vim.buffers if b.name is not None and b.name.endswith('.py')]
  97. if source is None:
  98. source = '\n'.join(vim.current.buffer)
  99. row = vim.current.window.cursor[0]
  100. if column is None:
  101. column = vim.current.window.cursor[1]
  102. buf_path = vim.current.buffer.name
  103. encoding = vim_eval('&encoding') or 'latin1'
  104. return jedi.Script(source, row, column, buf_path, encoding)
  105. @_check_jedi_availability(show_error=False)
  106. @catch_and_print_exceptions
  107. def completions():
  108. row, column = vim.current.window.cursor
  109. # Clear call signatures in the buffer so they aren't seen by the completer.
  110. # Call signatures in the command line can stay.
  111. if vim_eval("g:jedi#show_call_signatures") == '1':
  112. clear_call_signatures()
  113. if vim.eval('a:findstart') == '1':
  114. count = 0
  115. for char in reversed(vim.current.line[:column]):
  116. if not re.match('[\w\d]', char):
  117. break
  118. count += 1
  119. vim.command('return %i' % (column - count))
  120. else:
  121. base = vim.eval('a:base')
  122. source = ''
  123. for i, line in enumerate(vim.current.buffer):
  124. # enter this path again, otherwise source would be incomplete
  125. if i == row - 1:
  126. source += line[:column] + base + line[column:]
  127. else:
  128. source += line
  129. source += '\n'
  130. # here again hacks, because jedi has a different interface than vim
  131. column += len(base)
  132. try:
  133. script = get_script(source=source, column=column)
  134. completions = script.completions()
  135. signatures = script.call_signatures()
  136. out = []
  137. for c in completions:
  138. d = dict(word=PythonToVimStr(c.name[:len(base)] + c.complete),
  139. abbr=PythonToVimStr(c.name),
  140. # stuff directly behind the completion
  141. menu=PythonToVimStr(c.description),
  142. info=PythonToVimStr(c.docstring()), # docstr
  143. icase=1, # case insensitive
  144. dup=1 # allow duplicates (maybe later remove this)
  145. )
  146. out.append(d)
  147. strout = str(out)
  148. except Exception:
  149. # print to stdout, will be in :messages
  150. print(traceback.format_exc())
  151. strout = ''
  152. completions = []
  153. signatures = []
  154. show_call_signatures(signatures)
  155. vim.command('return ' + strout)
  156. @_check_jedi_availability(show_error=True)
  157. @catch_and_print_exceptions
  158. def goto(is_definition=False, is_related_name=False, no_output=False):
  159. definitions = []
  160. script = get_script()
  161. try:
  162. if is_related_name:
  163. definitions = script.usages()
  164. elif is_definition:
  165. definitions = script.goto_definitions()
  166. else:
  167. definitions = script.goto_assignments()
  168. except jedi.NotFoundError:
  169. echo_highlight("Cannot follow nothing. Put your cursor on a valid name.")
  170. else:
  171. if no_output:
  172. return definitions
  173. if not definitions:
  174. echo_highlight("Couldn't find any definitions for this.")
  175. elif len(definitions) == 1 and not is_related_name:
  176. # just add some mark to add the current position to the jumplist.
  177. # this is ugly, because it overrides the mark for '`', so if anyone
  178. # has a better idea, let me know.
  179. vim_command('normal! m`')
  180. d = list(definitions)[0]
  181. if d.in_builtin_module():
  182. if d.is_keyword:
  183. echo_highlight("Cannot get the definition of Python keywords.")
  184. else:
  185. echo_highlight("Builtin modules cannot be displayed (%s)."
  186. % d.desc_with_module)
  187. else:
  188. if d.module_path != vim.current.buffer.name:
  189. result = new_buffer(d.module_path)
  190. if not result:
  191. return
  192. vim.current.window.cursor = d.line, d.column
  193. else:
  194. # multiple solutions
  195. lst = []
  196. for d in definitions:
  197. if d.in_builtin_module():
  198. lst.append(dict(text=PythonToVimStr('Builtin ' + d.description)))
  199. else:
  200. lst.append(dict(filename=PythonToVimStr(d.module_path),
  201. lnum=d.line, col=d.column + 1,
  202. text=PythonToVimStr(d.description)))
  203. vim_eval('setqflist(%s)' % repr(lst))
  204. vim_eval('jedi#add_goto_window(' + str(len(lst)) + ')')
  205. return definitions
  206. @_check_jedi_availability(show_error=True)
  207. @catch_and_print_exceptions
  208. def show_documentation():
  209. script = get_script()
  210. try:
  211. definitions = script.goto_definitions()
  212. except jedi.NotFoundError:
  213. definitions = []
  214. except Exception:
  215. # print to stdout, will be in :messages
  216. definitions = []
  217. print("Exception, this shouldn't happen.")
  218. print(traceback.format_exc())
  219. if not definitions:
  220. echo_highlight('No documentation found for that.')
  221. vim.command('return')
  222. else:
  223. docs = ['Docstring for %s\n%s\n%s' % (d.desc_with_module, '=' * 40, d.docstring())
  224. if d.docstring() else '|No Docstring for %s|' % d for d in definitions]
  225. text = ('\n' + '-' * 79 + '\n').join(docs)
  226. vim.command('let l:doc = %s' % repr(PythonToVimStr(text)))
  227. vim.command('let l:doc_lines = %s' % len(text.split('\n')))
  228. return True
  229. @catch_and_print_exceptions
  230. def clear_call_signatures():
  231. # Check if using command line call signatures
  232. if vim_eval("g:jedi#show_call_signatures") == '2':
  233. vim_command('echo ""')
  234. return
  235. cursor = vim.current.window.cursor
  236. e = vim_eval('g:jedi#call_signature_escape')
  237. # We need two turns here to search and replace certain lines:
  238. # 1. Search for a line with a call signature and save the appended
  239. # characters
  240. # 2. Actually replace the line and redo the status quo.
  241. py_regex = r'%sjedi=([0-9]+), (.*?)%s.*?%sjedi%s'.replace('%s', e)
  242. for i, line in enumerate(vim.current.buffer):
  243. match = re.search(py_regex, line)
  244. if match is not None:
  245. # Some signs were added to minimize syntax changes due to call
  246. # signatures. We have to remove them again. The number of them is
  247. # specified in `match.group(1)`.
  248. after = line[match.end() + int(match.group(1)):]
  249. line = line[:match.start()] + match.group(2) + after
  250. vim.current.buffer[i] = line
  251. vim.current.window.cursor = cursor
  252. @_check_jedi_availability(show_error=False)
  253. @catch_and_print_exceptions
  254. def show_call_signatures(signatures=()):
  255. if vim_eval("has('conceal') && g:jedi#show_call_signatures") == '0':
  256. return
  257. if signatures == ():
  258. signatures = get_script().call_signatures()
  259. clear_call_signatures()
  260. if not signatures:
  261. return
  262. if vim_eval("g:jedi#show_call_signatures") == '2':
  263. return cmdline_call_signatures(signatures)
  264. for i, signature in enumerate(signatures):
  265. line, column = signature.bracket_start
  266. # signatures are listed above each other
  267. line_to_replace = line - i - 1
  268. # because there's a space before the bracket
  269. insert_column = column - 1
  270. if insert_column < 0 or line_to_replace <= 0:
  271. # Edge cases, when the call signature has no space on the screen.
  272. break
  273. # TODO check if completion menu is above or below
  274. line = vim_eval("getline(%s)" % line_to_replace)
  275. params = [p.description.replace('\n', '') for p in signature.params]
  276. try:
  277. # *_*PLACEHOLDER*_* makes something fat. See after/syntax file.
  278. params[signature.index] = '*_*%s*_*' % params[signature.index]
  279. except (IndexError, TypeError):
  280. pass
  281. # This stuff is reaaaaally a hack! I cannot stress enough, that
  282. # this is a stupid solution. But there is really no other yet.
  283. # There is no possibility in VIM to draw on the screen, but there
  284. # will be one (see :help todo Patch to access screen under Python.
  285. # (Marko Mahni, 2010 Jul 18))
  286. text = " (%s) " % ', '.join(params)
  287. text = ' ' * (insert_column - len(line)) + text
  288. end_column = insert_column + len(text) - 2 # -2 due to bold symbols
  289. # Need to decode it with utf8, because vim returns always a python 2
  290. # string even if it is unicode.
  291. e = vim_eval('g:jedi#call_signature_escape')
  292. if hasattr(e, 'decode'):
  293. e = e.decode('UTF-8')
  294. # replace line before with cursor
  295. regex = "xjedi=%sx%sxjedix".replace('x', e)
  296. prefix, replace = line[:insert_column], line[insert_column:end_column]
  297. # Check the replace stuff for strings, to append them
  298. # (don't want to break the syntax)
  299. regex_quotes = r'''\\*["']+'''
  300. # `add` are all the quotation marks.
  301. # join them with a space to avoid producing '''
  302. add = ' '.join(re.findall(regex_quotes, replace))
  303. # search backwards
  304. if add and replace[0] in ['"', "'"]:
  305. a = re.search(regex_quotes + '$', prefix)
  306. add = ('' if a is None else a.group(0)) + add
  307. tup = '%s, %s' % (len(add), replace)
  308. repl = prefix + (regex % (tup, text)) + add + line[end_column:]
  309. vim_eval('setline(%s, %s)' % (line_to_replace, repr(PythonToVimStr(repl))))
  310. @catch_and_print_exceptions
  311. def cmdline_call_signatures(signatures):
  312. def get_params(s):
  313. return [p.description.replace('\n', '') for p in s.params]
  314. if len(signatures) > 1:
  315. params = zip_longest(*map(get_params, signatures), fillvalue='_')
  316. params = ['(' + ', '.join(p) + ')' for p in params]
  317. else:
  318. params = get_params(signatures[0])
  319. text = ', '.join(params).replace('"', '\\"')
  320. # Allow 12 characters for ruler/showcmd - setting noruler/noshowcmd
  321. # here causes incorrect undo history
  322. max_msg_len = int(vim_eval('&columns')) - 12
  323. max_num_spaces = (max_msg_len - len(signatures[0].call_name)
  324. - len(text) - 2) # 2 accounts for parentheses
  325. if max_num_spaces < 0:
  326. return # No room for the message
  327. _, column = signatures[0].bracket_start
  328. num_spaces = min(int(vim_eval('g:jedi#first_col +'
  329. 'wincol() - col(".")')) +
  330. column - len(signatures[0].call_name),
  331. max_num_spaces)
  332. spaces = ' ' * num_spaces
  333. try:
  334. index = [s.index for s in signatures if isinstance(s.index, int)][0]
  335. left = text.index(params[index])
  336. right = left + len(params[index])
  337. vim_command(' echon "%s" | '
  338. 'echohl Function | echon "%s" | '
  339. 'echohl None | echon "(" | '
  340. 'echohl jediFunction | echon "%s" | '
  341. 'echohl jediFat | echon "%s" | '
  342. 'echohl jediFunction | echon "%s" | '
  343. 'echohl None | echon ")"'
  344. % (spaces, signatures[0].call_name, text[:left],
  345. text[left:right], text[right:]))
  346. except (TypeError, IndexError):
  347. vim_command(' echon "%s" | '
  348. 'echohl Function | echon "%s" | '
  349. 'echohl None | echon "(" | '
  350. 'echohl jediFunction | echon "%s" | '
  351. 'echohl None | echon ")"'
  352. % (spaces, signatures[0].call_name, text))
  353. @_check_jedi_availability(show_error=True)
  354. @catch_and_print_exceptions
  355. def rename():
  356. if not int(vim.eval('a:0')):
  357. _rename_cursor = vim.current.window.cursor
  358. vim_command('normal A ') # otherwise startinsert doesn't work well
  359. vim.current.window.cursor = _rename_cursor
  360. vim_command('augroup jedi_rename')
  361. vim_command('autocmd InsertLeave <buffer> call jedi#rename(1)')
  362. vim_command('augroup END')
  363. vim_command('normal! diw')
  364. vim_command(':startinsert')
  365. else:
  366. window_path = vim.current.buffer.name
  367. # reset autocommand
  368. vim_command('autocmd! jedi_rename InsertLeave')
  369. replace = vim_eval("expand('<cword>')")
  370. vim_command('normal! u') # undo new word
  371. cursor = vim.current.window.cursor
  372. vim_command('normal! u') # undo the space at the end
  373. vim.current.window.cursor = cursor
  374. if replace is None:
  375. echo_highlight('No rename possible, if no name is given.')
  376. else:
  377. temp_rename = goto(is_related_name=True, no_output=True)
  378. # sort the whole thing reverse (positions at the end of the line
  379. # must be first, because they move the stuff before the position).
  380. temp_rename = sorted(temp_rename, reverse=True,
  381. key=lambda x: (x.module_path, x.start_pos))
  382. for r in temp_rename:
  383. if r.in_builtin_module():
  384. continue
  385. if vim.current.buffer.name != r.module_path:
  386. result = new_buffer(r.module_path)
  387. if not result:
  388. return
  389. vim.current.window.cursor = r.start_pos
  390. vim_command('normal! cw%s' % replace)
  391. result = new_buffer(window_path)
  392. if not result:
  393. return
  394. vim.current.window.cursor = cursor
  395. echo_highlight('Jedi did %s renames!' % len(temp_rename))
  396. @_check_jedi_availability(show_error=True)
  397. @catch_and_print_exceptions
  398. def py_import():
  399. # args are the same as for the :edit command
  400. args = shsplit(vim.eval('a:args'))
  401. import_path = args.pop()
  402. text = 'import %s' % import_path
  403. scr = jedi.Script(text, 1, len(text), '')
  404. try:
  405. completion = scr.goto_assignments()[0]
  406. except IndexError:
  407. echo_highlight('Cannot find %s in sys.path!' % import_path)
  408. else:
  409. if completion.in_builtin_module():
  410. echo_highlight('%s is a builtin module.' % import_path)
  411. else:
  412. cmd_args = ' '.join([a.replace(' ', '\\ ') for a in args])
  413. new_buffer(completion.module_path, cmd_args)
  414. @catch_and_print_exceptions
  415. def py_import_completions():
  416. argl = vim.eval('a:argl')
  417. try:
  418. import jedi
  419. except ImportError:
  420. print('Pyimport completion requires jedi module: https://github.com/davidhalter/jedi')
  421. comps = []
  422. else:
  423. text = 'import %s' % argl
  424. script = jedi.Script(text, 1, len(text), '')
  425. comps = ['%s%s' % (argl, c.complete) for c in script.completions()]
  426. vim.command("return '%s'" % '\n'.join(comps))
  427. @catch_and_print_exceptions
  428. def new_buffer(path, options=''):
  429. # options are what you can to edit the edit options
  430. if vim_eval('g:jedi#use_tabs_not_buffers') == '1':
  431. _tabnew(path, options)
  432. elif not vim_eval('g:jedi#use_splits_not_buffers') == '1':
  433. user_split_option = vim_eval('g:jedi#use_splits_not_buffers')
  434. split_options = {
  435. 'top': 'topleft split',
  436. 'left': 'topleft vsplit',
  437. 'right': 'botright vsplit',
  438. 'bottom': 'botright split',
  439. 'winwidth': 'vs'
  440. }
  441. if user_split_option == 'winwidth' and vim.current.window.width <= 2 * int(vim_eval("&textwidth ? &textwidth : 80")):
  442. split_options['winwidth'] = 'sp'
  443. if user_split_option not in split_options:
  444. print('g:jedi#use_splits_not_buffers value is not correct, valid options are: %s' % ','.join(split_options.keys()))
  445. else:
  446. vim_command(split_options[user_split_option] + " %s" % path)
  447. else:
  448. if vim_eval("!&hidden && &modified") == '1':
  449. if vim_eval("bufname('%')") is None:
  450. echo_highlight('Cannot open a new buffer, use `:set hidden` or save your buffer')
  451. return False
  452. else:
  453. vim_command('w')
  454. vim_command('edit %s %s' % (options, escape_file_path(path)))
  455. # sometimes syntax is being disabled and the filetype not set.
  456. if vim_eval('!exists("g:syntax_on")') == '1':
  457. vim_command('syntax enable')
  458. if vim_eval("&filetype != 'python'") == '1':
  459. vim_command('set filetype=python')
  460. return True
  461. @catch_and_print_exceptions
  462. def _tabnew(path, options=''):
  463. """
  464. Open a file in a new tab or switch to an existing one.
  465. :param options: `:tabnew` options, read vim help.
  466. """
  467. path = os.path.abspath(path)
  468. if vim_eval('has("gui")') == '1':
  469. vim_command('tab drop %s %s' % (options, escape_file_path(path)))
  470. return
  471. for tab_nr in range(int(vim_eval("tabpagenr('$')"))):
  472. for buf_nr in vim_eval("tabpagebuflist(%i + 1)" % tab_nr):
  473. buf_nr = int(buf_nr) - 1
  474. try:
  475. buf_path = vim.buffers[buf_nr].name
  476. except (LookupError, ValueError):
  477. # Just do good old asking for forgiveness.
  478. # don't know why this happens :-)
  479. pass
  480. else:
  481. if buf_path == path:
  482. # tab exists, just switch to that tab
  483. vim_command('tabfirst | tabnext %i' % (tab_nr + 1))
  484. break
  485. else:
  486. continue
  487. break
  488. else:
  489. # tab doesn't exist, add a new one.
  490. vim_command('tabnew %s' % escape_file_path(path))
  491. def escape_file_path(path):
  492. return path.replace(' ', r'\ ')
  493. def print_to_stdout(level, str_out):
  494. print(str_out)