From 842fbfce8aec6003387ef2b5bb2c159f5adb40ae Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Mon, 25 Feb 2013 21:55:20 +0100 Subject: [PATCH 01/29] Add api.Interpreter: Jedi API for Python REPLs --- jedi/api.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++--- jedi/modules.py | 6 +++-- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/jedi/api.py b/jedi/api.py index e4f7f532..4c1af950 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -54,13 +54,13 @@ class Script(object): :type source_encoding: str """ def __init__(self, source, line, column, source_path, - source_encoding='utf-8'): + source_encoding='utf-8', fast=True): api_classes._clear_caches() debug.reset_time() self.source = modules.source_to_unicode(source, source_encoding) self.pos = line, column - self._module = modules.ModuleWithCursor(source_path, - source=self.source, position=self.pos) + self._module = modules.ModuleWithCursor( + source_path, source=self.source, position=self.pos, fast=fast) self._source_path = source_path self.source_path = None if source_path is None \ else os.path.abspath(source_path) @@ -503,6 +503,59 @@ class Script(object): return sorted(d, key=lambda x: (x.module_path or '', x.start_pos)) +class Interpreter(Script): + + """ + Jedi API for Python REPLs. + + >>> from itertools import chain + >>> script = Interpreter('cha', [locals()]) + >>> script.complete() + [] + + """ + + def __init__(self, source, namespaces=[], line=None, column=None, + source_path=None, 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 + super(Interpreter, self).__init__( + source, line, column, source_path, source_encoding, fast=False) + for ns in namespaces: + self._import_raw_namespace(ns) + + def _import_raw_namespace(self, raw_namespace): + for (variable, obj) in raw_namespace.items(): + try: + module = obj.__module__ + except: + continue + fakeimport = self._make_fakeimport(variable, module) + self._parser.scope.imports.append(fakeimport) + + def _make_fakeimport(self, variable, module): + submodule = self._parser.scope._sub_module + varname = pr.Name( + module=submodule, + names=[(variable, (0, 0))], + start_pos=(0, 0), + end_pos=(None, None)) + modname = pr.Name( + module=submodule, + names=[(module, (0, 0))], + start_pos=(0, 0), + end_pos=(None, None)) + fakeimport = pr.Import( + module=submodule, + namespace=varname, + from_ns=modname, + start_pos=(0, 0), + end_pos=(None, None)) + fakeimport.parent = submodule + return fakeimport + + def defined_names(source, source_path=None, source_encoding='utf-8'): """ Get all definitions in `source` sorted by its position. diff --git a/jedi/modules.py b/jedi/modules.py index 37e5bbac..035a894e 100644 --- a/jedi/modules.py +++ b/jedi/modules.py @@ -92,8 +92,10 @@ class ModuleWithCursor(Module): :param position: The position, the user is currently in. Only important \ for the main file. """ - def __init__(self, path, source, position): + def __init__(self, path, source, position, fast): super(ModuleWithCursor, self).__init__(path, source) + self._parserclass = fast_parser.FastParser if fast else \ + parsing.Parser self.position = position # this two are only used, because there is no nonlocal in Python 2 @@ -112,7 +114,7 @@ class ModuleWithCursor(Module): # Call the parser already here, because it will be used anyways. # Also, the position is here important (which will not be used by # default), therefore fill the cache here. - self._parser = fast_parser.FastParser(self.source, self.path, + self._parser = self._parserclass(self.source, self.path, self.position) # don't pickle that module, because it's changing fast cache.save_module(self.path, self.name, self._parser, From 3266aa2d00533753cf039098b53af79fe7de69bb Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 2 Mar 2013 19:56:12 +0100 Subject: [PATCH 02/29] Improve api.Interpreter docstring --- jedi/api.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/jedi/api.py b/jedi/api.py index 4c1af950..2703f6e9 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -508,10 +508,16 @@ class Interpreter(Script): """ Jedi API for Python REPLs. - >>> from itertools import chain - >>> script = Interpreter('cha', [locals()]) - >>> script.complete() - [] + 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 """ @@ -529,7 +535,7 @@ class Interpreter(Script): for (variable, obj) in raw_namespace.items(): try: module = obj.__module__ - except: + except AttributeError: continue fakeimport = self._make_fakeimport(variable, module) self._parser.scope.imports.append(fakeimport) From fb197c42abe5e72dccd177ffe2795ac1660f7122 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 2 Mar 2013 20:40:36 +0100 Subject: [PATCH 03/29] Add tests for api.Interpreter (2/3 fail) --- test/test_regression.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/test_regression.py b/test/test_regression.py index d17c2627..41061676 100755 --- a/test/test_regression.py +++ b/test/test_regression.py @@ -539,6 +539,32 @@ class TestSpeed(TestBase): #print(jedi.imports.imports_processed) +class TestInterpreterAPI(unittest.TestCase): + + def check_interpreter_complete(self, source, namespace, completions, + **kwds): + cs = api.Interpreter(source, [namespace], **kwds).complete() + actual = [c.word for c in cs] + self.assertEqual(actual, completions) + + def test_complete_raw_function(self): + self.check_interpreter_complete('join().up', + {'join': os.path.join}, + ['upper']) + + def test_complete_raw_module(self): + self.check_interpreter_complete('os.path.join().up', + {'os': os}, + ['upper']) + + def test_complete_raw_instance(self): + import datetime + dt = datetime.datetime(2013, 1, 1) + self.check_interpreter_complete('dt.strftime("%Y").up', + {'dt': dt}, + ['upper']) + + def test_settings_module(): """ jedi.settings and jedi.cache.settings must be the same module. From 17770356e25e43acb1440ff8214f6a55f3eb186a Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 2 Mar 2013 21:03:53 +0100 Subject: [PATCH 04/29] Implement the case where raw object is a module Previously failing test_complete_raw_module is passed. --- jedi/api.py | 48 +++++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/jedi/api.py b/jedi/api.py index 2703f6e9..151c0ade 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -533,31 +533,45 @@ class Interpreter(Script): def _import_raw_namespace(self, raw_namespace): for (variable, obj) in raw_namespace.items(): - try: - module = obj.__module__ - except AttributeError: + module = getattr(obj, '__module__', None) + if module: + fakeimport = self._make_fakeimport(module, variable) + self._parser.scope.imports.append(fakeimport) continue - fakeimport = self._make_fakeimport(variable, module) - self._parser.scope.imports.append(fakeimport) - def _make_fakeimport(self, variable, module): + if getattr(obj, '__file__', None): + fakeimport = self._make_fakeimport(obj.__name__) + self._parser.scope.imports.append(fakeimport) + continue + + def _make_fakeimport(self, module, variable=None): submodule = self._parser.scope._sub_module - varname = pr.Name( - module=submodule, - names=[(variable, (0, 0))], - start_pos=(0, 0), - end_pos=(None, None)) + if variable: + varname = pr.Name( + module=submodule, + names=[(variable, (0, 0))], + start_pos=(0, 0), + end_pos=(None, None)) + else: + varname = None modname = pr.Name( module=submodule, names=[(module, (0, 0))], start_pos=(0, 0), end_pos=(None, None)) - fakeimport = pr.Import( - module=submodule, - namespace=varname, - from_ns=modname, - start_pos=(0, 0), - end_pos=(None, None)) + if varname: + fakeimport = pr.Import( + module=submodule, + namespace=varname, + from_ns=modname, + start_pos=(0, 0), + end_pos=(None, None)) + else: + fakeimport = pr.Import( + module=submodule, + namespace=modname, + start_pos=(0, 0), + end_pos=(None, None)) fakeimport.parent = submodule return fakeimport From cf48d1272d72b2f87781833cb0eb445afbe7d79b Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sun, 3 Mar 2013 04:42:39 +0100 Subject: [PATCH 05/29] Use proper interface to add fake Import object --- jedi/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jedi/api.py b/jedi/api.py index 151c0ade..612dfa3e 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -532,16 +532,17 @@ class Interpreter(Script): self._import_raw_namespace(ns) def _import_raw_namespace(self, raw_namespace): + scope = self._parser.scope for (variable, obj) in raw_namespace.items(): module = getattr(obj, '__module__', None) if module: fakeimport = self._make_fakeimport(module, variable) - self._parser.scope.imports.append(fakeimport) + scope.add_import(fakeimport) continue if getattr(obj, '__file__', None): fakeimport = self._make_fakeimport(obj.__name__) - self._parser.scope.imports.append(fakeimport) + scope.add_import(fakeimport) continue def _make_fakeimport(self, module, variable=None): @@ -572,7 +573,6 @@ class Interpreter(Script): namespace=modname, start_pos=(0, 0), end_pos=(None, None)) - fakeimport.parent = submodule return fakeimport From 11abd92d2b5d743aa4c6af25473caab7b37fb29e Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sun, 3 Mar 2013 04:49:53 +0100 Subject: [PATCH 06/29] Support alias in _make_fakeimport --- jedi/api.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/jedi/api.py b/jedi/api.py index 612dfa3e..6bbe52d7 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -545,7 +545,7 @@ class Interpreter(Script): scope.add_import(fakeimport) continue - def _make_fakeimport(self, module, variable=None): + def _make_fakeimport(self, module, variable=None, alias=None): submodule = self._parser.scope._sub_module if variable: varname = pr.Name( @@ -560,17 +560,27 @@ class Interpreter(Script): names=[(module, (0, 0))], start_pos=(0, 0), end_pos=(None, None)) + if alias: + aliasname = pr.Name( + module=submodule, + names=[(alias, (0, 0))], + start_pos=(0, 0), + end_pos=(None, None)) + else: + aliasname = None if varname: fakeimport = pr.Import( module=submodule, namespace=varname, from_ns=modname, + alias=aliasname, start_pos=(0, 0), end_pos=(None, None)) else: fakeimport = pr.Import( module=submodule, namespace=modname, + alias=aliasname, start_pos=(0, 0), end_pos=(None, None)) return fakeimport From 06c03cafa21d2204ece43b801a74d9f7c6946396 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sun, 3 Mar 2013 03:18:11 +0100 Subject: [PATCH 07/29] Implement the case where raw object is an instance There is still a bug in this implementation. The corresponding test fails. --- jedi/api.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/jedi/api.py b/jedi/api.py index 6bbe52d7..a5b8bebb 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -11,6 +11,8 @@ from __future__ import with_statement import re import os import warnings +import itertools +import tokenize from jedi import parsing from jedi import parsing_representation as pr @@ -528,6 +530,12 @@ class Interpreter(Script): column = len(lines[-1]) if column is None else column super(Interpreter, self).__init__( source, line, column, source_path, source_encoding, fast=False) + + count = itertools.count() + fmt = '*jedi-{0}*'.format + self._genname = lambda: fmt(next(count)) + """Generate unique variable names to avoid name collision.""" + for ns in namespaces: self._import_raw_namespace(ns) @@ -545,6 +553,17 @@ class Interpreter(Script): scope.add_import(fakeimport) continue + 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): submodule = self._parser.scope._sub_module if variable: @@ -585,6 +604,37 @@ class Interpreter(Script): end_pos=(None, None)) return fakeimport + def _make_fakestatement(self, lhs, rhs, call=False): + """ + Make a fake statement object that represents ``lhs = rhs``. + + :rtype: :class:`parsing_representation.Statement` + """ + submodule = self._parser.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)) + def defined_names(source, source_path=None, source_encoding='utf-8'): """ From 45cad4c83bf925c12c258886444fe7bc5b0390ce Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sun, 3 Mar 2013 04:34:14 +0100 Subject: [PATCH 08/29] Fake imports should locate before fake statements --- jedi/api.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/jedi/api.py b/jedi/api.py index a5b8bebb..44be868f 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -569,21 +569,21 @@ class Interpreter(Script): if variable: varname = pr.Name( module=submodule, - names=[(variable, (0, 0))], - start_pos=(0, 0), + names=[(variable, (-1, 0))], + start_pos=(-1, 0), end_pos=(None, None)) else: varname = None modname = pr.Name( module=submodule, - names=[(module, (0, 0))], - start_pos=(0, 0), + names=[(module, (-1, 0))], + start_pos=(-1, 0), end_pos=(None, None)) if alias: aliasname = pr.Name( module=submodule, - names=[(alias, (0, 0))], - start_pos=(0, 0), + names=[(alias, (-1, 0))], + start_pos=(-1, 0), end_pos=(None, None)) else: aliasname = None @@ -593,14 +593,14 @@ class Interpreter(Script): namespace=varname, from_ns=modname, alias=aliasname, - start_pos=(0, 0), + start_pos=(-1, 0), end_pos=(None, None)) else: fakeimport = pr.Import( module=submodule, namespace=modname, alias=aliasname, - start_pos=(0, 0), + start_pos=(-1, 0), end_pos=(None, None)) return fakeimport From b4e3d1d65dfdd63bfb28d615001a1fc32efc7dc3 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sun, 3 Mar 2013 12:08:59 +0100 Subject: [PATCH 09/29] Fix test_complete_raw_instance example 'dt.strftime("%Y").up' does not work even if with `api.Script`. Change it to the one that can be handled by Jedi. --- test/test_regression.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/test_regression.py b/test/test_regression.py index 41061676..acbfc60e 100755 --- a/test/test_regression.py +++ b/test/test_regression.py @@ -543,9 +543,10 @@ class TestInterpreterAPI(unittest.TestCase): def check_interpreter_complete(self, source, namespace, completions, **kwds): - cs = api.Interpreter(source, [namespace], **kwds).complete() + script = api.Interpreter(source, [namespace], **kwds) + cs = script.complete() actual = [c.word for c in cs] - self.assertEqual(actual, completions) + self.assertEqual(sorted(actual), sorted(completions)) def test_complete_raw_function(self): self.check_interpreter_complete('join().up', @@ -560,9 +561,9 @@ class TestInterpreterAPI(unittest.TestCase): def test_complete_raw_instance(self): import datetime dt = datetime.datetime(2013, 1, 1) - self.check_interpreter_complete('dt.strftime("%Y").up', + self.check_interpreter_complete('(dt - dt).ti', {'dt': dt}, - ['upper']) + ['time', 'timetz', 'timetuple']) def test_settings_module(): From 12ac71b1fddece217d1146d4ac640d76cca50466 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sun, 3 Mar 2013 12:28:08 +0100 Subject: [PATCH 10/29] Document api.Interpreter --- jedi/api.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/jedi/api.py b/jedi/api.py index 44be868f..a1b67a64 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -525,6 +525,16 @@ class Interpreter(Script): def __init__(self, source, namespaces=[], line=None, column=None, source_path=None, source_encoding='utf-8'): + """ + Parse `source` and mixin interpreted Python objects from `namespaces`. + + :type source: str + :arg source: + :type namespaces: list of dict + :arg namespaces: + + Other optional arguments are same as the ones for :class:`Script`. + """ lines = source.splitlines() line = len(lines) if line is None else line column = len(lines[-1]) if column is None else column @@ -540,6 +550,12 @@ class Interpreter(Script): self._import_raw_namespace(ns) def _import_raw_namespace(self, raw_namespace): + """ + Import interpreted Python objects in a namespace. + + :type raw_namespace: dict + :arg raw_namespace: e.g., the dict given by `locals` + """ scope = self._parser.scope for (variable, obj) in raw_namespace.items(): module = getattr(obj, '__module__', None) @@ -565,6 +581,25 @@ class Interpreter(Script): 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` is given: ``import `` + - `module` and `variable` are given: ``from import `` + - all params are given: ``from import as `` + + :type module: str + :arg module: ```` part in ``from import ...`` + :type variable: str + :arg variable: ```` part in ``from ... import `` + :type alias: str + :arg alias: ```` part in ``... import ... as ``. + + :rtype: :class:`parsing_representation.Import` + """ submodule = self._parser.scope._sub_module if variable: varname = pr.Name( @@ -608,6 +643,10 @@ class Interpreter(Script): """ 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._parser.scope._sub_module From 6fb42e5f4bbbabd542e7e54bc4986a4e94aafde9 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sun, 3 Mar 2013 12:37:00 +0100 Subject: [PATCH 11/29] Document _import_raw_namespace more --- jedi/api.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/jedi/api.py b/jedi/api.py index a1b67a64..28ba607c 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -553,22 +553,40 @@ class Interpreter(Script): """ 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._parser.scope for (variable, obj) in raw_namespace.items(): + # Import functions and classes module = getattr(obj, '__module__', None) if module: fakeimport = self._make_fakeimport(module, variable) scope.add_import(fakeimport) continue + # Import modules if getattr(obj, '__file__', None): fakeimport = self._make_fakeimport(obj.__name__) scope.add_import(fakeimport) continue + # Import instances objclass = getattr(obj, '__class__', None) module = getattr(objclass, '__module__', None) if objclass and module: From fc79f0e258d1c8df32d120a4cdb51523e1062672 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sun, 3 Mar 2013 12:47:03 +0100 Subject: [PATCH 12/29] Support case like `from os.path import join as pjoin` --- jedi/api.py | 3 ++- test/test_regression.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/jedi/api.py b/jedi/api.py index 28ba607c..6576a27c 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -576,7 +576,8 @@ class Interpreter(Script): # Import functions and classes module = getattr(obj, '__module__', None) if module: - fakeimport = self._make_fakeimport(module, variable) + fakeimport = self._make_fakeimport(module, obj.__name__, + variable) scope.add_import(fakeimport) continue diff --git a/test/test_regression.py b/test/test_regression.py index acbfc60e..87a89803 100755 --- a/test/test_regression.py +++ b/test/test_regression.py @@ -553,6 +553,11 @@ class TestInterpreterAPI(unittest.TestCase): {'join': os.path.join}, ['upper']) + def test_complete_raw_function_different_name(self): + self.check_interpreter_complete('pjoin().up', + {'pjoin': os.path.join}, + ['upper']) + def test_complete_raw_module(self): self.check_interpreter_complete('os.path.join().up', {'os': os}, From af108910966edaebff5c945110d8e6e4e75de53b Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sun, 3 Mar 2013 12:50:58 +0100 Subject: [PATCH 13/29] Document api.Interpreter._genname --- jedi/api.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jedi/api.py b/jedi/api.py index 6576a27c..f8f98dab 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -544,7 +544,11 @@ class Interpreter(Script): count = itertools.count() fmt = '*jedi-{0}*'.format self._genname = lambda: fmt(next(count)) - """Generate unique variable names to avoid name collision.""" + """ + Generate unique variable names to avoid name collision. + To avoid name collision to already defined names, generated + names are invalid as Python identifier. + """ for ns in namespaces: self._import_raw_namespace(ns) From a93016db1bd66c0181d33525032b0db0211aca55 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sun, 3 Mar 2013 13:24:11 +0100 Subject: [PATCH 14/29] Python 2.5 compatibility fix --- jedi/api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jedi/api.py b/jedi/api.py index f8f98dab..cc9f165b 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -542,8 +542,7 @@ class Interpreter(Script): source, line, column, source_path, source_encoding, fast=False) count = itertools.count() - fmt = '*jedi-{0}*'.format - self._genname = lambda: fmt(next(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 From dc47d15de0c2956a367206d21a0886750df0ab88 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sun, 3 Mar 2013 13:35:48 +0100 Subject: [PATCH 15/29] Use locals() for tests in TestInterpreterAPI It is better to check that Interpreter does not fail with the real name space. Previously the doctest using locals() failed because of the bug in _import_raw_namespace. --- jedi/api.py | 11 ++++++----- test/test_regression.py | 11 +++++++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/jedi/api.py b/jedi/api.py index cc9f165b..a91e3e76 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -576,17 +576,18 @@ class Interpreter(Script): """ scope = self._parser.scope for (variable, obj) in raw_namespace.items(): + objname = getattr(obj, '__name__', None) + # Import functions and classes module = getattr(obj, '__module__', None) - if module: - fakeimport = self._make_fakeimport(module, obj.__name__, - variable) + if module and objname: + fakeimport = self._make_fakeimport(module, objname, variable) scope.add_import(fakeimport) continue # Import modules - if getattr(obj, '__file__', None): - fakeimport = self._make_fakeimport(obj.__name__) + if getattr(obj, '__file__', None) and objname: + fakeimport = self._make_fakeimport(objname) scope.add_import(fakeimport) continue diff --git a/test/test_regression.py b/test/test_regression.py index 87a89803..2d125fd5 100755 --- a/test/test_regression.py +++ b/test/test_regression.py @@ -549,25 +549,28 @@ class TestInterpreterAPI(unittest.TestCase): self.assertEqual(sorted(actual), sorted(completions)) def test_complete_raw_function(self): + from os.path import join self.check_interpreter_complete('join().up', - {'join': os.path.join}, + locals(), ['upper']) def test_complete_raw_function_different_name(self): + from os.path import join as pjoin self.check_interpreter_complete('pjoin().up', - {'pjoin': os.path.join}, + locals(), ['upper']) def test_complete_raw_module(self): + import os self.check_interpreter_complete('os.path.join().up', - {'os': os}, + locals(), ['upper']) def test_complete_raw_instance(self): import datetime dt = datetime.datetime(2013, 1, 1) self.check_interpreter_complete('(dt - dt).ti', - {'dt': dt}, + locals(), ['time', 'timetz', 'timetuple']) From c2e9ccda2bda4265c3cd953bc4a750fbd5f233cf Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 12 Mar 2013 14:25:53 +0100 Subject: [PATCH 16/29] Fix failing test: args for ModuleWithCursor --- jedi/modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jedi/modules.py b/jedi/modules.py index 035a894e..4a66d477 100644 --- a/jedi/modules.py +++ b/jedi/modules.py @@ -92,7 +92,7 @@ class ModuleWithCursor(Module): :param position: The position, the user is currently in. Only important \ for the main file. """ - def __init__(self, path, source, position, fast): + def __init__(self, path, source, position, fast=True): super(ModuleWithCursor, self).__init__(path, source) self._parserclass = fast_parser.FastParser if fast else \ parsing.Parser From 8fc396371c3213901acf853b93bbe6b4824f5459 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 12 Mar 2013 14:36:15 +0100 Subject: [PATCH 17/29] Respect settings.fast_parser --- jedi/api.py | 2 +- jedi/modules.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/jedi/api.py b/jedi/api.py index a91e3e76..7b325933 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -56,7 +56,7 @@ class Script(object): :type source_encoding: str """ def __init__(self, source, line, column, source_path, - source_encoding='utf-8', fast=True): + source_encoding='utf-8', fast=None): api_classes._clear_caches() debug.reset_time() self.source = modules.source_to_unicode(source, source_encoding) diff --git a/jedi/modules.py b/jedi/modules.py index 4a66d477..462e6989 100644 --- a/jedi/modules.py +++ b/jedi/modules.py @@ -91,9 +91,13 @@ class ModuleWithCursor(Module): :param path: The module path of the file or None. :param position: The position, the user is currently in. Only important \ for the main file. + :param fast: Use `fast_parser.FastParser` or not. If None, respect + `settings.fast_parser`. """ - def __init__(self, path, source, position, fast=True): + def __init__(self, path, source, position, fast=None): super(ModuleWithCursor, self).__init__(path, source) + if fast is None: + fast = settings.fast_parser self._parserclass = fast_parser.FastParser if fast else \ parsing.Parser self.position = position From e8914e78569e6df70ebf359429313e9a288b145e Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 12 Mar 2013 16:14:03 +0100 Subject: [PATCH 18/29] Document api.Interpreter --- docs/conf.py | 1 + jedi/api.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 20bfc6a3..1dfb18c4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -267,6 +267,7 @@ todo_include_todos = False # -- Options for autodoc module ------------------------------------------------ +autoclass_content = 'both' autodoc_member_order = 'bysource' autodoc_default_flags = [] #autodoc_default_flags = ['members', 'undoc-members'] diff --git a/jedi/api.py b/jedi/api.py index 7b325933..96d88ceb 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -529,11 +529,14 @@ class Interpreter(Script): Parse `source` and mixin interpreted Python objects from `namespaces`. :type source: str - :arg source: + :arg source: Code to parse. :type namespaces: list of dict - :arg namespaces: + :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`. """ lines = source.splitlines() line = len(lines) if line is None else line From 3d0458bca9b8ca5fdd6085b3cd19dc2a903c4a66 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 4 May 2013 17:50:42 +0200 Subject: [PATCH 19/29] Export Interpreter class to top-level namespace --- jedi/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jedi/__init__.py b/jedi/__init__.py index 7d74b8c4..a97fa88c 100644 --- a/jedi/__init__.py +++ b/jedi/__init__.py @@ -40,7 +40,9 @@ import sys # imports and circular imports... Just avoid it: 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, _quick_complete, +) from . import settings sys.path.pop(0) From 89edb739784863cbdb0884c63e3b1a4ef4e7cf5d Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 4 May 2013 17:58:02 +0200 Subject: [PATCH 20/29] Fix failure due to rebase --- jedi/modules.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jedi/modules.py b/jedi/modules.py index 462e6989..bb3d4303 100644 --- a/jedi/modules.py +++ b/jedi/modules.py @@ -22,6 +22,8 @@ import os from ast import literal_eval from jedi._compatibility import exec_function, unicode +from jedi import settings +from jedi import parsing from jedi import cache from jedi import parsing_representation as pr from jedi import fast_parser From 17f5b9a79d03ed8b5ed37d527cb34d1d45ee5fe9 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 4 May 2013 18:08:25 +0200 Subject: [PATCH 21/29] Fix test failure in Python 3.3 There is a new method introduced in Python 3.3: http://docs.python.org/3/library/datetime.html#datetime.datetime.timestamp --- test/test_regression.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/test_regression.py b/test/test_regression.py index 2d125fd5..783c4c9e 100755 --- a/test/test_regression.py +++ b/test/test_regression.py @@ -14,7 +14,7 @@ import textwrap from .base import TestBase, unittest, cwd_at import jedi -from jedi._compatibility import utf8, unicode +from jedi._compatibility import utf8, unicode, is_py33 from jedi import api api_classes = api.api_classes @@ -569,9 +569,12 @@ class TestInterpreterAPI(unittest.TestCase): 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(), - ['time', 'timetz', 'timetuple']) + completions) def test_settings_module(): From 633f582184005439ff4c2bb2a3e99d0b604dd8da Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 11 May 2013 21:33:42 +0200 Subject: [PATCH 22/29] Add jedi.interpret.ObjectImporter and move scope manipulation functions in api.Interpreter to there. --- jedi/api.py | 159 +----------------------------------------- jedi/interpret.py | 171 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 156 deletions(-) create mode 100644 jedi/interpret.py diff --git a/jedi/api.py b/jedi/api.py index 96d88ceb..cfa2c738 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -11,8 +11,6 @@ from __future__ import with_statement import re import os import warnings -import itertools -import tokenize from jedi import parsing from jedi import parsing_representation as pr @@ -22,6 +20,7 @@ from jedi import helpers from jedi import common from jedi import cache from jedi import modules +from jedi import interpret from jedi._compatibility import next, unicode import evaluate import keywords @@ -544,161 +543,9 @@ class Interpreter(Script): super(Interpreter, self).__init__( source, line, column, source_path, source_encoding, fast=False) - 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. - """ - + importer = interpret.ObjectImporter(self._parser.scope) for ns in namespaces: - self._import_raw_namespace(ns) - - 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._parser.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` is given: ``import `` - - `module` and `variable` are given: ``from import `` - - all params are given: ``from import as `` - - :type module: str - :arg module: ```` part in ``from import ...`` - :type variable: str - :arg variable: ```` part in ``from ... import `` - :type alias: str - :arg alias: ```` part in ``... import ... as ``. - - :rtype: :class:`parsing_representation.Import` - """ - submodule = self._parser.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._parser.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)) + importer.import_raw_namespace(ns) def defined_names(source, source_path=None, source_encoding='utf-8'): diff --git a/jedi/interpret.py b/jedi/interpret.py new file mode 100644 index 00000000..6792369c --- /dev/null +++ b/jedi/interpret.py @@ -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` and `variable`: ``from import `` + - all: ``from import as `` + + :type module: str + :arg module: ```` part in ``from import ...`` + :type variable: str + :arg variable: ```` part in ``from ... import `` + :type alias: str + :arg alias: ```` part in ``... import ... as ``. + + :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)) From 6af2a0677d583ab6b964477f79ccdb6f42653588 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 11 May 2013 21:36:43 +0200 Subject: [PATCH 23/29] Compute default line/column/source_path in api.Script --- jedi/api.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/jedi/api.py b/jedi/api.py index cfa2c738..ef28e3d4 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -54,8 +54,12 @@ class Script(object): ``unicode`` object (default ``'utf-8'``). :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', fast=None): + 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() debug.reset_time() self.source = modules.source_to_unicode(source, source_encoding) @@ -537,9 +541,6 @@ class Interpreter(Script): If `line` and `column` are None, they are assumed be at the end of `source`. """ - lines = source.splitlines() - line = len(lines) if line is None else line - column = len(lines[-1]) if column is None else column super(Interpreter, self).__init__( source, line, column, source_path, source_encoding, fast=False) From 08f8dcfae4d35e997cec9043f9f22d4a1e979829 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 11 May 2013 21:37:53 +0200 Subject: [PATCH 24/29] Do not repeat defaults in Interpreter.__init__ --- jedi/api.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/jedi/api.py b/jedi/api.py index ef28e3d4..fb7f2d09 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -526,8 +526,7 @@ class Interpreter(Script): """ - def __init__(self, source, namespaces=[], line=None, column=None, - source_path=None, source_encoding='utf-8'): + def __init__(self, source, namespaces=[], **kwds): """ Parse `source` and mixin interpreted Python objects from `namespaces`. @@ -541,8 +540,7 @@ class Interpreter(Script): If `line` and `column` are None, they are assumed be at the end of `source`. """ - super(Interpreter, self).__init__( - source, line, column, source_path, source_encoding, fast=False) + super(Interpreter, self).__init__(source, fast=False, **kwds) importer = interpret.ObjectImporter(self._parser.scope) for ns in namespaces: From 2846fe980b468ac43b1c09461a192486787664aa Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 11 May 2013 21:39:53 +0200 Subject: [PATCH 25/29] Remove api._quick_complete --- jedi/__init__.py | 4 +--- jedi/api.py | 22 ---------------------- test/test_regression.py | 21 --------------------- 3 files changed, 1 insertion(+), 46 deletions(-) diff --git a/jedi/__init__.py b/jedi/__init__.py index a97fa88c..83b69efa 100644 --- a/jedi/__init__.py +++ b/jedi/__init__.py @@ -40,9 +40,7 @@ import sys # imports and circular imports... Just avoid it: sys.path.insert(0, __path__[0]) -from .api import ( - Script, Interpreter, NotFoundError, set_debug_function, _quick_complete, -) +from .api import Script, Interpreter, NotFoundError, set_debug_function from . import settings sys.path.pop(0) diff --git a/jedi/api.py b/jedi/api.py index fb7f2d09..dd4dd5e9 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -577,25 +577,3 @@ def set_debug_function(func_cb=debug.print_to_stdout, warnings=True, debug.enable_warning = warnings debug.enable_notice = notices 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 - [, , ...] - - :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() diff --git a/test/test_regression.py b/test/test_regression.py index 783c4c9e..ca1b87b6 100755 --- a/test/test_regression.py +++ b/test/test_regression.py @@ -430,27 +430,6 @@ class TestFeature(TestBase): any_re""" self.assertEqual(self.goto_definitions(s)[0].full_name, 're.RegexObject') - def test_quick_completion(self): - sources = [ - ('import json; json.l', (1, 19)), - ('import json; json.l ', (1, 19)), - ('import json\njson.l', (2, 6)), - ('import json\njson.l ', (2, 6)), - ('import json\njson.l\n\n', (2, 6)), - ('import json\njson.l \n\n', (2, 6)), - ('import json\njson.l \n \n\n', (2, 6)), - ] - for source, pos in sources: - # Run quick_complete - quick_completions = api._quick_complete(source) - # Run real completion - script = jedi.Script(source, pos[0], pos[1], '') - real_completions = script.completions() - # Compare results - quick_values = [(c.full_name, c.line, c.column) for c in quick_completions] - real_values = [(c.full_name, c.line, c.column) for c in real_completions] - self.assertEqual(quick_values, real_values) - class TestGetDefinitions(TestBase): From a870fece0f9382661772eef04a53a986a0bc4e70 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 11 May 2013 21:47:55 +0200 Subject: [PATCH 26/29] Use fast parser in api.Interpreter --- jedi/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jedi/api.py b/jedi/api.py index dd4dd5e9..c4927540 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -540,9 +540,9 @@ class Interpreter(Script): If `line` and `column` are None, they are assumed be at the end of `source`. """ - super(Interpreter, self).__init__(source, fast=False, **kwds) + super(Interpreter, self).__init__(source, **kwds) - importer = interpret.ObjectImporter(self._parser.scope) + importer = interpret.ObjectImporter(self._parser.user_scope) for ns in namespaces: importer.import_raw_namespace(ns) From 19b3fef0c5ef6ecce738d4dc8c1cb09b3cac93ad Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 11 May 2013 21:59:39 +0200 Subject: [PATCH 27/29] Remove `fast` option from api.Script --- jedi/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jedi/api.py b/jedi/api.py index c4927540..e0243e46 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -55,7 +55,7 @@ class Script(object): :type source_encoding: str """ def __init__(self, source, line=None, column=None, source_path=None, - source_encoding='utf-8', fast=None): + 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 @@ -65,7 +65,7 @@ class Script(object): self.source = modules.source_to_unicode(source, source_encoding) self.pos = line, column self._module = modules.ModuleWithCursor( - source_path, source=self.source, position=self.pos, fast=fast) + source_path, source=self.source, position=self.pos) self._source_path = source_path self.source_path = None if source_path is None \ else os.path.abspath(source_path) From 52b3a326c4912f0bcc191a6e5eb59d66f0b28d2f Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 11 May 2013 22:01:48 +0200 Subject: [PATCH 28/29] Revert changes in jedi/modules.py in this branch --- jedi/modules.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/jedi/modules.py b/jedi/modules.py index bb3d4303..37e5bbac 100644 --- a/jedi/modules.py +++ b/jedi/modules.py @@ -22,8 +22,6 @@ import os from ast import literal_eval from jedi._compatibility import exec_function, unicode -from jedi import settings -from jedi import parsing from jedi import cache from jedi import parsing_representation as pr from jedi import fast_parser @@ -93,15 +91,9 @@ class ModuleWithCursor(Module): :param path: The module path of the file or None. :param position: The position, the user is currently in. Only important \ for the main file. - :param fast: Use `fast_parser.FastParser` or not. If None, respect - `settings.fast_parser`. """ - def __init__(self, path, source, position, fast=None): + def __init__(self, path, source, position): super(ModuleWithCursor, self).__init__(path, source) - if fast is None: - fast = settings.fast_parser - self._parserclass = fast_parser.FastParser if fast else \ - parsing.Parser self.position = position # this two are only used, because there is no nonlocal in Python 2 @@ -120,7 +112,7 @@ class ModuleWithCursor(Module): # Call the parser already here, because it will be used anyways. # Also, the position is here important (which will not be used by # default), therefore fill the cache here. - self._parser = self._parserclass(self.source, self.path, + self._parser = fast_parser.FastParser(self.source, self.path, self.position) # don't pickle that module, because it's changing fast cache.save_module(self.path, self.name, self._parser, From 3d6ef88795c0d3aae8fb1c1da519a6c003cdaa81 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sun, 12 May 2013 08:15:48 +0200 Subject: [PATCH 29/29] Add simple PYTHONSTARTUP file --- jedi/__main__.py | 2 ++ jedi/replstartup.py | 25 +++++++++++++++++++++++++ jedi/utils.py | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 jedi/__main__.py create mode 100644 jedi/replstartup.py create mode 100644 jedi/utils.py diff --git a/jedi/__main__.py b/jedi/__main__.py new file mode 100644 index 00000000..73cde226 --- /dev/null +++ b/jedi/__main__.py @@ -0,0 +1,2 @@ +from os import path +print(path.join(path.dirname(path.abspath(__file__)), 'replstartup.py')) diff --git a/jedi/replstartup.py b/jedi/replstartup.py new file mode 100644 index 00000000..40ee77da --- /dev/null +++ b/jedi/replstartup.py @@ -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 # 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. diff --git a/jedi/utils.py b/jedi/utils.py new file mode 100644 index 00000000..eb617fe7 --- /dev/null +++ b/jedi/utils.py @@ -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`~!@#$%^&*-=+[{]}\\|;:\'",<>/?')