https://github.com/python/cpython/commit/fe8ace30aeeb5991c59e2dcb65d8c32f0c262d26
commit: fe8ace30aeeb5991c59e2dcb65d8c32f0c262d26
branch: main
author: Vyron Vasileiadis <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-25T08:30:58Z
summary:

gh-156207: Fix curses Textbox.gather() for double-width characters (GH-156208)

gather() read the window cell by cell, so a double-width character, which
occupies two cells, was returned twice.  Read the whole line with in_wstr(),
which returns every character once.

files:
M Lib/curses/textpad.py
M Lib/test/test_curses.py

diff --git a/Lib/curses/textpad.py b/Lib/curses/textpad.py
index f6dfc990901d995..31ad37539ca4a42 100644
--- a/Lib/curses/textpad.py
+++ b/Lib/curses/textpad.py
@@ -200,14 +200,16 @@ def gather(self):
         result = ""
         self._update_max_yx()
         for y in range(self.maxy+1):
-            self.win.move(y, 0)
-            stop = self._end_of_line(y)
-            if stop == 0 and self.stripspaces:
-                continue
-            for x in range(self.maxx+1):
-                if self.stripspaces and x > stop:
-                    break
-                result = result + str(self.win.in_wch(y, x))
+            # The whole line: in_wstr() reads a double-width character once,
+            # skipping the continuation cell that holds its other half.
+            line = self.win.in_wstr(y, 0)
+            if self.stripspaces:
+                stripped = line.rstrip(' ')
+                if not stripped:
+                    continue
+                # Keep the blank the cursor rests on past the last character.
+                line = stripped + ' ' if len(stripped) < len(line) else 
stripped
+            result = result + line
             if self.maxy > 0:
                 result = result + "\n"
         return result
diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py
index 630de544a457f40..13b024941f739b3 100644
--- a/Lib/test/test_curses.py
+++ b/Lib/test/test_curses.py
@@ -2694,6 +2694,23 @@ def test_textbox_combining(self):
                 box.do_command(ch)
             self.assertEqual(box.gather(), text + ' ')
 
+    @requires_wide_build
+    def test_textbox_double_width(self):
+        # A double-width (East Asian) character occupies two cells.  gather()
+        # reads a whole line at a time so that the second cell, which holds
+        # the same character, is not reported as another one.
+        text = '你好'
+        if not self._encodable(text):
+            self.skipTest('the locale cannot encode %r' % text)
+        box, win = self._make_textbox(1, 12)
+        for ch in text:
+            box.do_command(ch)
+        self.assertEqual(box.gather(), text + ' ')
+        box, win = self._make_textbox(1, 12, stripspaces=False)
+        for ch in text:
+            box.do_command(ch)
+        self.assertEqual(box.gather(), text + ' ' * 8)
+
     def test_textbox_edit_wide(self):
         # edit() reads characters through get_wch().  Each character is pushed
         # with unget_wch(), which on a narrow build requires it to encode to a

_______________________________________________
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