From 81376a8b1514cdce9721d94b32dd24b14953524b Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 23 Aug 2026 14:57:32 +0300 Subject: [PATCH] [3.13] gh-156234: Fix and rewrite the curses documentation on reading (GH-156235) Fix wrong types: instr() and getstr() return a bytes object, not a str, and their n limits the number of bytes; getkey() returns a str; unctrl() returns a bytes object. Make clear whether an integer standing for a character is an encoded byte or a character code. Rewrite the documentation of getch(), get_wch(), getkey(), getstr() and instr(), following X/Open Curses. The docstrings of getstr() and instr() said that n defaults to 2047, while the limit on this branch is 1023. (cherry picked from commit be87bfa8a1a68516cedc618c7cf5bb7b0b251678) --- Doc/library/curses.rst | 93 ++++++++++++++++++++++++-------- Modules/_cursesmodule.c | 90 +++++++++++++++++-------------- Modules/clinic/_cursesmodule.c.h | 46 ++++++++++------ 3 files changed, 149 insertions(+), 80 deletions(-) diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst index 989505f79e68c2..61fb8b9da0ce30 100644 --- a/Doc/library/curses.rst +++ b/Doc/library/curses.rst @@ -28,6 +28,8 @@ Linux and the BSD variants of Unix. Whenever the documentation mentions a *character* it can be specified as an integer, a one-character Unicode string or a one-byte byte string. + An integer is the code of a single encoded byte, optionally combined with + attributes and a color pair, as returned by :meth:`window.inch`. Whenever the documentation mentions a *character string* it can be specified as a Unicode string or a byte string. @@ -484,8 +486,8 @@ The module :mod:`curses` defines the following functions: .. function:: putp(str) Equivalent to ``tputs(str, 1, putchar)``; emit the value of a specified - terminfo capability for the current terminal. Note that the output of :func:`putp` - always goes to standard output. + terminfo capability, a bytes object, for the current terminal. + Note that the output of :func:`putp` always goes to standard output. :func:`setupterm` (or :func:`initscr`) must be called first. @@ -656,7 +658,7 @@ The module :mod:`curses` defines the following functions: .. function:: tparm(str[, ...]) Instantiate the bytes object *str* with the supplied parameters, where *str* should - be a parameterized string obtained from the terminfo database. For example, + be a parameterized byte string obtained from the terminfo database. For example, ``tparm(tigetstr("cup"), 5, 3)`` could result in ``b'\033[6;4H'``, the exact result depending on terminal type. Up to nine integer parameters may be supplied. @@ -677,7 +679,8 @@ The module :mod:`curses` defines the following functions: .. function:: unctrl(ch) - Return a bytes object which is a printable representation of the character *ch*. + Return a bytes object which is a printable representation of the character *ch*; + any attributes and color pair are ignored. Control characters are represented as a caret followed by the character, for example as ``b'^C'``. Printing characters are left as they are. @@ -686,6 +689,9 @@ The module :mod:`curses` defines the following functions: Push *ch* so the next :meth:`~window.getch` will return it. + *ch* may be an integer (a key code or the code of an encoded byte), a byte, + or a string of length 1 which encodes to a single byte. + .. note:: Only one *ch* can be pushed before :meth:`!getch` is called. @@ -703,6 +709,9 @@ The module :mod:`curses` defines the following functions: Push *ch* so the next :meth:`~window.get_wch` will return it. + *ch* may be an integer (a character code, not a key code) or a string of + length 1. + .. note:: Only one *ch* can be pushed before :meth:`!get_wch` is called. @@ -988,27 +997,58 @@ Window objects .. method:: window.getch([y, x]) - Get a character. Note that the integer returned does *not* have to be in ASCII - range: function keys, keypad keys and so on are represented by numbers higher - than 255. In no-delay mode, return ``-1`` if there is no input, otherwise - wait until a key is pressed. + Read a key press, after moving the cursor to *y*, *x* if specified, + and return it as an integer. + The window is refreshed first if it is not a pad and was modified since + the last refresh. + Wait until a key is pressed, or return ``-1`` if the read is non-blocking + or times out (see :meth:`nodelay` and :meth:`timeout`). + + An ordinary key is returned as the code of a single byte of its encoding + in the current locale, + so a character encoded with several bytes takes several calls. + For example, in a UTF-8 locale ``'é'`` is read as ``195``, then ``169``. + Use :meth:`get_wch` to read it as a single character. + + In keypad mode (see :meth:`keypad`) function keys and other special keys + are returned as one of the :ref:`KEY_* constants `, + which cannot be mistaken for an ordinary key. + Otherwise, or if their escape sequence does not arrive in time + (see :meth:`notimeout` and :func:`set_escdelay`), + their bytes are returned one at a time. + + In echo mode (see :func:`echo`) the key is added to the window as by + :meth:`addch`; special keys are not echoed. .. method:: window.get_wch([y, x]) - Get a wide character. Return a character for most keys, or an integer for - function keys, keypad keys, and other special keys. - In no-delay mode, raise an exception if there is no input. + Read a key press, after moving the cursor to *y*, *x* if specified, + and return it as a one-character :class:`str`. + The window is refreshed first if it is not a pad and was modified since + the last refresh. + Wait until a key is pressed, or raise :exc:`error` if the read is + non-blocking or times out (see :meth:`nodelay` and :meth:`timeout`). + + In keypad mode (see :meth:`keypad`) function keys and other special keys + are returned as one of the :ref:`KEY_* constants `, + an integer. + Otherwise, or if their escape sequence does not arrive in time + (see :meth:`notimeout` and :func:`set_escdelay`), + their characters are returned one at a time. + + In echo mode (see :func:`echo`) the key is added to the window as by + :meth:`addch`; special keys are not echoed. .. versionadded:: 3.3 .. method:: window.getkey([y, x]) - Get a character, returning a string instead of an integer, as :meth:`getch` - does. Function keys, keypad keys and other special keys return a multibyte - string containing the key name. In no-delay mode, raise an exception if - there is no input. + Read a key press as :meth:`getch` does, but return it as a :class:`str`: + an ordinary key as a one-character string, the byte decoded as Latin-1, + and a special key as its name, such as ``'KEY_UP'`` (see :func:`keyname`). + Raise :exc:`error` instead of returning ``-1`` if there is no input. .. method:: window.getmaxyx() @@ -1028,9 +1068,12 @@ Window objects window.getstr(y, x) window.getstr(y, x, n) - Read a bytes object from the user, with primitive line editing capacity. - At most *n* characters are read (1023 by default). - The maximum value for *n* is 1023. + Read a line of input from the user, with primitive line editing capacity, + after moving the cursor to *y*, *x* if specified. + Return it as a bytes object, in the encoding of the current locale + and without the terminating newline. + At most *n* bytes are read; + *n* defaults to and cannot exceed 1023. .. method:: window.getyx() @@ -1128,11 +1171,11 @@ Window objects .. method:: window.instr([n]) window.instr(y, x[, n]) - Return a bytes object of characters, extracted from the window starting at the - current cursor position, or at *y*, *x* if specified, and stopping at the end - of the line. Attributes and color information are stripped - from the characters. If *n* is specified, :meth:`instr` returns a string - at most *n* characters long (exclusive of the trailing NUL). + Read the text of the window from the current cursor position, + or from *y*, *x* if specified, to the end of the line, + and return it as a bytes object, in the encoding of the current locale. + Attributes and color pairs are stripped. + At most *n* bytes are read; *n* defaults to and cannot exceed 1023. .. method:: window.is_linetouched(line) @@ -1153,6 +1196,8 @@ Window objects If *flag* is ``True``, escape sequences generated by some keys (keypad, function keys) will be interpreted by :mod:`curses`. If *flag* is ``False``, escape sequences will be left as is in the input stream. + Keypad mode is disabled by default, but :func:`wrapper` enables it for the + main window. .. method:: window.leaveok(flag) @@ -1505,6 +1550,8 @@ by some methods. | | color-pair field information | +-------------------------+-------------------------------+ +.. _curses-key-constants: + Keys are referred to by integer constants with names starting with ``KEY_``. The exact keycaps available are system dependent. diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index 1996488a0e554a..fa3c6cda76d779 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -1403,18 +1403,25 @@ _curses.window.getch ] / -Get a character code from terminal keyboard. +Read a key press and return it as an integer. -The integer returned does not have to be in ASCII range: function -keys, keypad keys and so on return numbers higher than 256. In -no-delay mode, -1 is returned if there is no input, else getch() -waits until a key is pressed. +Wait until a key is pressed, or return -1 if the read is +non-blocking or times out. + +An ordinary key is returned as the code of a single byte of its +encoding in the current locale, so a character encoded with several +bytes takes several calls. Use get_wch() to read it as a single +character. + +In keypad mode function keys and other special keys are returned as +one of the KEY_* constants, which cannot be mistaken for an ordinary +key. Otherwise their bytes are returned one at a time. [clinic start generated code]*/ static PyObject * _curses_window_getch_impl(PyCursesWindowObject *self, int group_right_1, int y, int x) -/*[clinic end generated code: output=e1639e87d545e676 input=0dc5ff40e079787a]*/ +/*[clinic end generated code: output=e1639e87d545e676 input=882ddab9b41afbbd]*/ { int rtn; @@ -1451,18 +1458,18 @@ _curses.window.getkey ] / -Get a character (string) from terminal keyboard. +Read a key press and return it as a str. -Returning a string instead of an integer, as getch() does. Function -keys, keypad keys and other special keys return a multibyte string -containing the key name. In no-delay mode, an exception is raised -if there is no input. +Read as getch() does, but return an ordinary key as a one-character +string, the byte decoded as Latin-1, and a special key as its name, +such as 'KEY_UP'. Raise curses.error instead of returning -1 if +there is no input. [clinic start generated code]*/ static PyObject * _curses_window_getkey_impl(PyCursesWindowObject *self, int group_right_1, int y, int x) -/*[clinic end generated code: output=8490a182db46b10f input=bd24a7da1ed9c73b]*/ +/*[clinic end generated code: output=8490a182db46b10f input=f054cf034c69e879]*/ { int rtn; @@ -1509,16 +1516,20 @@ _curses.window.get_wch ] / -Get a wide character from terminal keyboard. +Read a key press and return it as a one-character str. + +Wait until a key is pressed, or raise curses.error if the read is +non-blocking or times out. -Return a character for most keys, or an integer for function keys, -keypad keys, and other special keys. +In keypad mode function keys and other special keys are returned as +one of the KEY_* constants, an integer. Otherwise their characters +are returned one at a time. [clinic start generated code]*/ static PyObject * _curses_window_get_wch_impl(PyCursesWindowObject *self, int group_right_1, int y, int x) -/*[clinic end generated code: output=9f4f86e91fe50ef3 input=dd7e5367fb49dc48]*/ +/*[clinic end generated code: output=9f4f86e91fe50ef3 input=77eb2da426ebe71f]*/ { int ct; wint_t rtn; @@ -1557,10 +1568,10 @@ _curses.window.getstr X-coordinate. ] n: int = 1023 - Maximal number of characters. + Maximal number of bytes. / -Read a string from the user, with primitive line editing capacity. +Read a line of input and return it as a bytes object. [-clinic start generated code]*/ static PyObject * @@ -1815,15 +1826,15 @@ _curses.window.instr X-coordinate. ] n: int = 1023 - Maximal number of characters. + Maximal number of bytes. / -Return a string of characters, extracted from the window. +Return the text of the window as a bytes object. -Return a string of characters, extracted from the window starting at the -current cursor position, or at y, x if specified. Attributes are stripped -from the characters. If n is specified, instr() returns a string at most -n characters long (exclusive of the trailing NUL). +Read from the current cursor position, or from y, x if specified, to +the end of the line, and return the text in the encoding of the +current locale, with attributes and color pairs stripped. At most n +bytes are read. [-clinic start generated code]*/ static PyObject * PyCursesWindow_InStr(PyCursesWindowObject *self, PyObject *args) @@ -2585,33 +2596,31 @@ PyDoc_STRVAR(_curses_window_chgat__doc__, "window refresh."); PyDoc_STRVAR(_curses_window_getstr__doc__, -"getstr([[y, x,] n=2047])\n" -"Read a string from the user, with primitive line editing capacity.\n" +"getstr([[y, x,] n=1023])\n" +"Read a line of input and return it as a bytes object.\n" "\n" " y\n" " Y-coordinate.\n" " x\n" " X-coordinate.\n" " n\n" -" Maximal number of characters."); +" Maximal number of bytes."); PyDoc_STRVAR(_curses_window_instr__doc__, -"instr([y, x,] n=2047)\n" -"Return a string of characters, extracted from the window.\n" +"instr([y, x,] n=1023)\n" +"Return the text of the window as a bytes object.\n" "\n" " y\n" " Y-coordinate.\n" " x\n" " X-coordinate.\n" " n\n" -" Maximal number of characters.\n" +" Maximal number of bytes.\n" "\n" -"Return a string of characters, extracted from the window starting\n" -"at the current cursor position, or at y, x if specified, and\n" -"stopping at the end of the line. Attributes and color\n" -"information are stripped from the characters. If n is specified,\n" -"instr() returns a string at most n characters long (exclusive of\n" -"the trailing NUL)."); +"Read from the current cursor position, or from y, x if specified, to\n" +"the end of the line, and return the text in the encoding of the\n" +"current locale, with attributes and color pairs stripped. At most n\n" +"bytes are read."); static PyMethodDef PyCursesWindow_Methods[] = { _CURSES_WINDOW_ADDCH_METHODDEF @@ -4656,15 +4665,16 @@ _curses.unctrl ch: object / -Return a string which is a printable representation of the character ch. +Return a bytes object which is a printable representation of ch. -Control characters are displayed as a caret followed by the character, -for example as ^C. Printing characters are left as they are. +Control characters are displayed as a caret followed by the +character, for example as ^C. Printing characters are left as they +are. Any attributes and color pair are ignored. [clinic start generated code]*/ static PyObject * _curses_unctrl(PyObject *module, PyObject *ch) -/*[clinic end generated code: output=8e07fafc430c9434 input=cd1e35e16cd1ace4]*/ +/*[clinic end generated code: output=8e07fafc430c9434 input=6732d59733d3ed5b]*/ { chtype ch_; diff --git a/Modules/clinic/_cursesmodule.c.h b/Modules/clinic/_cursesmodule.c.h index 84069010cdcf22..8d2e008299a96e 100644 --- a/Modules/clinic/_cursesmodule.c.h +++ b/Modules/clinic/_cursesmodule.c.h @@ -758,17 +758,24 @@ _curses_window_getbkgd(PyCursesWindowObject *self, PyObject *Py_UNUSED(ignored)) PyDoc_STRVAR(_curses_window_getch__doc__, "getch([y, x])\n" -"Get a character code from terminal keyboard.\n" +"Read a key press and return it as an integer.\n" "\n" " y\n" " Y-coordinate.\n" " x\n" " X-coordinate.\n" "\n" -"The integer returned does not have to be in ASCII range: function\n" -"keys, keypad keys and so on return numbers higher than 256. In\n" -"no-delay mode, -1 is returned if there is no input, else getch()\n" -"waits until a key is pressed."); +"Wait until a key is pressed, or return -1 if the read is\n" +"non-blocking or times out.\n" +"\n" +"An ordinary key is returned as the code of a single byte of its\n" +"encoding in the current locale, so a character encoded with several\n" +"bytes takes several calls. Use get_wch() to read it as a single\n" +"character.\n" +"\n" +"In keypad mode function keys and other special keys are returned as\n" +"one of the KEY_* constants, which cannot be mistaken for an ordinary\n" +"key. Otherwise their bytes are returned one at a time."); #define _CURSES_WINDOW_GETCH_METHODDEF \ {"getch", (PyCFunction)_curses_window_getch, METH_VARARGS, _curses_window_getch__doc__}, @@ -806,17 +813,17 @@ _curses_window_getch(PyCursesWindowObject *self, PyObject *args) PyDoc_STRVAR(_curses_window_getkey__doc__, "getkey([y, x])\n" -"Get a character (string) from terminal keyboard.\n" +"Read a key press and return it as a str.\n" "\n" " y\n" " Y-coordinate.\n" " x\n" " X-coordinate.\n" "\n" -"Returning a string instead of an integer, as getch() does. Function\n" -"keys, keypad keys and other special keys return a multibyte string\n" -"containing the key name. In no-delay mode, an exception is raised\n" -"if there is no input."); +"Read as getch() does, but return an ordinary key as a one-character\n" +"string, the byte decoded as Latin-1, and a special key as its name,\n" +"such as \'KEY_UP\'. Raise curses.error instead of returning -1 if\n" +"there is no input."); #define _CURSES_WINDOW_GETKEY_METHODDEF \ {"getkey", (PyCFunction)_curses_window_getkey, METH_VARARGS, _curses_window_getkey__doc__}, @@ -856,15 +863,19 @@ _curses_window_getkey(PyCursesWindowObject *self, PyObject *args) PyDoc_STRVAR(_curses_window_get_wch__doc__, "get_wch([y, x])\n" -"Get a wide character from terminal keyboard.\n" +"Read a key press and return it as a one-character str.\n" "\n" " y\n" " Y-coordinate.\n" " x\n" " X-coordinate.\n" "\n" -"Return a character for most keys, or an integer for function keys,\n" -"keypad keys, and other special keys."); +"Wait until a key is pressed, or raise curses.error if the read is\n" +"non-blocking or times out.\n" +"\n" +"In keypad mode function keys and other special keys are returned as\n" +"one of the KEY_* constants, an integer. Otherwise their characters\n" +"are returned one at a time."); #define _CURSES_WINDOW_GET_WCH_METHODDEF \ {"get_wch", (PyCFunction)_curses_window_get_wch, METH_VARARGS, _curses_window_get_wch__doc__}, @@ -4201,10 +4212,11 @@ PyDoc_STRVAR(_curses_unctrl__doc__, "unctrl($module, ch, /)\n" "--\n" "\n" -"Return a string which is a printable representation of the character ch.\n" +"Return a bytes object which is a printable representation of ch.\n" "\n" -"Control characters are displayed as a caret followed by the character,\n" -"for example as ^C. Printing characters are left as they are."); +"Control characters are displayed as a caret followed by the\n" +"character, for example as ^C. Printing characters are left as they\n" +"are. Any attributes and color pair are ignored."); #define _CURSES_UNCTRL_METHODDEF \ {"unctrl", (PyCFunction)_curses_unctrl, METH_O, _curses_unctrl__doc__}, @@ -4409,4 +4421,4 @@ _curses_has_extended_color_support(PyObject *module, PyObject *Py_UNUSED(ignored #ifndef _CURSES_USE_DEFAULT_COLORS_METHODDEF #define _CURSES_USE_DEFAULT_COLORS_METHODDEF #endif /* !defined(_CURSES_USE_DEFAULT_COLORS_METHODDEF) */ -/*[clinic end generated code: output=298ec74288ea2852 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=a449af71cde3da29 input=a9049054013a1b77]*/