diff options
author | Terry Jan Reedy <tjreedy@udel.edu> | 2019-06-05 01:55:37 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-06-05 01:55:37 (GMT) |
commit | 949fe976d5c62ae63ed505ecf729f815d0baccfc (patch) | |
tree | f4757659cd68f68722a840bb70a10998b3bd412f /Lib/idlelib | |
parent | 59e7bbcaa4d0d556591f774c5ea4869c41fa95b0 (diff) | |
download | cpython-949fe976d5c62ae63ed505ecf729f815d0baccfc.zip cpython-949fe976d5c62ae63ed505ecf729f815d0baccfc.tar.gz cpython-949fe976d5c62ae63ed505ecf729f815d0baccfc.tar.bz2 |
bpo-35763: Make IDLE calltip note about '/' less obtrusive (GH-13791)
Add it to the end of the first line if there is room. Tests were reworked.
Diffstat (limited to 'Lib/idlelib')
-rw-r--r-- | Lib/idlelib/calltip.py | 8 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_calltip.py | 120 |
2 files changed, 67 insertions, 61 deletions
diff --git a/Lib/idlelib/calltip.py b/Lib/idlelib/calltip.py index b013a7f..a3dda26 100644 --- a/Lib/idlelib/calltip.py +++ b/Lib/idlelib/calltip.py @@ -118,7 +118,7 @@ _INDENT = ' '*4 # for wrapped signatures _first_param = re.compile(r'(?<=\()\w*\,?\s*') _default_callable_argspec = "See source or doc" _invalid_method = "invalid method signature" -_argument_positional = "\n['/' marks preceding arguments as positional-only]\n" +_argument_positional = " # '/' marks preceding args as positional-only." def get_argspec(ob): '''Return a string describing the signature of a callable object, or ''. @@ -144,11 +144,11 @@ def get_argspec(ob): if msg.startswith(_invalid_method): return _invalid_method - if '/' in argspec: - """Using AC's positional argument should add the explain""" + if '/' in argspec and len(argspec) < _MAX_COLS - len(_argument_positional): + # Add explanation TODO remove after 3.7, before 3.9. argspec += _argument_positional if isinstance(fob, type) and argspec == '()': - """fob with no argument, use default callable argspec""" + # If fob has no argument, use default callable argspec. argspec = _default_callable_argspec lines = (textwrap.wrap(argspec, _MAX_COLS, subsequent_indent=_INDENT) diff --git a/Lib/idlelib/idle_test/test_calltip.py b/Lib/idlelib/idle_test/test_calltip.py index 833351b..886959b 100644 --- a/Lib/idlelib/idle_test/test_calltip.py +++ b/Lib/idlelib/idle_test/test_calltip.py @@ -4,8 +4,7 @@ from idlelib import calltip import unittest import textwrap import types - -default_tip = calltip._default_callable_argspec +import re # Test Class TC is used in multiple get_argspec test methods @@ -28,6 +27,7 @@ class TC(): t6.tip = "(no, self)" def __call__(self, ci): 'doc' __call__.tip = "(self, ci)" + def nd(self): pass # No doc. # attaching .tip to wrapped methods does not work @classmethod def cm(cls, a): 'doc' @@ -36,11 +36,12 @@ class TC(): tc = TC() -signature = calltip.get_argspec # 2.7 and 3.x use different functions +default_tip = calltip._default_callable_argspec +get_spec = calltip.get_argspec -class Get_signatureTest(unittest.TestCase): - # The signature function must return a string, even if blank. +class Get_argspecTest(unittest.TestCase): + # The get_spec function must return a string, even if blank. # Test a variety of objects to be sure that none cause it to raise # (quite aside from getting as correct an answer as possible). # The tests of builtins may break if inspect or the docstrings change, @@ -49,57 +50,59 @@ class Get_signatureTest(unittest.TestCase): def test_builtins(self): + def tiptest(obj, out): + self.assertEqual(get_spec(obj), out) + # Python class that inherits builtin methods class List(list): "List() doc" # Simulate builtin with no docstring for default tip test class SB: __call__ = None - def gtest(obj, out): - self.assertEqual(signature(obj), out) - if List.__doc__ is not None: - gtest(List, '(iterable=(), /)' + calltip._argument_positional - + '\n' + List.__doc__) - gtest(list.__new__, + tiptest(List, + f'(iterable=(), /){calltip._argument_positional}' + f'\n{List.__doc__}') + tiptest(list.__new__, '(*args, **kwargs)\n' 'Create and return a new object. ' 'See help(type) for accurate signature.') - gtest(list.__init__, + tiptest(list.__init__, '(self, /, *args, **kwargs)' + calltip._argument_positional + '\n' + 'Initialize self. See help(type(self)) for accurate signature.') append_doc = (calltip._argument_positional + "\nAppend object to the end of the list.") - gtest(list.append, '(self, object, /)' + append_doc) - gtest(List.append, '(self, object, /)' + append_doc) - gtest([].append, '(object, /)' + append_doc) + tiptest(list.append, '(self, object, /)' + append_doc) + tiptest(List.append, '(self, object, /)' + append_doc) + tiptest([].append, '(object, /)' + append_doc) + + tiptest(types.MethodType, "method(function, instance)") + tiptest(SB(), default_tip) - gtest(types.MethodType, "method(function, instance)") - gtest(SB(), default_tip) - import re p = re.compile('') - gtest(re.sub, '''\ + tiptest(re.sub, '''\ (pattern, repl, string, count=0, flags=0) Return the string obtained by replacing the leftmost non-overlapping occurrences of the pattern in string by the replacement repl. repl can be either a string or a callable; if a string, backslash escapes in it are processed. If it is a callable, it's passed the Match object and must return''') - gtest(p.sub, '''\ + tiptest(p.sub, '''\ (repl, string, count=0) Return the string obtained by replacing the leftmost \ non-overlapping occurrences o...''') def test_signature_wrap(self): if textwrap.TextWrapper.__doc__ is not None: - self.assertEqual(signature(textwrap.TextWrapper), '''\ + self.assertEqual(get_spec(textwrap.TextWrapper), '''\ (width=70, initial_indent='', subsequent_indent='', expand_tabs=True, replace_whitespace=True, fix_sentence_endings=False, break_long_words=True, drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None, placeholder=' [...]')''') def test_properly_formated(self): + def foo(s='a'*100): pass @@ -112,35 +115,35 @@ non-overlapping occurrences o...''') indent = calltip._INDENT - str_foo = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\ - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\ - "aaaaaaaaaa')" - str_bar = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\ - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\ - "aaaaaaaaaa')\nHello Guido" - str_baz = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\ - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\ - "aaaaaaaaaa', z='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"\ - "bbbbbbbbbbbbbbbbb\n" + indent + "bbbbbbbbbbbbbbbbbbbbbb"\ - "bbbbbbbbbbbbbbbbbbbbbb')" - - self.assertEqual(calltip.get_argspec(foo), str_foo) - self.assertEqual(calltip.get_argspec(bar), str_bar) - self.assertEqual(calltip.get_argspec(baz), str_baz) + sfoo = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\ + "aaaaaaaaaa')" + sbar = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\ + "aaaaaaaaaa')\nHello Guido" + sbaz = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\ + "aaaaaaaaaa', z='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"\ + "bbbbbbbbbbbbbbbbb\n" + indent + "bbbbbbbbbbbbbbbbbbbbbb"\ + "bbbbbbbbbbbbbbbbbbbbbb')" + + for func,doc in [(foo, sfoo), (bar, sbar), (baz, sbaz)]: + with self.subTest(func=func, doc=doc): + self.assertEqual(get_spec(func), doc) def test_docline_truncation(self): def f(): pass f.__doc__ = 'a'*300 - self.assertEqual(signature(f), '()\n' + 'a' * (calltip._MAX_COLS-3) + '...') + self.assertEqual(get_spec(f), f"()\n{'a'*(calltip._MAX_COLS-3) + '...'}") def test_multiline_docstring(self): # Test fewer lines than max. - self.assertEqual(signature(range), + self.assertEqual(get_spec(range), "range(stop) -> range object\n" "range(start, stop[, step]) -> range object") # Test max lines - self.assertEqual(signature(bytes), '''\ + self.assertEqual(get_spec(bytes), '''\ bytes(iterable_of_ints) -> bytes bytes(string, encoding[, errors]) -> bytes bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer @@ -150,7 +153,7 @@ bytes() -> empty bytes object''') # Test more than max lines def f(): pass f.__doc__ = 'a\n' * 15 - self.assertEqual(signature(f), '()' + '\na' * calltip._MAX_LINES) + self.assertEqual(get_spec(f), '()' + '\na' * calltip._MAX_LINES) def test_functions(self): def t1(): 'doc' @@ -166,14 +169,16 @@ bytes() -> empty bytes object''') doc = '\ndoc' if t1.__doc__ is not None else '' for func in (t1, t2, t3, t4, t5, TC): - self.assertEqual(signature(func), func.tip + doc) + with self.subTest(func=func): + self.assertEqual(get_spec(func), func.tip + doc) def test_methods(self): doc = '\ndoc' if TC.__doc__ is not None else '' for meth in (TC.t1, TC.t2, TC.t3, TC.t4, TC.t5, TC.t6, TC.__call__): - self.assertEqual(signature(meth), meth.tip + doc) - self.assertEqual(signature(TC.cm), "(a)" + doc) - self.assertEqual(signature(TC.sm), "(b)" + doc) + with self.subTest(meth=meth): + self.assertEqual(get_spec(meth), meth.tip + doc) + self.assertEqual(get_spec(TC.cm), "(a)" + doc) + self.assertEqual(get_spec(TC.sm), "(b)" + doc) def test_bound_methods(self): # test that first parameter is correctly removed from argspec @@ -181,7 +186,8 @@ bytes() -> empty bytes object''') for meth, mtip in ((tc.t1, "()"), (tc.t4, "(*args)"), (tc.t6, "(self)"), (tc.__call__, '(ci)'), (tc, '(ci)'), (TC.cm, "(a)"),): - self.assertEqual(signature(meth), mtip + doc) + with self.subTest(meth=meth, mtip=mtip): + self.assertEqual(get_spec(meth), mtip + doc) def test_starred_parameter(self): # test that starred first parameter is *not* removed from argspec @@ -189,17 +195,18 @@ bytes() -> empty bytes object''') def m1(*args): pass c = C() for meth, mtip in ((C.m1, '(*args)'), (c.m1, "(*args)"),): - self.assertEqual(signature(meth), mtip) + with self.subTest(meth=meth, mtip=mtip): + self.assertEqual(get_spec(meth), mtip) - def test_invalid_method_signature(self): + def test_invalid_method_get_spec(self): class C: def m2(**kwargs): pass class Test: def __call__(*, a): pass mtip = calltip._invalid_method - self.assertEqual(signature(C().m2), mtip) - self.assertEqual(signature(Test()), mtip) + self.assertEqual(get_spec(C().m2), mtip) + self.assertEqual(get_spec(Test()), mtip) def test_non_ascii_name(self): # test that re works to delete a first parameter name that @@ -208,12 +215,9 @@ bytes() -> empty bytes object''') assert calltip._first_param.sub('', uni) == '(a)' def test_no_docstring(self): - def nd(s): - pass - TC.nd = nd - self.assertEqual(signature(nd), "(s)") - self.assertEqual(signature(TC.nd), "(s)") - self.assertEqual(signature(tc.nd), "()") + for meth, mtip in ((TC.nd, "(self)"), (tc.nd, "()")): + with self.subTest(meth=meth, mtip=mtip): + self.assertEqual(get_spec(meth), mtip) def test_attribute_exception(self): class NoCall: @@ -229,11 +233,13 @@ bytes() -> empty bytes object''') for meth, mtip in ((NoCall, default_tip), (CallA, default_tip), (NoCall(), ''), (CallA(), '(a, b, c)'), (CallB(), '(ci)')): - self.assertEqual(signature(meth), mtip) + with self.subTest(meth=meth, mtip=mtip): + self.assertEqual(get_spec(meth), mtip) def test_non_callables(self): for obj in (0, 0.0, '0', b'0', [], {}): - self.assertEqual(signature(obj), '') + with self.subTest(obj=obj): + self.assertEqual(get_spec(obj), '') class Get_entityTest(unittest.TestCase): |