Skip to content

Commit 1368b75

Browse files
committed
Add curses.window.in_wch
1 parent 7dc72b8 commit 1368b75

6 files changed

Lines changed: 152 additions & 1 deletion

File tree

Doc/library/curses.rst

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

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

10151027
.. method:: window.insch(ch[, attr])
10161028
window.insch(y, x, ch[, attr])

Doc/whatsnew/3.9.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ Add :func:`curses.get_escdelay`, :func:`curses.set_escdelay`,
152152
:func:`curses.get_tabsize`, and :func:`curses.set_tabsize` functions.
153153
(Contributed by Anthony Sottile in :issue:`38312`.)
154154

155+
Add :func:`curses.window.in_wch` function. (Contributed by Anthony Sottile in
156+
:issue:`39214`.)
157+
155158
fcntl
156159
-----
157160

Lib/test/test_curses.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,39 @@ def test_issue6243(self):
371371
curses.ungetch(1025)
372372
self.stdscr.getkey()
373373

374+
def test_in_wch(self):
375+
stdscr = self.stdscr
376+
377+
if not hasattr(stdscr, 'in_wch'):
378+
raise unittest.SkipTest('requires curses.window.in_wch')
379+
380+
if curses.has_colors():
381+
curses.init_pair(1, curses.COLOR_RED, 0)
382+
expected_pair = 1
383+
color_attr = curses.color_pair(expected_pair)
384+
else:
385+
expected_pair = 0
386+
color_attr = 0
387+
388+
def _norm(ch, attr, color):
389+
# for some reason, curses returns some color bits here?
390+
return ch, attr & ~curses.A_COLOR, color
391+
392+
for ch in ('a', '\xe9', '\u20ac', '\U0001f643'):
393+
expected = (ch, curses.A_BOLD, expected_pair)
394+
395+
stdscr.insstr(0, 0, ch, color_attr | curses.A_BOLD)
396+
self.assertEqual(_norm(*stdscr.in_wch()), expected)
397+
398+
stdscr.addstr(0, 0, ch, color_attr | curses.A_BOLD)
399+
self.assertEqual(_norm(*stdscr.in_wch(0, 0)), expected)
400+
401+
# in_wch can return multiple characters in the case of zero width
402+
# curses at max returns 5 characters in a cchar_t
403+
stdscr.addstr(0, 0, 'a' + '\u200b' * 10)
404+
expected = ('a\u200b\u200b\u200b\u200b', 0, 0)
405+
self.assertEqual(stdscr.in_wch(0, 0), expected)
406+
374407
@requires_curses_func('unget_wch')
375408
@unittest.skipIf(getattr(curses, 'ncurses_version', (99,)) < (5, 8),
376409
"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` functions - by Anthony Sottile.

Modules/_cursesmodule.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1579,6 +1579,54 @@ _curses_window_inch_impl(PyCursesWindowObject *self, int group_right_1,
15791579
return rtn;
15801580
}
15811581

1582+
#ifdef HAVE_NCURSESW
1583+
/*[clinic input]
1584+
_curses.window.in_wch
1585+
1586+
[
1587+
y: int
1588+
Starting Y-coordinate.
1589+
x: int
1590+
Starting X-coordinate.
1591+
]
1592+
/
1593+
1594+
Retrieve the wide character at the gven position in the window.
1595+
1596+
The return value is a 3-tuple of (character, attributes, color_pair).
1597+
[clinic start generated code]*/
1598+
1599+
static PyObject *
1600+
_curses_window_in_wch_impl(PyCursesWindowObject *self, int group_right_1,
1601+
int y, int x)
1602+
/*[clinic end generated code: output=846ca8a82f2ecab4 input=5c20d96b592b0e0b]*/
1603+
{
1604+
int ret;
1605+
1606+
cchar_t wcval;
1607+
1608+
wchar_t wstr[CCHARW_MAX + 1];
1609+
attr_t attr;
1610+
short color_pair;
1611+
1612+
if (group_right_1) {
1613+
ret = mvwin_wch(self->win, y, x, &wcval);
1614+
} else {
1615+
ret = win_wch(self->win, &wcval);
1616+
}
1617+
if (PyCursesCheckERR(ret, "in_wch") == NULL) {
1618+
return NULL;
1619+
}
1620+
1621+
ret = getcchar(&wcval, wstr, &attr, &color_pair, NULL);
1622+
if (PyCursesCheckERR(ret, "getcchar") == NULL) {
1623+
return NULL;
1624+
}
1625+
1626+
return Py_BuildValue("ukh", wstr, attr, color_pair);
1627+
}
1628+
#endif
1629+
15821630
/*[-clinic input]
15831631
_curses.window.instr
15841632
@@ -2374,6 +2422,7 @@ static PyMethodDef PyCursesWindow_Methods[] = {
23742422
{"immedok", (PyCFunction)PyCursesWindow_immedok, METH_VARARGS},
23752423
#endif
23762424
_CURSES_WINDOW_INCH_METHODDEF
2425+
_CURSES_WINDOW_IN_WCH_METHODDEF
23772426
_CURSES_WINDOW_INSCH_METHODDEF
23782427
{"insdelln", (PyCFunction)PyCursesWindow_winsdelln, METH_VARARGS},
23792428
{"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)