##// END OF EJS Templates
Merge branch 'issue1290'
Merge branch 'issue1290'

File last commit:

r2558:18cd5de2
r5914:f2084ede merge
Show More
ipy_traits_completer.py
219 lines | 6.7 KiB | text/x-python | PythonLexer
/ IPython / deathrow / ipy_traits_completer.py
"""Traits-aware tab completion.
This module provides a custom tab-completer that intelligently hides the names
that the enthought.traits library (http://code.enthought.com/traits)
automatically adds to all objects that inherit from its base HasTraits class.
Activation
==========
To use this, put in your ~/.ipython/ipy_user_conf.py file:
from ipy_traits_completer import activate
activate([complete_threshold])
The optional complete_threshold argument is the minimal length of text you need
to type for tab-completion to list names that are automatically generated by
traits. The default value is 3. Note that at runtime, you can change this
value simply by doing:
import ipy_traits_completer
ipy_traits_completer.COMPLETE_THRESHOLD = 4
Usage
=====
The system works as follows. If t is an empty object that HasTraits, then
(assuming the threshold is at the default value of 3):
In [7]: t.ed<TAB>
doesn't show anything at all, but:
In [7]: t.edi<TAB>
t.edit_traits t.editable_traits
shows these two names that come from traits. This allows you to complete on
the traits-specific names by typing at least 3 letters from them (or whatever
you set your threshold to), but to otherwise not see them in normal completion.
Notes
=====
- This requires Python 2.4 to work (I use sets). I don't think anyone is
using traits with 2.3 anyway, so that's OK.
- Imports from enthought.traits are deferred until an object with a class that
looks like it subclasses from HasTraits comes along. This test is done by
looking at the name of the class and its superclasses.
"""
#############################################################################
# IPython imports
from IPython.core.error import TryNext
from IPython.core.ipapi import get as ipget
from IPython.utils.dir2 import dir2
try:
set
except:
from sets import Set as set
#############################################################################
# Module constants
# The completion threshold
# This is currently implemented as a module global, since this sytem isn't
# likely to be modified at runtime by multiple instances. If needed in the
# future, we can always make it local to the completer as a function attribute.
COMPLETE_THRESHOLD = 3
# Set of names that Traits automatically adds to ANY traits-inheriting object.
# These are the names we'll filter out.
TRAIT_NAMES = None
def get_trait_names():
global TRAIT_NAMES
from enthought.traits.api import HasTraits
if TRAIT_NAMES is None:
TRAIT_NAMES = set( dir2(HasTraits()) ) - set( dir2(object()) )
else:
return TRAIT_NAMES
#############################################################################
# Code begins
def looks_like_isinstance(obj, classname):
""" Return True if the object has a class or superclass with the given class
name.
Ignores old-style classes.
"""
from types import InstanceType
t = type(obj)
if t is InstanceType:
# Old-style classes.
return False
elif t.__name__ == classname:
return True
for klass in t.__mro__:
if klass.__name__ == classname:
return True
return False
def trait_completer(self,event):
"""A custom IPython tab-completer that is traits-aware.
It tries to hide the internal traits attributes, and reveal them only when
it can reasonably guess that the user really is after one of them.
"""
#print '\nevent is:',event # dbg
symbol_parts = event.symbol.split('.')
base = '.'.join(symbol_parts[:-1])
#print 'base:',base # dbg
oinfo = self._ofind(base)
if not oinfo['found']:
raise TryNext
obj = oinfo['obj']
# OK, we got the object. See if it's traits, else punt
if not looks_like_isinstance(obj, 'HasTraits'):
raise TryNext
# Defer import until here so as not to require Traits until we get something
# that looks like it might be a HasTraits instance.
from enthought.traits.api import HasTraits
if not isinstance(obj, HasTraits):
raise TryNext
# it's a traits object, don't show the tr* attributes unless the completion
# begins with 'tr'
attrs = dir2(obj)
# Now, filter out the attributes that start with the user's request
attr_start = symbol_parts[-1]
if attr_start:
attrs = [a for a in attrs if a.startswith(attr_start)]
# Let's also respect the user's readline_omit__names setting:
omit__names = ipget().options.readline_omit__names
if omit__names == 1:
attrs = [a for a in attrs if not a.startswith('__')]
elif omit__names == 2:
attrs = [a for a in attrs if not a.startswith('_')]
#print '\nastart:<%r>' % attr_start # dbg
if len(attr_start)<COMPLETE_THRESHOLD:
attrs = list(set(attrs) - get_trait_names())
# The base of the completion, so we can form the final results list
bdot = base+'.'
tcomp = [bdot+a for a in attrs]
#print 'tcomp:',tcomp
return tcomp
def activate(complete_threshold = COMPLETE_THRESHOLD):
"""Activate the Traits completer.
:Keywords:
complete_threshold : int
The minimum number of letters that a user must type in order to
activate completion of traits-private names."""
if not (isinstance(complete_threshold,int) and
complete_threshold>0):
e='complete_threshold must be a positive integer, not %r' % \
complete_threshold
raise ValueError(e)
# Set the module global
global COMPLETE_THRESHOLD
COMPLETE_THRESHOLD = complete_threshold
# Activate the traits aware completer
ip = ipget()
ip.set_hook('complete_command', trait_completer, re_key = '.*')
#############################################################################
if __name__ == '__main__':
# Testing/debugging
from enthought.traits.api import HasTraits
# A sorted list of the names we'll filter out
TNL = list(get_trait_names())
TNL.sort()
# Make a few objects for testing
class TClean(HasTraits): pass
class Bunch(object): pass
# A clean traits object
t = TClean()
# A nested object containing t
f = Bunch()
f.t = t
# And a naked new-style object
o = object()
ip = ipget().IP
# A few simplistic tests
# Reset the threshold to the default, in case the test is running inside an
# instance of ipython that changed it
import ipy_traits_completer
ipy_traits_completer.COMPLETE_THRESHOLD = 3
assert ip.complete('t.ed') ==[]
# For some bizarre reason, these fail on the first time I run them, but not
# afterwards. Traits does some really weird stuff at object instantiation
# time...
ta = ip.complete('t.edi')
assert ta == ['t.edit_traits', 't.editable_traits']
print 'Tests OK'