Merge pull request #1025 from davidhalter/jedi-api

Use the new Jedi API
This commit is contained in:
Dave Halter
2020-12-26 23:47:32 +01:00
committed by GitHub
14 changed files with 372 additions and 257 deletions

63
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,63 @@
name: ci
on: push
jobs:
tests:
runs-on: ubuntu-20.04
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: Setup
run: |
sudo pip install pytest
vim --version
#- name: Setup tmate session
# uses: mxschmitt/action-tmate@v3
- name: Run tests
run: 'make test'
code-quality:
runs-on: ubuntu-20.04
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: Run tests
run: |
vim --version
make check
coverage:
runs-on: ubuntu-20.04
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: Install dependencies
run: |
sudo add-apt-repository ppa:neovim-ppa/stable -y
sudo apt-get update -q
sudo apt-get install neovim -y
sudo pip install pynvim pytest-cov
sudo pip list
nvim --version
- name: Run tests
run: |
make --keep-going test_coverage BUILD_VIRTUAL_ENV=$VIRTUAL_ENV
- name: Upload coverage data
run: |
coverage xml
bash <(curl -s https://codecov.io/bash) -X fix -f coverage.xml -F py${TRAVIS_PYTHON_VERSION//./}

View File

@@ -1,17 +1,10 @@
dist: xenial dist: bionic
language: python language: python
python: 3.7 python: 3.8
env: env:
matrix: - ENV=test
- ENV=test - ENV=check
- ENV=check - ENV=test_coverage
- ENV=test_coverage
matrix:
include:
- env: ENV=test_coverage
python: 2.7
- env: ENV=test_coverage
python: 3.8
install: install:
- | - |
if [ "$ENV" = "test" ]; then if [ "$ENV" = "test" ]; then

View File

@@ -208,6 +208,11 @@ get more information. If you set them to ``""``, they are not assigned.
let g:jedi#completions_command = "<C-Space>" let g:jedi#completions_command = "<C-Space>"
let g:jedi#rename_command = "<leader>r" let g:jedi#rename_command = "<leader>r"
A few examples of setting up your project:
.. code-block:: vim
let g:jedi#environment_path = "<leader>d"
Finally, if you don't want completion, but all the other features, use: Finally, if you don't want completion, but all the other features, use:

View File

@@ -35,6 +35,8 @@ let s:default_settings = {
\ 'popup_select_first': 1, \ 'popup_select_first': 1,
\ 'quickfix_window_height': 10, \ 'quickfix_window_height': 10,
\ 'force_py_version': "'auto'", \ 'force_py_version': "'auto'",
\ 'environment_path': "'auto'",
\ 'project_path': "'auto'",
\ 'smart_auto_mappings': 0, \ 'smart_auto_mappings': 0,
\ 'use_tag_stack': 1 \ 'use_tag_stack': 1
\ } \ }
@@ -422,6 +424,14 @@ function! jedi#py_import(args) abort
PythonJedi jedi_vim.py_import() PythonJedi jedi_vim.py_import()
endfun endfun
function! jedi#choose_environment(args) abort
PythonJedi jedi_vim.choose_environment()
endfun
function! jedi#load_project(args) abort
PythonJedi jedi_vim.load_project()
endfun
function! jedi#py_import_completions(argl, cmdl, pos) abort function! jedi#py_import_completions(argl, cmdl, pos) abort
PythonJedi jedi_vim.py_import_completions() PythonJedi jedi_vim.py_import_completions()
endfun endfun

View File

@@ -44,6 +44,7 @@ Contents *jedi-vim-contents*
6.12. force_py_version |g:jedi#force_py_version| 6.12. force_py_version |g:jedi#force_py_version|
6.13. smart_auto_mappings |g:jedi#smart_auto_mappings| 6.13. smart_auto_mappings |g:jedi#smart_auto_mappings|
6.14. use_tag_stack |g:jedi#use_tag_stack| 6.14. use_tag_stack |g:jedi#use_tag_stack|
6.15. environment_path |g:jedi#environment_path|
7. Testing |jedi-vim-testing| 7. Testing |jedi-vim-testing|
8. Contributing |jedi-vim-contributing| 8. Contributing |jedi-vim-contributing|
9. License |jedi-vim-license| 9. License |jedi-vim-license|
@@ -506,6 +507,17 @@ definition with arbitrary changes to the |jumplist|.
Options: 0 or 1 Options: 0 or 1
Default: 1 (enabled by default) Default: 1 (enabled by default)
------------------------------------------------------------------------------
6.15. `g:jedi#environment_path` *g:jedi#environment_path*
To use a specific virtualenv or a specific Python version it is possible to
set an interpreter.
Both setting the directory and setting a project is working.
Examples: "/usr/bin/python3.9", "venv", "../venv", "../venv/bin/python"
Default: "auto"
============================================================================== ==============================================================================
7. Testing *jedi-vim-testing* 7. Testing *jedi-vim-testing*

