Commit d5710f8b authored by Terry Jan Reedy's avatar Terry Jan Reedy

Issue #16638: Include up to 5 docstring header lines (before first blank) in

Idle calltips. This is needed for builtins, such bytes (which is why 5).
Based on patch by Serhiy Storchaka.
parent 758fa5ea
...@@ -116,17 +116,21 @@ def get_entity(expression): ...@@ -116,17 +116,21 @@ def get_entity(expression):
# exception, especially if user classes are involved. # exception, especially if user classes are involved.
return None return None
# The following are used in both get_argspec and tests # The following are used in get_argspec and some in tests
_MAX_COLS = 79
_MAX_LINES = 5 # enough for bytes
_first_param = re.compile('(?<=\()\w*\,?\s*') _first_param = re.compile('(?<=\()\w*\,?\s*')
_default_callable_argspec = "See source or doc" _default_callable_argspec = "See source or doc"
def get_argspec(ob): def get_argspec(ob):
'''Return a string describing the signature of a callable object, or ''. '''Return a string describing the signature of a callable object, or ''.
For Python-coded functions and methods, the first line is introspected. For Python-coded functions and methods, the first line is introspected.
Delete 'self' parameter for classes (.__init__) and bound methods. Delete 'self' parameter for classes (.__init__) and bound methods.
The last line is the first line of the doc string. For builtins, this typically The next lines are the first lines of the doc string up to the first
includes the arguments in addition to the return value. empty line or _MAX_LINES. For builtins, this typically includes
the arguments in addition to the return value.
''' '''
argspec = "" argspec = ""
try: try:
...@@ -150,13 +154,15 @@ def get_argspec(ob): ...@@ -150,13 +154,15 @@ def get_argspec(ob):
else: else:
doc = getattr(ob, "__doc__", "") doc = getattr(ob, "__doc__", "")
if doc: if doc:
doc = doc.lstrip() lines = [argspec] if argspec else []
pos = doc.find("\n") for line in doc.split('\n', 5)[:_MAX_LINES]:
if pos < 0 or pos > 70: line = line.strip()
pos = 70 if not line:
if argspec: break
argspec += "\n" if len(line) > _MAX_COLS:
argspec += doc[:pos] line = line[: _MAX_COLS - 3] + '...'
lines.append(line)
argspec = '\n'.join(lines)
if not argspec: if not argspec:
argspec = _default_callable_argspec argspec = _default_callable_argspec
return argspec return argspec
......
...@@ -42,17 +42,15 @@ class Get_signatureTest(unittest.TestCase): ...@@ -42,17 +42,15 @@ class Get_signatureTest(unittest.TestCase):
# For a simple mismatch, change the expected output to the actual. # For a simple mismatch, change the expected output to the actual.
def test_builtins(self): def test_builtins(self):
# These test will break if
# Python class that inherits builtin methods # Python class that inherits builtin methods
class List(list): "List() doc" class List(list): "List() doc"
# Simulate builtin with no docstring for default argspec test # Simulate builtin with no docstring for default tip test
class SB: __call__ = None class SB: __call__ = None
def gtest(obj, out): def gtest(obj, out):
self.assertEqual(signature(obj), out) self.assertEqual(signature(obj), out)
gtest(list, "list() -> new empty list")
gtest(List, List.__doc__) gtest(List, List.__doc__)
gtest(list.__new__, gtest(list.__new__,
'T.__new__(S, ...) -> a new object with type S, a subtype of T') 'T.__new__(S, ...) -> a new object with type S, a subtype of T')
...@@ -66,6 +64,21 @@ class Get_signatureTest(unittest.TestCase): ...@@ -66,6 +64,21 @@ class Get_signatureTest(unittest.TestCase):
gtest(types.MethodType, "method(function, instance)") gtest(types.MethodType, "method(function, instance)")
gtest(SB(), default_tip) gtest(SB(), default_tip)
def test_multiline_docstring(self):
# Test fewer lines than max.
self.assertEqual(signature(list),
"list() -> new empty list\n"
"list(iterable) -> new list initialized from iterable's items")
# Test max lines and line (currently) too long.
self.assertEqual(signature(bytes),
"bytes(iterable_of_ints) -> bytes\n"
"bytes(string, encoding[, errors]) -> bytes\n"
"bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer\n"
#bytes(int) -> bytes object of size given by the parameter initialized with null bytes
"bytes(int) -> bytes object of size given by the parameter initialized with n...\n"
"bytes() -> empty bytes object")
def test_functions(self): def test_functions(self):
def t1(): 'doc' def t1(): 'doc'
t1.tip = "()" t1.tip = "()"
...@@ -100,7 +113,8 @@ class Get_signatureTest(unittest.TestCase): ...@@ -100,7 +113,8 @@ class Get_signatureTest(unittest.TestCase):
assert ct._first_param.sub('', uni) == '(a)' assert ct._first_param.sub('', uni) == '(a)'
def test_no_docstring(self): def test_no_docstring(self):
def nd(s): pass def nd(s):
pass
TC.nd = nd TC.nd = nd
self.assertEqual(signature(nd), "(s)") self.assertEqual(signature(nd), "(s)")
self.assertEqual(signature(TC.nd), "(s)") self.assertEqual(signature(TC.nd), "(s)")
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment