https://github.com/python/cpython/commit/d4de07942819a8f7260f1d35b7fab39f3e065202
commit: d4de07942819a8f7260f1d35b7fab39f3e065202
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-06-27T10:45:50+03:00
summary:

[3.14] gh-71880: Allow editing the last cell in curses.textpad.Textbox 
(GH-152363) (GH-152365)

Textbox.edit() ignored typing in the lower-right cell of the window.  It is
now written with insch(), which fills the cell without moving the cursor out
of the window (addch() there raises an error and scrolls a scrollable window).
(cherry picked from commit 11b394381f30815a8ad0123afb0a55e7a0369f79)

Co-authored-by: Serhiy Storchaka <[email protected]>
Co-authored-by: Claude Opus 4.8 <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-06-26-23-56-40.gh-issue-71880.782D31.rst
M Lib/curses/textpad.py
M Lib/test/test_curses.py

diff --git a/Lib/curses/textpad.py b/Lib/curses/textpad.py
index c8dbf9fb614fcbe..57b2f4a523c95bc 100644
--- a/Lib/curses/textpad.py
+++ b/Lib/curses/textpad.py
@@ -74,16 +74,16 @@ def _insert_printable_char(self, ch):
         self._update_max_yx()
         (y, x) = self.win.getyx()
         backyx = None
-        while y < self.maxy or x < self.maxx:
+        while True:
             if self.insert_mode:
                 oldch = self.win.inch()
-            # The try-catch ignores the error we trigger from some curses
-            # versions by trying to write into the lowest-rightmost spot
-            # in the window.
-            try:
-                self.win.addch(ch)
-            except curses.error:
-                pass
+            if y >= self.maxy and x >= self.maxx:
+                # Use insch() in the lower-right cell: addch() there would move
+                # the cursor out of the window, raising an error and scrolling
+                # a scrollable window.
+                self.win.insch(ch)
+                break
+            self.win.addch(ch)
             if not self.insert_mode or not curses.ascii.isprint(oldch):
                 break
             ch = oldch
@@ -101,8 +101,7 @@ def do_command(self, ch):
         (y, x) = self.win.getyx()
         self.lastcmd = ch
         if curses.ascii.isprint(ch):
-            if y < self.maxy or x < self.maxx:
-                self._insert_printable_char(ch)
+            self._insert_printable_char(ch)
         elif ch == curses.ascii.SOH:                           # ^a
             self.win.move(y, 0)
         elif ch in (curses.ascii.STX,curses.KEY_LEFT,
diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py
index 2029c1d8a312327..5cbe83b8e868c82 100644
--- a/Lib/test/test_curses.py
+++ b/Lib/test/test_curses.py
@@ -1287,6 +1287,35 @@ def test_textbox_insert_mode(self):
         self._type(box, 'b')
         self.assertEqual(box.gather(), 'abXc ')
 
+    def test_textbox_fill_last_cell(self):
+        # The lower-right cell can be written, even though addch() there
+        # cannot advance the cursor past the end of the window.
+        box, win = self._make_textbox(1, 4, stripspaces=0)
+        self._type(box, 'abcd')
+        self.assertEqual(box.gather(), 'abcd')
+
+    def test_textbox_fill_last_cell_multiline(self):
+        box, win = self._make_textbox(2, 3, stripspaces=0)
+        self._type(box, 'abc')
+        box.do_command(curses.ascii.NL)    # ^j -> start of next line
+        self._type(box, 'def')             # 'f' lands in the lower-right cell
+        self.assertEqual(box.gather(), 'abc\ndef\n')
+
+    def test_textbox_fill_last_cell_insert_mode(self):
+        box, win = self._make_textbox(1, 4, insert_mode=True, stripspaces=0)
+        self._type(box, 'abcd')
+        self.assertEqual(box.gather(), 'abcd')
+
+    def test_textbox_fill_last_cell_scrollok(self):
+        # Writing the lower-right cell must not scroll the window even if it
+        # has scrolling enabled.
+        box, win = self._make_textbox(2, 3, stripspaces=0)
+        win.scrollok(True)
+        self._type(box, 'abc')
+        box.do_command(curses.ascii.NL)
+        self._type(box, 'def')
+        self.assertEqual(box.gather(), 'abc\ndef\n')
+
     def test_textbox_movement(self):
         box, win = self._make_textbox(3, 10)
         self._type(box, 'abc')
diff --git 
a/Misc/NEWS.d/next/Library/2026-06-26-23-56-40.gh-issue-71880.782D31.rst 
b/Misc/NEWS.d/next/Library/2026-06-26-23-56-40.gh-issue-71880.782D31.rst
new file mode 100644
index 000000000000000..5a12f428f2a8b6c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-06-26-23-56-40.gh-issue-71880.782D31.rst
@@ -0,0 +1,5 @@
+:class:`curses.textpad.Textbox` now lets the lower-right cell of the window be
+edited.  Writing it with :meth:`~curses.window.addch` would move the cursor
+past the end of the window, raising an error and scrolling a scrollable window,
+so it is now written with :meth:`~curses.window.insch`, which keeps the cursor
+in place.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to