View File

@@ -53,6 +53,10 @@ endif
" Pyimport command " Pyimport command
command! -nargs=1 -complete=custom,jedi#py_import_completions Pyimport :call jedi#py_import(<q-args>) command! -nargs=1 -complete=custom,jedi#py_import_completions Pyimport :call jedi#py_import(<q-args>)
command! -nargs=? -complete=file JediChooseEnvironment :call jedi#choose_environment(<q-args>)
command! -nargs=? -complete=file JediLoadProject :call jedi#load_project(<q-args>)
function! s:jedi_debug_info() function! s:jedi_debug_info()
" Ensure the autoload file has been loaded (and ignore any errors, which " Ensure the autoload file has been loaded (and ignore any errors, which
" will be displayed with the debug info). " will be displayed with the debug info).

View File

@@ -211,51 +211,84 @@ def _check_jedi_availability(show_error=False):
return func_receiver return func_receiver
current_environment = (None, None) # Tuple of cache key / project
_current_project_cache = None, None
def get_environment(use_cache=True): def get_project():
global current_environment vim_environment_path = vim_eval("g:jedi#environment_path")
vim_project_path = vim_eval("g:jedi#project_path")
vim_force_python_version = vim_eval("g:jedi#force_py_version") global _current_project_cache
if use_cache and vim_force_python_version == current_environment[0]: cache_key = dict(project_path=vim_project_path, environment_path=vim_environment_path)
return current_environment[1] if cache_key == _current_project_cache[0]:
return _current_project_cache[1]
environment = None if vim_environment_path in ("auto", "", None):
if vim_force_python_version == "auto": environment_path = None
environment = jedi.api.environment.get_cached_default_environment()
else: else:
force_python_version = vim_force_python_version environment_path = vim_environment_path
if '0000' in force_python_version or '9999' in force_python_version:
# It's probably a float that wasn't shortened.
try:
force_python_version = "{:.1f}".format(float(force_python_version))
except ValueError:
pass
elif isinstance(force_python_version, float):
force_python_version = "{:.1f}".format(force_python_version)
try: if vim_project_path in ("auto", "", None):
environment = jedi.get_system_environment(force_python_version) project_path = jedi.get_default_project().path
except jedi.InvalidPythonEnvironment as exc: else:
environment = jedi.api.environment.get_cached_default_environment() project_path = vim_project_path
echo_highlight(
"force_python_version=%s is not supported: %s - using %s." % (
vim_force_python_version, str(exc), str(environment)))
current_environment = (vim_force_python_version, environment) project = jedi.Project(project_path, environment_path=environment_path)
return environment
_current_project_cache = cache_key, project
def get_known_environments(): return project
"""Get known Jedi environments."""
envs = list(jedi.api.environment.find_virtualenvs())
envs.extend(jedi.api.environment.find_system_environments())
return envs
@catch_and_print_exceptions @catch_and_print_exceptions
def get_script(source=None, column=None): def choose_environment():
args = shsplit(vim.eval('a:args'))
envs = list(jedi.find_system_environments())
envs.extend(jedi.find_virtualenvs(paths=args or None))
env_paths = [env.executable for env in envs]
vim_command('belowright new')
vim.current.buffer[:] = env_paths
vim.current.buffer.name = "Hit Enter to Choose an Environment"
vim_command(
'setlocal buftype=nofile bufhidden=wipe noswapfile nobuflisted readonly nomodifiable')
vim_command('noremap <buffer> <ESC> :bw<CR>')
vim_command('noremap <buffer> <CR> :PythonJedi jedi_vim.choose_environment_hit_enter()<CR>')
@catch_and_print_exceptions
def choose_environment_hit_enter():
vim.vars['jedi#environment_path'] = vim.current.line
vim_command('bd')
@catch_and_print_exceptions
def load_project():
path = vim.eval('a:args')
vim.vars['jedi#project_path'] = path
env_path = vim_eval("g:jedi#environment_path")
if env_path == 'auto':
env_path = None
if path:
try:
project = jedi.Project.load(path)
except FileNotFoundError:
project = jedi.Project(path, environment_path=env_path)
project.save()
else:
project = jedi.get_default_project()
path = project.path
project.save()
global _current_project_cache
cache_key = dict(project_path=path, environment_path=env_path)
_current_project_cache = cache_key, project
@catch_and_print_exceptions
def get_script(source=None):
jedi.settings.additional_dynamic_modules = [ jedi.settings.additional_dynamic_modules = [
b.name for b in vim.buffers if ( b.name for b in vim.buffers if (
b.name is not None and b.name is not None and
@@ -263,16 +296,19 @@ def get_script(source=None, column=None):
b.options['buflisted'])] b.options['buflisted'])]
if source is None: if source is None:
source = '\n'.join(vim.current.buffer) source = '\n'.join(vim.current.buffer)
buf_path = vim.current.buffer.name
if not buf_path:
# If a buffer has no name its name is an empty string.
buf_path = None
return jedi.Script(source, path=buf_path, project=get_project())
def get_pos(column=None):
row = vim.current.window.cursor[0] row = vim.current.window.cursor[0]
if column is None: if column is None:
column = vim.current.window.cursor[1] column = vim.current.window.cursor[1]
buf_path = vim.current.buffer.name return row, column
return jedi.Script(
source, row, column, buf_path,
encoding=vim_eval('&encoding') or 'latin1',
environment=get_environment(),
)
@_check_jedi_availability(show_error=False) @_check_jedi_availability(show_error=False)
@@ -303,9 +339,9 @@ def completions():
# here again hacks, because jedi has a different interface than vim # here again hacks, because jedi has a different interface than vim
column += len(base) column += len(base)
try: try:
script = get_script(source=source, column=column) script = get_script(source=source)
completions = script.completions() completions = script.complete(*get_pos(column))
signatures = script.call_signatures() signatures = script.get_signatures(*get_pos(column))
add_info = "preview" in vim.eval("&completeopt").split(",") add_info = "preview" in vim.eval("&completeopt").split(",")
out = [] out = []
@@ -354,59 +390,64 @@ def tempfile(content):
def goto(mode="goto"): def goto(mode="goto"):
""" """
:param str mode: "definition", "assignment", "goto" :param str mode: "definition", "assignment", "goto"
:return: list of definitions/assignments :rtype: list of jedi.api.classes.Name
:rtype: list
""" """
script = get_script() script = get_script()
pos = get_pos()
if mode == "goto": if mode == "goto":
definitions = script.goto_assignments(follow_imports=True) names = script.goto(*pos, follow_imports=True)
elif mode == "definition": elif mode == "definition":
definitions = script.goto_definitions() names = script.infer(*pos)
elif mode == "assignment": elif mode == "assignment":
definitions = script.goto_assignments() names = script.goto(*pos)
elif mode == "stubs": elif mode == "stubs":
definitions = script.goto_assignments(follow_imports=True, only_stubs=True) names = script.goto(*pos, follow_imports=True, only_stubs=True)
if not definitions: if not names:
echo_highlight("Couldn't find any definitions for this.") echo_highlight("Couldn't find any definitions for this.")
elif len(definitions) == 1 and mode != "related_name": elif len(names) == 1 and mode != "related_name":
d = list(definitions)[0] n = list(names)[0]
if d.column is None: _goto_specific_name(n)
if d.is_keyword:
echo_highlight("Cannot get the definition of Python keywords.")
else:
echo_highlight("Builtin modules cannot be displayed (%s)."
% d.desc_with_module)
else:
using_tagstack = int(vim_eval('g:jedi#use_tag_stack')) == 1
if (d.module_path or '') != vim.current.buffer.name:
result = new_buffer(d.module_path,
using_tagstack=using_tagstack)
if not result:
return []
if (using_tagstack and d.module_path and
os.path.exists(d.module_path)):
tagname = d.name
with tempfile('{0}\t{1}\t{2}'.format(
tagname, d.module_path, 'call cursor({0}, {1})'.format(
d.line, d.column + 1))) as f:
old_tags = vim.eval('&tags')
old_wildignore = vim.eval('&wildignore')
try:
# Clear wildignore to ensure tag file isn't ignored
vim.command('set wildignore=')
vim.command('let &tags = %s' %
repr(PythonToVimStr(f.name)))
vim.command('tjump %s' % tagname)
finally:
vim.command('let &tags = %s' %
repr(PythonToVimStr(old_tags)))
vim.command('let &wildignore = %s' %
repr(PythonToVimStr(old_wildignore)))
vim.current.window.cursor = d.line, d.column
else: else:
show_goto_multi_results(definitions, mode) show_goto_multi_results(names, mode)
return definitions return names
def _goto_specific_name(n, options=''):
if n.column is None:
if n.is_keyword:
echo_highlight("Cannot get the definition of Python keywords.")
else:
echo_highlight("Builtin modules cannot be displayed (%s)."
% (n.full_name or n.name))
else:
using_tagstack = int(vim_eval('g:jedi#use_tag_stack')) == 1
module_path = str(n.module_path or '')
if module_path != vim.current.buffer.name:
result = new_buffer(module_path, options=options,
using_tagstack=using_tagstack)
if not result:
return []
if (using_tagstack and module_path and
os.path.exists(module_path)):
tagname = n.name
with tempfile('{0}\t{1}\t{2}'.format(
tagname, module_path, 'call cursor({0}, {1})'.format(
n.line, n.column + 1))) as f:
old_tags = vim.eval('&tags')
old_wildignore = vim.eval('&wildignore')
try:
# Clear wildignore to ensure tag file isn't ignored
vim.command('set wildignore=')
vim.command('let &tags = %s' %
repr(PythonToVimStr(f.name)))
vim.command('tjump %s' % tagname)
finally:
vim.command('let &tags = %s' %
repr(PythonToVimStr(old_tags)))
vim.command('let &wildignore = %s' %
repr(PythonToVimStr(old_wildignore)))
vim.current.window.cursor = n.line, n.column
def relpath(path): def relpath(path):
@@ -417,45 +458,45 @@ def relpath(path):
return path return path
def annotate_description(d): def annotate_description(n):
code = d.get_line_code().strip() code = n.get_line_code().strip()
if d.type == 'statement': if n.type == 'statement':
return code return code
if d.type == 'function': if n.type == 'function':
if code.startswith('def'): if code.startswith('def'):
return code return code
typ = 'def' typ = 'def'
else: else:
typ = d.type typ = n.type
return '[%s] %s' % (typ, code) return '[%s] %s' % (typ, code)
def show_goto_multi_results(definitions, mode): def show_goto_multi_results(names, mode):
"""Create (or reuse) a quickfix list for multiple definitions.""" """Create (or reuse) a quickfix list for multiple names."""
global _current_definitions global _current_names
lst = [] lst = []
(row, col) = vim.current.window.cursor (row, col) = vim.current.window.cursor
current_idx = None current_idx = None
current_def = None current_def = None
for d in definitions: for n in names:
if d.column is None: if n.column is None:
# Typically a namespace, in the future maybe other things as # Typically a namespace, in the future maybe other things as
# well. # well.
lst.append(dict(text=PythonToVimStr(d.description))) lst.append(dict(text=PythonToVimStr(n.description)))
else: else:
text = annotate_description(d) text = annotate_description(n)
lst.append(dict(filename=PythonToVimStr(relpath(d.module_path)), lst.append(dict(filename=PythonToVimStr(relpath(str(n.module_path))),
lnum=d.line, col=d.column + 1, lnum=n.line, col=n.column + 1,
text=PythonToVimStr(text))) text=PythonToVimStr(text)))
# Select current/nearest entry via :cc later. # Select current/nearest entry via :cc later.
if d.line == row and d.column <= col: if n.line == row and n.column <= col:
if (current_idx is None if (current_idx is None
or (abs(lst[current_idx]["col"] - col) or (abs(lst[current_idx]["col"] - col)
> abs(d.column - col))): > abs(n.column - col))):
current_idx = len(lst) current_idx = len(lst)
current_def = d current_def = n
# Build qflist title. # Build qflist title.
qf_title = mode qf_title = mode
@@ -468,8 +509,8 @@ def show_goto_multi_results(definitions, mode):
else: else:
select_entry = 0 select_entry = 0
qf_context = id(definitions) qf_context = id(names)
if (_current_definitions if (_current_names
and VimCompat.can_update_current_qflist_for_context(qf_context)): and VimCompat.can_update_current_qflist_for_context(qf_context)):
# Same list, only adjust title/selected entry. # Same list, only adjust title/selected entry.
VimCompat.setqflist_title(qf_title) VimCompat.setqflist_title(qf_title)
@@ -481,7 +522,7 @@ def show_goto_multi_results(definitions, mode):
vim_command('%d' % select_entry) vim_command('%d' % select_entry)
def _same_definitions(a, b): def _same_names(a, b):
"""Compare without _inference_state. """Compare without _inference_state.
Ref: https://github.com/davidhalter/jedi-vim/issues/952) Ref: https://github.com/davidhalter/jedi-vim/issues/952)
@@ -497,35 +538,35 @@ def _same_definitions(a, b):
@catch_and_print_exceptions @catch_and_print_exceptions
def usages(visuals=True): def usages(visuals=True):
script = get_script() script = get_script()
definitions = script.usages() names = script.get_references(*get_pos())
if not definitions: if not names:
echo_highlight("No usages found here.") echo_highlight("No usages found here.")
return definitions return names
if visuals: if visuals:
global _current_definitions global _current_names
if _current_definitions: if _current_names:
if _same_definitions(_current_definitions, definitions): if _same_names(_current_names, names):
definitions = _current_definitions names = _current_names
else: else:
clear_usages() clear_usages()
assert not _current_definitions assert not _current_names
show_goto_multi_results(definitions, "usages") show_goto_multi_results(names, "usages")
if not _current_definitions: if not _current_names:
_current_definitions = definitions _current_names = names
highlight_usages() highlight_usages()
else: else:
assert definitions is _current_definitions # updated above assert names is _current_names # updated above
return definitions return names
_current_definitions = None _current_names = None
"""Current definitions to use for highlighting.""" """Current definitions to use for highlighting."""
_pending_definitions = {} _pending_names = {}
"""Pending definitions for unloaded buffers.""" """Pending definitions for unloaded buffers."""
_placed_definitions_in_buffers = set() _placed_names_in_buffers = set()
"""Set of buffers for faster cleanup.""" """Set of buffers for faster cleanup."""
@@ -544,19 +585,19 @@ else:
def clear_usages(): def clear_usages():
"""Clear existing highlights.""" """Clear existing highlights."""
global _current_definitions global _current_names
if _current_definitions is None: if _current_names is None:
return return
_current_definitions = None _current_names = None
if IS_NVIM: if IS_NVIM:
for buf in _placed_definitions_in_buffers: for buf in _placed_names_in_buffers:
src_ids = buf.vars.get('_jedi_usages_src_ids') src_ids = buf.vars.get('_jedi_usages_src_ids')
if src_ids is not None: if src_ids is not None:
for src_id in src_ids: for src_id in src_ids:
buf.clear_highlight(src_id) buf.clear_highlight(src_id)
elif vim_prop_add: elif vim_prop_add:
for buf in _placed_definitions_in_buffers: for buf in _placed_names_in_buffers:
vim_prop_remove({ vim_prop_remove({
'type': 'jediUsage', 'type': 'jediUsage',
'all': 1, 'all': 1,
@@ -564,14 +605,14 @@ def clear_usages():
}) })
else: else:
# Unset current window only. # Unset current window only.
assert _current_definitions is None assert _current_names is None
highlight_usages_for_vim_win() highlight_usages_for_vim_win()
_placed_definitions_in_buffers.clear() _placed_names_in_buffers.clear()
def highlight_usages(): def highlight_usages():
"""Set definitions to be highlighted. """Set usage names to be highlighted.
With Neovim it will use the nvim_buf_add_highlight API to highlight all With Neovim it will use the nvim_buf_add_highlight API to highlight all
buffers already. buffers already.
@@ -580,41 +621,41 @@ def highlight_usages():
highlighted via matchaddpos, and autocommands are setup to highlight other highlighted via matchaddpos, and autocommands are setup to highlight other
windows on demand. Otherwise Vim's text-properties are used. windows on demand. Otherwise Vim's text-properties are used.
""" """
global _current_definitions, _pending_definitions global _current_names, _pending_names
definitions = _current_definitions names = _current_names
_pending_definitions = {} _pending_names = {}
if IS_NVIM or vim_prop_add: if IS_NVIM or vim_prop_add:
bufs = {x.name: x for x in vim.buffers} bufs = {x.name: x for x in vim.buffers}
defs_per_buf = {} defs_per_buf = {}
for definition in definitions: for name in names:
try: try:
buf = bufs[definition.module_path] buf = bufs[str(name.module_path)]
except KeyError: except KeyError:
continue continue
defs_per_buf.setdefault(buf, []).append(definition) defs_per_buf.setdefault(buf, []).append(name)
if IS_NVIM: if IS_NVIM:
# We need to remember highlight ids with Neovim's API. # We need to remember highlight ids with Neovim's API.
buf_src_ids = {} buf_src_ids = {}
for buf, definitions in defs_per_buf.items(): for buf, names in defs_per_buf.items():
buf_src_ids[buf] = [] buf_src_ids[buf] = []
for definition in definitions: for name in names:
src_id = _add_highlight_definition(buf, definition) src_id = _add_highlighted_name(buf, name)
buf_src_ids[buf].append(src_id) buf_src_ids[buf].append(src_id)
for buf, src_ids in buf_src_ids.items(): for buf, src_ids in buf_src_ids.items():
buf.vars['_jedi_usages_src_ids'] = src_ids buf.vars['_jedi_usages_src_ids'] = src_ids
else: else:
for buf, definitions in defs_per_buf.items(): for buf, names in defs_per_buf.items():
try: try:
for definition in definitions: for name in names:
_add_highlight_definition(buf, definition) _add_highlighted_name(buf, name)
except vim.error as exc: except vim.error as exc:
if exc.args[0].startswith('Vim:E275:'): if exc.args[0].startswith('Vim:E275:'):
# "Cannot add text property to unloaded buffer" # "Cannot add text property to unloaded buffer"
_pending_definitions.setdefault(buf.name, []).extend( _pending_names.setdefault(buf.name, []).extend(
definitions) names)
else: else:
highlight_usages_for_vim_win() highlight_usages_for_vim_win()
@@ -624,29 +665,29 @@ def _handle_pending_usages_for_buf():
buf = vim.current.buffer buf = vim.current.buffer
bufname = buf.name bufname = buf.name
try: try:
buf_defs = _pending_definitions[bufname] buf_names = _pending_names[bufname]
except KeyError: except KeyError:
return return
for definition in buf_defs: for name in buf_names:
_add_highlight_definition(buf, definition) _add_highlighted_name(buf, name)
del _pending_definitions[bufname] del _pending_names[bufname]
def _add_highlight_definition(buf, definition): def _add_highlighted_name(buf, name):
lnum = definition.line lnum = name.line
start_col = definition.column start_col = name.column
# Skip highlighting of module definitions that point to the start # Skip highlighting of module definitions that point to the start
# of the file. # of the file.
if definition.type == 'module' and lnum == 1 and start_col == 0: if name.type == 'module' and lnum == 1 and start_col == 0:
return return
_placed_definitions_in_buffers.add(buf) _placed_names_in_buffers.add(buf)
# TODO: validate that definition.name is at this position? # TODO: validate that name.name is at this position?
# Would skip the module definitions from above already. # Would skip the module definitions from above already.
length = len(definition.name) length = len(name.name)
if vim_prop_add: if vim_prop_add:
# XXX: needs jediUsage highlight (via after/syntax/python.vim). # XXX: needs jediUsage highlight (via after/syntax/python.vim).
global vim_prop_type_added global vim_prop_type_added
@@ -661,7 +702,7 @@ def _add_highlight_definition(buf, definition):
return return
assert IS_NVIM assert IS_NVIM
end_col = definition.column + length end_col = name.column + length
src_id = buf.add_highlight('jediUsage', lnum-1, start_col, end_col, src_id = buf.add_highlight('jediUsage', lnum-1, start_col, end_col,
src_id=0) src_id=0)
return src_id return src_id
@@ -674,9 +715,6 @@ def highlight_usages_for_vim_win():
(matchaddpos() only works for the current window.) (matchaddpos() only works for the current window.)
""" """
global _current_definitions
definitions = _current_definitions
win = vim.current.window win = vim.current.window
cur_matchids = win.vars.get('_jedi_usages_vim_matchids') cur_matchids = win.vars.get('_jedi_usages_vim_matchids')
@@ -690,14 +728,14 @@ def highlight_usages_for_vim_win():
vim.eval(expr) vim.eval(expr)
matchids = [] matchids = []
if definitions: if _current_names:
buffer_path = vim.current.buffer.name buffer_path = vim.current.buffer.name
for definition in definitions: for name in _current_names:
if (definition.module_path or '') == buffer_path: if (str(name.module_path) or '') == buffer_path:
positions = [ positions = [
[definition.line, [name.line,
definition.column + 1, name.column + 1,
len(definition.name)] len(name.name)]
] ]
expr = "matchaddpos('jediUsage', %s)" % repr(positions) expr = "matchaddpos('jediUsage', %s)" % repr(positions)
matchids.append(int(vim_eval(expr))) matchids.append(int(vim_eval(expr)))
@@ -719,27 +757,27 @@ def highlight_usages_for_vim_win():
def show_documentation(): def show_documentation():
script = get_script() script = get_script()
try: try:
definitions = script.goto_definitions() names = script.help(*get_pos())
except Exception: except Exception:
# print to stdout, will be in :messages # print to stdout, will be in :messages
definitions = [] names = []
print("Exception, this shouldn't happen.") print("Exception, this shouldn't happen.")
print(traceback.format_exc()) print(traceback.format_exc())
if not definitions: if not names:
echo_highlight('No documentation found for that.') echo_highlight('No documentation found for that.')
vim.command('return') vim.command('return')
return return
docs = [] docs = []
for d in definitions: for n in names:
doc = d.docstring() doc = n.docstring()
if doc: if doc:
title = 'Docstring for %s' % d.desc_with_module title = 'Docstring for %s %s' % (n.type, n.full_name or n.name)
underline = '=' * len(title) underline = '=' * len(title)
docs.append('%s\n%s\n%s' % (title, underline, doc)) docs.append('%s\n%s\n%s' % (title, underline, doc))
else: else:
docs.append('|No Docstring for %s|' % d) docs.append('|No Docstring for %s|' % n)
text = ('\n' + '-' * 79 + '\n').join(docs) text = ('\n' + '-' * 79 + '\n').join(docs)
vim.command('let l:doc = %s' % repr(PythonToVimStr(text))) vim.command('let l:doc = %s' % repr(PythonToVimStr(text)))
vim.command('let l:doc_lines = %s' % len(text.split('\n'))) vim.command('let l:doc_lines = %s' % len(text.split('\n')))
@@ -783,7 +821,7 @@ def show_call_signatures(signatures=()):
# buffer. # buffer.
clear_call_signatures() clear_call_signatures()
if signatures == (): if signatures == ():
signatures = get_script().call_signatures() signatures = get_script().get_signatures(*get_pos())
if not signatures: if not signatures:
return return
@@ -1006,18 +1044,19 @@ def do_rename(replace, orig=None):
# Sort the whole thing reverse (positions at the end of the line # Sort the whole thing reverse (positions at the end of the line
# must be first, because they move the stuff before the position). # must be first, because they move the stuff before the position).
temp_rename = sorted(temp_rename, reverse=True, temp_rename = sorted(temp_rename, reverse=True,
key=lambda x: (x.module_path, x.line, x.column)) key=lambda x: (str(x.module_path), x.line, x.column))
buffers = set() buffers = set()
for r in temp_rename: for r in temp_rename:
if r.in_builtin_module(): if r.in_builtin_module():
continue continue
if os.path.abspath(vim.current.buffer.name) != r.module_path: module_path = r.module_path
assert r.module_path is not None if os.path.abspath(vim.current.buffer.name) != str(module_path):
result = new_buffer(r.module_path) assert module_path is not None
result = new_buffer(module_path)
if not result: if not result:
echo_highlight('Failed to create buffer window for %s!' % ( echo_highlight('Failed to create buffer window for %s!' % (
r.module_path)) module_path))
continue continue
buffers.add(vim.current.buffer.name) buffers.add(vim.current.buffer.name)
@@ -1041,35 +1080,25 @@ def do_rename(replace, orig=None):
@_check_jedi_availability(show_error=True) @_check_jedi_availability(show_error=True)
@catch_and_print_exceptions @catch_and_print_exceptions
def py_import(): def py_import():
# args are the same as for the :edit command
args = shsplit(vim.eval('a:args')) args = shsplit(vim.eval('a:args'))
import_path = args.pop() import_path = args.pop()
text = 'import %s' % import_path name = next(get_project().search(import_path), None)
scr = jedi.Script(text, 1, len(text), '', environment=get_environment()) if name is None:
try: echo_highlight('Cannot find %s in your project or on sys.path!' % import_path)
completion = scr.goto_assignments()[0]
except IndexError:
echo_highlight('Cannot find %s in sys.path!' % import_path)
else: else:
if completion.column is None: # Python modules always have a line number. cmd_args = ' '.join([a.replace(' ', '\\ ') for a in args])
echo_highlight('%s is a builtin module.' % import_path) _goto_specific_name(name, options=cmd_args)
else:
cmd_args = ' '.join([a.replace(' ', '\\ ') for a in args])
new_buffer(completion.module_path, cmd_args)
@catch_and_print_exceptions @catch_and_print_exceptions
def py_import_completions(): def py_import_completions():
argl = vim.eval('a:argl') argl = vim.eval('a:argl')
try: if jedi is None:
import jedi
except ImportError:
print('Pyimport completion requires jedi module: https://github.com/davidhalter/jedi') print('Pyimport completion requires jedi module: https://github.com/davidhalter/jedi')
comps = [] comps = []
else: else:
text = 'import %s' % argl names = get_project().complete_search(argl)
script = jedi.Script(text, 1, len(text), '', environment=get_environment()) comps = [argl + n for n in sorted(set(c.complete for c in names))]
comps = ['%s%s' % (argl, c.complete) for c in script.completions()]
vim.command("return '%s'" % '\n'.join(comps)) vim.command("return '%s'" % '\n'.join(comps))

