Hello community, here is the log from the commit of package python-pyte for openSUSE:Factory checked in at 2017-11-10 14:56:36 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pyte (Old) and /work/SRC/openSUSE:Factory/.python-pyte.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pyte" Fri Nov 10 14:56:36 2017 rev:4 rq:539575 version:0.7.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pyte/python-pyte.changes 2017-09-05 15:19:32.600776416 +0200 +++ /work/SRC/openSUSE:Factory/.python-pyte.new/python-pyte.changes 2017-11-10 14:56:55.185114772 +0100 @@ -1,0 +2,25 @@ +Fri Nov 3 15:54:05 UTC 2017 - [email protected] + +- update to version 0.7.0: + * Removed deprecated "only" parameter of "Stream.attach". + * Removed deprecated "encoding" parameter of "ByteStream". + * Fixed "how == 3" handling in "DiffScreen.erase_in_display". + * Deprecated "DiffScreen". Its functionality has been backported to + the base "Screen" class. + * Fixed a bug in "DiffScreen.draw" which incorrectly handled the + case when the input of "draw" required several lines. + * Fixed a bug in "Screen" which did not ignore "ESC (" argument in + UTF8 mode. See issue #88 on GitHub. + * Changed "Screen.resize" to do nothing if the requested size + matches the current one. + * Disallowed private mode for + "Screen.report_device_attributes". This was causing an infinite + loop in Emacs and Vim. See issue #81 on GitHub. + * Fixed a bug in `OSC` parsing, which caused "Stream" to hang upon + receiving a palette reset request "ESC ] R". + * Changed "Screen.reset" not to reset `DECOM`. See discussion in + issue #95 on Github. + * Changed the first tabstop to be at the 9-th column. See PR #98 on + GitHub. Thanks to @gordon-quad! + +------------------------------------------------------------------- Old: ---- pyte-0.6.0.tar.gz New: ---- pyte-0.7.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pyte.spec ++++++ --- /var/tmp/diff_new_pack.g8YTpi/_old 2017-11-10 14:56:56.277075276 +0100 +++ /var/tmp/diff_new_pack.g8YTpi/_new 2017-11-10 14:56:56.281075132 +0100 @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %bcond_with test Name: python-pyte -Version: 0.6.0 +Version: 0.7.0 Release: 0 Summary: VTXXX-compatible linux terminal emulator License: LGPL-3.0 ++++++ pyte-0.6.0.tar.gz -> pyte-0.7.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyte-0.6.0/AUTHORS new/pyte-0.7.0/AUTHORS --- old/pyte-0.6.0/AUTHORS 2017-03-26 22:07:50.000000000 +0200 +++ new/pyte-0.7.0/AUTHORS 2017-10-07 23:04:31.000000000 +0200 @@ -14,3 +14,4 @@ - Andreas Stührk - Dmitriy Novozhilov - Sergey Zavgorodniy +- Byron Roosa diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyte-0.6.0/CHANGES new/pyte-0.7.0/CHANGES --- old/pyte-0.6.0/CHANGES 2017-05-28 03:52:57.000000000 +0200 +++ new/pyte-0.7.0/CHANGES 2017-10-07 23:07:08.000000000 +0200 @@ -3,6 +3,33 @@ Here you can see the full list of changes between each pyte release. +Version 0.7.0 +------------- + +Released on October 7th 2017. + +This release is NOT backward compatible with 0.6.X branch! + +- Removed deprecated ``only`` parameter of ``Stream.attach``. +- Removed deprecated ``encoding`` parameter of ``ByteStream``. +- Fixed ``how == 3`` handling in ``DiffScreen.erase_in_display``. +- Deprecated ``DiffScreen``. Its functionality has been backported + to the base ``Screen`` class. +- Fixed a bug in ``DiffScreen.draw`` which incorrectly handled the + case when the input of ``draw`` required several lines. +- Fixed a bug in ``Screen`` which did not ignore ``ESC (`` argument in + UTF8 mode. See issue #88 on GitHub. +- Changed ``Screen.resize`` to do nothing if the requested size matches + the current one. +- Disallowed private mode for ``Screen.report_device_attributes``. This + was causing an infinite loop in Emacs and Vim. See issue #81 on GitHub. +- Fixed a bug in `OSC` parsing, which caused ``Stream`` to hang upon + receiving a palette reset request ``ESC ] R``. +- Changed ``Screen.reset`` not to reset `DECOM`. See discussion in issue + #95 on Github. +- Changed the first tabstop to be at the 9-th column. See PR #98 on + GitHub. Thanks to @gordon-quad! + Version 0.6.0 ------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyte-0.6.0/PKG-INFO new/pyte-0.7.0/PKG-INFO --- old/pyte-0.6.0/PKG-INFO 2017-05-28 03:55:53.000000000 +0200 +++ new/pyte-0.7.0/PKG-INFO 2017-10-07 23:08:35.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pyte -Version: 0.6.0 +Version: 0.7.0 Summary: Simple VTXXX-compatible terminal emulator. Home-page: https://github.com/selectel/pyte Author: Sergei Lebedev @@ -17,7 +17,7 @@ | |_) || |_| || |_| __/ | .__/ \__, | \__|\___| | | __/ | - |_| |___/ 0.6.0 + |_| |___/ 0.7.0 What is ``pyte``? diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyte-0.6.0/README new/pyte-0.7.0/README --- old/pyte-0.6.0/README 2016-06-30 01:40:08.000000000 +0200 +++ new/pyte-0.7.0/README 2017-10-07 23:07:42.000000000 +0200 @@ -9,7 +9,7 @@ | |_) || |_| || |_| __/ | .__/ \__, | \__|\___| | | __/ | - |_| |___/ 0.6.0 + |_| |___/ 0.7.0 What is ``pyte``? diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyte-0.6.0/docs/api.rst new/pyte-0.7.0/docs/api.rst --- old/pyte-0.6.0/docs/api.rst 2016-01-10 13:06:21.000000000 +0100 +++ new/pyte-0.7.0/docs/api.rst 2017-05-29 22:01:56.000000000 +0200 @@ -4,14 +4,49 @@ API reference ============= -.. automodule:: pyte - .. automodule:: pyte.streams - :members: + + pyte.Stream + ^^^^^^^^^^^ + + .. autoclass:: pyte.Stream + + pyte.ByteStream + ^^^^^^^^^^^^^^^ + + .. autoclass:: pyte.ByteStream .. automodule:: pyte.screens - :members: - :exclude-members: Margins, Savepoint, _Char, Char, History, take + + pyte.screens.Screen + ^^^^^^^^^^^^^^^^^^^ + + .. autoclass:: pyte.screens.Cursor + :members: + + .. autoclass:: pyte.screens.Char + :members: + + .. autoclass:: pyte.screens.Screen + :members: + + pyte.screens.DiffScreen + ^^^^^^^^^^^^^^^^^^^^^^^ + + .. autoclass:: pyte.screens.DiffScreen + + pyte.screens.HistoryScreen + ^^^^^^^^^^^^^^^^^^^^^^^^^^ + + .. autoclass:: pyte.screens.History + + .. autoclass:: pyte.screens.HistoryScreen + :members: + + pyte.screens.DebugScreen + ^^^^^^^^^^^^^^^^^^^^^^^^ + + .. autoclass:: pyte.screens.DebugScreen .. automodule:: pyte.modes :members: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyte-0.6.0/docs/conf.py new/pyte-0.7.0/docs/conf.py --- old/pyte-0.6.0/docs/conf.py 2017-03-11 13:57:43.000000000 +0100 +++ new/pyte-0.7.0/docs/conf.py 2017-10-07 23:07:51.000000000 +0200 @@ -52,9 +52,9 @@ # built documents. # # The short X.Y version. -version = '0.6.0' +version = '0.7.0' # The full version, including alpha/beta/rc tags. -release = '0.6.0' +release = '0.7.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyte-0.6.0/docs/index.rst new/pyte-0.7.0/docs/index.rst --- old/pyte-0.6.0/docs/index.rst 2016-03-20 12:54:03.000000000 +0100 +++ new/pyte-0.7.0/docs/index.rst 2017-06-11 21:30:40.000000000 +0200 @@ -14,6 +14,9 @@ multiplexor. * `BastionSSH <https://github.com/wcc526/bastion-ssh>`_ -- a tool for protecting, monitoring and accessing multiple SSH resources. +* `Jumpserver <https://github.com/jumpserver/jumpserver>`_ -- an open source + springboard machine(fortress machine): authentication, authorization, + audit, automated operation and maintenance. .. note:: Using ``pyte``? Add yourself to this list and submit a pull request. @@ -27,7 +30,6 @@ .. toctree:: - :hidden: tutorial api diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyte-0.6.0/examples/webterm.py new/pyte-0.7.0/examples/webterm.py --- old/pyte-0.6.0/examples/webterm.py 2017-05-28 03:27:49.000000000 +0200 +++ new/pyte-0.7.0/examples/webterm.py 2017-10-07 23:02:28.000000000 +0200 @@ -5,9 +5,19 @@ An example showing how to use :mod:`pyte` to implement a basic single-user web terminal. + Client-side ``webterm.js`` supports + * incremental rendering via :data:`~pyte.screens.DiffScreen.dirty`, + * most of the common keyboard events, + * pagination on Meta + P/Meta + A. + .. note:: This example requires at least Python 3.5 and a recent version of ``aiohttp`` library. + .. seealso:: + + `The TTY demystified <http://www.linusakesson.net/programming/tty>`_ + for an introduction to the inner workings of the TTY subsystem. + :copyright: (c) 2017 by pyte authors and contributors, see AUTHORS for details. :license: LGPL, see LICENSE for more details. @@ -30,7 +40,7 @@ class Terminal: def __init__(self, columns, lines, p_in): - self.screen = pyte.DiffScreen(columns, lines) + self.screen = pyte.HistoryScreen(columns, lines) self.screen.set_mode(pyte.modes.LNM) self.screen.write_process_input = \ lambda data: p_in.write(data.encode()) @@ -84,7 +94,14 @@ try: async for msg in ws: if msg.type == aiohttp.WSMsgType.TEXT: - p_out.write(msg.data.encode()) + if msg.data == pyte.control.ESC + "N": + terminal.screen.next_page() + ws.send_str(terminal.dumps()) + elif msg.data == pyte.control.ESC + "P": + terminal.screen.prev_page() + ws.send_str(terminal.dumps()) + else: + p_out.write(msg.data.encode()) elif msg.type == aiohttp.WSMsgType.ERROR: raise ws.exception() except (asyncio.CancelledError, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyte-0.6.0/pyte/graphics.py new/pyte-0.7.0/pyte/graphics.py --- old/pyte-0.6.0/pyte/graphics.py 2017-03-11 13:57:20.000000000 +0100 +++ new/pyte-0.7.0/pyte/graphics.py 2017-06-11 21:34:13.000000000 +0200 @@ -23,7 +23,7 @@ #: >>> text[9] #: '+strikethrough' TEXT = { - 1: "+bold" , + 1: "+bold", 3: "+italics", 4: "+underscore", 7: "+reverse", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyte-0.6.0/pyte/screens.py new/pyte-0.7.0/pyte/screens.py --- old/pyte-0.6.0/pyte/screens.py 2017-05-28 03:52:05.000000000 +0200 +++ new/pyte-0.7.0/pyte/screens.py 2017-10-07 23:02:16.000000000 +0200 @@ -28,13 +28,13 @@ from __future__ import absolute_import, unicode_literals, division -import codecs import copy import json import math import os import sys import unicodedata +import warnings from collections import deque, namedtuple, defaultdict from wcwidth import wcwidth @@ -63,14 +63,12 @@ "g0_charset", "g1_charset", "charset", - "use_utf8", "origin", "wrap" ]) -#: A container for a single character, field names are *hopefully* -#: self-explanatory. -_Char = namedtuple("_Char", [ + +class Char(namedtuple("Char", [ "data", "fg", "bg", @@ -79,18 +77,28 @@ "underscore", "strikethrough", "reverse", -]) +])): + """A single styled on-screen character. - -class Char(_Char): - """A wrapper around :class:`_Char`, providing some useful defaults - for most of the attributes. + :param str data: unicode character. Invariant: ``len(data) == 1``. + :param str fg: foreground colour. Defaults to ``"default"``. + :param str bg: background colour. Defaults to ``"default"``. + :param bool bold: flag for rendering the character using bold font. + Defaults to ``False``. + :param bool italics: flag for rendering the character using italic font. + Defaults to ``False``. + :param bool underline: flag for rendering the character underlined. + Defaults to ``False``. + :param bool strikethrough: flag for rendering the character with a + strike-through line. Defaults to ``False``. + :param bool reverse: flag for swapping foreground and background colours + during rendering. Defaults to ``False``. """ __slots__ = () def __new__(cls, data, fg="default", bg="default", bold=False, - italics=False, underscore=False, reverse=False, - strikethrough=False): + italics=False, underscore=False, + strikethrough=False, reverse=False): return super(Char, cls).__new__(cls, data, fg, bg, bold, italics, underscore, strikethrough, reverse) @@ -143,6 +151,19 @@ A sparse ``lines x columns`` :class:`~pyte.screens.Char` matrix. + .. attribute:: dirty + + A set of line numbers, which should be re-drawn. The user is responsible + for clearing this set when changes have been applied. + + >>> screen = Screen(80, 24) + >>> screen.dirty.clear() + >>> screen.draw("!") + >>> list(screen.dirty) + [0] + + .. versionadded:: 0.7.0 + .. attribute:: cursor Reference to the :class:`~pyte.screens.Cursor` object, holding @@ -150,8 +171,12 @@ .. attribute:: margins - Top and bottom screen margins, defining the scrolling region; - the actual values are top and bottom line. + Margins determine which screen lines move during scrolling + (see :meth:`index` and :meth:`reverse_index`). Characters added + outside the scrolling region do not make the screen to scroll. + + The value is ``None`` if margins are set to screen boundaries, + otherwise -- a pair 0-based top and bottom line indices. .. attribute:: charset @@ -168,7 +193,7 @@ .. warning:: :data:`~pyte.modes.LNM` is reset by default, to match VT220 - specification. Unfortunatelly this makes :mode:`pyte` fail + specification. Unfortunatelly this makes :mod:`pyte` fail ``vttest`` for cursor movement. .. versionchanged:: 0.4.8 @@ -185,8 +210,7 @@ for a description of the presentational component, implemented by ``Screen``. """ - #: A plain empty character with default foreground and background - #: colors. + #: An empty character with default foreground and background colors. default_char = Char(data=" ", fg="default", bg="default") def __init__(self, columns, lines): @@ -194,6 +218,7 @@ self.columns = columns self.lines = lines self.buffer = defaultdict(lambda: StaticDefaultDict(self.default_char)) + self.dirty = set() self.reset() def __repr__(self): @@ -219,12 +244,13 @@ def reset(self): """Reset the terminal to its initial state. - * Scroll margins are reset to screen boundaries. + * Scrolling margins are reset to screen boundaries. * Cursor is moved to home location -- ``(0, 0)`` and its attributes are set to defaults (see :attr:`default_char`). * Screen is cleared -- each character is reset to :attr:`default_char`. * Tabstops are reset to "every eight columns". + * All lines are marked as :attr:`dirty`. .. note:: @@ -232,9 +258,11 @@ and tabstops should be reset as well, thanks to :manpage:`xterm` -- we now know that. """ + self.dirty.update(range(self.lines)) self.buffer.clear() + self.margins = None + self.mode = set([mo.DECAWM, mo.DECTCEM]) - self.margins = Margins(0, self.lines - 1) self.title = "" self.icon_name = "" @@ -242,13 +270,11 @@ self.charset = 0 self.g0_charset = cs.LAT1_MAP self.g1_charset = cs.VT100_MAP - self.use_utf8 = True - self.utf8_decoder = codecs.getincrementaldecoder("utf-8")("replace") # From ``man terminfo`` -- "... hardware tabs are initially # set every `n` spaces when the terminal is powered up. Since # we aim to support VT102 / VT220 and linux -- we use n = 8. - self.tabstops = set(range(7, self.columns, 8)) + self.tabstops = set(range(8, self.columns, 8)) self.cursor = Cursor(0, 0) self.cursor_position() @@ -264,68 +290,69 @@ be added at the right, and if it has more -- columns will be clipped at the right. - .. note:: According to `xterm`, we should also reset origin - mode and screen margins, see ``xterm/screen.c:1761``. - :param int lines: number of lines in the new screen. :param int columns: number of columns in the new screen. + + .. versionchanged:: 0.7.0 + + If the requested screen size is identical to the current screen + size, the method does nothing. """ lines = lines or self.lines columns = columns or self.columns - # First resize the lines: - diff = self.lines - lines + if lines == self.lines and columns == self.columns: + return # No changes. - # if the current display size is greater than requested - # size, take lines off the top. - if diff > 0: + self.dirty.update(range(lines)) + + if lines < self.lines: self.save_cursor() self.cursor_position(0, 0) - self.delete_lines(diff) + self.delete_lines(self.lines - lines) # Drop from the top. self.restore_cursor() - # Then resize the columns: - diff = self.columns - columns - - # if the current display size is greater than requested - # size, trim each line from the right to the new size. - if diff > 0: + if columns < self.columns: for line in self.buffer.values(): - for x in range(self.columns - diff, self.columns): + for x in range(columns, self.columns): line.pop(x, None) self.lines, self.columns = lines, columns self.set_margins() - self.reset_mode(mo.DECOM) def set_margins(self, top=None, bottom=None): """Select top and bottom margins for the scrolling region. - Margins determine which screen lines move during scrolling - (see :meth:`index` and :meth:`reverse_index`). Characters added - outside the scrolling region do not cause the screen to scroll. - :param int top: the smallest line number that is scrolled. :param int bottom: the biggest line number that is scrolled. """ - if top is None or bottom is None: - self.margins = Margins(0, self.lines - 1) + if top is None and bottom is None: + self.margins = None + return + + margins = self.margins or Margins(0, self.lines - 1) + + # Arguments are 1-based, while :attr:`margins` are zero + # based -- so we have to decrement them by one. We also + # make sure that both of them is bounded by [0, lines - 1]. + if top is None: + top = margins.top else: - # Arguments are 1-based, while :attr:`margins` are zero - # based -- so we have to decrement them by one. We also - # make sure that both of them is bounded by [0, lines - 1]. top = max(0, min(top - 1, self.lines - 1)) + if bottom is None: + bottom = margins.bottom + else: bottom = max(0, min(bottom - 1, self.lines - 1)) - # Even though VT102 and VT220 require DECSTBM to ignore - # regions of width less than 2, some programs (like aptitude - # for example) rely on it. Practicality beats purity. - if bottom - top >= 1: - self.margins = Margins(top, bottom) - - # The cursor moves to the home position when the top and - # bottom margins of the scrolling region (DECSTBM) changes. - self.cursor_position() + # Even though VT102 and VT220 require DECSTBM to ignore + # regions of width less than 2, some programs (like aptitude + # for example) rely on it. Practicality beats purity. + if bottom - top >= 1: + self.margins = Margins(top, bottom) + + # The cursor moves to the home position when the top and + # bottom margins of the scrolling region (DECSTBM) changes. + self.cursor_position() def set_mode(self, *modes, **kwargs): """Set (enable) a given list of modes. @@ -337,6 +364,8 @@ # private ones. if kwargs.get("private"): modes = [mode << 5 for mode in modes] + if mo.DECSCNM in modes: + self.dirty.update(range(self.lines)) self.mode.update(modes) @@ -347,8 +376,7 @@ self.erase_in_display(2) self.cursor_position() - # According to `vttest`, DECOM should also home the cursor, see - # vttest/main.c:303. + # According to VT520 manual, DECOM should also home the cursor. if mo.DECOM in modes: self.cursor_position() @@ -374,6 +402,8 @@ # private ones. if kwargs.get("private"): modes = [mode << 5 for mode in modes] + if mo.DECSCNM in modes: + self.dirty.update(range(self.lines)) self.mode.difference_update(modes) @@ -445,6 +475,7 @@ # entered. if self.cursor.x == self.columns: if mo.DECAWM in self.mode: + self.dirty.add(self.cursor.y) self.carriage_return() self.linefeed() elif char_width > 0: @@ -463,7 +494,8 @@ # A two-cell character has a stub slot after it. line[self.cursor.x] = self.cursor.attrs._replace(data=char) if self.cursor.x + 1 < self.columns: - line[self.cursor.x + 1] = self.cursor.attrs._replace(data=" ") + line[self.cursor.x + 1] = self.cursor.attrs \ + ._replace(data="") elif char_width == 0 and unicodedata.combining(char): # A zero-cell character is combined with the previous # character either on this or preceeding line. @@ -477,13 +509,15 @@ self.buffer[self.cursor.y - 1][self.columns - 1] = \ last._replace(data=normalized) else: - pass # Unprintable character or doesn't advance the cursor. + break # Unprintable character or doesn't advance the cursor. # .. note:: We can't use :meth:`cursor_forward()`, because that # way, we'll never know when to linefeed. if char_width > 0: self.cursor.x = min(self.cursor.x + char_width, self.columns) + self.dirty.add(self.cursor.y) + def set_title(self, param): """Set terminal title. @@ -506,11 +540,12 @@ """Move the cursor down one line in the same column. If the cursor is at the last line, create a new line at the bottom. """ - top, bottom = self.margins - + top, bottom = self.margins or Margins(0, self.lines - 1) if self.cursor.y == bottom: - for line in range(top, bottom): - self.buffer[line] = self.buffer[line + 1] + # TODO: mark only the lines within margins? + self.dirty.update(range(self.lines)) + for y in range(top, bottom): + self.buffer[y] = self.buffer[y + 1] self.buffer.pop(bottom, None) else: self.cursor_down() @@ -519,11 +554,12 @@ """Move the cursor up one line in the same column. If the cursor is at the first line, create a new line at the top. """ - top, bottom = self.margins - + top, bottom = self.margins or Margins(0, self.lines - 1) if self.cursor.y == top: - for line in range(bottom, top, -1): - self.buffer[line] = self.buffer[line - 1] + # TODO: mark only the lines within margins? + self.dirty.update(range(self.lines)) + for y in range(bottom, top, -1): + self.buffer[y] = self.buffer[y - 1] self.buffer.pop(top, None) else: self.cursor_up() @@ -562,7 +598,6 @@ self.g0_charset, self.g1_charset, self.charset, - self.use_utf8, mo.DECOM in self.mode, mo.DECAWM in self.mode)) @@ -576,7 +611,6 @@ self.g0_charset = savepoint.g0_charset self.g1_charset = savepoint.g1_charset self.charset = savepoint.charset - self.use_utf8 = savepoint.use_utf8 if savepoint.origin: self.set_mode(mo.DECOM) @@ -600,10 +634,11 @@ :param count: number of lines to insert. """ count = count or 1 - top, bottom = self.margins + top, bottom = self.margins or Margins(0, self.lines - 1) # If cursor is outside scrolling margins it -- do nothin'. if top <= self.cursor.y <= bottom: + self.dirty.update(range(self.cursor.y, self.lines)) for y in range(bottom, self.cursor.y - 1, -1): if y + count <= bottom and y in self.buffer: self.buffer[y + count] = self.buffer[y] @@ -620,10 +655,11 @@ :param int count: number of lines to delete. """ count = count or 1 - top, bottom = self.margins + top, bottom = self.margins or Margins(0, self.lines - 1) # If cursor is outside scrolling margins -- do nothin'. if top <= self.cursor.y <= bottom: + self.dirty.update(range(self.cursor.y, self.lines)) for y in range(self.cursor.y, bottom + 1): if y + count <= bottom: if y + count in self.buffer: @@ -641,6 +677,8 @@ :param int count: number of characters to insert. """ + self.dirty.add(self.cursor.y) + count = count or 1 line = self.buffer[self.cursor.y] for x in range(self.columns, self.cursor.x - 1, -1): @@ -656,7 +694,9 @@ :param int count: number of characters to delete. """ + self.dirty.add(self.cursor.y) count = count or 1 + line = self.buffer[self.cursor.y] for x in range(self.cursor.x, self.columns): if x + count <= self.columns: @@ -671,22 +711,26 @@ :param int count: number of characters to erase. - .. warning:: + .. note:: - Even though *ALL* of the VTXXX manuals state that character - attributes **should be reset to defaults**, ``libvte``, - ``xterm`` and ``ROTE`` completely ignore this. Same applies - too all ``erase_*()`` and ``delete_*()`` methods. + Using cursor attributes for character attributes may seem + illogical, but if recall that a terminal emulator emulates + a type writer, it starts to make sense. The only way a type + writer could erase a character is by typing over it. """ + self.dirty.add(self.cursor.y) count = count or 1 + line = self.buffer[self.cursor.y] for x in range(self.cursor.x, min(self.cursor.x + count, self.columns)): - self.buffer[self.cursor.y][x] = self.cursor.attrs + line[x] = self.cursor.attrs def erase_in_line(self, how=0, private=False): """Erase a line in a specific way. + Character attributes are set to cursor attributes. + :param int how: defines the way the line should be erased in: * ``0`` -- Erases from cursor to end of line, including cursor @@ -694,19 +738,15 @@ * ``1`` -- Erases from beginning of line to cursor, including cursor position. * ``2`` -- Erases complete line. - :param bool private: when ``True`` character attributes are left - unchanged **not implemented**. + :param bool private: when ``True`` only characters marked as + eraseable are affected **not implemented**. """ + self.dirty.add(self.cursor.y) if how == 0: - # a) erase from the cursor to the end of line, including - # the cursor, interval = range(self.cursor.x, self.columns) elif how == 1: - # b) erase from the beginning of the line to the cursor, - # including it, interval = range(self.cursor.x + 1) elif how == 2: - # c) erase the entire line. interval = range(self.columns) line = self.buffer[self.cursor.y] @@ -716,6 +756,8 @@ def erase_in_display(self, how=0, private=False): """Erases display in a specific way. + Character attributes are set to cursor attributes. + :param int how: defines the way the line should be erased in: * ``0`` -- Erases from cursor to end of screen, including @@ -725,27 +767,22 @@ * ``2`` and ``3`` -- Erases complete display. All lines are erased and changed to single-width. Cursor does not move. - :param bool private: when ``True`` character attributes are left - unchanged **not implemented**. + :param bool private: when ``True`` only characters marked as + eraseable are affected **not implemented**. """ if how == 0: - # a) erase from cursor to the end of the display, including - # the cursor, interval = range(self.cursor.y + 1, self.lines) elif how == 1: - # b) erase from the beginning of the display to the cursor, - # including it, interval = range(self.cursor.y) elif how == 2 or how == 3: - # c) erase the whole display. interval = range(self.lines) + self.dirty.update(interval) for y in interval: line = self.buffer[y] for x in line: line[x] = self.cursor.attrs - # In case of 0 or 1 we have to erase the line with the cursor. if how == 0 or how == 1: self.erase_in_line(how) @@ -781,7 +818,7 @@ cursor is bounded by top and and bottom margins, instead of ``[0; lines - 1]``. """ - if use_margins or mo.DECOM in self.mode: + if (use_margins or mo.DECOM in self.mode) and self.margins is not None: top, bottom = self.margins else: top, bottom = 0, self.lines - 1 @@ -794,7 +831,8 @@ :param int count: number of lines to skip. """ - self.cursor.y = max(self.cursor.y - (count or 1), self.margins.top) + top, _bottom = self.margins or Margins(0, self.lines - 1) + self.cursor.y = max(self.cursor.y - (count or 1), top) def cursor_up1(self, count=None): """Move cursor up the indicated # of lines to column 1. Cursor @@ -811,7 +849,8 @@ :param int count: number of lines to skip. """ - self.cursor.y = min(self.cursor.y + (count or 1), self.margins.bottom) + _top, bottom = self.margins or Margins(0, self.lines - 1) + self.cursor.y = min(self.cursor.y + (count or 1), bottom) def cursor_down1(self, count=None): """Move cursor down the indicated # of lines to column 1. @@ -860,7 +899,7 @@ # If origin mode (DECOM) is set, line number are relative to # the top scrolling margin. - if mo.DECOM in self.mode: + if self.margins is not None and mo.DECOM in self.mode: line += self.margins.top # Cursor is not allowed to move out of the scrolling region. @@ -904,6 +943,7 @@ def alignment_display(self): """Fills screen with uppercase E's for screen focus and alignment.""" + self.dirty.update(range(self.lines)) for y in range(self.lines): for x in range(self.columns): self.buffer[y][x] = self.buffer[y][x]._replace(data="E") @@ -957,10 +997,15 @@ """Report terminal identity. .. versionadded:: 0.5.0 + + .. versionchanged:: 0.7.0 + + If ``private`` keyword argument is set, the method does nothing. + This behaviour is consistent with VT220 manual. """ # We only implement "primary" DA which is the only DA request # VT102 understood, see ``VT102ID`` in ``linux/drivers/tty/vt.c``. - if mode == 0: + if mode == 0 and not kwargs.get("private"): self.write_process_input(ctrl.CSI + "?6c") def report_device_status(self, mode): @@ -1000,105 +1045,31 @@ class DiffScreen(Screen): - """A screen subclass, which maintains a set of dirty lines in its + """ + A screen subclass, which maintains a set of dirty lines in its :attr:`dirty` attribute. The end user is responsible for emptying a set, when a diff is applied. - .. attribute:: dirty - - A set of line numbers, which should be re-drawn. + .. deprecated:: 0.7.0 - >>> screen = DiffScreen(80, 24) - >>> screen.dirty.clear() - >>> screen.draw("!") - >>> list(screen.dirty) - [0] + The functionality contained in this class has been merged into + :class:`~pyte.screens.Screen` and will be removed in 0.8.0. + Please update your code accordingly. """ - def __init__(self, *args): - self.dirty = set() - super(DiffScreen, self).__init__(*args) - - def set_mode(self, *modes, **kwargs): - if mo.DECSCNM >> 5 in modes and kwargs.get("private"): - self.dirty.update(range(self.lines)) - super(DiffScreen, self).set_mode(*modes, **kwargs) - - def reset_mode(self, *modes, **kwargs): - if mo.DECSCNM >> 5 in modes and kwargs.get("private"): - self.dirty.update(range(self.lines)) - super(DiffScreen, self).reset_mode(*modes, **kwargs) - - def reset(self): - self.dirty.update(range(self.lines)) - super(DiffScreen, self).reset() - - def resize(self, *args, **kwargs): - self.dirty.update(range(self.lines)) - super(DiffScreen, self).resize(*args, **kwargs) - - def draw(self, *args): - # Call the superclass's method before marking the row as - # dirty, as when wrapping is enabled, draw() might change - # self.cursor.y. - super(DiffScreen, self).draw(*args) - self.dirty.add(self.cursor.y) - - def index(self): - if self.cursor.y == self.margins.bottom: - self.dirty.update(range(self.lines)) + def __init__(self, *args, **kwargs): + warnings.warn( + "The functionality of ``DiffScreen` has been merged into " + "``Screen`` and will be removed in 0.8.0. Please update " + "your code accordingly.", DeprecationWarning) - super(DiffScreen, self).index() - - def reverse_index(self): - if self.cursor.y == self.margins.top: - self.dirty.update(range(self.lines)) - - super(DiffScreen, self).reverse_index() - - def insert_lines(self, *args): - self.dirty.update(range(self.cursor.y, self.lines)) - super(DiffScreen, self).insert_lines(*args) - - def delete_lines(self, *args): - self.dirty.update(range(self.cursor.y, self.lines)) - super(DiffScreen, self).delete_lines(*args) - - def insert_characters(self, *args): - self.dirty.add(self.cursor.y) - super(DiffScreen, self).insert_characters(*args) - - def delete_characters(self, *args): - self.dirty.add(self.cursor.y) - super(DiffScreen, self).delete_characters(*args) - - def erase_characters(self, *args): - self.dirty.add(self.cursor.y) - super(DiffScreen, self).erase_characters(*args) - - def erase_in_line(self, *args): - self.dirty.add(self.cursor.y) - super(DiffScreen, self).erase_in_line(*args) - - def erase_in_display(self, how=0): - if how == 0: - self.dirty.update(range(self.cursor.y + 1, self.lines)) - elif how == 1: - self.dirty.update(range(self.cursor.y)) - elif how == 2: - self.dirty.update(range(self.lines)) - - super(DiffScreen, self).erase_in_display(how) - - def alignment_display(self): - self.dirty.update(range(self.lines)) - super(DiffScreen, self).alignment_display() + super(DiffScreen, self).__init__(*args, **kwargs) History = namedtuple("History", "top bottom ratio size position") -class HistoryScreen(DiffScreen): - """A :class:~`pyte.screens.DiffScreen` subclass, which keeps track +class HistoryScreen(Screen): + """A :class:~`pyte.screens.Screen` subclass, which keeps track of screen history and allows pagination. This is not linux-specific, but still useful; see page 462 of VT520 User's Manual. @@ -1215,7 +1186,7 @@ def index(self): """Overloaded to update top history with the removed lines.""" - top, bottom = self.margins + top, bottom = self.margins or Margins(0, self.lines - 1) if self.cursor.y == bottom: self.history.top.append(self.buffer[top]) @@ -1224,7 +1195,7 @@ def reverse_index(self): """Overloaded to update bottom history with the removed lines.""" - top, bottom = self.margins + top, bottom = self.margins or Margins(0, self.lines - 1) if self.cursor.y == top: self.history.bottom.append(self.buffer[bottom]) @@ -1241,16 +1212,16 @@ mid = min(len(self.history.top), int(math.ceil(self.lines * self.history.ratio))) - self.history.bottom.extendleft([ - self.buffer[line] - for line in range(self.lines - 1, self.lines - mid - 1, -1)]) + self.history.bottom.extendleft( + self.buffer[y] + for y in range(self.lines - 1, self.lines - mid - 1, -1)) self.history = self.history \ ._replace(position=self.history.position - self.lines) - for line in range(self.lines - 1, mid - 1, -1): - self.buffer[line] = self.buffer[line - mid] - for line in range(mid - 1, -1, -1): - self.buffer[line] = self.history.top.pop() + for y in range(self.lines - 1, mid - 1, -1): + self.buffer[y] = self.buffer[y - mid] + for y in range(mid - 1, -1, -1): + self.buffer[y] = self.history.top.pop() self.dirty = set(range(self.lines)) @@ -1260,14 +1231,14 @@ mid = min(len(self.history.bottom), int(math.ceil(self.lines * self.history.ratio))) - self.history.top.extend([self.buffer[line] for line in range(mid)]) + self.history.top.extend(self.buffer[y] for y in range(mid)) self.history = self.history \ ._replace(position=self.history.position + self.lines) - for line in range(self.lines - mid): - self.buffer[line] = self.buffer[line + mid] - for line in range(self.lines - mid, self.lines): - self.buffer[line] = self.history.bottom.popleft() + for y in range(self.lines - mid): + self.buffer[y] = self.buffer[y + mid] + for y in range(self.lines - mid, self.lines): + self.buffer[y] = self.history.bottom.popleft() self.dirty = set(range(self.lines)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyte-0.6.0/pyte/streams.py new/pyte-0.7.0/pyte/streams.py --- old/pyte-0.6.0/pyte/streams.py 2017-05-28 02:23:48.000000000 +0200 +++ new/pyte-0.7.0/pyte/streams.py 2017-06-19 21:56:05.000000000 +0200 @@ -57,7 +57,7 @@ `man console_codes <http://linux.die.net/man/4/console_codes>`_ For details on console codes listed bellow in :attr:`basic`, - :attr:`escape`, :attr:`csi`, :attr:`sharp` and :attr:`percent`. + :attr:`escape`, :attr:`csi`, :attr:`sharp`. """ #: Control sequences, which don't require any arguments. @@ -143,13 +143,10 @@ if screen is not None: self.attach(screen) - def attach(self, screen, only=()): + def attach(self, screen): """Adds a given screen to the listener queue. :param pyte.screens.Screen screen: a screen to attach to. - :param list only: a list of events you want to dispatch to a - given screen (empty by default, which means - -- dispatch all events). """ if self.listener is not None: warnings.warn("As of version 0.6.0 the listener queue is " @@ -157,12 +154,7 @@ "listener {0} will be replaced." .format(self.listener), DeprecationWarning) - if only: - warnings.warn( - "Passing ``only`` to ``Stream.attach`` is deprecated " - "and will be removed in 0.7.0", DeprecationWarning) - screen = _RestrictedListener(screen, only) - elif self.strict: + if self.strict: for event in self.events: if not hasattr(screen, event): raise TypeError("{0} is missing {1}".format(screen, event)) @@ -267,10 +259,14 @@ sharp_dispatch[(yield)]() if char == "%": self.select_other_charset((yield)) - elif char in "()" and not self.use_utf8: + elif char in "()": + code = yield + if self.use_utf8: + continue + # See http://www.cl.cam.ac.uk/~mgk25/unicode.html#term # for the why on the UTF-8 restriction. - listener.define_charset((yield), mode=char) + listener.define_charset(code, mode=char) else: escape_dispatch[char]() continue # Don't go to CSI. @@ -330,6 +326,11 @@ break # CSI is finished. elif char == OSC: code = yield + if code == "R": + continue # Reset palette. Not implemented. + elif code == "P": + continue # Set palette. Not implemented. + param = "" while True: char = yield @@ -366,7 +367,6 @@ # A noop since all input is Unicode-only. - class ByteStream(Stream): """A stream which takes bytes as input. @@ -379,13 +379,6 @@ using UTF-8. Defaults to ``True``. """ def __init__(self, *args, **kwargs): - if kwargs.pop("encodings", None): - warnings.warn( - "As of version 0.6.0 ``pyte.streams.ByteStream`` no longer " - "accepts an encoding as an argument an instead uses the " - "encoding provided via a CSI command. ``encoding`` will " - "be removed in 0.7.0", UserWarning) - super(ByteStream, self).__init__(*args, **kwargs) self.utf8_decoder = codecs.getincrementaldecoder("utf-8")("replace") @@ -404,20 +397,3 @@ self.utf8_decoder.reset() elif code in "G8": self.use_utf8 = True - - -class _RestrictedListener(object): - def __init__(self, listener, only): - if not only: - raise ValueError("``only`` must be non-empty") - - self.listener = listener - self.only = only - - def __getattribute__(self, attr): - if attr not in Stream.events: - return super(_RestrictedListener, self).__getattribute__(attr) - elif attr in self.only: - return getattr(self.listener, attr) - else: - return lambda *args, **kwargs: None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyte-0.6.0/pyte.egg-info/PKG-INFO new/pyte-0.7.0/pyte.egg-info/PKG-INFO --- old/pyte-0.6.0/pyte.egg-info/PKG-INFO 2017-05-28 03:55:51.000000000 +0200 +++ new/pyte-0.7.0/pyte.egg-info/PKG-INFO 2017-10-07 23:08:32.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pyte -Version: 0.6.0 +Version: 0.7.0 Summary: Simple VTXXX-compatible terminal emulator. Home-page: https://github.com/selectel/pyte Author: Sergei Lebedev @@ -17,7 +17,7 @@ | |_) || |_| || |_| __/ | .__/ \__, | \__|\___| | | __/ | - |_| |___/ 0.6.0 + |_| |___/ 0.7.0 What is ``pyte``? diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyte-0.6.0/setup.py new/pyte-0.7.0/setup.py --- old/pyte-0.6.0/setup.py 2017-03-26 22:12:16.000000000 +0200 +++ new/pyte-0.7.0/setup.py 2017-10-07 23:07:28.000000000 +0200 @@ -31,7 +31,7 @@ setup(name="pyte", - version="0.6.0", + version="0.7.0", packages=["pyte"], install_requires=["wcwidth"], setup_requires=["pytest-runner"], diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyte-0.6.0/tests/test_diff.py new/pyte-0.7.0/tests/test_diff.py --- old/pyte-0.6.0/tests/test_diff.py 2017-03-11 14:47:29.000000000 +0100 +++ new/pyte-0.7.0/tests/test_diff.py 2017-06-18 19:29:31.000000000 +0200 @@ -24,7 +24,7 @@ # c) resize(). screen.dirty.clear() - screen.resize() + screen.resize(130, 24) assert screen.dirty == set(range(screen.lines)) # d) alignment_display(). @@ -122,6 +122,10 @@ screen.erase_in_display(2) assert screen.dirty == set(range(0, screen.lines)) + screen.dirty.clear() + screen.erase_in_display(3) + assert screen.dirty == set(range(0, screen.lines)) + def test_draw_wrap(): screen = pyte.DiffScreen(80, 24) @@ -138,4 +142,12 @@ assert screen.cursor.y == 1 # regression test issue #36 where the wrong line was marked as # dirty - assert screen.dirty == set([1]) + assert screen.dirty == set([0, 1]) + + +def test_draw_multiple_chars_wrap(): + screen = pyte.Screen(5, 2) + screen.dirty.clear() + screen.draw("1234567890") + assert screen.cursor.y == 1 + assert screen.dirty == set([0, 1]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyte-0.6.0/tests/test_history.py new/pyte-0.7.0/tests/test_history.py --- old/pyte-0.6.0/tests/test_history.py 2017-05-28 03:53:55.000000000 +0200 +++ new/pyte-0.7.0/tests/test_history.py 2017-05-28 14:14:04.000000000 +0200 @@ -2,17 +2,16 @@ from __future__ import unicode_literals -import operator import os import pyte from pyte import control as ctrl, modes as mo -from pyte.compat import map, str +from pyte.compat import str def chars(history_lines, columns): - return ["".join(history_lines[line][col].data for col in range(columns)) - for line in range(len(history_lines))] + return ["".join(history_lines[y][x].data for x in range(columns)) + for y in range(len(history_lines))] def test_index(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyte-0.6.0/tests/test_screen.py new/pyte-0.7.0/tests/test_screen.py --- old/pyte-0.6.0/tests/test_screen.py 2017-05-28 01:40:55.000000000 +0200 +++ new/pyte-0.7.0/tests/test_screen.py 2017-10-07 23:02:16.000000000 +0200 @@ -27,6 +27,7 @@ return screen + def tolist(screen): return [[screen.buffer[y][x] for x in range(screen.columns)] for y in range(screen.lines)] @@ -185,31 +186,25 @@ screen.set_mode(mo.DECOM) screen.set_margins(0, 1) assert screen.columns == screen.lines == 2 - assert len(tolist(screen)[0]) == 2 assert tolist(screen) == [[screen.default_char, screen.default_char]] * 2 screen.resize(3, 3) assert screen.columns == screen.lines == 3 - assert len(tolist(screen)) == 3 - assert len(tolist(screen)[0]) == 3 assert tolist(screen) == [ [screen.default_char, screen.default_char, screen.default_char] ] * 3 - assert mo.DECOM not in screen.mode - assert screen.margins == (0, 2) + assert mo.DECOM in screen.mode + assert screen.margins is None screen.resize(2, 2) assert screen.columns == screen.lines == 2 - assert len(tolist(screen)) == 2 - assert len(tolist(screen)[0]) == 2 assert tolist(screen) == [[screen.default_char, screen.default_char]] * 2 # Quirks: - # a) if the current display is thinner than the requested size, + # a) if the current display is narrower than the requested size, # new columns should be added to the right. screen = update(pyte.Screen(2, 2), ["bo", "sh"], [None, None]) screen.resize(2, 3) - dsp = screen.display assert screen.display == ["bo ", "sh "] # b) if the current display is wider than the requested size, @@ -232,6 +227,13 @@ assert screen.display == ["sh"] +def test_resize_same(): + screen = pyte.Screen(2, 2) + screen.dirty.clear() + screen.resize(2, 2) + assert not screen.dirty + + def test_set_mode(): # Test mo.DECCOLM mode screen = update(pyte.Screen(3, 3), ["sam", "is ", "foo"]) @@ -635,7 +637,7 @@ screen = pyte.Screen(10, 10) # Making sure initial tabstops are in place ... - assert screen.tabstops == set([7]) + assert screen.tabstops == set([8]) # ... and clearing them. screen.clear_tab_stop(3) @@ -1419,7 +1421,7 @@ def test_set_margins(): screen = pyte.Screen(10, 10) - assert screen.margins == (0, 9) + assert screen.margins is None # a) ok-case screen.set_margins(1, 5) @@ -1430,9 +1432,9 @@ assert screen.margins != (99, 9) assert screen.margins == (0, 4) - # c) no margins provided + # c) no margins provided -- reset to full screen. screen.set_margins() - assert screen.margins == (0, screen.lines - 1) + assert screen.margins is None def test_hide_cursor(): @@ -1466,18 +1468,6 @@ assert acc.pop() == ctrl.CSI + "?6c" -def test_private_report_device_attributes(): - # Some console apps (e.g. ADOM) might add ``?`` to the DA request, - # even though the VT102/VT220 spec does not allow this. - screen = pyte.Screen(10, 10) - stream = pyte.Stream(screen) - - acc = [] - screen.write_process_input = acc.append - stream.feed(ctrl.CSI + "?0c") - assert acc.pop() == ctrl.CSI + "?6c" - - def test_report_device_status(): screen = pyte.Screen(10, 10) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyte-0.6.0/tests/test_stream.py new/pyte-0.7.0/tests/test_stream.py --- old/pyte-0.6.0/tests/test_stream.py 2017-03-22 21:29:23.000000000 +0100 +++ new/pyte-0.7.0/tests/test_stream.py 2017-06-11 21:30:40.000000000 +0200 @@ -3,7 +3,6 @@ from __future__ import unicode_literals import sys -import warnings if sys.version_info[0] == 2: from cStringIO import StringIO @@ -13,7 +12,7 @@ import pytest import pyte -from pyte import control as ctrl, escape as esc +from pyte import charsets as cs, control as ctrl, escape as esc class counter(object): @@ -221,23 +220,12 @@ stream.detach(screen) -def test_attach_only(): - drawn = [] - - class DrawOnly(object): - def linefeed(self): - raise RuntimeError - - def draw(self, data): - drawn.append(data) - - screen = DrawOnly() - stream = pyte.Stream() - with warnings.catch_warnings(): - stream.attach(screen, only=["draw"]) - - stream.feed("foo\nbar") - assert drawn == ["foo", "bar"] +def test_define_charset(): + # Should be a noop. All input is UTF8. + screen = pyte.Screen(3, 3) + stream = pyte.Stream(screen) + stream.feed(ctrl.ESC + "(B") + assert screen.display[0] == " " * 3 @pytest.mark.parametrize("input,expected", [ @@ -266,6 +254,28 @@ assert handler.args == ("Нерусский текст", ) +def test_byte_stream_define_charset_unknown(): + screen = pyte.Screen(3, 3) + stream = pyte.ByteStream(screen) + stream.select_other_charset("@") + default_g0_charset = screen.g0_charset + # ``"Z"`` is not supported by Linux terminal, so expect a noop. + assert "Z" not in cs.MAPS + stream.feed((ctrl.ESC + "(Z").encode()) + assert screen.display[0] == " " * 3 + assert screen.g0_charset == default_g0_charset + + [email protected]("charset,mapping", cs.MAPS.items()) +def test_byte_stream_define_charset(charset, mapping): + screen = pyte.Screen(3, 3) + stream = pyte.ByteStream(screen) + stream.select_other_charset("@") + stream.feed((ctrl.ESC + "(" + charset).encode()) + assert screen.display[0] == " " * 3 + assert screen.g0_charset == mapping + + def test_byte_stream_select_other_charset(): stream = pyte.ByteStream(pyte.Screen(3, 3)) assert stream.use_utf8 # on by default.
