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