codekingpro/portable-devtools
115k
1# CallTips.py - An IDLE extension that provides "Call Tips" - ie, a floating window that2# displays parameter information as you open parens.3 4import inspect5import string6import sys7import traceback8 9 10class CallTips:11 menudefs = []12 13 keydefs = {14 "<<paren-open>>": ["<Key-parenleft>"],15 "<<paren-close>>": ["<Key-parenright>"],16 "<<check-calltip-cancel>>": ["<KeyRelease>"],17 "<<calltip-cancel>>": ["<ButtonPress>", "<Key-Escape>"],18 }19 20 windows_keydefs = {}21 22 unix_keydefs = {}23 24 def __init__(self, editwin):25 self.editwin = editwin26 self.text = editwin.text27 self.calltip = None28 if hasattr(self.text, "make_calltip_window"):29 self._make_calltip_window = self.text.make_calltip_window30 else:31 self._make_calltip_window = self._make_tk_calltip_window32 33 def close(self):34 self._make_calltip_window = None35 36 # Makes a Tk based calltip window. Used by IDLE, but not Pythonwin.37 # See __init__ above for how this is used.38 def _make_tk_calltip_window(self):39 import CallTipWindow40 41 return CallTipWindow.CallTip(self.text)42 43 def _remove_calltip_window(self):44 if self.calltip:45 self.calltip.hidetip()46 self.calltip = None47 48 def paren_open_event(self, event):49 self._remove_calltip_window()50 arg_text = get_arg_text(self.get_object_at_cursor())51 if arg_text:52 self.calltip_start = self.text.index("insert")53 self.calltip = self._make_calltip_window()54 self.calltip.showtip(arg_text)55 return "" # so the event is handled normally.56 57 def paren_close_event(self, event):58 # Now just hides, but later we should check if other59 # paren'd expressions remain open.60 self._remove_calltip_window()61 return "" # so the event is handled normally.62 63 def check_calltip_cancel_event(self, event):64 if self.calltip:65 # If we have moved before the start of the calltip,66 # or off the calltip line, then cancel the tip.67 # (Later need to be smarter about multi-line, etc)68 if self.text.compare(69 "insert", "<=", self.calltip_start70 ) or self.text.compare("insert", ">", self.calltip_start + " lineend"):71 self._remove_calltip_window()72 return "" # so the event is handled normally.73 74 def calltip_cancel_event(self, event):75 self._remove_calltip_window()76 return "" # so the event is handled normally.77 78 def get_object_at_cursor(79 self,80 wordchars="._"81 + string.ascii_uppercase82 + string.ascii_lowercase83 + string.digits,84 ):85 # XXX - This needs to be moved to a better place86 # so the "." attribute lookup code can also use it.87 text = self.text88 chars = text.get("insert linestart", "insert")89 i = len(chars)90 while i and chars[i - 1] in wordchars:91 i = i - 192 word = chars[i:]93 if word:94 # How is this for a hack!95 import __main__96 97 namespace = sys.modules.copy()98 namespace.update(__main__.__dict__)99 try:100 return eval(word, namespace)101 except:102 pass103 return None # Can't find an object.104 105 106def _find_constructor(class_ob):107 # Given a class object, return a function object used for the108 # constructor (ie, __init__() ) or None if we can't find one.109 try:110 return class_ob.__init__111 except AttributeError:112 for base in class_ob.__bases__:113 rc = _find_constructor(base)114 if rc is not None:115 return rc116 return None117 118 119def get_arg_text(ob):120 # Get a string describing the arguments for the given object.121 argText = ""122 if ob is not None:123 if inspect.isclass(ob):124 # Look for the highest __init__ in the class chain.125 fob = _find_constructor(ob)126 if fob is None:127 fob = lambda: None128 else:129 fob = ob130 if inspect.isfunction(fob) or inspect.ismethod(fob):131 try:132 argText = str(inspect.signature(fob))133 except:134 print("Failed to format the args")135 traceback.print_exc()136 # See if we can use the docstring137 if hasattr(ob, "__doc__"):138 doc = ob.__doc__139 try:140 doc = doc.strip()141 pos = doc.find("\n")142 except AttributeError:143 ## New style classes may have __doc__ slot without actually144 ## having a string assigned to it145 pass146 else:147 if pos < 0 or pos > 70:148 pos = 70149 if argText:150 argText = argText + "\n"151 argText = argText + doc[:pos]152 153 return argText154 155 156#################################################157#158# Test code159#160if __name__ == "__main__":161 162 def t1():163 "()"164 165 def t2(a, b=None):166 "(a, b=None)"167 168 def t3(a, *args):169 "(a, *args)"170 171 def t4(*args):172 "(*args)"173 174 def t5(a, *args):175 "(a, *args)"176 177 def t6(a, b=None, *args, **kw):178 "(a, b=None, *args, **kw)"179 180 class TC:181 "(self, a=None, *b)"182 183 def __init__(self, a=None, *b):184 "(self, a=None, *b)"185 186 def t1(self):187 "(self)"188 189 def t2(self, a, b=None):190 "(self, a, b=None)"191 192 def t3(self, a, *args):193 "(self, a, *args)"194 195 def t4(self, *args):196 "(self, *args)"197 198 def t5(self, a, *args):199 "(self, a, *args)"200 201 def t6(self, a, b=None, *args, **kw):202 "(self, a, b=None, *args, **kw)"203 204 def test(tests):205 failed = []206 for t in tests:207 expected = t.__doc__ + "\n" + t.__doc__208 if get_arg_text(t) != expected:209 failed.append(t)210 print(211 "%s - expected %s, but got %s"212 % (t, repr(expected), repr(get_arg_text(t)))213 )214 print("%d of %d tests failed" % (len(failed), len(tests)))215 216 tc = TC()217 tests = t1, t2, t3, t4, t5, t6, TC, tc.t1, tc.t2, tc.t3, tc.t4, tc.t5, tc.t6218 219 test(tests)220 