Show More
@@ -0,0 +1,173 | |||
|
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 Uses syntax highlighting for presenting the various information elements. | |||
|
6 | 6 | Similar in spirit to the inspect module, but all calls take a name argument to |
|
7 | 7 | reference the name under which an object is being read. |
|
8 | 8 | |
|
9 |
$Id: OInspect.py 25 |
|
|
9 | $Id: OInspect.py 2568 2007-07-29 21:38:44Z fperez $ | |
|
10 | 10 | """ |
|
11 | 11 | |
|
12 | 12 | #***************************************************************************** |
@@ -468,8 +468,9 class Inspector: | |||
|
468 | 468 | if ds: |
|
469 | 469 | class_ds = getdoc(obj.__class__) |
|
470 | 470 | # Skip Python's auto-generated docstrings |
|
471 | if class_ds.startswith('function(code, globals[, name[,') or \ | |
|
472 |
class_ds.startswith(' |
|
|
471 | if class_ds and \ | |
|
472 | (class_ds.startswith('function(code, globals[,') or \ | |
|
473 | class_ds.startswith('instancemethod(function, instance,')): | |
|
473 | 474 | class_ds = None |
|
474 | 475 | if class_ds and ds != class_ds: |
|
475 | 476 | out.writeln(header('Class Docstring:\n') + |
@@ -530,6 +531,8 class Inspector: | |||
|
530 | 531 | - show_all(False): show all names, including those starting with |
|
531 | 532 | underscores. |
|
532 | 533 | """ |
|
534 | #print 'ps pattern:<%r>' % pattern # dbg | |
|
535 | ||
|
533 | 536 | # defaults |
|
534 | 537 | type_pattern = 'all' |
|
535 | 538 | filter = '' |
@@ -84,17 +84,10 try: | |||
|
84 | 84 | except NameError: |
|
85 | 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 | 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 | 91 | class Completer: |
|
99 | 92 | def __init__(self,namespace=None,global_namespace=None): |
|
100 | 93 | """Create a new completer for the command line. |
@@ -199,55 +192,15 class Completer: | |||
|
199 | 192 | |
|
200 | 193 | expr, attr = m.group(1, 3) |
|
201 | 194 | try: |
|
202 |
obj |
|
|
195 | obj = eval(expr, self.namespace) | |
|
203 | 196 | except: |
|
204 | 197 | try: |
|
205 |
obj |
|
|
198 | obj = eval(expr, self.global_namespace) | |
|
206 | 199 | except: |
|
207 | 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: | |
|
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() | |
|
202 | words = dir2(obj) | |
|
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 | 204 | # Build match list to return |
|
252 | 205 | n = len(attr) |
|
253 | 206 | return ["%s.%s" % (expr, w) for w in words if w[:n] == attr ] |
@@ -566,7 +519,7 class IPCompleter(Completer): | |||
|
566 | 519 | return argMatches |
|
567 | 520 | |
|
568 | 521 | def dispatch_custom_completer(self,text): |
|
569 |
# |
|
|
522 | #print "Custom! '%s' %s" % (text, self.custom_completers) # dbg | |
|
570 | 523 | line = self.full_lbuf |
|
571 | 524 | if not line.strip(): |
|
572 | 525 | return None |
@@ -590,7 +543,7 class IPCompleter(Completer): | |||
|
590 | 543 | self.custom_completers.s_matches(cmd), |
|
591 | 544 | try_magic, |
|
592 | 545 | self.custom_completers.flat_matches(self.lbuf)): |
|
593 |
# |
|
|
546 | #print "try",c # dbg | |
|
594 | 547 | try: |
|
595 | 548 | res = c(event) |
|
596 | 549 | return [r for r in res if r.lower().startswith(text.lower())] |
@@ -5,7 +5,7 General purpose utilities. | |||
|
5 | 5 | This is a grab-bag of stuff I find useful in most programs I write. Some of |
|
6 | 6 | these things are also convenient when working at the command line. |
|
7 | 7 | |
|
8 |
$Id: genutils.py 2 |
|
|
8 | $Id: genutils.py 2568 2007-07-29 21:38:44Z fperez $""" | |
|
9 | 9 | |
|
10 | 10 | #***************************************************************************** |
|
11 | 11 | # Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu> |
@@ -1742,6 +1742,70 def map_method(method,object_list,*argseq,**kw): | |||
|
1742 | 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 | 1809 | def import_fail_info(mod_name,fns=None): |
|
1746 | 1810 | """Inform load failure for a module.""" |
|
1747 | 1811 |
@@ -22,6 +22,8 import pprint | |||
|
22 | 22 | import re |
|
23 | 23 | import types |
|
24 | 24 | |
|
25 | from IPython.genutils import dir2 | |
|
26 | ||
|
25 | 27 | def create_typestr2type_dicts(dont_include_in_type2type2str=["lambda"]): |
|
26 | 28 | """Return dictionaries mapping lower case typename to type objects, from |
|
27 | 29 | the types package, and vice versa.""" |
@@ -62,7 +64,6 def show_hidden(str,show_all=False): | |||
|
62 | 64 | """Return true for strings starting with single _ if show_all is true.""" |
|
63 | 65 | return show_all or str.startswith("__") or not str.startswith("_") |
|
64 | 66 | |
|
65 | ||
|
66 | 67 | class NameSpace(object): |
|
67 | 68 | """NameSpace holds the dictionary for a namespace and implements filtering |
|
68 | 69 | on name and types""" |
@@ -78,8 +79,20 class NameSpace(object): | |||
|
78 | 79 | if type(obj) == types.DictType: |
|
79 | 80 | self._ns = obj |
|
80 | 81 | else: |
|
81 | self._ns = dict([(key,getattr(obj,key)) for key in dir(obj) | |
|
82 | if isinstance(key, basestring)]) | |
|
82 | kv = [] | |
|
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 | 97 | def get_ns(self): |
|
85 | 98 | """Return name space dictionary with objects matching type and name patterns.""" |
@@ -1,3 +1,20 | |||
|
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 | 18 | 2007-07-25 Fernando Perez <Fernando.Perez@colorado.edu> |
|
2 | 19 | |
|
3 | 20 | * IPython/OInspect.py (Inspector.pinfo): fix small glitches in |
General Comments 0
You need to be logged in to leave comments.
Login now