CallTips.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. # CallTips.py - An IDLE extension that provides "Call Tips" - ie, a floating window that
  2. # displays parameter information as you open parens.
  3. import string
  4. import sys
  5. import inspect
  6. import traceback
  7. class CallTips:
  8. menudefs = [
  9. ]
  10. keydefs = {
  11. '<<paren-open>>': ['<Key-parenleft>'],
  12. '<<paren-close>>': ['<Key-parenright>'],
  13. '<<check-calltip-cancel>>': ['<KeyRelease>'],
  14. '<<calltip-cancel>>': ['<ButtonPress>', '<Key-Escape>'],
  15. }
  16. windows_keydefs = {
  17. }
  18. unix_keydefs = {
  19. }
  20. def __init__(self, editwin):
  21. self.editwin = editwin
  22. self.text = editwin.text
  23. self.calltip = None
  24. if hasattr(self.text, "make_calltip_window"):
  25. self._make_calltip_window = self.text.make_calltip_window
  26. else:
  27. self._make_calltip_window = self._make_tk_calltip_window
  28. def close(self):
  29. self._make_calltip_window = None
  30. # Makes a Tk based calltip window. Used by IDLE, but not Pythonwin.
  31. # See __init__ above for how this is used.
  32. def _make_tk_calltip_window(self):
  33. import CallTipWindow
  34. return CallTipWindow.CallTip(self.text)
  35. def _remove_calltip_window(self):
  36. if self.calltip:
  37. self.calltip.hidetip()
  38. self.calltip = None
  39. def paren_open_event(self, event):
  40. self._remove_calltip_window()
  41. arg_text = get_arg_text(self.get_object_at_cursor())
  42. if arg_text:
  43. self.calltip_start = self.text.index("insert")
  44. self.calltip = self._make_calltip_window()
  45. self.calltip.showtip(arg_text)
  46. return "" #so the event is handled normally.
  47. def paren_close_event(self, event):
  48. # Now just hides, but later we should check if other
  49. # paren'd expressions remain open.
  50. self._remove_calltip_window()
  51. return "" #so the event is handled normally.
  52. def check_calltip_cancel_event(self, event):
  53. if self.calltip:
  54. # If we have moved before the start of the calltip,
  55. # or off the calltip line, then cancel the tip.
  56. # (Later need to be smarter about multi-line, etc)
  57. if self.text.compare("insert", "<=", self.calltip_start) or \
  58. self.text.compare("insert", ">", self.calltip_start + " lineend"):
  59. self._remove_calltip_window()
  60. return "" #so the event is handled normally.
  61. def calltip_cancel_event(self, event):
  62. self._remove_calltip_window()
  63. return "" #so the event is handled normally.
  64. def get_object_at_cursor(self,
  65. wordchars="._" + string.ascii_uppercase + string.ascii_lowercase + string.digits):
  66. # XXX - This needs to be moved to a better place
  67. # so the "." attribute lookup code can also use it.
  68. text = self.text
  69. chars = text.get("insert linestart", "insert")
  70. i = len(chars)
  71. while i and chars[i-1] in wordchars:
  72. i = i-1
  73. word = chars[i:]
  74. if word:
  75. # How is this for a hack!
  76. import sys, __main__
  77. namespace = sys.modules.copy()
  78. namespace.update(__main__.__dict__)
  79. try:
  80. return eval(word, namespace)
  81. except:
  82. pass
  83. return None # Can't find an object.
  84. def _find_constructor(class_ob):
  85. # Given a class object, return a function object used for the
  86. # constructor (ie, __init__() ) or None if we can't find one.
  87. try:
  88. if sys.version_info < (3,):
  89. return class_ob.__init__.im_func
  90. else:
  91. return class_ob.__init__.__func__
  92. except AttributeError:
  93. for base in class_ob.__bases__:
  94. rc = _find_constructor(base)
  95. if rc is not None: return rc
  96. return None
  97. def get_arg_text(ob):
  98. # Get a string describing the arguments for the given object.
  99. argText = ""
  100. if ob is not None:
  101. argOffset = 0
  102. if inspect.isclass(ob):
  103. # Look for the highest __init__ in the class chain.
  104. fob = _find_constructor(ob)
  105. if fob is None:
  106. fob = lambda: None
  107. else:
  108. fob = ob
  109. if inspect.isfunction(fob) or inspect.ismethod(fob):
  110. try:
  111. # py3k has a 'getfullargspec' which can handle py3k specific things.
  112. arg_getter = getattr(inspect, "getfullargspec", inspect.getargspec)
  113. argText = inspect.formatargspec(*arg_getter(fob))
  114. except:
  115. print "Failed to format the args"
  116. traceback.print_exc()
  117. # See if we can use the docstring
  118. if hasattr(ob, "__doc__"):
  119. doc=ob.__doc__
  120. try:
  121. doc = doc.strip()
  122. pos = doc.find("\n")
  123. except AttributeError:
  124. ## New style classes may have __doc__ slot without actually
  125. ## having a string assigned to it
  126. pass
  127. else:
  128. if pos<0 or pos>70: pos=70
  129. if argText: argText = argText + "\n"
  130. argText = argText + doc[:pos]
  131. return argText
  132. #################################################
  133. #
  134. # Test code
  135. #
  136. if __name__=='__main__':
  137. def t1(): "()"
  138. def t2(a, b=None): "(a, b=None)"
  139. def t3(a, *args): "(a, *args)"
  140. def t4(*args): "(*args)"
  141. def t5(a, *args): "(a, *args)"
  142. def t6(a, b=None, *args, **kw): "(a, b=None, *args, **kw)"
  143. class TC:
  144. "(self, a=None, *b)"
  145. def __init__(self, a=None, *b): "(self, a=None, *b)"
  146. def t1(self): "(self)"
  147. def t2(self, a, b=None): "(self, a, b=None)"
  148. def t3(self, a, *args): "(self, a, *args)"
  149. def t4(self, *args): "(self, *args)"
  150. def t5(self, a, *args): "(self, a, *args)"
  151. def t6(self, a, b=None, *args, **kw): "(self, a, b=None, *args, **kw)"
  152. def test( tests ):
  153. failed=[]
  154. for t in tests:
  155. expected = t.__doc__ + "\n" + t.__doc__
  156. if get_arg_text(t) != expected:
  157. failed.append(t)
  158. print "%s - expected %s, but got %s" % (t, repr(expected), repr(get_arg_text(t)))
  159. print "%d of %d tests failed" % (len(failed), len(tests))
  160. tc = TC()
  161. tests = t1, t2, t3, t4, t5, t6, \
  162. TC, tc.t1, tc.t2, tc.t3, tc.t4, tc.t5, tc.t6
  163. test(tests)