1 """CallTips.py - An IDLE Extension to Jog Your Memory 2 3 Call Tips are floating windows which display function, class, and method 4 parameter and docstring information when you type an opening parenthesis, and 5 which disappear when you type a closing parenthesis. 6 7 """ 8 import re 9 import sys 10 import types 11 12 from idlelib import CallTipWindow 13 from idlelib.HyperParser import HyperParser 14 15 import __main__ 16 17 class CallTips: 18 19 menudefs = [ 20 ('edit', [ 21 ("Show call tip", "<<force-open-calltip>>"), 22 ]) 23 ] 24 25 def __init__(self, editwin=None): 26 if editwin is None: # subprocess and test 27 self.editwin = None 28 return 29 self.editwin = editwin 30 self.text = editwin.text 31 self.calltip = None 32 self._make_calltip_window = self._make_tk_calltip_window 33 34 def close(self): 35 self._make_calltip_window = None 36 37 def _make_tk_calltip_window(self): 38 # See __init__ for usage 39 return CallTipWindow.CallTip(self.text) 40 41 def _remove_calltip_window(self, event=None): 42 if self.calltip: 43 self.calltip.hidetip() 44 self.calltip = None 45 46 def force_open_calltip_event(self, event): 47 """Happens when the user really wants to open a CallTip, even if a 48 function call is needed. 49 """ 50 self.open_calltip(True) 51 52 def try_open_calltip_event(self, event): 53 """Happens when it would be nice to open a CallTip, but not really 54 necessary, for example after an opening bracket, so function calls 55 won't be made. 56 """ 57 self.open_calltip(False) 58 59 def refresh_calltip_event(self, event): 60 """If there is already a calltip window, check if it is still needed, 61 and if so, reload it. 62 """ 63 if self.calltip and self.calltip.is_active(): 64 self.open_calltip(False) 65 66 def open_calltip(self, evalfuncs): 67 self._remove_calltip_window() 68 69 hp = HyperParser(self.editwin, "insert") 70 sur_paren = hp.get_surrounding_brackets('(') 71 if not sur_paren: 72 return 73 hp.set_index(sur_paren[0]) 74 expression = hp.get_expression() 75 if not expression or (not evalfuncs and expression.find('(') != -1): 76 return 77 arg_text = self.fetch_tip(expression) 78 if not arg_text: 79 return 80 self.calltip = self._make_calltip_window() 81 self.calltip.showtip(arg_text, sur_paren[0], sur_paren[1]) 82 83 def fetch_tip(self, expression): 84 """Return the argument list and docstring of a function or class 85 86 If there is a Python subprocess, get the calltip there. Otherwise, 87 either fetch_tip() is running in the subprocess itself or it was called 88 in an IDLE EditorWindow before any script had been run. 89 90 The subprocess environment is that of the most recently run script. If 91 two unrelated modules are being edited some calltips in the current 92 module may be inoperative if the module was not the last to run. 93 94 To find methods, fetch_tip must be fed a fully qualified name. 95 96 """ 97 try: 98 rpcclt = self.editwin.flist.pyshell.interp.rpcclt 99 except AttributeError: 100 rpcclt = None 101 if rpcclt: 102 return rpcclt.remotecall("exec", "get_the_calltip", 103 (expression,), {}) 104 else: 105 entity = self.get_entity(expression) 106 return get_arg_text(entity) 107 108 def get_entity(self, expression): 109 """Return the object corresponding to expression evaluated 110 in a namespace spanning sys.modules and __main.dict__. 111 """ 112 if expression: 113 namespace = sys.modules.copy() 114 namespace.update(__main__.__dict__) 115 try: 116 return eval(expression, namespace) 117 except BaseException: 118 # An uncaught exception closes idle, and eval can raise any 119 # exception, especially if user classes are involved. 120 return None 121 122 def _find_constructor(class_ob): 123 # Given a class object, return a function object used for the 124 # constructor (ie, __init__() ) or None if we can't find one. 125 try: 126 return class_ob.__init__.im_func 127 except AttributeError: 128 for base in class_ob.__bases__: 129 rc = _find_constructor(base) 130 if rc is not None: return rc 131 return None 132 133 def get_arg_text(ob): 134 """Get a string describing the arguments for the given object, 135 only if it is callable.""" 136 arg_text = "" 137 if ob is not None and hasattr(ob, '__call__'): 138 arg_offset = 0 139 if type(ob) in (types.ClassType, types.TypeType): 140 # Look for the highest __init__ in the class chain. 141 fob = _find_constructor(ob) 142 if fob is None: 143 fob = lambda: None 144 else: 145 arg_offset = 1 146 elif type(ob)==types.MethodType: 147 # bit of a hack for methods - turn it into a function 148 # but we drop the "self" param. 149 fob = ob.im_func 150 arg_offset = 1 151 else: 152 fob = ob 153 # Try to build one for Python defined functions 154 if type(fob) in [types.FunctionType, types.LambdaType]: 155 argcount = fob.func_code.co_argcount 156 real_args = fob.func_code.co_varnames[arg_offset:argcount] 157 defaults = fob.func_defaults or [] 158 defaults = list(map(lambda name: "=%s" % repr(name), defaults)) 159 defaults = [""] * (len(real_args) - len(defaults)) + defaults 160 items = map(lambda arg, dflt: arg + dflt, real_args, defaults) 161 if fob.func_code.co_flags & 0x4: 162 items.append("...") 163 if fob.func_code.co_flags & 0x8: 164 items.append("***") 165 arg_text = ", ".join(items) 166 arg_text = "(%s)" % re.sub("\.\d+", "<tuple>", arg_text) 167 # See if we can use the docstring 168 doc = getattr(ob, "__doc__", "") 169 if doc: 170 doc = doc.lstrip() 171 pos = doc.find("\n") 172 if pos < 0 or pos > 70: 173 pos = 70 174 if arg_text: 175 arg_text += "\n" 176 arg_text += doc[:pos] 177 return arg_text 178 179 ################################################# 180 # 181 # Test code 182 # 183 if __name__=='__main__': 184 185 def t1(): "()" 186 def t2(a, b=None): "(a, b=None)" 187 def t3(a, *args): "(a, ...)" 188 def t4(*args): "(...)" 189 def t5(a, *args): "(a, ...)" 190 def t6(a, b=None, *args, **kw): "(a, b=None, ..., ***)" 191 def t7((a, b), c, (d, e)): "(<tuple>, c, <tuple>)" 192 193 class TC(object): 194 "(ai=None, ...)" 195 def __init__(self, ai=None, *b): "(ai=None, ...)" 196 def t1(self): "()" 197 def t2(self, ai, b=None): "(ai, b=None)" 198 def t3(self, ai, *args): "(ai, ...)" 199 def t4(self, *args): "(...)" 200 def t5(self, ai, *args): "(ai, ...)" 201 def t6(self, ai, b=None, *args, **kw): "(ai, b=None, ..., ***)" 202 def t7(self, (ai, b), c, (d, e)): "(<tuple>, c, <tuple>)" 203 204 def test(tests): 205 ct = CallTips() 206 failed=[] 207 for t in tests: 208 expected = t.__doc__ + "\n" + t.__doc__ 209 name = t.__name__ 210 # exercise fetch_tip(), not just get_arg_text() 211 try: 212 qualified_name = "%s.%s" % (t.im_class.__name__, name) 213 except AttributeError: 214 qualified_name = name 215 arg_text = ct.fetch_tip(qualified_name) 216 if arg_text != expected: 217 failed.append(t) 218 fmt = "%s - expected %s, but got %s" 219 print fmt % (t.__name__, expected, get_arg_text(t)) 220 print "%d of %d tests failed" % (len(failed), len(tests)) 221 222 tc = TC() 223 tests = (t1, t2, t3, t4, t5, t6, t7, 224 TC, tc.t1, tc.t2, tc.t3, tc.t4, tc.t5, tc.t6, tc.t7) 225 226 test(tests) 227