View File

@@ -2,7 +2,7 @@
import sys import sys
import vim import vim
from jedi_vim import PythonToVimStr from jedi_vim import PythonToVimStr, jedi
def echo(msg): def echo(msg):
@@ -32,6 +32,13 @@ def format_exc_info(exc_info=None, tb_indent=2):
return '{0}'.format(('\n' + indent).join(lines)) return '{0}'.format(('\n' + indent).join(lines))
def get_known_environments():
"""Get known Jedi environments."""
envs = list(jedi.find_virtualenvs())
envs.extend(jedi.find_system_environments())
return envs
def display_debug_info(): def display_debug_info():
echo(' - global sys.executable: `{0}`'.format(sys.executable)) echo(' - global sys.executable: `{0}`'.format(sys.executable))
echo(' - global sys.version: `{0}`'.format( echo(' - global sys.version: `{0}`'.format(
@@ -58,7 +65,8 @@ def display_debug_info():
echo(' - version: {0}'.format(jedi_vim.jedi.__version__)) echo(' - version: {0}'.format(jedi_vim.jedi.__version__))
try: try:
environment = jedi_vim.get_environment(use_cache=False) project = jedi_vim.get_project()
environment = project.get_environment()
except AttributeError: except AttributeError:
script_evaluator = jedi_vim.jedi.Script('')._evaluator script_evaluator = jedi_vim.jedi.Script('')._evaluator
try: try:
@@ -81,7 +89,7 @@ def display_debug_info():
if environment: if environment:
echo('\n##### Known environments\n\n') echo('\n##### Known environments\n\n')
for environment in jedi_vim.get_known_environments(): for environment in get_known_environments():
echo(' - {0} ({1})\n'.format( echo(' - {0} ({1})\n'.format(
environment, environment,
environment.executable, environment.executable,

View File

@@ -0,0 +1,29 @@
source plugin/jedi.vim
source test/_utils.vim
describe 'simple:'
before
new
normal! ifoo
end
after
bd!
end
it 'choose'
Expect g:jedi#environment_path == 'auto'
Expect bufname('%') == ''
JediChooseEnvironment
" A Python executable needs to be a few letters
Expect len(getline('.')) > 5
Expect bufname('%') == 'Hit Enter to Choose an Environment'
execute "normal \<CR>"
Expect g:jedi#environment_path != 'auto'
bd " TODO why is this necessary? There seems to be a random buffer.
Expect bufname('%') == ''
Expect getline('.') == 'foo'
end
end

View File

@@ -18,10 +18,10 @@ describe 'documentation docstrings'
let header = getline(1, 2) let header = getline(1, 2)
PythonJedi vim.vars["is_py2"] = sys.version_info[0] == 2 PythonJedi vim.vars["is_py2"] = sys.version_info[0] == 2
if g:is_py2 if g:is_py2
Expect header[0] == "Docstring for __builtin__:class ImportError" Expect header[0] == "Docstring for class __builtin__.ImportError"
Expect header[1] == "===========================================" Expect header[1] == "==========================================="
else else
Expect header[0] == "Docstring for builtins:class ImportError" Expect header[0] == "Docstring for class builtins.ImportError"
Expect header[1] == "========================================" Expect header[1] == "========================================"
endif endif
let content = join(getline(3, '$'), "\n") let content = join(getline(3, '$'), "\n")

View File

@@ -73,27 +73,6 @@ describe 'goto with tabs:'
Expect tabpagenr('$') == 1 Expect tabpagenr('$') == 1
Expect bufname('%') == '' Expect bufname('%') == ''
end end
" it 'multi definitions'
" " This used to behave differently. Now we don't have any real multi
" " definitions.
"
" " put = ['import tokenize']
" " normal G$\d
" " Expect CurrentBufferIsModule('tokenize') == 1
" " Expect CurrentBufferIsModule('token') == 0
" " execute "normal \<CR>"
" " Expect tabpagenr('$') == 2
" " Expect winnr('$') == 1
" " Expect CurrentBufferIsModule('token') == 1
"
" " bd
" " normal G$\d
" " execute "normal j\<CR>"
" " Expect tabpagenr('$') == 2
" " Expect winnr('$') == 1
" " Expect CurrentBufferIsModule('tokenize') == 1
" end
end end
@@ -124,25 +103,6 @@ describe 'goto with buffers'
Expect line('.') == 1 Expect line('.') == 1
Expect col('.') == 1 Expect col('.') == 1
end end
" it 'multi definitions'
" " set hidden
" " put = ['import tokenize']
" " normal G$\d
" " Expect CurrentBufferIsModule('tokenize') == 0
" " Expect CurrentBufferIsModule('token') == 0
" " execute "normal \<CR>"
" " Expect tabpagenr('$') == 1
" " Expect winnr('$') == 1
" " Expect CurrentBufferIsModule('token') == 1
"
" " bd
" " normal G$\d
" " execute "normal j\<CR>"
" " Expect tabpagenr('$') == 1
" " Expect winnr('$') == 1
" " Expect CurrentBufferIsModule('tokenize') == 1
" end
end end

View File

@@ -4,10 +4,12 @@ source test/_utils.vim
describe 'pyimport' describe 'pyimport'
before before
let g:jedi#use_tabs_not_buffers = 1 let g:jedi#use_tabs_not_buffers = 1
let g:jedi#project_path = 'autoload'
end end
after after
try | %bwipeout! | catch | endtry try | %bwipeout! | catch | endtry
unlet g:jedi#project_path
end end
it 'open_tab' it 'open_tab'