diff options
Diffstat (limited to '')
-rw-r--r-- | third_party/python/blessed/blessed/terminal.py | 1502 | ||||
-rw-r--r-- | third_party/python/blessed/blessed/terminal.pyi | 106 |
2 files changed, 1608 insertions, 0 deletions
diff --git a/third_party/python/blessed/blessed/terminal.py b/third_party/python/blessed/blessed/terminal.py new file mode 100644 index 0000000000..38bd2bb66b --- /dev/null +++ b/third_party/python/blessed/blessed/terminal.py @@ -0,0 +1,1502 @@ +# -*- coding: utf-8 -*- +# pylint: disable=too-many-lines +"""Module containing :class:`Terminal`, the primary API entry point.""" +# std imports +import os +import re +import sys +import time +import codecs +import locale +import select +import struct +import platform +import warnings +import functools +import contextlib +import collections + +# local +from .color import COLOR_DISTANCE_ALGORITHMS +from .keyboard import (_time_left, + _read_until, + resolve_sequence, + get_keyboard_codes, + get_leading_prefixes, + get_keyboard_sequences) +from .sequences import Termcap, Sequence, SequenceTextWrapper +from .colorspace import RGB_256TABLE +from .formatters import (COLORS, + COMPOUNDABLES, + FormattingString, + NullCallableString, + ParameterizingString, + FormattingOtherString, + split_compound, + resolve_attribute, + resolve_capability) +from ._capabilities import CAPABILITY_DATABASE, CAPABILITIES_ADDITIVES, CAPABILITIES_RAW_MIXIN + +# isort: off + +try: + InterruptedError +except NameError: + # alias py2 exception to py3 + # pylint: disable=redefined-builtin + InterruptedError = select.error + + +HAS_TTY = True +if platform.system() == 'Windows': + IS_WINDOWS = True + import jinxed as curses # pylint: disable=import-error + from jinxed.win32 import get_console_input_encoding # pylint: disable=import-error +else: + IS_WINDOWS = False + import curses + + try: + import fcntl + import termios + import tty + except ImportError: + _TTY_METHODS = ('setraw', 'cbreak', 'kbhit', 'height', 'width') + _MSG_NOSUPPORT = ( + "One or more of the modules: 'termios', 'fcntl', and 'tty' " + "are not found on your platform '{platform}'. " + "The following methods of Terminal are dummy/no-op " + "unless a deriving class overrides them: {tty_methods}." + .format(platform=platform.system(), + tty_methods=', '.join(_TTY_METHODS))) + warnings.warn(_MSG_NOSUPPORT) + HAS_TTY = False + +_CUR_TERM = None # See comments at end of file + + +class Terminal(object): + """ + An abstraction for color, style, positioning, and input in the terminal. + + This keeps the endless calls to ``tigetstr()`` and ``tparm()`` out of your code, acts + intelligently when somebody pipes your output to a non-terminal, and abstracts over the + complexity of unbuffered keyboard input. It uses the terminfo database to remain portable across + terminal types. + """ + # pylint: disable=too-many-instance-attributes,too-many-public-methods + # Too many public methods (28/20) + # Too many instance attributes (12/7) + + #: Sugary names for commonly-used capabilities + _sugar = dict( + save='sc', + restore='rc', + clear_eol='el', + clear_bol='el1', + clear_eos='ed', + enter_fullscreen='smcup', + exit_fullscreen='rmcup', + move='cup', + move_yx='cup', + move_x='hpa', + move_y='vpa', + hide_cursor='civis', + normal_cursor='cnorm', + reset_colors='op', + normal='sgr0', + reverse='rev', + italic='sitm', + no_italic='ritm', + shadow='sshm', + no_shadow='rshm', + standout='smso', + no_standout='rmso', + subscript='ssubm', + no_subscript='rsubm', + superscript='ssupm', + no_superscript='rsupm', + underline='smul', + no_underline='rmul', + cursor_report='u6', + cursor_request='u7', + terminal_answerback='u8', + terminal_enquire='u9', + ) + + def __init__(self, kind=None, stream=None, force_styling=False): + """ + Initialize the terminal. + + :arg str kind: A terminal string as taken by :func:`curses.setupterm`. + Defaults to the value of the ``TERM`` environment variable. + + .. note:: Terminals withing a single process must share a common + ``kind``. See :obj:`_CUR_TERM`. + + :arg file stream: A file-like object representing the Terminal output. + Defaults to the original value of :obj:`sys.__stdout__`, like + :func:`curses.initscr` does. + + If ``stream`` is not a tty, empty Unicode strings are returned for + all capability values, so things like piping your program output to + a pipe or file does not emit terminal sequences. + + :arg bool force_styling: Whether to force the emission of capabilities + even if :obj:`sys.__stdout__` does not seem to be connected to a + terminal. If you want to force styling to not happen, use + ``force_styling=None``. + + This comes in handy if users are trying to pipe your output through + something like ``less -r`` or build systems which support decoding + of terminal sequences. + """ + # pylint: disable=global-statement,too-many-branches + global _CUR_TERM + self.errors = ['parameters: kind=%r, stream=%r, force_styling=%r' % + (kind, stream, force_styling)] + self._normal = None # cache normal attr, preventing recursive lookups + # we assume our input stream to be line-buffered until either the + # cbreak of raw context manager methods are entered with an attached tty. + self._line_buffered = True + + self._stream = stream + self._keyboard_fd = None + self._init_descriptor = None + self._is_a_tty = False + self.__init__streams() + + if IS_WINDOWS and self._init_descriptor is not None: + self._kind = kind or curses.get_term(self._init_descriptor) + else: + self._kind = kind or os.environ.get('TERM', 'dumb') or 'dumb' + + self._does_styling = False + if force_styling is None and self.is_a_tty: + self.errors.append('force_styling is None') + elif force_styling or self.is_a_tty: + self._does_styling = True + + if self.does_styling: + # Initialize curses (call setupterm), so things like tigetstr() work. + try: + curses.setupterm(self._kind, self._init_descriptor) + except curses.error as err: + msg = 'Failed to setupterm(kind={0!r}): {1}'.format(self._kind, err) + warnings.warn(msg) + self.errors.append(msg) + self._kind = None + self._does_styling = False + else: + if _CUR_TERM is None or self._kind == _CUR_TERM: + _CUR_TERM = self._kind + else: + # termcap 'kind' is immutable in a python process! Once + # initialized by setupterm, it is unsupported by the + # 'curses' module to change the terminal type again. If you + # are a downstream developer and you need this + # functionality, consider sub-processing, instead. + warnings.warn( + 'A terminal of kind "%s" has been requested; due to an' + ' internal python curses bug, terminal capabilities' + ' for a terminal of kind "%s" will continue to be' + ' returned for the remainder of this process.' % ( + self._kind, _CUR_TERM,)) + + self.__init__color_capabilities() + self.__init__capabilities() + self.__init__keycodes() + + def __init__streams(self): + # pylint: disable=too-complex,too-many-branches + # Agree to disagree ! + stream_fd = None + + # Default stream is stdout + if self._stream is None: + self._stream = sys.__stdout__ + + if not hasattr(self._stream, 'fileno'): + self.errors.append('stream has no fileno method') + elif not callable(self._stream.fileno): + self.errors.append('stream.fileno is not callable') + else: + try: + stream_fd = self._stream.fileno() + except ValueError as err: + # The stream is not a file, such as the case of StringIO, or, when it has been + # "detached", such as might be the case of stdout in some test scenarios. + self.errors.append('Unable to determine output stream file descriptor: %s' % err) + else: + self._is_a_tty = os.isatty(stream_fd) + if not self._is_a_tty: + self.errors.append('stream not a TTY') + + # Keyboard valid as stdin only when output stream is stdout or stderr and is a tty. + if self._stream in (sys.__stdout__, sys.__stderr__): + try: + self._keyboard_fd = sys.__stdin__.fileno() + except (AttributeError, ValueError) as err: + self.errors.append('Unable to determine input stream file descriptor: %s' % err) + else: + # _keyboard_fd only non-None if both stdin and stdout is a tty. + if not self.is_a_tty: + self.errors.append('Output stream is not a TTY') + self._keyboard_fd = None + elif not os.isatty(self._keyboard_fd): + self.errors.append('Input stream is not a TTY') + self._keyboard_fd = None + else: + self.errors.append('Output stream is not a default stream') + + # The descriptor to direct terminal initialization sequences to. + self._init_descriptor = stream_fd + if stream_fd is None: + try: + self._init_descriptor = sys.__stdout__.fileno() + except ValueError as err: + self.errors.append('Unable to determine __stdout__ file descriptor: %s' % err) + + def __init__color_capabilities(self): + self._color_distance_algorithm = 'cie2000' + if not self.does_styling: + self.number_of_colors = 0 + elif IS_WINDOWS or os.environ.get('COLORTERM') in ('truecolor', '24bit'): + self.number_of_colors = 1 << 24 + else: + self.number_of_colors = max(0, curses.tigetnum('colors') or -1) + + def __clear_color_capabilities(self): + for cached_color_cap in set(dir(self)) & COLORS: + delattr(self, cached_color_cap) + + def __init__capabilities(self): + # important that we lay these in their ordered direction, so that our + # preferred, 'color' over 'set_a_attributes1', for example. + self.caps = collections.OrderedDict() + + # some static injected patterns, esp. without named attribute access. + for name, (attribute, pattern) in CAPABILITIES_ADDITIVES.items(): + self.caps[name] = Termcap(name, pattern, attribute) + + for name, (attribute, kwds) in CAPABILITY_DATABASE.items(): + if self.does_styling: + # attempt dynamic lookup + cap = getattr(self, attribute) + if cap: + self.caps[name] = Termcap.build( + name, cap, attribute, **kwds) + continue + + # fall-back + pattern = CAPABILITIES_RAW_MIXIN.get(name) + if pattern: + self.caps[name] = Termcap(name, pattern, attribute) + + # make a compiled named regular expression table + self.caps_compiled = re.compile( + '|'.join(cap.pattern for name, cap in self.caps.items())) + + # for tokenizer, the '.lastgroup' is the primary lookup key for + # 'self.caps', unless 'MISMATCH'; then it is an unmatched character. + self._caps_compiled_any = re.compile('|'.join( + cap.named_pattern for name, cap in self.caps.items() + ) + '|(?P<MISMATCH>.)') + self._caps_unnamed_any = re.compile('|'.join( + '({0})'.format(cap.pattern) for name, cap in self.caps.items() + ) + '|(.)') + + def __init__keycodes(self): + # Initialize keyboard data determined by capability. + # Build database of int code <=> KEY_NAME. + self._keycodes = get_keyboard_codes() + + # Store attributes as: self.KEY_NAME = code. + for key_code, key_name in self._keycodes.items(): + setattr(self, key_name, key_code) + + # Build database of sequence <=> KEY_NAME. + self._keymap = get_keyboard_sequences(self) + + # build set of prefixes of sequences + self._keymap_prefixes = get_leading_prefixes(self._keymap) + + # keyboard stream buffer + self._keyboard_buf = collections.deque() + + if self._keyboard_fd is not None: + # set input encoding and initialize incremental decoder + + if IS_WINDOWS: + self._encoding = get_console_input_encoding() \ + or locale.getpreferredencoding() or 'UTF-8' + else: + self._encoding = locale.getpreferredencoding() or 'UTF-8' + + try: + self._keyboard_decoder = codecs.getincrementaldecoder(self._encoding)() + except LookupError as err: + # encoding is illegal or unsupported, use 'UTF-8' + warnings.warn('LookupError: {0}, defaulting to UTF-8 for keyboard.'.format(err)) + self._encoding = 'UTF-8' + self._keyboard_decoder = codecs.getincrementaldecoder(self._encoding)() + + def __getattr__(self, attr): + r""" + Return a terminal capability as Unicode string. + + For example, ``term.bold`` is a unicode string that may be prepended + to text to set the video attribute for bold, which should also be + terminated with the pairing :attr:`normal`. This capability + returns a callable, so you can use ``term.bold("hi")`` which + results in the joining of ``(term.bold, "hi", term.normal)``. + + Compound formatters may also be used. For example:: + + >>> term.bold_blink_red_on_green("merry x-mas!") + + For a parameterized capability such as ``move`` (or ``cup``), pass the + parameters as positional arguments:: + + >>> term.move(line, column) + + See the manual page `terminfo(5) + <https://invisible-island.net/ncurses/man/terminfo.5.html>`_ for a + complete list of capabilities and their arguments. + """ + if not self._does_styling: + return NullCallableString() + # Fetch the missing 'attribute' into some kind of curses-resolved + # capability, and cache by attaching to this Terminal class instance. + # + # Note that this will prevent future calls to __getattr__(), but + # that's precisely the idea of the cache! + val = resolve_attribute(self, attr) + setattr(self, attr, val) + return val + + @property + def kind(self): + """ + Read-only property: Terminal kind determined on class initialization. + + :rtype: str + """ + return self._kind + + @property + def does_styling(self): + """ + Read-only property: Whether this class instance may emit sequences. + + :rtype: bool + """ + return self._does_styling + + @property + def is_a_tty(self): + """ + Read-only property: Whether :attr:`~.stream` is a terminal. + + :rtype: bool + """ + return self._is_a_tty + + @property + def height(self): + """ + Read-only property: Height of the terminal (in number of lines). + + :rtype: int + """ + return self._height_and_width().ws_row + + @property + def width(self): + """ + Read-only property: Width of the terminal (in number of columns). + + :rtype: int + """ + return self._height_and_width().ws_col + + @property + def pixel_height(self): + """ + Read-only property: Height ofthe terminal (in pixels). + + :rtype: int + """ + return self._height_and_width().ws_ypixel + + @property + def pixel_width(self): + """ + Read-only property: Width of terminal (in pixels). + + :rtype: int + """ + return self._height_and_width().ws_xpixel + + @staticmethod + def _winsize(fd): + """ + Return named tuple describing size of the terminal by ``fd``. + + If the given platform does not have modules :mod:`termios`, + :mod:`fcntl`, or :mod:`tty`, window size of 80 columns by 25 + rows is always returned. + + :arg int fd: file descriptor queries for its window size. + :raises IOError: the file descriptor ``fd`` is not a terminal. + :rtype: WINSZ + :returns: named tuple describing size of the terminal + + WINSZ is a :class:`collections.namedtuple` instance, whose structure + directly maps to the return value of the :const:`termios.TIOCGWINSZ` + ioctl return value. The return parameters are: + + - ``ws_row``: width of terminal by its number of character cells. + - ``ws_col``: height of terminal by its number of character cells. + - ``ws_xpixel``: width of terminal by pixels (not accurate). + - ``ws_ypixel``: height of terminal by pixels (not accurate). + """ + if HAS_TTY: + # pylint: disable=protected-access + data = fcntl.ioctl(fd, termios.TIOCGWINSZ, WINSZ._BUF) + return WINSZ(*struct.unpack(WINSZ._FMT, data)) + return WINSZ(ws_row=25, ws_col=80, ws_xpixel=0, ws_ypixel=0) + + def _height_and_width(self): + """ + Return a tuple of (terminal height, terminal width). + + If :attr:`stream` or :obj:`sys.__stdout__` is not a tty or does not + support :func:`fcntl.ioctl` of :const:`termios.TIOCGWINSZ`, a window + size of 80 columns by 25 rows is returned for any values not + represented by environment variables ``LINES`` and ``COLUMNS``, which + is the default text mode of IBM PC compatibles. + + :rtype: WINSZ + :returns: Named tuple specifying the terminal size + + WINSZ is a :class:`collections.namedtuple` instance, whose structure + directly maps to the return value of the :const:`termios.TIOCGWINSZ` + ioctl return value. The return parameters are: + + - ``ws_row``: height of terminal by its number of cell rows. + - ``ws_col``: width of terminal by its number of cell columns. + - ``ws_xpixel``: width of terminal by pixels (not accurate). + - ``ws_ypixel``: height of terminal by pixels (not accurate). + + .. note:: the peculiar (height, width, width, height) order, which + matches the return order of TIOCGWINSZ! + """ + for fd in (self._init_descriptor, sys.__stdout__): + try: + if fd is not None: + return self._winsize(fd) + except (IOError, OSError, ValueError, TypeError): # pylint: disable=overlapping-except + pass + + return WINSZ(ws_row=int(os.getenv('LINES', '25')), + ws_col=int(os.getenv('COLUMNS', '80')), + ws_xpixel=None, + ws_ypixel=None) + + @contextlib.contextmanager + def location(self, x=None, y=None): + """ + Context manager for temporarily moving the cursor. + + :arg int x: horizontal position, from left, *0*, to right edge of screen, *self.width - 1*. + :arg int y: vertical position, from top, *0*, to bottom of screen, *self.height - 1*. + :return: a context manager. + :rtype: Iterator + + Move the cursor to a certain position on entry, do any kind of I/O, and upon exit + let you print stuff there, then return the cursor to its original position: + + + .. code-block:: python + + term = Terminal() + with term.location(y=0, x=0): + for row_num in range(term.height-1): + print('Row #{row_num}') + print(term.clear_eol + 'Back to original location.') + + Specify ``x`` to move to a certain column, ``y`` to move to a certain + row, both, or neither. If you specify neither, only the saving and + restoration of cursor position will happen. This can be useful if you + simply want to restore your place after doing some manual cursor + movement. + + Calls cannot be nested: only one should be entered at a time. + + .. note:: The argument order *(x, y)* differs from the return value order *(y, x)* + of :meth:`get_location`, or argument order *(y, x)* of :meth:`move`. This is + for API Compaibility with the blessings library, sorry for the trouble! + """ + # pylint: disable=invalid-name + # Invalid argument name "x" + + # Save position and move to the requested column, row, or both: + self.stream.write(self.save) + if x is not None and y is not None: + self.stream.write(self.move(y, x)) + elif x is not None: + self.stream.write(self.move_x(x)) + elif y is not None: + self.stream.write(self.move_y(y)) + try: + self.stream.flush() + yield + finally: + # Restore original cursor position: + self.stream.write(self.restore) + self.stream.flush() + + def get_location(self, timeout=None): + r""" + Return tuple (row, column) of cursor position. + + :arg float timeout: Return after time elapsed in seconds with value ``(-1, -1)`` indicating + that the remote end did not respond. + :rtype: tuple + :returns: cursor position as tuple in form of ``(y, x)``. When a timeout is specified, + always ensure the return value is checked for ``(-1, -1)``. + + The location of the cursor is determined by emitting the ``u7`` terminal capability, or + VT100 `Query Cursor Position + <https://www2.ccs.neu.edu/research/gpc/VonaUtils/vona/terminal/vtansi.htm#status>`_ + when such capability is undefined, which elicits a response from a reply string described by + capability ``u6``, or again VT100's definition of ``\x1b[%i%d;%dR`` when undefined. + + The ``(y, x)`` return value matches the parameter order of the :meth:`move_xy` capability. + The following sequence should cause the cursor to not move at all:: + + >>> term = Terminal() + >>> term.move_yx(*term.get_location())) + + And the following should assert True with a terminal: + + >>> term = Terminal() + >>> given_y, given_x = 10, 20 + >>> with term.location(y=given_y, x=given_x): + ... result_y, result_x = term.get_location() + ... + >>> assert given_x == result_x, (given_x, result_x) + >>> assert given_y == result_y, (given_y, result_y) + + """ + # Local lines attached by termios and remote login protocols such as + # ssh and telnet both provide a means to determine the window + # dimensions of a connected client, but **no means to determine the + # location of the cursor**. + # + # from https://invisible-island.net/ncurses/terminfo.src.html, + # + # > The System V Release 4 and XPG4 terminfo format defines ten string + # > capabilities for use by applications, <u0>...<u9>. In this file, + # > we use certain of these capabilities to describe functions which + # > are not covered by terminfo. The mapping is as follows: + # > + # > u9 terminal enquire string (equiv. to ANSI/ECMA-48 DA) + # > u8 terminal answerback description + # > u7 cursor position request (equiv. to VT100/ANSI/ECMA-48 DSR 6) + # > u6 cursor position report (equiv. to ANSI/ECMA-48 CPR) + query_str = self.u7 or u'\x1b[6n' + response_str = getattr(self, self.caps['cursor_report'].attribute) or u'\x1b[%i%d;%dR' + + # determine response format as a regular expression + response_re = self.caps['cursor_report'].re_compiled + + # Avoid changing user's desired raw or cbreak mode if already entered, + # by entering cbreak mode ourselves. This is necessary to receive user + # input without awaiting a human to press the return key. This mode + # also disables echo, which we should also hide, as our input is an + # sequence that is not meaningful for display as an output sequence. + + ctx = None + try: + if self._line_buffered: + ctx = self.cbreak() + ctx.__enter__() # pylint: disable=no-member + + # emit the 'query cursor position' sequence, + self.stream.write(query_str) + self.stream.flush() + + # expect a response, + match, data = _read_until(term=self, + pattern=response_re, + timeout=timeout) + + # ensure response sequence is excluded from subsequent input, + if match: + data = (data[:match.start()] + data[match.end():]) + + # re-buffer keyboard data, if any + self.ungetch(data) + + if match: + # return matching sequence response, the cursor location. + row, col = (int(val) for val in match.groups()) + + # Per https://invisible-island.net/ncurses/terminfo.src.html + # The cursor position report (<u6>) string must contain two + # scanf(3)-style %d format elements. The first of these must + # correspond to the Y coordinate and the second to the %d. + # If the string contains the sequence %i, it is taken as an + # instruction to decrement each value after reading it (this is + # the inverse sense from the cup string). + if u'%i' in response_str: + row -= 1 + col -= 1 + return row, col + + finally: + if ctx is not None: + ctx.__exit__(None, None, None) # pylint: disable=no-member + + # We chose to return an illegal value rather than an exception, + # favoring that users author function filters, such as max(0, y), + # rather than crowbarring such logic into an exception handler. + return -1, -1 + + @contextlib.contextmanager + def fullscreen(self): + """ + Context manager that switches to secondary screen, restoring on exit. + + Under the hood, this switches between the primary screen buffer and + the secondary one. The primary one is saved on entry and restored on + exit. Likewise, the secondary contents are also stable and are + faithfully restored on the next entry:: + + with term.fullscreen(): + main() + + .. note:: There is only one primary and one secondary screen buffer. + :meth:`fullscreen` calls cannot be nested, only one should be + entered at a time. + """ + self.stream.write(self.enter_fullscreen) + self.stream.flush() + try: + yield + finally: + self.stream.write(self.exit_fullscreen) + self.stream.flush() + + @contextlib.contextmanager + def hidden_cursor(self): + """ + Context manager that hides the cursor, setting visibility on exit. + + with term.hidden_cursor(): + main() + + .. note:: :meth:`hidden_cursor` calls cannot be nested: only one + should be entered at a time. + """ + self.stream.write(self.hide_cursor) + self.stream.flush() + try: + yield + finally: + self.stream.write(self.normal_cursor) + self.stream.flush() + + def move_xy(self, x, y): + """ + A callable string that moves the cursor to the given ``(x, y)`` screen coordinates. + + :arg int x: horizontal position, from left, *0*, to right edge of screen, *self.width - 1*. + :arg int y: vertical position, from top, *0*, to bottom of screen, *self.height - 1*. + :rtype: ParameterizingString + :returns: Callable string that moves the cursor to the given coordinates + """ + # this is just a convenience alias to the built-in, but hidden 'move' + # attribute -- we encourage folks to use only (x, y) positional + # arguments, or, if they must use (y, x), then use the 'move_yx' + # alias. + return self.move(y, x) + + def move_yx(self, y, x): + """ + A callable string that moves the cursor to the given ``(y, x)`` screen coordinates. + + :arg int y: vertical position, from top, *0*, to bottom of screen, *self.height - 1*. + :arg int x: horizontal position, from left, *0*, to right edge of screen, *self.width - 1*. + :rtype: ParameterizingString + :returns: Callable string that moves the cursor to the given coordinates + """ + return self.move(y, x) + + @property + def move_left(self): + """Move cursor 1 cells to the left, or callable string for n>1 cells.""" + return FormattingOtherString(self.cub1, ParameterizingString(self.cub)) + + @property + def move_right(self): + """Move cursor 1 or more cells to the right, or callable string for n>1 cells.""" + return FormattingOtherString(self.cuf1, ParameterizingString(self.cuf)) + + @property + def move_up(self): + """Move cursor 1 or more cells upwards, or callable string for n>1 cells.""" + return FormattingOtherString(self.cuu1, ParameterizingString(self.cuu)) + + @property + def move_down(self): + """Move cursor 1 or more cells downwards, or callable string for n>1 cells.""" + return FormattingOtherString(self.cud1, ParameterizingString(self.cud)) + + @property + def color(self): + """ + A callable string that sets the foreground color. + + :rtype: ParameterizingString + + The capability is unparameterized until called and passed a number, at which point it + returns another string which represents a specific color change. This second string can + further be called to color a piece of text and set everything back to normal afterward. + + This should not be used directly, but rather a specific color by name or + :meth:`~.Terminal.color_rgb` value. + """ + if not self.does_styling: + return NullCallableString() + return ParameterizingString(self._foreground_color, + self.normal, 'color') + + def color_rgb(self, red, green, blue): + """ + Provides callable formatting string to set foreground color to the specified RGB color. + + :arg int red: RGB value of Red. + :arg int green: RGB value of Green. + :arg int blue: RGB value of Blue. + :rtype: FormattingString + :returns: Callable string that sets the foreground color + + If the terminal does not support RGB color, the nearest supported + color will be determined using :py:attr:`color_distance_algorithm`. + """ + if self.number_of_colors == 1 << 24: + # "truecolor" 24-bit + fmt_attr = u'\x1b[38;2;{0};{1};{2}m'.format(red, green, blue) + return FormattingString(fmt_attr, self.normal) + + # color by approximation to 256 or 16-color terminals + color_idx = self.rgb_downconvert(red, green, blue) + return FormattingString(self._foreground_color(color_idx), self.normal) + + @property + def on_color(self): + """ + A callable capability that sets the background color. + + :rtype: ParameterizingString + """ + if not self.does_styling: + return NullCallableString() + return ParameterizingString(self._background_color, + self.normal, 'on_color') + + def on_color_rgb(self, red, green, blue): + """ + Provides callable formatting string to set background color to the specified RGB color. + + :arg int red: RGB value of Red. + :arg int green: RGB value of Green. + :arg int blue: RGB value of Blue. + :rtype: FormattingString + :returns: Callable string that sets the foreground color + + If the terminal does not support RGB color, the nearest supported + color will be determined using :py:attr:`color_distance_algorithm`. + """ + if self.number_of_colors == 1 << 24: + fmt_attr = u'\x1b[48;2;{0};{1};{2}m'.format(red, green, blue) + return FormattingString(fmt_attr, self.normal) + + color_idx = self.rgb_downconvert(red, green, blue) + return FormattingString(self._background_color(color_idx), self.normal) + + def formatter(self, value): + """ + Provides callable formatting string to set color and other text formatting options. + + :arg str value: Sugary, ordinary, or compound formatted terminal capability, + such as "red_on_white", "normal", "red", or "bold_on_black". + :rtype: :class:`FormattingString` or :class:`NullCallableString` + :returns: Callable string that sets color and other text formatting options + + Calling ``term.formatter('bold_on_red')`` is equivalent to ``term.bold_on_red``, but a + string that is not a valid text formatter will return a :class:`NullCallableString`. + This is intended to allow validation of text formatters without the possibility of + inadvertently returning another terminal capability. + """ + formatters = split_compound(value) + if all((fmt in COLORS or fmt in COMPOUNDABLES) for fmt in formatters): + return getattr(self, value) + + return NullCallableString() + + def rgb_downconvert(self, red, green, blue): + """ + Translate an RGB color to a color code of the terminal's color depth. + + :arg int red: RGB value of Red (0-255). + :arg int green: RGB value of Green (0-255). + :arg int blue: RGB value of Blue (0-255). + :rtype: int + :returns: Color code of downconverted RGB color + """ + # Though pre-computing all 1 << 24 options is memory-intensive, a pre-computed + # "k-d tree" of 256 (x,y,z) vectors of a colorspace in 3 dimensions, such as a + # cone of HSV, or simply 255x255x255 RGB square, any given rgb value is just a + # nearest-neighbor search of 256 points, which k-d should be much faster by + # sub-dividing / culling search points, rather than our "search all 256 points + # always" approach. + fn_distance = COLOR_DISTANCE_ALGORITHMS[self.color_distance_algorithm] + color_idx = 7 + shortest_distance = None + for cmp_depth, cmp_rgb in enumerate(RGB_256TABLE): + cmp_distance = fn_distance(cmp_rgb, (red, green, blue)) + if shortest_distance is None or cmp_distance < shortest_distance: + shortest_distance = cmp_distance + color_idx = cmp_depth + if cmp_depth >= self.number_of_colors: + break + return color_idx + + @property + def normal(self): + """ + A capability that resets all video attributes. + + :rtype: str + + ``normal`` is an alias for ``sgr0`` or ``exit_attribute_mode``. Any + styling attributes previously applied, such as foreground or + background colors, reverse video, or bold are reset to defaults. + """ + if self._normal: + return self._normal + self._normal = resolve_capability(self, 'normal') + return self._normal + + def link(self, url, text, url_id=''): + """ + Display ``text`` that when touched or clicked, navigates to ``url``. + + Optional ``url_id`` may be specified, so that non-adjacent cells can reference a single + target, all cells painted with the same "id" will highlight on hover, rather than any + individual one, as described in "Hovering and underlining the id parameter" of gist + https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda. + + :param str url: Hyperlink URL. + :param str text: Clickable text. + :param str url_id: Optional 'id'. + :rtype: str + :returns: String of ``text`` as a hyperlink to ``url``. + """ + assert len(url) < 2000, (len(url), url) + if url_id: + assert len(str(url_id)) < 250, (len(str(url_id)), url_id) + params = 'id={0}'.format(url_id) + else: + params = '' + if not self.does_styling: + return text + return ('\x1b]8;{0};{1}\x1b\\{2}' + '\x1b]8;;\x1b\\'.format(params, url, text)) + + @property + def stream(self): + """ + Read-only property: stream the terminal outputs to. + + This is a convenience attribute. It is used internally for implied + writes performed by context managers :meth:`~.hidden_cursor`, + :meth:`~.fullscreen`, :meth:`~.location`, and :meth:`~.keypad`. + """ + return self._stream + + @property + def number_of_colors(self): + """ + Number of colors supported by terminal. + + Common return values are 0, 8, 16, 256, or 1 << 24. + + This may be used to test whether the terminal supports colors, + and at what depth, if that's a concern. + + If this property is assigned a value of 88, the value 16 will be saved. This is due to the + the rarity of 88 color support and the inconsistency of behavior between implementations. + + Assigning this property to a value other than 0, 4, 8, 16, 88, 256, or 1 << 24 will + raise an :py:exc:`AssertionError`. + """ + return self._number_of_colors + + @number_of_colors.setter + def number_of_colors(self, value): + assert value in (0, 4, 8, 16, 88, 256, 1 << 24) + # Because 88 colors is rare and we can't guarantee consistent behavior, + # when 88 colors is detected, it is treated as 16 colors + self._number_of_colors = 16 if value == 88 else value + self.__clear_color_capabilities() + + @property + def color_distance_algorithm(self): + """ + Color distance algorithm used by :meth:`rgb_downconvert`. + + The slowest, but most accurate, 'cie2000', is default. Other available options are 'rgb', + 'rgb-weighted', 'cie76', and 'cie94'. + """ + return self._color_distance_algorithm + + @color_distance_algorithm.setter + def color_distance_algorithm(self, value): + assert value in COLOR_DISTANCE_ALGORITHMS + self._color_distance_algorithm = value + self.__clear_color_capabilities() + + @property + def _foreground_color(self): + """ + Convenience capability to support :attr:`~.on_color`. + + Prefers returning sequence for capability ``setaf``, "Set foreground color to #1, using ANSI + escape". If the given terminal does not support such sequence, fallback to returning + attribute ``setf``, "Set foreground color #1". + """ + return self.setaf or self.setf + + @property + def _background_color(self): + """ + Convenience capability to support :attr:`~.on_color`. + + Prefers returning sequence for capability ``setab``, "Set background color to #1, using ANSI + escape". If the given terminal does not support such sequence, fallback to returning + attribute ``setb``, "Set background color #1". + """ + return self.setab or self.setb + + def ljust(self, text, width=None, fillchar=u' '): + """ + Left-align ``text``, which may contain terminal sequences. + + :arg str text: String to be aligned + :arg int width: Total width to fill with aligned text. If + unspecified, the whole width of the terminal is filled. + :arg str fillchar: String for padding the right of ``text`` + :rtype: str + :returns: String of ``text``, left-aligned by ``width``. + """ + # Left justification is different from left alignment, but we continue + # the vocabulary error of the str method for polymorphism. + if width is None: + width = self.width + return Sequence(text, self).ljust(width, fillchar) + + def rjust(self, text, width=None, fillchar=u' '): + """ + Right-align ``text``, which may contain terminal sequences. + + :arg str text: String to be aligned + :arg int width: Total width to fill with aligned text. If + unspecified, the whole width of the terminal is used. + :arg str fillchar: String for padding the left of ``text`` + :rtype: str + :returns: String of ``text``, right-aligned by ``width``. + """ + if width is None: + width = self.width + return Sequence(text, self).rjust(width, fillchar) + + def center(self, text, width=None, fillchar=u' '): + """ + Center ``text``, which may contain terminal sequences. + + :arg str text: String to be centered + :arg int width: Total width in which to center text. If + unspecified, the whole width of the terminal is used. + :arg str fillchar: String for padding the left and right of ``text`` + :rtype: str + :returns: String of ``text``, centered by ``width`` + """ + if width is None: + width = self.width + return Sequence(text, self).center(width, fillchar) + + def truncate(self, text, width=None): + r""" + Truncate ``text`` to maximum ``width`` printable characters, retaining terminal sequences. + + :arg str text: Text to truncate + :arg int width: The maximum width to truncate it to + :rtype: str + :returns: ``text`` truncated to at most ``width`` printable characters + + >>> term.truncate(u'xyz\x1b[0;3m', 2) + u'xy\x1b[0;3m' + """ + if width is None: + width = self.width + return Sequence(text, self).truncate(width) + + def length(self, text): + u""" + Return printable length of a string containing sequences. + + :arg str text: String to measure. May contain terminal sequences. + :rtype: int + :returns: The number of terminal character cells the string will occupy + when printed + + Wide characters that consume 2 character cells are supported: + + >>> term = Terminal() + >>> term.length(term.clear + term.red(u'コンニチハ')) + 10 + + .. note:: Sequences such as 'clear', which is considered as a + "movement sequence" because it would move the cursor to + (y, x)(0, 0), are evaluated as a printable length of + *0*. + """ + return Sequence(text, self).length() + + def strip(self, text, chars=None): + r""" + Return ``text`` without sequences and leading or trailing whitespace. + + :rtype: str + :returns: Text with leading and trailing whitespace removed + + >>> term.strip(u' \x1b[0;3m xyz ') + u'xyz' + """ + return Sequence(text, self).strip(chars) + + def rstrip(self, text, chars=None): + r""" + Return ``text`` without terminal sequences or trailing whitespace. + + :rtype: str + :returns: Text with terminal sequences and trailing whitespace removed + + >>> term.rstrip(u' \x1b[0;3m xyz ') + u' xyz' + """ + return Sequence(text, self).rstrip(chars) + + def lstrip(self, text, chars=None): + r""" + Return ``text`` without terminal sequences or leading whitespace. + + :rtype: str + :returns: Text with terminal sequences and leading whitespace removed + + >>> term.lstrip(u' \x1b[0;3m xyz ') + u'xyz ' + """ + return Sequence(text, self).lstrip(chars) + + def strip_seqs(self, text): + r""" + Return ``text`` stripped of only its terminal sequences. + + :rtype: str + :returns: Text with terminal sequences removed + + >>> term.strip_seqs(u'\x1b[0;3mxyz') + u'xyz' + >>> term.strip_seqs(term.cuf(5) + term.red(u'test')) + u' test' + + .. note:: Non-destructive sequences that adjust horizontal distance + (such as ``\b`` or ``term.cuf(5)``) are replaced by destructive + space or erasing. + """ + return Sequence(text, self).strip_seqs() + + def split_seqs(self, text, maxsplit=0): + r""" + Return ``text`` split by individual character elements and sequences. + + :arg str text: String containing sequences + :arg int maxsplit: When maxsplit is nonzero, at most maxsplit splits + occur, and the remainder of the string is returned as the final element + of the list (same meaning is argument for :func:`re.split`). + :rtype: list[str] + :returns: List of sequences and individual characters + + >>> term.split_seqs(term.underline(u'xyz')) + ['\x1b[4m', 'x', 'y', 'z', '\x1b(B', '\x1b[m'] + + >>> term.split_seqs(term.underline(u'xyz'), 1) + ['\x1b[4m', r'xyz\x1b(B\x1b[m'] + """ + pattern = self._caps_unnamed_any + result = [] + for idx, match in enumerate(re.finditer(pattern, text)): + result.append(match.group()) + if maxsplit and idx == maxsplit: + remaining = text[match.end():] + if remaining: + result[-1] += remaining + break + return result + + def wrap(self, text, width=None, **kwargs): + r""" + Text-wrap a string, returning a list of wrapped lines. + + :arg str text: Unlike :func:`textwrap.wrap`, ``text`` may contain + terminal sequences, such as colors, bold, or underline. By + default, tabs in ``text`` are expanded by + :func:`string.expandtabs`. + :arg int width: Unlike :func:`textwrap.wrap`, ``width`` will + default to the width of the attached terminal. + :arg \**kwargs: See :py:class:`textwrap.TextWrapper` + :rtype: list + :returns: List of wrapped lines + + See :class:`textwrap.TextWrapper` for keyword arguments that can + customize wrapping behaviour. + """ + width = self.width if width is None else width + wrapper = SequenceTextWrapper(width=width, term=self, **kwargs) + lines = [] + for line in text.splitlines(): + lines.extend(iter(wrapper.wrap(line)) if line.strip() else (u'',)) + + return lines + + def getch(self): + """ + Read, decode, and return the next byte from the keyboard stream. + + :rtype: unicode + :returns: a single unicode character, or ``u''`` if a multi-byte + sequence has not yet been fully received. + + This method name and behavior mimics curses ``getch(void)``, and + it supports :meth:`inkey`, reading only one byte from + the keyboard string at a time. This method should always return + without blocking if called after :meth:`kbhit` has returned True. + + Implementors of alternate input stream methods should override + this method. + """ + assert self._keyboard_fd is not None + byte = os.read(self._keyboard_fd, 1) + return self._keyboard_decoder.decode(byte, final=False) + + def ungetch(self, text): + """ + Buffer input data to be discovered by next call to :meth:`~.inkey`. + + :arg str text: String to be buffered as keyboard input. + """ + self._keyboard_buf.extendleft(text) + + def kbhit(self, timeout=None): + """ + Return whether a keypress has been detected on the keyboard. + + This method is used by :meth:`inkey` to determine if a byte may + be read using :meth:`getch` without blocking. The standard + implementation simply uses the :func:`select.select` call on stdin. + + :arg float timeout: When ``timeout`` is 0, this call is + non-blocking, otherwise blocking indefinitely until keypress + is detected when None (default). When ``timeout`` is a + positive number, returns after ``timeout`` seconds have + elapsed (float). + :rtype: bool + :returns: True if a keypress is awaiting to be read on the keyboard + attached to this terminal. When input is not a terminal, False is + always returned. + """ + stime = time.time() + ready_r = [None, ] + check_r = [self._keyboard_fd] if self._keyboard_fd is not None else [] + + while HAS_TTY: + try: + ready_r, _, _ = select.select(check_r, [], [], timeout) + except InterruptedError: + # Beginning with python3.5, IntrruptError is no longer thrown + # https://www.python.org/dev/peps/pep-0475/ + # + # For previous versions of python, we take special care to + # retry select on InterruptedError exception, namely to handle + # a custom SIGWINCH handler. When installed, it would cause + # select() to be interrupted with errno 4 (EAGAIN). + # + # Just as in python3.5, it is ignored, and a new timeout value + # is derived from the previous unless timeout becomes negative. + # because the signal handler has blocked beyond timeout, then + # False is returned. Otherwise, when timeout is None, we + # continue to block indefinitely (default). + if timeout is not None: + # subtract time already elapsed, + timeout -= time.time() - stime + if timeout > 0: + continue + # no time remains after handling exception (rare) + ready_r = [] # pragma: no cover + break # pragma: no cover + else: + break + + return False if self._keyboard_fd is None else check_r == ready_r + + @contextlib.contextmanager + def cbreak(self): + """ + Allow each keystroke to be read immediately after it is pressed. + + This is a context manager for :func:`tty.setcbreak`. + + This context manager activates 'rare' mode, the opposite of 'cooked' + mode: On entry, :func:`tty.setcbreak` mode is activated disabling + line-buffering of keyboard input and turning off automatic echo of + input as output. + + .. note:: You must explicitly print any user input you would like + displayed. If you provide any kind of editing, you must handle + backspace and other line-editing control functions in this mode + as well! + + **Normally**, characters received from the keyboard cannot be read + by Python until the *Return* key is pressed. Also known as *cooked* or + *canonical input* mode, it allows the tty driver to provide + line-editing before shuttling the input to your program and is the + (implicit) default terminal mode set by most unix shells before + executing programs. + + Technically, this context manager sets the :mod:`termios` attributes + of the terminal attached to :obj:`sys.__stdin__`. + + .. note:: :func:`tty.setcbreak` sets ``VMIN = 1`` and ``VTIME = 0``, + see http://www.unixwiz.net/techtips/termios-vmin-vtime.html + """ + if HAS_TTY and self._keyboard_fd is not None: + # Save current terminal mode: + save_mode = termios.tcgetattr(self._keyboard_fd) + save_line_buffered = self._line_buffered + tty.setcbreak(self._keyboard_fd, termios.TCSANOW) + try: + self._line_buffered = False + yield + finally: + # Restore prior mode: + termios.tcsetattr(self._keyboard_fd, + termios.TCSAFLUSH, + save_mode) + self._line_buffered = save_line_buffered + else: + yield + + @contextlib.contextmanager + def raw(self): + r""" + A context manager for :func:`tty.setraw`. + + Although both :meth:`break` and :meth:`raw` modes allow each keystroke + to be read immediately after it is pressed, Raw mode disables + processing of input and output. + + In cbreak mode, special input characters such as ``^C`` or ``^S`` are + interpreted by the terminal driver and excluded from the stdin stream. + In raw mode these values are receive by the :meth:`inkey` method. + + Because output processing is not done, the newline ``'\n'`` is not + enough, you must also print carriage return to ensure that the cursor + is returned to the first column:: + + with term.raw(): + print("printing in raw mode", end="\r\n") + """ + if HAS_TTY and self._keyboard_fd is not None: + # Save current terminal mode: + save_mode = termios.tcgetattr(self._keyboard_fd) + save_line_buffered = self._line_buffered + tty.setraw(self._keyboard_fd, termios.TCSANOW) + try: + self._line_buffered = False + yield + finally: + # Restore prior mode: + termios.tcsetattr(self._keyboard_fd, + termios.TCSAFLUSH, + save_mode) + self._line_buffered = save_line_buffered + else: + yield + + @contextlib.contextmanager + def keypad(self): + r""" + Context manager that enables directional keypad input. + + On entrying, this puts the terminal into "keyboard_transmit" mode by + emitting the keypad_xmit (smkx) capability. On exit, it emits + keypad_local (rmkx). + + On an IBM-PC keyboard with numeric keypad of terminal-type *xterm*, + with numlock off, the lower-left diagonal key transmits sequence + ``\\x1b[F``, translated to :class:`~.Terminal` attribute + ``KEY_END``. + + However, upon entering :meth:`keypad`, ``\\x1b[OF`` is transmitted, + translating to ``KEY_LL`` (lower-left key), allowing you to determine + diagonal direction keys. + """ + try: + self.stream.write(self.smkx) + self.stream.flush() + yield + finally: + self.stream.write(self.rmkx) + self.stream.flush() + + def inkey(self, timeout=None, esc_delay=0.35): + """ + Read and return the next keyboard event within given timeout. + + Generally, this should be used inside the :meth:`raw` context manager. + + :arg float timeout: Number of seconds to wait for a keystroke before + returning. When ``None`` (default), this method may block + indefinitely. + :arg float esc_delay: To distinguish between the keystroke of + ``KEY_ESCAPE``, and sequences beginning with escape, the parameter + ``esc_delay`` specifies the amount of time after receiving escape + (``chr(27)``) to seek for the completion of an application key + before returning a :class:`~.Keystroke` instance for + ``KEY_ESCAPE``. + :rtype: :class:`~.Keystroke`. + :returns: :class:`~.Keystroke`, which may be empty (``u''``) if + ``timeout`` is specified and keystroke is not received. + + .. note:: When used without the context manager :meth:`cbreak`, or + :meth:`raw`, :obj:`sys.__stdin__` remains line-buffered, and this + function will block until the return key is pressed! + + .. note:: On Windows, a 10 ms sleep is added to the key press detection loop to reduce CPU + load. Due to the behavior of :py:func:`time.sleep` on Windows, this will actually + result in a 15.6 ms delay when using the default `time resolution + <https://docs.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod>`_. + Decreasing the time resolution will reduce this to 10 ms, while increasing it, which + is rarely done, will have a perceptable impact on the behavior. + """ + resolve = functools.partial(resolve_sequence, + mapper=self._keymap, + codes=self._keycodes) + + stime = time.time() + + # re-buffer previously received keystrokes, + ucs = u'' + while self._keyboard_buf: + ucs += self._keyboard_buf.pop() + + # receive all immediately available bytes + while self.kbhit(timeout=0): + ucs += self.getch() + + # decode keystroke, if any + ks = resolve(text=ucs) + + # so long as the most immediately received or buffered keystroke is + # incomplete, (which may be a multibyte encoding), block until until + # one is received. + while not ks and self.kbhit(timeout=_time_left(stime, timeout)): + ucs += self.getch() + ks = resolve(text=ucs) + + # handle escape key (KEY_ESCAPE) vs. escape sequence (like those + # that begin with \x1b[ or \x1bO) up to esc_delay when + # received. This is not optimal, but causes least delay when + # "meta sends escape" is used, or when an unsupported sequence is + # sent. + # + # The statement, "ucs in self._keymap_prefixes" has an effect on + # keystrokes such as Alt + Z ("\x1b[z" with metaSendsEscape): because + # no known input sequences begin with such phrasing to allow it to be + # returned more quickly than esc_delay otherwise blocks for. + if ks.code == self.KEY_ESCAPE: + esctime = time.time() + while (ks.code == self.KEY_ESCAPE and + ucs in self._keymap_prefixes and + self.kbhit(timeout=_time_left(esctime, esc_delay))): + ucs += self.getch() + ks = resolve(text=ucs) + + # buffer any remaining text received + self.ungetch(ucs[len(ks):]) + return ks + + +class WINSZ(collections.namedtuple('WINSZ', ( + 'ws_row', 'ws_col', 'ws_xpixel', 'ws_ypixel'))): + """ + Structure represents return value of :const:`termios.TIOCGWINSZ`. + + .. py:attribute:: ws_row + + rows, in characters + + .. py:attribute:: ws_col + + columns, in characters + + .. py:attribute:: ws_xpixel + + horizontal size, pixels + + .. py:attribute:: ws_ypixel + + vertical size, pixels + """ + #: format of termios structure + _FMT = 'hhhh' + #: buffer of termios structure appropriate for ioctl argument + _BUF = '\x00' * struct.calcsize(_FMT) + + +#: _CUR_TERM = None +#: From libcurses/doc/ncurses-intro.html (ESR, Thomas Dickey, et. al):: +#: +#: "After the call to setupterm(), the global variable cur_term is set to +#: point to the current structure of terminal capabilities. By calling +#: setupterm() for each terminal, and saving and restoring cur_term, it +#: is possible for a program to use two or more terminals at once." +#: +#: However, if you study Python's ``./Modules/_cursesmodule.c``, you'll find:: +#: +#: if (!initialised_setupterm && setupterm(termstr,fd,&err) == ERR) { +#: +#: Python - perhaps wrongly - will not allow for re-initialisation of new +#: terminals through :func:`curses.setupterm`, so the value of cur_term cannot +#: be changed once set: subsequent calls to :func:`curses.setupterm` have no +#: effect. +#: +#: Therefore, the :attr:`Terminal.kind` of each :class:`Terminal` is +#: essentially a singleton. This global variable reflects that, and a warning +#: is emitted if somebody expects otherwise. diff --git a/third_party/python/blessed/blessed/terminal.pyi b/third_party/python/blessed/blessed/terminal.pyi new file mode 100644 index 0000000000..3d8eea4db7 --- /dev/null +++ b/third_party/python/blessed/blessed/terminal.pyi @@ -0,0 +1,106 @@ +# std imports +from typing import IO, Any, List, Tuple, Union, Optional, OrderedDict, ContextManager + +# local +from .keyboard import Keystroke +from .sequences import Termcap +from .formatters import (FormattingString, + NullCallableString, + ParameterizingString, + FormattingOtherString) + +HAS_TTY: bool + +class Terminal: + caps: OrderedDict[str, Termcap] + errors: List[str] = ... + def __init__( + self, + kind: Optional[str] = ..., + stream: Optional[IO[str]] = ..., + force_styling: bool = ..., + ) -> None: ... + def __getattr__( + self, attr: str + ) -> Union[NullCallableString, ParameterizingString, FormattingString]: ... + @property + def kind(self) -> str: ... + @property + def does_styling(self) -> bool: ... + @property + def is_a_tty(self) -> bool: ... + @property + def height(self) -> int: ... + @property + def width(self) -> int: ... + @property + def pixel_height(self) -> int: ... + @property + def pixel_width(self) -> int: ... + def location( + self, x: Optional[int] = ..., y: Optional[int] = ... + ) -> ContextManager[None]: ... + def get_location(self, timeout: Optional[float] = ...) -> Tuple[int, int]: ... + def fullscreen(self) -> ContextManager[None]: ... + def hidden_cursor(self) -> ContextManager[None]: ... + def move_xy(self, x: int, y: int) -> ParameterizingString: ... + def move_yx(self, y: int, x: int) -> ParameterizingString: ... + @property + def move_left(self) -> FormattingOtherString: ... + @property + def move_right(self) -> FormattingOtherString: ... + @property + def move_up(self) -> FormattingOtherString: ... + @property + def move_down(self) -> FormattingOtherString: ... + @property + def color(self) -> Union[NullCallableString, ParameterizingString]: ... + def color_rgb(self, red: int, green: int, blue: int) -> FormattingString: ... + @property + def on_color(self) -> Union[NullCallableString, ParameterizingString]: ... + def on_color_rgb(self, red: int, green: int, blue: int) -> FormattingString: ... + def formatter(self, value: str) -> Union[NullCallableString, FormattingString]: ... + def rgb_downconvert(self, red: int, green: int, blue: int) -> int: ... + @property + def normal(self) -> str: ... + def link(self, url: str, text: str, url_id: str = ...) -> str: ... + @property + def stream(self) -> IO[str]: ... + @property + def number_of_colors(self) -> int: ... + @number_of_colors.setter + def number_of_colors(self, value: int) -> None: ... + @property + def color_distance_algorithm(self) -> str: ... + @color_distance_algorithm.setter + def color_distance_algorithm(self, value: str) -> None: ... + def ljust( + self, text: str, width: Optional[int] = ..., fillchar: str = ... + ) -> str: ... + def rjust( + self, text: str, width: Optional[int] = ..., fillchar: str = ... + ) -> str: ... + def center( + self, text: str, width: Optional[int] = ..., fillchar: str = ... + ) -> str: ... + def truncate(self, text: str, width: Optional[int] = ...) -> str: ... + def length(self, text: str) -> int: ... + def strip(self, text: str, chars: Optional[str] = ...) -> str: ... + def rstrip(self, text: str, chars: Optional[str] = ...) -> str: ... + def lstrip(self, text: str, chars: Optional[str] = ...) -> str: ... + def strip_seqs(self, text: str) -> str: ... + def split_seqs(self, text: str, maxsplit: int) -> List[str]: ... + def wrap( + self, text: str, width: Optional[int] = ..., **kwargs: Any + ) -> List[str]: ... + def getch(self) -> str: ... + def ungetch(self, text: str) -> None: ... + def kbhit(self, timeout: Optional[float] = ...) -> bool: ... + def cbreak(self) -> ContextManager[None]: ... + def raw(self) -> ContextManager[None]: ... + def keypad(self) -> ContextManager[None]: ... + def inkey( + self, timeout: Optional[float] = ..., esc_delay: float = ... + ) -> Keystroke: ... + +class WINSZ: ... |