Skip to content

Commit 223cbff

Browse files
gh-153864: Fix curses window.insch() for non-ASCII characters on a wide build (GH-153865)
On a wide build, winsch() does not locale-decode a byte above 127, unlike waddch(), so insch() inserted '¤' (U+00A4) instead of '€' for byte 0xA4 under ISO-8859-15. Decode the byte with btowc() and insert it as a wide character, like addch(). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9f5af27 commit 223cbff

3 files changed

Lines changed: 25 additions & 7 deletions

File tree

Lib/test/test_curses.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -785,13 +785,10 @@ def test_output_character(self):
785785
stdscr.move(2, 0)
786786
stdscr.echochar(v)
787787
self.assertEqual(self._read_char(2, 0), c)
788-
# insch() round-trips a byte only where its code point equals
789-
# the byte value (Latin-1): on a wide build ncurses winsch
790-
# stores a printable byte directly as a code point instead of
791-
# decoding it through the locale.
792-
if ord(c) < 0x100:
793-
stdscr.insch(1, 0, v)
794-
self.assertEqual(self._read_char(1, 0), c)
788+
# insch() decodes the byte through the locale like addch(), so
789+
# it round-trips the same character.
790+
stdscr.insch(1, 0, v)
791+
self.assertEqual(self._read_char(1, 0), c)
795792

796793
# The same characters supplied as a str. Unlike the int path above, a
797794
# str is stored as a wide-character cell on a wide build, so every
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
On a wide :mod:`curses` build, :meth:`curses.window.insch` now inserts a
2+
non-ASCII byte as the character it encodes in the window's encoding,
3+
consistently with :meth:`~curses.window.addch`, instead of its code point.

Modules/_cursesmodule.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3568,6 +3568,24 @@ _curses_window_insch_impl(PyCursesWindowObject *self, int group_left_1,
35683568
if (type == 0) {
35693569
return NULL;
35703570
}
3571+
if (type == 1) {
3572+
/* winsch() does not locale-decode a byte above 127 on a wide build,
3573+
unlike waddch(), so decode it here and insert it as a wide
3574+
character. (gh-153864) */
3575+
chtype cch = ch_ & A_CHARTEXT;
3576+
if (cch > 127) {
3577+
wint_t wc = btowc((int)cch);
3578+
if (wc != WEOF) {
3579+
wchar_t wstr[2] = { (wchar_t)wc, L'\0' };
3580+
attr_t cattr = (attr_t)((ch_ | attr) & ~(chtype)A_CHARTEXT);
3581+
if (curses_setcchar(&wch, wstr, cattr, PAIR_NUMBER(cattr)) == ERR) {
3582+
curses_window_set_error(self, "setcchar", "insch");
3583+
return NULL;
3584+
}
3585+
type = 2;
3586+
}
3587+
}
3588+
}
35713589
if (type == 2) {
35723590
if (!group_left_1) {
35733591
rtn = wins_wch(self->win, &wch);

0 commit comments

Comments
 (0)