diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst index f27b2ff1c09..df259109c6f 100644 --- a/Doc/library/curses.rst +++ b/Doc/library/curses.rst @@ -598,6 +598,17 @@ The module :mod:`curses` defines the following functions: Only one *ch* can be pushed before :meth:`getch` is called. +.. function:: unget_wch(ch) + + Push *ch* so the next :meth:`get_wch` will return it. + + .. note:: + + Only one *ch* can be pushed before :meth:`get_wch` is called. + + .. versionadded:: 3.3 + + .. function:: ungetmouse(id, x, y, z, bstate) Push a :const:`KEY_MOUSE` event onto the input queue, associating the given diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index c767e9388f6..8caf0deaef0 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -264,6 +264,20 @@ def test_issue6243(stdscr): curses.ungetch(1025) stdscr.getkey() +def test_unget_wch(stdscr): + ch = '\xe9' + curses.unget_wch(ch) + read = stdscr.get_wch() + read = chr(read) + if read != ch: + raise AssertionError("%r != %r" % (read, ch)) + + ch = ord('\xe9') + curses.unget_wch(ch) + read = stdscr.get_wch() + if read != ch: + raise AssertionError("%r != %r" % (read, ch)) + def main(stdscr): curses.savetty() try: @@ -272,6 +286,7 @@ def main(stdscr): test_userptr_without_set(stdscr) test_resize_term(stdscr) test_issue6243(stdscr) + test_unget_wch(stdscr) finally: curses.resetty() diff --git a/Misc/NEWS b/Misc/NEWS index fbcdb8d1989..85af2acf3e7 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -271,6 +271,9 @@ Core and Builtins Library ------- +- Issue #12567: Add curses.unget_wch() function. Push a character so the next + get_wch() will return it. + - Issue #9561: distutils and packaging now writes egg-info files using UTF-8, instead of the locale encoding. diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index 6d720245a72..ef0a66c0ede 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -2696,6 +2696,71 @@ PyCurses_UngetCh(PyObject *self, PyObject *args) return PyCursesCheckERR(ungetch(ch), "ungetch"); } +#ifdef HAVE_NCURSESW +/* Convert an object to a character (wchar_t): + + - int + - str of length 1 + + Return 1 on success, 0 on error. */ +static int +PyCurses_ConvertToWchar_t(PyObject *obj, + wchar_t *wch) +{ + if (PyUnicode_Check(obj)) { + wchar_t buffer[2]; + if (PyUnicode_AsWideChar(obj, buffer, 2) != 1) { + PyErr_Format(PyExc_TypeError, + "expect bytes or str of length 1, or int, " + "got a str of length %zi", + PyUnicode_GET_SIZE(obj)); + return 0; + } + *wch = buffer[0]; + return 2; + } + else if (PyLong_CheckExact(obj)) { + long value; + int overflow; + value = PyLong_AsLongAndOverflow(obj, &overflow); + if (overflow) { + PyErr_SetString(PyExc_OverflowError, + "int doesn't fit in long"); + return 0; + } + *wch = (wchar_t)value; + if ((long)*wch != value) { + PyErr_Format(PyExc_OverflowError, + "character doesn't fit in wchar_t"); + return 0; + } + return 1; + } + else { + PyErr_Format(PyExc_TypeError, + "expect bytes or str of length 1, or int, got %s", + Py_TYPE(obj)->tp_name); + return 0; + } +} + +static PyObject * +PyCurses_Unget_Wch(PyObject *self, PyObject *args) +{ + PyObject *obj; + wchar_t wch; + + PyCursesInitialised; + + if (!PyArg_ParseTuple(args,"O", &obj)) + return NULL; + + if (!PyCurses_ConvertToWchar_t(obj, &wch)) + return NULL; + return PyCursesCheckERR(unget_wch(wch), "unget_wch"); +} +#endif + static PyObject * PyCurses_Use_Env(PyObject *self, PyObject *args) { @@ -2823,6 +2888,9 @@ static PyMethodDef PyCurses_methods[] = { {"typeahead", (PyCFunction)PyCurses_TypeAhead, METH_VARARGS}, {"unctrl", (PyCFunction)PyCurses_UnCtrl, METH_VARARGS}, {"ungetch", (PyCFunction)PyCurses_UngetCh, METH_VARARGS}, +#ifdef HAVE_NCURSESW + {"unget_wch", (PyCFunction)PyCurses_Unget_Wch, METH_VARARGS}, +#endif {"use_env", (PyCFunction)PyCurses_Use_Env, METH_VARARGS}, #ifndef STRICT_SYSV_CURSES {"use_default_colors", (PyCFunction)PyCurses_Use_Default_Colors, METH_NOARGS},