From: Terry Jan Reedy Date: Wed, 22 Jan 2014 01:45:03 +0000 (-0500) Subject: Issue #16638: Include up to 5 docstring header lines (before first blank) in X-Git-Tag: v2.7.8~99 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=82c48e098aff18ab203ed7a22b17b38ea43992e1;p=python Issue #16638: Include up to 5 docstring header lines (before first blank) in Idle calltips. This is needed for builtins, such 3.x bytes (which is why 5). Based on patch by Serhiy Storchaka. --- diff --git a/Lib/idlelib/CallTips.py b/Lib/idlelib/CallTips.py index 4ac28edc2b..7197cf5ad4 100644 --- a/Lib/idlelib/CallTips.py +++ b/Lib/idlelib/CallTips.py @@ -130,14 +130,24 @@ def _find_constructor(class_ob): if rc is not None: return rc return None +# The following are used in get_arg_text +_MAX_COLS = 79 +_MAX_LINES = 5 # enough for bytes + def get_arg_text(ob): - """Get a string describing the arguments for the given object, - only if it is callable.""" - arg_text = "" + '''Return a string describing the signature of a callable object, or ''. + + For Python-coded functions and methods, the first line is introspected. + Delete 'self' parameter for classes (.__init__) and bound methods. + The next lines are the first lines of the doc string up to the first + empty line or _MAX_LINES. For builtins, this typically includes + the arguments in addition to the return value. + ''' + argspec = "" try: ob_call = ob.__call__ except BaseException: - return arg_text + return argspec arg_offset = 0 if type(ob) in (types.ClassType, types.TypeType): @@ -171,22 +181,24 @@ def get_arg_text(ob): items.append("*args") if fob.func_code.co_flags & 0x8: items.append("**kwds") - arg_text = ", ".join(items) - arg_text = "(%s)" % re.sub("(?", arg_text) + argspec = ", ".join(items) + argspec = "(%s)" % re.sub("(?", argspec) # See if we can use the docstring if isinstance(ob_call, types.MethodType): doc = ob_call.__doc__ else: 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 + lines = [argspec] if argspec else [] + for line in doc.split('\n', 5)[:_MAX_LINES]: + line = line.strip() + if not line: + break + if len(line) > _MAX_COLS: + line = line[: _MAX_COLS - 3] + '...' + lines.append(line) + argspec = '\n'.join(lines) + return argspec if __name__ == '__main__': from unittest import main diff --git a/Lib/idlelib/idle_test/test_calltips.py b/Lib/idlelib/idle_test/test_calltips.py index a75d88df94..338f2c685e 100644 --- a/Lib/idlelib/idle_test/test_calltips.py +++ b/Lib/idlelib/idle_test/test_calltips.py @@ -53,7 +53,6 @@ class Get_signatureTest(unittest.TestCase): def gtest(obj, out): self.assertEqual(signature(obj), out) - gtest(list, "()\nlist() -> new empty list") gtest(List, '()\n' + List.__doc__) gtest(list.__new__, 'T.__new__(S, ...) -> a new object with type S, a subtype of T') @@ -67,6 +66,20 @@ class Get_signatureTest(unittest.TestCase): gtest(types.MethodType, '()\ninstancemethod(function, instance, class)') gtest(SB(), default_tip) + def test_multiline_docstring(self): + # Test fewer lines than max. + self.assertEqual(signature(list), + "()\nlist() -> new empty list\n" + "list(iterable) -> new list initialized from iterable's items") + + # Test max lines and line (currently) too long. + def f(): + pass + s = 'a\nb\nc\nd\n' + f.__doc__ = s + 300 * 'e' + 'f' + self.assertEqual(signature(f), + '()\n' + s + (ct._MAX_COLS - 3) * 'e' + '...') + def test_functions(self): def t1(): 'doc' t1.tip = "()"