Skip to content

Commit

Permalink
Merge PR ipython#668 (greedy completion)
Browse files Browse the repository at this point in the history
  • 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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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'.
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>
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:
Expand All @@ -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
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
information on each call about the current line."""

Completer.__init__(self, namespace, global_namespace)

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

Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
14 changes: 8 additions & 6 deletions IPython/core/interactiveshell.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
11 changes: 11 additions & 0 deletions IPython/core/tests/test_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

2 changes: 2 additions & 0 deletions IPython/frontend/terminal/ipapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -198,6 +199,7 @@ def _classes_default(self):
TerminalInteractiveShell,
ProfileDir,
PlainTextFormatter,
Completer,
]

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.