Skip to content

gh-97928: Partially restore the behavior of tkinter.Text.count() by default #115031

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,12 @@ tkinter
a dict instead of a tuple.
(Contributed by Serhiy Storchaka in :gh:`43457`.)

* Add new optional keyword-only parameter *return_ints* in
the :meth:`!Text.count` method.
Passing ``return_ints=True`` makes it always returning the single count
as an integer instead of a 1-tuple or ``None``.
(Contributed by Serhiy Storchaka in :gh:`97928`.)

* Add support of the "vsapi" element type in
the :meth:`~tkinter.ttk.Style.element_create` method of
:class:`tkinter.ttk.Style`.
Expand Down Expand Up @@ -1247,13 +1253,6 @@ 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
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`.)

* Functions :c:func:`PyDict_GetItem`, :c:func:`PyDict_GetItemString`,
:c:func:`PyMapping_HasKey`, :c:func:`PyMapping_HasKeyString`,
:c:func:`PyObject_HasAttr`, :c:func:`PyObject_HasAttrString`, and
Expand Down
2 changes: 1 addition & 1 deletion Lib/idlelib/sidebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def get_displaylines(text, index):
"""Display height, in lines, of a logical line in a Tk text widget."""
return text.count(f"{index} linestart",
f"{index} lineend",
"displaylines")
"displaylines", return_ints=True)

def get_widget_padding(widget):
"""Get the total padding of a Tk widget, including its border."""
Expand Down
44 changes: 32 additions & 12 deletions Lib/test/test_tkinter/test_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,27 +52,47 @@ def test_count(self):
options = ('chars', 'indices', 'lines',
'displaychars', 'displayindices', 'displaylines',
'xpixels', 'ypixels')
self.assertEqual(len(text.count('1.0', 'end', *options, return_ints=True)), 8)
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.0', 'end', 'chars', 'lines', return_ints=True),
(124, 4))
self.assertEqual(text.count('1.3', '4.5', 'chars', 'lines'), (92, 3))
self.assertEqual(text.count('4.5', '1.3', 'chars', 'lines', return_ints=True),
(-92, -3))
self.assertEqual(text.count('4.5', '1.3', 'chars', 'lines'), (-92, -3))
self.assertEqual(text.count('1.3', '1.3', 'chars', 'lines', return_ints=True),
(0, 0))
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.assertEqual(text.count('1.0', 'end', 'indices'), 124)
self.assertEqual(text.count('1.0', 'end', 'lines', return_ints=True), 4)
self.assertEqual(text.count('1.0', 'end', 'lines'), (4,))
self.assertEqual(text.count('end', '1.0', 'lines', return_ints=True), -4)
self.assertEqual(text.count('end', '1.0', 'lines'), (-4,))
self.assertEqual(text.count('1.3', '1.5', 'lines', return_ints=True), 0)
self.assertEqual(text.count('1.3', '1.5', 'lines'), None)
self.assertEqual(text.count('1.3', '1.3', 'lines', return_ints=True), 0)
self.assertEqual(text.count('1.3', '1.3', 'lines'), None)
# Count 'indices' by default.
self.assertEqual(text.count('1.0', 'end', return_ints=True), 124)
self.assertEqual(text.count('1.0', 'end'), (124,))
self.assertEqual(text.count('1.0', 'end', 'indices', return_ints=True), 124)
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')

self.assertIsInstance(text.count('1.3', '1.5', 'ypixels'), int)
self.assertIsInstance(text.count('1.3', '1.5', 'ypixels', return_ints=True), int)
self.assertIsInstance(text.count('1.3', '1.5', 'ypixels'), tuple)
self.assertIsInstance(text.count('1.3', '1.5', 'update', 'ypixels', return_ints=True), 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.3', 'update', 'ypixels', return_ints=True), 0)
self.assertEqual(text.count('1.3', '1.3', 'update', 'ypixels'), None)
self.assertEqual(text.count('1.3', '1.5', 'update', 'indices', return_ints=True), 2)
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)
self.assertEqual(text.count('1.3', '1.3', 'update', 'indices', return_ints=True), 0)
self.assertEqual(text.count('1.3', '1.3', 'update', 'indices'), None)
self.assertEqual(text.count('1.3', '1.5', 'update', return_ints=True), 2)
self.assertEqual(text.count('1.3', '1.5', 'update'), (2,))
self.assertEqual(text.count('1.3', '1.3', 'update', return_ints=True), 0)
self.assertEqual(text.count('1.3', '1.3', 'update'), None)


if __name__ == "__main__":
Expand Down
23 changes: 15 additions & 8 deletions Lib/tkinter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3745,27 +3745,34 @@ def compare(self, index1, op, index2):
return self.tk.getboolean(self.tk.call(
self._w, 'compare', index1, op, index2))

def count(self, index1, index2, *options): # new in Tk 8.5
def count(self, index1, index2, *options, return_ints=False): # 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
(and this holds for each of the possible options).

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". 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."""
counting option given, if more than one option is specified or
return_ints is false (default), otherwise it is an integer.
Valid counting options are "chars", "displaychars",
"displayindices", "displaylines", "indices", "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.
"""
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
if not return_ints:
if not res:
res = None
elif len(options) <= 1:
res = (res,)
return res

def debug(self, boolean=None):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Partially revert the behavior of :meth:`tkinter.Text.count`. By default it
preserves the behavior of older Python versions, except that setting
``wantobjects`` to 0 no longer has effect. Add a new parameter *return_ints*:
specifying ``return_ints=True`` makes ``Text.count()`` always returning the
single count as an integer instead of a 1-tuple or ``None``.