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 inspect 8import re 9import sys 10import textwrap 11import types 12 13from idlelib import calltip_w 14from idlelib.hyperparser import HyperParser 15import __main__ 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 self._remove_calltip_window() 59 60 hp = HyperParser(self.editwin, "insert") 61 sur_paren = hp.get_surrounding_brackets('(') 62 if not sur_paren: 63 return 64 hp.set_index(sur_paren[0]) 65 expression = hp.get_expression() 66 if not expression: 67 return 68 if not evalfuncs and (expression.find('(') != -1): 69 return 70 argspec = self.fetch_tip(expression) 71 if not argspec: 72 return 73 self.active_calltip = self._calltip_window() 74 self.active_calltip.showtip(argspec, sur_paren[0], sur_paren[1]) 75 76 def fetch_tip(self, expression): 77 """Return the argument list and docstring of a function or class. 78 79 If there is a Python subprocess, get the calltip there. Otherwise, 80 either this fetch_tip() is running in the subprocess or it was 81 called in an IDLE running without the subprocess. 82 83 The subprocess environment is that of the most recently run script. If 84 two unrelated modules are being edited some calltips in the current 85 module may be inoperative if the module was not the last to run. 86 87 To find methods, fetch_tip must be fed a fully qualified name. 88 89 """ 90 try: 91 rpcclt = self.editwin.flist.pyshell.interp.rpcclt 92 except AttributeError: 93 rpcclt = None 94 if rpcclt: 95 return rpcclt.remotecall("exec", "get_the_calltip", 96 (expression,), {}) 97 else: 98 return get_argspec(get_entity(expression)) 99 100 101def get_entity(expression): 102 """Return the object corresponding to expression evaluated 103 in a namespace spanning sys.modules and __main.dict__. 104 """ 105 if expression: 106 namespace = sys.modules.copy() 107 namespace.update(__main__.__dict__) 108 try: 109 return eval(expression, namespace) 110 except BaseException: 111 # An uncaught exception closes idle, and eval can raise any 112 # exception, especially if user classes are involved. 113 return None 114 115# The following are used in get_argspec and some in tests 116_MAX_COLS = 85 117_MAX_LINES = 5 # enough for bytes 118_INDENT = ' '*4 # for wrapped signatures 119_first_param = re.compile(r'(?<=\()\w*\,?\s*') 120_default_callable_argspec = "See source or doc" 121_invalid_method = "invalid method signature" 122_argument_positional = "\n['/' marks preceding arguments as positional-only]\n" 123 124def get_argspec(ob): 125 '''Return a string describing the signature of a callable object, or ''. 126 127 For Python-coded functions and methods, the first line is introspected. 128 Delete 'self' parameter for classes (.__init__) and bound methods. 129 The next lines are the first lines of the doc string up to the first 130 empty line or _MAX_LINES. For builtins, this typically includes 131 the arguments in addition to the return value. 132 ''' 133 argspec = default = "" 134 try: 135 ob_call = ob.__call__ 136 except BaseException: 137 return default 138 139 fob = ob_call if isinstance(ob_call, types.MethodType) else ob 140 141 try: 142 argspec = str(inspect.signature(fob)) 143 except ValueError as err: 144 msg = str(err) 145 if msg.startswith(_invalid_method): 146 return _invalid_method 147 148 if '/' in argspec: 149 """Using AC's positional argument should add the explain""" 150 argspec += _argument_positional 151 if isinstance(fob, type) and argspec == '()': 152 """fob with no argument, use default callable argspec""" 153 argspec = _default_callable_argspec 154 155 lines = (textwrap.wrap(argspec, _MAX_COLS, subsequent_indent=_INDENT) 156 if len(argspec) > _MAX_COLS else [argspec] if argspec else []) 157 158 if isinstance(ob_call, types.MethodType): 159 doc = ob_call.__doc__ 160 else: 161 doc = getattr(ob, "__doc__", "") 162 if doc: 163 for line in doc.split('\n', _MAX_LINES)[:_MAX_LINES]: 164 line = line.strip() 165 if not line: 166 break 167 if len(line) > _MAX_COLS: 168 line = line[: _MAX_COLS - 3] + '...' 169 lines.append(line) 170 argspec = '\n'.join(lines) 171 if not argspec: 172 argspec = _default_callable_argspec 173 return argspec 174 175 176if __name__ == '__main__': 177 from unittest import main 178 main('idlelib.idle_test.test_calltip', verbosity=2) 179