##// END OF EJS Templates
- Implement a traits-aware tab-completer. See ipy_traits_completer in...
fperez -
Show More
@@ -0,0 +1,173 b''
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
58 #############################################################################
59 # Module constants
60
61 # The completion threshold
62 # This is currently implemented as a module global, since this sytem isn't
63 # likely to be modified at runtime by multiple instances. If needed in the
64 # future, we can always make it local to the completer as a function attribute.
65 COMPLETE_THRESHOLD = 3
66
67 # Set of names that Traits automatically adds to ANY traits-inheriting object.
68 # These are the names we'll filter out.
69 TRAIT_NAMES = set( dir2(T.HasTraits()) ) - set( dir2(object()) )
70
71 #############################################################################
72 # Code begins
73
74 def trait_completer(self,event):
75 """A custom IPython tab-completer that is traits-aware.
76
77 It tries to hide the internal traits attributes, and reveal them only when
78 it can reasonably guess that the user really is after one of them.
79 """
80
81 #print '\nevent is:',event # dbg
82 symbol_parts = event.symbol.split('.')
83 base = '.'.join(symbol_parts[:-1])
84 #print 'base:',base # dbg
85
86 oinfo = self._ofind(base)
87 if not oinfo['found']:
88 raise TryNext
89
90 obj = oinfo['obj']
91 # OK, we got the object. See if it's traits, else punt
92 if not isinstance(obj,T.HasTraits):
93 raise TryNext
94
95 # it's a traits object, don't show the tr* attributes unless the completion
96 # begins with 'tr'
97 attrs = dir2(obj)
98 # Now, filter out the attributes that start with the user's request
99 attr_start = symbol_parts[-1]
100 if attr_start:
101 attrs = [a for a in attrs if a.startswith(attr_start)]
102
103 #print '\nastart:<%r>' % attr_start # dbg
104
105 if len(attr_start)<COMPLETE_THRESHOLD:
106 attrs = list(set(attrs) - TRAIT_NAMES)
107
108 # The base of the completion, so we can form the final results list
109 bdot = base+'.'
110
111 tcomp = [bdot+a for a in attrs]
112 #print 'tcomp:',tcomp
113 return tcomp
114
115 def activate(complete_threshold = COMPLETE_THRESHOLD):
116 """Activate the Traits completer.
117
118 :Keywords:
119 complete_threshold : int
120 The minimum number of letters that a user must type in order to
121 activate completion of traits-private names."""
122
123 if not (isinstance(complete_threshold,int) and
124 complete_threshold>0):
125 e='complete_threshold must be a positive integer, not %r' % \
126 complete_threshold
127 raise ValueError(e)
128
129 # Set the module global
130 global COMPLETE_THRESHOLD
131 COMPLETE_THRESHOLD = complete_threshold
132
133 # Activate the traits aware completer
134 ip = ipget()
135 ip.set_hook('complete_command', trait_completer, re_key = '.*')
136
137
138 #############################################################################
139 if __name__ == '__main__':
140 # Testing/debugging
141
142 # A sorted list of the names we'll filter out
143 TNL = list(TRAIT_NAMES)
144 TNL.sort()
145
146 # Make a few objects for testing
147 class TClean(T.HasTraits): pass
148 class Bunch(object): pass
149 # A clean traits object
150 t = TClean()
151 # A nested object containing t
152 f = Bunch()
153 f.t = t
154 # And a naked new-style object
155 o = object()
156
157 ip = ipget().IP
158
159 # A few simplistic tests
160
161 # Reset the threshold to the default, in case the test is running inside an
162 # instance of ipython that changed it
163 import ipy_traits_completer
164 ipy_traits_completer.COMPLETE_THRESHOLD = 3
165
166 assert ip.complete('t.ed') ==[]
167
168 # For some bizarre reason, these fail on the first time I run them, but not
169 # afterwards. Traits does some really weird stuff at object instantiation
170 # time...
171 ta = ip.complete('t.edi')
172 assert ta == ['t.edit_traits', 't.editable_traits']
173 print 'Tests OK'
@@ -6,7 +6,7 b' Uses syntax highlighting for presenting the various information elements.'
6 Similar in spirit to the inspect module, but all calls take a name argument to
6 Similar in spirit to the inspect module, but all calls take a name argument to
7 reference the name under which an object is being read.
7 reference the name under which an object is being read.
8
8
9 $Id: OInspect.py 2558 2007-07-25 19:54:28Z fperez $
9 $Id: OInspect.py 2568 2007-07-29 21:38:44Z fperez $
10 """
10 """
11
11
12 #*****************************************************************************
12 #*****************************************************************************
@@ -468,8 +468,9 b' class Inspector:'
468 if ds:
468 if ds:
469 class_ds = getdoc(obj.__class__)
469 class_ds = getdoc(obj.__class__)
470 # Skip Python's auto-generated docstrings
470 # Skip Python's auto-generated docstrings
471 if class_ds.startswith('function(code, globals[, name[,') or \
471 if class_ds and \
472 class_ds.startswith('instancemethod(function, instance,'):
472 (class_ds.startswith('function(code, globals[,') or \
473 class_ds.startswith('instancemethod(function, instance,')):
473 class_ds = None
474 class_ds = None
474 if class_ds and ds != class_ds:
475 if class_ds and ds != class_ds:
475 out.writeln(header('Class Docstring:\n') +
476 out.writeln(header('Class Docstring:\n') +
@@ -530,6 +531,8 b' class Inspector:'
530 - show_all(False): show all names, including those starting with
531 - show_all(False): show all names, including those starting with
531 underscores.
532 underscores.
532 """
533 """
534 #print 'ps pattern:<%r>' % pattern # dbg
535
533 # defaults
536 # defaults
534 type_pattern = 'all'
537 type_pattern = 'all'
535 filter = ''
538 filter = ''
@@ -84,17 +84,10 b' try:'
84 except NameError:
84 except NameError:
85 from sets import Set as set
85 from sets import Set as set
86
86
87 from IPython.genutils import debugx
87 from IPython.genutils import debugx, dir2
88
88
89 __all__ = ['Completer','IPCompleter']
89 __all__ = ['Completer','IPCompleter']
90
90
91 def get_class_members(cls):
92 ret = dir(cls)
93 if hasattr(cls,'__bases__'):
94 for base in cls.__bases__:
95 ret.extend(get_class_members(base))
96 return ret
97
98 class Completer:
91 class Completer:
99 def __init__(self,namespace=None,global_namespace=None):
92 def __init__(self,namespace=None,global_namespace=None):
100 """Create a new completer for the command line.
93 """Create a new completer for the command line.
@@ -199,55 +192,15 b' class Completer:'
199
192
200 expr, attr = m.group(1, 3)
193 expr, attr = m.group(1, 3)
201 try:
194 try:
202 object = eval(expr, self.namespace)
195 obj = eval(expr, self.namespace)
203 except:
196 except:
204 try:
197 try:
205 object = eval(expr, self.global_namespace)
198 obj = eval(expr, self.global_namespace)
206 except:
199 except:
207 return []
200 return []
208
209
210 # Start building the attribute list via dir(), and then complete it
211 # with a few extra special-purpose calls.
212 words = dir(object)
213
214 if hasattr(object,'__class__'):
215 words.append('__class__')
216 words.extend(get_class_members(object.__class__))
217
218 # Some libraries (such as traits) may introduce duplicates, we want to
219 # track and clean this up if it happens
220 may_have_dupes = False
221
222 # this is the 'dir' function for objects with Enthought's traits
223 if hasattr(object, 'trait_names'):
224 try:
225 words.extend(object.trait_names())
226 may_have_dupes = True
227 except TypeError:
228 # This will happen if `object` is a class and not an instance.
229 pass
230
231 # Support for PyCrust-style _getAttributeNames magic method.
232 if hasattr(object, '_getAttributeNames'):
233 try:
234 words.extend(object._getAttributeNames())
235 may_have_dupes = True
236 except TypeError:
237 # `object` is a class and not an instance. Ignore
238 # this error.
239 pass
240
201
241 if may_have_dupes:
202 words = dir2(obj)
242 # eliminate possible duplicates, as some traits may also
243 # appear as normal attributes in the dir() call.
244 words = list(set(words))
245 words.sort()
246
203
247 # filter out non-string attributes which may be stuffed by dir() calls
248 # and poor coding in third-party modules
249 words = [w for w in words
250 if isinstance(w, basestring) and w != "__builtins__"]
251 # Build match list to return
204 # Build match list to return
252 n = len(attr)
205 n = len(attr)
253 return ["%s.%s" % (expr, w) for w in words if w[:n] == attr ]
206 return ["%s.%s" % (expr, w) for w in words if w[:n] == attr ]
@@ -566,7 +519,7 b' class IPCompleter(Completer):'
566 return argMatches
519 return argMatches
567
520
568 def dispatch_custom_completer(self,text):
521 def dispatch_custom_completer(self,text):
569 # print "Custom! '%s' %s" % (text, self.custom_completers) # dbg
522 #print "Custom! '%s' %s" % (text, self.custom_completers) # dbg
570 line = self.full_lbuf
523 line = self.full_lbuf
571 if not line.strip():
524 if not line.strip():
572 return None
525 return None
@@ -590,7 +543,7 b' class IPCompleter(Completer):'
590 self.custom_completers.s_matches(cmd),
543 self.custom_completers.s_matches(cmd),
591 try_magic,
544 try_magic,
592 self.custom_completers.flat_matches(self.lbuf)):
545 self.custom_completers.flat_matches(self.lbuf)):
593 # print "try",c # dbg
546 #print "try",c # dbg
594 try:
547 try:
595 res = c(event)
548 res = c(event)
596 return [r for r in res if r.lower().startswith(text.lower())]
549 return [r for r in res if r.lower().startswith(text.lower())]
@@ -5,7 +5,7 b' General purpose utilities.'
5 This is a grab-bag of stuff I find useful in most programs I write. Some of
5 This is a grab-bag of stuff I find useful in most programs I write. Some of
6 these things are also convenient when working at the command line.
6 these things are also convenient when working at the command line.
7
7
8 $Id: genutils.py 2439 2007-06-14 18:41:48Z vivainio $"""
8 $Id: genutils.py 2568 2007-07-29 21:38:44Z fperez $"""
9
9
10 #*****************************************************************************
10 #*****************************************************************************
11 # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu>
11 # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu>
@@ -1742,6 +1742,70 b' def map_method(method,object_list,*argseq,**kw):'
1742 return out_list
1742 return out_list
1743
1743
1744 #----------------------------------------------------------------------------
1744 #----------------------------------------------------------------------------
1745 def get_class_members(cls):
1746 ret = dir(cls)
1747 if hasattr(cls,'__bases__'):
1748 for base in cls.__bases__:
1749 ret.extend(get_class_members(base))
1750 return ret
1751
1752 #----------------------------------------------------------------------------
1753 def dir2(obj):
1754 """dir2(obj) -> list of strings
1755
1756 Extended version of the Python builtin dir(), which does a few extra
1757 checks, and supports common objects with unusual internals that confuse
1758 dir(), such as Traits and PyCrust.
1759
1760 This version is guaranteed to return only a list of true strings, whereas
1761 dir() returns anything that objects inject into themselves, even if they
1762 are later not really valid for attribute access (many extension libraries
1763 have such bugs).
1764 """
1765
1766 # Start building the attribute list via dir(), and then complete it
1767 # with a few extra special-purpose calls.
1768 words = dir(obj)
1769
1770 if hasattr(obj,'__class__'):
1771 words.append('__class__')
1772 words.extend(get_class_members(obj.__class__))
1773 #if '__base__' in words: 1/0
1774
1775 # Some libraries (such as traits) may introduce duplicates, we want to
1776 # track and clean this up if it happens
1777 may_have_dupes = False
1778
1779 # this is the 'dir' function for objects with Enthought's traits
1780 if hasattr(obj, 'trait_names'):
1781 try:
1782 words.extend(obj.trait_names())
1783 may_have_dupes = True
1784 except TypeError:
1785 # This will happen if `obj` is a class and not an instance.
1786 pass
1787
1788 # Support for PyCrust-style _getAttributeNames magic method.
1789 if hasattr(obj, '_getAttributeNames'):
1790 try:
1791 words.extend(obj._getAttributeNames())
1792 may_have_dupes = True
1793 except TypeError:
1794 # `obj` is a class and not an instance. Ignore
1795 # this error.
1796 pass
1797
1798 if may_have_dupes:
1799 # eliminate possible duplicates, as some traits may also
1800 # appear as normal attributes in the dir() call.
1801 words = list(set(words))
1802 words.sort()
1803
1804 # filter out non-string attributes which may be stuffed by dir() calls
1805 # and poor coding in third-party modules
1806 return [w for w in words if isinstance(w, basestring)]
1807
1808 #----------------------------------------------------------------------------
1745 def import_fail_info(mod_name,fns=None):
1809 def import_fail_info(mod_name,fns=None):
1746 """Inform load failure for a module."""
1810 """Inform load failure for a module."""
1747
1811
@@ -22,6 +22,8 b' import pprint'
22 import re
22 import re
23 import types
23 import types
24
24
25 from IPython.genutils import dir2
26
25 def create_typestr2type_dicts(dont_include_in_type2type2str=["lambda"]):
27 def create_typestr2type_dicts(dont_include_in_type2type2str=["lambda"]):
26 """Return dictionaries mapping lower case typename to type objects, from
28 """Return dictionaries mapping lower case typename to type objects, from
27 the types package, and vice versa."""
29 the types package, and vice versa."""
@@ -62,7 +64,6 b' def show_hidden(str,show_all=False):'
62 """Return true for strings starting with single _ if show_all is true."""
64 """Return true for strings starting with single _ if show_all is true."""
63 return show_all or str.startswith("__") or not str.startswith("_")
65 return show_all or str.startswith("__") or not str.startswith("_")
64
66
65
66 class NameSpace(object):
67 class NameSpace(object):
67 """NameSpace holds the dictionary for a namespace and implements filtering
68 """NameSpace holds the dictionary for a namespace and implements filtering
68 on name and types"""
69 on name and types"""
@@ -78,8 +79,20 b' class NameSpace(object):'
78 if type(obj) == types.DictType:
79 if type(obj) == types.DictType:
79 self._ns = obj
80 self._ns = obj
80 else:
81 else:
81 self._ns = dict([(key,getattr(obj,key)) for key in dir(obj)
82 kv = []
82 if isinstance(key, basestring)])
83 for key in dir2(obj):
84 if isinstance(key, basestring):
85 # This seemingly unnecessary try/except is actually needed
86 # because there is code out there with metaclasses that
87 # create 'write only' attributes, where a getattr() call
88 # will fail even if the attribute appears listed in the
89 # object's dictionary. Properties can actually do the same
90 # thing. In particular, Traits use this pattern
91 try:
92 kv.append((key,getattr(obj,key)))
93 except AttributeError:
94 pass
95 self._ns = dict(kv)
83
96
84 def get_ns(self):
97 def get_ns(self):
85 """Return name space dictionary with objects matching type and name patterns."""
98 """Return name space dictionary with objects matching type and name patterns."""
@@ -1,3 +1,20 b''
1 2007-07-29 Fernando Perez <Fernando.Perez@colorado.edu>
2
3 * IPython/Extensions/ipy_traits_completer.py: Add a new custom
4 completer that it traits-aware, so that traits objects don't show
5 all of their internal attributes all the time.
6
7 * IPython/genutils.py (dir2): moved this code from inside
8 completer.py to expose it publicly, so I could use it in the
9 wildcards bugfix.
10
11 * IPython/wildcard.py (NameSpace.__init__): fix a bug reported by
12 Stefan with Traits.
13
14 * IPython/completer.py (Completer.attr_matches): change internal
15 var name from 'object' to 'obj', since 'object' is now a builtin
16 and this can lead to weird bugs if reusing this code elsewhere.
17
1 2007-07-25 Fernando Perez <Fernando.Perez@colorado.edu>
18 2007-07-25 Fernando Perez <Fernando.Perez@colorado.edu>
2
19
3 * IPython/OInspect.py (Inspector.pinfo): fix small glitches in
20 * IPython/OInspect.py (Inspector.pinfo): fix small glitches in
General Comments 0
You need to be logged in to leave comments. Login now