Skip to content

Commit

Permalink
Merge PR #668 (greedy completion)
Browse files Browse the repository at this point in the history
closes gh-668
closes gh-651
  • Loading branch information
minrk committed Sep 15, 2011
2 parents 08bae62 + 98d886b commit 1021cbe
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 97 deletions.
62 changes: 48 additions & 14 deletions IPython/core/completer.py
Expand Up @@ -78,12 +78,14 @@
import shlex import shlex
import sys import sys


from IPython.config.configurable import Configurable
from IPython.core.error import TryNext from IPython.core.error import TryNext
from IPython.core.prefilter import ESC_MAGIC from IPython.core.prefilter import ESC_MAGIC
from IPython.utils import generics from IPython.utils import generics
from IPython.utils import io from IPython.utils import io
from IPython.utils.dir2 import dir2 from IPython.utils.dir2 import dir2
from IPython.utils.process import arg_split from IPython.utils.process import arg_split
from IPython.utils.traitlets import CBool


#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
# Globals # Globals
Expand Down Expand Up @@ -210,6 +212,8 @@ def single_dir_expand(matches):


class Bunch(object): pass class Bunch(object): pass


DELIMS = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?'
GREEDY_DELIMS = ' \r\n'


class CompletionSplitter(object): class CompletionSplitter(object):
"""An object to split an input line in a manner similar to readline. """An object to split an input line in a manner similar to readline.
Expand All @@ -228,7 +232,7 @@ class CompletionSplitter(object):


# A string of delimiter characters. The default value makes sense for # A string of delimiter characters. The default value makes sense for
# IPython's most typical usage patterns. # IPython's most typical usage patterns.
_delims = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?' _delims = DELIMS


# The expression (a normal string) to be compiled into a regular expression # 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 # for actual splitting. We store it as an attribute mostly for ease of
Expand Down Expand Up @@ -260,11 +264,20 @@ def split_line(self, line, cursor_pos=None):
return self._delim_re.split(l)[-1] return self._delim_re.split(l)[-1]




class Completer(object): class Completer(Configurable):
def __init__(self, namespace=None, global_namespace=None):
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. """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 If unspecified, the default namespace where completions are performed
is __main__ (technically, __main__.__dict__). Namespaces should be is __main__ (technically, __main__.__dict__). Namespaces should be
Expand Down Expand Up @@ -294,6 +307,8 @@ def __init__(self, namespace=None, global_namespace=None):
self.global_namespace = {} self.global_namespace = {}
else: else:
self.global_namespace = global_namespace self.global_namespace = global_namespace

super(Completer, self).__init__(config=config)


def complete(self, text, state): def complete(self, text, state):
"""Return the next possible completion for 'text'. """Return the next possible completion for 'text'.
Expand Down Expand Up @@ -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> # Another option, seems to work great. Catches things like ''.<tab>
m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text) 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 [] return []


expr, attr = m.group(1, 3)
try: try:
obj = eval(expr, self.namespace) obj = eval(expr, self.namespace)
except: except:
Expand All @@ -380,8 +401,19 @@ def attr_matches(self, text):
class IPCompleter(Completer): class IPCompleter(Completer):
"""Extension of the completer class with IPython-specific features""" """Extension of the completer class with IPython-specific features"""


def __init__(self, shell, namespace=None, global_namespace=None, def _greedy_changed(self, name, old, new):
omit__names=True, alias_table=None, use_readline=True): """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 """IPCompleter() -> completer
Return a completer object suitable for use by the readline library Return a completer object suitable for use by the readline library
Expand Down Expand Up @@ -411,8 +443,6 @@ def __init__(self, shell, namespace=None, global_namespace=None,
without readline, though in that case callers must provide some extra without readline, though in that case callers must provide some extra
information on each call about the current line.""" information on each call about the current line."""


Completer.__init__(self, namespace, global_namespace)

self.magic_escape = ESC_MAGIC self.magic_escape = ESC_MAGIC
self.splitter = CompletionSplitter() self.splitter = CompletionSplitter()


Expand All @@ -424,6 +454,10 @@ def __init__(self, shell, namespace=None, global_namespace=None,
else: else:
self.readline = None 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 # List where completion matches will be stored
self.matches = [] self.matches = []
self.omit__names = omit__names self.omit__names = omit__names
Expand Down Expand Up @@ -579,7 +613,7 @@ def alias_matches(self, text):
def python_matches(self,text): def python_matches(self,text):
"""Match attributes or global python names""" """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: if "." in text:
try: try:
matches = self.attr_matches(text) matches = self.attr_matches(text)
Expand Down Expand Up @@ -680,7 +714,7 @@ def python_func_kw_matches(self,text):
return argMatches return argMatches


def dispatch_custom_completer(self, text): 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 line = self.line_buffer
if not line.strip(): if not line.strip():
return None return None
Expand Down
14 changes: 8 additions & 6 deletions IPython/core/interactiveshell.py
Expand Up @@ -1771,12 +1771,14 @@ def init_completer(self):
from IPython.core.completerlib import (module_completer, from IPython.core.completerlib import (module_completer,
magic_run_completer, cd_completer) magic_run_completer, cd_completer)


self.Completer = IPCompleter(self, self.Completer = IPCompleter(shell=self,
self.user_ns, namespace=self.user_ns,
self.user_global_ns, global_namespace=self.user_global_ns,
self.readline_omit__names, omit__names=self.readline_omit__names,
self.alias_manager.alias_table, alias_table=self.alias_manager.alias_table,
self.has_readline) use_readline=self.has_readline,
config=self.config,
)


# Add custom completers to the basic ones built into IPCompleter # Add custom completers to the basic ones built into IPCompleter
sdisp = self.strdispatchers.get('complete_command', StrDispatch()) sdisp = self.strdispatchers.get('complete_command', StrDispatch())
Expand Down
11 changes: 11 additions & 0 deletions IPython/core/tests/test_completer.py
Expand Up @@ -181,3 +181,14 @@ def test_local_file_completions():
finally: finally:
# prevent failures from making chdir stick # prevent failures from making chdir stick
os.chdir(cwd) 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)

2 changes: 2 additions & 0 deletions IPython/frontend/terminal/ipapp.py
Expand Up @@ -35,6 +35,7 @@
from IPython.config.application import boolean_flag from IPython.config.application import boolean_flag
from IPython.core import release from IPython.core import release
from IPython.core import usage from IPython.core import usage
from IPython.core.completer import Completer
from IPython.core.crashhandler import CrashHandler from IPython.core.crashhandler import CrashHandler
from IPython.core.formatters import PlainTextFormatter from IPython.core.formatters import PlainTextFormatter
from IPython.core.application import ( from IPython.core.application import (
Expand Down Expand Up @@ -198,6 +199,7 @@ def _classes_default(self):
TerminalInteractiveShell, TerminalInteractiveShell,
ProfileDir, ProfileDir,
PlainTextFormatter, PlainTextFormatter,
Completer,
] ]


subcommands = Dict(dict( subcommands = Dict(dict(
Expand Down
77 changes: 0 additions & 77 deletions IPython/quarantine/ipy_greedycompleter.py

This file was deleted.

0 comments on commit 1021cbe

Please sign in to comment.