Skip to content

Commit f16f2b8

Browse files
committed
Add curses.window.in_wch
1 parent feb51f3 commit f16f2b8

6 files changed

Lines changed: 155 additions & 1 deletion

File tree

Doc/library/curses.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,18 @@ the following methods and attributes:
10141014
Return the character at the given position in the window. The bottom 8 bits are
10151015
the character proper, and upper bits are the attributes.
10161016

1017+
.. note::
1018+
1019+
``inch`` only works for ASCII characters. Use :meth:`in_wch` instead
1020+
for unicode support.
1021+
1022+
.. method:: window.in_wch([x, y])
1023+
1024+
Return the wide character at the given position in the window. The return
1025+
value is a 3-tuple ``(character, attributes, color_pair)``.
1026+
1027+
.. versionadded:: 3.13
1028+
10171029

10181030
.. method:: window.insch(ch[, attr])
10191031
window.insch(y, x, ch[, attr])

Doc/whatsnew/3.13.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ array
9494
It can be used instead of ``'u'`` type code, which is deprecated.
9595
(Contributed by Inada Naoki in :gh:`80480`.)
9696

97+
curses
98+
------
99+
100+
* Add :func:`curses.window.in_wch` function. (Contributed by Anthony Sottile
101+
in :gh:`83395`.)
102+
97103
io
98104
--
99105

Lib/test/test_curses.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,39 @@ def test_issue6243(self):
11071107
curses.ungetch(1025)
11081108
self.stdscr.getkey()
11091109

1110+
def test_in_wch(self):
1111+
stdscr = self.stdscr
1112+
1113+
if not hasattr(stdscr, 'in_wch'):
1114+
raise unittest.SkipTest('requires curses.window.in_wch')
1115+
1116+
if curses.has_colors():
1117+
curses.init_pair(1, curses.COLOR_RED, 0)
1118+
expected_pair = 1
1119+
color_attr = curses.color_pair(expected_pair)
1120+
else:
1121+
expected_pair = 0
1122+
color_attr = 0
1123+
1124+
def _norm(ch, attr, color):
1125+
# for some reason, curses returns some color bits here?
1126+
return ch, attr & ~curses.A_COLOR, color
1127+
1128+
for ch in ('a', '\xe9', '\u20ac', '\U0001f643'):
1129+
expected = (ch, curses.A_BOLD, expected_pair)
1130+
1131+
stdscr.insstr(0, 0, ch, color_attr | curses.A_BOLD)
1132+
self.assertEqual(_norm(*stdscr.in_wch()), expected)
1133+
1134+
stdscr.addstr(0, 0, ch, color_attr | curses.A_BOLD)
1135+
self.assertEqual(_norm(*stdscr.in_wch(0, 0)), expected)
1136+
1137+
# in_wch can return multiple characters in the case of zero width
1138+
# curses at max returns 5 characters in a cchar_t
1139+
stdscr.addstr(0, 0, 'a' + '\u200b' * 10)
1140+
expected = ('a\u200b\u200b\u200b\u200b', 0, 0)
1141+
self.assertEqual(stdscr.in_wch(0, 0), expected)
1142+
11101143
@requires_curses_func('unget_wch')
11111144
@unittest.skipIf(getattr(curses, 'ncurses_version', (99,)) < (5, 8),
11121145
"unget_wch is broken in ncurses 5.7 and earlier")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add :func:`curses.window.in_wch` function - by Anthony Sottile.

Modules/_cursesmodule.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1722,6 +1722,54 @@ _curses_window_inch_impl(PyCursesWindowObject *self, int group_right_1,
17221722
return rtn;
17231723
}
17241724

1725+
#ifdef HAVE_NCURSESW
1726+
/*[clinic input]
1727+
_curses.window.in_wch
1728+
1729+
[
1730+
y: int
1731+
Starting Y-coordinate.
1732+
x: int
1733+
Starting X-coordinate.
1734+
]
1735+
/
1736+
1737+
Retrieve the wide character at the gven position in the window.
1738+
1739+
The return value is a 3-tuple of (character, attributes, color_pair).
1740+
[clinic start generated code]*/
1741+
1742+
static PyObject *
1743+
_curses_window_in_wch_impl(PyCursesWindowObject *self, int group_right_1,
1744+
int y, int x)
1745+
/*[clinic end generated code: output=846ca8a82f2ecab4 input=5c20d96b592b0e0b]*/
1746+
{
1747+
int ret;
1748+
1749+
cchar_t wcval;
1750+
1751+
wchar_t wstr[CCHARW_MAX + 1];
1752+
attr_t attr;
1753+
short color_pair;
1754+
1755+
if (group_right_1) {
1756+
ret = mvwin_wch(self->win, y, x, &wcval);
1757+
} else {
1758+
ret = win_wch(self->win, &wcval);
1759+
}
1760+
if (PyCursesCheckERR(ret, "in_wch") == NULL) {
1761+
return NULL;
1762+
}
1763+
1764+
ret = getcchar(&wcval, wstr, &attr, &color_pair, NULL);
1765+
if (PyCursesCheckERR(ret, "getcchar") == NULL) {
1766+
return NULL;
1767+
}
1768+
1769+
return Py_BuildValue("ukh", wstr, attr, color_pair);
1770+
}
1771+
#endif
1772+
17251773
/*[-clinic input]
17261774
_curses.window.instr
17271775
@@ -2516,6 +2564,7 @@ static PyMethodDef PyCursesWindow_Methods[] = {
25162564
{"immedok", (PyCFunction)PyCursesWindow_immedok, METH_VARARGS},
25172565
#endif
25182566
_CURSES_WINDOW_INCH_METHODDEF
2567+
_CURSES_WINDOW_IN_WCH_METHODDEF
25192568
_CURSES_WINDOW_INSCH_METHODDEF
25202569
{"insdelln", (PyCFunction)PyCursesWindow_winsdelln, METH_VARARGS},
25212570
{"insertln", (PyCFunction)PyCursesWindow_winsertln, METH_NOARGS},

Modules/clinic/_cursesmodule.c.h

Lines changed: 54 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)