17db96d56Sopenharmony_ci"""Pop up a reminder of how to call a function. 27db96d56Sopenharmony_ci 37db96d56Sopenharmony_ciCall Tips are floating windows which display function, class, and method 47db96d56Sopenharmony_ciparameter and docstring information when you type an opening parenthesis, and 57db96d56Sopenharmony_ciwhich disappear when you type a closing parenthesis. 67db96d56Sopenharmony_ci""" 77db96d56Sopenharmony_ciimport __main__ 87db96d56Sopenharmony_ciimport inspect 97db96d56Sopenharmony_ciimport re 107db96d56Sopenharmony_ciimport sys 117db96d56Sopenharmony_ciimport textwrap 127db96d56Sopenharmony_ciimport types 137db96d56Sopenharmony_ci 147db96d56Sopenharmony_cifrom idlelib import calltip_w 157db96d56Sopenharmony_cifrom idlelib.hyperparser import HyperParser 167db96d56Sopenharmony_ci 177db96d56Sopenharmony_ci 187db96d56Sopenharmony_ciclass Calltip: 197db96d56Sopenharmony_ci 207db96d56Sopenharmony_ci def __init__(self, editwin=None): 217db96d56Sopenharmony_ci if editwin is None: # subprocess and test 227db96d56Sopenharmony_ci self.editwin = None 237db96d56Sopenharmony_ci else: 247db96d56Sopenharmony_ci self.editwin = editwin 257db96d56Sopenharmony_ci self.text = editwin.text 267db96d56Sopenharmony_ci self.active_calltip = None 277db96d56Sopenharmony_ci self._calltip_window = self._make_tk_calltip_window 287db96d56Sopenharmony_ci 297db96d56Sopenharmony_ci def close(self): 307db96d56Sopenharmony_ci self._calltip_window = None 317db96d56Sopenharmony_ci 327db96d56Sopenharmony_ci def _make_tk_calltip_window(self): 337db96d56Sopenharmony_ci # See __init__ for usage 347db96d56Sopenharmony_ci return calltip_w.CalltipWindow(self.text) 357db96d56Sopenharmony_ci 367db96d56Sopenharmony_ci def remove_calltip_window(self, event=None): 377db96d56Sopenharmony_ci if self.active_calltip: 387db96d56Sopenharmony_ci self.active_calltip.hidetip() 397db96d56Sopenharmony_ci self.active_calltip = None 407db96d56Sopenharmony_ci 417db96d56Sopenharmony_ci def force_open_calltip_event(self, event): 427db96d56Sopenharmony_ci "The user selected the menu entry or hotkey, open the tip." 437db96d56Sopenharmony_ci self.open_calltip(True) 447db96d56Sopenharmony_ci return "break" 457db96d56Sopenharmony_ci 467db96d56Sopenharmony_ci def try_open_calltip_event(self, event): 477db96d56Sopenharmony_ci """Happens when it would be nice to open a calltip, but not really 487db96d56Sopenharmony_ci necessary, for example after an opening bracket, so function calls 497db96d56Sopenharmony_ci won't be made. 507db96d56Sopenharmony_ci """ 517db96d56Sopenharmony_ci self.open_calltip(False) 527db96d56Sopenharmony_ci 537db96d56Sopenharmony_ci def refresh_calltip_event(self, event): 547db96d56Sopenharmony_ci if self.active_calltip and self.active_calltip.tipwindow: 557db96d56Sopenharmony_ci self.open_calltip(False) 567db96d56Sopenharmony_ci 577db96d56Sopenharmony_ci def open_calltip(self, evalfuncs): 587db96d56Sopenharmony_ci """Maybe close an existing calltip and maybe open a new calltip. 597db96d56Sopenharmony_ci 607db96d56Sopenharmony_ci Called from (force_open|try_open|refresh)_calltip_event functions. 617db96d56Sopenharmony_ci """ 627db96d56Sopenharmony_ci hp = HyperParser(self.editwin, "insert") 637db96d56Sopenharmony_ci sur_paren = hp.get_surrounding_brackets('(') 647db96d56Sopenharmony_ci 657db96d56Sopenharmony_ci # If not inside parentheses, no calltip. 667db96d56Sopenharmony_ci if not sur_paren: 677db96d56Sopenharmony_ci self.remove_calltip_window() 687db96d56Sopenharmony_ci return 697db96d56Sopenharmony_ci 707db96d56Sopenharmony_ci # If a calltip is shown for the current parentheses, do 717db96d56Sopenharmony_ci # nothing. 727db96d56Sopenharmony_ci if self.active_calltip: 737db96d56Sopenharmony_ci opener_line, opener_col = map(int, sur_paren[0].split('.')) 747db96d56Sopenharmony_ci if ( 757db96d56Sopenharmony_ci (opener_line, opener_col) == 767db96d56Sopenharmony_ci (self.active_calltip.parenline, self.active_calltip.parencol) 777db96d56Sopenharmony_ci ): 787db96d56Sopenharmony_ci return 797db96d56Sopenharmony_ci 807db96d56Sopenharmony_ci hp.set_index(sur_paren[0]) 817db96d56Sopenharmony_ci try: 827db96d56Sopenharmony_ci expression = hp.get_expression() 837db96d56Sopenharmony_ci except ValueError: 847db96d56Sopenharmony_ci expression = None 857db96d56Sopenharmony_ci if not expression: 867db96d56Sopenharmony_ci # No expression before the opening parenthesis, e.g. 877db96d56Sopenharmony_ci # because it's in a string or the opener for a tuple: 887db96d56Sopenharmony_ci # Do nothing. 897db96d56Sopenharmony_ci return 907db96d56Sopenharmony_ci 917db96d56Sopenharmony_ci # At this point, the current index is after an opening 927db96d56Sopenharmony_ci # parenthesis, in a section of code, preceded by a valid 937db96d56Sopenharmony_ci # expression. If there is a calltip shown, it's not for the 947db96d56Sopenharmony_ci # same index and should be closed. 957db96d56Sopenharmony_ci self.remove_calltip_window() 967db96d56Sopenharmony_ci 977db96d56Sopenharmony_ci # Simple, fast heuristic: If the preceding expression includes 987db96d56Sopenharmony_ci # an opening parenthesis, it likely includes a function call. 997db96d56Sopenharmony_ci if not evalfuncs and (expression.find('(') != -1): 1007db96d56Sopenharmony_ci return 1017db96d56Sopenharmony_ci 1027db96d56Sopenharmony_ci argspec = self.fetch_tip(expression) 1037db96d56Sopenharmony_ci if not argspec: 1047db96d56Sopenharmony_ci return 1057db96d56Sopenharmony_ci self.active_calltip = self._calltip_window() 1067db96d56Sopenharmony_ci self.active_calltip.showtip(argspec, sur_paren[0], sur_paren[1]) 1077db96d56Sopenharmony_ci 1087db96d56Sopenharmony_ci def fetch_tip(self, expression): 1097db96d56Sopenharmony_ci """Return the argument list and docstring of a function or class. 1107db96d56Sopenharmony_ci 1117db96d56Sopenharmony_ci If there is a Python subprocess, get the calltip there. Otherwise, 1127db96d56Sopenharmony_ci either this fetch_tip() is running in the subprocess or it was 1137db96d56Sopenharmony_ci called in an IDLE running without the subprocess. 1147db96d56Sopenharmony_ci 1157db96d56Sopenharmony_ci The subprocess environment is that of the most recently run script. If 1167db96d56Sopenharmony_ci two unrelated modules are being edited some calltips in the current 1177db96d56Sopenharmony_ci module may be inoperative if the module was not the last to run. 1187db96d56Sopenharmony_ci 1197db96d56Sopenharmony_ci To find methods, fetch_tip must be fed a fully qualified name. 1207db96d56Sopenharmony_ci 1217db96d56Sopenharmony_ci """ 1227db96d56Sopenharmony_ci try: 1237db96d56Sopenharmony_ci rpcclt = self.editwin.flist.pyshell.interp.rpcclt 1247db96d56Sopenharmony_ci except AttributeError: 1257db96d56Sopenharmony_ci rpcclt = None 1267db96d56Sopenharmony_ci if rpcclt: 1277db96d56Sopenharmony_ci return rpcclt.remotecall("exec", "get_the_calltip", 1287db96d56Sopenharmony_ci (expression,), {}) 1297db96d56Sopenharmony_ci else: 1307db96d56Sopenharmony_ci return get_argspec(get_entity(expression)) 1317db96d56Sopenharmony_ci 1327db96d56Sopenharmony_ci 1337db96d56Sopenharmony_cidef get_entity(expression): 1347db96d56Sopenharmony_ci """Return the object corresponding to expression evaluated 1357db96d56Sopenharmony_ci in a namespace spanning sys.modules and __main.dict__. 1367db96d56Sopenharmony_ci """ 1377db96d56Sopenharmony_ci if expression: 1387db96d56Sopenharmony_ci namespace = {**sys.modules, **__main__.__dict__} 1397db96d56Sopenharmony_ci try: 1407db96d56Sopenharmony_ci return eval(expression, namespace) # Only protect user code. 1417db96d56Sopenharmony_ci except BaseException: 1427db96d56Sopenharmony_ci # An uncaught exception closes idle, and eval can raise any 1437db96d56Sopenharmony_ci # exception, especially if user classes are involved. 1447db96d56Sopenharmony_ci return None 1457db96d56Sopenharmony_ci 1467db96d56Sopenharmony_ci# The following are used in get_argspec and some in tests 1477db96d56Sopenharmony_ci_MAX_COLS = 85 1487db96d56Sopenharmony_ci_MAX_LINES = 5 # enough for bytes 1497db96d56Sopenharmony_ci_INDENT = ' '*4 # for wrapped signatures 1507db96d56Sopenharmony_ci_first_param = re.compile(r'(?<=\()\w*\,?\s*') 1517db96d56Sopenharmony_ci_default_callable_argspec = "See source or doc" 1527db96d56Sopenharmony_ci_invalid_method = "invalid method signature" 1537db96d56Sopenharmony_ci 1547db96d56Sopenharmony_cidef get_argspec(ob): 1557db96d56Sopenharmony_ci '''Return a string describing the signature of a callable object, or ''. 1567db96d56Sopenharmony_ci 1577db96d56Sopenharmony_ci For Python-coded functions and methods, the first line is introspected. 1587db96d56Sopenharmony_ci Delete 'self' parameter for classes (.__init__) and bound methods. 1597db96d56Sopenharmony_ci The next lines are the first lines of the doc string up to the first 1607db96d56Sopenharmony_ci empty line or _MAX_LINES. For builtins, this typically includes 1617db96d56Sopenharmony_ci the arguments in addition to the return value. 1627db96d56Sopenharmony_ci ''' 1637db96d56Sopenharmony_ci # Determine function object fob to inspect. 1647db96d56Sopenharmony_ci try: 1657db96d56Sopenharmony_ci ob_call = ob.__call__ 1667db96d56Sopenharmony_ci except BaseException: # Buggy user object could raise anything. 1677db96d56Sopenharmony_ci return '' # No popup for non-callables. 1687db96d56Sopenharmony_ci # For Get_argspecTest.test_buggy_getattr_class, CallA() & CallB(). 1697db96d56Sopenharmony_ci fob = ob_call if isinstance(ob_call, types.MethodType) else ob 1707db96d56Sopenharmony_ci 1717db96d56Sopenharmony_ci # Initialize argspec and wrap it to get lines. 1727db96d56Sopenharmony_ci try: 1737db96d56Sopenharmony_ci argspec = str(inspect.signature(fob)) 1747db96d56Sopenharmony_ci except Exception as err: 1757db96d56Sopenharmony_ci msg = str(err) 1767db96d56Sopenharmony_ci if msg.startswith(_invalid_method): 1777db96d56Sopenharmony_ci return _invalid_method 1787db96d56Sopenharmony_ci else: 1797db96d56Sopenharmony_ci argspec = '' 1807db96d56Sopenharmony_ci 1817db96d56Sopenharmony_ci if isinstance(fob, type) and argspec == '()': 1827db96d56Sopenharmony_ci # If fob has no argument, use default callable argspec. 1837db96d56Sopenharmony_ci argspec = _default_callable_argspec 1847db96d56Sopenharmony_ci 1857db96d56Sopenharmony_ci lines = (textwrap.wrap(argspec, _MAX_COLS, subsequent_indent=_INDENT) 1867db96d56Sopenharmony_ci if len(argspec) > _MAX_COLS else [argspec] if argspec else []) 1877db96d56Sopenharmony_ci 1887db96d56Sopenharmony_ci # Augment lines from docstring, if any, and join to get argspec. 1897db96d56Sopenharmony_ci doc = inspect.getdoc(ob) 1907db96d56Sopenharmony_ci if doc: 1917db96d56Sopenharmony_ci for line in doc.split('\n', _MAX_LINES)[:_MAX_LINES]: 1927db96d56Sopenharmony_ci line = line.strip() 1937db96d56Sopenharmony_ci if not line: 1947db96d56Sopenharmony_ci break 1957db96d56Sopenharmony_ci if len(line) > _MAX_COLS: 1967db96d56Sopenharmony_ci line = line[: _MAX_COLS - 3] + '...' 1977db96d56Sopenharmony_ci lines.append(line) 1987db96d56Sopenharmony_ci argspec = '\n'.join(lines) 1997db96d56Sopenharmony_ci 2007db96d56Sopenharmony_ci return argspec or _default_callable_argspec 2017db96d56Sopenharmony_ci 2027db96d56Sopenharmony_ci 2037db96d56Sopenharmony_ciif __name__ == '__main__': 2047db96d56Sopenharmony_ci from unittest import main 2057db96d56Sopenharmony_ci main('idlelib.idle_test.test_calltip', verbosity=2) 206