From 8b5e130e555876e880730c9659ab58520519f5f7 Mon Sep 17 00:00:00 2001 From: David Halter Date: Fri, 16 Aug 2013 01:38:58 +0430 Subject: [PATCH] fix a problem with setup_readline, using __dict__ instead of a simple dir(), #280 --- jedi/utils.py | 101 ++++++++++++++++++++++----------------------- test/test_utils.py | 23 +++++++---- 2 files changed, 66 insertions(+), 58 deletions(-) diff --git a/jedi/utils.py b/jedi/utils.py index 4cacf921..dba4a4e5 100644 --- a/jedi/utils.py +++ b/jedi/utils.py @@ -10,7 +10,7 @@ from jedi._compatibility import builtins from jedi import Interpreter -def setup_readline(namespace=__main__.__dict__): +def setup_readline(namespace_module=__main__): """ Install Jedi completer to :mod:`readline`. @@ -51,62 +51,61 @@ def setup_readline(namespace=__main__.__dict__): bash). """ + class JediRL(): + def complete(self, text, state): + """ + This complete stuff is pretty weird, a generator would make + a lot more sense, but probably due to backwards compatibility + this is still the way how it works. + + The only important part is stuff in the ``state == 0`` flow, + everything else has been copied from the ``rlcompleter`` std. + library module. + """ + if state == 0: + interpreter = Interpreter(text, [namespace_module.__dict__]) + + # The following part is a bit hackish, because it tries to + # directly access some jedi internals. The goal is to just + # use the "default" completion with ``getattr`` if + # possible. + path, dot, like = interpreter._get_completion_parts() + # Shouldn't be an import statement and just a simple path + # with dots. + is_import = re.match('^\s*(import|from) ', text) + if not is_import and (not path or re.match('^[\w][\w\d.]*$', path)): + paths = path.split('.') if path else [] + namespaces = (namespace_module, builtins) + for p in paths: + old, namespaces = namespaces, [] + for n in old: + try: + namespaces.append(getattr(n, p)) + except AttributeError: + pass + + self.matches = [] + for n in namespaces: + for name in dir(n): + if name.lower().startswith(like.lower()): + self.matches.append(path + dot + name) + else: + completions = interpreter.completions() + + before = text[:len(text) - len(like)] + self.matches = [before + c.name_with_symbols + for c in completions] + try: + return self.matches[state] + except IndexError: + return None + try: import readline except ImportError: print("Module readline not available.") else: - class JediRL(): - def complete(self, text, state): - """ - This complete stuff is pretty weird, a generator would make - a lot more sense, but probably due to backwards compatibility - this is still the way how it works. - - The only important part is stuff in the ``state == 0`` flow, - everything else has been copied from the ``rlcompleter`` std. - library module. - """ - if state == 0: - interpreter = Interpreter(text, [namespace]) - - # The following part is a bit hackish, because it tries to - # directly access some jedi internals. The goal is to just - # use the "default" completion with ``getattr`` if - # possible. - path, dot, like = interpreter._get_completion_parts() - # Shouldn't be an import statement and just a simple path - # with dots. - is_import = re.match('^\s*(import|from) ', text) - if not is_import and (not path or re.match('^[\w][\w\d.]*$', path)): - paths = path.split('.') if path else [] - namespaces = (namespace, builtins.__dict__) - for p in paths: - old, namespaces = namespaces, [] - for n in old: - try: - namespaces.append(n[p].__dict__) - except (KeyError, AttributeError): - pass - - self.matches = [] - for n in namespaces: - for name in n.keys(): - if name.lower().startswith(like.lower()): - self.matches.append(path + dot + name) - else: - completions = interpreter.completions() - - before = text[:len(text) - len(like)] - self.matches = [before + c.name_with_symbols - for c in completions] - try: - return self.matches[state] - except IndexError: - return None - readline.set_completer(JediRL().complete) - readline.parse_and_bind("tab: complete") # No delimiters, Jedi handles that. readline.set_completer_delims('') diff --git a/test/test_utils.py b/test/test_utils.py index 6533fdf4..565311af 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -5,9 +5,13 @@ from .helpers import TestCase class TestSetupReadline(TestCase): + class NameSpace(): + pass + def __init__(self, *args, **kwargs): super(type(self), self).__init__(*args, **kwargs) - self.namespace = dict() + + self.namespace = self.NameSpace() utils.setup_readline(self.namespace) def completions(self, text): @@ -37,8 +41,8 @@ class TestSetupReadline(TestCase): def test_modules(self): import sys import os - self.namespace['sys'] = sys - self.namespace['os'] = os + self.namespace.sys = sys + self.namespace.os = os assert self.completions('os.path.join') == ['os.path.join'] assert self.completions('os.path.join().upper') == ['os.path.join().upper'] @@ -46,14 +50,19 @@ class TestSetupReadline(TestCase): c = set(['os.' + d for d in dir(os) if d.startswith('ch')]) assert set(self.completions('os.ch')) == set(c) - del self.namespace['sys'] - del self.namespace['os'] + del self.namespace.sys + del self.namespace.os def test_import(self): s = 'from os.path import a' assert set(self.completions(s)) == set([s + 'ltsep', s + 'bspath']) assert self.completions('import keyword') == ['import keyword'] + def test_preexisting_values(self): + self.namespace.a = range(10) + assert set(self.completions('a.')) == set(['a.' + n for n in dir(range(1))]) + del self.namespace.a + def test_colorama(self): """ Only test it if colorama library is available. @@ -67,7 +76,7 @@ class TestSetupReadline(TestCase): except ImportError: pass else: - self.namespace['colorama'] = colorama + self.namespace.colorama = colorama assert self.completions('colorama') assert self.completions('colorama.Fore.BLACK') == ['colorama.Fore.BLACK'] - del self.namespace['colorama'] + del self.namespace.colorama