Permalink
Browse files

Merge PR #668 (greedy completion)

closes gh-668
closes gh-651
  • Loading branch information...
2 parents 08bae62 + 98d886b commit 1021cbe9f8691141096b5631fadfde59bc2b206a @minrk minrk committed Sep 15, 2011
View
@@ -78,12 +78,14 @@
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 @@ def split_line(self, line, cursor_pos=None):
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 @@ def __init__(self, namespace=None, global_namespace=None):
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 @@ def attr_matches(self, text):
"""
- #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 ''.<tab>
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 @@ def attr_matches(self, text):
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 @@ def __init__(self, shell, namespace=None, global_namespace=None,
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 @@ def __init__(self, shell, namespace=None, global_namespace=None,
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 @@ def alias_matches(self, text):
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 @@ def python_func_kw_matches(self,text):
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
@@ -1771,12 +1771,14 @@ def init_completer(self):
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())
@@ -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)
+
@@ -35,6 +35,7 @@
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 @@ def _classes_default(self):
TerminalInteractiveShell,
ProfileDir,
PlainTextFormatter,
+ Completer,
]
subcommands = Dict(dict(
@@ -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 ''.<tab>
- 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()

0 comments on commit 1021cbe

Please sign in to comment.