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