jpayne@68
|
1 """Pop up a reminder of how to call a function.
|
jpayne@68
|
2
|
jpayne@68
|
3 Call Tips are floating windows which display function, class, and method
|
jpayne@68
|
4 parameter and docstring information when you type an opening parenthesis, and
|
jpayne@68
|
5 which disappear when you type a closing parenthesis.
|
jpayne@68
|
6 """
|
jpayne@68
|
7 import __main__
|
jpayne@68
|
8 import inspect
|
jpayne@68
|
9 import re
|
jpayne@68
|
10 import sys
|
jpayne@68
|
11 import textwrap
|
jpayne@68
|
12 import types
|
jpayne@68
|
13
|
jpayne@68
|
14 from idlelib import calltip_w
|
jpayne@68
|
15 from idlelib.hyperparser import HyperParser
|
jpayne@68
|
16
|
jpayne@68
|
17
|
jpayne@68
|
18 class Calltip:
|
jpayne@68
|
19
|
jpayne@68
|
20 def __init__(self, editwin=None):
|
jpayne@68
|
21 if editwin is None: # subprocess and test
|
jpayne@68
|
22 self.editwin = None
|
jpayne@68
|
23 else:
|
jpayne@68
|
24 self.editwin = editwin
|
jpayne@68
|
25 self.text = editwin.text
|
jpayne@68
|
26 self.active_calltip = None
|
jpayne@68
|
27 self._calltip_window = self._make_tk_calltip_window
|
jpayne@68
|
28
|
jpayne@68
|
29 def close(self):
|
jpayne@68
|
30 self._calltip_window = None
|
jpayne@68
|
31
|
jpayne@68
|
32 def _make_tk_calltip_window(self):
|
jpayne@68
|
33 # See __init__ for usage
|
jpayne@68
|
34 return calltip_w.CalltipWindow(self.text)
|
jpayne@68
|
35
|
jpayne@68
|
36 def _remove_calltip_window(self, event=None):
|
jpayne@68
|
37 if self.active_calltip:
|
jpayne@68
|
38 self.active_calltip.hidetip()
|
jpayne@68
|
39 self.active_calltip = None
|
jpayne@68
|
40
|
jpayne@68
|
41 def force_open_calltip_event(self, event):
|
jpayne@68
|
42 "The user selected the menu entry or hotkey, open the tip."
|
jpayne@68
|
43 self.open_calltip(True)
|
jpayne@68
|
44 return "break"
|
jpayne@68
|
45
|
jpayne@68
|
46 def try_open_calltip_event(self, event):
|
jpayne@68
|
47 """Happens when it would be nice to open a calltip, but not really
|
jpayne@68
|
48 necessary, for example after an opening bracket, so function calls
|
jpayne@68
|
49 won't be made.
|
jpayne@68
|
50 """
|
jpayne@68
|
51 self.open_calltip(False)
|
jpayne@68
|
52
|
jpayne@68
|
53 def refresh_calltip_event(self, event):
|
jpayne@68
|
54 if self.active_calltip and self.active_calltip.tipwindow:
|
jpayne@68
|
55 self.open_calltip(False)
|
jpayne@68
|
56
|
jpayne@68
|
57 def open_calltip(self, evalfuncs):
|
jpayne@68
|
58 self._remove_calltip_window()
|
jpayne@68
|
59
|
jpayne@68
|
60 hp = HyperParser(self.editwin, "insert")
|
jpayne@68
|
61 sur_paren = hp.get_surrounding_brackets('(')
|
jpayne@68
|
62 if not sur_paren:
|
jpayne@68
|
63 return
|
jpayne@68
|
64 hp.set_index(sur_paren[0])
|
jpayne@68
|
65 expression = hp.get_expression()
|
jpayne@68
|
66 if not expression:
|
jpayne@68
|
67 return
|
jpayne@68
|
68 if not evalfuncs and (expression.find('(') != -1):
|
jpayne@68
|
69 return
|
jpayne@68
|
70 argspec = self.fetch_tip(expression)
|
jpayne@68
|
71 if not argspec:
|
jpayne@68
|
72 return
|
jpayne@68
|
73 self.active_calltip = self._calltip_window()
|
jpayne@68
|
74 self.active_calltip.showtip(argspec, sur_paren[0], sur_paren[1])
|
jpayne@68
|
75
|
jpayne@68
|
76 def fetch_tip(self, expression):
|
jpayne@68
|
77 """Return the argument list and docstring of a function or class.
|
jpayne@68
|
78
|
jpayne@68
|
79 If there is a Python subprocess, get the calltip there. Otherwise,
|
jpayne@68
|
80 either this fetch_tip() is running in the subprocess or it was
|
jpayne@68
|
81 called in an IDLE running without the subprocess.
|
jpayne@68
|
82
|
jpayne@68
|
83 The subprocess environment is that of the most recently run script. If
|
jpayne@68
|
84 two unrelated modules are being edited some calltips in the current
|
jpayne@68
|
85 module may be inoperative if the module was not the last to run.
|
jpayne@68
|
86
|
jpayne@68
|
87 To find methods, fetch_tip must be fed a fully qualified name.
|
jpayne@68
|
88
|
jpayne@68
|
89 """
|
jpayne@68
|
90 try:
|
jpayne@68
|
91 rpcclt = self.editwin.flist.pyshell.interp.rpcclt
|
jpayne@68
|
92 except AttributeError:
|
jpayne@68
|
93 rpcclt = None
|
jpayne@68
|
94 if rpcclt:
|
jpayne@68
|
95 return rpcclt.remotecall("exec", "get_the_calltip",
|
jpayne@68
|
96 (expression,), {})
|
jpayne@68
|
97 else:
|
jpayne@68
|
98 return get_argspec(get_entity(expression))
|
jpayne@68
|
99
|
jpayne@68
|
100
|
jpayne@68
|
101 def get_entity(expression):
|
jpayne@68
|
102 """Return the object corresponding to expression evaluated
|
jpayne@68
|
103 in a namespace spanning sys.modules and __main.dict__.
|
jpayne@68
|
104 """
|
jpayne@68
|
105 if expression:
|
jpayne@68
|
106 namespace = {**sys.modules, **__main__.__dict__}
|
jpayne@68
|
107 try:
|
jpayne@68
|
108 return eval(expression, namespace) # Only protect user code.
|
jpayne@68
|
109 except BaseException:
|
jpayne@68
|
110 # An uncaught exception closes idle, and eval can raise any
|
jpayne@68
|
111 # exception, especially if user classes are involved.
|
jpayne@68
|
112 return None
|
jpayne@68
|
113
|
jpayne@68
|
114 # The following are used in get_argspec and some in tests
|
jpayne@68
|
115 _MAX_COLS = 85
|
jpayne@68
|
116 _MAX_LINES = 5 # enough for bytes
|
jpayne@68
|
117 _INDENT = ' '*4 # for wrapped signatures
|
jpayne@68
|
118 _first_param = re.compile(r'(?<=\()\w*\,?\s*')
|
jpayne@68
|
119 _default_callable_argspec = "See source or doc"
|
jpayne@68
|
120 _invalid_method = "invalid method signature"
|
jpayne@68
|
121 _argument_positional = " # '/' marks preceding args as positional-only."
|
jpayne@68
|
122
|
jpayne@68
|
123 def get_argspec(ob):
|
jpayne@68
|
124 '''Return a string describing the signature of a callable object, or ''.
|
jpayne@68
|
125
|
jpayne@68
|
126 For Python-coded functions and methods, the first line is introspected.
|
jpayne@68
|
127 Delete 'self' parameter for classes (.__init__) and bound methods.
|
jpayne@68
|
128 The next lines are the first lines of the doc string up to the first
|
jpayne@68
|
129 empty line or _MAX_LINES. For builtins, this typically includes
|
jpayne@68
|
130 the arguments in addition to the return value.
|
jpayne@68
|
131 '''
|
jpayne@68
|
132 argspec = default = ""
|
jpayne@68
|
133 try:
|
jpayne@68
|
134 ob_call = ob.__call__
|
jpayne@68
|
135 except BaseException:
|
jpayne@68
|
136 return default
|
jpayne@68
|
137
|
jpayne@68
|
138 fob = ob_call if isinstance(ob_call, types.MethodType) else ob
|
jpayne@68
|
139
|
jpayne@68
|
140 try:
|
jpayne@68
|
141 argspec = str(inspect.signature(fob))
|
jpayne@68
|
142 except ValueError as err:
|
jpayne@68
|
143 msg = str(err)
|
jpayne@68
|
144 if msg.startswith(_invalid_method):
|
jpayne@68
|
145 return _invalid_method
|
jpayne@68
|
146
|
jpayne@68
|
147 if '/' in argspec and len(argspec) < _MAX_COLS - len(_argument_positional):
|
jpayne@68
|
148 # Add explanation TODO remove after 3.7, before 3.9.
|
jpayne@68
|
149 argspec += _argument_positional
|
jpayne@68
|
150 if isinstance(fob, type) and argspec == '()':
|
jpayne@68
|
151 # If fob has no argument, use default callable argspec.
|
jpayne@68
|
152 argspec = _default_callable_argspec
|
jpayne@68
|
153
|
jpayne@68
|
154 lines = (textwrap.wrap(argspec, _MAX_COLS, subsequent_indent=_INDENT)
|
jpayne@68
|
155 if len(argspec) > _MAX_COLS else [argspec] if argspec else [])
|
jpayne@68
|
156
|
jpayne@68
|
157 if isinstance(ob_call, types.MethodType):
|
jpayne@68
|
158 doc = ob_call.__doc__
|
jpayne@68
|
159 else:
|
jpayne@68
|
160 doc = getattr(ob, "__doc__", "")
|
jpayne@68
|
161 if doc:
|
jpayne@68
|
162 for line in doc.split('\n', _MAX_LINES)[:_MAX_LINES]:
|
jpayne@68
|
163 line = line.strip()
|
jpayne@68
|
164 if not line:
|
jpayne@68
|
165 break
|
jpayne@68
|
166 if len(line) > _MAX_COLS:
|
jpayne@68
|
167 line = line[: _MAX_COLS - 3] + '...'
|
jpayne@68
|
168 lines.append(line)
|
jpayne@68
|
169 argspec = '\n'.join(lines)
|
jpayne@68
|
170 if not argspec:
|
jpayne@68
|
171 argspec = _default_callable_argspec
|
jpayne@68
|
172 return argspec
|
jpayne@68
|
173
|
jpayne@68
|
174
|
jpayne@68
|
175 if __name__ == '__main__':
|
jpayne@68
|
176 from unittest import main
|
jpayne@68
|
177 main('idlelib.idle_test.test_calltip', verbosity=2)
|