blob: 22a8a29c1f3b44d924e67ab411e32bc481a10649 [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.
Thomas Wouterscf297e42007-02-23 15:07:44 +00006
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +00007"""
Thomas Wouterscf297e42007-02-23 15:07:44 +00008import re
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +00009import sys
David Scherer7aced172000-08-15 01:13:23 +000010import types
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +000011import inspect
David Scherer7aced172000-08-15 01:13:23 +000012
Kurt B. Kaiser2d7f6a02007-08-22 23:01:33 +000013from idlelib import CallTipWindow
14from idlelib.HyperParser import HyperParser
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +000015
16import __main__
17
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
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +000029 else:
30 self.editwin = editwin
31 self.text = editwin.text
32 self.active_calltip = None
33 self._calltip_window = self._make_tk_calltip_window
David Scherer7aced172000-08-15 01:13:23 +000034
35 def close(self):
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +000036 self._calltip_window = None
David Scherer7aced172000-08-15 01:13:23 +000037
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):
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +000043 if self.active_calltip:
44 self.active_calltip.hidetip()
45 self.active_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):
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +000048 "The user selected the menu entry or hotkey, open the tip."
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000049 self.open_calltip(True)
50
51 def try_open_calltip_event(self, event):
52 """Happens when it would be nice to open a CallTip, but not really
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +000053 necessary, for example after an opening bracket, so function calls
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000054 won't be made.
55 """
56 self.open_calltip(False)
57
58 def refresh_calltip_event(self, event):
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +000059 if self.active_calltip and self.active_calltip.is_active():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000060 self.open_calltip(False)
61
62 def open_calltip(self, evalfuncs):
David Scherer7aced172000-08-15 01:13:23 +000063 self._remove_calltip_window()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000064
65 hp = HyperParser(self.editwin, "insert")
66 sur_paren = hp.get_surrounding_brackets('(')
67 if not sur_paren:
68 return
69 hp.set_index(sur_paren[0])
Terry Jan Reedye606e232012-06-03 00:27:54 -040070 expression = hp.get_expression()
71 if not expression:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000072 return
Terry Jan Reedye606e232012-06-03 00:27:54 -040073 if not evalfuncs and (expression.find('(') != -1):
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000074 return
Terry Jan Reedye606e232012-06-03 00:27:54 -040075 argspec = self.fetch_tip(expression)
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +000076 if not argspec:
77 return
78 self.active_calltip = self._calltip_window()
79 self.active_calltip.showtip(argspec, sur_paren[0], sur_paren[1])
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000080
Terry Jan Reedye606e232012-06-03 00:27:54 -040081 def fetch_tip(self, expression):
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +000082 """Return the argument list and docstring of a function or class.
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000083
Kurt B. Kaisere54710b2002-12-12 19:15:39 +000084 If there is a Python subprocess, get the calltip there. Otherwise,
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +000085 either this fetch_tip() is running in the subprocess or it was
86 called in an IDLE running without the subprocess.
Kurt B. Kaisere54710b2002-12-12 19:15:39 +000087
88 The subprocess environment is that of the most recently run script. If
89 two unrelated modules are being edited some calltips in the current
90 module may be inoperative if the module was not the last to run.
91
Thomas Wouterscf297e42007-02-23 15:07:44 +000092 To find methods, fetch_tip must be fed a fully qualified name.
93
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000094 """
Kurt B. Kaisere54710b2002-12-12 19:15:39 +000095 try:
96 rpcclt = self.editwin.flist.pyshell.interp.rpcclt
Terry Jan Reedye606e232012-06-03 00:27:54 -040097 except AttributeError:
Kurt B. Kaisere54710b2002-12-12 19:15:39 +000098 rpcclt = None
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +000099 if rpcclt:
100 return rpcclt.remotecall("exec", "get_the_calltip",
Terry Jan Reedye606e232012-06-03 00:27:54 -0400101 (expression,), {})
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +0000102 else:
Terry Jan Reedye606e232012-06-03 00:27:54 -0400103 entity = self.get_entity(expression)
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +0000104 return get_argspec(entity)
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +0000105
Terry Jan Reedye606e232012-06-03 00:27:54 -0400106 def get_entity(self, expression):
107 """Return the object corresponding to expression evaluated
108 in a namespace spanning sys.modules and __main.dict__.
109 """
110 if expression:
David Scherer7aced172000-08-15 01:13:23 +0000111 namespace = sys.modules.copy()
112 namespace.update(__main__.__dict__)
113 try:
Terry Jan Reedye606e232012-06-03 00:27:54 -0400114 return eval(expression, namespace)
115 except BaseException:
116 # An uncaught exception closes idle, and eval can raise any
117 # exception, especially if user classes are involved.
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +0000118 return None
David Scherer7aced172000-08-15 01:13:23 +0000119
120def _find_constructor(class_ob):
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +0000121 "Find the nearest __init__() in the class tree."
David Scherer7aced172000-08-15 01:13:23 +0000122 try:
Christian Heimesff737952007-11-27 10:40:20 +0000123 return class_ob.__init__.__func__
David Scherer7aced172000-08-15 01:13:23 +0000124 except AttributeError:
125 for base in class_ob.__bases__:
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +0000126 init = _find_constructor(base)
127 if init:
128 return init
129 return None
David Scherer7aced172000-08-15 01:13:23 +0000130
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +0000131def get_argspec(ob):
Terry Jan Reedyc5301ef2012-05-27 21:29:17 -0400132 """Get a string describing the arguments for the given object,
133 only if it is callable."""
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +0000134 argspec = ""
Terry Jan Reedyc5301ef2012-05-27 21:29:17 -0400135 if ob is not None and hasattr(ob, '__call__'):
Guido van Rossum13257902007-06-07 23:15:56 +0000136 if isinstance(ob, type):
David Scherer7aced172000-08-15 01:13:23 +0000137 fob = _find_constructor(ob)
138 if fob is None:
139 fob = lambda: None
Martin v. Löwis95c95ce2007-07-22 14:41:55 +0000140 elif isinstance(ob, types.MethodType):
Christian Heimesff737952007-11-27 10:40:20 +0000141 fob = ob.__func__
David Scherer7aced172000-08-15 01:13:23 +0000142 else:
143 fob = ob
Guido van Rossum13257902007-06-07 23:15:56 +0000144 if isinstance(fob, (types.FunctionType, types.LambdaType)):
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +0000145 argspec = inspect.formatargspec(*inspect.getfullargspec(fob))
146 pat = re.compile('self\,?\s*')
147 argspec = pat.sub("", argspec)
Kurt B. Kaiser6145a622003-07-23 15:42:14 +0000148 doc = getattr(ob, "__doc__", "")
Kurt B. Kaiser908aece2002-09-15 22:02:58 +0000149 if doc:
Kurt B. Kaiser6145a622003-07-23 15:42:14 +0000150 doc = doc.lstrip()
Kurt B. Kaiser908aece2002-09-15 22:02:58 +0000151 pos = doc.find("\n")
152 if pos < 0 or pos > 70:
153 pos = 70
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +0000154 if argspec:
155 argspec += "\n"
156 argspec += doc[:pos]
157 return argspec
David Scherer7aced172000-08-15 01:13:23 +0000158
159#################################################
160#
161# Test code
162#
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +0000163def main():
David Scherer7aced172000-08-15 01:13:23 +0000164 def t1(): "()"
165 def t2(a, b=None): "(a, b=None)"
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +0000166 def t3(a, *args): "(a, *args)"
167 def t4(*args): "(*args)"
168 def t5(a, *args): "(a, *args)"
169 def t6(a, b=None, *args, **kw): "(a, b=None, *args, **kw)"
David Scherer7aced172000-08-15 01:13:23 +0000170
Thomas Wouterscf297e42007-02-23 15:07:44 +0000171 class TC(object):
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +0000172 "(ai=None, *b)"
173 def __init__(self, ai=None, *b): "(ai=None, *b)"
David Scherer7aced172000-08-15 01:13:23 +0000174 def t1(self): "()"
Thomas Wouterscf297e42007-02-23 15:07:44 +0000175 def t2(self, ai, b=None): "(ai, b=None)"
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +0000176 def t3(self, ai, *args): "(ai, *args)"
177 def t4(self, *args): "(*args)"
178 def t5(self, ai, *args): "(ai, *args)"
179 def t6(self, ai, b=None, *args, **kw): "(ai, b=None, *args, **kw)"
180
181 __main__.__dict__.update(locals())
David Scherer7aced172000-08-15 01:13:23 +0000182
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +0000183 def test(tests):
184 ct = CallTips()
David Scherer7aced172000-08-15 01:13:23 +0000185 failed=[]
186 for t in tests:
187 expected = t.__doc__ + "\n" + t.__doc__
Kurt B. Kaiser5afa1df2002-10-10 08:25:24 +0000188 name = t.__name__
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +0000189 # exercise fetch_tip(), not just get_argspec()
Thomas Wouterscf297e42007-02-23 15:07:44 +0000190 try:
Christian Heimesff737952007-11-27 10:40:20 +0000191 qualified_name = "%s.%s" % (t.__self__.__class__.__name__, name)
Thomas Wouterscf297e42007-02-23 15:07:44 +0000192 except AttributeError:
193 qualified_name = name
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +0000194 argspec = ct.fetch_tip(qualified_name)
195 if argspec != expected:
David Scherer7aced172000-08-15 01:13:23 +0000196 failed.append(t)
Thomas Wouterscf297e42007-02-23 15:07:44 +0000197 fmt = "%s - expected %s, but got %s"
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +0000198 print(fmt % (t.__name__, expected, get_argspec(t)))
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000199 print("%d of %d tests failed" % (len(failed), len(tests)))
David Scherer7aced172000-08-15 01:13:23 +0000200
201 tc = TC()
Guido van Rossum1bc535d2007-05-15 18:46:22 +0000202 tests = (t1, t2, t3, t4, t5, t6,
203 TC, tc.t1, tc.t2, tc.t3, tc.t4, tc.t5, tc.t6)
David Scherer7aced172000-08-15 01:13:23 +0000204
205 test(tests)
Kurt B. Kaiser152b3c22007-08-30 06:35:15 +0000206
207if __name__ == '__main__':
208 main()