Index: CallTips.py
===================================================================
--- CallTips.py	(revision 59481)
+++ CallTips.py	(working copy)
@@ -5,9 +5,10 @@
 which disappear when you type a closing parenthesis.
 
 """
-import re
 import sys
 import types
+import inspect
+from repr import repr as limited_len_repr
 
 import CallTipWindow
 from HyperParser import HyperParser
@@ -74,11 +75,12 @@
         name = hp.get_expression()
         if not name or (not evalfuncs and name.find('(') != -1):
             return
-        arg_text = self.fetch_tip(name)
-        if not arg_text:
+        arg_text, doc = self.fetch_tip(name)
+        if not (arg_text or doc):
             return
         self.calltip = self._make_calltip_window()
-        self.calltip.showtip(arg_text, sur_paren[0], sur_paren[1])
+        self.calltip.showtip(name, arg_text, doc,
+                             sur_paren[0], sur_paren[1])
 
     def fetch_tip(self, name):
         """Return the argument list and docstring of a function or class
@@ -103,7 +105,7 @@
                                      (name,), {})
         else:
             entity = self.get_entity(name)
-            return get_arg_text(entity)
+            return get_arg_text_and_doc(entity)
 
     def get_entity(self, name):
         "Lookup name in a namespace spanning sys.modules and __main.dict__"
@@ -119,58 +121,62 @@
     # Given a class object, return a function object used for the
     # constructor (ie, __init__() ) or None if we can't find one.
     try:
-        return class_ob.__init__.im_func
+        return class_ob.__init__
     except AttributeError:
         for base in class_ob.__bases__:
             rc = _find_constructor(base)
             if rc is not None: return rc
     return None
 
-def get_arg_text(ob):
-    """Get a string describing the arguments for the given object"""
-    arg_text = ""
-    if ob is not None:
-        arg_offset = 0
-        if type(ob) in (types.ClassType, types.TypeType):
-            # Look for the highest __init__ in the class chain.
-            fob = _find_constructor(ob)
-            if fob is None:
-                fob = lambda: None
-            else:
-                arg_offset = 1
-        elif type(ob)==types.MethodType:
-            # bit of a hack for methods - turn it into a function
-            # but we drop the "self" param.
-            fob = ob.im_func
+def get_arg_text_and_doc(ob):
+    """Get the call signature and the doc-string for the given object"""
+    if ob is None:
+        return "", ""
+
+    fob = ob
+    arg_offset = 0 # set to 1 to drop initial argument, e.g. "self"
+    doc = inspect.getdoc(ob)
+    if isinstance(ob, (types.ClassType, types.TypeType)):
+        # Look for the highest __init__ in the class chain
+        fob = _find_constructor(ob)
+        if fob is None:
+            fob = lambda: None
+        else:
             arg_offset = 1
+            # use __init__'s doc-string if available, otherwise use the
+            # class's doc-string
+            doc = inspect.getdoc(fob) or doc
+    elif isinstance(ob, (types.FunctionType,
+                         types.LambdaType,
+                         types.BuiltinFunctionType)):
+        pass
+    elif isinstance(ob, (types.MethodType, types.BuiltinMethodType)):
+        arg_offset = 1
+    elif callable(ob):
+        try:
+            fob = ob.__call__
+        except AttributeError:
+            pass
         else:
-            fob = ob
-        # Try to build one for Python defined functions
-        if type(fob) in [types.FunctionType, types.LambdaType]:
-            argcount = fob.func_code.co_argcount
-            real_args = fob.func_code.co_varnames[arg_offset:argcount]
-            defaults = fob.func_defaults or []
-            defaults = list(map(lambda name: "=%s" % repr(name), defaults))
-            defaults = [""] * (len(real_args) - len(defaults)) + defaults
-            items = map(lambda arg, dflt: arg + dflt, real_args, defaults)
-            if fob.func_code.co_flags & 0x4:
-                items.append("...")
-            if fob.func_code.co_flags & 0x8:
-                items.append("***")
-            arg_text = ", ".join(items)
-            arg_text = "(%s)" % re.sub("\.\d+", "<tuple>", arg_text)
-        # See if we can use the docstring
-        doc = getattr(ob, "__doc__", "")
-        if doc:
-            doc = doc.lstrip()
-            pos = doc.find("\n")
-            if pos < 0 or pos > 70:
-                pos = 70
-            if arg_text:
-                arg_text += "\n"
-            arg_text += doc[:pos]
-    return arg_text
+            arg_offset = 1
+            doc = inspect.getdoc(fob)
+    if not doc:
+        doc = ""
 
+    arg_text = ""
+    # Try to get the call signature for Python defined functions/methods
+    if isinstance(fob, (types.FunctionType,
+                        types.LambdaType,
+                        types.MethodType)):
+        args, varargs, varkw, defaults = inspect.getargspec(fob)
+        def formatvalue(obj):
+            return "=" + limited_len_repr(obj)
+        arg_text = inspect.formatargspec(args[arg_offset:],
+                                         varargs, varkw, defaults,
+                                         formatvalue=formatvalue)
+
+    return arg_text, doc
+
 #################################################
 #
 # Test code
