mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 14:04:26 +08:00
Merge branch 'interpreter-api' into dev
Conflicts: jedi/__init__.py test/test_regression.py See: #145
This commit is contained in:
@@ -266,6 +266,7 @@ todo_include_todos = False
|
|||||||
|
|
||||||
# -- Options for autodoc module ------------------------------------------------
|
# -- Options for autodoc module ------------------------------------------------
|
||||||
|
|
||||||
|
autoclass_content = 'both'
|
||||||
autodoc_member_order = 'bysource'
|
autodoc_member_order = 'bysource'
|
||||||
autodoc_default_flags = []
|
autodoc_default_flags = []
|
||||||
#autodoc_default_flags = ['members', 'undoc-members']
|
#autodoc_default_flags = ['members', 'undoc-members']
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import sys
|
|||||||
# imports and circular imports... Just avoid it:
|
# imports and circular imports... Just avoid it:
|
||||||
sys.path.insert(0, __path__[0])
|
sys.path.insert(0, __path__[0])
|
||||||
|
|
||||||
from .api import Script, NotFoundError, set_debug_function, _quick_complete, \
|
from .api import Script, Interpreter, NotFoundError, set_debug_function, \
|
||||||
preload_module
|
preload_module
|
||||||
from . import settings
|
from . import settings
|
||||||
|
|
||||||
|
|||||||
2
jedi/__main__.py
Normal file
2
jedi/__main__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from os import path
|
||||||
|
print(path.join(path.dirname(path.abspath(__file__)), 'replstartup.py'))
|
||||||
74
jedi/api.py
74
jedi/api.py
@@ -20,6 +20,7 @@ from jedi import helpers
|
|||||||
from jedi import common
|
from jedi import common
|
||||||
from jedi import cache
|
from jedi import cache
|
||||||
from jedi import modules
|
from jedi import modules
|
||||||
|
from jedi import interpret
|
||||||
from jedi._compatibility import next, unicode
|
from jedi._compatibility import next, unicode
|
||||||
import evaluate
|
import evaluate
|
||||||
import keywords
|
import keywords
|
||||||
@@ -53,14 +54,18 @@ class Script(object):
|
|||||||
``unicode`` object (default ``'utf-8'``).
|
``unicode`` object (default ``'utf-8'``).
|
||||||
:type source_encoding: str
|
:type source_encoding: str
|
||||||
"""
|
"""
|
||||||
def __init__(self, source, line, column, source_path,
|
def __init__(self, source, line=None, column=None, source_path=None,
|
||||||
source_encoding='utf-8'):
|
source_encoding='utf-8'):
|
||||||
|
lines = source.splitlines()
|
||||||
|
line = len(lines) if line is None else line
|
||||||
|
column = len(lines[-1]) if column is None else column
|
||||||
|
|
||||||
api_classes._clear_caches()
|
api_classes._clear_caches()
|
||||||
debug.reset_time()
|
debug.reset_time()
|
||||||
self.source = modules.source_to_unicode(source, source_encoding)
|
self.source = modules.source_to_unicode(source, source_encoding)
|
||||||
self.pos = line, column
|
self.pos = line, column
|
||||||
self._module = modules.ModuleWithCursor(source_path,
|
self._module = modules.ModuleWithCursor(
|
||||||
source=self.source, position=self.pos)
|
source_path, source=self.source, position=self.pos)
|
||||||
self._source_path = source_path
|
self._source_path = source_path
|
||||||
self.source_path = None if source_path is None \
|
self.source_path = None if source_path is None \
|
||||||
else os.path.abspath(source_path)
|
else os.path.abspath(source_path)
|
||||||
@@ -503,6 +508,45 @@ class Script(object):
|
|||||||
return sorted(d, key=lambda x: (x.module_path or '', x.start_pos))
|
return sorted(d, key=lambda x: (x.module_path or '', x.start_pos))
|
||||||
|
|
||||||
|
|
||||||
|
class Interpreter(Script):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Jedi API for Python REPLs.
|
||||||
|
|
||||||
|
In addition to completion of simple attribute access, Jedi
|
||||||
|
supports code completion based on static code analysis.
|
||||||
|
Jedi can complete attributes of object which is not initialized
|
||||||
|
yet.
|
||||||
|
|
||||||
|
>>> from os.path import join
|
||||||
|
>>> namespace = locals()
|
||||||
|
>>> script = Interpreter('join().up', [namespace])
|
||||||
|
>>> print(script.complete()[0].word)
|
||||||
|
upper
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, source, namespaces=[], **kwds):
|
||||||
|
"""
|
||||||
|
Parse `source` and mixin interpreted Python objects from `namespaces`.
|
||||||
|
|
||||||
|
:type source: str
|
||||||
|
:arg source: Code to parse.
|
||||||
|
:type namespaces: list of dict
|
||||||
|
:arg namespaces: a list of namespace dictionaries such as the one
|
||||||
|
returned by :func:`locals`.
|
||||||
|
|
||||||
|
Other optional arguments are same as the ones for :class:`Script`.
|
||||||
|
If `line` and `column` are None, they are assumed be at the end of
|
||||||
|
`source`.
|
||||||
|
"""
|
||||||
|
super(Interpreter, self).__init__(source, **kwds)
|
||||||
|
|
||||||
|
importer = interpret.ObjectImporter(self._parser.user_scope)
|
||||||
|
for ns in namespaces:
|
||||||
|
importer.import_raw_namespace(ns)
|
||||||
|
|
||||||
|
|
||||||
def defined_names(source, source_path=None, source_encoding='utf-8'):
|
def defined_names(source, source_path=None, source_encoding='utf-8'):
|
||||||
"""
|
"""
|
||||||
Get all definitions in `source` sorted by its position.
|
Get all definitions in `source` sorted by its position.
|
||||||
@@ -545,25 +589,3 @@ def set_debug_function(func_cb=debug.print_to_stdout, warnings=True,
|
|||||||
debug.enable_warning = warnings
|
debug.enable_warning = warnings
|
||||||
debug.enable_notice = notices
|
debug.enable_notice = notices
|
||||||
debug.enable_speed = speed
|
debug.enable_speed = speed
|
||||||
|
|
||||||
|
|
||||||
def _quick_complete(source):
|
|
||||||
"""
|
|
||||||
Convenience function to complete a source string at the end.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
>>> _quick_complete('''
|
|
||||||
... import datetime
|
|
||||||
... datetime.da''') #doctest: +ELLIPSIS
|
|
||||||
[<Completion: date>, <Completion: datetime>, ...]
|
|
||||||
|
|
||||||
:param source: The source code to be completed.
|
|
||||||
:type source: string
|
|
||||||
:return: Completion objects as returned by :meth:`complete`.
|
|
||||||
:rtype: list of :class:`api_classes.Completion`
|
|
||||||
"""
|
|
||||||
lines = re.sub(r'[\n\r\s]*$', '', source).splitlines()
|
|
||||||
pos = len(lines), len(lines[-1])
|
|
||||||
script = Script(source, pos[0], pos[1], '')
|
|
||||||
return script.completions()
|
|
||||||
|
|||||||
171
jedi/interpret.py
Normal file
171
jedi/interpret.py
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
"""
|
||||||
|
Module to handle interpreted Python objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import tokenize
|
||||||
|
|
||||||
|
from jedi import parsing_representation as pr
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectImporter(object):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Import objects in "raw" namespace such as :func:`locals`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, scope):
|
||||||
|
self.scope = scope
|
||||||
|
|
||||||
|
count = itertools.count()
|
||||||
|
self._genname = lambda: '*jedi-%s*' % next(count)
|
||||||
|
"""
|
||||||
|
Generate unique variable names to avoid name collision.
|
||||||
|
To avoid name collision to already defined names, generated
|
||||||
|
names are invalid as Python identifier.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def import_raw_namespace(self, raw_namespace):
|
||||||
|
"""
|
||||||
|
Import interpreted Python objects in a namespace.
|
||||||
|
|
||||||
|
Three kinds of objects are treated here.
|
||||||
|
|
||||||
|
1. Functions and classes. The objects imported like this::
|
||||||
|
|
||||||
|
from os.path import join
|
||||||
|
|
||||||
|
2. Modules. The objects imported like this::
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
3. Instances. The objects created like this::
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
dt = datetime(2013, 1, 1)
|
||||||
|
|
||||||
|
:type raw_namespace: dict
|
||||||
|
:arg raw_namespace: e.g., the dict given by `locals`
|
||||||
|
"""
|
||||||
|
scope = self.scope
|
||||||
|
for (variable, obj) in raw_namespace.items():
|
||||||
|
objname = getattr(obj, '__name__', None)
|
||||||
|
|
||||||
|
# Import functions and classes
|
||||||
|
module = getattr(obj, '__module__', None)
|
||||||
|
if module and objname:
|
||||||
|
fakeimport = self.make_fakeimport(module, objname, variable)
|
||||||
|
scope.add_import(fakeimport)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Import modules
|
||||||
|
if getattr(obj, '__file__', None) and objname:
|
||||||
|
fakeimport = self.make_fakeimport(objname)
|
||||||
|
scope.add_import(fakeimport)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Import instances
|
||||||
|
objclass = getattr(obj, '__class__', None)
|
||||||
|
module = getattr(objclass, '__module__', None)
|
||||||
|
if objclass and module:
|
||||||
|
alias = self._genname()
|
||||||
|
fakeimport = self.make_fakeimport(module, objclass.__name__,
|
||||||
|
alias)
|
||||||
|
fakestmt = self.make_fakestatement(variable, alias, call=True)
|
||||||
|
scope.add_import(fakeimport)
|
||||||
|
scope.add_statement(fakestmt)
|
||||||
|
continue
|
||||||
|
|
||||||
|
def make_fakeimport(self, module, variable=None, alias=None):
|
||||||
|
"""
|
||||||
|
Make a fake import object.
|
||||||
|
|
||||||
|
The following statements are created depending on what parameters
|
||||||
|
are given:
|
||||||
|
|
||||||
|
- only `module`: ``import <module>``
|
||||||
|
- `module` and `variable`: ``from <module> import <variable>``
|
||||||
|
- all: ``from <module> import <variable> as <alias>``
|
||||||
|
|
||||||
|
:type module: str
|
||||||
|
:arg module: ``<module>`` part in ``from <module> import ...``
|
||||||
|
:type variable: str
|
||||||
|
:arg variable: ``<variable>`` part in ``from ... import <variable>``
|
||||||
|
:type alias: str
|
||||||
|
:arg alias: ``<alias>`` part in ``... import ... as <alias>``.
|
||||||
|
|
||||||
|
:rtype: :class:`parsing_representation.Import`
|
||||||
|
"""
|
||||||
|
submodule = self.scope._sub_module
|
||||||
|
if variable:
|
||||||
|
varname = pr.Name(
|
||||||
|
module=submodule,
|
||||||
|
names=[(variable, (-1, 0))],
|
||||||
|
start_pos=(-1, 0),
|
||||||
|
end_pos=(None, None))
|
||||||
|
else:
|
||||||
|
varname = None
|
||||||
|
modname = pr.Name(
|
||||||
|
module=submodule,
|
||||||
|
names=[(module, (-1, 0))],
|
||||||
|
start_pos=(-1, 0),
|
||||||
|
end_pos=(None, None))
|
||||||
|
if alias:
|
||||||
|
aliasname = pr.Name(
|
||||||
|
module=submodule,
|
||||||
|
names=[(alias, (-1, 0))],
|
||||||
|
start_pos=(-1, 0),
|
||||||
|
end_pos=(None, None))
|
||||||
|
else:
|
||||||
|
aliasname = None
|
||||||
|
if varname:
|
||||||
|
fakeimport = pr.Import(
|
||||||
|
module=submodule,
|
||||||
|
namespace=varname,
|
||||||
|
from_ns=modname,
|
||||||
|
alias=aliasname,
|
||||||
|
start_pos=(-1, 0),
|
||||||
|
end_pos=(None, None))
|
||||||
|
else:
|
||||||
|
fakeimport = pr.Import(
|
||||||
|
module=submodule,
|
||||||
|
namespace=modname,
|
||||||
|
alias=aliasname,
|
||||||
|
start_pos=(-1, 0),
|
||||||
|
end_pos=(None, None))
|
||||||
|
return fakeimport
|
||||||
|
|
||||||
|
def make_fakestatement(self, lhs, rhs, call=False):
|
||||||
|
"""
|
||||||
|
Make a fake statement object that represents ``lhs = rhs``.
|
||||||
|
|
||||||
|
:type call: bool
|
||||||
|
:arg call: When `call` is true, make a fake statement that represents
|
||||||
|
``lhs = rhs()``.
|
||||||
|
|
||||||
|
:rtype: :class:`parsing_representation.Statement`
|
||||||
|
"""
|
||||||
|
submodule = self.scope._sub_module
|
||||||
|
lhsname = pr.Name(
|
||||||
|
module=submodule,
|
||||||
|
names=[(lhs, (0, 0))],
|
||||||
|
start_pos=(0, 0),
|
||||||
|
end_pos=(None, None))
|
||||||
|
rhsname = pr.Name(
|
||||||
|
module=submodule,
|
||||||
|
names=[(rhs, (0, 0))],
|
||||||
|
start_pos=(0, 0),
|
||||||
|
end_pos=(None, None))
|
||||||
|
token_list = [lhsname, (tokenize.OP, '=', (0, 0)), rhsname]
|
||||||
|
if call:
|
||||||
|
token_list.extend([
|
||||||
|
(tokenize.OP, '(', (0, 0)),
|
||||||
|
(tokenize.OP, ')', (0, 0)),
|
||||||
|
])
|
||||||
|
return pr.Statement(
|
||||||
|
module=submodule,
|
||||||
|
set_vars=[lhsname],
|
||||||
|
used_vars=[rhsname],
|
||||||
|
token_list=token_list,
|
||||||
|
start_pos=(0, 0),
|
||||||
|
end_pos=(None, None))
|
||||||
25
jedi/replstartup.py
Normal file
25
jedi/replstartup.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
"""
|
||||||
|
``PYTHONSTARTUP`` to use Jedi in your Python interpreter.
|
||||||
|
|
||||||
|
To use Jedi completion in Python interpreter, add the following in your shell
|
||||||
|
setup (e.g., ``.bashrc``)::
|
||||||
|
|
||||||
|
export PYTHONSTARTUP="$(python -m jedi)"
|
||||||
|
|
||||||
|
Then you will be able to use Jedi completer in your Python interpreter::
|
||||||
|
|
||||||
|
$ python
|
||||||
|
Python 2.7.2+ (default, Jul 20 2012, 22:15:08)
|
||||||
|
[GCC 4.6.1] on linux2
|
||||||
|
Type "help", "copyright", "credits" or "license" for more information.
|
||||||
|
>>> import os
|
||||||
|
>>> os.path.join().split().in<TAB> # doctest: +SKIP
|
||||||
|
os.path.join().split().index os.path.join().split().insert
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import jedi.utils
|
||||||
|
jedi.utils.setup_readline()
|
||||||
|
del jedi
|
||||||
|
# Note: try not to do many things here, as it will contaminate global
|
||||||
|
# namespace of the interpreter.
|
||||||
41
jedi/utils.py
Normal file
41
jedi/utils.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
"""
|
||||||
|
Utilities for end-users.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from jedi import Interpreter
|
||||||
|
|
||||||
|
|
||||||
|
def readline_complete(text, state):
|
||||||
|
"""
|
||||||
|
Function to be passed to :func:`readline.set_completer`.
|
||||||
|
|
||||||
|
Usage::
|
||||||
|
|
||||||
|
import readline
|
||||||
|
readline.set_completer(readline_complete)
|
||||||
|
|
||||||
|
"""
|
||||||
|
ns = vars(sys.modules['__main__'])
|
||||||
|
completions = Interpreter(text, [ns]).completions()
|
||||||
|
try:
|
||||||
|
return text + completions[state].complete
|
||||||
|
except IndexError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def setup_readline():
|
||||||
|
"""
|
||||||
|
Install Jedi completer to :mod:`readline`.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import readline
|
||||||
|
except ImportError:
|
||||||
|
print("Module readline not available.")
|
||||||
|
else:
|
||||||
|
readline.set_completer(readline_complete)
|
||||||
|
readline.parse_and_bind("tab: complete")
|
||||||
|
|
||||||
|
# Default delimiters minus "()":
|
||||||
|
readline.set_completer_delims(' \t\n`~!@#$%^&*-=+[{]}\\|;:\'",<>/?')
|
||||||
@@ -14,7 +14,7 @@ import textwrap
|
|||||||
from .base import TestBase, unittest, cwd_at
|
from .base import TestBase, unittest, cwd_at
|
||||||
|
|
||||||
import jedi
|
import jedi
|
||||||
from jedi._compatibility import utf8, unicode
|
from jedi._compatibility import utf8, unicode, is_py33
|
||||||
from jedi import api, parsing, common
|
from jedi import api, parsing, common
|
||||||
api_classes = api.api_classes
|
api_classes = api.api_classes
|
||||||
|
|
||||||
@@ -562,6 +562,44 @@ class TestSpeed(TestBase):
|
|||||||
#print(jedi.imports.imports_processed)
|
#print(jedi.imports.imports_processed)
|
||||||
|
|
||||||
|
|
||||||
|
class TestInterpreterAPI(unittest.TestCase):
|
||||||
|
|
||||||
|
def check_interpreter_complete(self, source, namespace, completions,
|
||||||
|
**kwds):
|
||||||
|
script = api.Interpreter(source, [namespace], **kwds)
|
||||||
|
cs = script.complete()
|
||||||
|
actual = [c.word for c in cs]
|
||||||
|
self.assertEqual(sorted(actual), sorted(completions))
|
||||||
|
|
||||||
|
def test_complete_raw_function(self):
|
||||||
|
from os.path import join
|
||||||
|
self.check_interpreter_complete('join().up',
|
||||||
|
locals(),
|
||||||
|
['upper'])
|
||||||
|
|
||||||
|
def test_complete_raw_function_different_name(self):
|
||||||
|
from os.path import join as pjoin
|
||||||
|
self.check_interpreter_complete('pjoin().up',
|
||||||
|
locals(),
|
||||||
|
['upper'])
|
||||||
|
|
||||||
|
def test_complete_raw_module(self):
|
||||||
|
import os
|
||||||
|
self.check_interpreter_complete('os.path.join().up',
|
||||||
|
locals(),
|
||||||
|
['upper'])
|
||||||
|
|
||||||
|
def test_complete_raw_instance(self):
|
||||||
|
import datetime
|
||||||
|
dt = datetime.datetime(2013, 1, 1)
|
||||||
|
completions = ['time', 'timetz', 'timetuple']
|
||||||
|
if is_py33:
|
||||||
|
completions += ['timestamp']
|
||||||
|
self.check_interpreter_complete('(dt - dt).ti',
|
||||||
|
locals(),
|
||||||
|
completions)
|
||||||
|
|
||||||
|
|
||||||
def test_settings_module():
|
def test_settings_module():
|
||||||
"""
|
"""
|
||||||
jedi.settings and jedi.cache.settings must be the same module.
|
jedi.settings and jedi.cache.settings must be the same module.
|
||||||
|
|||||||
Reference in New Issue
Block a user