From 5fe26fb50068243e68a39947d0d6bd7f59af6666 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 19 Jun 2026 00:19:18 +0300 Subject: [PATCH 1/2] gh-151674: Add tkinter Text.edit_canundo() and Text.edit_canredo() Wrap the Tk text widget "edit canundo" and "edit canredo" subcommands, which report whether the undo and redo stacks are non-empty. --- Doc/library/tkinter.rst | 14 +++++++++++ Doc/whatsnew/3.16.rst | 8 +++++++ Lib/test/test_tkinter/test_text.py | 23 +++++++++++++++++++ Lib/tkinter/__init__.py | 16 +++++++++++++ ...-06-19-12-00-00.gh-issue-151674.FCYTvs.rst | 3 +++ 5 files changed, 64 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-06-19-12-00-00.gh-issue-151674.FCYTvs.rst diff --git a/Doc/library/tkinter.rst b/Doc/library/tkinter.rst index b0421721bf8d7e..3d8983c57a12bf 100644 --- a/Doc/library/tkinter.rst +++ b/Doc/library/tkinter.rst @@ -5475,6 +5475,20 @@ Widget classes inserted or deleted. Otherwise set the flag to the boolean *arg*. + .. method:: edit_canundo() + + Return ``True`` if there is an edit action on the undo stack that can be + undone, and ``False`` otherwise. + + .. versionadded:: next + + .. method:: edit_canredo() + + Return ``True`` if there is an edit action on the redo stack that can be + reapplied, and ``False`` otherwise. + + .. versionadded:: next + .. method:: edit_undo() Undo the most recent edit action, that is, all the inserts and deletes diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 8e4c4a1e9b1de0..1068c585d7f734 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -138,6 +138,14 @@ shlex a string, even if it is already safe for a shell without being quoted. (Contributed by Jay Berry in :gh:`148846`.) +tkinter +------- + +* Added new :class:`!tkinter.Text` methods :meth:`~tkinter.Text.edit_canundo` + and :meth:`~tkinter.Text.edit_canredo` which return whether an undo or redo + is possible. + (Contributed by Serhiy Storchaka in :gh:`151674`.) + xml --- diff --git a/Lib/test/test_tkinter/test_text.py b/Lib/test/test_tkinter/test_text.py index 453a4505a0a4da..188b0dd2a9d549 100644 --- a/Lib/test/test_tkinter/test_text.py +++ b/Lib/test/test_tkinter/test_text.py @@ -25,6 +25,29 @@ def test_debug(self): text.debug(olddebug) self.assertEqual(text.debug(), olddebug) + def test_edit_undo_redo(self): + text = self.text + text.configure(undo=True) + + self.assertIs(text.edit_canundo(), False) + self.assertIs(text.edit_canredo(), False) + + text.insert('1.0', 'spam') + self.assertIs(text.edit_canundo(), True) + self.assertIs(text.edit_canredo(), False) + + text.edit_undo() + self.assertIs(text.edit_canundo(), False) + self.assertIs(text.edit_canredo(), True) + + text.edit_redo() + self.assertIs(text.edit_canundo(), True) + self.assertIs(text.edit_canredo(), False) + + text.edit_reset() + self.assertIs(text.edit_canundo(), False) + self.assertIs(text.edit_canredo(), False) + def test_search(self): text = self.text diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index ba8365f56c37a7..559fe87ed46193 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -3970,6 +3970,22 @@ def edit(self, *args): """ return self.tk.call(self._w, 'edit', *args) + def edit_canredo(self): + """Return whether redo is possible. + + Return True if redo is possible, i.e. when the redo stack is + not empty, and False otherwise. + """ + return self.tk.getboolean(self.edit("canredo")) + + def edit_canundo(self): + """Return whether undo is possible. + + Return True if undo is possible, i.e. when the undo stack is + not empty, and False otherwise. + """ + return self.tk.getboolean(self.edit("canundo")) + def edit_modified(self, arg=None): """Get or Set the modified flag diff --git a/Misc/NEWS.d/next/Library/2026-06-19-12-00-00.gh-issue-151674.FCYTvs.rst b/Misc/NEWS.d/next/Library/2026-06-19-12-00-00.gh-issue-151674.FCYTvs.rst new file mode 100644 index 00000000000000..84e21d4cbc4bd7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-19-12-00-00.gh-issue-151674.FCYTvs.rst @@ -0,0 +1,3 @@ +Add the :meth:`~tkinter.Text.edit_canundo` and :meth:`~tkinter.Text.edit_canredo` +methods of :class:`!tkinter.Text`, wrapping the Tk ``edit canundo`` and +``edit canredo`` subcommands. From 34c2bbb4ba4a980db1feba4492928a1bcfff0719 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 20 Jun 2026 02:06:30 +0300 Subject: [PATCH 2/2] gh-151678: Fix a duplicate test_edit_undo_redo in TextTest A second test_edit_undo_redo silently shadowed the earlier one, so its edit_canundo()/edit_canredo() coverage was never run. Rename it to test_edit_canundo_canredo and move it next to the other edit tests. Co-Authored-By: Claude Opus 4.8 (1M context) --- Lib/test/test_tkinter/test_text.py | 46 +++++++++++++++--------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_tkinter/test_text.py b/Lib/test/test_tkinter/test_text.py index 97653198073380..0279198b179404 100644 --- a/Lib/test/test_tkinter/test_text.py +++ b/Lib/test/test_tkinter/test_text.py @@ -26,29 +26,6 @@ def test_debug(self): text.debug(olddebug) self.assertEqual(text.debug(), olddebug) - def test_edit_undo_redo(self): - text = self.text - text.configure(undo=True) - - self.assertIs(text.edit_canundo(), False) - self.assertIs(text.edit_canredo(), False) - - text.insert('1.0', 'spam') - self.assertIs(text.edit_canundo(), True) - self.assertIs(text.edit_canredo(), False) - - text.edit_undo() - self.assertIs(text.edit_canundo(), False) - self.assertIs(text.edit_canredo(), True) - - text.edit_redo() - self.assertIs(text.edit_canundo(), True) - self.assertIs(text.edit_canredo(), False) - - text.edit_reset() - self.assertIs(text.edit_canundo(), False) - self.assertIs(text.edit_canredo(), False) - def test_index(self): text = self.text text.insert('1.0', 'Lorem ipsum\ndolor sit amet') @@ -329,6 +306,29 @@ def test_edit_undo_redo(self): text.edit_reset() self.assertRaises(TclError, text.edit_undo) + def test_edit_canundo_canredo(self): + text = self.text + text.configure(undo=True) + + self.assertIs(text.edit_canundo(), False) + self.assertIs(text.edit_canredo(), False) + + text.insert('1.0', 'spam') + self.assertIs(text.edit_canundo(), True) + self.assertIs(text.edit_canredo(), False) + + text.edit_undo() + self.assertIs(text.edit_canundo(), False) + self.assertIs(text.edit_canredo(), True) + + text.edit_redo() + self.assertIs(text.edit_canundo(), True) + self.assertIs(text.edit_canredo(), False) + + text.edit_reset() + self.assertIs(text.edit_canundo(), False) + self.assertIs(text.edit_canredo(), False) + def test_dump(self): text = self.text text.insert('1.0', 'hello')