@@ -179,43 +185,80 @@
 
     def t1(): "()"
     def t2(a, b=None): "(a, b=None)"
-    def t3(a, *args): "(a, ...)"
-    def t4(*args): "(...)"
-    def t5(a, *args): "(a, ...)"
-    def t6(a, b=None, *args, **kw): "(a, b=None, ..., ***)"
-    def t7((a, b), c, (d, e)): "(<tuple>, c, <tuple>)"
+    def t3(a, *args): "(a, *args)"
+    def t4(*args): "(*args)"
+    def t5(a, *args): "(a, *args)"
+    def t6(a, b=None, *args, **kw): "(a, b=None, *args, **kw)"
+    def t7((a, b), c, (d, e)): "((a, b), c, (d, e))"
 
-    class TC(object):
-        "(ai=None, ...)"
-        def __init__(self, ai=None, *b): "(ai=None, ...)"
+    class TC1(object):
+        "this should not be used at all"
+        def __init__(self, a=None, *args, **kw): "(a=None, *args, **kw)"
         def t1(self): "()"
-        def t2(self, ai, b=None): "(ai, b=None)"
-        def t3(self, ai, *args): "(ai, ...)"
-        def t4(self, *args): "(...)"
-        def t5(self, ai, *args): "(ai, ...)"
-        def t6(self, ai, b=None, *args, **kw): "(ai, b=None, ..., ***)"
-        def t7(self, (ai, b), c, (d, e)): "(<tuple>, c, <tuple>)"
+        def t2(self, a, b=None): "(a, b=None)"
+        def t3(self, a, *args): "(a, *args)"
+        def t4(self, *args): "(*args)"
+        def t5(self, a, *args): "(a, *args)"
+        def t6(self, a, b=None, *args, **kw): "(a, b=None, *args, **kw)"
+        def t7(self, (a, b), c, (d, e)): "((a, b), c, (d, e))"
+        @classmethod
+        def t8(klass, a, b=None, *args, **kw): "(a, b=None, *args, **kw)"
+        @staticmethod
+        def t9(a, b=None, *args, **kw): "(a, b=None, *args, **kw)"
+        def __call__(self, a, b=None, *args, **kw): "(a, b=None, *args, **kw)"
 
-    def test(tests):
+    class TC2():
+        "this should not be used at all"
+        def __init__(self, a=None, *args, **kw): "(a=None, *args, **kw)"
+        def t1(self): "()"
+        def t2(self, a, b=None): "(a, b=None)"
+        def t3(self, a, *args): "(a, *args)"
+        def t4(self, *args): "(*args)"
+        def t5(self, a, *args): "(a, *args)"
+        def t6(self, a, b=None, *args, **kw): "(a, b=None, *args, **kw)"
+        def t7(self, (a, b), c, (d, e)): "((a, b), c, (d, e))"
+        @classmethod
+        def t8(klass, a, b=None, *args, **kw): "(a, b=None, *args, **kw)"
+        @staticmethod
+        def t9(a, b=None, *args, **kw): "(a, b=None, *args, **kw)"
+        def __call__(self, a, b=None, *args, **kw): "(a, b=None, *args, **kw)"
+
+    tc1 = TC1()
+    tc2 = TC2()
+
+    tests = """t1 t2 t3 t4 t5 t6 t7
+               tc1.t1 tc1.t2 tc1.t3 tc1.t4 tc1.t5 tc1.t6 tc1.t7
+               TC1 TC1.t8 TC1.t9 tc1
+               tc2.t1 tc2.t2 tc2.t3 tc2.t4 tc2.t5 tc2.t6 tc2.t7
+               TC2 TC2.t8 TC2.t9 tc2""".split()
+    builtin_tests = ["int", "dir", "execfile", "[].append", "sys.exit"]
+
+    def test(tests, builtin_tests):
         ct = CallTips()
         failed=[]
-        for t in tests:
-            expected = t.__doc__ + "\n" + t.__doc__
-            name = t.__name__
-            # exercise fetch_tip(), not just get_arg_text()
-            try:
-                qualified_name = "%s.%s" % (t.im_class.__name__, name)
-            except AttributeError:
-                qualified_name = name
-            arg_text = ct.fetch_tip(qualified_name)
-            if arg_text != expected:
-                failed.append(t)
-                fmt = "%s - expected %s, but got %s"
-                print  fmt % (t.__name__, expected, get_arg_text(t))
-        print "%d of %d tests failed" % (len(failed), len(tests))
+        for name in tests:
+            t = eval(name)
+            if isinstance(t, (types.ClassType, types.TypeType)):
+                expected = t.__init__.__doc__
+            elif isinstance(t, (types.InstanceType, TC1, TC2)):
+                expected = t.__call__.__doc__
+            else:
+                expected = t.__doc__
+            expected = (expected, expected)
+            # exercise fetch_tip(), not just get_arg_text_and_doc()
+            arg_text, doc = ct.fetch_tip(name)
+            if (arg_text, doc) != expected:
+                failed.append(name)
+                print "%s - expected %r, but got %r" % (name, expected,
+                                                        (arg_text, doc))
+        for name in builtin_tests:
+            arg_text, doc = ct.fetch_tip(name)
+            if arg_text != '' or not doc:
+                failed.append(name)
+                expected = "('', <documentation>)"
+                print "%s - expected %s, but got %r" % (name, expected,
+                                                        (arg_text, doc))
+        print "%d of %d tests failed" % (len(failed),
+                                         len(tests) + len(builtin_tests))
 
