(root)/
Python-3.11.7/
Lib/
idlelib/
calltip.py
       1  """Pop up a reminder of how to call a function.
       2  
       3  Call Tips are floating windows which display function, class, and method
       4  parameter and docstring information when you type an opening parenthesis, and
       5  which disappear when you type a closing parenthesis.
       6  """
       7  import __main__
       8  import inspect
       9  import re
      10  import sys
      11  import textwrap
      12  import types
      13  
      14  from idlelib import calltip_w
      15  from idlelib.hyperparser import HyperParser
      16  
      17  
      18  class ESC[4;38;5;81mCalltip:
      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  
     133  def 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  
     154  def 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  
     203  if __name__ == '__main__':
     204      from unittest import main
     205      main('idlelib.idle_test.test_calltip', verbosity=2)