From de2f3e39ed1f65fe20a0adfc95c8142163bef7bd Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 14 Oct 2022 17:33:09 +0300 Subject: [PATCH 1/4] gh-97928: Change the behavior of tkinter.Text.count() It now always returns an integer if one or less counting options are specified. Previously it could return a single count as a 1-tuple, an integer (only if option "update" was specified) or None if no items found. The result is now the same if wantobjects is set to 0. --- Doc/whatsnew/3.12.rst | 7 +++ Lib/test/test_tkinter/test_text.py | 54 ++++++------------- Lib/tkinter/__init__.py | 29 +++++----- ...2-10-14-21-11-10.gh-issue-97928.Pdxh1G.rst | 6 +++ 4 files changed, 46 insertions(+), 50 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-10-14-21-11-10.gh-issue-97928.Pdxh1G.rst diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 525efc405c8520..9ed7ee7c61dea4 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -520,6 +520,13 @@ Changes in the Python API :class:`bytes` type is accepted for bytes strings. (Contributed by Victor Stinner in :gh:`98393`.) +* :meth:`tkinter.Text.count` now always returns an integer if one or less + counting options are specified. + Previously it could return a single count as a 1-tuple, an integer (only if + option ``"update"`` was specified) or ``None`` if no items found. + The result is now the same if ``wantobjects`` is set to ``0``. + (Contributed by Serhiy Storchaka in :gh:`97928`.) + Build Changes ============= diff --git a/Lib/test/test_tkinter/test_text.py b/Lib/test/test_tkinter/test_text.py index 328e4256ce0711..c168e1e1610387 100644 --- a/Lib/test/test_tkinter/test_text.py +++ b/Lib/test/test_tkinter/test_text.py @@ -41,8 +41,6 @@ def test_search(self): self.assertEqual(text.search('test', '1.0', 'end'), '1.3') def test_count(self): - # XXX Some assertions do not check against the intended result, - # but instead check the current result to prevent regression. text = self.text text.insert('1.0', 'Lorem ipsum dolor sit amet,\n' @@ -53,44 +51,26 @@ def test_count(self): options = ('chars', 'indices', 'lines', 'displaychars', 'displayindices', 'displaylines', 'xpixels', 'ypixels') - if self.wantobjects: - self.assertEqual(len(text.count('1.0', 'end', *options)), 8) - else: - text.count('1.0', 'end', *options) - self.assertEqual(text.count('1.0', 'end', 'chars', 'lines'), (124, 4) - if self.wantobjects else '124 4') - self.assertEqual(text.count('1.3', '4.5', 'chars', 'lines'), (92, 3) - if self.wantobjects else '92 3') - self.assertEqual(text.count('4.5', '1.3', 'chars', 'lines'), (-92, -3) - if self.wantobjects else '-92 -3') - self.assertEqual(text.count('1.3', '1.3', 'chars', 'lines'), (0, 0) - if self.wantobjects else '0 0') - self.assertEqual(text.count('1.0', 'end', 'lines'), (4,) - if self.wantobjects else ('4',)) - self.assertEqual(text.count('end', '1.0', 'lines'), (-4,) - if self.wantobjects else ('-4',)) - self.assertEqual(text.count('1.3', '1.5', 'lines'), None - if self.wantobjects else ('0',)) - self.assertEqual(text.count('1.3', '1.3', 'lines'), None - if self.wantobjects else ('0',)) - self.assertEqual(text.count('1.0', 'end'), (124,) # 'indices' by default - if self.wantobjects else ('124',)) + self.assertEqual(len(text.count('1.0', 'end', *options)), 8) + self.assertEqual(text.count('1.0', 'end', 'chars', 'lines'), (124, 4)) + self.assertEqual(text.count('1.3', '4.5', 'chars', 'lines'), (92, 3)) + self.assertEqual(text.count('4.5', '1.3', 'chars', 'lines'), (-92, -3)) + self.assertEqual(text.count('1.3', '1.3', 'chars', 'lines'), (0, 0)) + self.assertEqual(text.count('1.0', 'end', 'lines'), 4) + self.assertEqual(text.count('end', '1.0', 'lines'), -4) + self.assertEqual(text.count('1.3', '1.5', 'lines'), 0) + self.assertEqual(text.count('1.3', '1.3', 'lines'), 0) + self.assertEqual(text.count('1.0', 'end'), 124) # 'indices' by default self.assertRaises(tkinter.TclError, text.count, '1.0', 'end', 'spam') self.assertRaises(tkinter.TclError, text.count, '1.0', 'end', '-lines') - self.assertIsInstance(text.count('1.3', '1.5', 'ypixels'), tuple) - self.assertIsInstance(text.count('1.3', '1.5', 'update', 'ypixels'), int - if self.wantobjects else str) - self.assertEqual(text.count('1.3', '1.3', 'update', 'ypixels'), None - if self.wantobjects else '0') - self.assertEqual(text.count('1.3', '1.5', 'update', 'indices'), 2 - if self.wantobjects else '2') - self.assertEqual(text.count('1.3', '1.3', 'update', 'indices'), None - if self.wantobjects else '0') - self.assertEqual(text.count('1.3', '1.5', 'update'), (2,) - if self.wantobjects else ('2',)) - self.assertEqual(text.count('1.3', '1.3', 'update'), None - if self.wantobjects else ('0',)) + self.assertIsInstance(text.count('1.3', '1.5', 'ypixels'), int) + self.assertIsInstance(text.count('1.3', '1.5', 'update', 'ypixels'), int) + self.assertEqual(text.count('1.3', '1.3', 'update', 'ypixels'), 0) + self.assertEqual(text.count('1.3', '1.5', 'update', 'indices'), 2) + self.assertEqual(text.count('1.3', '1.3', 'update', 'indices'), 0) + self.assertEqual(text.count('1.3', '1.5', 'update'), 2) + self.assertEqual(text.count('1.3', '1.3', 'update'), 0) if __name__ == "__main__": diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index a8e7bf490ad463..fac5678136f1ad 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -3636,25 +3636,28 @@ def compare(self, index1, op, index2): return self.tk.getboolean(self.tk.call( self._w, 'compare', index1, op, index2)) - def count(self, index1, index2, *args): # new in Tk 8.5 + def count(self, index1, index2, *options): # new in Tk 8.5 """Counts the number of relevant things between the two indices. - If index1 is after index2, the result will be a negative number + + If INDEX1 is after INDEX2, the result will be a negative number (and this holds for each of the possible options). - The actual items which are counted depends on the options given by - args. The result is a list of integers, one for the result of each - counting option given. Valid counting options are "chars", + The actual items which are counted depends on the options given. + The result is a tuple of integers, one for the result of each + counting option given, if more than one option is specified, + otherwise it is an integer. Valid counting options are "chars", "displaychars", "displayindices", "displaylines", "indices", - "lines", "xpixels" and "ypixels". There is an additional possible + "lines", "xpixels" and "ypixels". The default value, if no + option is specified, is "indices". There is an additional possible option "update", which if given then all subsequent options ensure that any possible out of date information is recalculated.""" - args = ['-%s' % arg for arg in args] - args += [index1, index2] - res = self.tk.call(self._w, 'count', *args) or None - if res is not None and len(args) <= 3: - return (res, ) - else: - return res + options = ['-%s' % arg for arg in options] + res = self.tk.call(self._w, 'count', *options, index1, index2) + if not isinstance(res, int): + res = self._getints(res) + if len(res) == 1: + res, = res + return res def debug(self, boolean=None): """Turn on the internal consistency checks of the B-Tree inside the text diff --git a/Misc/NEWS.d/next/Library/2022-10-14-21-11-10.gh-issue-97928.Pdxh1G.rst b/Misc/NEWS.d/next/Library/2022-10-14-21-11-10.gh-issue-97928.Pdxh1G.rst new file mode 100644 index 00000000000000..4acf396f840d61 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-10-14-21-11-10.gh-issue-97928.Pdxh1G.rst @@ -0,0 +1,6 @@ +Change the behavior of :meth:`tkinter.Text.count`. It now always returns an +integer if one or less counting options are specified. Previously it could +return a single count as a 1-tuple, an integer (only if option ``"update"`` +was specified) or ``None`` if no items found. The result is now the same if +``wantobjects`` is set to ``0``. + From 9a9b237a9e94483fa58a465fb1eba57b8ceda2d8 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 20 Oct 2022 17:05:07 +0300 Subject: [PATCH 2/4] Fix IDLE. --- Lib/idlelib/sidebar.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Lib/idlelib/sidebar.py b/Lib/idlelib/sidebar.py index fb1084dbf3f18b..166c04342907f9 100644 --- a/Lib/idlelib/sidebar.py +++ b/Lib/idlelib/sidebar.py @@ -25,10 +25,9 @@ def get_end_linenumber(text): def get_displaylines(text, index): """Display height, in lines, of a logical line in a Tk text widget.""" - res = text.count(f"{index} linestart", - f"{index} lineend", - "displaylines") - return res[0] if res else 0 + return text.count(f"{index} linestart", + f"{index} lineend", + "displaylines") def get_widget_padding(widget): """Get the total padding of a Tk widget, including its border.""" From f23eed801119c99b471242570a6741c60ac55d02 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 24 Oct 2023 11:39:32 +0300 Subject: [PATCH 3/4] Minor tweaks. --- Lib/test/test_tkinter/test_text.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_tkinter/test_text.py b/Lib/test/test_tkinter/test_text.py index c168e1e1610387..f809c4510e3a1f 100644 --- a/Lib/test/test_tkinter/test_text.py +++ b/Lib/test/test_tkinter/test_text.py @@ -10,6 +10,7 @@ class TextTest(AbstractTkTest, unittest.TestCase): def setUp(self): super().setUp() self.text = tkinter.Text(self.root) + self.text.pack() def test_debug(self): text = self.text @@ -61,6 +62,7 @@ def test_count(self): self.assertEqual(text.count('1.3', '1.5', 'lines'), 0) self.assertEqual(text.count('1.3', '1.3', 'lines'), 0) self.assertEqual(text.count('1.0', 'end'), 124) # 'indices' by default + self.assertEqual(text.count('1.0', 'end', 'indices'), 124) self.assertRaises(tkinter.TclError, text.count, '1.0', 'end', 'spam') self.assertRaises(tkinter.TclError, text.count, '1.0', 'end', '-lines') From 9d036a2226becd865db6de5eb3f8971b1fd92930 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 24 Oct 2023 12:06:49 +0300 Subject: [PATCH 4/4] Silence Sphinx warning. --- Doc/whatsnew/3.13.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index bf11437670d92a..a514659e383e4b 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -943,7 +943,7 @@ that may require changes to your code. Changes in the Python API ------------------------- -* :meth:`tkinter.Text.count` now always returns an integer if one or less +* :meth:`!tkinter.Text.count` now always returns an integer if one or less counting options are specified. Previously it could return a single count as a 1-tuple, an integer (only if option ``"update"`` was specified) or ``None`` if no items found.