Skip to content
Browse files

Initial import -- starting from version 0.4.0

  • Loading branch information...
0 parents commit bf7df79e521c9465f09aa02609afda5ea1d486c3 @superbobry superbobry committed Jun 21, 2011
Showing with 7,049 additions and 0 deletions.
  1. +5 −0 AUTHORS.rst
  2. +41 −0 CHANGES.rst
  3. +165 −0 LICENSE
  4. +94 −0 README.rst
  5. +766 −0 docs/_static/rtd.css
  6. +176 −0 docs/conf.py
  7. +106 −0 docs/index.rst
  8. +27 −0 examples/helloworld.py
  9. +45 −0 pyte/__init__.py
  10. +101 −0 pyte/charsets.py
  11. +64 −0 pyte/control.py
  12. +149 −0 pyte/escape.py
  13. +72 −0 pyte/graphics.py
  14. +58 −0 pyte/modes.py
  15. +945 −0 pyte/screens.py
  16. +394 −0 pyte/streams.py
  17. +2,095 −0 runtests.py
  18. +67 −0 setup.py
  19. +52 −0 tests/__init__.py
  20. +266 −0 tests/test_history.py
  21. +1,169 −0 tests/test_screen.py
  22. +192 −0 tests/test_stream.py
5 AUTHORS.rst
@@ -0,0 +1,5 @@
+Authors
+=======
+
+- George Shuklin <george.shuklin@gmail.com>
+- Sergei Lebedev <superbobry@gmail.com>
41 CHANGES.rst
@@ -0,0 +1,41 @@
+2011-06-21 version 0.4.0:
+
+ * Improved cursor movement -- ``Screen`` passes all but one tests
+ in `vttest`.
+ * Changed the way ``Stream`` interacts with ``Screen`` -- event
+ handlers are now implicitly looked up in screen's ``__dict__``,
+ not connected manually.
+ * Changed cursor API -- cursor position and attributes are encapsulated
+ in a separate ``Cursor`` class.
+ * Added support for `DECSCNM` -- toggle screen-wide reverse-video
+ mode.
+ * Added a couple of useful ``Screen`` subclasses: ``HistoryScreen``
+ which allows screen pagination and ``DiffScreen`` which tracks
+ the changed lines.
+
+
+2011-05-31 version 0.3.9:
+
+ * Added initial support for G0-1 charsets (mappings taken from ``tty``
+ kernel driver) and SI, SO escape sequences.
+ * Changed ``ByteStream`` to support fallback encodings -- it nows
+ takes a list of ``(encoding, errors)`` pairs and traverses it
+ left to right on ``feed()``.
+ * Switched to ``unicode_literals`` -- one step closer to Python3.
+
+
+2011-05-23 version 0.3.8:
+
+ * Major rewrite of ``Screen`` internals -- highlights: inherits from
+ ``list``; each character is represented by ``namedtuple`` which
+ also holds SGR data.
+ * Numerous bugfixes, especialy in methods, dealing with manipulating
+ character attributes.
+
+
+2011-05-16 version 0.3.7:
+
+ * Added support for ANSI color codes, as listed in
+ ``man console_codes``. Not implemnted yet: setting alternate font,
+ setting and resetting mappings, blinking text.
+ * Added a couple of trivial usage examples in the `examples/` dir.
165 LICENSE
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
94 README.rst
@@ -0,0 +1,94 @@
+::
+ _______ __ __ _______ _______
+ | || | | || || |
+ | _ || |_| ||_ _|| ___|
+ | |_| || | | | | |___
+ | ___||_ _| | | | ___|
+ | | | | | | | |___
+ |___| |___| |___| |_______|
+
+ -- `chicks dig dudes with terminals` (c) @samfoo
+
+
+About
+-----
+
+What is ``pyte``? It's an in memory VTXXX-compatible terminal emulator.
+*XXX* stands for a series video terminals, developed by
+`DEC <http://en.wikipedia.org/wiki/Digital_Equipment_Corporation>`_ between
+1970 and 1995. The first, and probably the most famous one, was VT100
+terminal, which is now a de-facto standard for all virtual terminal
+emulators. ``pyte`` follows the suit.
+
+So, why would one need a terminal emulator library?
+
+* To screen scrape terminal apps, for example ``htop`` or ``aptitude``.
+* To write cross platform terminal emulators; either with a graphical
+ (`xterm <http://invisible-island.net/xterm/>`_,
+ `rxvt <http://www.rxvt.org/>`_) or a web interface, like
+ `AjaxTerm <http://antony.lesuisse.org/software/ajaxterm/>`_.
+* To have fun, hacking on the ancient, poorly documented technologies.
+
+**Note**: ``pyte`` started as a fork of `vt102 <http://github.com/samfoo/vt102`_,
+which is an incomplete implementation of VT100 features.
+
+
+Installation
+------------
+
+If you have `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_
+you can use ``easy_install -U pyte``. Otherwise, you can download the source
+from `GitHub <http://github.com/selectel/pyte>`_ and run ``python setup.py install``.
+
+
+Example
+-------
+
+ >>> import pyte
+ >>> screen = pyte.Screen(80, 24)
+ >>> stream = pyte.Stream()
+ >>> stream.attach(screen)
+ >>> stream.feed(u"\u001b7\u001b[?47h\u001b)0\u001b[H\u001b[2J\u001b[H"
+ u"\u001b[2;1HNetHack, Copyright 1985-2003\r\u001b[3;1"
+ u"H By Stichting Mathematisch Centrum and M. "
+ u"Stephenson.\r\u001b[4;1H See license for de"
+ u"tails.\r\u001b[5;1H\u001b[6;1H\u001b[7;1HShall I pi"
+ u"ck a character's race, role, gender and alignment f"
+ u"or you? [ynq] ")
+ >>> screen.display
+ [' ',
+ 'NetHack, Copyright 1985-2003 ',
+ ' By Stichting Mathematisch Centrum and M. Stephenson. ',
+ ' See license for details. ',
+ ' ',
+ ' ',
+ "Shall I pick a character's race, role, gender and alignment for you? [ynq] ",
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ']
+ >>>
+
+
+Options?
+--------
+
+``pyte`` is not alone in the weird world of terminal emulator libraries,
+here's a few other options worth checking out:
+`Termemulator <http://sourceforge.net/projects/termemulator/>`_,
+`pyqonsole <http://hg.logilab.org/pyqonsole/>`_,
+`webtty <http://code.google.com/p/webtty/>`_
+`AjaxTerm <http://antony.lesuisse.org/software/ajaxterm/>`_
766 docs/_static/rtd.css
@@ -0,0 +1,766 @@
+/*
+ * rtd.css
+ * ~~~~~~~~~~~~~~~
+ *
+ * Sphinx stylesheet -- sphinxdoc theme. Originally created by
+ * Armin Ronacher for Werkzeug.
+ *
+ * Customized for ReadTheDocs by Eric Pierce & Eric Holscher
+ *
+ * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+/* RTD colors
+ * light blue: #e8ecef
+ * medium blue: #8ca1af
+ * dark blue: #465158
+ * dark grey: #444444
+ *
+ * white hover: #d1d9df;
+ * medium blue hover: #697983;
+ * green highlight: #8ecc4c
+ * light blue (project bar): #e8ecef
+ */
+
+@import url("basic.css");
+
+/* PAGE LAYOUT -------------------------------------------------------------- */
+
+body {
+ font: 100%/1.5 "ff-meta-web-pro-1","ff-meta-web-pro-2",Arial,"Helvetica Neue",sans-serif;
+ text-align: center;
+ color: black;
+ background-color: #465158;
+ padding: 0;
+ margin: 0;
+}
+
+div.document {
+ text-align: left;
+ background-color: #e8ecef;
+}
+
+div.bodywrapper {
+ background-color: #ffffff;
+ border-left: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+ margin: 0 0 0 16em;
+}
+
+div.body {
+ margin: 0;
+ padding: 0.5em 1.3em;
+ max-width: 55em;
+ min-width: 20em;
+}
+
+div.related {
+ font-size: 1em;
+ background-color: #465158;
+}
+
+div.documentwrapper {
+ float: left;
+ width: 100%;
+ background-color: #e8ecef;
+}
+
+
+/* HEADINGS --------------------------------------------------------------- */
+
+h1 {
+ margin: 0;
+ padding: 0.7em 0 0.3em 0;
+ font-size: 1.5em;
+ line-height: 1.15;
+ color: #111;
+ clear: both;
+}
+
+h2 {
+ margin: 2em 0 0.2em 0;
+ font-size: 1.35em;
+ padding: 0;
+ color: #465158;
+}
+
+h3 {
+ margin: 1em 0 -0.3em 0;
+ font-size: 1.2em;
+ color: #6c818f;
+}
+
+div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a {
+ color: black;
+}
+
+h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor {
+ display: none;
+ margin: 0 0 0 0.3em;
+ padding: 0 0.2em 0 0.2em;
+ color: #aaa !important;
+}
+
+h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor,
+h5:hover a.anchor, h6:hover a.anchor {
+ display: inline;
+}
+
+h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover,
+h5 a.anchor:hover, h6 a.anchor:hover {
+ color: #777;
+ background-color: #eee;
+}
+
+
+/* LINKS ------------------------------------------------------------------ */
+
+/* Normal links get a pseudo-underline */
+a {
+ color: #444;
+ text-decoration: none;
+ border-bottom: 1px solid #ccc;
+}
+
+/* Links in sidebar, TOC, index trees and tables have no underline */
+.sphinxsidebar a,
+.toctree-wrapper a,
+.indextable a,
+#indices-and-tables a {
+ color: #444;
+ text-decoration: none;
+ border-bottom: none;
+}
+
+/* Most links get an underline-effect when hovered */
+a:hover,
+div.toctree-wrapper a:hover,
+.indextable a:hover,
+#indices-and-tables a:hover {
+ color: #111;
+ text-decoration: none;
+ border-bottom: 1px solid #111;
+}
+
+/* Footer links */
+div.footer a {
+ color: #86989B;
+ text-decoration: none;
+ border: none;
+}
+div.footer a:hover {
+ color: #a6b8bb;
+ text-decoration: underline;
+ border: none;
+}
+
+/* Permalink anchor (subtle grey with a red hover) */
+div.body a.headerlink {
+ color: #ccc;
+ font-size: 1em;
+ margin-left: 6px;
+ padding: 0 4px 0 4px;
+ text-decoration: none;
+ border: none;
+}
+div.body a.headerlink:hover {
+ color: #c60f0f;
+ border: none;
+}
+
+
+/* NAVIGATION BAR --------------------------------------------------------- */
+
+div.related ul {
+ height: 2.5em;
+}
+
+div.related ul li {
+ margin: 0;
+ padding: 0.65em 0;
+ float: left;
+ display: block;
+ color: white; /* For the >> separators */
+ font-size: 0.8em;
+}
+
+div.related ul li.right {
+ float: right;
+ margin-right: 5px;
+ color: transparent; /* Hide the | separators */
+}
+
+/* "Breadcrumb" links in nav bar */
+div.related ul li a {
+ order: none;
+ background-color: inherit;
+ font-weight: bold;
+ margin: 6px 0 6px 4px;
+ line-height: 1.75em;
+ color: #ffffff;
+ padding: 0.4em 0.8em;
+ border: none;
+ border-radius: 3px;
+}
+/* previous / next / modules / index links look more like buttons */
+div.related ul li.right a {
+ margin: 0.375em 0;
+ background-color: #697983;
+ text-shadow: 0 1px rgba(0, 0, 0, 0.5);
+ border-radius: 3px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+}
+/* All navbar links light up as buttons when hovered */
+div.related ul li a:hover {
+ background-color: #8ca1af;
+ color: #ffffff;
+ text-decoration: none;
+ border-radius: 3px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+}
+/* Take extra precautions for tt within links */
+a tt,
+div.related ul li a tt {
+ background: inherit !important;
+ color: inherit !important;
+}
+
+
+/* SIDEBAR ---------------------------------------------------------------- */
+
+div.sphinxsidebarwrapper {
+ padding: 0;
+}
+
+div.sphinxsidebar {
+ margin: 0;
+ margin-left: -100%;
+ float: left;
+ top: 3em;
+ left: 0;
+ padding: 0 1em;
+ width: 14em;
+ font-size: 1em;
+ text-align: left;
+ background-color: #e8ecef;
+}
+
+div.sphinxsidebar img {
+ max-width: 12em;
+}
+
+div.sphinxsidebar h3, div.sphinxsidebar h4 {
+ margin: 1.2em 0 0.3em 0;
+ font-size: 1em;
+ padding: 0;
+ color: #222222;
+ font-family: "ff-meta-web-pro-1", "ff-meta-web-pro-2", "Arial", "Helvetica Neue", sans-serif;
+}
+
+div.sphinxsidebar h3 a {
+ color: #444444;
+}
+
+div.sphinxsidebar ul,
+div.sphinxsidebar p {
+ margin-top: 0;
+ padding-left: 0;
+ line-height: 130%;
+ background-color: #e8ecef;
+}
+
+/* No bullets for nested lists, but a little extra indentation */
+div.sphinxsidebar ul ul {
+ list-style-type: none;
+ margin-left: 1.5em;
+ padding: 0;
+}
+
+/* A little top/bottom padding to prevent adjacent links' borders
+ * from overlapping each other */
+div.sphinxsidebar ul li {
+ padding: 1px 0;
+}
+
+/* A little left-padding to make these align with the ULs */
+div.sphinxsidebar p.topless {
+ padding-left: 0 0 0 1em;
+}
+
+/* Make these into hidden one-liners */
+div.sphinxsidebar ul li,
+div.sphinxsidebar p.topless {
+ white-space: nowrap;
+ overflow: hidden;
+}
+/* ...which become visible when hovered */
+div.sphinxsidebar ul li:hover,
+div.sphinxsidebar p.topless:hover {
+ overflow: visible;
+}
+
+/* Search text box and "Go" button */
+#searchbox {
+ margin-top: 2em;
+ margin-bottom: 1em;
+ background: #ddd;
+ padding: 0.5em;
+ border-radius: 6px;
+ -moz-border-radius: 6px;
+ -webkit-border-radius: 6px;
+}
+#searchbox h3 {
+ margin-top: 0;
+}
+
+/* Make search box and button abut and have a border */
+input,
+div.sphinxsidebar input {
+ border: 1px solid #999;
+ float: left;
+}
+
+/* Search textbox */
+input[type="text"] {
+ margin: 0;
+ padding: 0 3px;
+ height: 20px;
+ width: 144px;
+ border-top-left-radius: 3px;
+ border-bottom-left-radius: 3px;
+ -moz-border-radius-topleft: 3px;
+ -moz-border-radius-bottomleft: 3px;
+ -webkit-border-top-left-radius: 3px;
+ -webkit-border-bottom-left-radius: 3px;
+}
+/* Search button */
+input[type="submit"] {
+ margin: 0 0 0 -1px; /* -1px prevents a double-border with textbox */
+ height: 22px;
+ color: #444;
+ background-color: #e8ecef;
+ padding: 1px 4px;
+ font-weight: bold;
+ border-top-right-radius: 3px;
+ border-bottom-right-radius: 3px;
+ -moz-border-radius-topright: 3px;
+ -moz-border-radius-bottomright: 3px;
+ -webkit-border-top-right-radius: 3px;
+ -webkit-border-bottom-right-radius: 3px;
+}
+input[type="submit"]:hover {
+ color: #ffffff;
+ background-color: #8ecc4c;
+}
+
+div.sphinxsidebar p.searchtip {
+ clear: both;
+ padding: 0.5em 0 0 0;
+ background: #ddd;
+ color: #666;
+ font-size: 0.9em;
+}
+
+/* Sidebar links are unusual */
+div.sphinxsidebar li a,
+div.sphinxsidebar p a {
+ background: #e8ecef; /* In case links overlap main content */
+ border-radius: 3px;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border: 1px solid transparent; /* To prevent things jumping around on hover */
+ padding: 0 5px 0 5px;
+}
+div.sphinxsidebar li a:hover,
+div.sphinxsidebar p a:hover {
+ color: #111;
+ text-decoration: none;
+ border: 1px solid #888;
+}
+
+/* Tweak any link appearing in a heading */
+div.sphinxsidebar h3 a {
+}
+
+
+
+
+/* OTHER STUFF ------------------------------------------------------------ */
+
+cite, code, tt {
+ font-family: 'Consolas', 'Deja Vu Sans Mono',
+ 'Bitstream Vera Sans Mono', monospace;
+ font-size: 0.95em;
+ letter-spacing: 0.01em;
+}
+
+tt {
+ background-color: #f2f2f2;
+ color: #444;
+}
+
+tt.descname, tt.descclassname, tt.xref {
+ border: 0;
+}
+
+hr {
+ border: 1px solid #abc;
+ margin: 2em;
+}
+
+pre, #_fontwidthtest {
+ font-family: 'Consolas', 'Deja Vu Sans Mono',
+ 'Bitstream Vera Sans Mono', monospace;
+ margin: 1em 2em;
+ font-size: 0.95em;
+ letter-spacing: 0.015em;
+ line-height: 120%;
+ padding: 0.5em;
+ border: 1px solid #ccc;
+ background-color: #eee;
+ border-radius: 6px;
+ -moz-border-radius: 6px;
+ -webkit-border-radius: 6px;
+}
+
+pre a {
+ color: inherit;
+ text-decoration: underline;
+}
+
+td.linenos pre {
+ padding: 0.5em 0;
+}
+
+div.quotebar {
+ background-color: #f8f8f8;
+ max-width: 250px;
+ float: right;
+ padding: 2px 7px;
+ border: 1px solid #ccc;
+}
+
+div.topic {
+ background-color: #f8f8f8;
+}
+
+table {
+ border-collapse: collapse;
+ margin: 0 -0.5em 0 -0.5em;
+}
+
+table td, table th {
+ padding: 0.2em 0.5em 0.2em 0.5em;
+}
+
+
+/* ADMONITIONS AND WARNINGS ------------------------------------------------- */
+
+/* Shared by admonitions and warnings */
+div.admonition, div.warning {
+ font-size: 0.9em;
+ margin: 2em;
+ padding: 0;
+ /*
+ border-radius: 6px;
+ -moz-border-radius: 6px;
+ -webkit-border-radius: 6px;
+ */
+}
+div.admonition p, div.warning p {
+ margin: 0.5em 1em 0.5em 1em;
+ padding: 0;
+}
+div.admonition pre, div.warning pre {
+ margin: 0.4em 1em 0.4em 1em;
+}
+div.admonition p.admonition-title,
+div.warning p.admonition-title {
+ margin: 0;
+ padding: 0.1em 0 0.1em 0.5em;
+ color: white;
+ font-weight: bold;
+ font-size: 1.1em;
+ text-shadow: 0 1px rgba(0, 0, 0, 0.5);
+}
+div.admonition ul, div.admonition ol,
+div.warning ul, div.warning ol {
+ margin: 0.1em 0.5em 0.5em 3em;
+ padding: 0;
+}
+
+
+/* Admonitions only */
+div.admonition {
+ border: 1px solid #609060;
+ background-color: #e9ffe9;
+}
+div.admonition p.admonition-title {
+ background-color: #70A070;
+ border-bottom: 1px solid #609060;
+}
+
+
+/* Warnings only */
+div.warning {
+ border: 1px solid #900000;
+ background-color: #ffe9e9;
+}
+div.warning p.admonition-title {
+ background-color: #b04040;
+ border-bottom: 1px solid #900000;
+}
+
+
+
+div.versioninfo {
+ margin: 1em 0 0 0;
+ border: 1px solid #ccc;
+ background-color: #DDEAF0;
+ padding: 8px;
+ line-height: 1.3em;
+ font-size: 0.9em;
+}
+
+.viewcode-back {
+ font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
+ 'Verdana', sans-serif;
+}
+
+div.viewcode-block:target {
+ background-color: #f4debf;
+ border-top: 1px solid #ac9;
+ border-bottom: 1px solid #ac9;
+}
+
+dl {
+ margin: 1em 0 2.5em 0;
+}
+
+/* Highlight target when you click an internal link */
+dt:target {
+ background: #ffe080;
+}
+/* Don't highlight whole divs */
+div.highlight {
+ background: transparent;
+}
+/* But do highlight spans (so search results can be highlighted) */
+span.highlight {
+ background: #ffe080;
+}
+
+div.footer {
+ background-color: #465158;
+ color: #eeeeee;
+ padding: 0 2em 2em 2em;
+ clear: both;
+ font-size: 0.8em;
+ text-align: center;
+}
+
+p {
+ margin: 0.8em 0 0.5em 0;
+}
+
+.section p img {
+ margin: 1em 2em;
+}
+
+
+/* MOBILE LAYOUT -------------------------------------------------------------- */
+
+@media screen and (max-width: 600px) {
+
+ h1, h2, h3, h4, h5 {
+ position: relative;
+ }
+
+ ul {
+ padding-left: 1.75em;
+ }
+
+ div.bodywrapper a.headerlink, #indices-and-tables h1 a {
+ color: #e6e6e6;
+ font-size: 80%;
+ float: right;
+ line-height: 1.8;
+ position: absolute;
+ right: -0.7em;
+ visibility: inherit;
+ }
+
+ div.bodywrapper h1 a.headerlink, #indices-and-tables h1 a {
+ line-height: 1.5;
+ }
+
+ pre {
+ font-size: 0.7em;
+ overflow: auto;
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ }
+
+ div.related ul {
+ height: 2.5em;
+ padding: 0;
+ text-align: left;
+ }
+
+ div.related ul li {
+ clear: both;
+ color: #465158;
+ padding: 0.2em 0;
+ }
+
+ div.related ul li:last-child {
+ border-bottom: 1px dotted #8ca1af;
+ padding-bottom: 0.4em;
+ margin-bottom: 1em;
+ width: 100%;
+ }
+
+ div.related ul li a {
+ color: #465158;
+ padding-right: 0;
+ }
+
+ div.related ul li a:hover {
+ background: inherit;
+ color: inherit;
+ }
+
+ div.related ul li.right {
+ clear: none;
+ padding: 0.65em 0;
+ margin-bottom: 0.5em;
+ }
+
+ div.related ul li.right a {
+ color: #fff;
+ padding-right: 0.8em;
+ }
+
+ div.related ul li.right a:hover {
+ background-color: #8ca1af;
+ }
+
+ div.body {
+ clear: both;
+ min-width: 0;
+ word-wrap: break-word;
+ }
+
+ div.bodywrapper {
+ margin: 0 0 0 0;
+ }
+
+ div.sphinxsidebar {
+ float: none;
+ margin: 0;
+ width: auto;
+ }
+
+ div.sphinxsidebar input[type="text"] {
+ height: 2em;
+ line-height: 2em;
+ width: 70%;
+ }
+
+ div.sphinxsidebar input[type="submit"] {
+ height: 2em;
+ margin-left: 0.5em;
+ width: 20%;
+ }
+
+ div.sphinxsidebar p.searchtip {
+ background: inherit;
+ margin-bottom: 1em;
+ }
+
+ div.sphinxsidebar ul li, div.sphinxsidebar p.topless {
+ white-space: normal;
+ }
+
+ .bodywrapper img {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+ max-width: 100%;
+ }
+
+ div.documentwrapper {
+ float: none;
+ }
+
+ div.admonition, div.warning, pre, blockquote {
+ margin-left: 0em;
+ margin-right: 0em;
+ }
+
+ .body p img {
+ margin: 0;
+ }
+
+ #searchbox {
+ background: transparent;
+ }
+
+ .related:not(:first-child) li {
+ display: none;
+ }
+
+ .related:not(:first-child) li.right {
+ display: block;
+ }
+
+ div.footer {
+ padding: 1em;
+ }
+
+ .rtd_doc_footer .badge {
+ float: none;
+ margin: 1em auto;
+ position: static;
+ }
+
+ .rtd_doc_footer .badge.revsys-inline {
+ margin-right: auto;
+ margin-bottom: 2em;
+ }
+
+ table.indextable {
+ display: block;
+ width: auto;
+ }
+
+ .indextable tr {
+ display: block;
+ }
+
+ .indextable td {
+ display: block;
+ padding: 0;
+ width: auto !important;
+ }
+
+ .indextable td dt {
+ margin: 1em 0;
+ }
+
+ ul.search {
+ margin-left: 0.25em;
+ }
+
+ ul.search li div.context {
+ font-size: 90%;
+ line-height: 1.1;
+ margin-bottom: 1;
+ margin-left: 0;
+ }
+
+}
176 docs/conf.py
@@ -0,0 +1,176 @@
+# -*- coding: utf-8 -*-
+#
+# pyte documentation build configuration file, created by
+# sphinx-quickstart on Fri Apr 8 12:49:51 2011.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+sys.path.insert(0, os.path.abspath('..'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo',
+ 'sphinx.ext.intersphinx', 'sphinx.ext.doctest',
+ 'sphinx.ext.viewcode']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'pyte'
+copyright = u'2011, Selectel'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.4.0'
+# The full version, including alpha/beta/rc tags.
+release = '0.4.0'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+html_style = 'rtd.css'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'pytedoc'
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'http://docs.python.org/': None}
+
+autodoc_member_order = 'bysource'
+todo_include_todos = True
106 docs/index.rst
@@ -0,0 +1,106 @@
+.. pyte documentation master file, created by
+ sphinx-quickstart on Fri Apr 8 12:49:51 2011.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+Welcome to pyte's documentation!
+=================================
+
+What is ``pyte``? It's an in memory VTXXX-compatible terminal emulator.
+*XXX* stands for a series video terminals, developed by
+`DEC <http://en.wikipedia.org/wiki/Digital_Equipment_Corporation>`_ between
+1970 and 1995. The first, and probably the most famous one, was VT100
+terminal, which is now a de-facto standard for all virtual terminal
+emulators. ``pyte`` follows the suit.
+
+So, why would one need a terminal emulator library?
+
+* To screen scrape terminal apps, for example ``htop`` or ``aptitude``.
+* To write cross platform terminal emulators; either with a graphical
+ (`xterm <http://invisible-island.net/xterm/>`_,
+ `rxvt <http://www.rxvt.org/>`_) or a web interface, like
+ `AjaxTerm <http://antony.lesuisse.org/software/ajaxterm/>`_.
+* To have fun, hacking on the ancient, poorly documented technologies.
+
+**Note**: ``pyte`` started as a fork of `vt102 <http://github.com/samfoo/vt102`_,
+which is an incomplete implementation of VT100 features.
+
+
+Usage
+=====
+
+There are two important classes in ``pyte``:
+:class:`~pyte.screens.Screen` and :class:`~pyte.streams.Stream`. The
+``Screen`` is the terminal screen emulator. It maintains an in-memory
+buffer of text and text-attributes to display on screen. The ``Stream``
+is the stream processor. It manages the state of the input and dispatches
+events to anything that's listening about things that are going on.
+Events are things like ``LINEFEED``, ``DRAW "a"``, or ``CURSOR_POSITION 10 10``.
+See the :ref:`API <api>` for more details.
+
+In general, if you just want to know what's being displayed on screen you
+can do something like the following:
+
+ >>> import pyte
+ >>> screen = pyte.Screen(80, 24)
+ >>> stream = pyte.Stream()
+ >>> stream.attach(screen)
+ >>> stream.feed(u"\u001b7\u001b[?47h\u001b)0\u001b[H\u001b[2J\u001b[H"
+ u"\u001b[2;1HNetHack, Copyright 1985-2003\r\u001b[3;1"
+ u"H By Stichting Mathematisch Centrum and M. "
+ u"Stephenson.\r\u001b[4;1H See license for de"
+ u"tails.\r\u001b[5;1H\u001b[6;1H\u001b[7;1HShall I pi"
+ u"ck a character's race, role, gender and alignment f"
+ u"or you? [ynq] ")
+ >>> screen.display
+ [' ',
+ 'NetHack, Copyright 1985-2003 ',
+ ' By Stichting Mathematisch Centrum and M. Stephenson. ',
+ ' See license for details. ',
+ ' ',
+ ' ',
+ "Shall I pick a character's race, role, gender and alignment for you? [ynq] ",
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ']
+ >>>
+
+.. _api:
+
+API
+===
+
+.. automodule:: pyte.streams
+ :members:
+
+.. automodule:: pyte.screens
+ :members:
+
+.. automodule:: pyte.modes
+ :members:
+
+.. automodule:: pyte.control
+ :members:
+
+.. automodule:: pyte.escape
+ :members:
+
+.. automodule:: pyte.graphics
+ :members:
+
+.. automodule:: pyte.charsets
+ :members:
27 examples/helloworld.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+"""
+ helloworld
+ ~~~~~~~~~~
+
+ A minimal working example for :mod:`pyte`.
+
+ :copyright: (c) 2011 by Selectel, see AUTHORS for more details.
+ :license: LGPL, see LICENSE for more details.
+"""
+
+from __future__ import print_function
+
+import sys
+sys.path.append("..")
+
+import pyte
+
+
+if __name__ == "__main__":
+ stream = pyte.Stream()
+ screen = pyte.Screen(80, 24)
+ screen.attach(stream)
+ stream.feed(u"Hello world!")
+
+ for idx, line in enumerate(screen.display, 1):
+ print(u"%2i" % idx, line, u"")
45 pyte/__init__.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+"""
+ pyte
+ ~~~~
+
+ `pyte` implements a mix of VT100, VT220 and VT520 specification,
+ and aims to support most of the `TERM=linux` functionality.
+
+ Two classes: :class:`~pyte.Stream`, which parses the command stream
+ and dispatches events for commands, and :class:`~pyte.Screen` which,
+ when used with a stream maintains a buffer of strings representing
+ the screen of a terminal.
+
+ :copyright: (c) 2011 by Selectel, see AUTHORS for more details.
+ :license: LGPL, see LICENSE for more details.
+"""
+
+__all__ = ("Screen", "DiffScreen", "HistoryScreen",
+ "Stream", "ByteStream", "DebugStream",
+ "ctrl", "esc", "mo", "g", "cs")
+
+from . import (
+ control as ctrl,
+ charsets as cs,
+ escape as esc,
+ graphics as g,
+ modes as mo
+)
+from .screens import Screen, DiffScreen, HistoryScreen
+from .streams import Stream, ByteStream, DebugStream
+
+
+if __debug__:
+ def dis(chars):
+ """A :func:`dis.dis` for terminals.
+
+ >>> dis(u"\u0007")
+ BELL
+ >>> dis(u"\x9b20m")
+ SELECT-GRAPHIC-RENDITION 20
+ """
+ if isinstance(chars, unicode):
+ chars = chars.encode("utf-8")
+
+ return DebugStream().feed(chars)
101 pyte/charsets.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+"""
+ pyte.charsets
+ ~~~~~~~~~~~~~~
+
+ This module defines ``G0`` and ``G1`` charset mappings the same way
+ they are defined for linux terminal, see
+ ``linux/drivers/tty/consolemap.c`` @ http://git.kernel.org
+
+ .. note:: ``VT100_MAP`` and ``IBMPC_MAP`` were taken unchanged
+ from linux kernel source and therefore are licensed
+ under **GPL**.
+
+ :copyright: (c) 2011 by Selectel, see AUTHORS for more details.
+ :license: LGPL, see LICENSE for more details.
+"""
+
+from __future__ import unicode_literals
+
+
+#: Latin1.
+LAT1_MAP = map(unichr, xrange(256))
+
+#: VT100 graphic character set.
+VT100_MAP = "".join(unichr(c) for c in [
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
+ 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
+ 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+ 0x0028, 0x0029, 0x002a, 0x2192, 0x2190, 0x2191, 0x2193, 0x002f,
+ 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+ 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+ 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x00a0,
+ 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1,
+ 0x2591, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba,
+ 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c,
+ 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x007f,
+ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
+ 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
+ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
+ 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
+ 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
+ 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
+ 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
+ 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
+ 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7,
+ 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
+ 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7,
+ 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
+ 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
+ 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
+ 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
+ 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff
+])
+
+#: IBM Codepage 437.
+IBMPC_MAP = "".join(unichr(c) for c in [
+ 0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
+ 0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c,
+ 0x25b6, 0x25c0, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8,
+ 0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+ 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+ 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+ 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+ 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+ 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302,
+ 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7,
+ 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
+ 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9,
+ 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
+ 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba,
+ 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
+ 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
+ 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f,
+ 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b,
+ 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
+ 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4,
+ 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
+ 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248,
+ 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0
+])
+
+
+MAPS = {
+ "B": LAT1_MAP,
+ "0": VT100_MAP,
+ "U": IBMPC_MAP
+}
64 pyte/control.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+"""
+ pyte.control
+ ~~~~~~~~~~~~
+
+ This module defines simple control sequences, recognized by
+ :class:`~pyte.streams.Stream`, the set of codes here is for
+ ``TERM=linux`` which is a superset of VT102.
+
+ :copyright: (c) 2011 by Selectel, see AUTHORS for more details.
+ :license: LGPL, see LICENSE for more details.
+"""
+
+from __future__ import unicode_literals
+
+#: *Space*: Not suprisingly -- ``" "``.
+SP = " "
+
+#: *Null*: Does nothing.
+NUL = "\u0000"
+
+#: *Bell*: Beeps.
+BEL = "\u0007"
+
+#: *Backspace*: Backspace one column, but not past the begining of the
+#: line.
+BS = "\u0008"
+
+#: *Horizontal tab*: Move cursor to the next tab stop, or to the end
+#: of the line if there is no earlier tab stop.
+HT = "\u0009"
+
+#: *Linefeed*: Give a line feed, and, if :data:`pyte.modes.LNM` (new
+#: line mode) is set also a carriage return.
+LF = "\n"
+#: *Vertical tab*: Same as :data:`LF`.
+VT = "\u000b"
+#: *Form feed*: Same as :data:`LF`.
+FF = "\u000c"
+
+#: *Carriage return*: Move cursor to left margin on current line.
+CR = "\r"
+
+#: *Shift out*: Activate G1 character set.
+SO = "\u000e"
+
+#: *Shift in*: Activate G0 character set.
+SI = "\u000f"
+
+#: *Cancel*: Interrupt escape sequence. If received during an escape or
+#: control sequence, cancels the sequence and displays substitution
+#: character.
+CAN = "\u0018"
+#: *Substitute*: Same as :data:`CAN`.
+SUB = "\u001a"
+
+#: *Escape*: Starts an escape sequence.
+ESC = "\u001b"
+
+#: *Delete*: Is ingored.
+DEL = "\u007f"
+
+#: *Control sequence introducer*: An equavalent for ``ESC [``.
+CSI = "\u009b"
149 pyte/escape.py
@@ -0,0 +1,149 @@
+# -*- coding: utf-8 -*-
+"""
+ pyte.escape
+ ~~~~~~~~~~~
+
+ This module defines bot CSI and non-CSI escape sequences, recognized
+ by :class:`~pyte.streams.Stream` and subclasses.
+
+ :copyright: (c) 2011 by Selectel, see AUTHORS for more details.
+ :license: LGPL, see LICENSE for more details.
+"""
+
+from __future__ import unicode_literals
+
+
+#: *Reset*.
+RIS = "c"
+
+#: *Index*: Move cursor down one line in same column. If the cursor is
+#: at the bottom margin, the screen performs a scroll-up.
+IND = "D"
+
+#: *Next line*: Same as :data:`pyte.control.LF`.
+NEL = "E"
+
+#: Tabulation set: Set a horizontal tab stop at cursor position.
+HTS = "H"
+
+#: *Reverse index*: Move cursor up one line in same column. If the
+#: cursor is at the top margin, the screen performs a scroll-down.
+RI = "M"
+
+#: Save cursor: Save cursor position, character attribute (graphic
+#: rendition), character set, and origin mode selection (see
+#: :data:`DECRC`).
+DECSC = "7"
+
+#: *Restore cursor*: Restore previously saved cursor position, character
+#: attribute (graphic rendition), character set, and origin mode
+#: selection. If none were saved, move cursor to home position.
+DECRC = "8"
+
+
+# "Sharp" escape sequences.
+# -------------------------
+
+#: *Alignment display*: Fill screen with uppercase E's for testing
+#: screen focus and alignment.
+DECALN = "8"
+
+
+# ECMA-48 CSI sequences.
+# ---------------------
+
+#: *Insert character*: Insert the indicated # of blank characters.
+ICH = "@"
+
+#: *Cursor up*: Move cursor up the indicated # of lines in same column.
+#: Cursor stops at top margin.
+CUU = "A"
+
+#: *Cursor down*: Move cursor down the indicated # of lines in same
+#: column. Cursor stops at bottom margin.
+CUD = "B"
+
+#: *Cursor forward*: Move cursor right the indicated # of columns.
+#: Cursor stops at right margin.
+CUF = "C"
+
+#: *Cursor back*: Move cursor left the indicated # of columns. Cursor
+#: stops at left margin.
+CUB = "D"
+
+#: *Cursor next line*: Move cursor down the indicated # of lines to
+#: column 1.
+CNL = "E"
+
+#: *Cursor previous line*: Move cursor up the indicated # of lines to
+#: column 1.
+CPL = "F"
+
+#: *Cursor horizontal align*: Move cursor to the indicated column in
+#: current line.
+CHA = "G"
+
+#: *Cursor position*: Move cursor to the indicated line, column (origin
+#: at ``1, 1``).
+CUP = "H"
+
+#: *Erase data* (default: from cursor to end of line).
+ED = "J"
+
+#: *Erase in line* (default: from cursor to end of line).
+EL = "K"
+
+#: *Insert line*: Insert the indicated # of blank lines, starting from
+#: the current line. Lines displayed below cursor move down. Lines moved
+#: past the bottom margin are lost.
+IL = "L"
+
+#: *Delete line*: Delete the indicated # of lines, starting from the
+#: current line. As lines are deleted, lines displayed below cursor
+#: move up. Lines added to bottom of screen have spaces with same
+#: character attributes as last line move up.
+DL = "M"
+
+#: *Delete character*: Delete the indicated # of characters on the
+#: current line. When character is deleted, all characters to the right
+#: of cursor move left.
+DCH = "P"
+
+#: *Erase character*: Erase the indicated # of characters on the
+#: current line.
+ECH = "X"
+
+#: *Horizontal position relative*: Same as :data:`CUF`.
+HPR = "a"
+
+#: *Vertical position adjust*: Move cursor to the indicated line,
+#: current column.
+VPA = "d"
+
+#: *Vertical position relative*: Same as :data:`CUD`.
+VPR = "e"
+
+#: *Horizontal / Vertical position*: Same as :data:`CUP`.
+HVP = "f"
+
+#: *Tabulation clear*: Clears a horizontal tab stop at cursor position.
+TBC = "g"
+
+#: *Set mode*.
+SM = "h"
+
+#: *Reset mode*.
+RM = "l"
+
+#: *Select graphics rendition*: The terminal can display the following
+#: character attributes that change the character display without
+#: changing the character (see :mod:`pyte.graphics`).
+SGR = "m"
+
+#: *Select top and bottom margins*: Selects margins, defining the
+#: scrolling region; parameters are top and bottom line. If called
+#: without any arguments, whole screen is used.
+DECSTBM = "r"
+
+#: *Horizontal position adjust*: Same as :data:`CHA`.
+HPA = "'"
72 pyte/graphics.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+"""
+ pyte.graphics
+ ~~~~~~~~~~~~~
+
+ This module defines graphic-related constants, mostly taken from
+ :manpage:`console_codes(4)` and
+ http://pueblo.sourceforge.net/doc/manual/ansi_color_codes.html.
+
+ :copyright: (c) 2011 by Selectel, see AUTHORS for more details.
+ :license: LGPL, see LICENSE for more details.
+"""
+
+#: A mapping of ANSI text style codes to style names, "+" means the:
+#: attribute is set, "-" -- reset; example:
+#:
+#: >>> text[1]
+#: '+bold'
+#: >>> text[9]
+#: '+strikethrough'
+TEXT = {
+ 1: "+bold" ,
+ 3: "+italics",
+ 4: "+underscore",
+ 7: "+reverse",
+ 9: "+strikethrough",
+ 22: "-bold",
+ 23: "-italics",
+ 24: "-underscore",
+ 27: "-reverse",
+ 29: "-strikethrough"
+}
+
+
+#: A mapping of ANSI foreground color codes to color names, example:
+#:
+#: >>> FG[30]
+#: 'black'
+#: >>> FG[38]
+#: 'default'
+FG = {
+ 30: "black",
+ 31: "red",
+ 32: "green",
+ 33: "brown",
+ 34: "blue",
+ 35: "magenta",
+ 36: "cyan",
+ 37: "white",
+ 39: "default" # white.
+}
+
+#: A mapping of ANSI background color codes to color names, example:
+#:
+#: >>> BG[40]
+#: 'black'
+#: >>> BG[48]
+#: 'default'
+BG = {
+ 40: "black",
+ 41: "red",
+ 42: "green",
+ 43: "brown",
+ 44: "blue",
+ 45: "magenta",
+ 46: "cyan",
+ 47: "white",
+ 49: "default" # black.
+}
+
+# Reverse mapping of all available attributes -- keep this private!
+_SGR = dict((v, k) for k, v in BG.items() + FG.items() + TEXT.items())
58 pyte/modes.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+"""
+ pyte.modes
+ ~~~~~~~~~~
+
+ This module defines terminal mode switches, used by
+ :class:`~pyte.screens.Screen`. There're two types of terminal modes:
+
+ * `non-private` which should be set with ``ESC [ N h``, where ``N``
+ is an integer, representing mode being set; and
+ * `private` which should be set with ``ESC [ ? N h``.
+
+ The latter are shifted 5 times to the right, to be easily
+ distinguishable from the former ones; for example `Origin Mode`
+ -- :data:`DECOM` is ``192`` not ``6``.
+
+ >>> DECOM
+ 192
+
+ :copyright: (c) 2011 by Selectel, see AUTHORS for more details.
+ :license: LGPL, see LICENSE for more details.
+"""
+
+#: *Line Feed/New Line Mode*: When enabled, causes a received
+#: :data:`~pyte.control.LF`, :data:`pyte.control.FF`, or
+#: :data:`~pyte.control.VT` to move the cursor to the first column of
+#: the next line.
+LNM = 20
+
+#: *Insert/Replace Mode*: When enabled, new display characters move
+#: old display characters to the right. Characters moved past the
+#: right margin are lost. Otherwise, new display characters replace
+#: old display characters at the cursor position.
+IRM = 4
+
+
+# Private modes.
+# ..............
+
+#: *Text Cursor Enable Mode*: determines if the text cursor is
+#: visible.
+DECTCEM = 25 << 5
+
+#: *Screen Mode*: toggles screen-wide reverse-video mode.
+DECSCNM = 5 << 5
+
+#: *Origin Mode*: allows cursor addressing relative to a user-defined
+#: origin. This mode resets when the terminal is powered up or reset.
+#: It does not affect the erase in display (ED) function.
+DECOM = 6 << 5
+
+#: *Auto Wrap Mode*: selects where received graphic characters appear
+#: when the cursor is at the right margin.
+DECAWM = 7 << 5
+
+#: *Column Mode*: selects the number of columns per line (80 or 132)
+#: on the screen.
+DECCOLM = 3 << 5
945 pyte/screens.py
@@ -0,0 +1,945 @@
+# -*- coding: utf-8 -*-
+"""
+ pyte.screens
+ ~~~~~~~~~~~~
+
+ This module provides classes for terminal screens, currently
+ there's only one base screen implementation, but who knows what
+ the future will bring :).
+
+ .. warning:: from ``xterm/main.c`` «If you think you know what all
+ of this code is doing, you are probably very mistaken.
+ There be serious and nasty dragons here» -- nothing
+ has changed.
+
+ :copyright: (c) 2011 Selectel, see AUTHORS for more details.
+ :license: LGPL, see LICENSE for more details.
+"""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import copy
+import math
+import operator
+from collections import namedtuple, deque
+from itertools import islice, repeat
+
+from . import modes as mo, graphics as g, charsets as cs
+
+
+def take(n, iterable):
+ """Returns first n items of the iterable as a list."""
+ return list(islice(iterable, n))
+
+
+#: A container for screen's scroll margins.
+Margins = namedtuple("Margins", "top bottom")
+
+#: A container for savepoint, created on :data:`~pyte.escape.DECSC`.
+Savepoint = namedtuple("Savepoint", [
+ "cursor",
+ "g0_charset",
+ "g1_charset",
+ "charset",
+ "origin",
+ "wrap"
+])
+
+#: A container for a single character, field names are *hopefully*
+#: self-explanatory.
+_Char = namedtuple("_Char", [
+ "data",
+ "fg",
+ "bg",
+ "bold",
+ "italics",
+ "underscore",
+ "strikethrough",
+ "reverse",
+])
+
+
+class Char(_Char):
+ """A wrapper around :class:`_Char`, providing some useful defaults
+ for most of the attributes.
+ """
+ def __new__(cls, data, fg="default", bg="default", bold=False,
+ italics=False, underscore=False, reverse=False,
+ strikethrough=False):
+ return _Char.__new__(cls, data, fg, bg, bold, italics, underscore,
+ reverse, strikethrough)
+
+
+class Cursor(object):
+ """Screen cursor.
+
+ :param int x: horizontal cursor position.
+ :param int y: vertical cursor position.
+ :param pyte.screens.Char attrs: cursor attributes (see
+ :meth:`Screen.selectel_graphic_rendition`
+ for details).
+ """
+ __slots__ = "x y attrs".split()
+
+ def __init__(self, x, y, attrs=Char(" ")):
+ self.x, self.y, self.attrs = x, y, attrs
+
+
+
+class Screen(list):
+ """
+ A screen is an in-memory matrix of characters that represents the
+ screen display of the terminal. It can be instantiated on it's own
+ and given explicit commands, or it can be attached to a stream and
+ will respond to events.
+
+ .. attribute:: cursor
+
+ Reference to the :class:`~pyte.screens.Cursor` object, holding
+ cursor position and attributes.
+
+ .. attribute:: margins
+
+ Top and bottom screen margins, defining the scrolling region;
+ the actual values are top and bottom line.
+
+ .. note::
+
+ According to ``ECMA-48`` standard, **lines and columnns are
+ 1-indexed**, so, for instance ``ESC [ 10;10 f`` really means
+ -- move cursor to position (9, 9) in the display matrix.
+
+ .. seealso::
+
+ `Standard ECMA-48, Section 6.1.1 \
+ <http://www.ecma-international.org/publications/standards/Ecma-048.htm>`_
+ For a description of the presentational component, implemented
+ by ``Screen``.
+ """
+ #: A plain empty character with default foreground and background
+ #: colors.
+ default_char = Char(data=" ", fg="default", bg="default")
+
+ #: An inifinite sequence of default characters, used for populating
+ #: new lines and columns.
+ default_line = repeat(default_char)
+
+ def __init__(self, columns, lines):
+ self.savepoints = []
+ self.lines, self.columns = lines, columns
+ self.reset()
+
+ @property
+ def size(self):
+ """Returns screen size -- ``(lines, columns)`` when
+ :data:`~pyte.modes.DECTCEM` mode is set, otherwise returns
+ ``None``.
+ """
+ if mo.DECTCEM in self.mode:
+ return self.lines, self.columns
+
+ @property
+ def display(self):
+ """Returns a :func:`list` of screen lines as unicode strings."""
+ return ["".join(map(operator.attrgetter("data"), line))
+ for line in self]
+
+ def reset(self):
+ """Resets the terminal to its initial state.
+
+ * Scroll 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".
+
+ .. note::
+
+ Neither VT220 nor VT102 manuals mentioned that terminal modes
+ and tabstops should be reset as well, thanks to
+ :manpage:`xterm` -- we now know that.
+ """
+ self[:] = (take(self.columns, self.default_line)
+ for _ in xrange(self.lines))
+ self.mode = set([mo.DECAWM, mo.DECTCEM, mo.LNM, mo.DECTCEM])
+ self.margins = Margins(0, self.lines - 1)
+
+ # According to VT220 manual and ``linux/drivers/tty/vt.c``
+ # the default G0 charset is latin-1, but for reasons unknown
+ # latin-1 breaks ascii-graphics; so G0 defaults to cp437.
+ self.g0_charset = self.charset = cs.IBMPC_MAP
+ self.g1_charset = cs.VT100_MAP
+
+ # 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(xrange(7, self.columns, 8))
+
+ self.cursor = Cursor(0, 0)
+ self.cursor_position()
+
+ def resize(self, lines=None, columns=None):
+ """Resize the screen to the given dimensions.
+
+ If the requested screen size has more lines than the existing
+ screen, lines will be added at the bottom. If the requested
+ size has less lines than the existing screen lines will be
+ clipped at the top of the screen. Similarly, if the existing
+ screen has less columns than the requested screen, columns will
+ 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.
+ """
+ lines = lines or self.lines
+ columns = columns or self.columns
+
+ # First resize the lines:
+ diff = self.lines - lines
+
+ # a) if the current display size is less than the requested
+ # size, add lines to the bottom.
+ if diff < 0:
+ self.extend(take(self.columns, self.default_line)
+ for _ in xrange(diff, 0))
+ # b) if the current display size is greater than requested
+ # size, take lines off the top.
+ elif diff > 0:
+ self[:diff] = ()
+
+ # Then resize the columns:
+ diff = self.columns - columns
+
+ # a) if the current display size is less than the requested
+ # size, expand each line to the new size.
+ if diff < 0:
+ for y in xrange(lines):
+ self[y].extend(take(abs(diff), self.default_line))
+ # b) if the current display size is greater than requested
+ # size, trim each line from the right to the new size.
+ elif diff > 0:
+ self[:] = (line[:columns] for line in self)
+
+ self.lines, self.columns = lines, columns
+ self.margins = Margins(0, self.lines - 1)
+ self.reset_mode(mo.DECOM)
+
+ def set_margins(self, top=None, bottom=None):
+ """Selects 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:
+ return
+
+ # 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))
+ 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()
+
+ def set_charset(self, code, mode):
+ """Set active ``G0`` or ``G1`` charset.
+
+ :param unicode code: character set code, should be a character
+ from ``"B0UK"`` -- otherwise ignored.
+ :param unicode mode: if ``"("`` ``G0`` charset is set, if
+ ``")"`` -- we operate on ``G1``.
+
+ .. warning:: user-defined charsets are currently not supported.
+ """
+ if code in cs.MAPS:
+ setattr(self, {"(": "g0_charset", ")": "g1_charset"}[mode],
+ cs.MAPS[code])
+
+ def set_mode(self, *modes, **kwargs):
+ """Sets (enables) a given list of modes.
+
+ :param list modes: modes to set, where each mode is a constant
+ from :mod:`pyte.modes`.
+ """
+ # Private mode codes are shifted, to be distingiushed from non
+ # private ones.
+ if kwargs.get("private"):
+ modes = [mode << 5 for mode in modes]
+
+ self.mode.update(modes)
+
+ # When DECOLM mode is set, the screen is erased and the cursor
+ # moves to the home position.
+ if mo.DECCOLM in modes:
+ self.resize(columns=132)
+ self.erase_in_display(2)
+ self.cursor_position()
+
+ # According to `vttest`, DECOM should also home the cursor, see
+ # vttest/main.c:303.
+ if mo.DECOM in modes:
+ self.cursor_position()
+
+ # Mark all displayed characters as reverse.
+ if mo.DECSCNM in modes:
+ self[:] = ([char._replace(reverse=True) for char in line]
+ for line in self)
+ self.select_graphic_rendition(g._SGR["+reverse"])
+
+ def reset_mode(self, *modes, **kwargs):
+ """Resets (disables) a given list of modes.
+
+ :param list modes: modes to reset -- hopefully, each mode is a
+ constant from :mod:`pyte.modes`.
+ """
+ # Private mode codes are shifted, to be distingiushed from non
+ # private ones.
+ if kwargs.get("private"):
+ modes = [mode << 5 for mode in modes]
+
+ self.mode.difference_update(modes)
+
+ # Lines below follow the logic in :meth:`set_mode`.
+ if mo.DECCOLM in modes:
+ self.resize(columns=80)
+ self.erase_in_display(2)
+ self.cursor_position()
+
+ if mo.DECOM in modes:
+ self.cursor_position()
+
+ if mo.DECSCNM in modes:
+ self[:] = ([char._replace(reverse=False) for char in line]
+ for line in self)
+ self.select_graphic_rendition(g._SGR["-reverse"])
+
+ def shift_in(self):
+ """Activates ``G0`` character set."""
+ self.charset = self.g0_charset
+
+ def shift_out(self):
+ """Activates ``G1`` character set."""
+ self.charset = self.g1_charset
+
+ def draw(self, char):
+ """Display a character at the current cursor position and advance
+ the cursor if :data:`~pyte.modes.DECAWM` is set.
+
+ :param unicode char: a character to display.
+ """
+ # Translating a given character.
+ char = char.translate(self.charset)
+
+ # If this was the last column in a line and auto wrap mode is
+ # enabled, move the cursor to the next line. Otherwise replace
+ # characters already displayed with newly entered.
+ if self.cursor.x == self.columns:
+ if mo.DECAWM in self.mode:
+ self.linefeed()
+ else:
+ self.cursor.x -= 1
+
+ # If Insert mode is set, new characters move old characters to
+ # the right, otherwise terminal is in Replace mode and new
+ # characters replace old characters at cursor position.
+ if mo.IRM in self.mode:
+ self.insert_characters(1)
+
+ self[self.cursor.y][self.cursor.x] = self.cursor.attrs._replace(data=char)
+
+ # .. note:: We can't use :meth:`cursor_forward()`, because that
+ # way, we'll never know when to linefeed.
+ self.cursor.x += 1
+
+ def carriage_return(self):
+ """Move the cursor to the beginning of the current line."""
+ self.cursor.x = 0
+
+ def index(self):
+ """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
+
+ if self.cursor.y == bottom:
+ self.pop(top)
+ self.insert(bottom, take(self.columns, self.default_line))
+ else:
+ self.cursor_down()
+
+ def reverse_index(self):
+ """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
+
+ if self.cursor.y == top:
+ self.pop(bottom)
+ self.insert(top, take(self.columns, self.default_line))
+ else:
+ self.cursor_up()
+
+ def linefeed(self):
+ """Performs an index and, if :data:`~pyte.modes.LNM` is set, a
+ carriage return.
+ """
+ self.index()
+
+ if mo.LNM in self.mode:
+ self.carriage_return()
+
+ def tab(self):
+ """Move to the next tab space, or the end of the screen if there
+ aren't anymore left.
+ """
+ for stop in sorted(self.tabstops):
+ if self.cursor.x < stop:
+ column = stop
+ break
+ else:
+ column = self.columns - 1
+
+ self.cursor.x = column
+
+ def backspace(self):
+ """Move cursor to the left one or keep it in it's position if
+ it's at the beginning of the line already.
+ """
+ self.cursor_back()
+
+ def save_cursor(self):
+ """Push the current cursor position onto the stack."""
+ self.savepoints.append(Savepoint(copy.copy(self.cursor),
+ self.g0_charset,
+ self.g1_charset,
+ self.charset,
+ mo.DECOM in self.mode,
+ mo.DECAWM in self.mode))
+
+ def restore_cursor(self):
+ """Set the current cursor position to whatever cursor is on top
+ of the stack.
+ """
+ if self.savepoints:
+ savepoint = self.savepoints.pop()
+
+ self.g0_charset = savepoint.g0_charset
+ self.g1_charset = savepoint.g1_charset
+ self.charset = savepoint.charset
+
+ if savepoint.origin: self.set_mode(mo.DECOM)
+ if savepoint.wrap: self.set_mode(mo.DECAWM)
+
+ self.cursor = savepoint.cursor
+ self.ensure_bounds(use_margins=True)
+ else:
+ # If nothing was saved, the cursor moves to home position;
+ # origin mode is reset. :todo: DECAWM?
+ self.reset_mode(mo.DECOM)
+ self.cursor_position()
+
+ def insert_lines(self, count=None):
+ """Inserts the indicated # of lines at line with cursor. Lines
+ displayed **at** and below the cursor move down. Lines moved
+ past the bottom margin are lost.
+
+ :param count: number of lines to delete.
+ """
+ count = count or 1
+ top, bottom = self.margins
+
+ # If cursor is outside scrolling margins it -- do nothin'.
+ if top <= self.cursor.y <= bottom:
+ # v +1, because xrange() is exclusive.
+ for line in xrange(self.cursor.y, min(bottom + 1, self.cursor.y + count)):
+ self.pop(bottom)
+ self.insert(line, take(self.columns, self.default_line))
+
+ self.carriage_return()
+
+ def delete_lines(self, count=None):
+ """Deletes the indicated # of lines, starting at line with
+ cursor. As lines are deleted, lines displayed below cursor
+ move up. Lines added to bottom of screen have spaces with same