1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
|
"""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'
|