blob: 3db26362c24b736b9a0e32469a92c1f2d8f1611f [file] [log] [blame]
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +00001"""CallTips.py - An IDLE Extension to Jog Your Memory
David Scherer7aced172000-08-15 01:13:23 +00002
Kurt B. Kaisere54710b2002-12-12 19:15:39 +00003Call 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.
Kurt B. Kaiserecf796e2007-02-05 23:02:16 +00006
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +00007"""
Terry Jan Reedye7a72a12014-01-26 19:55:07 -05008import __main__
Kurt B. Kaiserecf796e2007-02-05 23:02:16 +00009import re
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +000010import sys
Terry Jan Reedye7a72a12014-01-26 19:55:07 -050011import textwrap
David Scherer7aced172000-08-15 01:13:23 +000012import types
13
Florent Xiclunad630c042010-04-02 07:24:52 +000014from idlelib import CallTipWindow
15from idlelib.HyperParser import HyperParser
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +000016
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +000017
David Scherer7aced172000-08-15 01:13:23 +000018class CallTips:
19
20 menudefs = [
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000021 ('edit', [
22 ("Show call tip", "<<force-open-calltip>>"),
23 ])
David Scherer7aced172000-08-15 01:13:23 +000024 ]
25
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +000026 def __init__(self, editwin=None):
Raymond Hettingerc5e378d2004-05-04 08:34:56 +000027 if editwin is None: # subprocess and test
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +000028 self.editwin = None
29 return
David Scherer7aced172000-08-15 01:13:23 +000030 self.editwin = editwin
31 self.text = editwin.text
32 self.calltip = None
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +000033 self._make_calltip_window = self._make_tk_calltip_window
David Scherer7aced172000-08-15 01:13:23 +000034
35 def close(self):
36 self._make_calltip_window = None
37
David Scherer7aced172000-08-15 01:13:23 +000038 def _make_tk_calltip_window(self):
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +000039 # See __init__ for usage
David Scherer7aced172000-08-15 01:13:23 +000040 return CallTipWindow.CallTip(self.text)
41
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000042 def _remove_calltip_window(self, event=None):
David Scherer7aced172000-08-15 01:13:23 +000043 if self.calltip:
44 self.calltip.hidetip()
45 self.calltip = None
Kurt B. Kaiserae676472001-07-12 23:10:35 +000046
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000047 def force_open_calltip_event(self, event):
48 """Happens when the user really wants to open a CallTip, even if a
49 function call is needed.
50 """
51 self.open_calltip(True)
52
53 def try_open_calltip_event(self, event):
54 """Happens when it would be nice to open a CallTip, but not really
Mark Dickinson3e4caeb2009-02-21 20:27:01 +000055 necessary, for example after an opening bracket, so function calls
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000056 won't be made.
57 """
58 self.open_calltip(False)
59
60 def refresh_calltip_event(self, event):
61 """If there is already a calltip window, check if it is still needed,
62 and if so, reload it.
63 """
64 if self.calltip and self.calltip.is_active():
65 self.open_calltip(False)
66
67 def open_calltip(self, evalfuncs):
David Scherer7aced172000-08-15 01:13:23 +000068 self._remove_calltip_window()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000069
70 hp = HyperParser(self.editwin, "insert")
71 sur_paren = hp.get_surrounding_brackets('(')
72 if not sur_paren:
73 return
74 hp.set_index(sur_paren[0])
Terry Jan Reedye93bc512012-06-03 00:58:36 -040075 expression = hp.get_expression()
76 if not expression or (not evalfuncs and expression.find('(') != -1):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000077 return
Terry Jan Reedye93bc512012-06-03 00:58:36 -040078 arg_text = self.fetch_tip(expression)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000079 if not arg_text:
80 return
81 self.calltip = self._make_calltip_window()
82 self.calltip.showtip(arg_text, sur_paren[0], sur_paren[1])
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000083
Terry Jan Reedye93bc512012-06-03 00:58:36 -040084 def fetch_tip(self, expression):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000085 """Return the argument list and docstring of a function or class
86
Kurt B. Kaisere54710b2002-12-12 19:15:39 +000087 If there is a Python subprocess, get the calltip there. Otherwise,
88 either fetch_tip() is running in the subprocess itself or it was called
89 in an IDLE EditorWindow before any script had been run.
90
91 The subprocess environment is that of the most recently run script. If
92 two unrelated modules are being edited some calltips in the current
93 module may be inoperative if the module was not the last to run.
94
Kurt B. Kaiserecf796e2007-02-05 23:02:16 +000095 To find methods, fetch_tip must be fed a fully qualified name.
96
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000097 """
Kurt B. Kaisere54710b2002-12-12 19:15:39 +000098 try:
99 rpcclt = self.editwin.flist.pyshell.interp.rpcclt
Terry Jan Reedye93bc512012-06-03 00:58:36 -0400100 except AttributeError:
Kurt B. Kaisere54710b2002-12-12 19:15:39 +0000101 rpcclt = None
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +0000102 if rpcclt:
103 return rpcclt.remotecall("exec", "get_the_calltip",
Terry Jan Reedye93bc512012-06-03 00:58:36 -0400104 (expression,), {})
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +0000105 else:
Terry Jan Reedye93bc512012-06-03 00:58:36 -0400106 entity = self.get_entity(expression)
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +0000107 return get_arg_text(entity)
108
Terry Jan Reedye93bc512012-06-03 00:58:36 -0400109 def get_entity(self, expression):
110 """Return the object corresponding to expression evaluated
111 in a namespace spanning sys.modules and __main.dict__.
112 """
113 if expression:
David Scherer7aced172000-08-15 01:13:23 +0000114 namespace = sys.modules.copy()
115 namespace.update(__main__.__dict__)
116 try:
Terry Jan Reedye93bc512012-06-03 00:58:36 -0400117 return eval(expression, namespace)
118 except BaseException:
119 # An uncaught exception closes idle, and eval can raise any
120 # exception, especially if user classes are involved.
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +0000121 return None
David Scherer7aced172000-08-15 01:13:23 +0000122
123def _find_constructor(class_ob):
124 # Given a class object, return a function object used for the
125 # constructor (ie, __init__() ) or None if we can't find one.
126 try:
127 return class_ob.__init__.im_func
128 except AttributeError:
129 for base in class_ob.__bases__:
130 rc = _find_constructor(base)
131 if rc is not None: return rc
132 return None
133
Terry Jan Reedy82c48e02014-01-21 20:45:03 -0500134# The following are used in get_arg_text
Terry Jan Reedye7a72a12014-01-26 19:55:07 -0500135_MAX_COLS = 85
Terry Jan Reedy82c48e02014-01-21 20:45:03 -0500136_MAX_LINES = 5 # enough for bytes
Terry Jan Reedye7a72a12014-01-26 19:55:07 -0500137_INDENT = ' '*4 # for wrapped signatures
Terry Jan Reedy82c48e02014-01-21 20:45:03 -0500138
David Scherer7aced172000-08-15 01:13:23 +0000139def get_arg_text(ob):
Terry Jan Reedy82c48e02014-01-21 20:45:03 -0500140 '''Return a string describing the signature of a callable object, or ''.
141
142 For Python-coded functions and methods, the first line is introspected.
143 Delete 'self' parameter for classes (.__init__) and bound methods.
144 The next lines are the first lines of the doc string up to the first
145 empty line or _MAX_LINES. For builtins, this typically includes
146 the arguments in addition to the return value.
147 '''
148 argspec = ""
Terry Jan Reedy21334e72014-01-21 15:36:36 -0500149 try:
150 ob_call = ob.__call__
151 except BaseException:
Terry Jan Reedye7a72a12014-01-26 19:55:07 -0500152 if type(ob) is types.ClassType: # old-style
153 ob_call = ob
154 else:
155 return argspec
Terry Jan Reedy21334e72014-01-21 15:36:36 -0500156
157 arg_offset = 0
158 if type(ob) in (types.ClassType, types.TypeType):
Terry Jan Reedye7a72a12014-01-26 19:55:07 -0500159 # Look for the first __init__ in the class chain with .im_func.
160 # Slot wrappers (builtins, classes defined in funcs) do not.
Terry Jan Reedy21334e72014-01-21 15:36:36 -0500161 fob = _find_constructor(ob)
162 if fob is None:
163 fob = lambda: None
164 else:
Kurt B. Kaiserecf796e2007-02-05 23:02:16 +0000165 arg_offset = 1
Terry Jan Reedy21334e72014-01-21 15:36:36 -0500166 elif type(ob) == types.MethodType:
167 # bit of a hack for methods - turn it into a function
168 # and drop the "self" param for bound methods
169 fob = ob.im_func
Terry Jan Reedyee0b6722014-06-04 03:09:56 -0400170 if ob.im_self is not None:
Terry Jan Reedy21334e72014-01-21 15:36:36 -0500171 arg_offset = 1
172 elif type(ob_call) == types.MethodType:
173 # a callable class instance
174 fob = ob_call.im_func
175 arg_offset = 1
176 else:
177 fob = ob
178 # Try to build one for Python defined functions
179 if type(fob) in [types.FunctionType, types.LambdaType]:
180 argcount = fob.func_code.co_argcount
181 real_args = fob.func_code.co_varnames[arg_offset:argcount]
182 defaults = fob.func_defaults or []
183 defaults = list(map(lambda name: "=%s" % repr(name), defaults))
184 defaults = [""] * (len(real_args) - len(defaults)) + defaults
185 items = map(lambda arg, dflt: arg + dflt, real_args, defaults)
Terry Jan Reedy1d6a0c42014-06-09 20:02:18 -0400186 for flag, pre, name in ((0x4, '*', 'args'), (0x8, '**', 'kwargs')):
187 if fob.func_code.co_flags & flag:
188 pre_name = pre + name
189 if name not in real_args:
190 items.append(pre_name)
191 else:
192 i = 1
193 while ((name+'%s') % i) in real_args:
194 i += 1
195 items.append((pre_name+'%s') % i)
Terry Jan Reedy82c48e02014-01-21 20:45:03 -0500196 argspec = ", ".join(items)
197 argspec = "(%s)" % re.sub("(?<!\d)\.\d+", "<tuple>", argspec)
Terry Jan Reedye7a72a12014-01-26 19:55:07 -0500198
199 lines = (textwrap.wrap(argspec, _MAX_COLS, subsequent_indent=_INDENT)
200 if len(argspec) > _MAX_COLS else [argspec] if argspec else [])
201
Terry Jan Reedy21334e72014-01-21 15:36:36 -0500202 if isinstance(ob_call, types.MethodType):
203 doc = ob_call.__doc__
204 else:
205 doc = getattr(ob, "__doc__", "")
206 if doc:
Terry Jan Reedye7a72a12014-01-26 19:55:07 -0500207 for line in doc.split('\n', _MAX_LINES)[:_MAX_LINES]:
Terry Jan Reedy82c48e02014-01-21 20:45:03 -0500208 line = line.strip()
209 if not line:
210 break
211 if len(line) > _MAX_COLS:
212 line = line[: _MAX_COLS - 3] + '...'
213 lines.append(line)
214 argspec = '\n'.join(lines)
215 return argspec
David Scherer7aced172000-08-15 01:13:23 +0000216
Terry Jan Reedy02fd1fd2014-01-21 00:26:10 -0500217if __name__ == '__main__':
Terry Jan Reedyb8fd9ca2013-05-30 14:47:33 -0400218 from unittest import main
Terry Jan Reedy21334e72014-01-21 15:36:36 -0500219 main('idlelib.idle_test.test_calltips', verbosity=2)