~ellisonbg/ipython/bugfixes0411409

« back to all changes in this revision

Viewing changes to IPython/Extensions/ipy_traits_completer.py

  • Committer: ville
  • Date: 2008-02-16 09:50:47 UTC
  • mto: (0.12.1 ipython_main)
  • mto: This revision was merged to the branch mainline in revision 990.
  • Revision ID: ville@ville-pc-20080216095047-500x6dluki1iz40o
initialization (no svn history)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""Traits-aware tab completion.
 
2
 
 
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.
 
6
 
 
7
 
 
8
Activation
 
9
==========
 
10
 
 
11
To use this, put in your ~/.ipython/ipy_user_conf.py file:
 
12
 
 
13
    from ipy_traits_completer import activate
 
14
    activate([complete_threshold])
 
15
 
 
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:
 
20
 
 
21
    import ipy_traits_completer
 
22
    ipy_traits_completer.COMPLETE_THRESHOLD = 4
 
23
 
 
24
 
 
25
Usage
 
26
=====
 
27
 
 
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):
 
30
 
 
31
In [7]: t.ed<TAB>
 
32
 
 
33
doesn't show anything at all, but:
 
34
 
 
35
In [7]: t.edi<TAB>
 
36
t.edit_traits      t.editable_traits
 
37
 
 
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.
 
41
 
 
42
 
 
43
Notes
 
44
=====
 
45
 
 
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.
 
48
"""
 
49
 
 
50
#############################################################################
 
51
# External imports
 
52
from enthought.traits import api as T
 
53
 
 
54
# IPython imports
 
55
from IPython.ipapi import TryNext, get as ipget
 
56
from IPython.genutils import dir2
 
57
try:
 
58
    set
 
59
except:
 
60
    from sets import Set as set
 
61
 
 
62
#############################################################################
 
63
# Module constants
 
64
 
 
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
 
70
 
 
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()) )
 
74
 
 
75
#############################################################################
 
76
# Code begins
 
77
 
 
78
def trait_completer(self,event):
 
79
    """A custom IPython tab-completer that is traits-aware.
 
80
 
 
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.
 
83
    """
 
84
    
 
85
    #print '\nevent is:',event  # dbg
 
86
    symbol_parts = event.symbol.split('.')
 
87
    base = '.'.join(symbol_parts[:-1])
 
88
    #print 'base:',base  # dbg
 
89
 
 
90
    oinfo = self._ofind(base)
 
91
    if not oinfo['found']:
 
92
        raise TryNext
 
93
 
 
94
    obj = oinfo['obj']
 
95
    # OK, we got the object.  See if it's traits, else punt
 
96
    if not isinstance(obj,T.HasTraits):
 
97
        raise TryNext
 
98
 
 
99
    # it's a traits object, don't show the tr* attributes unless the completion
 
100
    # begins with 'tr'
 
101
    attrs = dir2(obj)
 
102
    # Now, filter out the attributes that start with the user's request
 
103
    attr_start = symbol_parts[-1]
 
104
    if attr_start:
 
105
        attrs = [a for a in attrs if a.startswith(attr_start)]
 
106
    
 
107
    # Let's also respect the user's readline_omit__names setting:
 
108
    omit__names = ipget().options.readline_omit__names
 
109
    if omit__names == 1:
 
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('_')]
 
113
 
 
114
    #print '\nastart:<%r>' % attr_start  # dbg
 
115
 
 
116
    if len(attr_start)<COMPLETE_THRESHOLD:
 
117
        attrs = list(set(attrs) - TRAIT_NAMES)
 
118
        
 
119
    # The base of the completion, so we can form the final results list
 
120
    bdot = base+'.'
 
121
 
 
122
    tcomp = [bdot+a for a in attrs]
 
123
    #print 'tcomp:',tcomp
 
124
    return tcomp
 
125
 
 
126
def activate(complete_threshold = COMPLETE_THRESHOLD):
 
127
    """Activate the Traits completer.
 
128
 
 
129
    :Keywords:
 
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."""
 
133
    
 
134
    if not (isinstance(complete_threshold,int) and
 
135
            complete_threshold>0):
 
136
        e='complete_threshold must be a positive integer, not %r'  % \
 
137
           complete_threshold
 
138
        raise ValueError(e)
 
139
 
 
140
    # Set the module global
 
141
    global COMPLETE_THRESHOLD
 
142
    COMPLETE_THRESHOLD = complete_threshold
 
143
 
 
144
    # Activate the traits aware completer
 
145
    ip = ipget()
 
146
    ip.set_hook('complete_command', trait_completer, re_key = '.*')
 
147
 
 
148
 
 
149
#############################################################################
 
150
if __name__ == '__main__':
 
151
    # Testing/debugging
 
152
 
 
153
    # A sorted list of the names we'll filter out
 
154
    TNL = list(TRAIT_NAMES)
 
155
    TNL.sort()
 
156
 
 
157
    # Make a few objects for testing
 
158
    class TClean(T.HasTraits): pass
 
159
    class Bunch(object): pass
 
160
    # A clean traits object
 
161
    t = TClean()
 
162
    # A nested object containing t
 
163
    f = Bunch()
 
164
    f.t = t
 
165
    # And a naked new-style object
 
166
    o = object()
 
167
 
 
168
    ip = ipget().IP
 
169
    
 
170
    # A few simplistic tests
 
171
 
 
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
 
176
 
 
177
    assert ip.complete('t.ed') ==[]
 
178
 
 
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
 
181
    # time...
 
182
    ta = ip.complete('t.edi')
 
183
    assert ta == ['t.edit_traits', 't.editable_traits']
 
184
    print 'Tests OK'