-    tc = TC()
-    tests = (t1, t2, t3, t4, t5, t6, t7,
-             TC, tc.t1, tc.t2, tc.t3, tc.t4, tc.t5, tc.t6, tc.t7)
-
-    test(tests)
+    test(tests, builtin_tests)
Index: CallTipWindow.py
===================================================================
--- CallTipWindow.py	(revision 59481)
+++ CallTipWindow.py	(working copy)
@@ -6,11 +6,15 @@
 """
 from Tkinter import *
 
+from configHandler import idleConf
+import textView
+
 HIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-hide>>"
 HIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>")
 CHECKHIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-checkhide>>"
 CHECKHIDE_SEQUENCES = ("<KeyRelease>", "<ButtonRelease>")
 CHECKHIDE_TIME = 100 # miliseconds
+SHOW_FULL_DOC_EVENT_NAME = "<<calltipwindow-showfulldoc>>"
 
 MARK_RIGHT = "calltipwindowregion_right"
 
@@ -22,6 +26,7 @@
         self.parenline = self.parencol = None
         self.lastline = None
         self.hideid = self.checkhideid = None
+        self.showfulldoc_seqs_and_id = None
 
     def position_window(self):
         """Check if needs to reposition the window, and if so - do it."""
@@ -44,10 +49,12 @@
         y = box[1] + box[3] + self.widget.winfo_rooty()
         self.tipwindow.wm_geometry("+%d+%d" % (x, y))
 
-    def showtip(self, text, parenleft, parenright):
+    def showtip(self, name, arg_text, doc,
+                parenleft, parenright):
         """Show the calltip, bind events which will close it and reposition it.
         """
         # truncate overly long calltip
+        text = '\n'.join([x for x in [arg_text] + doc.splitlines()[:1] if x])
         if len(text) >= 79:
             textlines = text.splitlines()
             for i, line in enumerate(textlines):
@@ -89,6 +96,28 @@
         for seq in HIDE_SEQUENCES:
             self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq)
 
+        if doc and (len(doc) >= 79 or '\n' in doc):
+            # bind the force-open-calltip sequence(s) to open the full
+            # doc-string in a new window
+            text = ''
+            if arg_text:
+                text += name + arg_text
+            if text and doc:
+                text += '\n\n'
+            text += doc
+            title = "In-code documentation for %s" % name
+            def show_full_doc_event(event):
+                textView.view_text(self.widget, title, text)
+                return "break"
+            seqs = idleConf.GetOption(
+                'extensions', 'CallTips_cfgBindings', 'force-open-calltip',
+                type='str', default='<Control-Key-backslash>')
+            seqs = seqs.split()
+            for seq in seqs:
+                self.widget.event_add(SHOW_FULL_DOC_EVENT_NAME, seq)
+            id = self.widget.bind(SHOW_FULL_DOC_EVENT_NAME, show_full_doc_event)
+            self.showfulldoc_seqs_and_id = (seqs, id)
+
     def checkhide_event(self, event=None):
         if not self.tipwindow:
             # If the event was triggered by the same event that unbinded
@@ -122,6 +151,12 @@
             self.widget.event_delete(HIDE_VIRTUAL_EVENT_NAME, seq)
         self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideid)
         self.hideid = None
+        if self.showfulldoc_seqs_and_id is not None:
+            seqs, id = self.showfulldoc_seqs_and_id
+            for seq in seqs:
+                self.widget.event_delete(SHOW_FULL_DOC_EVENT_NAME, seq)
+            self.widget.unbind(SHOW_FULL_DOC_EVENT_NAME, id)
+            self.showfulldoc_seqs_and_id = None
 
         self.label.destroy()
         self.label = None
@@ -158,8 +193,19 @@
         root.mainloop()
 
     def calltip_show(self, event):
-        self.calltip.showtip("Hello world")
+        paren_left = self.text.index("insert")
+        paren_right = self.text.search(")", paren_left)
+        if not paren_right:
+            paren_right = "end"
+        self.calltip.showtip("string.strip", "(s, chars=None)",
+                             """strip(s [,chars]) -> string
 
+Return a copy of the string s with leading and trailing
+whitespace removed.
+If chars is given and not None, remove characters in chars instead.
+If chars is unicode, S will be converted to unicode before stripping.""",
+                             paren_left, paren_right)
+
     def calltip_hide(self, event):
         self.calltip.hidetip()
 
