1"""Pop up a reminder of how to call a function. 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""" 7import __main__ 8import inspect 9import re 10import sys 11import textwrap 12import types 13 14from idlelib import calltip_w 15from idlelib.hyperparser import HyperParser 16 17 18class Calltip: 19 20 def __init__(self, editwin=None): 21 if editwin is None: # subprocess and test 22 self.editwin = None 23 else: 24 self.editwin = editwin 25 self.text = editwin.text 26 self.active_calltip = None 27 self._calltip_window = self._make_tk_calltip_window 28 29 def close(self): 30 self._calltip_window = None 31 32 def _make_tk_calltip_window(self): 33 # See __init__ for usage 34 return calltip_w.CalltipWindow(self.text) 35 36 def remove_calltip_window(self, event=None): 37 if self.active_calltip: 38 self.active_calltip.hidetip() 39 self.active_calltip = None 40 41 def force_open_calltip_event(self, event): 42 "The user selected the menu entry or hotkey, open the tip." 43 self.open_calltip(True) 44 return "break" 45 46 def try_open_calltip_event(self, event): 47 """Happens when it would be nice to open a calltip, but not really 48 necessary, for example after an opening bracket, so function calls 49 won't be made. 50 """ 51 self.open_calltip(False) 52 53 def refresh_calltip_event(self, event): 54 if self.active_calltip and self.active_calltip.tipwindow: 55 self.open_calltip(False) 56 57 def open_calltip(self, evalfuncs): 58 """Maybe close an existing calltip and maybe open a new calltip. 59 60 Called from (force_open|try_open|refresh)_calltip_event functions. 61 """ 62 hp = HyperParser(self.editwin, "insert") 63 sur_paren = hp.get_surrounding_brackets('(') 64 65 # If not inside parentheses, no calltip. 66 if not sur_paren: 67 self.remove_calltip_window() 68 return 69 70 # If a calltip is shown for the current parentheses, do 71 # nothing. 72 if self.active_calltip: 73 opener_line, opener_col = map(int, sur_paren[0].split('.')) 74 if ( 75 (opener_line, opener_col) == 76 (self.active_calltip.parenline, self.active_calltip.parencol) 77 ): 78 return 79 80 hp.set_index(sur_paren[0]) 81 try: 82 expression = hp.get_expression() 83 except ValueError: 84 expression = None 85 if not expression: 86 # No expression before the opening parenthesis, e.g. 87 # because it's in a string or the opener for a tuple: 88 # Do nothing. 89 return 90 91 # At this point, the current index is after an opening 92 # parenthesis, in a section of code, preceded by a valid 93 # expression. If there is a calltip shown, it's not for the 94 # same index and should be closed. 95 self.remove_calltip_window() 96 97 # Simple, fast heuristic: If the preceding expression includes 98 # an opening parenthesis, it likely includes a function call. 99 if not evalfuncs and (expression.find('(') != -1): 100 return 101 102 argspec = self.fetch_tip(expression) 103 if not argspec: 104 return 105 self.active_calltip = self._calltip_window() 106 self.active_calltip.showtip(argspec, sur_paren[0], sur_paren[1]) 107 108 def fetch_tip(self, expression): 109 """Return the argument list and docstring of a function or class. 110 111 If there is a Python subprocess, get the calltip there. Otherwise, 112 either this fetch_tip() is running in the subprocess or it was 113 called in an IDLE running without the subprocess. 114 115 The subprocess environment is that of the most recently run script. If 116 two unrelated modules are being edited some calltips in the current 117 module may be inoperative if the module was not the last to run. 118 119 To find methods, fetch_tip must be fed a fully qualified name. 120 121 """ 122 try: 123 rpcclt = self.editwin.flist.pyshell.interp.rpcclt 124 except AttributeError: 125 rpcclt = None 126 if rpcclt: 127 return rpcclt.remotecall("exec", "get_the_calltip", 128 (expression,), {}) 129 else: 130 return get_argspec(get_entity(expression)) 131 132 133def get_entity(expression): 134 """Return the object corresponding to expression evaluated 135 in a namespace spanning sys.modules and __main.dict__. 136 """ 137 if expression: 138 namespace = {**sys.modules, **__main__.__dict__} 139 try: 140 return eval(expression, namespace) # Only protect user code. 141 except BaseException: 142 # An uncaught exception closes idle, and eval can raise any 143 # exception, especially if user classes are involved. 144 return None 145 146# The following are used in get_argspec and some in tests 147_MAX_COLS = 85 148_MAX_LINES = 5 # enough for bytes 149_INDENT = ' '*4 # for wrapped signatures 150_first_param = re.compile(r'(?<=\()\w*\,?\s*') 151_default_callable_argspec = "See source or doc" 152_invalid_method = "invalid method signature" 153 154def get_argspec(ob): 155 '''Return a string describing the signature of a callable object, or ''. 156 157 For Python-coded functions and methods, the first line is introspected. 158 Delete 'self' parameter for classes (.__init__) and bound methods. 159 The next lines are the first lines of the doc string up to the first 160 empty line or _MAX_LINES. For builtins, this typically includes 161 the arguments in addition to the return value. 162 ''' 163 # Determine function object fob to inspect. 164 try: 165 ob_call = ob.__call__ 166 except BaseException: # Buggy user object could raise anything. 167 return '' # No popup for non-callables. 168 # For Get_argspecTest.test_buggy_getattr_class, CallA() & CallB(). 169 fob = ob_call if isinstance(ob_call, types.MethodType) else ob 170 171 # Initialize argspec and wrap it to get lines. 172 try: 173 argspec = str(inspect.signature(fob)) 174 except Exception as err: 175 msg = str(err) 176 if msg.startswith(_invalid_method): 177 return _invalid_method 178 else: 179 argspec = '' 180 181 if isinstance(fob, type) and argspec == '()': 182 # If fob has no argument, use default callable argspec. 183 argspec = _default_callable_argspec 184 185 lines = (textwrap.wrap(argspec, _MAX_COLS, subsequent_indent=_INDENT) 186 if len(argspec) > _MAX_COLS else [argspec] if argspec else []) 187 188 # Augment lines from docstring, if any, and join to get argspec. 189 doc = inspect.getdoc(ob) 190 if doc: 191 for line in doc.split('\n', _MAX_LINES)[:_MAX_LINES]: 192 line = line.strip() 193 if not line: 194 break 195 if len(line) > _MAX_COLS: 196 line = line[: _MAX_COLS - 3] + '...' 197 lines.append(line) 198 argspec = '\n'.join(lines) 199 200 return argspec or _default_callable_argspec 201 202 203if __name__ == '__main__': 204 from unittest import main 205 main('idlelib.idle_test.test_calltip', verbosity=2) 206