0001 """CallTips.py - An IDLE Extension to Jog Your Memory 0002 0003 Call Tips are floating windows which display function, class, and method 0004 parameter and docstring information when you type an opening parenthesis, and 0005 which disappear when you type a closing parenthesis. 0006 0007 Future plans include extending the functionality to include class attributes. 0008 0009 """ 0010 import sys 0011 import string 0012 import types 0013 0014 import CallTipWindow 0015 0016 import __main__ 0017 0018 class CallTips: 0019 0020 menudefs = [ 0021 ] 0022 0023 def __init__(self, editwin=None): 0024 if editwin is None: # subprocess and test 0025 self.editwin = None 0026 return 0027 self.editwin = editwin 0028 self.text = editwin.text 0029 self.calltip = None 0030 self._make_calltip_window = self._make_tk_calltip_window 0031 0032 def close(self): 0033 self._make_calltip_window = None 0034 0035 def _make_tk_calltip_window(self): 0036 # See __init__ for usage 0037 return CallTipWindow.CallTip(self.text) 0038 0039 def _remove_calltip_window(self): 0040 if self.calltip: 0041 self.calltip.hidetip() 0042 self.calltip = None 0043 0044 def paren_open_event(self, event): 0045 self._remove_calltip_window() 0046 name = self.get_name_at_cursor() 0047 arg_text = self.fetch_tip(name) 0048 if arg_text: 0049 self.calltip_start = self.text.index("insert") 0050 self.calltip = self._make_calltip_window() 0051 self.calltip.showtip(arg_text) 0052 return "" #so the event is handled normally. 0053 0054 def paren_close_event(self, event): 0055 # Now just hides, but later we should check if other 0056 # paren'd expressions remain open. 0057 self._remove_calltip_window() 0058 return "" #so the event is handled normally. 0059 0060 def check_calltip_cancel_event(self, event): 0061 if self.calltip: 0062 # If we have moved before the start of the calltip, 0063 # or off the calltip line, then cancel the tip. 0064 # (Later need to be smarter about multi-line, etc) 0065 if self.text.compare("insert", "<=", self.calltip_start) or \ 0066 self.text.compare("insert", ">", self.calltip_start 0067 + " lineend"): 0068 self._remove_calltip_window() 0069 return "" #so the event is handled normally. 0070 0071 def calltip_cancel_event(self, event): 0072 self._remove_calltip_window() 0073 return "" #so the event is handled normally. 0074 0075 __IDCHARS = "._" + string.ascii_letters + string.digits 0076 0077 def get_name_at_cursor(self): 0078 idchars = self.__IDCHARS 0079 str = self.text.get("insert linestart", "insert") 0080 i = len(str) 0081 while i and str[i-1] in idchars: 0082 i -= 1 0083 return str[i:] 0084 0085 def fetch_tip(self, name): 0086 """Return the argument list and docstring of a function or class 0087 0088 If there is a Python subprocess, get the calltip there. Otherwise, 0089 either fetch_tip() is running in the subprocess itself or it was called 0090 in an IDLE EditorWindow before any script had been run. 0091 0092 The subprocess environment is that of the most recently run script. If 0093 two unrelated modules are being edited some calltips in the current 0094 module may be inoperative if the module was not the last to run. 0095 0096 """ 0097 try: 0098 rpcclt = self.editwin.flist.pyshell.interp.rpcclt 0099 except: 0100 rpcclt = None 0101 if rpcclt: 0102 return rpcclt.remotecall("exec", "get_the_calltip", 0103 (name,), {}) 0104 else: 0105 entity = self.get_entity(name) 0106 return get_arg_text(entity) 0107 0108 def get_entity(self, name): 0109 "Lookup name in a namespace spanning sys.modules and __main.dict__" 0110 if name: 0111 namespace = sys.modules.copy() 0112 namespace.update(__main__.__dict__) 0113 try: 0114 return eval(name, namespace) 0115 except: 0116 return None 0117 0118 def _find_constructor(class_ob): 0119 # Given a class object, return a function object used for the 0120 # constructor (ie, __init__() ) or None if we can't find one. 0121 try: 0122 return class_ob.__init__.im_func 0123 except AttributeError: 0124 for base in class_ob.__bases__: 0125 rc = _find_constructor(base) 0126 if rc is not None: return rc 0127 return None 0128 0129 def get_arg_text(ob): 0130 "Get a string describing the arguments for the given object" 0131 argText = "" 0132 if ob is not None: 0133 argOffset = 0 0134 if type(ob)==types.ClassType: 0135 # Look for the highest __init__ in the class chain. 0136 fob = _find_constructor(ob) 0137 if fob is None: 0138 fob = lambda: None 0139 else: 0140 argOffset = 1 0141 elif type(ob)==types.MethodType: 0142 # bit of a hack for methods - turn it into a function 0143 # but we drop the "self" param. 0144 fob = ob.im_func 0145 argOffset = 1 0146 else: 0147 fob = ob 0148 # Try and build one for Python defined functions 0149 if type(fob) in [types.FunctionType, types.LambdaType]: 0150 try: 0151 realArgs = fob.func_code.co_varnames[argOffset:fob.func_code.co_argcount] 0152 defaults = fob.func_defaults or [] 0153 defaults = list(map(lambda name: "=%s" % name, defaults)) 0154 defaults = [""] * (len(realArgs)-len(defaults)) + defaults 0155 items = map(lambda arg, dflt: arg+dflt, realArgs, defaults) 0156 if fob.func_code.co_flags & 0x4: 0157 items.append("...") 0158 if fob.func_code.co_flags & 0x8: 0159 items.append("***") 0160 argText = ", ".join(items) 0161 argText = "(%s)" % argText 0162 except: 0163 pass 0164 # See if we can use the docstring 0165 doc = getattr(ob, "__doc__", "") 0166 if doc: 0167 doc = doc.lstrip() 0168 pos = doc.find("\n") 0169 if pos < 0 or pos > 70: 0170 pos = 70 0171 if argText: 0172 argText += "\n" 0173 argText += doc[:pos] 0174 return argText 0175 0176 ################################################# 0177 # 0178 # Test code 0179 # 0180 if __name__=='__main__': 0181 0182 def t1(): "()" 0183 def t2(a, b=None): "(a, b=None)" 0184 def t3(a, *args): "(a, ...)" 0185 def t4(*args): "(...)" 0186 def t5(a, *args): "(a, ...)" 0187 def t6(a, b=None, *args, **kw): "(a, b=None, ..., ***)" 0188 0189 class TC: 0190 "(a=None, ...)" 0191 def __init__(self, a=None, *b): "(a=None, ...)" 0192 def t1(self): "()" 0193 def t2(self, a, b=None): "(a, b=None)" 0194 def t3(self, a, *args): "(a, ...)" 0195 def t4(self, *args): "(...)" 0196 def t5(self, a, *args): "(a, ...)" 0197 def t6(self, a, b=None, *args, **kw): "(a, b=None, ..., ***)" 0198 0199 def test(tests): 0200 ct = CallTips() 0201 failed=[] 0202 for t in tests: 0203 expected = t.__doc__ + "\n" + t.__doc__ 0204 name = t.__name__ 0205 arg_text = ct.fetch_tip(name) 0206 if arg_text != expected: 0207 failed.append(t) 0208 print "%s - expected %s, but got %s" % (t, expected, 0209 get_arg_text(entity)) 0210 print "%d of %d tests failed" % (len(failed), len(tests)) 0211 0212 tc = TC() 0213 tests = (t1, t2, t3, t4, t5, t6, 0214 TC, tc.t1, tc.t2, tc.t3, tc.t4, tc.t5, tc.t6) 0215 0216 test(tests) 0217
Generated by PyXR 0.9.4