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 | 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 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 | 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(' |
|
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 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 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 class Completer: | |||||
199 |
|
192 | |||
200 | expr, attr = m.group(1, 3) |
|
193 | expr, attr = m.group(1, 3) | |
201 | try: |
|
194 | try: | |
202 |
obj |
|
195 | obj = eval(expr, self.namespace) | |
203 | except: |
|
196 | except: | |
204 | try: |
|
197 | try: | |
205 |
obj |
|
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 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 |
# |
|
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 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 |
# |
|
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 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 2 |
|
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 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 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 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 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 | |||||
|
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