diff --git a/IPython/core/completer.py b/IPython/core/completer.py index 098d6de..351d647 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -78,12 +78,14 @@ import re import shlex import sys +from IPython.config.configurable import Configurable from IPython.core.error import TryNext from IPython.core.prefilter import ESC_MAGIC from IPython.utils import generics from IPython.utils import io from IPython.utils.dir2 import dir2 from IPython.utils.process import arg_split +from IPython.utils.traitlets import CBool #----------------------------------------------------------------------------- # Globals @@ -210,6 +212,8 @@ def single_dir_expand(matches): class Bunch(object): pass +DELIMS = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?' +GREEDY_DELIMS = ' \r\n' class CompletionSplitter(object): """An object to split an input line in a manner similar to readline. @@ -228,7 +232,7 @@ class CompletionSplitter(object): # A string of delimiter characters. The default value makes sense for # IPython's most typical usage patterns. - _delims = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?' + _delims = DELIMS # The expression (a normal string) to be compiled into a regular expression # for actual splitting. We store it as an attribute mostly for ease of @@ -260,11 +264,20 @@ class CompletionSplitter(object): return self._delim_re.split(l)[-1] -class Completer(object): - def __init__(self, namespace=None, global_namespace=None): +class Completer(Configurable): + + greedy = CBool(False, config=True, + help="""Activate greedy completion + + This will enable completion on elements of lists, results of function calls, etc., + but can be unsafe because the code is actually evaluated on TAB. + """ + ) + + def __init__(self, namespace=None, global_namespace=None, config=None): """Create a new completer for the command line. - Completer([namespace,global_namespace]) -> completer instance. + Completer(namespace=ns,global_namespace=ns2) -> completer instance. If unspecified, the default namespace where completions are performed is __main__ (technically, __main__.__dict__). Namespaces should be @@ -294,6 +307,8 @@ class Completer(object): self.global_namespace = {} else: self.global_namespace = global_namespace + + super(Completer, self).__init__(config=config) def complete(self, text, state): """Return the next possible completion for 'text'. @@ -349,14 +364,20 @@ class Completer(object): """ - #print 'Completer->attr_matches, txt=%r' % text # dbg + #io.rprint('Completer->attr_matches, txt=%r' % text) # dbg # Another option, seems to work great. Catches things like ''. m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text) - if not m: + if m: + expr, attr = m.group(1, 3) + elif self.greedy: + m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer) + if not m2: + return [] + expr, attr = m2.group(1,2) + else: return [] - - expr, attr = m.group(1, 3) + try: obj = eval(expr, self.namespace) except: @@ -380,8 +401,19 @@ class Completer(object): class IPCompleter(Completer): """Extension of the completer class with IPython-specific features""" - def __init__(self, shell, namespace=None, global_namespace=None, - omit__names=True, alias_table=None, use_readline=True): + def _greedy_changed(self, name, old, new): + """update the splitter and readline delims when greedy is changed""" + if new: + self.splitter.set_delims(GREEDY_DELIMS) + else: + self.splitter.set_delims(DELIMS) + + if self.readline: + self.readline.set_completer_delims(self.splitter.get_delims()) + + def __init__(self, shell=None, namespace=None, global_namespace=None, + omit__names=True, alias_table=None, use_readline=True, + config=None): """IPCompleter() -> completer Return a completer object suitable for use by the readline library @@ -411,8 +443,6 @@ class IPCompleter(Completer): without readline, though in that case callers must provide some extra information on each call about the current line.""" - Completer.__init__(self, namespace, global_namespace) - self.magic_escape = ESC_MAGIC self.splitter = CompletionSplitter() @@ -424,6 +454,10 @@ class IPCompleter(Completer): else: self.readline = None + # _greedy_changed() depends on splitter and readline being defined: + Completer.__init__(self, namespace=namespace, global_namespace=global_namespace, + config=config) + # List where completion matches will be stored self.matches = [] self.omit__names = omit__names @@ -579,7 +613,7 @@ class IPCompleter(Completer): def python_matches(self,text): """Match attributes or global python names""" - #print 'Completer->python_matches, txt=%r' % text # dbg + #io.rprint('Completer->python_matches, txt=%r' % text) # dbg if "." in text: try: matches = self.attr_matches(text) @@ -680,7 +714,7 @@ class IPCompleter(Completer): return argMatches def dispatch_custom_completer(self, text): - #print "Custom! '%s' %s" % (text, self.custom_completers) # dbg + #io.rprint("Custom! '%s' %s" % (text, self.custom_completers)) # dbg line = self.line_buffer if not line.strip(): return None diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 928032a..2b3fb36 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -1771,12 +1771,14 @@ class InteractiveShell(SingletonConfigurable, Magic): from IPython.core.completerlib import (module_completer, magic_run_completer, cd_completer) - self.Completer = IPCompleter(self, - self.user_ns, - self.user_global_ns, - self.readline_omit__names, - self.alias_manager.alias_table, - self.has_readline) + self.Completer = IPCompleter(shell=self, + namespace=self.user_ns, + global_namespace=self.user_global_ns, + omit__names=self.readline_omit__names, + alias_table=self.alias_manager.alias_table, + use_readline=self.has_readline, + config=self.config, + ) # Add custom completers to the basic ones built into IPCompleter sdisp = self.strdispatchers.get('complete_command', StrDispatch()) diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py index 58caa26..27be653 100644 --- a/IPython/core/tests/test_completer.py +++ b/IPython/core/tests/test_completer.py @@ -181,3 +181,14 @@ def test_local_file_completions(): finally: # prevent failures from making chdir stick os.chdir(cwd) + +def test_greedy_completions(): + ip = get_ipython() + ip.Completer.greedy = False + ip.ex('a=range(5)') + _,c = ip.complete('.',line='a[0].') + nt.assert_false('a[0].real' in c, "Shouldn't have completed on a[0]: %s"%c) + ip.Completer.greedy = True + _,c = ip.complete('.',line='a[0].') + nt.assert_true('a[0].real' in c, "Should have completed on a[0]: %s"%c) + diff --git a/IPython/frontend/terminal/ipapp.py b/IPython/frontend/terminal/ipapp.py index f496e2f..a770ebe 100755 --- a/IPython/frontend/terminal/ipapp.py +++ b/IPython/frontend/terminal/ipapp.py @@ -35,6 +35,7 @@ from IPython.config.loader import ( from IPython.config.application import boolean_flag from IPython.core import release from IPython.core import usage +from IPython.core.completer import Completer from IPython.core.crashhandler import CrashHandler from IPython.core.formatters import PlainTextFormatter from IPython.core.application import ( @@ -198,6 +199,7 @@ class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp): TerminalInteractiveShell, ProfileDir, PlainTextFormatter, + Completer, ] subcommands = Dict(dict( diff --git a/IPython/quarantine/ipy_greedycompleter.py b/IPython/quarantine/ipy_greedycompleter.py deleted file mode 100644 index 1804338..0000000 --- a/IPython/quarantine/ipy_greedycompleter.py +++ /dev/null @@ -1,77 +0,0 @@ -""" Greedy completer extension for IPython - -Normal tab completer refuses to evaluate nonsafe stuff. This will evaluate -everything, so you need to consider the consequences of pressing tab -yourself! - -Note that this extension simplifies readline interaction by setting -only whitespace as completer delimiter. If this works well, we will -do the same in default completer. - -""" -from IPython.core import ipapi -from IPython.core.error import TryNext -from IPython.utils import generics -from IPython.utils.dir2 import dir2 - -def attr_matches(self, text): - """Compute matches when text contains a dot. - - MONKEYPATCHED VERSION (ipy_greedycompleter.py) - - Assuming the text is of the form NAME.NAME....[NAME], and is - evaluatable in self.namespace or self.global_namespace, it will be - evaluated and its attributes (as revealed by dir()) are used as - possible completions. (For class instances, class members are are - also considered.) - - WARNING: this can still invoke arbitrary C code, if an object - with a __getattr__ hook is evaluated. - - """ - import re - - force_complete = 1 - # Another option, seems to work great. Catches things like ''. - m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text) - - if m: - expr, attr = m.group(1, 3) - else: - # force match - eval anything that ends with colon - if not force_complete: - return [] - - m2 = re.match(r"(.+)\.(\w*)$", self.lbuf) - if not m2: - return [] - expr, attr = m2.group(1,2) - - - try: - obj = eval(expr, self.namespace) - except: - try: - obj = eval(expr, self.global_namespace) - except: - return [] - - words = dir2(obj) - - try: - words = generics.complete_object(obj, words) - except TryNext: - pass - # Build match list to return - n = len(attr) - res = ["%s.%s" % (expr, w) for w in words if w[:n] == attr ] - return res - -def main(): - import IPython.utils.rlineimpl as readline - readline.set_completer_delims(" \n\t") - # monkeypatch - the code will be folded to normal completer later on - import IPython.core.completer - IPython.core.completer.Completer.attr_matches = attr_matches - -main() \ No newline at end of file