1
"""Traits-aware tab completion.
3
This module provides a custom tab-completer that intelligently hides the names
4
that the enthought.traits library (http://code.enthought.com/traits)
5
automatically adds to all objects that inherit from its base HasTraits class.
11
To use this, put in your ~/.ipython/ipy_user_conf.py file:
13
from ipy_traits_completer import activate
14
activate([complete_threshold])
16
The optional complete_threshold argument is the minimal length of text you need
17
to type for tab-completion to list names that are automatically generated by
18
traits. The default value is 3. Note that at runtime, you can change this
19
value simply by doing:
21
import ipy_traits_completer
22
ipy_traits_completer.COMPLETE_THRESHOLD = 4
28
The system works as follows. If t is an empty object that HasTraits, then
29
(assuming the threshold is at the default value of 3):
33
doesn't show anything at all, but:
36
t.edit_traits t.editable_traits
38
shows these two names that come from traits. This allows you to complete on
39
the traits-specific names by typing at least 3 letters from them (or whatever
40
you set your threshold to), but to otherwise not see them in normal completion.
46
- This requires Python 2.4 to work (I use sets). I don't think anyone is
47
using traits with 2.3 anyway, so that's OK.
50
#############################################################################
52
from enthought.traits import api as T
55
from IPython.ipapi import TryNext, get as ipget
56
from IPython.genutils import dir2
60
from sets import Set as set
62
#############################################################################
65
# The completion threshold
66
# This is currently implemented as a module global, since this sytem isn't
67
# likely to be modified at runtime by multiple instances. If needed in the
68
# future, we can always make it local to the completer as a function attribute.
69
COMPLETE_THRESHOLD = 3
71
# Set of names that Traits automatically adds to ANY traits-inheriting object.
72
# These are the names we'll filter out.
73
TRAIT_NAMES = set( dir2(T.HasTraits()) ) - set( dir2(object()) )
75
#############################################################################
78
def trait_completer(self,event):
79
"""A custom IPython tab-completer that is traits-aware.
81
It tries to hide the internal traits attributes, and reveal them only when
82
it can reasonably guess that the user really is after one of them.
85
#print '\nevent is:',event # dbg
86
symbol_parts = event.symbol.split('.')
87
base = '.'.join(symbol_parts[:-1])
88
#print 'base:',base # dbg
90
oinfo = self._ofind(base)
91
if not oinfo['found']:
95
# OK, we got the object. See if it's traits, else punt
96
if not isinstance(obj,T.HasTraits):
99
# it's a traits object, don't show the tr* attributes unless the completion
102
# Now, filter out the attributes that start with the user's request
103
attr_start = symbol_parts[-1]
105
attrs = [a for a in attrs if a.startswith(attr_start)]
107
# Let's also respect the user's readline_omit__names setting:
108
omit__names = ipget().options.readline_omit__names
110
attrs = [a for a in attrs if not a.startswith('__')]
111
elif omit__names == 2:
112
attrs = [a for a in attrs if not a.startswith('_')]
114
#print '\nastart:<%r>' % attr_start # dbg
116
if len(attr_start)<COMPLETE_THRESHOLD:
117
attrs = list(set(attrs) - TRAIT_NAMES)
119
# The base of the completion, so we can form the final results list
122
tcomp = [bdot+a for a in attrs]
123
#print 'tcomp:',tcomp
126
def activate(complete_threshold = COMPLETE_THRESHOLD):
127
"""Activate the Traits completer.
130
complete_threshold : int
131
The minimum number of letters that a user must type in order to
132
activate completion of traits-private names."""
134
if not (isinstance(complete_threshold,int) and
135
complete_threshold>0):
136
e='complete_threshold must be a positive integer, not %r' % \
140
# Set the module global
141
global COMPLETE_THRESHOLD
142
COMPLETE_THRESHOLD = complete_threshold
144
# Activate the traits aware completer
146
ip.set_hook('complete_command', trait_completer, re_key = '.*')
149
#############################################################################
150
if __name__ == '__main__':
153
# A sorted list of the names we'll filter out
154
TNL = list(TRAIT_NAMES)
157
# Make a few objects for testing
158
class TClean(T.HasTraits): pass
159
class Bunch(object): pass
160
# A clean traits object
162
# A nested object containing t
165
# And a naked new-style object
170
# A few simplistic tests
172
# Reset the threshold to the default, in case the test is running inside an
173
# instance of ipython that changed it
174
import ipy_traits_completer
175
ipy_traits_completer.COMPLETE_THRESHOLD = 3
177
assert ip.complete('t.ed') ==[]
179
# For some bizarre reason, these fail on the first time I run them, but not
180
# afterwards. Traits does some really weird stuff at object instantiation
182
ta = ip.complete('t.edi')
183
assert ta == ['t.edit_traits', 't.editable_traits']