summaryrefslogtreecommitdiffstats
path: root/third_party/python/blessed
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/python/blessed
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--third_party/python/blessed/blessed-1.19.1.dist-info/LICENSE20
-rw-r--r--third_party/python/blessed/blessed-1.19.1.dist-info/METADATA269
-rw-r--r--third_party/python/blessed/blessed-1.19.1.dist-info/RECORD23
-rw-r--r--third_party/python/blessed/blessed-1.19.1.dist-info/WHEEL6
-rw-r--r--third_party/python/blessed/blessed-1.19.1.dist-info/top_level.txt1
-rw-r--r--third_party/python/blessed/blessed/__init__.py23
-rw-r--r--third_party/python/blessed/blessed/_capabilities.py168
-rw-r--r--third_party/python/blessed/blessed/_capabilities.pyi7
-rw-r--r--third_party/python/blessed/blessed/color.py258
-rw-r--r--third_party/python/blessed/blessed/color.pyi17
-rw-r--r--third_party/python/blessed/blessed/colorspace.py973
-rw-r--r--third_party/python/blessed/blessed/colorspace.pyi12
-rw-r--r--third_party/python/blessed/blessed/formatters.py498
-rw-r--r--third_party/python/blessed/blessed/formatters.pyi70
-rw-r--r--third_party/python/blessed/blessed/keyboard.py449
-rw-r--r--third_party/python/blessed/blessed/keyboard.pyi28
-rw-r--r--third_party/python/blessed/blessed/py.typed0
-rw-r--r--third_party/python/blessed/blessed/sequences.py461
-rw-r--r--third_party/python/blessed/blessed/sequences.pyi55
-rw-r--r--third_party/python/blessed/blessed/terminal.py1502
-rw-r--r--third_party/python/blessed/blessed/terminal.pyi106
-rw-r--r--third_party/python/blessed/blessed/win_terminal.py163
-rw-r--r--third_party/python/blessed/blessed/win_terminal.pyi11
23 files changed, 5120 insertions, 0 deletions
diff --git a/third_party/python/blessed/blessed-1.19.1.dist-info/LICENSE b/third_party/python/blessed/blessed-1.19.1.dist-info/LICENSE
new file mode 100644
index 0000000000..4b0713283e
--- /dev/null
+++ b/third_party/python/blessed/blessed-1.19.1.dist-info/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2014 Jeff Quast
+Copyright (c) 2011 Erik Rose
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/third_party/python/blessed/blessed-1.19.1.dist-info/METADATA b/third_party/python/blessed/blessed-1.19.1.dist-info/METADATA
new file mode 100644
index 0000000000..c0e3c98cdd
--- /dev/null
+++ b/third_party/python/blessed/blessed-1.19.1.dist-info/METADATA
@@ -0,0 +1,269 @@
+Metadata-Version: 2.1
+Name: blessed
+Version: 1.19.1
+Summary: Easy, practical library for making terminal apps, by providing an elegant, well-documented interface to Colors, Keyboard input, and screen Positioning capabilities.
+Home-page: https://github.com/jquast/blessed
+Author: Jeff Quast, Erik Rose, Avram Lubkin
+Author-email: contact@jeffquast.com
+License: MIT
+Project-URL: Documentation, https://blessed.readthedocs.io
+Keywords: terminal,sequences,tty,curses,ncurses,formatting,style,color,console,keyboard,ansi,xterm
+Platform: UNKNOWN
+Classifier: Intended Audience :: Developers
+Classifier: Natural Language :: English
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Console
+Classifier: Environment :: Console :: Curses
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Software Development :: User Interfaces
+Classifier: Topic :: Terminals
+Classifier: Typing :: Typed
+Requires-Python: >=2.7
+License-File: LICENSE
+Requires-Dist: wcwidth (>=0.1.4)
+Requires-Dist: six (>=1.9.0)
+Requires-Dist: jinxed (>=1.1.0) ; platform_system == "Windows"
+Requires-Dist: ordereddict (==1.1) ; python_version < "2.7"
+Requires-Dist: backports.functools-lru-cache (>=1.2.1) ; python_version < "3.2"
+
+| |pypi_downloads| |codecov| |windows| |linux| |mac| |bsd|
+
+Introduction
+============
+
+Blessed is an easy, practical *library* for making *terminal* apps, by providing an elegant,
+well-documented interface to Colors_, Keyboard_ input, and screen position and Location_
+capabilities.
+
+.. code-block:: python
+
+ from blessed import Terminal
+
+ term = Terminal()
+
+ print(term.home + term.clear + term.move_y(term.height // 2))
+ print(term.black_on_darkkhaki(term.center('press any key to continue.')))
+
+ with term.cbreak(), term.hidden_cursor():
+ inp = term.inkey()
+
+ print(term.move_down(2) + 'You pressed ' + term.bold(repr(inp)))
+
+.. figure:: https://dxtz6bzwq9sxx.cloudfront.net/demo_basic_intro.gif
+ :alt: Animation of running the code example
+
+It's meant to be *fun* and *easy*, to do basic terminal graphics and styling with Python using
+*blessed*. Terminal_ is the only class you need to import and the only object you should need for
+Terminal capabilities.
+
+Whether you want to improve CLI apps with colors, or make fullscreen applications or games,
+*blessed* should help get you started quickly. Your users will love it because it works on Windows,
+Mac, and Linux, and you will love it because it has plenty of documentation and examples!
+
+Full documentation at https://blessed.readthedocs.io/en/latest/
+
+Examples
+--------
+
+.. figure:: https://dxtz6bzwq9sxx.cloudfront.net/blessed_demo_intro.gif
+ :alt: Animations of x11-colorpicker.py, bounce.py, worms.py, and plasma.py
+
+ x11-colorpicker.py_, bounce.py_, worms.py_, and plasma.py_, from our repository.
+
+Exemplary 3rd-party examples which use *blessed*,
+
+.. figure:: https://dxtz6bzwq9sxx.cloudfront.net/demo_3rdparty_voltron.png
+ :alt: Screenshot of 'Voltron' (By the author of Voltron, from their README).
+
+ Voltron_ is an extensible debugger UI toolkit written in Python
+
+.. figure:: https://dxtz6bzwq9sxx.cloudfront.net/demo_3rdparty_cursewords.gif
+ :alt: Animation of 'cursewords' (By the author of cursewords, from their README).
+
+ cursewords_ is "graphical" command line program for solving crossword puzzles in the terminal.
+
+.. figure:: https://dxtz6bzwq9sxx.cloudfront.net/demo_3rdparty_githeat.gif
+ :alt: Animation of 'githeat.interactive', using blessed repository at the time of capture.
+
+ GitHeat_ builds an interactive heatmap of git history.
+
+.. figure:: https://dxtz6bzwq9sxx.cloudfront.net/demo_3rdparty_dashing.gif
+ :alt: Animations from 'Dashing' (By the author of Dashing, from their README)
+
+ Dashing_ is a library to quickly create terminal-based dashboards.
+
+.. figure:: https://dxtz6bzwq9sxx.cloudfront.net/demo_3rdparty_enlighten.gif
+ :alt: Animations from 'Enlighten' (By the author of Enlighten, from their README)
+
+ Enlighten_ is a console progress bar library that allows simultaneous output without redirection.
+
+.. figure:: https://dxtz6bzwq9sxx.cloudfront.net/blessed_3rdparty_macht.gif
+ :alt: Demonstration of 'macht', a 2048 clone
+
+ macht_ is a clone of the (briefly popular) puzzle game, 2048.
+
+Requirements
+------------
+
+*Blessed* works with Windows, Mac, Linux, and BSD's, on Python 2.7, 3.4, 3.5, 3.6, 3.7, and 3.8.
+
+Brief Overview
+--------------
+
+*Blessed* is more than just a Python wrapper around curses_:
+
+* Styles_, Colors_, and maybe a little positioning without necessarily clearing the whole screen
+ first.
+* Works great with Python's new f-strings_ or any other kind of string formatting.
+* Provides up-to-the-moment Location_ and terminal height and width, so you can respond to terminal
+ size changes.
+* Avoids making a mess if the output gets piped to a non-terminal, you can output sequences to any
+ file-like object such as *StringIO*, files, pipes or sockets.
+* Uses `terminfo(5)`_ so it works with any terminal type and capability: No more C-like calls to
+ tigetstr_ and tparm_.
+* Non-obtrusive calls to only the capabilities database ensures that you are free to mix and match
+ with calls to any other curses application code or library you like.
+* Provides context managers `Terminal.fullscreen()`_ and `Terminal.hidden_cursor()`_ to safely
+ express terminal modes, curses development will no longer fudge up your shell.
+* Act intelligently when somebody redirects your output to a file, omitting all of the special
+ sequences colors, but still containing all of the text.
+
+*Blessed* is a fork of `blessings <https://github.com/erikrose/blessings>`_, which does all of
+the same above with the same API, as well as following **enhancements**:
+
+* Windows support, new since Dec. 2019!
+* Dead-simple keyboard handling: safely decoding unicode input in your system's preferred locale and
+ supports application/arrow keys.
+* 24-bit color support, using `Terminal.color_rgb()`_ and `Terminal.on_color_rgb()`_ and all X11
+ Colors_ by name, and not by number.
+* Determine cursor location using `Terminal.get_location()`_, enter key-at-a-time input mode using
+ `Terminal.cbreak()`_ or `Terminal.raw()`_ context managers, and read timed key presses using
+ `Terminal.inkey()`_.
+* Allows the *printable length* of strings that contain sequences to be determined by
+ `Terminal.length()`_, supporting additional methods `Terminal.wrap()`_ and `Terminal.center()`_,
+ terminal-aware variants of the built-in function `textwrap.wrap()`_ and method `str.center()`_,
+ respectively.
+* Allows sequences to be removed from strings that contain them, using `Terminal.strip_seqs()`_ or
+ sequences and whitespace using `Terminal.strip()`_.
+
+Before And After
+----------------
+
+With the built-in curses_ module, this is how you would typically
+print some underlined text at the bottom of the screen:
+
+.. code-block:: python
+
+ from curses import tigetstr, setupterm, tparm
+ from fcntl import ioctl
+ from os import isatty
+ import struct
+ import sys
+ from termios import TIOCGWINSZ
+
+ # If we want to tolerate having our output piped to other commands or
+ # files without crashing, we need to do all this branching:
+ if hasattr(sys.stdout, 'fileno') and isatty(sys.stdout.fileno()):
+ setupterm()
+ sc = tigetstr('sc')
+ cup = tigetstr('cup')
+ rc = tigetstr('rc')
+ underline = tigetstr('smul')
+ normal = tigetstr('sgr0')
+ else:
+ sc = cup = rc = underline = normal = ''
+
+ # Save cursor position.
+ print(sc)
+
+ if cup:
+ # tigetnum('lines') doesn't always update promptly, hence this:
+ height = struct.unpack('hhhh', ioctl(0, TIOCGWINSZ, '\000' * 8))[0]
+
+ # Move cursor to bottom.
+ print(tparm(cup, height - 1, 0))
+
+ print('This is {under}underlined{normal}!'
+ .format(under=underline, normal=normal))
+
+ # Restore cursor position.
+ print(rc)
+
+The same program with *Blessed* is simply:
+
+.. code-block:: python
+
+ from blessed import Terminal
+
+ term = Terminal()
+ with term.location(0, term.height - 1):
+ print('This is ' + term.underline('underlined') + '!', end='')
+
+.. _curses: https://docs.python.org/3/library/curses.html
+.. _tigetstr: http://man.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man3/tigetstr.3
+.. _tparm: http://man.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man3/tparm.3
+.. _`terminfo(5)`: https://invisible-island.net/ncurses/man/terminfo.5.html
+.. _str.center(): https://docs.python.org/3/library/stdtypes.html#str.center
+.. _textwrap.wrap(): https://docs.python.org/3/library/textwrap.html#textwrap.wrap
+.. _Terminal: https://blessed.readthedocs.io/en/stable/terminal.html
+.. _`Terminal.fullscreen()`: https://blessed.readthedocs.io/en/latest/api/terminal.html#blessed.terminal.Terminal.fullscreen
+.. _`Terminal.get_location()`: https://blessed.readthedocs.io/en/latest/location.html#finding-the-cursor
+.. _`Terminal.color_rgb()`: https://blessed.readthedocs.io/en/stable/api/terminal.html#blessed.terminal.Terminal.color_rgb
+.. _`Terminal.hidden_cursor()`: https://blessed.readthedocs.io/en/latest/api/terminal.html#blessed.terminal.Terminal.hidden_cursor
+.. _`Terminal.on_color_rgb()`: https://blessed.readthedocs.io/en/stable/api/terminal.html#blessed.terminal.Terminal.on_color_rgb
+.. _`Terminal.length()`: https://blessed.readthedocs.io/en/stable/api/terminal.html#blessed.terminal.Terminal.length
+.. _`Terminal.strip()`: https://blessed.readthedocs.io/en/stable/api/terminal.html#blessed.terminal.Terminal.strip
+.. _`Terminal.rstrip()`: https://blessed.readthedocs.io/en/stable/api/terminal.html#blessed.terminal.Terminal.rstrip
+.. _`Terminal.lstrip()`: https://blessed.readthedocs.io/en/stable/api/terminal.html#blessed.terminal.Terminal.lstrip
+.. _`Terminal.strip_seqs()`: https://blessed.readthedocs.io/en/stable/api/terminal.html#blessed.terminal.Terminal.strip_seqs
+.. _`Terminal.wrap()`: https://blessed.readthedocs.io/en/stable/api/terminal.html#blessed.terminal.Terminal.wrap
+.. _`Terminal.center()`: https://blessed.readthedocs.io/en/stable/api/terminal.html#blessed.terminal.Terminal.center
+.. _`Terminal.rjust()`: https://blessed.readthedocs.io/en/stable/api/terminal.html#blessed.terminal.Terminal.rjust
+.. _`Terminal.ljust()`: https://blessed.readthedocs.io/en/stable/api/terminal.html#blessed.terminal.Terminal.ljust
+.. _`Terminal.cbreak()`: https://blessed.readthedocs.io/en/stable/api/terminal.html#blessed.terminal.Terminal.cbreak
+.. _`Terminal.raw()`: https://blessed.readthedocs.io/en/stable/api/terminal.html#blessed.terminal.Terminal.raw
+.. _`Terminal.inkey()`: https://blessed.readthedocs.io/en/stable/api/terminal.html#blessed.terminal.Terminal.inkey
+.. _Colors: https://blessed.readthedocs.io/en/stable/colors.html
+.. _Styles: https://blessed.readthedocs.io/en/stable/terminal.html#styles
+.. _Location: https://blessed.readthedocs.io/en/stable/location.html
+.. _Keyboard: https://blessed.readthedocs.io/en/stable/keyboard.html
+.. _Examples: https://blessed.readthedocs.io/en/stable/examples.html
+.. _x11-colorpicker.py: https://blessed.readthedocs.io/en/stable/examples.html#x11-colorpicker-py
+.. _bounce.py: https://blessed.readthedocs.io/en/stable/examples.html#bounce-py
+.. _worms.py: https://blessed.readthedocs.io/en/stable/examples.html#worms-py
+.. _plasma.py: https://blessed.readthedocs.io/en/stable/examples.html#plasma-py
+.. _Voltron: https://github.com/snare/voltron
+.. _cursewords: https://github.com/thisisparker/cursewords
+.. _GitHeat: https://github.com/AmmsA/Githeat
+.. _Dashing: https://github.com/FedericoCeratto/dashing
+.. _Enlighten: https://github.com/Rockhopper-Technologies/enlighten
+.. _macht: https://github.com/rolfmorel/macht
+.. _f-strings: https://docs.python.org/3/reference/lexical_analysis.html#f-strings
+.. |pypi_downloads| image:: https://img.shields.io/pypi/dm/blessed.svg?logo=pypi
+ :alt: Downloads
+ :target: https://pypi.org/project/blessed/
+.. |codecov| image:: https://codecov.io/gh/jquast/blessed/branch/master/graph/badge.svg
+ :alt: codecov.io Code Coverage
+ :target: https://codecov.io/gh/jquast/blessed/
+.. |linux| image:: https://img.shields.io/badge/Linux-yes-success?logo=linux
+ :alt: Linux supported
+.. |windows| image:: https://img.shields.io/badge/Windows-NEW-success?logo=windows
+ :alt: Windows supported
+.. |mac| image:: https://img.shields.io/badge/MacOS-yes-success?logo=apple
+ :alt: MacOS supported
+.. |bsd| image:: https://img.shields.io/badge/BSD-yes-success?logo=freebsd
+ :alt: BSD supported
+
+
diff --git a/third_party/python/blessed/blessed-1.19.1.dist-info/RECORD b/third_party/python/blessed/blessed-1.19.1.dist-info/RECORD
new file mode 100644
index 0000000000..ddbe360887
--- /dev/null
+++ b/third_party/python/blessed/blessed-1.19.1.dist-info/RECORD
@@ -0,0 +1,23 @@
+blessed/__init__.py,sha256=cVCRzlNO_XNDjs-hlDkzv5m3o5BMXxD-hVm30bbWQ1w,687
+blessed/_capabilities.py,sha256=Thj8lgDvhfM6TttvziDu0mabqZqYnwAwC3NTtSMntxc,6292
+blessed/_capabilities.pyi,sha256=If5dG9LhrIyTxUuCuV44bNxnWMk3S7Xvry3oGOBHp2k,265
+blessed/color.py,sha256=D5VmWAsZxSYIERKmkJ7FVhZgNssEtUkV8GcMbDnoxVk,7502
+blessed/color.pyi,sha256=4DNLFe-SMCVKbsvuMFLWUzNCdy45bgtZuk3wP51HlKQ,690
+blessed/colorspace.py,sha256=GEoKL18C9VrBlt-LqbhFirBuhqUmS-tyy0sb9VNJS5U,35322
+blessed/colorspace.pyi,sha256=zwo_F4rf0GSPJsW2irvxHQ3SqNlGgt7VQOFN2hXnTnw,234
+blessed/formatters.py,sha256=3QQiwMC51EdXy6-ZbCPRfw87X49jErvk2-k1yIBwk9g,19416
+blessed/formatters.pyi,sha256=gDgcWIk3pqif7SZgL5DG--GjO7QXwzen5UNgqQ_XHsw,2091
+blessed/keyboard.py,sha256=2WNuDJp_bb5tyRho6cGUup6Hqvp7bFgGDC5D5HbJ2-I,17687
+blessed/keyboard.pyi,sha256=9ibu_A44OkWKcdy_36EeHVhK4ybnurig5arrXxwVOeM,796
+blessed/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+blessed/sequences.py,sha256=Hjs2ddTcXFWqxdL_PKuMoPvaJ3gRRjsZxdy9dByXvLU,17126
+blessed/sequences.pyi,sha256=_zJkZm8S015g242mRUoEp12Qs27DS348vFqkSRsYfHc,1852
+blessed/terminal.py,sha256=QA7yt-E7U72WF7RGkfR7g_HcGAuGFalF3aUP6Zm1L6Y,59889
+blessed/terminal.pyi,sha256=ujihEHr4Ii6r8xw2UBOnAjcY1Wm7dlGbGwfLBqyQQiU,4078
+blessed/win_terminal.py,sha256=uGl52EiEq4K3udsZJHn2tlnESUHg_77fxWrEuFD77WY,5804
+blessed/win_terminal.pyi,sha256=GoS67cnj927_SXZRr1WCLD21ie_w7zlA20V33clpV7E,333
+blessed-1.19.1.dist-info/LICENSE,sha256=YBSQ1biC0QDEeC-dqb_dI_lg5reeNazESZQ5XBj01X0,1083
+blessed-1.19.1.dist-info/METADATA,sha256=H1PBstJVFYedNvC-3vOFQE0PItwv5f6wtmlKy0P5hYQ,13155
+blessed-1.19.1.dist-info/WHEEL,sha256=Z-nyYpwrcSqxfdux5Mbn_DQ525iP7J2DG3JgGvOYyTQ,110
+blessed-1.19.1.dist-info/top_level.txt,sha256=2lUIfLwFZtAucvesS5UE8_MxXID5rSx_3gJ2-1JGckA,8
+blessed-1.19.1.dist-info/RECORD,,
diff --git a/third_party/python/blessed/blessed-1.19.1.dist-info/WHEEL b/third_party/python/blessed/blessed-1.19.1.dist-info/WHEEL
new file mode 100644
index 0000000000..01b8fc7d4a
--- /dev/null
+++ b/third_party/python/blessed/blessed-1.19.1.dist-info/WHEEL
@@ -0,0 +1,6 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.36.2)
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
+
diff --git a/third_party/python/blessed/blessed-1.19.1.dist-info/top_level.txt b/third_party/python/blessed/blessed-1.19.1.dist-info/top_level.txt
new file mode 100644
index 0000000000..d43de1b8be
--- /dev/null
+++ b/third_party/python/blessed/blessed-1.19.1.dist-info/top_level.txt
@@ -0,0 +1 @@
+blessed
diff --git a/third_party/python/blessed/blessed/__init__.py b/third_party/python/blessed/blessed/__init__.py
new file mode 100644
index 0000000000..d1a89c1af7
--- /dev/null
+++ b/third_party/python/blessed/blessed/__init__.py
@@ -0,0 +1,23 @@
+"""
+A thin, practical wrapper around terminal capabilities in Python.
+
+http://pypi.python.org/pypi/blessed
+"""
+# std imports
+import sys as _sys
+import platform as _platform
+
+# isort: off
+if _platform.system() == 'Windows':
+ from blessed.win_terminal import Terminal
+else:
+ from blessed.terminal import Terminal # type: ignore
+
+if (3, 0, 0) <= _sys.version_info[:3] < (3, 2, 3):
+ # Good till 3.2.10
+ # Python 3.x < 3.2.3 has a bug in which tparm() erroneously takes a string.
+ raise ImportError('Blessed needs Python 3.2.3 or greater for Python 3 '
+ 'support due to http://bugs.python.org/issue10570.')
+
+__all__ = ('Terminal',)
+__version__ = "1.19.1"
diff --git a/third_party/python/blessed/blessed/_capabilities.py b/third_party/python/blessed/blessed/_capabilities.py
new file mode 100644
index 0000000000..c4df54bca6
--- /dev/null
+++ b/third_party/python/blessed/blessed/_capabilities.py
@@ -0,0 +1,168 @@
+"""Terminal capability builder patterns."""
+# std imports
+import re
+from collections import OrderedDict
+
+__all__ = (
+ 'CAPABILITY_DATABASE',
+ 'CAPABILITIES_RAW_MIXIN',
+ 'CAPABILITIES_ADDITIVES',
+ 'CAPABILITIES_CAUSE_MOVEMENT',
+)
+
+CAPABILITY_DATABASE = OrderedDict((
+ ('bell', ('bel', {})),
+ ('carriage_return', ('cr', {})),
+ ('change_scroll_region', ('csr', {'nparams': 2})),
+ ('clear_all_tabs', ('tbc', {})),
+ ('clear_screen', ('clear', {})),
+ ('clr_bol', ('el1', {})),
+ ('clr_eol', ('el', {})),
+ ('clr_eos', ('clear_eos', {})),
+ ('column_address', ('hpa', {'nparams': 1})),
+ ('cursor_address', ('cup', {'nparams': 2, 'match_grouped': True})),
+ ('cursor_down', ('cud1', {})),
+ ('cursor_home', ('home', {})),
+ ('cursor_invisible', ('civis', {})),
+ ('cursor_left', ('cub1', {})),
+ ('cursor_normal', ('cnorm', {})),
+ ('cursor_report', ('u6', {'nparams': 2, 'match_grouped': True})),
+ ('cursor_right', ('cuf1', {})),
+ ('cursor_up', ('cuu1', {})),
+ ('cursor_visible', ('cvvis', {})),
+ ('delete_character', ('dch1', {})),
+ ('delete_line', ('dl1', {})),
+ ('enter_blink_mode', ('blink', {})),
+ ('enter_bold_mode', ('bold', {})),
+ ('enter_dim_mode', ('dim', {})),
+ ('enter_fullscreen', ('smcup', {})),
+ ('enter_standout_mode', ('standout', {})),
+ ('enter_superscript_mode', ('superscript', {})),
+ ('enter_susimpleript_mode', ('susimpleript', {})),
+ ('enter_underline_mode', ('underline', {})),
+ ('erase_chars', ('ech', {'nparams': 1})),
+ ('exit_alt_charset_mode', ('rmacs', {})),
+ ('exit_am_mode', ('rmam', {})),
+ ('exit_attribute_mode', ('sgr0', {})),
+ ('exit_ca_mode', ('rmcup', {})),
+ ('exit_fullscreen', ('rmcup', {})),
+ ('exit_insert_mode', ('rmir', {})),
+ ('exit_standout_mode', ('rmso', {})),
+ ('exit_underline_mode', ('rmul', {})),
+ ('flash_hook', ('hook', {})),
+ ('flash_screen', ('flash', {})),
+ ('insert_line', ('il1', {})),
+ ('keypad_local', ('rmkx', {})),
+ ('keypad_xmit', ('smkx', {})),
+ ('meta_off', ('rmm', {})),
+ ('meta_on', ('smm', {})),
+ ('orig_pair', ('op', {})),
+ ('parm_down_cursor', ('cud', {'nparams': 1})),
+ ('parm_left_cursor', ('cub', {'nparams': 1, 'match_grouped': True})),
+ ('parm_dch', ('dch', {'nparams': 1})),
+ ('parm_delete_line', ('dl', {'nparams': 1})),
+ ('parm_ich', ('ich', {'nparams': 1})),
+ ('parm_index', ('indn', {'nparams': 1})),
+ ('parm_insert_line', ('il', {'nparams': 1})),
+ ('parm_right_cursor', ('cuf', {'nparams': 1, 'match_grouped': True})),
+ ('parm_rindex', ('rin', {'nparams': 1})),
+ ('parm_up_cursor', ('cuu', {'nparams': 1})),
+ ('print_screen', ('mc0', {})),
+ ('prtr_off', ('mc4', {})),
+ ('prtr_on', ('mc5', {})),
+ ('reset_1string', ('r1', {})),
+ ('reset_2string', ('r2', {})),
+ ('reset_3string', ('r3', {})),
+ ('restore_cursor', ('rc', {})),
+ ('row_address', ('vpa', {'nparams': 1})),
+ ('save_cursor', ('sc', {})),
+ ('scroll_forward', ('ind', {})),
+ ('scroll_reverse', ('rev', {})),
+ ('set0_des_seq', ('s0ds', {})),
+ ('set1_des_seq', ('s1ds', {})),
+ ('set2_des_seq', ('s2ds', {})),
+ ('set3_des_seq', ('s3ds', {})),
+ # this 'color' is deceiving, but often matching, and a better match
+ # than set_a_attributes1 or set_a_foreground.
+ ('color', ('_foreground_color', {'nparams': 1, 'match_any': True,
+ 'numeric': 1})),
+ ('set_a_foreground', ('color', {'nparams': 1, 'match_any': True,
+ 'numeric': 1})),
+ ('set_a_background', ('on_color', {'nparams': 1, 'match_any': True,
+ 'numeric': 1})),
+ ('set_tab', ('hts', {})),
+ ('tab', ('ht', {})),
+ ('italic', ('sitm', {})),
+ ('no_italic', ('sitm', {})),
+))
+
+CAPABILITIES_RAW_MIXIN = {
+ 'bell': re.escape('\a'),
+ 'carriage_return': re.escape('\r'),
+ 'cursor_left': re.escape('\b'),
+ 'cursor_report': re.escape('\x1b') + r'\[(\d+)\;(\d+)R',
+ 'cursor_right': re.escape('\x1b') + r'\[C',
+ 'exit_attribute_mode': re.escape('\x1b') + r'\[m',
+ 'parm_left_cursor': re.escape('\x1b') + r'\[(\d+)D',
+ 'parm_right_cursor': re.escape('\x1b') + r'\[(\d+)C',
+ 'restore_cursor': re.escape(r'\x1b\[u'),
+ 'save_cursor': re.escape(r'\x1b\[s'),
+ 'scroll_forward': re.escape('\n'),
+ 'set0_des_seq': re.escape('\x1b(B'),
+ 'tab': re.escape('\t'),
+}
+_ANY_NOTESC = '[^' + re.escape('\x1b') + ']*'
+
+CAPABILITIES_ADDITIVES = {
+ 'link': ('link',
+ re.escape('\x1b') + r'\]8;' + _ANY_NOTESC + ';' +
+ _ANY_NOTESC + re.escape('\x1b') + '\\\\'),
+ 'color256': ('color', re.escape('\x1b') + r'\[38;5;\d+m'),
+ 'on_color256': ('on_color', re.escape('\x1b') + r'\[48;5;\d+m'),
+ 'color_rgb': ('color_rgb', re.escape('\x1b') + r'\[38;2;\d+;\d+;\d+m'),
+ 'on_color_rgb': ('on_color_rgb', re.escape('\x1b') + r'\[48;2;\d+;\d+;\d+m'),
+ 'shift_in': ('', re.escape('\x0f')),
+ 'shift_out': ('', re.escape('\x0e')),
+ # sgr(...) outputs strangely, use the basic ANSI/EMCA-48 codes here.
+ 'set_a_attributes1': (
+ 'sgr', re.escape('\x1b') + r'\[\d+m'),
+ 'set_a_attributes2': (
+ 'sgr', re.escape('\x1b') + r'\[\d+\;\d+m'),
+ 'set_a_attributes3': (
+ 'sgr', re.escape('\x1b') + r'\[\d+\;\d+\;\d+m'),
+ 'set_a_attributes4': (
+ 'sgr', re.escape('\x1b') + r'\[\d+\;\d+\;\d+\;\d+m'),
+ # this helps where xterm's sgr0 includes set0_des_seq, we'd
+ # rather like to also match this immediate substring.
+ 'sgr0': ('sgr0', re.escape('\x1b') + r'\[m'),
+ 'backspace': ('', re.escape('\b')),
+ 'ascii_tab': ('', re.escape('\t')),
+ 'clr_eol': ('', re.escape('\x1b[K')),
+ 'clr_eol0': ('', re.escape('\x1b[0K')),
+ 'clr_bol': ('', re.escape('\x1b[1K')),
+ 'clr_eosK': ('', re.escape('\x1b[2K')),
+}
+
+CAPABILITIES_CAUSE_MOVEMENT = (
+ 'ascii_tab',
+ 'backspace',
+ 'carriage_return',
+ 'clear_screen',
+ 'column_address',
+ 'cursor_address',
+ 'cursor_down',
+ 'cursor_home',
+ 'cursor_left',
+ 'cursor_right',
+ 'cursor_up',
+ 'enter_fullscreen',
+ 'exit_fullscreen',
+ 'parm_down_cursor',
+ 'parm_left_cursor',
+ 'parm_right_cursor',
+ 'parm_up_cursor',
+ 'restore_cursor',
+ 'row_address',
+ 'scroll_forward',
+ 'tab',
+)
diff --git a/third_party/python/blessed/blessed/_capabilities.pyi b/third_party/python/blessed/blessed/_capabilities.pyi
new file mode 100644
index 0000000000..04c59c35fa
--- /dev/null
+++ b/third_party/python/blessed/blessed/_capabilities.pyi
@@ -0,0 +1,7 @@
+# std imports
+from typing import Any, Dict, Tuple, OrderedDict
+
+CAPABILITY_DATABASE: OrderedDict[str, Tuple[str, Dict[str, Any]]]
+CAPABILITIES_RAW_MIXIN: Dict[str, str]
+CAPABILITIES_ADDITIVES: Dict[str, Tuple[str, str]]
+CAPABILITIES_CAUSE_MOVEMENT: Tuple[str, ...]
diff --git a/third_party/python/blessed/blessed/color.py b/third_party/python/blessed/blessed/color.py
new file mode 100644
index 0000000000..482fc0e11e
--- /dev/null
+++ b/third_party/python/blessed/blessed/color.py
@@ -0,0 +1,258 @@
+# -*- coding: utf-8 -*-
+"""
+Sub-module providing color functions.
+
+References,
+
+- https://en.wikipedia.org/wiki/Color_difference
+- http://www.easyrgb.com/en/math.php
+- Measuring Colour by R.W.G. Hunt and M.R. Pointer
+"""
+
+# std imports
+from math import cos, exp, sin, sqrt, atan2
+
+# isort: off
+try:
+ from functools import lru_cache
+except ImportError:
+ # lru_cache was added in Python 3.2
+ from backports.functools_lru_cache import lru_cache
+
+
+def rgb_to_xyz(red, green, blue):
+ """
+ Convert standard RGB color to XYZ color.
+
+ :arg int red: RGB value of Red.
+ :arg int green: RGB value of Green.
+ :arg int blue: RGB value of Blue.
+ :returns: Tuple (X, Y, Z) representing XYZ color
+ :rtype: tuple
+
+ D65/2° standard illuminant
+ """
+ rgb = []
+ for val in red, green, blue:
+ val /= 255.0
+ if val > 0.04045:
+ val = pow((val + 0.055) / 1.055, 2.4)
+ else:
+ val /= 12.92
+ val *= 100
+ rgb.append(val)
+
+ red, green, blue = rgb # pylint: disable=unbalanced-tuple-unpacking
+ x_val = red * 0.4124 + green * 0.3576 + blue * 0.1805
+ y_val = red * 0.2126 + green * 0.7152 + blue * 0.0722
+ z_val = red * 0.0193 + green * 0.1192 + blue * 0.9505
+
+ return x_val, y_val, z_val
+
+
+def xyz_to_lab(x_val, y_val, z_val):
+ """
+ Convert XYZ color to CIE-Lab color.
+
+ :arg float x_val: XYZ value of X.
+ :arg float y_val: XYZ value of Y.
+ :arg float z_val: XYZ value of Z.
+ :returns: Tuple (L, a, b) representing CIE-Lab color
+ :rtype: tuple
+
+ D65/2° standard illuminant
+ """
+ xyz = []
+ for val, ref in (x_val, 95.047), (y_val, 100.0), (z_val, 108.883):
+ val /= ref
+ val = pow(val, 1 / 3.0) if val > 0.008856 else 7.787 * val + 16 / 116.0
+ xyz.append(val)
+
+ x_val, y_val, z_val = xyz # pylint: disable=unbalanced-tuple-unpacking
+ cie_l = 116 * y_val - 16
+ cie_a = 500 * (x_val - y_val)
+ cie_b = 200 * (y_val - z_val)
+
+ return cie_l, cie_a, cie_b
+
+
+@lru_cache(maxsize=256)
+def rgb_to_lab(red, green, blue):
+ """
+ Convert RGB color to CIE-Lab color.
+
+ :arg int red: RGB value of Red.
+ :arg int green: RGB value of Green.
+ :arg int blue: RGB value of Blue.
+ :returns: Tuple (L, a, b) representing CIE-Lab color
+ :rtype: tuple
+
+ D65/2° standard illuminant
+ """
+ return xyz_to_lab(*rgb_to_xyz(red, green, blue))
+
+
+def dist_rgb(rgb1, rgb2):
+ """
+ Determine distance between two rgb colors.
+
+ :arg tuple rgb1: RGB color definition
+ :arg tuple rgb2: RGB color definition
+ :returns: Square of the distance between provided colors
+ :rtype: float
+
+ This works by treating RGB colors as coordinates in three dimensional
+ space and finding the closest point within the configured color range
+ using the formula::
+
+ d^2 = (r2 - r1)^2 + (g2 - g1)^2 + (b2 - b1)^2
+
+ For efficiency, the square of the distance is returned
+ which is sufficient for comparisons
+ """
+ return sum(pow(rgb1[idx] - rgb2[idx], 2) for idx in (0, 1, 2))
+
+
+def dist_rgb_weighted(rgb1, rgb2):
+ """
+ Determine the weighted distance between two rgb colors.
+
+ :arg tuple rgb1: RGB color definition
+ :arg tuple rgb2: RGB color definition
+ :returns: Square of the distance between provided colors
+ :rtype: float
+
+ Similar to a standard distance formula, the values are weighted
+ to approximate human perception of color differences
+
+ For efficiency, the square of the distance is returned
+ which is sufficient for comparisons
+ """
+ red_mean = (rgb1[0] + rgb2[0]) / 2.0
+
+ return ((2 + red_mean / 256) * pow(rgb1[0] - rgb2[0], 2) +
+ 4 * pow(rgb1[1] - rgb2[1], 2) +
+ (2 + (255 - red_mean) / 256) * pow(rgb1[2] - rgb2[2], 2))
+
+
+def dist_cie76(rgb1, rgb2):
+ """
+ Determine distance between two rgb colors using the CIE94 algorithm.
+
+ :arg tuple rgb1: RGB color definition
+ :arg tuple rgb2: RGB color definition
+ :returns: Square of the distance between provided colors
+ :rtype: float
+
+ For efficiency, the square of the distance is returned
+ which is sufficient for comparisons
+ """
+ l_1, a_1, b_1 = rgb_to_lab(*rgb1)
+ l_2, a_2, b_2 = rgb_to_lab(*rgb2)
+ return pow(l_1 - l_2, 2) + pow(a_1 - a_2, 2) + pow(b_1 - b_2, 2)
+
+
+def dist_cie94(rgb1, rgb2):
+ # pylint: disable=too-many-locals
+ """
+ Determine distance between two rgb colors using the CIE94 algorithm.
+
+ :arg tuple rgb1: RGB color definition
+ :arg tuple rgb2: RGB color definition
+ :returns: Square of the distance between provided colors
+ :rtype: float
+
+ For efficiency, the square of the distance is returned
+ which is sufficient for comparisons
+ """
+ l_1, a_1, b_1 = rgb_to_lab(*rgb1)
+ l_2, a_2, b_2 = rgb_to_lab(*rgb2)
+
+ s_l = k_l = k_c = k_h = 1
+ k_1 = 0.045
+ k_2 = 0.015
+
+ delta_l = l_1 - l_2
+ delta_a = a_1 - a_2
+ delta_b = b_1 - b_2
+ c_1 = sqrt(a_1 ** 2 + b_1 ** 2)
+ c_2 = sqrt(a_2 ** 2 + b_2 ** 2)
+ delta_c = c_1 - c_2
+ delta_h = sqrt(delta_a ** 2 + delta_b ** 2 + delta_c ** 2)
+ s_c = 1 + k_1 * c_1
+ s_h = 1 + k_2 * c_1
+
+ return ((delta_l / (k_l * s_l)) ** 2 + # pylint: disable=superfluous-parens
+ (delta_c / (k_c * s_c)) ** 2 +
+ (delta_h / (k_h * s_h)) ** 2)
+
+
+def dist_cie2000(rgb1, rgb2):
+ # pylint: disable=too-many-locals
+ """
+ Determine distance between two rgb colors using the CIE2000 algorithm.
+
+ :arg tuple rgb1: RGB color definition
+ :arg tuple rgb2: RGB color definition
+ :returns: Square of the distance between provided colors
+ :rtype: float
+
+ For efficiency, the square of the distance is returned
+ which is sufficient for comparisons
+ """
+ s_l = k_l = k_c = k_h = 1
+
+ l_1, a_1, b_1 = rgb_to_lab(*rgb1)
+ l_2, a_2, b_2 = rgb_to_lab(*rgb2)
+
+ delta_l = l_2 - l_1
+ l_mean = (l_1 + l_2) / 2
+
+ c_1 = sqrt(a_1 ** 2 + b_1 ** 2)
+ c_2 = sqrt(a_2 ** 2 + b_2 ** 2)
+ c_mean = (c_1 + c_2) / 2
+ delta_c = c_1 - c_2
+
+ g_x = sqrt(c_mean ** 7 / (c_mean ** 7 + 25 ** 7))
+ h_1 = atan2(b_1, a_1 + (a_1 / 2) * (1 - g_x)) % 360
+ h_2 = atan2(b_2, a_2 + (a_2 / 2) * (1 - g_x)) % 360
+
+ if 0 in (c_1, c_2):
+ delta_h_prime = 0
+ h_mean = h_1 + h_2
+ else:
+ delta_h_prime = h_2 - h_1
+ if abs(delta_h_prime) <= 180:
+ h_mean = (h_1 + h_2) / 2
+ else:
+ if h_2 <= h_1:
+ delta_h_prime += 360
+ else:
+ delta_h_prime -= 360
+ h_mean = (h_1 + h_2 + 360) / 2 if h_1 + h_2 < 360 else (h_1 + h_2 - 360) / 2
+
+ delta_h = 2 * sqrt(c_1 * c_2) * sin(delta_h_prime / 2)
+
+ t_x = (1 -
+ 0.17 * cos(h_mean - 30) +
+ 0.24 * cos(2 * h_mean) +
+ 0.32 * cos(3 * h_mean + 6) -
+ 0.20 * cos(4 * h_mean - 63))
+
+ s_l = 1 + (0.015 * (l_mean - 50) ** 2) / sqrt(20 + (l_mean - 50) ** 2)
+ s_c = 1 + 0.045 * c_mean
+ s_h = 1 + 0.015 * c_mean * t_x
+ r_t = -2 * g_x * sin(abs(60 * exp(-1 * abs((delta_h - 275) / 25) ** 2)))
+
+ delta_l = delta_l / (k_l * s_l)
+ delta_c = delta_c / (k_c * s_c)
+ delta_h = delta_h / (k_h * s_h)
+
+ return delta_l ** 2 + delta_c ** 2 + delta_h ** 2 + r_t * delta_c * delta_h
+
+
+COLOR_DISTANCE_ALGORITHMS = {'rgb': dist_rgb,
+ 'rgb-weighted': dist_rgb_weighted,
+ 'cie76': dist_cie76,
+ 'cie94': dist_cie94,
+ 'cie2000': dist_cie2000}
diff --git a/third_party/python/blessed/blessed/color.pyi b/third_party/python/blessed/blessed/color.pyi
new file mode 100644
index 0000000000..ece82e3e98
--- /dev/null
+++ b/third_party/python/blessed/blessed/color.pyi
@@ -0,0 +1,17 @@
+# std imports
+from typing import Dict, Tuple, Callable
+
+_RGB = Tuple[int, int, int]
+
+def rgb_to_xyz(red: int, green: int, blue: int) -> Tuple[float, float, float]: ...
+def xyz_to_lab(
+ x_val: float, y_val: float, z_val: float
+) -> Tuple[float, float, float]: ...
+def rgb_to_lab(red: int, green: int, blue: int) -> Tuple[float, float, float]: ...
+def dist_rgb(rgb1: _RGB, rgb2: _RGB) -> float: ...
+def dist_rgb_weighted(rgb1: _RGB, rgb2: _RGB) -> float: ...
+def dist_cie76(rgb1: _RGB, rgb2: _RGB) -> float: ...
+def dist_cie94(rgb1: _RGB, rgb2: _RGB) -> float: ...
+def dist_cie2000(rgb1: _RGB, rgb2: _RGB) -> float: ...
+
+COLOR_DISTANCE_ALGORITHMS: Dict[str, Callable[[_RGB, _RGB], float]]
diff --git a/third_party/python/blessed/blessed/colorspace.py b/third_party/python/blessed/blessed/colorspace.py
new file mode 100644
index 0000000000..36fe646b35
--- /dev/null
+++ b/third_party/python/blessed/blessed/colorspace.py
@@ -0,0 +1,973 @@
+"""
+Color reference data.
+
+References,
+
+- https://github.com/freedesktop/xorg-rgb/blob/master/rgb.txt
+- https://github.com/ThomasDickey/xterm-snapshots/blob/master/256colres.h
+- https://github.com/ThomasDickey/xterm-snapshots/blob/master/XTerm-col.ad
+- https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
+- https://gist.github.com/XVilka/8346728
+- https://devblogs.microsoft.com/commandline/24-bit-color-in-the-windows-console/
+- http://jdebp.uk/Softwares/nosh/guide/TerminalCapabilities.html
+"""
+# std imports
+import collections
+
+__all__ = (
+ 'CGA_COLORS',
+ 'RGBColor',
+ 'RGB_256TABLE',
+ 'X11_COLORNAMES_TO_RGB',
+)
+
+CGA_COLORS = set(
+ ('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'))
+
+
+class RGBColor(collections.namedtuple("RGBColor", ["red", "green", "blue"])):
+ """Named tuple for an RGB color definition."""
+
+ def __str__(self):
+ return '#{0:02x}{1:02x}{2:02x}'.format(*self)
+
+
+#: X11 Color names to (XTerm-defined) RGB values from xorg-rgb/rgb.txt
+X11_COLORNAMES_TO_RGB = {
+ 'aliceblue': RGBColor(240, 248, 255),
+ 'antiquewhite': RGBColor(250, 235, 215),
+ 'antiquewhite1': RGBColor(255, 239, 219),
+ 'antiquewhite2': RGBColor(238, 223, 204),
+ 'antiquewhite3': RGBColor(205, 192, 176),
+ 'antiquewhite4': RGBColor(139, 131, 120),
+ 'aqua': RGBColor(0, 255, 255),
+ 'aquamarine': RGBColor(127, 255, 212),
+ 'aquamarine1': RGBColor(127, 255, 212),
+ 'aquamarine2': RGBColor(118, 238, 198),
+ 'aquamarine3': RGBColor(102, 205, 170),
+ 'aquamarine4': RGBColor(69, 139, 116),
+ 'azure': RGBColor(240, 255, 255),
+ 'azure1': RGBColor(240, 255, 255),
+ 'azure2': RGBColor(224, 238, 238),
+ 'azure3': RGBColor(193, 205, 205),
+ 'azure4': RGBColor(131, 139, 139),
+ 'beige': RGBColor(245, 245, 220),
+ 'bisque': RGBColor(255, 228, 196),
+ 'bisque1': RGBColor(255, 228, 196),
+ 'bisque2': RGBColor(238, 213, 183),
+ 'bisque3': RGBColor(205, 183, 158),
+ 'bisque4': RGBColor(139, 125, 107),
+ 'black': RGBColor(0, 0, 0),
+ 'blanchedalmond': RGBColor(255, 235, 205),
+ 'blue': RGBColor(0, 0, 255),
+ 'blue1': RGBColor(0, 0, 255),
+ 'blue2': RGBColor(0, 0, 238),
+ 'blue3': RGBColor(0, 0, 205),
+ 'blue4': RGBColor(0, 0, 139),
+ 'blueviolet': RGBColor(138, 43, 226),
+ 'brown': RGBColor(165, 42, 42),
+ 'brown1': RGBColor(255, 64, 64),
+ 'brown2': RGBColor(238, 59, 59),
+ 'brown3': RGBColor(205, 51, 51),
+ 'brown4': RGBColor(139, 35, 35),
+ 'burlywood': RGBColor(222, 184, 135),
+ 'burlywood1': RGBColor(255, 211, 155),
+ 'burlywood2': RGBColor(238, 197, 145),
+ 'burlywood3': RGBColor(205, 170, 125),
+ 'burlywood4': RGBColor(139, 115, 85),
+ 'cadetblue': RGBColor(95, 158, 160),
+ 'cadetblue1': RGBColor(152, 245, 255),
+ 'cadetblue2': RGBColor(142, 229, 238),
+ 'cadetblue3': RGBColor(122, 197, 205),
+ 'cadetblue4': RGBColor(83, 134, 139),
+ 'chartreuse': RGBColor(127, 255, 0),
+ 'chartreuse1': RGBColor(127, 255, 0),
+ 'chartreuse2': RGBColor(118, 238, 0),
+ 'chartreuse3': RGBColor(102, 205, 0),
+ 'chartreuse4': RGBColor(69, 139, 0),
+ 'chocolate': RGBColor(210, 105, 30),
+ 'chocolate1': RGBColor(255, 127, 36),
+ 'chocolate2': RGBColor(238, 118, 33),
+ 'chocolate3': RGBColor(205, 102, 29),
+ 'chocolate4': RGBColor(139, 69, 19),
+ 'coral': RGBColor(255, 127, 80),
+ 'coral1': RGBColor(255, 114, 86),
+ 'coral2': RGBColor(238, 106, 80),
+ 'coral3': RGBColor(205, 91, 69),
+ 'coral4': RGBColor(139, 62, 47),
+ 'cornflowerblue': RGBColor(100, 149, 237),
+ 'cornsilk': RGBColor(255, 248, 220),
+ 'cornsilk1': RGBColor(255, 248, 220),
+ 'cornsilk2': RGBColor(238, 232, 205),
+ 'cornsilk3': RGBColor(205, 200, 177),
+ 'cornsilk4': RGBColor(139, 136, 120),
+ 'crimson': RGBColor(220, 20, 60),
+ 'cyan': RGBColor(0, 255, 255),
+ 'cyan1': RGBColor(0, 255, 255),
+ 'cyan2': RGBColor(0, 238, 238),
+ 'cyan3': RGBColor(0, 205, 205),
+ 'cyan4': RGBColor(0, 139, 139),
+ 'darkblue': RGBColor(0, 0, 139),
+ 'darkcyan': RGBColor(0, 139, 139),
+ 'darkgoldenrod': RGBColor(184, 134, 11),
+ 'darkgoldenrod1': RGBColor(255, 185, 15),
+ 'darkgoldenrod2': RGBColor(238, 173, 14),
+ 'darkgoldenrod3': RGBColor(205, 149, 12),
+ 'darkgoldenrod4': RGBColor(139, 101, 8),
+ 'darkgray': RGBColor(169, 169, 169),
+ 'darkgreen': RGBColor(0, 100, 0),
+ 'darkgrey': RGBColor(169, 169, 169),
+ 'darkkhaki': RGBColor(189, 183, 107),
+ 'darkmagenta': RGBColor(139, 0, 139),
+ 'darkolivegreen': RGBColor(85, 107, 47),
+ 'darkolivegreen1': RGBColor(202, 255, 112),
+ 'darkolivegreen2': RGBColor(188, 238, 104),
+ 'darkolivegreen3': RGBColor(162, 205, 90),
+ 'darkolivegreen4': RGBColor(110, 139, 61),
+ 'darkorange': RGBColor(255, 140, 0),
+ 'darkorange1': RGBColor(255, 127, 0),
+ 'darkorange2': RGBColor(238, 118, 0),
+ 'darkorange3': RGBColor(205, 102, 0),
+ 'darkorange4': RGBColor(139, 69, 0),
+ 'darkorchid': RGBColor(153, 50, 204),
+ 'darkorchid1': RGBColor(191, 62, 255),
+ 'darkorchid2': RGBColor(178, 58, 238),
+ 'darkorchid3': RGBColor(154, 50, 205),
+ 'darkorchid4': RGBColor(104, 34, 139),
+ 'darkred': RGBColor(139, 0, 0),
+ 'darksalmon': RGBColor(233, 150, 122),
+ 'darkseagreen': RGBColor(143, 188, 143),
+ 'darkseagreen1': RGBColor(193, 255, 193),
+ 'darkseagreen2': RGBColor(180, 238, 180),
+ 'darkseagreen3': RGBColor(155, 205, 155),
+ 'darkseagreen4': RGBColor(105, 139, 105),
+ 'darkslateblue': RGBColor(72, 61, 139),
+ 'darkslategray': RGBColor(47, 79, 79),
+ 'darkslategray1': RGBColor(151, 255, 255),
+ 'darkslategray2': RGBColor(141, 238, 238),
+ 'darkslategray3': RGBColor(121, 205, 205),
+ 'darkslategray4': RGBColor(82, 139, 139),
+ 'darkslategrey': RGBColor(47, 79, 79),
+ 'darkturquoise': RGBColor(0, 206, 209),
+ 'darkviolet': RGBColor(148, 0, 211),
+ 'deeppink': RGBColor(255, 20, 147),
+ 'deeppink1': RGBColor(255, 20, 147),
+ 'deeppink2': RGBColor(238, 18, 137),
+ 'deeppink3': RGBColor(205, 16, 118),
+ 'deeppink4': RGBColor(139, 10, 80),
+ 'deepskyblue': RGBColor(0, 191, 255),
+ 'deepskyblue1': RGBColor(0, 191, 255),
+ 'deepskyblue2': RGBColor(0, 178, 238),
+ 'deepskyblue3': RGBColor(0, 154, 205),
+ 'deepskyblue4': RGBColor(0, 104, 139),
+ 'dimgray': RGBColor(105, 105, 105),
+ 'dimgrey': RGBColor(105, 105, 105),
+ 'dodgerblue': RGBColor(30, 144, 255),
+ 'dodgerblue1': RGBColor(30, 144, 255),
+ 'dodgerblue2': RGBColor(28, 134, 238),
+ 'dodgerblue3': RGBColor(24, 116, 205),
+ 'dodgerblue4': RGBColor(16, 78, 139),
+ 'firebrick': RGBColor(178, 34, 34),
+ 'firebrick1': RGBColor(255, 48, 48),
+ 'firebrick2': RGBColor(238, 44, 44),
+ 'firebrick3': RGBColor(205, 38, 38),
+ 'firebrick4': RGBColor(139, 26, 26),
+ 'floralwhite': RGBColor(255, 250, 240),
+ 'forestgreen': RGBColor(34, 139, 34),
+ 'fuchsia': RGBColor(255, 0, 255),
+ 'gainsboro': RGBColor(220, 220, 220),
+ 'ghostwhite': RGBColor(248, 248, 255),
+ 'gold': RGBColor(255, 215, 0),
+ 'gold1': RGBColor(255, 215, 0),
+ 'gold2': RGBColor(238, 201, 0),
+ 'gold3': RGBColor(205, 173, 0),
+ 'gold4': RGBColor(139, 117, 0),
+ 'goldenrod': RGBColor(218, 165, 32),
+ 'goldenrod1': RGBColor(255, 193, 37),
+ 'goldenrod2': RGBColor(238, 180, 34),
+ 'goldenrod3': RGBColor(205, 155, 29),
+ 'goldenrod4': RGBColor(139, 105, 20),
+ 'gray': RGBColor(190, 190, 190),
+ 'gray0': RGBColor(0, 0, 0),
+ 'gray1': RGBColor(3, 3, 3),
+ 'gray10': RGBColor(26, 26, 26),
+ 'gray100': RGBColor(255, 255, 255),
+ 'gray11': RGBColor(28, 28, 28),
+ 'gray12': RGBColor(31, 31, 31),
+ 'gray13': RGBColor(33, 33, 33),
+ 'gray14': RGBColor(36, 36, 36),
+ 'gray15': RGBColor(38, 38, 38),
+ 'gray16': RGBColor(41, 41, 41),
+ 'gray17': RGBColor(43, 43, 43),
+ 'gray18': RGBColor(46, 46, 46),
+ 'gray19': RGBColor(48, 48, 48),
+ 'gray2': RGBColor(5, 5, 5),
+ 'gray20': RGBColor(51, 51, 51),
+ 'gray21': RGBColor(54, 54, 54),
+ 'gray22': RGBColor(56, 56, 56),
+ 'gray23': RGBColor(59, 59, 59),
+ 'gray24': RGBColor(61, 61, 61),
+ 'gray25': RGBColor(64, 64, 64),
+ 'gray26': RGBColor(66, 66, 66),
+ 'gray27': RGBColor(69, 69, 69),
+ 'gray28': RGBColor(71, 71, 71),
+ 'gray29': RGBColor(74, 74, 74),
+ 'gray3': RGBColor(8, 8, 8),
+ 'gray30': RGBColor(77, 77, 77),
+ 'gray31': RGBColor(79, 79, 79),
+ 'gray32': RGBColor(82, 82, 82),
+ 'gray33': RGBColor(84, 84, 84),
+ 'gray34': RGBColor(87, 87, 87),
+ 'gray35': RGBColor(89, 89, 89),
+ 'gray36': RGBColor(92, 92, 92),
+ 'gray37': RGBColor(94, 94, 94),
+ 'gray38': RGBColor(97, 97, 97),
+ 'gray39': RGBColor(99, 99, 99),
+ 'gray4': RGBColor(10, 10, 10),
+ 'gray40': RGBColor(102, 102, 102),
+ 'gray41': RGBColor(105, 105, 105),
+ 'gray42': RGBColor(107, 107, 107),
+ 'gray43': RGBColor(110, 110, 110),
+ 'gray44': RGBColor(112, 112, 112),
+ 'gray45': RGBColor(115, 115, 115),
+ 'gray46': RGBColor(117, 117, 117),
+ 'gray47': RGBColor(120, 120, 120),
+ 'gray48': RGBColor(122, 122, 122),
+ 'gray49': RGBColor(125, 125, 125),
+ 'gray5': RGBColor(13, 13, 13),
+ 'gray50': RGBColor(127, 127, 127),
+ 'gray51': RGBColor(130, 130, 130),
+ 'gray52': RGBColor(133, 133, 133),
+ 'gray53': RGBColor(135, 135, 135),
+ 'gray54': RGBColor(138, 138, 138),
+ 'gray55': RGBColor(140, 140, 140),
+ 'gray56': RGBColor(143, 143, 143),
+ 'gray57': RGBColor(145, 145, 145),
+ 'gray58': RGBColor(148, 148, 148),
+ 'gray59': RGBColor(150, 150, 150),
+ 'gray6': RGBColor(15, 15, 15),
+ 'gray60': RGBColor(153, 153, 153),
+ 'gray61': RGBColor(156, 156, 156),
+ 'gray62': RGBColor(158, 158, 158),
+ 'gray63': RGBColor(161, 161, 161),
+ 'gray64': RGBColor(163, 163, 163),
+ 'gray65': RGBColor(166, 166, 166),
+ 'gray66': RGBColor(168, 168, 168),
+ 'gray67': RGBColor(171, 171, 171),
+ 'gray68': RGBColor(173, 173, 173),
+ 'gray69': RGBColor(176, 176, 176),
+ 'gray7': RGBColor(18, 18, 18),
+ 'gray70': RGBColor(179, 179, 179),
+ 'gray71': RGBColor(181, 181, 181),
+ 'gray72': RGBColor(184, 184, 184),
+ 'gray73': RGBColor(186, 186, 186),
+ 'gray74': RGBColor(189, 189, 189),
+ 'gray75': RGBColor(191, 191, 191),
+ 'gray76': RGBColor(194, 194, 194),
+ 'gray77': RGBColor(196, 196, 196),
+ 'gray78': RGBColor(199, 199, 199),
+ 'gray79': RGBColor(201, 201, 201),
+ 'gray8': RGBColor(20, 20, 20),
+ 'gray80': RGBColor(204, 204, 204),
+ 'gray81': RGBColor(207, 207, 207),
+ 'gray82': RGBColor(209, 209, 209),
+ 'gray83': RGBColor(212, 212, 212),
+ 'gray84': RGBColor(214, 214, 214),
+ 'gray85': RGBColor(217, 217, 217),
+ 'gray86': RGBColor(219, 219, 219),
+ 'gray87': RGBColor(222, 222, 222),
+ 'gray88': RGBColor(224, 224, 224),
+ 'gray89': RGBColor(227, 227, 227),
+ 'gray9': RGBColor(23, 23, 23),
+ 'gray90': RGBColor(229, 229, 229),
+ 'gray91': RGBColor(232, 232, 232),
+ 'gray92': RGBColor(235, 235, 235),
+ 'gray93': RGBColor(237, 237, 237),
+ 'gray94': RGBColor(240, 240, 240),
+ 'gray95': RGBColor(242, 242, 242),
+ 'gray96': RGBColor(245, 245, 245),
+ 'gray97': RGBColor(247, 247, 247),
+ 'gray98': RGBColor(250, 250, 250),
+ 'gray99': RGBColor(252, 252, 252),
+ 'green': RGBColor(0, 255, 0),
+ 'green1': RGBColor(0, 255, 0),
+ 'green2': RGBColor(0, 238, 0),
+ 'green3': RGBColor(0, 205, 0),
+ 'green4': RGBColor(0, 139, 0),
+ 'greenyellow': RGBColor(173, 255, 47),
+ 'grey': RGBColor(190, 190, 190),
+ 'grey0': RGBColor(0, 0, 0),
+ 'grey1': RGBColor(3, 3, 3),
+ 'grey10': RGBColor(26, 26, 26),
+ 'grey100': RGBColor(255, 255, 255),
+ 'grey11': RGBColor(28, 28, 28),
+ 'grey12': RGBColor(31, 31, 31),
+ 'grey13': RGBColor(33, 33, 33),
+ 'grey14': RGBColor(36, 36, 36),
+ 'grey15': RGBColor(38, 38, 38),
+ 'grey16': RGBColor(41, 41, 41),
+ 'grey17': RGBColor(43, 43, 43),
+ 'grey18': RGBColor(46, 46, 46),
+ 'grey19': RGBColor(48, 48, 48),
+ 'grey2': RGBColor(5, 5, 5),
+ 'grey20': RGBColor(51, 51, 51),
+ 'grey21': RGBColor(54, 54, 54),
+ 'grey22': RGBColor(56, 56, 56),
+ 'grey23': RGBColor(59, 59, 59),
+ 'grey24': RGBColor(61, 61, 61),
+ 'grey25': RGBColor(64, 64, 64),
+ 'grey26': RGBColor(66, 66, 66),
+ 'grey27': RGBColor(69, 69, 69),
+ 'grey28': RGBColor(71, 71, 71),
+ 'grey29': RGBColor(74, 74, 74),
+ 'grey3': RGBColor(8, 8, 8),
+ 'grey30': RGBColor(77, 77, 77),
+ 'grey31': RGBColor(79, 79, 79),
+ 'grey32': RGBColor(82, 82, 82),
+ 'grey33': RGBColor(84, 84, 84),
+ 'grey34': RGBColor(87, 87, 87),
+ 'grey35': RGBColor(89, 89, 89),
+ 'grey36': RGBColor(92, 92, 92),
+ 'grey37': RGBColor(94, 94, 94),
+ 'grey38': RGBColor(97, 97, 97),
+ 'grey39': RGBColor(99, 99, 99),
+ 'grey4': RGBColor(10, 10, 10),
+ 'grey40': RGBColor(102, 102, 102),
+ 'grey41': RGBColor(105, 105, 105),
+ 'grey42': RGBColor(107, 107, 107),
+ 'grey43': RGBColor(110, 110, 110),
+ 'grey44': RGBColor(112, 112, 112),
+ 'grey45': RGBColor(115, 115, 115),
+ 'grey46': RGBColor(117, 117, 117),
+ 'grey47': RGBColor(120, 120, 120),
+ 'grey48': RGBColor(122, 122, 122),
+ 'grey49': RGBColor(125, 125, 125),
+ 'grey5': RGBColor(13, 13, 13),
+ 'grey50': RGBColor(127, 127, 127),
+ 'grey51': RGBColor(130, 130, 130),
+ 'grey52': RGBColor(133, 133, 133),
+ 'grey53': RGBColor(135, 135, 135),
+ 'grey54': RGBColor(138, 138, 138),
+ 'grey55': RGBColor(140, 140, 140),
+ 'grey56': RGBColor(143, 143, 143),
+ 'grey57': RGBColor(145, 145, 145),
+ 'grey58': RGBColor(148, 148, 148),
+ 'grey59': RGBColor(150, 150, 150),
+ 'grey6': RGBColor(15, 15, 15),
+ 'grey60': RGBColor(153, 153, 153),
+ 'grey61': RGBColor(156, 156, 156),
+ 'grey62': RGBColor(158, 158, 158),
+ 'grey63': RGBColor(161, 161, 161),
+ 'grey64': RGBColor(163, 163, 163),
+ 'grey65': RGBColor(166, 166, 166),
+ 'grey66': RGBColor(168, 168, 168),
+ 'grey67': RGBColor(171, 171, 171),
+ 'grey68': RGBColor(173, 173, 173),
+ 'grey69': RGBColor(176, 176, 176),
+ 'grey7': RGBColor(18, 18, 18),
+ 'grey70': RGBColor(179, 179, 179),
+ 'grey71': RGBColor(181, 181, 181),
+ 'grey72': RGBColor(184, 184, 184),
+ 'grey73': RGBColor(186, 186, 186),
+ 'grey74': RGBColor(189, 189, 189),
+ 'grey75': RGBColor(191, 191, 191),
+ 'grey76': RGBColor(194, 194, 194),
+ 'grey77': RGBColor(196, 196, 196),
+ 'grey78': RGBColor(199, 199, 199),
+ 'grey79': RGBColor(201, 201, 201),
+ 'grey8': RGBColor(20, 20, 20),
+ 'grey80': RGBColor(204, 204, 204),
+ 'grey81': RGBColor(207, 207, 207),
+ 'grey82': RGBColor(209, 209, 209),
+ 'grey83': RGBColor(212, 212, 212),
+ 'grey84': RGBColor(214, 214, 214),
+ 'grey85': RGBColor(217, 217, 217),
+ 'grey86': RGBColor(219, 219, 219),
+ 'grey87': RGBColor(222, 222, 222),
+ 'grey88': RGBColor(224, 224, 224),
+ 'grey89': RGBColor(227, 227, 227),
+ 'grey9': RGBColor(23, 23, 23),
+ 'grey90': RGBColor(229, 229, 229),
+ 'grey91': RGBColor(232, 232, 232),
+ 'grey92': RGBColor(235, 235, 235),
+ 'grey93': RGBColor(237, 237, 237),
+ 'grey94': RGBColor(240, 240, 240),
+ 'grey95': RGBColor(242, 242, 242),
+ 'grey96': RGBColor(245, 245, 245),
+ 'grey97': RGBColor(247, 247, 247),
+ 'grey98': RGBColor(250, 250, 250),
+ 'grey99': RGBColor(252, 252, 252),
+ 'honeydew': RGBColor(240, 255, 240),
+ 'honeydew1': RGBColor(240, 255, 240),
+ 'honeydew2': RGBColor(224, 238, 224),
+ 'honeydew3': RGBColor(193, 205, 193),
+ 'honeydew4': RGBColor(131, 139, 131),
+ 'hotpink': RGBColor(255, 105, 180),
+ 'hotpink1': RGBColor(255, 110, 180),
+ 'hotpink2': RGBColor(238, 106, 167),
+ 'hotpink3': RGBColor(205, 96, 144),
+ 'hotpink4': RGBColor(139, 58, 98),
+ 'indianred': RGBColor(205, 92, 92),
+ 'indianred1': RGBColor(255, 106, 106),
+ 'indianred2': RGBColor(238, 99, 99),
+ 'indianred3': RGBColor(205, 85, 85),
+ 'indianred4': RGBColor(139, 58, 58),
+ 'indigo': RGBColor(75, 0, 130),
+ 'ivory': RGBColor(255, 255, 240),
+ 'ivory1': RGBColor(255, 255, 240),
+ 'ivory2': RGBColor(238, 238, 224),
+ 'ivory3': RGBColor(205, 205, 193),
+ 'ivory4': RGBColor(139, 139, 131),
+ 'khaki': RGBColor(240, 230, 140),
+ 'khaki1': RGBColor(255, 246, 143),
+ 'khaki2': RGBColor(238, 230, 133),
+ 'khaki3': RGBColor(205, 198, 115),
+ 'khaki4': RGBColor(139, 134, 78),
+ 'lavender': RGBColor(230, 230, 250),
+ 'lavenderblush': RGBColor(255, 240, 245),
+ 'lavenderblush1': RGBColor(255, 240, 245),
+ 'lavenderblush2': RGBColor(238, 224, 229),
+ 'lavenderblush3': RGBColor(205, 193, 197),
+ 'lavenderblush4': RGBColor(139, 131, 134),
+ 'lawngreen': RGBColor(124, 252, 0),
+ 'lemonchiffon': RGBColor(255, 250, 205),
+ 'lemonchiffon1': RGBColor(255, 250, 205),
+ 'lemonchiffon2': RGBColor(238, 233, 191),
+ 'lemonchiffon3': RGBColor(205, 201, 165),
+ 'lemonchiffon4': RGBColor(139, 137, 112),
+ 'lightblue': RGBColor(173, 216, 230),
+ 'lightblue1': RGBColor(191, 239, 255),
+ 'lightblue2': RGBColor(178, 223, 238),
+ 'lightblue3': RGBColor(154, 192, 205),
+ 'lightblue4': RGBColor(104, 131, 139),
+ 'lightcoral': RGBColor(240, 128, 128),
+ 'lightcyan': RGBColor(224, 255, 255),
+ 'lightcyan1': RGBColor(224, 255, 255),
+ 'lightcyan2': RGBColor(209, 238, 238),
+ 'lightcyan3': RGBColor(180, 205, 205),
+ 'lightcyan4': RGBColor(122, 139, 139),
+ 'lightgoldenrod': RGBColor(238, 221, 130),
+ 'lightgoldenrod1': RGBColor(255, 236, 139),
+ 'lightgoldenrod2': RGBColor(238, 220, 130),
+ 'lightgoldenrod3': RGBColor(205, 190, 112),
+ 'lightgoldenrod4': RGBColor(139, 129, 76),
+ 'lightgoldenrodyellow': RGBColor(250, 250, 210),
+ 'lightgray': RGBColor(211, 211, 211),
+ 'lightgreen': RGBColor(144, 238, 144),
+ 'lightgrey': RGBColor(211, 211, 211),
+ 'lightpink': RGBColor(255, 182, 193),
+ 'lightpink1': RGBColor(255, 174, 185),
+ 'lightpink2': RGBColor(238, 162, 173),
+ 'lightpink3': RGBColor(205, 140, 149),
+ 'lightpink4': RGBColor(139, 95, 101),
+ 'lightsalmon': RGBColor(255, 160, 122),
+ 'lightsalmon1': RGBColor(255, 160, 122),
+ 'lightsalmon2': RGBColor(238, 149, 114),
+ 'lightsalmon3': RGBColor(205, 129, 98),
+ 'lightsalmon4': RGBColor(139, 87, 66),
+ 'lightseagreen': RGBColor(32, 178, 170),
+ 'lightskyblue': RGBColor(135, 206, 250),
+ 'lightskyblue1': RGBColor(176, 226, 255),
+ 'lightskyblue2': RGBColor(164, 211, 238),
+ 'lightskyblue3': RGBColor(141, 182, 205),
+ 'lightskyblue4': RGBColor(96, 123, 139),
+ 'lightslateblue': RGBColor(132, 112, 255),
+ 'lightslategray': RGBColor(119, 136, 153),
+ 'lightslategrey': RGBColor(119, 136, 153),
+ 'lightsteelblue': RGBColor(176, 196, 222),
+ 'lightsteelblue1': RGBColor(202, 225, 255),
+ 'lightsteelblue2': RGBColor(188, 210, 238),
+ 'lightsteelblue3': RGBColor(162, 181, 205),
+ 'lightsteelblue4': RGBColor(110, 123, 139),
+ 'lightyellow': RGBColor(255, 255, 224),
+ 'lightyellow1': RGBColor(255, 255, 224),
+ 'lightyellow2': RGBColor(238, 238, 209),
+ 'lightyellow3': RGBColor(205, 205, 180),
+ 'lightyellow4': RGBColor(139, 139, 122),
+ 'lime': RGBColor(0, 255, 0),
+ 'limegreen': RGBColor(50, 205, 50),
+ 'linen': RGBColor(250, 240, 230),
+ 'magenta': RGBColor(255, 0, 255),
+ 'magenta1': RGBColor(255, 0, 255),
+ 'magenta2': RGBColor(238, 0, 238),
+ 'magenta3': RGBColor(205, 0, 205),
+ 'magenta4': RGBColor(139, 0, 139),
+ 'maroon': RGBColor(176, 48, 96),
+ 'maroon1': RGBColor(255, 52, 179),
+ 'maroon2': RGBColor(238, 48, 167),
+ 'maroon3': RGBColor(205, 41, 144),
+ 'maroon4': RGBColor(139, 28, 98),
+ 'mediumaquamarine': RGBColor(102, 205, 170),
+ 'mediumblue': RGBColor(0, 0, 205),
+ 'mediumorchid': RGBColor(186, 85, 211),
+ 'mediumorchid1': RGBColor(224, 102, 255),
+ 'mediumorchid2': RGBColor(209, 95, 238),
+ 'mediumorchid3': RGBColor(180, 82, 205),
+ 'mediumorchid4': RGBColor(122, 55, 139),
+ 'mediumpurple': RGBColor(147, 112, 219),
+ 'mediumpurple1': RGBColor(171, 130, 255),
+ 'mediumpurple2': RGBColor(159, 121, 238),
+ 'mediumpurple3': RGBColor(137, 104, 205),
+ 'mediumpurple4': RGBColor(93, 71, 139),
+ 'mediumseagreen': RGBColor(60, 179, 113),
+ 'mediumslateblue': RGBColor(123, 104, 238),
+ 'mediumspringgreen': RGBColor(0, 250, 154),
+ 'mediumturquoise': RGBColor(72, 209, 204),
+ 'mediumvioletred': RGBColor(199, 21, 133),
+ 'midnightblue': RGBColor(25, 25, 112),
+ 'mintcream': RGBColor(245, 255, 250),
+ 'mistyrose': RGBColor(255, 228, 225),
+ 'mistyrose1': RGBColor(255, 228, 225),
+ 'mistyrose2': RGBColor(238, 213, 210),
+ 'mistyrose3': RGBColor(205, 183, 181),
+ 'mistyrose4': RGBColor(139, 125, 123),
+ 'moccasin': RGBColor(255, 228, 181),
+ 'navajowhite': RGBColor(255, 222, 173),
+ 'navajowhite1': RGBColor(255, 222, 173),
+ 'navajowhite2': RGBColor(238, 207, 161),
+ 'navajowhite3': RGBColor(205, 179, 139),
+ 'navajowhite4': RGBColor(139, 121, 94),
+ 'navy': RGBColor(0, 0, 128),
+ 'navyblue': RGBColor(0, 0, 128),
+ 'oldlace': RGBColor(253, 245, 230),
+ 'olive': RGBColor(128, 128, 0),
+ 'olivedrab': RGBColor(107, 142, 35),
+ 'olivedrab1': RGBColor(192, 255, 62),
+ 'olivedrab2': RGBColor(179, 238, 58),
+ 'olivedrab3': RGBColor(154, 205, 50),
+ 'olivedrab4': RGBColor(105, 139, 34),
+ 'orange': RGBColor(255, 165, 0),
+ 'orange1': RGBColor(255, 165, 0),
+ 'orange2': RGBColor(238, 154, 0),
+ 'orange3': RGBColor(205, 133, 0),
+ 'orange4': RGBColor(139, 90, 0),
+ 'orangered': RGBColor(255, 69, 0),
+ 'orangered1': RGBColor(255, 69, 0),
+ 'orangered2': RGBColor(238, 64, 0),
+ 'orangered3': RGBColor(205, 55, 0),
+ 'orangered4': RGBColor(139, 37, 0),
+ 'orchid': RGBColor(218, 112, 214),
+ 'orchid1': RGBColor(255, 131, 250),
+ 'orchid2': RGBColor(238, 122, 233),
+ 'orchid3': RGBColor(205, 105, 201),
+ 'orchid4': RGBColor(139, 71, 137),
+ 'palegoldenrod': RGBColor(238, 232, 170),
+ 'palegreen': RGBColor(152, 251, 152),
+ 'palegreen1': RGBColor(154, 255, 154),
+ 'palegreen2': RGBColor(144, 238, 144),
+ 'palegreen3': RGBColor(124, 205, 124),
+ 'palegreen4': RGBColor(84, 139, 84),
+ 'paleturquoise': RGBColor(175, 238, 238),
+ 'paleturquoise1': RGBColor(187, 255, 255),
+ 'paleturquoise2': RGBColor(174, 238, 238),
+ 'paleturquoise3': RGBColor(150, 205, 205),
+ 'paleturquoise4': RGBColor(102, 139, 139),
+ 'palevioletred': RGBColor(219, 112, 147),
+ 'palevioletred1': RGBColor(255, 130, 171),
+ 'palevioletred2': RGBColor(238, 121, 159),
+ 'palevioletred3': RGBColor(205, 104, 137),
+ 'palevioletred4': RGBColor(139, 71, 93),
+ 'papayawhip': RGBColor(255, 239, 213),
+ 'peachpuff': RGBColor(255, 218, 185),
+ 'peachpuff1': RGBColor(255, 218, 185),
+ 'peachpuff2': RGBColor(238, 203, 173),
+ 'peachpuff3': RGBColor(205, 175, 149),
+ 'peachpuff4': RGBColor(139, 119, 101),
+ 'peru': RGBColor(205, 133, 63),
+ 'pink': RGBColor(255, 192, 203),
+ 'pink1': RGBColor(255, 181, 197),
+ 'pink2': RGBColor(238, 169, 184),
+ 'pink3': RGBColor(205, 145, 158),
+ 'pink4': RGBColor(139, 99, 108),
+ 'plum': RGBColor(221, 160, 221),
+ 'plum1': RGBColor(255, 187, 255),
+ 'plum2': RGBColor(238, 174, 238),
+ 'plum3': RGBColor(205, 150, 205),
+ 'plum4': RGBColor(139, 102, 139),
+ 'powderblue': RGBColor(176, 224, 230),
+ 'purple': RGBColor(160, 32, 240),
+ 'purple1': RGBColor(155, 48, 255),
+ 'purple2': RGBColor(145, 44, 238),
+ 'purple3': RGBColor(125, 38, 205),
+ 'purple4': RGBColor(85, 26, 139),
+ 'rebeccapurple': RGBColor(102, 51, 153),
+ 'red': RGBColor(255, 0, 0),
+ 'red1': RGBColor(255, 0, 0),
+ 'red2': RGBColor(238, 0, 0),
+ 'red3': RGBColor(205, 0, 0),
+ 'red4': RGBColor(139, 0, 0),
+ 'rosybrown': RGBColor(188, 143, 143),
+ 'rosybrown1': RGBColor(255, 193, 193),
+ 'rosybrown2': RGBColor(238, 180, 180),
+ 'rosybrown3': RGBColor(205, 155, 155),
+ 'rosybrown4': RGBColor(139, 105, 105),
+ 'royalblue': RGBColor(65, 105, 225),
+ 'royalblue1': RGBColor(72, 118, 255),
+ 'royalblue2': RGBColor(67, 110, 238),
+ 'royalblue3': RGBColor(58, 95, 205),
+ 'royalblue4': RGBColor(39, 64, 139),
+ 'saddlebrown': RGBColor(139, 69, 19),
+ 'salmon': RGBColor(250, 128, 114),
+ 'salmon1': RGBColor(255, 140, 105),
+ 'salmon2': RGBColor(238, 130, 98),
+ 'salmon3': RGBColor(205, 112, 84),
+ 'salmon4': RGBColor(139, 76, 57),
+ 'sandybrown': RGBColor(244, 164, 96),
+ 'seagreen': RGBColor(46, 139, 87),
+ 'seagreen1': RGBColor(84, 255, 159),
+ 'seagreen2': RGBColor(78, 238, 148),
+ 'seagreen3': RGBColor(67, 205, 128),
+ 'seagreen4': RGBColor(46, 139, 87),
+ 'seashell': RGBColor(255, 245, 238),
+ 'seashell1': RGBColor(255, 245, 238),
+ 'seashell2': RGBColor(238, 229, 222),
+ 'seashell3': RGBColor(205, 197, 191),
+ 'seashell4': RGBColor(139, 134, 130),
+ 'sienna': RGBColor(160, 82, 45),
+ 'sienna1': RGBColor(255, 130, 71),
+ 'sienna2': RGBColor(238, 121, 66),
+ 'sienna3': RGBColor(205, 104, 57),
+ 'sienna4': RGBColor(139, 71, 38),
+ 'silver': RGBColor(192, 192, 192),
+ 'skyblue': RGBColor(135, 206, 235),
+ 'skyblue1': RGBColor(135, 206, 255),
+ 'skyblue2': RGBColor(126, 192, 238),
+ 'skyblue3': RGBColor(108, 166, 205),
+ 'skyblue4': RGBColor(74, 112, 139),
+ 'slateblue': RGBColor(106, 90, 205),
+ 'slateblue1': RGBColor(131, 111, 255),
+ 'slateblue2': RGBColor(122, 103, 238),
+ 'slateblue3': RGBColor(105, 89, 205),
+ 'slateblue4': RGBColor(71, 60, 139),
+ 'slategray': RGBColor(112, 128, 144),
+ 'slategray1': RGBColor(198, 226, 255),
+ 'slategray2': RGBColor(185, 211, 238),
+ 'slategray3': RGBColor(159, 182, 205),
+ 'slategray4': RGBColor(108, 123, 139),
+ 'slategrey': RGBColor(112, 128, 144),
+ 'snow': RGBColor(255, 250, 250),
+ 'snow1': RGBColor(255, 250, 250),
+ 'snow2': RGBColor(238, 233, 233),
+ 'snow3': RGBColor(205, 201, 201),
+ 'snow4': RGBColor(139, 137, 137),
+ 'springgreen': RGBColor(0, 255, 127),
+ 'springgreen1': RGBColor(0, 255, 127),
+ 'springgreen2': RGBColor(0, 238, 118),
+ 'springgreen3': RGBColor(0, 205, 102),
+ 'springgreen4': RGBColor(0, 139, 69),
+ 'steelblue': RGBColor(70, 130, 180),
+ 'steelblue1': RGBColor(99, 184, 255),
+ 'steelblue2': RGBColor(92, 172, 238),
+ 'steelblue3': RGBColor(79, 148, 205),
+ 'steelblue4': RGBColor(54, 100, 139),
+ 'tan': RGBColor(210, 180, 140),
+ 'tan1': RGBColor(255, 165, 79),
+ 'tan2': RGBColor(238, 154, 73),
+ 'tan3': RGBColor(205, 133, 63),
+ 'tan4': RGBColor(139, 90, 43),
+ 'teal': RGBColor(0, 128, 128),
+ 'thistle': RGBColor(216, 191, 216),
+ 'thistle1': RGBColor(255, 225, 255),
+ 'thistle2': RGBColor(238, 210, 238),
+ 'thistle3': RGBColor(205, 181, 205),
+ 'thistle4': RGBColor(139, 123, 139),
+ 'tomato': RGBColor(255, 99, 71),
+ 'tomato1': RGBColor(255, 99, 71),
+ 'tomato2': RGBColor(238, 92, 66),
+ 'tomato3': RGBColor(205, 79, 57),
+ 'tomato4': RGBColor(139, 54, 38),
+ 'turquoise': RGBColor(64, 224, 208),
+ 'turquoise1': RGBColor(0, 245, 255),
+ 'turquoise2': RGBColor(0, 229, 238),
+ 'turquoise3': RGBColor(0, 197, 205),
+ 'turquoise4': RGBColor(0, 134, 139),
+ 'violet': RGBColor(238, 130, 238),
+ 'violetred': RGBColor(208, 32, 144),
+ 'violetred1': RGBColor(255, 62, 150),
+ 'violetred2': RGBColor(238, 58, 140),
+ 'violetred3': RGBColor(205, 50, 120),
+ 'violetred4': RGBColor(139, 34, 82),
+ 'webgray': RGBColor(128, 128, 128),
+ 'webgreen': RGBColor(0, 128, 0),
+ 'webgrey': RGBColor(128, 128, 128),
+ 'webmaroon': RGBColor(128, 0, 0),
+ 'webpurple': RGBColor(128, 0, 128),
+ 'wheat': RGBColor(245, 222, 179),
+ 'wheat1': RGBColor(255, 231, 186),
+ 'wheat2': RGBColor(238, 216, 174),
+ 'wheat3': RGBColor(205, 186, 150),
+ 'wheat4': RGBColor(139, 126, 102),
+ 'white': RGBColor(255, 255, 255),
+ 'whitesmoke': RGBColor(245, 245, 245),
+ 'x11gray': RGBColor(190, 190, 190),
+ 'x11green': RGBColor(0, 255, 0),
+ 'x11grey': RGBColor(190, 190, 190),
+ 'x11maroon': RGBColor(176, 48, 96),
+ 'x11purple': RGBColor(160, 32, 240),
+ 'yellow': RGBColor(255, 255, 0),
+ 'yellow1': RGBColor(255, 255, 0),
+ 'yellow2': RGBColor(238, 238, 0),
+ 'yellow3': RGBColor(205, 205, 0),
+ 'yellow4': RGBColor(139, 139, 0),
+ 'yellowgreen': RGBColor(154, 205, 50)
+}
+
+#: Curses color indices of 8, 16, and 256-color terminals
+RGB_256TABLE = (
+ RGBColor(0, 0, 0),
+ RGBColor(205, 0, 0),
+ RGBColor(0, 205, 0),
+ RGBColor(205, 205, 0),
+ RGBColor(0, 0, 238),
+ RGBColor(205, 0, 205),
+ RGBColor(0, 205, 205),
+ RGBColor(229, 229, 229),
+ RGBColor(127, 127, 127),
+ RGBColor(255, 0, 0),
+ RGBColor(0, 255, 0),
+ RGBColor(255, 255, 0),
+ RGBColor(92, 92, 255),
+ RGBColor(255, 0, 255),
+ RGBColor(0, 255, 255),
+ RGBColor(255, 255, 255),
+ RGBColor(0, 0, 0),
+ RGBColor(0, 0, 95),
+ RGBColor(0, 0, 135),
+ RGBColor(0, 0, 175),
+ RGBColor(0, 0, 215),
+ RGBColor(0, 0, 255),
+ RGBColor(0, 95, 0),
+ RGBColor(0, 95, 95),
+ RGBColor(0, 95, 135),
+ RGBColor(0, 95, 175),
+ RGBColor(0, 95, 215),
+ RGBColor(0, 95, 255),
+ RGBColor(0, 135, 0),
+ RGBColor(0, 135, 95),
+ RGBColor(0, 135, 135),
+ RGBColor(0, 135, 175),
+ RGBColor(0, 135, 215),
+ RGBColor(0, 135, 255),
+ RGBColor(0, 175, 0),
+ RGBColor(0, 175, 95),
+ RGBColor(0, 175, 135),
+ RGBColor(0, 175, 175),
+ RGBColor(0, 175, 215),
+ RGBColor(0, 175, 255),
+ RGBColor(0, 215, 0),
+ RGBColor(0, 215, 95),
+ RGBColor(0, 215, 135),
+ RGBColor(0, 215, 175),
+ RGBColor(0, 215, 215),
+ RGBColor(0, 215, 255),
+ RGBColor(0, 255, 0),
+ RGBColor(0, 255, 95),
+ RGBColor(0, 255, 135),
+ RGBColor(0, 255, 175),
+ RGBColor(0, 255, 215),
+ RGBColor(0, 255, 255),
+ RGBColor(95, 0, 0),
+ RGBColor(95, 0, 95),
+ RGBColor(95, 0, 135),
+ RGBColor(95, 0, 175),
+ RGBColor(95, 0, 215),
+ RGBColor(95, 0, 255),
+ RGBColor(95, 95, 0),
+ RGBColor(95, 95, 95),
+ RGBColor(95, 95, 135),
+ RGBColor(95, 95, 175),
+ RGBColor(95, 95, 215),
+ RGBColor(95, 95, 255),
+ RGBColor(95, 135, 0),
+ RGBColor(95, 135, 95),
+ RGBColor(95, 135, 135),
+ RGBColor(95, 135, 175),
+ RGBColor(95, 135, 215),
+ RGBColor(95, 135, 255),
+ RGBColor(95, 175, 0),
+ RGBColor(95, 175, 95),
+ RGBColor(95, 175, 135),
+ RGBColor(95, 175, 175),
+ RGBColor(95, 175, 215),
+ RGBColor(95, 175, 255),
+ RGBColor(95, 215, 0),
+ RGBColor(95, 215, 95),
+ RGBColor(95, 215, 135),
+ RGBColor(95, 215, 175),
+ RGBColor(95, 215, 215),
+ RGBColor(95, 215, 255),
+ RGBColor(95, 255, 0),
+ RGBColor(95, 255, 95),
+ RGBColor(95, 255, 135),
+ RGBColor(95, 255, 175),
+ RGBColor(95, 255, 215),
+ RGBColor(95, 255, 255),
+ RGBColor(135, 0, 0),
+ RGBColor(135, 0, 95),
+ RGBColor(135, 0, 135),
+ RGBColor(135, 0, 175),
+ RGBColor(135, 0, 215),
+ RGBColor(135, 0, 255),
+ RGBColor(135, 95, 0),
+ RGBColor(135, 95, 95),
+ RGBColor(135, 95, 135),
+ RGBColor(135, 95, 175),
+ RGBColor(135, 95, 215),
+ RGBColor(135, 95, 255),
+ RGBColor(135, 135, 0),
+ RGBColor(135, 135, 95),
+ RGBColor(135, 135, 135),
+ RGBColor(135, 135, 175),
+ RGBColor(135, 135, 215),
+ RGBColor(135, 135, 255),
+ RGBColor(135, 175, 0),
+ RGBColor(135, 175, 95),
+ RGBColor(135, 175, 135),
+ RGBColor(135, 175, 175),
+ RGBColor(135, 175, 215),
+ RGBColor(135, 175, 255),
+ RGBColor(135, 215, 0),
+ RGBColor(135, 215, 95),
+ RGBColor(135, 215, 135),
+ RGBColor(135, 215, 175),
+ RGBColor(135, 215, 215),
+ RGBColor(135, 215, 255),
+ RGBColor(135, 255, 0),
+ RGBColor(135, 255, 95),
+ RGBColor(135, 255, 135),
+ RGBColor(135, 255, 175),
+ RGBColor(135, 255, 215),
+ RGBColor(135, 255, 255),
+ RGBColor(175, 0, 0),
+ RGBColor(175, 0, 95),
+ RGBColor(175, 0, 135),
+ RGBColor(175, 0, 175),
+ RGBColor(175, 0, 215),
+ RGBColor(175, 0, 255),
+ RGBColor(175, 95, 0),
+ RGBColor(175, 95, 95),
+ RGBColor(175, 95, 135),
+ RGBColor(175, 95, 175),
+ RGBColor(175, 95, 215),
+ RGBColor(175, 95, 255),
+ RGBColor(175, 135, 0),
+ RGBColor(175, 135, 95),
+ RGBColor(175, 135, 135),
+ RGBColor(175, 135, 175),
+ RGBColor(175, 135, 215),
+ RGBColor(175, 135, 255),
+ RGBColor(175, 175, 0),
+ RGBColor(175, 175, 95),
+ RGBColor(175, 175, 135),
+ RGBColor(175, 175, 175),
+ RGBColor(175, 175, 215),
+ RGBColor(175, 175, 255),
+ RGBColor(175, 215, 0),
+ RGBColor(175, 215, 95),
+ RGBColor(175, 215, 135),
+ RGBColor(175, 215, 175),
+ RGBColor(175, 215, 215),
+ RGBColor(175, 215, 255),
+ RGBColor(175, 255, 0),
+ RGBColor(175, 255, 95),
+ RGBColor(175, 255, 135),
+ RGBColor(175, 255, 175),
+ RGBColor(175, 255, 215),
+ RGBColor(175, 255, 255),
+ RGBColor(215, 0, 0),
+ RGBColor(215, 0, 95),
+ RGBColor(215, 0, 135),
+ RGBColor(215, 0, 175),
+ RGBColor(215, 0, 215),
+ RGBColor(215, 0, 255),
+ RGBColor(215, 95, 0),
+ RGBColor(215, 95, 95),
+ RGBColor(215, 95, 135),
+ RGBColor(215, 95, 175),
+ RGBColor(215, 95, 215),
+ RGBColor(215, 95, 255),
+ RGBColor(215, 135, 0),
+ RGBColor(215, 135, 95),
+ RGBColor(215, 135, 135),
+ RGBColor(215, 135, 175),
+ RGBColor(215, 135, 215),
+ RGBColor(215, 135, 255),
+ RGBColor(215, 175, 0),
+ RGBColor(215, 175, 95),
+ RGBColor(215, 175, 135),
+ RGBColor(215, 175, 175),
+ RGBColor(215, 175, 215),
+ RGBColor(215, 175, 255),
+ RGBColor(215, 215, 0),
+ RGBColor(215, 215, 95),
+ RGBColor(215, 215, 135),
+ RGBColor(215, 215, 175),
+ RGBColor(215, 215, 215),
+ RGBColor(215, 215, 255),
+ RGBColor(215, 255, 0),
+ RGBColor(215, 255, 95),
+ RGBColor(215, 255, 135),
+ RGBColor(215, 255, 175),
+ RGBColor(215, 255, 215),
+ RGBColor(215, 255, 255),
+ RGBColor(255, 0, 0),
+ RGBColor(255, 0, 135),
+ RGBColor(255, 0, 95),
+ RGBColor(255, 0, 175),
+ RGBColor(255, 0, 215),
+ RGBColor(255, 0, 255),
+ RGBColor(255, 95, 0),
+ RGBColor(255, 95, 95),
+ RGBColor(255, 95, 135),
+ RGBColor(255, 95, 175),
+ RGBColor(255, 95, 215),
+ RGBColor(255, 95, 255),
+ RGBColor(255, 135, 0),
+ RGBColor(255, 135, 95),
+ RGBColor(255, 135, 135),
+ RGBColor(255, 135, 175),
+ RGBColor(255, 135, 215),
+ RGBColor(255, 135, 255),
+ RGBColor(255, 175, 0),
+ RGBColor(255, 175, 95),
+ RGBColor(255, 175, 135),
+ RGBColor(255, 175, 175),
+ RGBColor(255, 175, 215),
+ RGBColor(255, 175, 255),
+ RGBColor(255, 215, 0),
+ RGBColor(255, 215, 95),
+ RGBColor(255, 215, 135),
+ RGBColor(255, 215, 175),
+ RGBColor(255, 215, 215),
+ RGBColor(255, 215, 255),
+ RGBColor(255, 255, 0),
+ RGBColor(255, 255, 95),
+ RGBColor(255, 255, 135),
+ RGBColor(255, 255, 175),
+ RGBColor(255, 255, 215),
+ RGBColor(255, 255, 255),
+ RGBColor(8, 8, 8),
+ RGBColor(18, 18, 18),
+ RGBColor(28, 28, 28),
+ RGBColor(38, 38, 38),
+ RGBColor(48, 48, 48),
+ RGBColor(58, 58, 58),
+ RGBColor(68, 68, 68),
+ RGBColor(78, 78, 78),
+ RGBColor(88, 88, 88),
+ RGBColor(98, 98, 98),
+ RGBColor(108, 108, 108),
+ RGBColor(118, 118, 118),
+ RGBColor(128, 128, 128),
+ RGBColor(138, 138, 138),
+ RGBColor(148, 148, 148),
+ RGBColor(158, 158, 158),
+ RGBColor(168, 168, 168),
+ RGBColor(178, 178, 178),
+ RGBColor(188, 188, 188),
+ RGBColor(198, 198, 198),
+ RGBColor(208, 208, 208),
+ RGBColor(218, 218, 218),
+ RGBColor(228, 228, 228),
+ RGBColor(238, 238, 238),
+)
diff --git a/third_party/python/blessed/blessed/colorspace.pyi b/third_party/python/blessed/blessed/colorspace.pyi
new file mode 100644
index 0000000000..a799cd01cf
--- /dev/null
+++ b/third_party/python/blessed/blessed/colorspace.pyi
@@ -0,0 +1,12 @@
+# std imports
+from typing import Set, Dict, Tuple, NamedTuple
+
+CGA_COLORS: Set[str]
+
+class RGBColor(NamedTuple):
+ red: int
+ green: int
+ blue: int
+
+X11_COLORNAMES_TO_RGB: Dict[str, RGBColor]
+RGB_256TABLE: Tuple[RGBColor, ...]
diff --git a/third_party/python/blessed/blessed/formatters.py b/third_party/python/blessed/blessed/formatters.py
new file mode 100644
index 0000000000..ed1badc5ad
--- /dev/null
+++ b/third_party/python/blessed/blessed/formatters.py
@@ -0,0 +1,498 @@
+"""Sub-module providing sequence-formatting functions."""
+# std imports
+import platform
+
+# 3rd party
+import six
+
+# local
+from blessed.colorspace import CGA_COLORS, X11_COLORNAMES_TO_RGB
+
+# isort: off
+# curses
+if platform.system() == 'Windows':
+ import jinxed as curses # pylint: disable=import-error
+else:
+ import curses
+
+
+def _make_colors():
+ """
+ Return set of valid colors and their derivatives.
+
+ :rtype: set
+ :returns: Color names with prefixes
+ """
+ colors = set()
+ # basic CGA foreground color, background, high intensity, and bold
+ # background ('iCE colors' in my day).
+ for cga_color in CGA_COLORS:
+ colors.add(cga_color)
+ colors.add('on_' + cga_color)
+ colors.add('bright_' + cga_color)
+ colors.add('on_bright_' + cga_color)
+
+ # foreground and background VGA color
+ for vga_color in X11_COLORNAMES_TO_RGB:
+ colors.add(vga_color)
+ colors.add('on_' + vga_color)
+ return colors
+
+
+#: Valid colors and their background (on), bright, and bright-background
+#: derivatives.
+COLORS = _make_colors()
+
+#: Attributes that may be compounded with colors, by underscore, such as
+#: 'reverse_indigo'.
+COMPOUNDABLES = set('bold underline reverse blink italic standout'.split())
+
+
+class ParameterizingString(six.text_type):
+ r"""
+ A Unicode string which can be called as a parameterizing termcap.
+
+ For example::
+
+ >>> from blessed import Terminal
+ >>> term = Terminal()
+ >>> color = ParameterizingString(term.color, term.normal, 'color')
+ >>> color(9)('color #9')
+ u'\x1b[91mcolor #9\x1b(B\x1b[m'
+ """
+
+ def __new__(cls, cap, normal=u'', name=u'<not specified>'):
+ # pylint: disable = missing-return-doc, missing-return-type-doc
+ """
+ Class constructor accepting 3 positional arguments.
+
+ :arg str cap: parameterized string suitable for curses.tparm()
+ :arg str normal: terminating sequence for this capability (optional).
+ :arg str name: name of this terminal capability (optional).
+ """
+ new = six.text_type.__new__(cls, cap)
+ new._normal = normal
+ new._name = name
+ return new
+
+ def __call__(self, *args):
+ """
+ Returning :class:`FormattingString` instance for given parameters.
+
+ Return evaluated terminal capability (self), receiving arguments
+ ``*args``, followed by the terminating sequence (self.normal) into
+ a :class:`FormattingString` capable of being called.
+
+ :raises TypeError: Mismatch between capability and arguments
+ :raises curses.error: :func:`curses.tparm` raised an exception
+ :rtype: :class:`FormattingString` or :class:`NullCallableString`
+ :returns: Callable string for given parameters
+ """
+ try:
+ # Re-encode the cap, because tparm() takes a bytestring in Python
+ # 3. However, appear to be a plain Unicode string otherwise so
+ # concats work.
+ attr = curses.tparm(self.encode('latin1'), *args).decode('latin1')
+ return FormattingString(attr, self._normal)
+ except TypeError as err:
+ # If the first non-int (i.e. incorrect) arg was a string, suggest
+ # something intelligent:
+ if args and isinstance(args[0], six.string_types):
+ raise TypeError(
+ "Unknown terminal capability, %r, or, TypeError "
+ "for arguments %r: %s" % (self._name, args, err))
+ # Somebody passed a non-string; I don't feel confident
+ # guessing what they were trying to do.
+ raise
+ except curses.error as err:
+ # ignore 'tparm() returned NULL', you won't get any styling,
+ # even if does_styling is True. This happens on win32 platforms
+ # with http://www.lfd.uci.edu/~gohlke/pythonlibs/#curses installed
+ if "tparm() returned NULL" not in six.text_type(err):
+ raise
+ return NullCallableString()
+
+
+class ParameterizingProxyString(six.text_type):
+ r"""
+ A Unicode string which can be called to proxy missing termcap entries.
+
+ This class supports the function :func:`get_proxy_string`, and mirrors
+ the behavior of :class:`ParameterizingString`, except that instead of
+ a capability name, receives a format string, and callable to filter the
+ given positional ``*args`` of :meth:`ParameterizingProxyString.__call__`
+ into a terminal sequence.
+
+ For example::
+
+ >>> from blessed import Terminal
+ >>> term = Terminal('screen')
+ >>> hpa = ParameterizingString(term.hpa, term.normal, 'hpa')
+ >>> hpa(9)
+ u''
+ >>> fmt = u'\x1b[{0}G'
+ >>> fmt_arg = lambda *arg: (arg[0] + 1,)
+ >>> hpa = ParameterizingProxyString((fmt, fmt_arg), term.normal, 'hpa')
+ >>> hpa(9)
+ u'\x1b[10G'
+ """
+
+ def __new__(cls, fmt_pair, normal=u'', name=u'<not specified>'):
+ # pylint: disable = missing-return-doc, missing-return-type-doc
+ """
+ Class constructor accepting 4 positional arguments.
+
+ :arg tuple fmt_pair: Two element tuple containing:
+ - format string suitable for displaying terminal sequences
+ - callable suitable for receiving __call__ arguments for formatting string
+ :arg str normal: terminating sequence for this capability (optional).
+ :arg str name: name of this terminal capability (optional).
+ """
+ assert isinstance(fmt_pair, tuple), fmt_pair
+ assert callable(fmt_pair[1]), fmt_pair[1]
+ new = six.text_type.__new__(cls, fmt_pair[0])
+ new._fmt_args = fmt_pair[1]
+ new._normal = normal
+ new._name = name
+ return new
+
+ def __call__(self, *args):
+ """
+ Returning :class:`FormattingString` instance for given parameters.
+
+ Arguments are determined by the capability. For example, ``hpa``
+ (move_x) receives only a single integer, whereas ``cup`` (move)
+ receives two integers. See documentation in terminfo(5) for the
+ given capability.
+
+ :rtype: FormattingString
+ :returns: Callable string for given parameters
+ """
+ return FormattingString(self.format(*self._fmt_args(*args)),
+ self._normal)
+
+
+class FormattingString(six.text_type):
+ r"""
+ A Unicode string which doubles as a callable.
+
+ This is used for terminal attributes, so that it may be used both
+ directly, or as a callable. When used directly, it simply emits
+ the given terminal sequence. When used as a callable, it wraps the
+ given (string) argument with the 2nd argument used by the class
+ constructor::
+
+ >>> from blessed import Terminal
+ >>> term = Terminal()
+ >>> style = FormattingString(term.bright_blue, term.normal)
+ >>> print(repr(style))
+ u'\x1b[94m'
+ >>> style('Big Blue')
+ u'\x1b[94mBig Blue\x1b(B\x1b[m'
+ """
+
+ def __new__(cls, sequence, normal=u''):
+ # pylint: disable = missing-return-doc, missing-return-type-doc
+ """
+ Class constructor accepting 2 positional arguments.
+
+ :arg str sequence: terminal attribute sequence.
+ :arg str normal: terminating sequence for this attribute (optional).
+ """
+ new = six.text_type.__new__(cls, sequence)
+ new._normal = normal
+ return new
+
+ def __call__(self, *args):
+ """
+ Return ``text`` joined by ``sequence`` and ``normal``.
+
+ :raises TypeError: Not a string type
+ :rtype: str
+ :returns: Arguments wrapped in sequence and normal
+ """
+ # Jim Allman brings us this convenience of allowing existing
+ # unicode strings to be joined as a call parameter to a formatting
+ # string result, allowing nestation:
+ #
+ # >>> t.red('This is ', t.bold('extremely'), ' dangerous!')
+ for idx, ucs_part in enumerate(args):
+ if not isinstance(ucs_part, six.string_types):
+ expected_types = ', '.join(_type.__name__ for _type in six.string_types)
+ raise TypeError(
+ "TypeError for FormattingString argument, "
+ "%r, at position %s: expected type %s, "
+ "got %s" % (ucs_part, idx, expected_types,
+ type(ucs_part).__name__))
+ postfix = u''
+ if self and self._normal:
+ postfix = self._normal
+ _refresh = self._normal + self
+ args = [_refresh.join(ucs_part.split(self._normal))
+ for ucs_part in args]
+
+ return self + u''.join(args) + postfix
+
+
+class FormattingOtherString(six.text_type):
+ r"""
+ A Unicode string which doubles as a callable for another sequence when called.
+
+ This is used for the :meth:`~.Terminal.move_up`, ``down``, ``left``, and ``right()``
+ family of functions::
+
+ >>> from blessed import Terminal
+ >>> term = Terminal()
+ >>> move_right = FormattingOtherString(term.cuf1, term.cuf)
+ >>> print(repr(move_right))
+ u'\x1b[C'
+ >>> print(repr(move_right(666)))
+ u'\x1b[666C'
+ >>> print(repr(move_right()))
+ u'\x1b[C'
+ """
+
+ def __new__(cls, direct, target):
+ # pylint: disable = missing-return-doc, missing-return-type-doc
+ """
+ Class constructor accepting 2 positional arguments.
+
+ :arg str direct: capability name for direct formatting, eg ``('x' + term.right)``.
+ :arg str target: capability name for callable, eg ``('x' + term.right(99))``.
+ """
+ new = six.text_type.__new__(cls, direct)
+ new._callable = target
+ return new
+
+ def __getnewargs__(self):
+ # return arguments used for the __new__ method upon unpickling.
+ return six.text_type.__new__(six.text_type, self), self._callable
+
+ def __call__(self, *args):
+ """Return ``text`` by ``target``."""
+ if args:
+ return self._callable(*args)
+ return self
+
+
+class NullCallableString(six.text_type):
+ """
+ A dummy callable Unicode alternative to :class:`FormattingString`.
+
+ This is used for colors on terminals that do not support colors, it is just a basic form of
+ unicode that may also act as a callable.
+ """
+
+ def __new__(cls):
+ """Class constructor."""
+ return six.text_type.__new__(cls, u'')
+
+ def __call__(self, *args):
+ """
+ Allow empty string to be callable, returning given string, if any.
+
+ When called with an int as the first arg, return an empty Unicode. An
+ int is a good hint that I am a :class:`ParameterizingString`, as there
+ are only about half a dozen string-returning capabilities listed in
+ terminfo(5) which accept non-int arguments, they are seldom used.
+
+ When called with a non-int as the first arg (no no args at all), return
+ the first arg, acting in place of :class:`FormattingString` without
+ any attributes.
+ """
+ if not args or isinstance(args[0], int):
+ # As a NullCallableString, even when provided with a parameter,
+ # such as t.color(5), we must also still be callable, fe:
+ #
+ # >>> t.color(5)('shmoo')
+ #
+ # is actually simplified result of NullCallable()() on terminals
+ # without color support, so turtles all the way down: we return
+ # another instance.
+ return NullCallableString()
+ return u''.join(args)
+
+
+def get_proxy_string(term, attr):
+ """
+ Proxy and return callable string for proxied attributes.
+
+ :arg Terminal term: :class:`~.Terminal` instance.
+ :arg str attr: terminal capability name that may be proxied.
+ :rtype: None or :class:`ParameterizingProxyString`.
+ :returns: :class:`ParameterizingProxyString` for some attributes
+ of some terminal types that support it, where the terminfo(5)
+ database would otherwise come up empty, such as ``move_x``
+ attribute for ``term.kind`` of ``screen``. Otherwise, None.
+ """
+ # normalize 'screen-256color', or 'ansi.sys' to its basic names
+ term_kind = next(iter(_kind for _kind in ('screen', 'ansi',)
+ if term.kind.startswith(_kind)), term)
+ _proxy_table = { # pragma: no cover
+ 'screen': {
+ # proxy move_x/move_y for 'screen' terminal type, used by tmux(1).
+ 'hpa': ParameterizingProxyString(
+ (u'\x1b[{0}G', lambda *arg: (arg[0] + 1,)), term.normal, attr),
+ 'vpa': ParameterizingProxyString(
+ (u'\x1b[{0}d', lambda *arg: (arg[0] + 1,)), term.normal, attr),
+ },
+ 'ansi': {
+ # proxy show/hide cursor for 'ansi' terminal type. There is some
+ # demand for a richly working ANSI terminal type for some reason.
+ 'civis': ParameterizingProxyString(
+ (u'\x1b[?25l', lambda *arg: ()), term.normal, attr),
+ 'cnorm': ParameterizingProxyString(
+ (u'\x1b[?25h', lambda *arg: ()), term.normal, attr),
+ 'hpa': ParameterizingProxyString(
+ (u'\x1b[{0}G', lambda *arg: (arg[0] + 1,)), term.normal, attr),
+ 'vpa': ParameterizingProxyString(
+ (u'\x1b[{0}d', lambda *arg: (arg[0] + 1,)), term.normal, attr),
+ 'sc': '\x1b[s',
+ 'rc': '\x1b[u',
+ }
+ }
+ return _proxy_table.get(term_kind, {}).get(attr, None)
+
+
+def split_compound(compound):
+ """
+ Split compound formating string into segments.
+
+ >>> split_compound('bold_underline_bright_blue_on_red')
+ ['bold', 'underline', 'bright_blue', 'on_red']
+
+ :arg str compound: a string that may contain compounds, separated by
+ underline (``_``).
+ :rtype: list
+ :returns: List of formating string segments
+ """
+ merged_segs = []
+ # These occur only as prefixes, so they can always be merged:
+ mergeable_prefixes = ['on', 'bright', 'on_bright']
+ for segment in compound.split('_'):
+ if merged_segs and merged_segs[-1] in mergeable_prefixes:
+ merged_segs[-1] += '_' + segment
+ else:
+ merged_segs.append(segment)
+ return merged_segs
+
+
+def resolve_capability(term, attr):
+ """
+ Resolve a raw terminal capability using :func:`tigetstr`.
+
+ :arg Terminal term: :class:`~.Terminal` instance.
+ :arg str attr: terminal capability name.
+ :returns: string of the given terminal capability named by ``attr``,
+ which may be empty (u'') if not found or not supported by the
+ given :attr:`~.Terminal.kind`.
+ :rtype: str
+ """
+ if not term.does_styling:
+ return u''
+ val = curses.tigetstr(term._sugar.get(attr, attr)) # pylint: disable=protected-access
+ # Decode sequences as latin1, as they are always 8-bit bytes, so when
+ # b'\xff' is returned, this is decoded as u'\xff'.
+ return u'' if val is None else val.decode('latin1')
+
+
+def resolve_color(term, color):
+ """
+ Resolve a simple color name to a callable capability.
+
+ This function supports :func:`resolve_attribute`.
+
+ :arg Terminal term: :class:`~.Terminal` instance.
+ :arg str color: any string found in set :const:`COLORS`.
+ :returns: a string class instance which emits the terminal sequence
+ for the given color, and may be used as a callable to wrap the
+ given string with such sequence.
+ :returns: :class:`NullCallableString` when
+ :attr:`~.Terminal.number_of_colors` is 0,
+ otherwise :class:`FormattingString`.
+ :rtype: :class:`NullCallableString` or :class:`FormattingString`
+ """
+ # pylint: disable=protected-access
+ if term.number_of_colors == 0:
+ return NullCallableString()
+
+ # fg/bg capabilities terminals that support 0-256+ colors.
+ vga_color_cap = (term._background_color if 'on_' in color else
+ term._foreground_color)
+
+ base_color = color.rsplit('_', 1)[-1]
+ if base_color in CGA_COLORS:
+ # curses constants go up to only 7, so add an offset to get at the
+ # bright colors at 8-15:
+ offset = 8 if 'bright_' in color else 0
+ base_color = color.rsplit('_', 1)[-1]
+ attr = 'COLOR_%s' % (base_color.upper(),)
+ fmt_attr = vga_color_cap(getattr(curses, attr) + offset)
+ return FormattingString(fmt_attr, term.normal)
+
+ assert base_color in X11_COLORNAMES_TO_RGB, (
+ 'color not known', base_color)
+ rgb = X11_COLORNAMES_TO_RGB[base_color]
+
+ # downconvert X11 colors to CGA, EGA, or VGA color spaces
+ if term.number_of_colors <= 256:
+ fmt_attr = vga_color_cap(term.rgb_downconvert(*rgb))
+ return FormattingString(fmt_attr, term.normal)
+
+ # Modern 24-bit color terminals are written pretty basically. The
+ # foreground and background sequences are:
+ # - ^[38;2;<r>;<g>;<b>m
+ # - ^[48;2;<r>;<g>;<b>m
+ fgbg_seq = ('48' if 'on_' in color else '38')
+ assert term.number_of_colors == 1 << 24
+ fmt_attr = u'\x1b[' + fgbg_seq + ';2;{0};{1};{2}m'
+ return FormattingString(fmt_attr.format(*rgb), term.normal)
+
+
+def resolve_attribute(term, attr):
+ """
+ Resolve a terminal attribute name into a capability class.
+
+ :arg Terminal term: :class:`~.Terminal` instance.
+ :arg str attr: Sugary, ordinary, or compound formatted terminal
+ capability, such as "red_on_white", "normal", "red", or
+ "bold_on_black".
+ :returns: a string class instance which emits the terminal sequence
+ for the given terminal capability, or may be used as a callable to
+ wrap the given string with such sequence.
+ :returns: :class:`NullCallableString` when
+ :attr:`~.Terminal.number_of_colors` is 0,
+ otherwise :class:`FormattingString`.
+ :rtype: :class:`NullCallableString` or :class:`FormattingString`
+ """
+ if attr in COLORS:
+ return resolve_color(term, attr)
+
+ # A direct compoundable, such as `bold' or `on_red'.
+ if attr in COMPOUNDABLES:
+ sequence = resolve_capability(term, attr)
+ return FormattingString(sequence, term.normal)
+
+ # Given `bold_on_red', resolve to ('bold', 'on_red'), RECURSIVE
+ # call for each compounding section, joined and returned as
+ # a completed completed FormattingString.
+ formatters = split_compound(attr)
+ if all((fmt in COLORS or fmt in COMPOUNDABLES) for fmt in formatters):
+ resolution = (resolve_attribute(term, fmt) for fmt in formatters)
+ return FormattingString(u''.join(resolution), term.normal)
+
+ # otherwise, this is our end-game: given a sequence such as 'csr'
+ # (change scrolling region), return a ParameterizingString instance,
+ # that when called, performs and returns the final string after curses
+ # capability lookup is performed.
+ tparm_capseq = resolve_capability(term, attr)
+ if not tparm_capseq:
+ # and, for special terminals, such as 'screen', provide a Proxy
+ # ParameterizingString for attributes they do not claim to support,
+ # but actually do! (such as 'hpa' and 'vpa').
+ proxy = get_proxy_string(term,
+ term._sugar.get(attr, attr)) # pylint: disable=protected-access
+ if proxy is not None:
+ return proxy
+
+ return ParameterizingString(tparm_capseq, term.normal, attr)
diff --git a/third_party/python/blessed/blessed/formatters.pyi b/third_party/python/blessed/blessed/formatters.pyi
new file mode 100644
index 0000000000..32a3dc2df3
--- /dev/null
+++ b/third_party/python/blessed/blessed/formatters.pyi
@@ -0,0 +1,70 @@
+# std imports
+from typing import (Any,
+ Set,
+ List,
+ Type,
+ Tuple,
+ Union,
+ TypeVar,
+ Callable,
+ NoReturn,
+ Optional,
+ overload)
+
+# local
+from .terminal import Terminal
+
+COLORS: Set[str]
+COMPOUNDABLES: Set[str]
+
+_T = TypeVar("_T")
+
+class ParameterizingString(str):
+ def __new__(cls: Type[_T], cap: str, normal: str = ..., name: str = ...) -> _T: ...
+ @overload
+ def __call__(
+ self, *args: int
+ ) -> Union["FormattingString", "NullCallableString"]: ...
+ @overload
+ def __call__(self, *args: str) -> NoReturn: ...
+
+class ParameterizingProxyString(str):
+ def __new__(
+ cls: Type[_T],
+ fmt_pair: Tuple[str, Callable[..., Tuple[object, ...]]],
+ normal: str = ...,
+ name: str = ...,
+ ) -> _T: ...
+ def __call__(self, *args: Any) -> "FormattingString": ...
+
+class FormattingString(str):
+ def __new__(cls: Type[_T], sequence: str, normal: str = ...) -> _T: ...
+ @overload
+ def __call__(self, *args: int) -> NoReturn: ...
+ @overload
+ def __call__(self, *args: str) -> str: ...
+
+class FormattingOtherString(str):
+ def __new__(
+ cls: Type[_T], direct: ParameterizingString, target: ParameterizingString = ...
+ ) -> _T: ...
+ def __call__(self, *args: Union[int, str]) -> str: ...
+
+class NullCallableString(str):
+ def __new__(cls: Type[_T]) -> _T: ...
+ @overload
+ def __call__(self, *args: int) -> "NullCallableString": ...
+ @overload
+ def __call__(self, *args: str) -> str: ...
+
+def get_proxy_string(
+ term: Terminal, attr: str
+) -> Optional[ParameterizingProxyString]: ...
+def split_compound(compound: str) -> List[str]: ...
+def resolve_capability(term: Terminal, attr: str) -> str: ...
+def resolve_color(
+ term: Terminal, color: str
+) -> Union[NullCallableString, FormattingString]: ...
+def resolve_attribute(
+ term: Terminal, attr: str
+) -> Union[ParameterizingString, FormattingString]: ...
diff --git a/third_party/python/blessed/blessed/keyboard.py b/third_party/python/blessed/blessed/keyboard.py
new file mode 100644
index 0000000000..2736da160f
--- /dev/null
+++ b/third_party/python/blessed/blessed/keyboard.py
@@ -0,0 +1,449 @@
+"""Sub-module providing 'keyboard awareness'."""
+
+# std imports
+import re
+import time
+import platform
+from collections import OrderedDict
+
+# 3rd party
+import six
+
+# isort: off
+# curses
+if platform.system() == 'Windows':
+ # pylint: disable=import-error
+ import jinxed as curses
+ from jinxed.has_key import _capability_names as capability_names
+else:
+ import curses
+ from curses.has_key import _capability_names as capability_names
+
+
+class Keystroke(six.text_type):
+ """
+ A unicode-derived class for describing a single keystroke.
+
+ A class instance describes a single keystroke received on input,
+ which may contain multiple characters as a multibyte sequence,
+ which is indicated by properties :attr:`is_sequence` returning
+ ``True``.
+
+ When the string is a known sequence, :attr:`code` matches terminal
+ class attributes for comparison, such as ``term.KEY_LEFT``.
+
+ The string-name of the sequence, such as ``u'KEY_LEFT'`` is accessed
+ by property :attr:`name`, and is used by the :meth:`__repr__` method
+ to display a human-readable form of the Keystroke this class
+ instance represents. It may otherwise by joined, split, or evaluated
+ just as as any other unicode string.
+ """
+
+ def __new__(cls, ucs='', code=None, name=None):
+ """Class constructor."""
+ new = six.text_type.__new__(cls, ucs)
+ new._name = name
+ new._code = code
+ return new
+
+ @property
+ def is_sequence(self):
+ """Whether the value represents a multibyte sequence (bool)."""
+ return self._code is not None
+
+ def __repr__(self):
+ """Docstring overwritten."""
+ return (six.text_type.__repr__(self) if self._name is None else
+ self._name)
+ __repr__.__doc__ = six.text_type.__doc__
+
+ @property
+ def name(self):
+ """String-name of key sequence, such as ``u'KEY_LEFT'`` (str)."""
+ return self._name
+
+ @property
+ def code(self):
+ """Integer keycode value of multibyte sequence (int)."""
+ return self._code
+
+
+def get_curses_keycodes():
+ """
+ Return mapping of curses key-names paired by their keycode integer value.
+
+ :rtype: dict
+ :returns: Dictionary of (name, code) pairs for curses keyboard constant
+ values and their mnemonic name. Such as code ``260``, with the value of
+ its key-name identity, ``u'KEY_LEFT'``.
+ """
+ _keynames = [attr for attr in dir(curses)
+ if attr.startswith('KEY_')]
+ return {keyname: getattr(curses, keyname) for keyname in _keynames}
+
+
+def get_keyboard_codes():
+ """
+ Return mapping of keycode integer values paired by their curses key-name.
+
+ :rtype: dict
+ :returns: Dictionary of (code, name) pairs for curses keyboard constant
+ values and their mnemonic name. Such as key ``260``, with the value of
+ its identity, ``u'KEY_LEFT'``.
+
+ These keys are derived from the attributes by the same of the curses module,
+ with the following exceptions:
+
+ * ``KEY_DELETE`` in place of ``KEY_DC``
+ * ``KEY_INSERT`` in place of ``KEY_IC``
+ * ``KEY_PGUP`` in place of ``KEY_PPAGE``
+ * ``KEY_PGDOWN`` in place of ``KEY_NPAGE``
+ * ``KEY_ESCAPE`` in place of ``KEY_EXIT``
+ * ``KEY_SUP`` in place of ``KEY_SR``
+ * ``KEY_SDOWN`` in place of ``KEY_SF``
+
+ This function is the inverse of :func:`get_curses_keycodes`. With the
+ given override "mixins" listed above, the keycode for the delete key will
+ map to our imaginary ``KEY_DELETE`` mnemonic, effectively erasing the
+ phrase ``KEY_DC`` from our code vocabulary for anyone that wishes to use
+ the return value to determine the key-name by keycode.
+ """
+ keycodes = OrderedDict(get_curses_keycodes())
+ keycodes.update(CURSES_KEYCODE_OVERRIDE_MIXIN)
+ # merge _CURSES_KEYCODE_ADDINS added to our module space
+ keycodes.update((name, value) for name, value in globals().items() if name.startswith('KEY_'))
+
+ # invert dictionary (key, values) => (values, key), preferring the
+ # last-most inserted value ('KEY_DELETE' over 'KEY_DC').
+ return dict(zip(keycodes.values(), keycodes.keys()))
+
+
+def _alternative_left_right(term):
+ r"""
+ Determine and return mapping of left and right arrow keys sequences.
+
+ :arg blessed.Terminal term: :class:`~.Terminal` instance.
+ :rtype: dict
+ :returns: Dictionary of sequences ``term._cuf1``, and ``term._cub1``,
+ valued as ``KEY_RIGHT``, ``KEY_LEFT`` (when appropriate).
+
+ This function supports :func:`get_terminal_sequences` to discover
+ the preferred input sequence for the left and right application keys.
+
+ It is necessary to check the value of these sequences to ensure we do not
+ use ``u' '`` and ``u'\b'`` for ``KEY_RIGHT`` and ``KEY_LEFT``,
+ preferring their true application key sequence, instead.
+ """
+ # pylint: disable=protected-access
+ keymap = {}
+ if term._cuf1 and term._cuf1 != u' ':
+ keymap[term._cuf1] = curses.KEY_RIGHT
+ if term._cub1 and term._cub1 != u'\b':
+ keymap[term._cub1] = curses.KEY_LEFT
+ return keymap
+
+
+def get_keyboard_sequences(term):
+ r"""
+ Return mapping of keyboard sequences paired by keycodes.
+
+ :arg blessed.Terminal term: :class:`~.Terminal` instance.
+ :returns: mapping of keyboard unicode sequences paired by keycodes
+ as integer. This is used as the argument ``mapper`` to
+ the supporting function :func:`resolve_sequence`.
+ :rtype: OrderedDict
+
+ Initialize and return a keyboard map and sequence lookup table,
+ (sequence, keycode) from :class:`~.Terminal` instance ``term``,
+ where ``sequence`` is a multibyte input sequence of unicode
+ characters, such as ``u'\x1b[D'``, and ``keycode`` is an integer
+ value, matching curses constant such as term.KEY_LEFT.
+
+ The return value is an OrderedDict instance, with their keys
+ sorted longest-first.
+ """
+ # A small gem from curses.has_key that makes this all possible,
+ # _capability_names: a lookup table of terminal capability names for
+ # keyboard sequences (fe. kcub1, key_left), keyed by the values of
+ # constants found beginning with KEY_ in the main curses module
+ # (such as KEY_LEFT).
+ #
+ # latin1 encoding is used so that bytes in 8-bit range of 127-255
+ # have equivalent chr() and unichr() values, so that the sequence
+ # of a kermit or avatar terminal, for example, remains unchanged
+ # in its byte sequence values even when represented by unicode.
+ #
+ sequence_map = dict((
+ (seq.decode('latin1'), val)
+ for (seq, val) in (
+ (curses.tigetstr(cap), val)
+ for (val, cap) in capability_names.items()
+ ) if seq
+ ) if term.does_styling else ())
+
+ sequence_map.update(_alternative_left_right(term))
+ sequence_map.update(DEFAULT_SEQUENCE_MIXIN)
+
+ # This is for fast lookup matching of sequences, preferring
+ # full-length sequence such as ('\x1b[D', KEY_LEFT)
+ # over simple sequences such as ('\x1b', KEY_EXIT).
+ return OrderedDict((
+ (seq, sequence_map[seq]) for seq in sorted(
+ sequence_map.keys(), key=len, reverse=True)))
+
+
+def get_leading_prefixes(sequences):
+ """
+ Return a set of proper prefixes for given sequence of strings.
+
+ :arg iterable sequences
+ :rtype: set
+ :return: Set of all string prefixes
+
+ Given an iterable of strings, all textparts leading up to the final
+ string is returned as a unique set. This function supports the
+ :meth:`~.Terminal.inkey` method by determining whether the given
+ input is a sequence that **may** lead to a final matching pattern.
+
+ >>> prefixes(['abc', 'abdf', 'e', 'jkl'])
+ set([u'a', u'ab', u'abd', u'j', u'jk'])
+ """
+ return {seq[:i] for seq in sequences for i in range(1, len(seq))}
+
+
+def resolve_sequence(text, mapper, codes):
+ r"""
+ Return a single :class:`Keystroke` instance for given sequence ``text``.
+
+ :arg str text: string of characters received from terminal input stream.
+ :arg OrderedDict mapper: unicode multibyte sequences, such as ``u'\x1b[D'``
+ paired by their integer value (260)
+ :arg dict codes: a :type:`dict` of integer values (such as 260) paired
+ by their mnemonic name, such as ``'KEY_LEFT'``.
+ :rtype: Keystroke
+ :returns: Keystroke instance for the given sequence
+
+ The given ``text`` may extend beyond a matching sequence, such as
+ ``u\x1b[Dxxx`` returns a :class:`Keystroke` instance of attribute
+ :attr:`Keystroke.sequence` valued only ``u\x1b[D``. It is up to
+ calls to determine that ``xxx`` remains unresolved.
+ """
+ for sequence, code in mapper.items():
+ if text.startswith(sequence):
+ return Keystroke(ucs=sequence, code=code, name=codes[code])
+ return Keystroke(ucs=text and text[0] or u'')
+
+
+def _time_left(stime, timeout):
+ """
+ Return time remaining since ``stime`` before given ``timeout``.
+
+ This function assists determining the value of ``timeout`` for
+ class method :meth:`~.Terminal.kbhit` and similar functions.
+
+ :arg float stime: starting time for measurement
+ :arg float timeout: timeout period, may be set to None to
+ indicate no timeout (where None is always returned).
+ :rtype: float or int
+ :returns: time remaining as float. If no time is remaining,
+ then the integer ``0`` is returned.
+ """
+ return max(0, timeout - (time.time() - stime)) if timeout else timeout
+
+
+def _read_until(term, pattern, timeout):
+ """
+ Convenience read-until-pattern function, supporting :meth:`~.get_location`.
+
+ :arg blessed.Terminal term: :class:`~.Terminal` instance.
+ :arg float timeout: timeout period, may be set to None to indicate no
+ timeout (where 0 is always returned).
+ :arg str pattern: target regular expression pattern to seek.
+ :rtype: tuple
+ :returns: tuple in form of ``(match, str)``, *match*
+ may be :class:`re.MatchObject` if pattern is discovered
+ in input stream before timeout has elapsed, otherwise
+ None. ``str`` is any remaining text received exclusive
+ of the matching pattern).
+
+ The reason a tuple containing non-matching data is returned, is that the
+ consumer should push such data back into the input buffer by
+ :meth:`~.Terminal.ungetch` if any was received.
+
+ For example, when a user is performing rapid input keystrokes while its
+ terminal emulator surreptitiously responds to this in-band sequence, we
+ must ensure any such keyboard data is well-received by the next call to
+ term.inkey() without delay.
+ """
+ stime = time.time()
+ match, buf = None, u''
+
+ # first, buffer all pending data. pexpect library provides a
+ # 'searchwindowsize' attribute that limits this memory region. We're not
+ # concerned about OOM conditions: only (human) keyboard input and terminal
+ # response sequences are expected.
+
+ while True: # pragma: no branch
+ # block as long as necessary to ensure at least one character is
+ # received on input or remaining timeout has elapsed.
+ ucs = term.inkey(timeout=_time_left(stime, timeout))
+ # while the keyboard buffer is "hot" (has input), we continue to
+ # aggregate all awaiting data. We do this to ensure slow I/O
+ # calls do not unnecessarily give up within the first 'while' loop
+ # for short timeout periods.
+ while ucs:
+ buf += ucs
+ ucs = term.inkey(timeout=0)
+
+ match = re.search(pattern=pattern, string=buf)
+ if match is not None:
+ # match
+ break
+
+ if timeout is not None and not _time_left(stime, timeout):
+ # timeout
+ break
+
+ return match, buf
+
+
+#: Though we may determine *keynames* and codes for keyboard input that
+#: generate multibyte sequences, it is also especially useful to aliases
+#: a few basic ASCII characters such as ``KEY_TAB`` instead of ``u'\t'`` for
+#: uniformity.
+#:
+#: Furthermore, many key-names for application keys enabled only by context
+#: manager :meth:`~.Terminal.keypad` are surprisingly absent. We inject them
+#: here directly into the curses module.
+_CURSES_KEYCODE_ADDINS = (
+ 'TAB',
+ 'KP_MULTIPLY',
+ 'KP_ADD',
+ 'KP_SEPARATOR',
+ 'KP_SUBTRACT',
+ 'KP_DECIMAL',
+ 'KP_DIVIDE',
+ 'KP_EQUAL',
+ 'KP_0',
+ 'KP_1',
+ 'KP_2',
+ 'KP_3',
+ 'KP_4',
+ 'KP_5',
+ 'KP_6',
+ 'KP_7',
+ 'KP_8',
+ 'KP_9')
+
+_LASTVAL = max(get_curses_keycodes().values())
+for keycode_name in _CURSES_KEYCODE_ADDINS:
+ _LASTVAL += 1
+ globals()['KEY_' + keycode_name] = _LASTVAL
+
+#: In a perfect world, terminal emulators would always send exactly what
+#: the terminfo(5) capability database plans for them, accordingly by the
+#: value of the ``TERM`` name they declare.
+#:
+#: But this isn't a perfect world. Many vt220-derived terminals, such as
+#: those declaring 'xterm', will continue to send vt220 codes instead of
+#: their native-declared codes, for backwards-compatibility.
+#:
+#: This goes for many: rxvt, putty, iTerm.
+#:
+#: These "mixins" are used for *all* terminals, regardless of their type.
+#:
+#: Furthermore, curses does not provide sequences sent by the keypad,
+#: at least, it does not provide a way to distinguish between keypad 0
+#: and numeric 0.
+DEFAULT_SEQUENCE_MIXIN = (
+ # these common control characters (and 127, ctrl+'?') mapped to
+ # an application key definition.
+ (six.unichr(10), curses.KEY_ENTER),
+ (six.unichr(13), curses.KEY_ENTER),
+ (six.unichr(8), curses.KEY_BACKSPACE),
+ (six.unichr(9), KEY_TAB), # noqa # pylint: disable=undefined-variable
+ (six.unichr(27), curses.KEY_EXIT),
+ (six.unichr(127), curses.KEY_BACKSPACE),
+
+ (u"\x1b[A", curses.KEY_UP),
+ (u"\x1b[B", curses.KEY_DOWN),
+ (u"\x1b[C", curses.KEY_RIGHT),
+ (u"\x1b[D", curses.KEY_LEFT),
+ (u"\x1b[1;2A", curses.KEY_SR),
+ (u"\x1b[1;2B", curses.KEY_SF),
+ (u"\x1b[1;2C", curses.KEY_SRIGHT),
+ (u"\x1b[1;2D", curses.KEY_SLEFT),
+ (u"\x1b[F", curses.KEY_END),
+ (u"\x1b[H", curses.KEY_HOME),
+ # not sure where these are from .. please report
+ (u"\x1b[K", curses.KEY_END),
+ (u"\x1b[U", curses.KEY_NPAGE),
+ (u"\x1b[V", curses.KEY_PPAGE),
+
+ # keys sent after term.smkx (keypad_xmit) is emitted, source:
+ # http://www.xfree86.org/current/ctlseqs.html#PC-Style%20Function%20Keys
+ # http://fossies.org/linux/rxvt/doc/rxvtRef.html#KeyCodes
+ #
+ # keypad, numlock on
+ (u"\x1bOM", curses.KEY_ENTER), # noqa return
+ (u"\x1bOj", KEY_KP_MULTIPLY), # noqa * # pylint: disable=undefined-variable
+ (u"\x1bOk", KEY_KP_ADD), # noqa + # pylint: disable=undefined-variable
+ (u"\x1bOl", KEY_KP_SEPARATOR), # noqa , # pylint: disable=undefined-variable
+ (u"\x1bOm", KEY_KP_SUBTRACT), # noqa - # pylint: disable=undefined-variable
+ (u"\x1bOn", KEY_KP_DECIMAL), # noqa . # pylint: disable=undefined-variable
+ (u"\x1bOo", KEY_KP_DIVIDE), # noqa / # pylint: disable=undefined-variable
+ (u"\x1bOX", KEY_KP_EQUAL), # noqa = # pylint: disable=undefined-variable
+ (u"\x1bOp", KEY_KP_0), # noqa 0 # pylint: disable=undefined-variable
+ (u"\x1bOq", KEY_KP_1), # noqa 1 # pylint: disable=undefined-variable
+ (u"\x1bOr", KEY_KP_2), # noqa 2 # pylint: disable=undefined-variable
+ (u"\x1bOs", KEY_KP_3), # noqa 3 # pylint: disable=undefined-variable
+ (u"\x1bOt", KEY_KP_4), # noqa 4 # pylint: disable=undefined-variable
+ (u"\x1bOu", KEY_KP_5), # noqa 5 # pylint: disable=undefined-variable
+ (u"\x1bOv", KEY_KP_6), # noqa 6 # pylint: disable=undefined-variable
+ (u"\x1bOw", KEY_KP_7), # noqa 7 # pylint: disable=undefined-variable
+ (u"\x1bOx", KEY_KP_8), # noqa 8 # pylint: disable=undefined-variable
+ (u"\x1bOy", KEY_KP_9), # noqa 9 # pylint: disable=undefined-variable
+
+ # keypad, numlock off
+ (u"\x1b[1~", curses.KEY_FIND), # find
+ (u"\x1b[2~", curses.KEY_IC), # insert (0)
+ (u"\x1b[3~", curses.KEY_DC), # delete (.), "Execute"
+ (u"\x1b[4~", curses.KEY_SELECT), # select
+ (u"\x1b[5~", curses.KEY_PPAGE), # pgup (9)
+ (u"\x1b[6~", curses.KEY_NPAGE), # pgdown (3)
+ (u"\x1b[7~", curses.KEY_HOME), # home
+ (u"\x1b[8~", curses.KEY_END), # end
+ (u"\x1b[OA", curses.KEY_UP), # up (8)
+ (u"\x1b[OB", curses.KEY_DOWN), # down (2)
+ (u"\x1b[OC", curses.KEY_RIGHT), # right (6)
+ (u"\x1b[OD", curses.KEY_LEFT), # left (4)
+ (u"\x1b[OF", curses.KEY_END), # end (1)
+ (u"\x1b[OH", curses.KEY_HOME), # home (7)
+
+ # The vt220 placed F1-F4 above the keypad, in place of actual
+ # F1-F4 were local functions (hold screen, print screen,
+ # set up, data/talk, break).
+ (u"\x1bOP", curses.KEY_F1),
+ (u"\x1bOQ", curses.KEY_F2),
+ (u"\x1bOR", curses.KEY_F3),
+ (u"\x1bOS", curses.KEY_F4),
+)
+
+#: Override mixins for a few curses constants with easier
+#: mnemonics: there may only be a 1:1 mapping when only a
+#: keycode (int) is given, where these phrases are preferred.
+CURSES_KEYCODE_OVERRIDE_MIXIN = (
+ ('KEY_DELETE', curses.KEY_DC),
+ ('KEY_INSERT', curses.KEY_IC),
+ ('KEY_PGUP', curses.KEY_PPAGE),
+ ('KEY_PGDOWN', curses.KEY_NPAGE),
+ ('KEY_ESCAPE', curses.KEY_EXIT),
+ ('KEY_SUP', curses.KEY_SR),
+ ('KEY_SDOWN', curses.KEY_SF),
+ ('KEY_UP_LEFT', curses.KEY_A1),
+ ('KEY_UP_RIGHT', curses.KEY_A3),
+ ('KEY_CENTER', curses.KEY_B2),
+ ('KEY_BEGIN', curses.KEY_BEG),
+)
+
+__all__ = ('Keystroke', 'get_keyboard_codes', 'get_keyboard_sequences',)
diff --git a/third_party/python/blessed/blessed/keyboard.pyi b/third_party/python/blessed/blessed/keyboard.pyi
new file mode 100644
index 0000000000..ae76393f14
--- /dev/null
+++ b/third_party/python/blessed/blessed/keyboard.pyi
@@ -0,0 +1,28 @@
+# std imports
+from typing import Set, Dict, Type, Mapping, TypeVar, Iterable, Optional, OrderedDict
+
+# local
+from .terminal import Terminal
+
+_T = TypeVar("_T")
+
+class Keystroke(str):
+ def __new__(
+ cls: Type[_T],
+ ucs: str = ...,
+ code: Optional[int] = ...,
+ name: Optional[str] = ...,
+ ) -> _T: ...
+ @property
+ def is_sequence(self) -> bool: ...
+ @property
+ def name(self) -> Optional[str]: ...
+ @property
+ def code(self) -> Optional[int]: ...
+
+def get_keyboard_codes() -> Dict[int, str]: ...
+def get_keyboard_sequences(term: Terminal) -> OrderedDict[str, int]: ...
+def get_leading_prefixes(sequences: Iterable[str]) -> Set[str]: ...
+def resolve_sequence(
+ text: str, mapper: Mapping[str, int], codes: Mapping[int, str]
+) -> Keystroke: ...
diff --git a/third_party/python/blessed/blessed/py.typed b/third_party/python/blessed/blessed/py.typed
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/third_party/python/blessed/blessed/py.typed
diff --git a/third_party/python/blessed/blessed/sequences.py b/third_party/python/blessed/blessed/sequences.py
new file mode 100644
index 0000000000..6735ab8504
--- /dev/null
+++ b/third_party/python/blessed/blessed/sequences.py
@@ -0,0 +1,461 @@
+# -*- coding: utf-8 -*-
+"""Module providing 'sequence awareness'."""
+# std imports
+import re
+import math
+import textwrap
+
+# 3rd party
+import six
+from wcwidth import wcwidth
+
+# local
+from blessed._capabilities import CAPABILITIES_CAUSE_MOVEMENT
+
+__all__ = ('Sequence', 'SequenceTextWrapper', 'iter_parse', 'measure_length')
+
+
+class Termcap(object):
+ """Terminal capability of given variable name and pattern."""
+
+ def __init__(self, name, pattern, attribute):
+ """
+ Class initializer.
+
+ :arg str name: name describing capability.
+ :arg str pattern: regular expression string.
+ :arg str attribute: :class:`~.Terminal` attribute used to build
+ this terminal capability.
+ """
+ self.name = name
+ self.pattern = pattern
+ self.attribute = attribute
+ self._re_compiled = None
+
+ def __repr__(self):
+ # pylint: disable=redundant-keyword-arg
+ return '<Termcap {self.name}:{self.pattern!r}>'.format(self=self)
+
+ @property
+ def named_pattern(self):
+ """Regular expression pattern for capability with named group."""
+ # pylint: disable=redundant-keyword-arg
+ return '(?P<{self.name}>{self.pattern})'.format(self=self)
+
+ @property
+ def re_compiled(self):
+ """Compiled regular expression pattern for capability."""
+ if self._re_compiled is None:
+ self._re_compiled = re.compile(self.pattern)
+ return self._re_compiled
+
+ @property
+ def will_move(self):
+ """Whether capability causes cursor movement."""
+ return self.name in CAPABILITIES_CAUSE_MOVEMENT
+
+ def horizontal_distance(self, text):
+ """
+ Horizontal carriage adjusted by capability, may be negative.
+
+ :rtype: int
+ :arg str text: for capabilities *parm_left_cursor*,
+ *parm_right_cursor*, provide the matching sequence
+ text, its interpreted distance is returned.
+
+ :returns: 0 except for matching '
+ """
+ value = {
+ 'cursor_left': -1,
+ 'backspace': -1,
+ 'cursor_right': 1,
+ 'tab': 8,
+ 'ascii_tab': 8,
+ }.get(self.name, None)
+ if value is not None:
+ return value
+
+ unit = {
+ 'parm_left_cursor': -1,
+ 'parm_right_cursor': 1
+ }.get(self.name, None)
+ if unit is not None:
+ value = int(self.re_compiled.match(text).group(1))
+ return unit * value
+
+ return 0
+
+ # pylint: disable=too-many-arguments
+ @classmethod
+ def build(cls, name, capability, attribute, nparams=0,
+ numeric=99, match_grouped=False, match_any=False,
+ match_optional=False):
+ r"""
+ Class factory builder for given capability definition.
+
+ :arg str name: Variable name given for this pattern.
+ :arg str capability: A unicode string representing a terminal
+ capability to build for. When ``nparams`` is non-zero, it
+ must be a callable unicode string (such as the result from
+ ``getattr(term, 'bold')``.
+ :arg str attribute: The terminfo(5) capability name by which this
+ pattern is known.
+ :arg int nparams: number of positional arguments for callable.
+ :arg int numeric: Value to substitute into capability to when generating pattern
+ :arg bool match_grouped: If the numeric pattern should be
+ grouped, ``(\d+)`` when ``True``, ``\d+`` default.
+ :arg bool match_any: When keyword argument ``nparams`` is given,
+ *any* numeric found in output is suitable for building as
+ pattern ``(\d+)``. Otherwise, only the first matching value of
+ range *(numeric - 1)* through *(numeric + 1)* will be replaced by
+ pattern ``(\d+)`` in builder.
+ :arg bool match_optional: When ``True``, building of numeric patterns
+ containing ``(\d+)`` will be built as optional, ``(\d+)?``.
+ :rtype: blessed.sequences.Termcap
+ :returns: Terminal capability instance for given capability definition
+ """
+ _numeric_regex = r'\d+'
+ if match_grouped:
+ _numeric_regex = r'(\d+)'
+ if match_optional:
+ _numeric_regex = r'(\d+)?'
+ numeric = 99 if numeric is None else numeric
+
+ # basic capability attribute, not used as a callable
+ if nparams == 0:
+ return cls(name, re.escape(capability), attribute)
+
+ # a callable capability accepting numeric argument
+ _outp = re.escape(capability(*(numeric,) * nparams))
+ if not match_any:
+ for num in range(numeric - 1, numeric + 2):
+ if str(num) in _outp:
+ pattern = _outp.replace(str(num), _numeric_regex)
+ return cls(name, pattern, attribute)
+
+ if match_grouped:
+ pattern = re.sub(r'(\d+)', lambda x: _numeric_regex, _outp)
+ else:
+ pattern = re.sub(r'\d+', lambda x: _numeric_regex, _outp)
+ return cls(name, pattern, attribute)
+
+
+class SequenceTextWrapper(textwrap.TextWrapper):
+ """Docstring overridden."""
+
+ def __init__(self, width, term, **kwargs):
+ """
+ Class initializer.
+
+ This class supports the :meth:`~.Terminal.wrap` method.
+ """
+ self.term = term
+ textwrap.TextWrapper.__init__(self, width, **kwargs)
+
+ def _wrap_chunks(self, chunks):
+ """
+ Sequence-aware variant of :meth:`textwrap.TextWrapper._wrap_chunks`.
+
+ :raises ValueError: ``self.width`` is not a positive integer
+ :rtype: list
+ :returns: text chunks adjusted for width
+
+ This simply ensures that word boundaries are not broken mid-sequence, as standard python
+ textwrap would incorrectly determine the length of a string containing sequences, and may
+ also break consider sequences part of a "word" that may be broken by hyphen (``-``), where
+ this implementation corrects both.
+ """
+ lines = []
+ if self.width <= 0 or not isinstance(self.width, int):
+ raise ValueError(
+ "invalid width {0!r}({1!r}) (must be integer > 0)"
+ .format(self.width, type(self.width)))
+
+ term = self.term
+ drop_whitespace = not hasattr(self, 'drop_whitespace'
+ ) or self.drop_whitespace
+ chunks.reverse()
+ while chunks:
+ cur_line = []
+ cur_len = 0
+ indent = self.subsequent_indent if lines else self.initial_indent
+ width = self.width - len(indent)
+ if drop_whitespace and (
+ Sequence(chunks[-1], term).strip() == '' and lines):
+ del chunks[-1]
+ while chunks:
+ chunk_len = Sequence(chunks[-1], term).length()
+ if cur_len + chunk_len > width:
+ break
+ cur_line.append(chunks.pop())
+ cur_len += chunk_len
+ if chunks and Sequence(chunks[-1], term).length() > width:
+ self._handle_long_word(chunks, cur_line, cur_len, width)
+ if drop_whitespace and (
+ cur_line and Sequence(cur_line[-1], term).strip() == ''):
+ del cur_line[-1]
+ if cur_line:
+ lines.append(indent + u''.join(cur_line))
+ return lines
+
+ def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
+ """
+ Sequence-aware :meth:`textwrap.TextWrapper._handle_long_word`.
+
+ This simply ensures that word boundaries are not broken mid-sequence, as standard python
+ textwrap would incorrectly determine the length of a string containing sequences, and may
+ also break consider sequences part of a "word" that may be broken by hyphen (``-``), where
+ this implementation corrects both.
+ """
+ # Figure out when indent is larger than the specified width, and make
+ # sure at least one character is stripped off on every pass
+ space_left = 1 if width < 1 else width - cur_len
+ # If we're allowed to break long words, then do so: put as much
+ # of the next chunk onto the current line as will fit.
+
+ if self.break_long_words:
+ term = self.term
+ chunk = reversed_chunks[-1]
+ idx = nxt = 0
+ for text, _ in iter_parse(term, chunk):
+ nxt += len(text)
+ if Sequence(chunk[:nxt], term).length() > space_left:
+ break
+ idx = nxt
+ cur_line.append(chunk[:idx])
+ reversed_chunks[-1] = chunk[idx:]
+
+ # Otherwise, we have to preserve the long word intact. Only add
+ # it to the current line if there's nothing already there --
+ # that minimizes how much we violate the width constraint.
+ elif not cur_line:
+ cur_line.append(reversed_chunks.pop())
+
+ # If we're not allowed to break long words, and there's already
+ # text on the current line, do nothing. Next time through the
+ # main loop of _wrap_chunks(), we'll wind up here again, but
+ # cur_len will be zero, so the next line will be entirely
+ # devoted to the long word that we can't handle right now.
+
+
+SequenceTextWrapper.__doc__ = textwrap.TextWrapper.__doc__
+
+
+class Sequence(six.text_type):
+ """
+ A "sequence-aware" version of the base :class:`str` class.
+
+ This unicode-derived class understands the effect of escape sequences
+ of printable length, allowing a properly implemented :meth:`rjust`,
+ :meth:`ljust`, :meth:`center`, and :meth:`length`.
+ """
+
+ def __new__(cls, sequence_text, term):
+ # pylint: disable = missing-return-doc, missing-return-type-doc
+ """
+ Class constructor.
+
+ :arg str sequence_text: A string that may contain sequences.
+ :arg blessed.Terminal term: :class:`~.Terminal` instance.
+ """
+ new = six.text_type.__new__(cls, sequence_text)
+ new._term = term
+ return new
+
+ def ljust(self, width, fillchar=u' '):
+ """
+ Return string containing sequences, left-adjusted.
+
+ :arg int width: Total width given to left-adjust ``text``. If
+ unspecified, the width of the attached terminal is used (default).
+ :arg str fillchar: String for padding right-of ``text``.
+ :returns: String of ``text``, left-aligned by ``width``.
+ :rtype: str
+ """
+ rightside = fillchar * int(
+ (max(0.0, float(width.__index__() - self.length()))) / float(len(fillchar)))
+ return u''.join((self, rightside))
+
+ def rjust(self, width, fillchar=u' '):
+ """
+ Return string containing sequences, right-adjusted.
+
+ :arg int width: Total width given to right-adjust ``text``. If
+ unspecified, the width of the attached terminal is used (default).
+ :arg str fillchar: String for padding left-of ``text``.
+ :returns: String of ``text``, right-aligned by ``width``.
+ :rtype: str
+ """
+ leftside = fillchar * int(
+ (max(0.0, float(width.__index__() - self.length()))) / float(len(fillchar)))
+ return u''.join((leftside, self))
+
+ def center(self, width, fillchar=u' '):
+ """
+ Return string containing sequences, centered.
+
+ :arg int width: Total width given to center ``text``. If
+ unspecified, the width of the attached terminal is used (default).
+ :arg str fillchar: String for padding left and right-of ``text``.
+ :returns: String of ``text``, centered by ``width``.
+ :rtype: str
+ """
+ split = max(0.0, float(width.__index__()) - self.length()) / 2
+ leftside = fillchar * int(
+ (max(0.0, math.floor(split))) / float(len(fillchar)))
+ rightside = fillchar * int(
+ (max(0.0, math.ceil(split))) / float(len(fillchar)))
+ return u''.join((leftside, self, rightside))
+
+ def truncate(self, width):
+ """
+ Truncate a string in a sequence-aware manner.
+
+ Any printable characters beyond ``width`` are removed, while all
+ sequences remain in place. Horizontal Sequences are first expanded
+ by :meth:`padd`.
+
+ :arg int width: The printable width to truncate the string to.
+ :rtype: str
+ :returns: String truncated to at most ``width`` printable characters.
+ """
+ output = ""
+ current_width = 0
+ target_width = width.__index__()
+ parsed_seq = iter_parse(self._term, self.padd())
+
+ # Retain all text until non-cap width reaches desired width
+ for text, cap in parsed_seq:
+ if not cap:
+ # use wcwidth clipped to 0 because it can sometimes return -1
+ current_width += max(wcwidth(text), 0)
+ if current_width > target_width:
+ break
+ output += text
+
+ # Return with remaining caps appended
+ return output + ''.join(text for text, cap in parsed_seq if cap)
+
+ def length(self):
+ r"""
+ Return the printable length of string containing sequences.
+
+ Strings containing ``term.left`` or ``\b`` will cause "overstrike",
+ but a length less than 0 is not ever returned. So ``_\b+`` is a
+ length of 1 (displays as ``+``), but ``\b`` alone is simply a
+ length of 0.
+
+ Some characters may consume more than one cell, mainly those CJK
+ Unified Ideographs (Chinese, Japanese, Korean) defined by Unicode
+ as half or full-width characters.
+
+ For example:
+
+ >>> from blessed import Terminal
+ >>> from blessed.sequences import Sequence
+ >>> term = Terminal()
+ >>> msg = term.clear + term.red(u'コンニチハ')
+ >>> Sequence(msg, term).length()
+ 10
+
+ .. note:: Although accounted for, strings containing sequences such
+ as ``term.clear`` will not give accurate returns, it is not
+ considered lengthy (a length of 0).
+ """
+ # because control characters may return -1, "clip" their length to 0.
+ return sum(max(wcwidth(w_char), 0) for w_char in self.padd(strip=True))
+
+ def strip(self, chars=None):
+ """
+ Return string of sequences, leading and trailing whitespace removed.
+
+ :arg str chars: Remove characters in chars instead of whitespace.
+ :rtype: str
+ :returns: string of sequences with leading and trailing whitespace removed.
+ """
+ return self.strip_seqs().strip(chars)
+
+ def lstrip(self, chars=None):
+ """
+ Return string of all sequences and leading whitespace removed.
+
+ :arg str chars: Remove characters in chars instead of whitespace.
+ :rtype: str
+ :returns: string of sequences with leading removed.
+ """
+ return self.strip_seqs().lstrip(chars)
+
+ def rstrip(self, chars=None):
+ """
+ Return string of all sequences and trailing whitespace removed.
+
+ :arg str chars: Remove characters in chars instead of whitespace.
+ :rtype: str
+ :returns: string of sequences with trailing removed.
+ """
+ return self.strip_seqs().rstrip(chars)
+
+ def strip_seqs(self):
+ """
+ Return ``text`` stripped of only its terminal sequences.
+
+ :rtype: str
+ :returns: Text with terminal sequences removed
+ """
+ return self.padd(strip=True)
+
+ def padd(self, strip=False):
+ """
+ Return non-destructive horizontal movement as destructive spacing.
+
+ :arg bool strip: Strip terminal sequences
+ :rtype: str
+ :returns: Text adjusted for horizontal movement
+ """
+ outp = ''
+ for text, cap in iter_parse(self._term, self):
+ if not cap:
+ outp += text
+ continue
+
+ value = cap.horizontal_distance(text)
+ if value > 0:
+ outp += ' ' * value
+ elif value < 0:
+ outp = outp[:value]
+ elif not strip:
+ outp += text
+ return outp
+
+
+def iter_parse(term, text):
+ """
+ Generator yields (text, capability) for characters of ``text``.
+
+ value for ``capability`` may be ``None``, where ``text`` is
+ :class:`str` of length 1. Otherwise, ``text`` is a full
+ matching sequence of given capability.
+ """
+ for match in term._caps_compiled_any.finditer(text): # pylint: disable=protected-access
+ name = match.lastgroup
+ value = match.group(name)
+ if name == 'MISMATCH':
+ yield (value, None)
+ else:
+ yield value, term.caps[name]
+
+
+def measure_length(text, term):
+ """
+ .. deprecated:: 1.12.0.
+
+ :rtype: int
+ :returns: Length of the first sequence in the string
+ """
+ try:
+ text, capability = next(iter_parse(term, text))
+ if capability:
+ return len(text)
+ except StopIteration:
+ return 0
+ return 0
diff --git a/third_party/python/blessed/blessed/sequences.pyi b/third_party/python/blessed/blessed/sequences.pyi
new file mode 100644
index 0000000000..4460b7a466
--- /dev/null
+++ b/third_party/python/blessed/blessed/sequences.pyi
@@ -0,0 +1,55 @@
+# std imports
+import textwrap
+from typing import Any, Type, Tuple, Pattern, TypeVar, Iterator, Optional, SupportsIndex
+
+# local
+from .terminal import Terminal
+
+_T = TypeVar("_T")
+
+class Termcap:
+ name: str = ...
+ pattern: str = ...
+ attribute: str = ...
+ def __init__(self, name: str, pattern: str, attribute: str) -> None: ...
+ @property
+ def named_pattern(self) -> str: ...
+ @property
+ def re_compiled(self) -> Pattern[str]: ...
+ @property
+ def will_move(self) -> bool: ...
+ def horizontal_distance(self, text: str) -> int: ...
+ @classmethod
+ def build(
+ cls,
+ name: str,
+ capability: str,
+ attribute: str,
+ nparams: int = ...,
+ numeric: int = ...,
+ match_grouped: bool = ...,
+ match_any: bool = ...,
+ match_optional: bool = ...,
+ ) -> "Termcap": ...
+
+class SequenceTextWrapper(textwrap.TextWrapper):
+ term: Terminal = ...
+ def __init__(self, width: int, term: Terminal, **kwargs: Any) -> None: ...
+
+class Sequence(str):
+ def __new__(cls: Type[_T], sequence_text: str, term: Terminal) -> _T: ...
+ def ljust(self, width: SupportsIndex, fillchar: str = ...) -> str: ...
+ def rjust(self, width: SupportsIndex, fillchar: str = ...) -> str: ...
+ def center(self, width: SupportsIndex, fillchar: str = ...) -> str: ...
+ def truncate(self, width: SupportsIndex) -> str: ...
+ def length(self) -> int: ...
+ def strip(self, chars: Optional[str] = ...) -> str: ...
+ def lstrip(self, chars: Optional[str] = ...) -> str: ...
+ def rstrip(self, chars: Optional[str] = ...) -> str: ...
+ def strip_seqs(self) -> str: ...
+ def padd(self, strip: bool = ...) -> str: ...
+
+def iter_parse(
+ term: Terminal, text: str
+) -> Iterator[Tuple[str, Optional[Termcap]]]: ...
+def measure_length(text: str, term: Terminal) -> int: ...
diff --git a/third_party/python/blessed/blessed/terminal.py b/third_party/python/blessed/blessed/terminal.py
new file mode 100644
index 0000000000..38bd2bb66b
--- /dev/null
+++ b/third_party/python/blessed/blessed/terminal.py
@@ -0,0 +1,1502 @@
+# -*- coding: utf-8 -*-
+# pylint: disable=too-many-lines
+"""Module containing :class:`Terminal`, the primary API entry point."""
+# std imports
+import os
+import re
+import sys
+import time
+import codecs
+import locale
+import select
+import struct
+import platform
+import warnings
+import functools
+import contextlib
+import collections
+
+# local
+from .color import COLOR_DISTANCE_ALGORITHMS
+from .keyboard import (_time_left,
+ _read_until,
+ resolve_sequence,
+ get_keyboard_codes,
+ get_leading_prefixes,
+ get_keyboard_sequences)
+from .sequences import Termcap, Sequence, SequenceTextWrapper
+from .colorspace import RGB_256TABLE
+from .formatters import (COLORS,
+ COMPOUNDABLES,
+ FormattingString,
+ NullCallableString,
+ ParameterizingString,
+ FormattingOtherString,
+ split_compound,
+ resolve_attribute,
+ resolve_capability)
+from ._capabilities import CAPABILITY_DATABASE, CAPABILITIES_ADDITIVES, CAPABILITIES_RAW_MIXIN
+
+# isort: off
+
+try:
+ InterruptedError
+except NameError:
+ # alias py2 exception to py3
+ # pylint: disable=redefined-builtin
+ InterruptedError = select.error
+
+
+HAS_TTY = True
+if platform.system() == 'Windows':
+ IS_WINDOWS = True
+ import jinxed as curses # pylint: disable=import-error
+ from jinxed.win32 import get_console_input_encoding # pylint: disable=import-error
+else:
+ IS_WINDOWS = False
+ import curses
+
+ try:
+ import fcntl
+ import termios
+ import tty
+ except ImportError:
+ _TTY_METHODS = ('setraw', 'cbreak', 'kbhit', 'height', 'width')
+ _MSG_NOSUPPORT = (
+ "One or more of the modules: 'termios', 'fcntl', and 'tty' "
+ "are not found on your platform '{platform}'. "
+ "The following methods of Terminal are dummy/no-op "
+ "unless a deriving class overrides them: {tty_methods}."
+ .format(platform=platform.system(),
+ tty_methods=', '.join(_TTY_METHODS)))
+ warnings.warn(_MSG_NOSUPPORT)
+ HAS_TTY = False
+
+_CUR_TERM = None # See comments at end of file
+
+
+class Terminal(object):
+ """
+ An abstraction for color, style, positioning, and input in the terminal.
+
+ This keeps the endless calls to ``tigetstr()`` and ``tparm()`` out of your code, acts
+ intelligently when somebody pipes your output to a non-terminal, and abstracts over the
+ complexity of unbuffered keyboard input. It uses the terminfo database to remain portable across
+ terminal types.
+ """
+ # pylint: disable=too-many-instance-attributes,too-many-public-methods
+ # Too many public methods (28/20)
+ # Too many instance attributes (12/7)
+
+ #: Sugary names for commonly-used capabilities
+ _sugar = dict(
+ save='sc',
+ restore='rc',
+ clear_eol='el',
+ clear_bol='el1',
+ clear_eos='ed',
+ enter_fullscreen='smcup',
+ exit_fullscreen='rmcup',
+ move='cup',
+ move_yx='cup',
+ move_x='hpa',
+ move_y='vpa',
+ hide_cursor='civis',
+ normal_cursor='cnorm',
+ reset_colors='op',
+ normal='sgr0',
+ reverse='rev',
+ italic='sitm',
+ no_italic='ritm',
+ shadow='sshm',
+ no_shadow='rshm',
+ standout='smso',
+ no_standout='rmso',
+ subscript='ssubm',
+ no_subscript='rsubm',
+ superscript='ssupm',
+ no_superscript='rsupm',
+ underline='smul',
+ no_underline='rmul',
+ cursor_report='u6',
+ cursor_request='u7',
+ terminal_answerback='u8',
+ terminal_enquire='u9',
+ )
+
+ def __init__(self, kind=None, stream=None, force_styling=False):
+ """
+ Initialize the terminal.
+
+ :arg str kind: A terminal string as taken by :func:`curses.setupterm`.
+ Defaults to the value of the ``TERM`` environment variable.
+
+ .. note:: Terminals withing a single process must share a common
+ ``kind``. See :obj:`_CUR_TERM`.
+
+ :arg file stream: A file-like object representing the Terminal output.
+ Defaults to the original value of :obj:`sys.__stdout__`, like
+ :func:`curses.initscr` does.
+
+ If ``stream`` is not a tty, empty Unicode strings are returned for
+ all capability values, so things like piping your program output to
+ a pipe or file does not emit terminal sequences.
+
+ :arg bool force_styling: Whether to force the emission of capabilities
+ even if :obj:`sys.__stdout__` does not seem to be connected to a
+ terminal. If you want to force styling to not happen, use
+ ``force_styling=None``.
+
+ This comes in handy if users are trying to pipe your output through
+ something like ``less -r`` or build systems which support decoding
+ of terminal sequences.
+ """
+ # pylint: disable=global-statement,too-many-branches
+ global _CUR_TERM
+ self.errors = ['parameters: kind=%r, stream=%r, force_styling=%r' %
+ (kind, stream, force_styling)]
+ self._normal = None # cache normal attr, preventing recursive lookups
+ # we assume our input stream to be line-buffered until either the
+ # cbreak of raw context manager methods are entered with an attached tty.
+ self._line_buffered = True
+
+ self._stream = stream
+ self._keyboard_fd = None
+ self._init_descriptor = None
+ self._is_a_tty = False
+ self.__init__streams()
+
+ if IS_WINDOWS and self._init_descriptor is not None:
+ self._kind = kind or curses.get_term(self._init_descriptor)
+ else:
+ self._kind = kind or os.environ.get('TERM', 'dumb') or 'dumb'
+
+ self._does_styling = False
+ if force_styling is None and self.is_a_tty:
+ self.errors.append('force_styling is None')
+ elif force_styling or self.is_a_tty:
+ self._does_styling = True
+
+ if self.does_styling:
+ # Initialize curses (call setupterm), so things like tigetstr() work.
+ try:
+ curses.setupterm(self._kind, self._init_descriptor)
+ except curses.error as err:
+ msg = 'Failed to setupterm(kind={0!r}): {1}'.format(self._kind, err)
+ warnings.warn(msg)
+ self.errors.append(msg)
+ self._kind = None
+ self._does_styling = False
+ else:
+ if _CUR_TERM is None or self._kind == _CUR_TERM:
+ _CUR_TERM = self._kind
+ else:
+ # termcap 'kind' is immutable in a python process! Once
+ # initialized by setupterm, it is unsupported by the
+ # 'curses' module to change the terminal type again. If you
+ # are a downstream developer and you need this
+ # functionality, consider sub-processing, instead.
+ warnings.warn(
+ 'A terminal of kind "%s" has been requested; due to an'
+ ' internal python curses bug, terminal capabilities'
+ ' for a terminal of kind "%s" will continue to be'
+ ' returned for the remainder of this process.' % (
+ self._kind, _CUR_TERM,))
+
+ self.__init__color_capabilities()
+ self.__init__capabilities()
+ self.__init__keycodes()
+
+ def __init__streams(self):
+ # pylint: disable=too-complex,too-many-branches
+ # Agree to disagree !
+ stream_fd = None
+
+ # Default stream is stdout
+ if self._stream is None:
+ self._stream = sys.__stdout__
+
+ if not hasattr(self._stream, 'fileno'):
+ self.errors.append('stream has no fileno method')
+ elif not callable(self._stream.fileno):
+ self.errors.append('stream.fileno is not callable')
+ else:
+ try:
+ stream_fd = self._stream.fileno()
+ except ValueError as err:
+ # The stream is not a file, such as the case of StringIO, or, when it has been
+ # "detached", such as might be the case of stdout in some test scenarios.
+ self.errors.append('Unable to determine output stream file descriptor: %s' % err)
+ else:
+ self._is_a_tty = os.isatty(stream_fd)
+ if not self._is_a_tty:
+ self.errors.append('stream not a TTY')
+
+ # Keyboard valid as stdin only when output stream is stdout or stderr and is a tty.
+ if self._stream in (sys.__stdout__, sys.__stderr__):
+ try:
+ self._keyboard_fd = sys.__stdin__.fileno()
+ except (AttributeError, ValueError) as err:
+ self.errors.append('Unable to determine input stream file descriptor: %s' % err)
+ else:
+ # _keyboard_fd only non-None if both stdin and stdout is a tty.
+ if not self.is_a_tty:
+ self.errors.append('Output stream is not a TTY')
+ self._keyboard_fd = None
+ elif not os.isatty(self._keyboard_fd):
+ self.errors.append('Input stream is not a TTY')
+ self._keyboard_fd = None
+ else:
+ self.errors.append('Output stream is not a default stream')
+
+ # The descriptor to direct terminal initialization sequences to.
+ self._init_descriptor = stream_fd
+ if stream_fd is None:
+ try:
+ self._init_descriptor = sys.__stdout__.fileno()
+ except ValueError as err:
+ self.errors.append('Unable to determine __stdout__ file descriptor: %s' % err)
+
+ def __init__color_capabilities(self):
+ self._color_distance_algorithm = 'cie2000'
+ if not self.does_styling:
+ self.number_of_colors = 0
+ elif IS_WINDOWS or os.environ.get('COLORTERM') in ('truecolor', '24bit'):
+ self.number_of_colors = 1 << 24
+ else:
+ self.number_of_colors = max(0, curses.tigetnum('colors') or -1)
+
+ def __clear_color_capabilities(self):
+ for cached_color_cap in set(dir(self)) & COLORS:
+ delattr(self, cached_color_cap)
+
+ def __init__capabilities(self):
+ # important that we lay these in their ordered direction, so that our
+ # preferred, 'color' over 'set_a_attributes1', for example.
+ self.caps = collections.OrderedDict()
+
+ # some static injected patterns, esp. without named attribute access.
+ for name, (attribute, pattern) in CAPABILITIES_ADDITIVES.items():
+ self.caps[name] = Termcap(name, pattern, attribute)
+
+ for name, (attribute, kwds) in CAPABILITY_DATABASE.items():
+ if self.does_styling:
+ # attempt dynamic lookup
+ cap = getattr(self, attribute)
+ if cap:
+ self.caps[name] = Termcap.build(
+ name, cap, attribute, **kwds)
+ continue
+
+ # fall-back
+ pattern = CAPABILITIES_RAW_MIXIN.get(name)
+ if pattern:
+ self.caps[name] = Termcap(name, pattern, attribute)
+
+ # make a compiled named regular expression table
+ self.caps_compiled = re.compile(
+ '|'.join(cap.pattern for name, cap in self.caps.items()))
+
+ # for tokenizer, the '.lastgroup' is the primary lookup key for
+ # 'self.caps', unless 'MISMATCH'; then it is an unmatched character.
+ self._caps_compiled_any = re.compile('|'.join(
+ cap.named_pattern for name, cap in self.caps.items()
+ ) + '|(?P<MISMATCH>.)')
+ self._caps_unnamed_any = re.compile('|'.join(
+ '({0})'.format(cap.pattern) for name, cap in self.caps.items()
+ ) + '|(.)')
+
+ def __init__keycodes(self):
+ # Initialize keyboard data determined by capability.
+ # Build database of int code <=> KEY_NAME.
+ self._keycodes = get_keyboard_codes()
+
+ # Store attributes as: self.KEY_NAME = code.
+ for key_code, key_name in self._keycodes.items():
+ setattr(self, key_name, key_code)
+
+ # Build database of sequence <=> KEY_NAME.
+ self._keymap = get_keyboard_sequences(self)
+
+ # build set of prefixes of sequences
+ self._keymap_prefixes = get_leading_prefixes(self._keymap)
+
+ # keyboard stream buffer
+ self._keyboard_buf = collections.deque()
+
+ if self._keyboard_fd is not None:
+ # set input encoding and initialize incremental decoder
+
+ if IS_WINDOWS:
+ self._encoding = get_console_input_encoding() \
+ or locale.getpreferredencoding() or 'UTF-8'
+ else:
+ self._encoding = locale.getpreferredencoding() or 'UTF-8'
+
+ try:
+ self._keyboard_decoder = codecs.getincrementaldecoder(self._encoding)()
+ except LookupError as err:
+ # encoding is illegal or unsupported, use 'UTF-8'
+ warnings.warn('LookupError: {0}, defaulting to UTF-8 for keyboard.'.format(err))
+ self._encoding = 'UTF-8'
+ self._keyboard_decoder = codecs.getincrementaldecoder(self._encoding)()
+
+ def __getattr__(self, attr):
+ r"""
+ Return a terminal capability as Unicode string.
+
+ For example, ``term.bold`` is a unicode string that may be prepended
+ to text to set the video attribute for bold, which should also be
+ terminated with the pairing :attr:`normal`. This capability
+ returns a callable, so you can use ``term.bold("hi")`` which
+ results in the joining of ``(term.bold, "hi", term.normal)``.
+
+ Compound formatters may also be used. For example::
+
+ >>> term.bold_blink_red_on_green("merry x-mas!")
+
+ For a parameterized capability such as ``move`` (or ``cup``), pass the
+ parameters as positional arguments::
+
+ >>> term.move(line, column)
+
+ See the manual page `terminfo(5)
+ <https://invisible-island.net/ncurses/man/terminfo.5.html>`_ for a
+ complete list of capabilities and their arguments.
+ """
+ if not self._does_styling:
+ return NullCallableString()
+ # Fetch the missing 'attribute' into some kind of curses-resolved
+ # capability, and cache by attaching to this Terminal class instance.
+ #
+ # Note that this will prevent future calls to __getattr__(), but
+ # that's precisely the idea of the cache!
+ val = resolve_attribute(self, attr)
+ setattr(self, attr, val)
+ return val
+
+ @property
+ def kind(self):
+ """
+ Read-only property: Terminal kind determined on class initialization.
+
+ :rtype: str
+ """
+ return self._kind
+
+ @property
+ def does_styling(self):
+ """
+ Read-only property: Whether this class instance may emit sequences.
+
+ :rtype: bool
+ """
+ return self._does_styling
+
+ @property
+ def is_a_tty(self):
+ """
+ Read-only property: Whether :attr:`~.stream` is a terminal.
+
+ :rtype: bool
+ """
+ return self._is_a_tty
+
+ @property
+ def height(self):
+ """
+ Read-only property: Height of the terminal (in number of lines).
+
+ :rtype: int
+ """
+ return self._height_and_width().ws_row
+
+ @property
+ def width(self):
+ """
+ Read-only property: Width of the terminal (in number of columns).
+
+ :rtype: int
+ """
+ return self._height_and_width().ws_col
+
+ @property
+ def pixel_height(self):
+ """
+ Read-only property: Height ofthe terminal (in pixels).
+
+ :rtype: int
+ """
+ return self._height_and_width().ws_ypixel
+
+ @property
+ def pixel_width(self):
+ """
+ Read-only property: Width of terminal (in pixels).
+
+ :rtype: int
+ """
+ return self._height_and_width().ws_xpixel
+
+ @staticmethod
+ def _winsize(fd):
+ """
+ Return named tuple describing size of the terminal by ``fd``.
+
+ If the given platform does not have modules :mod:`termios`,
+ :mod:`fcntl`, or :mod:`tty`, window size of 80 columns by 25
+ rows is always returned.
+
+ :arg int fd: file descriptor queries for its window size.
+ :raises IOError: the file descriptor ``fd`` is not a terminal.
+ :rtype: WINSZ
+ :returns: named tuple describing size of the terminal
+
+ WINSZ is a :class:`collections.namedtuple` instance, whose structure
+ directly maps to the return value of the :const:`termios.TIOCGWINSZ`
+ ioctl return value. The return parameters are:
+
+ - ``ws_row``: width of terminal by its number of character cells.
+ - ``ws_col``: height of terminal by its number of character cells.
+ - ``ws_xpixel``: width of terminal by pixels (not accurate).
+ - ``ws_ypixel``: height of terminal by pixels (not accurate).
+ """
+ if HAS_TTY:
+ # pylint: disable=protected-access
+ data = fcntl.ioctl(fd, termios.TIOCGWINSZ, WINSZ._BUF)
+ return WINSZ(*struct.unpack(WINSZ._FMT, data))
+ return WINSZ(ws_row=25, ws_col=80, ws_xpixel=0, ws_ypixel=0)
+
+ def _height_and_width(self):
+ """
+ Return a tuple of (terminal height, terminal width).
+
+ If :attr:`stream` or :obj:`sys.__stdout__` is not a tty or does not
+ support :func:`fcntl.ioctl` of :const:`termios.TIOCGWINSZ`, a window
+ size of 80 columns by 25 rows is returned for any values not
+ represented by environment variables ``LINES`` and ``COLUMNS``, which
+ is the default text mode of IBM PC compatibles.
+
+ :rtype: WINSZ
+ :returns: Named tuple specifying the terminal size
+
+ WINSZ is a :class:`collections.namedtuple` instance, whose structure
+ directly maps to the return value of the :const:`termios.TIOCGWINSZ`
+ ioctl return value. The return parameters are:
+
+ - ``ws_row``: height of terminal by its number of cell rows.
+ - ``ws_col``: width of terminal by its number of cell columns.
+ - ``ws_xpixel``: width of terminal by pixels (not accurate).
+ - ``ws_ypixel``: height of terminal by pixels (not accurate).
+
+ .. note:: the peculiar (height, width, width, height) order, which
+ matches the return order of TIOCGWINSZ!
+ """
+ for fd in (self._init_descriptor, sys.__stdout__):
+ try:
+ if fd is not None:
+ return self._winsize(fd)
+ except (IOError, OSError, ValueError, TypeError): # pylint: disable=overlapping-except
+ pass
+
+ return WINSZ(ws_row=int(os.getenv('LINES', '25')),
+ ws_col=int(os.getenv('COLUMNS', '80')),
+ ws_xpixel=None,
+ ws_ypixel=None)
+
+ @contextlib.contextmanager
+ def location(self, x=None, y=None):
+ """
+ Context manager for temporarily moving the cursor.
+
+ :arg int x: horizontal position, from left, *0*, to right edge of screen, *self.width - 1*.
+ :arg int y: vertical position, from top, *0*, to bottom of screen, *self.height - 1*.
+ :return: a context manager.
+ :rtype: Iterator
+
+ Move the cursor to a certain position on entry, do any kind of I/O, and upon exit
+ let you print stuff there, then return the cursor to its original position:
+
+
+ .. code-block:: python
+
+ term = Terminal()
+ with term.location(y=0, x=0):
+ for row_num in range(term.height-1):
+ print('Row #{row_num}')
+ print(term.clear_eol + 'Back to original location.')
+
+ Specify ``x`` to move to a certain column, ``y`` to move to a certain
+ row, both, or neither. If you specify neither, only the saving and
+ restoration of cursor position will happen. This can be useful if you
+ simply want to restore your place after doing some manual cursor
+ movement.
+
+ Calls cannot be nested: only one should be entered at a time.
+
+ .. note:: The argument order *(x, y)* differs from the return value order *(y, x)*
+ of :meth:`get_location`, or argument order *(y, x)* of :meth:`move`. This is
+ for API Compaibility with the blessings library, sorry for the trouble!
+ """
+ # pylint: disable=invalid-name
+ # Invalid argument name "x"
+
+ # Save position and move to the requested column, row, or both:
+ self.stream.write(self.save)
+ if x is not None and y is not None:
+ self.stream.write(self.move(y, x))
+ elif x is not None:
+ self.stream.write(self.move_x(x))
+ elif y is not None:
+ self.stream.write(self.move_y(y))
+ try:
+ self.stream.flush()
+ yield
+ finally:
+ # Restore original cursor position:
+ self.stream.write(self.restore)
+ self.stream.flush()
+
+ def get_location(self, timeout=None):
+ r"""
+ Return tuple (row, column) of cursor position.
+
+ :arg float timeout: Return after time elapsed in seconds with value ``(-1, -1)`` indicating
+ that the remote end did not respond.
+ :rtype: tuple
+ :returns: cursor position as tuple in form of ``(y, x)``. When a timeout is specified,
+ always ensure the return value is checked for ``(-1, -1)``.
+
+ The location of the cursor is determined by emitting the ``u7`` terminal capability, or
+ VT100 `Query Cursor Position
+ <https://www2.ccs.neu.edu/research/gpc/VonaUtils/vona/terminal/vtansi.htm#status>`_
+ when such capability is undefined, which elicits a response from a reply string described by
+ capability ``u6``, or again VT100's definition of ``\x1b[%i%d;%dR`` when undefined.
+
+ The ``(y, x)`` return value matches the parameter order of the :meth:`move_xy` capability.
+ The following sequence should cause the cursor to not move at all::
+
+ >>> term = Terminal()
+ >>> term.move_yx(*term.get_location()))
+
+ And the following should assert True with a terminal:
+
+ >>> term = Terminal()
+ >>> given_y, given_x = 10, 20
+ >>> with term.location(y=given_y, x=given_x):
+ ... result_y, result_x = term.get_location()
+ ...
+ >>> assert given_x == result_x, (given_x, result_x)
+ >>> assert given_y == result_y, (given_y, result_y)
+
+ """
+ # Local lines attached by termios and remote login protocols such as
+ # ssh and telnet both provide a means to determine the window
+ # dimensions of a connected client, but **no means to determine the
+ # location of the cursor**.
+ #
+ # from https://invisible-island.net/ncurses/terminfo.src.html,
+ #
+ # > The System V Release 4 and XPG4 terminfo format defines ten string
+ # > capabilities for use by applications, <u0>...<u9>. In this file,
+ # > we use certain of these capabilities to describe functions which
+ # > are not covered by terminfo. The mapping is as follows:
+ # >
+ # > u9 terminal enquire string (equiv. to ANSI/ECMA-48 DA)
+ # > u8 terminal answerback description
+ # > u7 cursor position request (equiv. to VT100/ANSI/ECMA-48 DSR 6)
+ # > u6 cursor position report (equiv. to ANSI/ECMA-48 CPR)
+ query_str = self.u7 or u'\x1b[6n'
+ response_str = getattr(self, self.caps['cursor_report'].attribute) or u'\x1b[%i%d;%dR'
+
+ # determine response format as a regular expression
+ response_re = self.caps['cursor_report'].re_compiled
+
+ # Avoid changing user's desired raw or cbreak mode if already entered,
+ # by entering cbreak mode ourselves. This is necessary to receive user
+ # input without awaiting a human to press the return key. This mode
+ # also disables echo, which we should also hide, as our input is an
+ # sequence that is not meaningful for display as an output sequence.
+
+ ctx = None
+ try:
+ if self._line_buffered:
+ ctx = self.cbreak()
+ ctx.__enter__() # pylint: disable=no-member
+
+ # emit the 'query cursor position' sequence,
+ self.stream.write(query_str)
+ self.stream.flush()
+
+ # expect a response,
+ match, data = _read_until(term=self,
+ pattern=response_re,
+ timeout=timeout)
+
+ # ensure response sequence is excluded from subsequent input,
+ if match:
+ data = (data[:match.start()] + data[match.end():])
+
+ # re-buffer keyboard data, if any
+ self.ungetch(data)
+
+ if match:
+ # return matching sequence response, the cursor location.
+ row, col = (int(val) for val in match.groups())
+
+ # Per https://invisible-island.net/ncurses/terminfo.src.html
+ # The cursor position report (<u6>) string must contain two
+ # scanf(3)-style %d format elements. The first of these must
+ # correspond to the Y coordinate and the second to the %d.
+ # If the string contains the sequence %i, it is taken as an
+ # instruction to decrement each value after reading it (this is
+ # the inverse sense from the cup string).
+ if u'%i' in response_str:
+ row -= 1
+ col -= 1
+ return row, col
+
+ finally:
+ if ctx is not None:
+ ctx.__exit__(None, None, None) # pylint: disable=no-member
+
+ # We chose to return an illegal value rather than an exception,
+ # favoring that users author function filters, such as max(0, y),
+ # rather than crowbarring such logic into an exception handler.
+ return -1, -1
+
+ @contextlib.contextmanager
+ def fullscreen(self):
+ """
+ Context manager that switches to secondary screen, restoring on exit.
+
+ Under the hood, this switches between the primary screen buffer and
+ the secondary one. The primary one is saved on entry and restored on
+ exit. Likewise, the secondary contents are also stable and are
+ faithfully restored on the next entry::
+
+ with term.fullscreen():
+ main()
+
+ .. note:: There is only one primary and one secondary screen buffer.
+ :meth:`fullscreen` calls cannot be nested, only one should be
+ entered at a time.
+ """
+ self.stream.write(self.enter_fullscreen)
+ self.stream.flush()
+ try:
+ yield
+ finally:
+ self.stream.write(self.exit_fullscreen)
+ self.stream.flush()
+
+ @contextlib.contextmanager
+ def hidden_cursor(self):
+ """
+ Context manager that hides the cursor, setting visibility on exit.
+
+ with term.hidden_cursor():
+ main()
+
+ .. note:: :meth:`hidden_cursor` calls cannot be nested: only one
+ should be entered at a time.
+ """
+ self.stream.write(self.hide_cursor)
+ self.stream.flush()
+ try:
+ yield
+ finally:
+ self.stream.write(self.normal_cursor)
+ self.stream.flush()
+
+ def move_xy(self, x, y):
+ """
+ A callable string that moves the cursor to the given ``(x, y)`` screen coordinates.
+
+ :arg int x: horizontal position, from left, *0*, to right edge of screen, *self.width - 1*.
+ :arg int y: vertical position, from top, *0*, to bottom of screen, *self.height - 1*.
+ :rtype: ParameterizingString
+ :returns: Callable string that moves the cursor to the given coordinates
+ """
+ # this is just a convenience alias to the built-in, but hidden 'move'
+ # attribute -- we encourage folks to use only (x, y) positional
+ # arguments, or, if they must use (y, x), then use the 'move_yx'
+ # alias.
+ return self.move(y, x)
+
+ def move_yx(self, y, x):
+ """
+ A callable string that moves the cursor to the given ``(y, x)`` screen coordinates.
+
+ :arg int y: vertical position, from top, *0*, to bottom of screen, *self.height - 1*.
+ :arg int x: horizontal position, from left, *0*, to right edge of screen, *self.width - 1*.
+ :rtype: ParameterizingString
+ :returns: Callable string that moves the cursor to the given coordinates
+ """
+ return self.move(y, x)
+
+ @property
+ def move_left(self):
+ """Move cursor 1 cells to the left, or callable string for n>1 cells."""
+ return FormattingOtherString(self.cub1, ParameterizingString(self.cub))
+
+ @property
+ def move_right(self):
+ """Move cursor 1 or more cells to the right, or callable string for n>1 cells."""
+ return FormattingOtherString(self.cuf1, ParameterizingString(self.cuf))
+
+ @property
+ def move_up(self):
+ """Move cursor 1 or more cells upwards, or callable string for n>1 cells."""
+ return FormattingOtherString(self.cuu1, ParameterizingString(self.cuu))
+
+ @property
+ def move_down(self):
+ """Move cursor 1 or more cells downwards, or callable string for n>1 cells."""
+ return FormattingOtherString(self.cud1, ParameterizingString(self.cud))
+
+ @property
+ def color(self):
+ """
+ A callable string that sets the foreground color.
+
+ :rtype: ParameterizingString
+
+ The capability is unparameterized until called and passed a number, at which point it
+ returns another string which represents a specific color change. This second string can
+ further be called to color a piece of text and set everything back to normal afterward.
+
+ This should not be used directly, but rather a specific color by name or
+ :meth:`~.Terminal.color_rgb` value.
+ """
+ if not self.does_styling:
+ return NullCallableString()
+ return ParameterizingString(self._foreground_color,
+ self.normal, 'color')
+
+ def color_rgb(self, red, green, blue):
+ """
+ Provides callable formatting string to set foreground color to the specified RGB color.
+
+ :arg int red: RGB value of Red.
+ :arg int green: RGB value of Green.
+ :arg int blue: RGB value of Blue.
+ :rtype: FormattingString
+ :returns: Callable string that sets the foreground color
+
+ If the terminal does not support RGB color, the nearest supported
+ color will be determined using :py:attr:`color_distance_algorithm`.
+ """
+ if self.number_of_colors == 1 << 24:
+ # "truecolor" 24-bit
+ fmt_attr = u'\x1b[38;2;{0};{1};{2}m'.format(red, green, blue)
+ return FormattingString(fmt_attr, self.normal)
+
+ # color by approximation to 256 or 16-color terminals
+ color_idx = self.rgb_downconvert(red, green, blue)
+ return FormattingString(self._foreground_color(color_idx), self.normal)
+
+ @property
+ def on_color(self):
+ """
+ A callable capability that sets the background color.
+
+ :rtype: ParameterizingString
+ """
+ if not self.does_styling:
+ return NullCallableString()
+ return ParameterizingString(self._background_color,
+ self.normal, 'on_color')
+
+ def on_color_rgb(self, red, green, blue):
+ """
+ Provides callable formatting string to set background color to the specified RGB color.
+
+ :arg int red: RGB value of Red.
+ :arg int green: RGB value of Green.
+ :arg int blue: RGB value of Blue.
+ :rtype: FormattingString
+ :returns: Callable string that sets the foreground color
+
+ If the terminal does not support RGB color, the nearest supported
+ color will be determined using :py:attr:`color_distance_algorithm`.
+ """
+ if self.number_of_colors == 1 << 24:
+ fmt_attr = u'\x1b[48;2;{0};{1};{2}m'.format(red, green, blue)
+ return FormattingString(fmt_attr, self.normal)
+
+ color_idx = self.rgb_downconvert(red, green, blue)
+ return FormattingString(self._background_color(color_idx), self.normal)
+
+ def formatter(self, value):
+ """
+ Provides callable formatting string to set color and other text formatting options.
+
+ :arg str value: Sugary, ordinary, or compound formatted terminal capability,
+ such as "red_on_white", "normal", "red", or "bold_on_black".
+ :rtype: :class:`FormattingString` or :class:`NullCallableString`
+ :returns: Callable string that sets color and other text formatting options
+
+ Calling ``term.formatter('bold_on_red')`` is equivalent to ``term.bold_on_red``, but a
+ string that is not a valid text formatter will return a :class:`NullCallableString`.
+ This is intended to allow validation of text formatters without the possibility of
+ inadvertently returning another terminal capability.
+ """
+ formatters = split_compound(value)
+ if all((fmt in COLORS or fmt in COMPOUNDABLES) for fmt in formatters):
+ return getattr(self, value)
+
+ return NullCallableString()
+
+ def rgb_downconvert(self, red, green, blue):
+ """
+ Translate an RGB color to a color code of the terminal's color depth.
+
+ :arg int red: RGB value of Red (0-255).
+ :arg int green: RGB value of Green (0-255).
+ :arg int blue: RGB value of Blue (0-255).
+ :rtype: int
+ :returns: Color code of downconverted RGB color
+ """
+ # Though pre-computing all 1 << 24 options is memory-intensive, a pre-computed
+ # "k-d tree" of 256 (x,y,z) vectors of a colorspace in 3 dimensions, such as a
+ # cone of HSV, or simply 255x255x255 RGB square, any given rgb value is just a
+ # nearest-neighbor search of 256 points, which k-d should be much faster by
+ # sub-dividing / culling search points, rather than our "search all 256 points
+ # always" approach.
+ fn_distance = COLOR_DISTANCE_ALGORITHMS[self.color_distance_algorithm]
+ color_idx = 7
+ shortest_distance = None
+ for cmp_depth, cmp_rgb in enumerate(RGB_256TABLE):
+ cmp_distance = fn_distance(cmp_rgb, (red, green, blue))
+ if shortest_distance is None or cmp_distance < shortest_distance:
+ shortest_distance = cmp_distance
+ color_idx = cmp_depth
+ if cmp_depth >= self.number_of_colors:
+ break
+ return color_idx
+
+ @property
+ def normal(self):
+ """
+ A capability that resets all video attributes.
+
+ :rtype: str
+
+ ``normal`` is an alias for ``sgr0`` or ``exit_attribute_mode``. Any
+ styling attributes previously applied, such as foreground or
+ background colors, reverse video, or bold are reset to defaults.
+ """
+ if self._normal:
+ return self._normal
+ self._normal = resolve_capability(self, 'normal')
+ return self._normal
+
+ def link(self, url, text, url_id=''):
+ """
+ Display ``text`` that when touched or clicked, navigates to ``url``.
+
+ Optional ``url_id`` may be specified, so that non-adjacent cells can reference a single
+ target, all cells painted with the same "id" will highlight on hover, rather than any
+ individual one, as described in "Hovering and underlining the id parameter" of gist
+ https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda.
+
+ :param str url: Hyperlink URL.
+ :param str text: Clickable text.
+ :param str url_id: Optional 'id'.
+ :rtype: str
+ :returns: String of ``text`` as a hyperlink to ``url``.
+ """
+ assert len(url) < 2000, (len(url), url)
+ if url_id:
+ assert len(str(url_id)) < 250, (len(str(url_id)), url_id)
+ params = 'id={0}'.format(url_id)
+ else:
+ params = ''
+ if not self.does_styling:
+ return text
+ return ('\x1b]8;{0};{1}\x1b\\{2}'
+ '\x1b]8;;\x1b\\'.format(params, url, text))
+
+ @property
+ def stream(self):
+ """
+ Read-only property: stream the terminal outputs to.
+
+ This is a convenience attribute. It is used internally for implied
+ writes performed by context managers :meth:`~.hidden_cursor`,
+ :meth:`~.fullscreen`, :meth:`~.location`, and :meth:`~.keypad`.
+ """
+ return self._stream
+
+ @property
+ def number_of_colors(self):
+ """
+ Number of colors supported by terminal.
+
+ Common return values are 0, 8, 16, 256, or 1 << 24.
+
+ This may be used to test whether the terminal supports colors,
+ and at what depth, if that's a concern.
+
+ If this property is assigned a value of 88, the value 16 will be saved. This is due to the
+ the rarity of 88 color support and the inconsistency of behavior between implementations.
+
+ Assigning this property to a value other than 0, 4, 8, 16, 88, 256, or 1 << 24 will
+ raise an :py:exc:`AssertionError`.
+ """
+ return self._number_of_colors
+
+ @number_of_colors.setter
+ def number_of_colors(self, value):
+ assert value in (0, 4, 8, 16, 88, 256, 1 << 24)
+ # Because 88 colors is rare and we can't guarantee consistent behavior,
+ # when 88 colors is detected, it is treated as 16 colors
+ self._number_of_colors = 16 if value == 88 else value
+ self.__clear_color_capabilities()
+
+ @property
+ def color_distance_algorithm(self):
+ """
+ Color distance algorithm used by :meth:`rgb_downconvert`.
+
+ The slowest, but most accurate, 'cie2000', is default. Other available options are 'rgb',
+ 'rgb-weighted', 'cie76', and 'cie94'.
+ """
+ return self._color_distance_algorithm
+
+ @color_distance_algorithm.setter
+ def color_distance_algorithm(self, value):
+ assert value in COLOR_DISTANCE_ALGORITHMS
+ self._color_distance_algorithm = value
+ self.__clear_color_capabilities()
+
+ @property
+ def _foreground_color(self):
+ """
+ Convenience capability to support :attr:`~.on_color`.
+
+ Prefers returning sequence for capability ``setaf``, "Set foreground color to #1, using ANSI
+ escape". If the given terminal does not support such sequence, fallback to returning
+ attribute ``setf``, "Set foreground color #1".
+ """
+ return self.setaf or self.setf
+
+ @property
+ def _background_color(self):
+ """
+ Convenience capability to support :attr:`~.on_color`.
+
+ Prefers returning sequence for capability ``setab``, "Set background color to #1, using ANSI
+ escape". If the given terminal does not support such sequence, fallback to returning
+ attribute ``setb``, "Set background color #1".
+ """
+ return self.setab or self.setb
+
+ def ljust(self, text, width=None, fillchar=u' '):
+ """
+ Left-align ``text``, which may contain terminal sequences.
+
+ :arg str text: String to be aligned
+ :arg int width: Total width to fill with aligned text. If
+ unspecified, the whole width of the terminal is filled.
+ :arg str fillchar: String for padding the right of ``text``
+ :rtype: str
+ :returns: String of ``text``, left-aligned by ``width``.
+ """
+ # Left justification is different from left alignment, but we continue
+ # the vocabulary error of the str method for polymorphism.
+ if width is None:
+ width = self.width
+ return Sequence(text, self).ljust(width, fillchar)
+
+ def rjust(self, text, width=None, fillchar=u' '):
+ """
+ Right-align ``text``, which may contain terminal sequences.
+
+ :arg str text: String to be aligned
+ :arg int width: Total width to fill with aligned text. If
+ unspecified, the whole width of the terminal is used.
+ :arg str fillchar: String for padding the left of ``text``
+ :rtype: str
+ :returns: String of ``text``, right-aligned by ``width``.
+ """
+ if width is None:
+ width = self.width
+ return Sequence(text, self).rjust(width, fillchar)
+
+ def center(self, text, width=None, fillchar=u' '):
+ """
+ Center ``text``, which may contain terminal sequences.
+
+ :arg str text: String to be centered
+ :arg int width: Total width in which to center text. If
+ unspecified, the whole width of the terminal is used.
+ :arg str fillchar: String for padding the left and right of ``text``
+ :rtype: str
+ :returns: String of ``text``, centered by ``width``
+ """
+ if width is None:
+ width = self.width
+ return Sequence(text, self).center(width, fillchar)
+
+ def truncate(self, text, width=None):
+ r"""
+ Truncate ``text`` to maximum ``width`` printable characters, retaining terminal sequences.
+
+ :arg str text: Text to truncate
+ :arg int width: The maximum width to truncate it to
+ :rtype: str
+ :returns: ``text`` truncated to at most ``width`` printable characters
+
+ >>> term.truncate(u'xyz\x1b[0;3m', 2)
+ u'xy\x1b[0;3m'
+ """
+ if width is None:
+ width = self.width
+ return Sequence(text, self).truncate(width)
+
+ def length(self, text):
+ u"""
+ Return printable length of a string containing sequences.
+
+ :arg str text: String to measure. May contain terminal sequences.
+ :rtype: int
+ :returns: The number of terminal character cells the string will occupy
+ when printed
+
+ Wide characters that consume 2 character cells are supported:
+
+ >>> term = Terminal()
+ >>> term.length(term.clear + term.red(u'コンニチハ'))
+ 10
+
+ .. note:: Sequences such as 'clear', which is considered as a
+ "movement sequence" because it would move the cursor to
+ (y, x)(0, 0), are evaluated as a printable length of
+ *0*.
+ """
+ return Sequence(text, self).length()
+
+ def strip(self, text, chars=None):
+ r"""
+ Return ``text`` without sequences and leading or trailing whitespace.
+
+ :rtype: str
+ :returns: Text with leading and trailing whitespace removed
+
+ >>> term.strip(u' \x1b[0;3m xyz ')
+ u'xyz'
+ """
+ return Sequence(text, self).strip(chars)
+
+ def rstrip(self, text, chars=None):
+ r"""
+ Return ``text`` without terminal sequences or trailing whitespace.
+
+ :rtype: str
+ :returns: Text with terminal sequences and trailing whitespace removed
+
+ >>> term.rstrip(u' \x1b[0;3m xyz ')
+ u' xyz'
+ """
+ return Sequence(text, self).rstrip(chars)
+
+ def lstrip(self, text, chars=None):
+ r"""
+ Return ``text`` without terminal sequences or leading whitespace.
+
+ :rtype: str
+ :returns: Text with terminal sequences and leading whitespace removed
+
+ >>> term.lstrip(u' \x1b[0;3m xyz ')
+ u'xyz '
+ """
+ return Sequence(text, self).lstrip(chars)
+
+ def strip_seqs(self, text):
+ r"""
+ Return ``text`` stripped of only its terminal sequences.
+
+ :rtype: str
+ :returns: Text with terminal sequences removed
+
+ >>> term.strip_seqs(u'\x1b[0;3mxyz')
+ u'xyz'
+ >>> term.strip_seqs(term.cuf(5) + term.red(u'test'))
+ u' test'
+
+ .. note:: Non-destructive sequences that adjust horizontal distance
+ (such as ``\b`` or ``term.cuf(5)``) are replaced by destructive
+ space or erasing.
+ """
+ return Sequence(text, self).strip_seqs()
+
+ def split_seqs(self, text, maxsplit=0):
+ r"""
+ Return ``text`` split by individual character elements and sequences.
+
+ :arg str text: String containing sequences
+ :arg int maxsplit: When maxsplit is nonzero, at most maxsplit splits
+ occur, and the remainder of the string is returned as the final element
+ of the list (same meaning is argument for :func:`re.split`).
+ :rtype: list[str]
+ :returns: List of sequences and individual characters
+
+ >>> term.split_seqs(term.underline(u'xyz'))
+ ['\x1b[4m', 'x', 'y', 'z', '\x1b(B', '\x1b[m']
+
+ >>> term.split_seqs(term.underline(u'xyz'), 1)
+ ['\x1b[4m', r'xyz\x1b(B\x1b[m']
+ """
+ pattern = self._caps_unnamed_any
+ result = []
+ for idx, match in enumerate(re.finditer(pattern, text)):
+ result.append(match.group())
+ if maxsplit and idx == maxsplit:
+ remaining = text[match.end():]
+ if remaining:
+ result[-1] += remaining
+ break
+ return result
+
+ def wrap(self, text, width=None, **kwargs):
+ r"""
+ Text-wrap a string, returning a list of wrapped lines.
+
+ :arg str text: Unlike :func:`textwrap.wrap`, ``text`` may contain
+ terminal sequences, such as colors, bold, or underline. By
+ default, tabs in ``text`` are expanded by
+ :func:`string.expandtabs`.
+ :arg int width: Unlike :func:`textwrap.wrap`, ``width`` will
+ default to the width of the attached terminal.
+ :arg \**kwargs: See :py:class:`textwrap.TextWrapper`
+ :rtype: list
+ :returns: List of wrapped lines
+
+ See :class:`textwrap.TextWrapper` for keyword arguments that can
+ customize wrapping behaviour.
+ """
+ width = self.width if width is None else width
+ wrapper = SequenceTextWrapper(width=width, term=self, **kwargs)
+ lines = []
+ for line in text.splitlines():
+ lines.extend(iter(wrapper.wrap(line)) if line.strip() else (u'',))
+
+ return lines
+
+ def getch(self):
+ """
+ Read, decode, and return the next byte from the keyboard stream.
+
+ :rtype: unicode
+ :returns: a single unicode character, or ``u''`` if a multi-byte
+ sequence has not yet been fully received.
+
+ This method name and behavior mimics curses ``getch(void)``, and
+ it supports :meth:`inkey`, reading only one byte from
+ the keyboard string at a time. This method should always return
+ without blocking if called after :meth:`kbhit` has returned True.
+
+ Implementors of alternate input stream methods should override
+ this method.
+ """
+ assert self._keyboard_fd is not None
+ byte = os.read(self._keyboard_fd, 1)
+ return self._keyboard_decoder.decode(byte, final=False)
+
+ def ungetch(self, text):
+ """
+ Buffer input data to be discovered by next call to :meth:`~.inkey`.
+
+ :arg str text: String to be buffered as keyboard input.
+ """
+ self._keyboard_buf.extendleft(text)
+
+ def kbhit(self, timeout=None):
+ """
+ Return whether a keypress has been detected on the keyboard.
+
+ This method is used by :meth:`inkey` to determine if a byte may
+ be read using :meth:`getch` without blocking. The standard
+ implementation simply uses the :func:`select.select` call on stdin.
+
+ :arg float timeout: When ``timeout`` is 0, this call is
+ non-blocking, otherwise blocking indefinitely until keypress
+ is detected when None (default). When ``timeout`` is a
+ positive number, returns after ``timeout`` seconds have
+ elapsed (float).
+ :rtype: bool
+ :returns: True if a keypress is awaiting to be read on the keyboard
+ attached to this terminal. When input is not a terminal, False is
+ always returned.
+ """
+ stime = time.time()
+ ready_r = [None, ]
+ check_r = [self._keyboard_fd] if self._keyboard_fd is not None else []
+
+ while HAS_TTY:
+ try:
+ ready_r, _, _ = select.select(check_r, [], [], timeout)
+ except InterruptedError:
+ # Beginning with python3.5, IntrruptError is no longer thrown
+ # https://www.python.org/dev/peps/pep-0475/
+ #
+ # For previous versions of python, we take special care to
+ # retry select on InterruptedError exception, namely to handle
+ # a custom SIGWINCH handler. When installed, it would cause
+ # select() to be interrupted with errno 4 (EAGAIN).
+ #
+ # Just as in python3.5, it is ignored, and a new timeout value
+ # is derived from the previous unless timeout becomes negative.
+ # because the signal handler has blocked beyond timeout, then
+ # False is returned. Otherwise, when timeout is None, we
+ # continue to block indefinitely (default).
+ if timeout is not None:
+ # subtract time already elapsed,
+ timeout -= time.time() - stime
+ if timeout > 0:
+ continue
+ # no time remains after handling exception (rare)
+ ready_r = [] # pragma: no cover
+ break # pragma: no cover
+ else:
+ break
+
+ return False if self._keyboard_fd is None else check_r == ready_r
+
+ @contextlib.contextmanager
+ def cbreak(self):
+ """
+ Allow each keystroke to be read immediately after it is pressed.
+
+ This is a context manager for :func:`tty.setcbreak`.
+
+ This context manager activates 'rare' mode, the opposite of 'cooked'
+ mode: On entry, :func:`tty.setcbreak` mode is activated disabling
+ line-buffering of keyboard input and turning off automatic echo of
+ input as output.
+
+ .. note:: You must explicitly print any user input you would like
+ displayed. If you provide any kind of editing, you must handle
+ backspace and other line-editing control functions in this mode
+ as well!
+
+ **Normally**, characters received from the keyboard cannot be read
+ by Python until the *Return* key is pressed. Also known as *cooked* or
+ *canonical input* mode, it allows the tty driver to provide
+ line-editing before shuttling the input to your program and is the
+ (implicit) default terminal mode set by most unix shells before
+ executing programs.
+
+ Technically, this context manager sets the :mod:`termios` attributes
+ of the terminal attached to :obj:`sys.__stdin__`.
+
+ .. note:: :func:`tty.setcbreak` sets ``VMIN = 1`` and ``VTIME = 0``,
+ see http://www.unixwiz.net/techtips/termios-vmin-vtime.html
+ """
+ if HAS_TTY and self._keyboard_fd is not None:
+ # Save current terminal mode:
+ save_mode = termios.tcgetattr(self._keyboard_fd)
+ save_line_buffered = self._line_buffered
+ tty.setcbreak(self._keyboard_fd, termios.TCSANOW)
+ try:
+ self._line_buffered = False
+ yield
+ finally:
+ # Restore prior mode:
+ termios.tcsetattr(self._keyboard_fd,
+ termios.TCSAFLUSH,
+ save_mode)
+ self._line_buffered = save_line_buffered
+ else:
+ yield
+
+ @contextlib.contextmanager
+ def raw(self):
+ r"""
+ A context manager for :func:`tty.setraw`.
+
+ Although both :meth:`break` and :meth:`raw` modes allow each keystroke
+ to be read immediately after it is pressed, Raw mode disables
+ processing of input and output.
+
+ In cbreak mode, special input characters such as ``^C`` or ``^S`` are
+ interpreted by the terminal driver and excluded from the stdin stream.
+ In raw mode these values are receive by the :meth:`inkey` method.
+
+ Because output processing is not done, the newline ``'\n'`` is not
+ enough, you must also print carriage return to ensure that the cursor
+ is returned to the first column::
+
+ with term.raw():
+ print("printing in raw mode", end="\r\n")
+ """
+ if HAS_TTY and self._keyboard_fd is not None:
+ # Save current terminal mode:
+ save_mode = termios.tcgetattr(self._keyboard_fd)
+ save_line_buffered = self._line_buffered
+ tty.setraw(self._keyboard_fd, termios.TCSANOW)
+ try:
+ self._line_buffered = False
+ yield
+ finally:
+ # Restore prior mode:
+ termios.tcsetattr(self._keyboard_fd,
+ termios.TCSAFLUSH,
+ save_mode)
+ self._line_buffered = save_line_buffered
+ else:
+ yield
+
+ @contextlib.contextmanager
+ def keypad(self):
+ r"""
+ Context manager that enables directional keypad input.
+
+ On entrying, this puts the terminal into "keyboard_transmit" mode by
+ emitting the keypad_xmit (smkx) capability. On exit, it emits
+ keypad_local (rmkx).
+
+ On an IBM-PC keyboard with numeric keypad of terminal-type *xterm*,
+ with numlock off, the lower-left diagonal key transmits sequence
+ ``\\x1b[F``, translated to :class:`~.Terminal` attribute
+ ``KEY_END``.
+
+ However, upon entering :meth:`keypad`, ``\\x1b[OF`` is transmitted,
+ translating to ``KEY_LL`` (lower-left key), allowing you to determine
+ diagonal direction keys.
+ """
+ try:
+ self.stream.write(self.smkx)
+ self.stream.flush()
+ yield
+ finally:
+ self.stream.write(self.rmkx)
+ self.stream.flush()
+
+ def inkey(self, timeout=None, esc_delay=0.35):
+ """
+ Read and return the next keyboard event within given timeout.
+
+ Generally, this should be used inside the :meth:`raw` context manager.
+
+ :arg float timeout: Number of seconds to wait for a keystroke before
+ returning. When ``None`` (default), this method may block
+ indefinitely.
+ :arg float esc_delay: To distinguish between the keystroke of
+ ``KEY_ESCAPE``, and sequences beginning with escape, the parameter
+ ``esc_delay`` specifies the amount of time after receiving escape
+ (``chr(27)``) to seek for the completion of an application key
+ before returning a :class:`~.Keystroke` instance for
+ ``KEY_ESCAPE``.
+ :rtype: :class:`~.Keystroke`.
+ :returns: :class:`~.Keystroke`, which may be empty (``u''``) if
+ ``timeout`` is specified and keystroke is not received.
+
+ .. note:: When used without the context manager :meth:`cbreak`, or
+ :meth:`raw`, :obj:`sys.__stdin__` remains line-buffered, and this
+ function will block until the return key is pressed!
+
+ .. note:: On Windows, a 10 ms sleep is added to the key press detection loop to reduce CPU
+ load. Due to the behavior of :py:func:`time.sleep` on Windows, this will actually
+ result in a 15.6 ms delay when using the default `time resolution
+ <https://docs.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod>`_.
+ Decreasing the time resolution will reduce this to 10 ms, while increasing it, which
+ is rarely done, will have a perceptable impact on the behavior.
+ """
+ resolve = functools.partial(resolve_sequence,
+ mapper=self._keymap,
+ codes=self._keycodes)
+
+ stime = time.time()
+
+ # re-buffer previously received keystrokes,
+ ucs = u''
+ while self._keyboard_buf:
+ ucs += self._keyboard_buf.pop()
+
+ # receive all immediately available bytes
+ while self.kbhit(timeout=0):
+ ucs += self.getch()
+
+ # decode keystroke, if any
+ ks = resolve(text=ucs)
+
+ # so long as the most immediately received or buffered keystroke is
+ # incomplete, (which may be a multibyte encoding), block until until
+ # one is received.
+ while not ks and self.kbhit(timeout=_time_left(stime, timeout)):
+ ucs += self.getch()
+ ks = resolve(text=ucs)
+
+ # handle escape key (KEY_ESCAPE) vs. escape sequence (like those
+ # that begin with \x1b[ or \x1bO) up to esc_delay when
+ # received. This is not optimal, but causes least delay when
+ # "meta sends escape" is used, or when an unsupported sequence is
+ # sent.
+ #
+ # The statement, "ucs in self._keymap_prefixes" has an effect on
+ # keystrokes such as Alt + Z ("\x1b[z" with metaSendsEscape): because
+ # no known input sequences begin with such phrasing to allow it to be
+ # returned more quickly than esc_delay otherwise blocks for.
+ if ks.code == self.KEY_ESCAPE:
+ esctime = time.time()
+ while (ks.code == self.KEY_ESCAPE and
+ ucs in self._keymap_prefixes and
+ self.kbhit(timeout=_time_left(esctime, esc_delay))):
+ ucs += self.getch()
+ ks = resolve(text=ucs)
+
+ # buffer any remaining text received
+ self.ungetch(ucs[len(ks):])
+ return ks
+
+
+class WINSZ(collections.namedtuple('WINSZ', (
+ 'ws_row', 'ws_col', 'ws_xpixel', 'ws_ypixel'))):
+ """
+ Structure represents return value of :const:`termios.TIOCGWINSZ`.
+
+ .. py:attribute:: ws_row
+
+ rows, in characters
+
+ .. py:attribute:: ws_col
+
+ columns, in characters
+
+ .. py:attribute:: ws_xpixel
+
+ horizontal size, pixels
+
+ .. py:attribute:: ws_ypixel
+
+ vertical size, pixels
+ """
+ #: format of termios structure
+ _FMT = 'hhhh'
+ #: buffer of termios structure appropriate for ioctl argument
+ _BUF = '\x00' * struct.calcsize(_FMT)
+
+
+#: _CUR_TERM = None
+#: From libcurses/doc/ncurses-intro.html (ESR, Thomas Dickey, et. al)::
+#:
+#: "After the call to setupterm(), the global variable cur_term is set to
+#: point to the current structure of terminal capabilities. By calling
+#: setupterm() for each terminal, and saving and restoring cur_term, it
+#: is possible for a program to use two or more terminals at once."
+#:
+#: However, if you study Python's ``./Modules/_cursesmodule.c``, you'll find::
+#:
+#: if (!initialised_setupterm && setupterm(termstr,fd,&err) == ERR) {
+#:
+#: Python - perhaps wrongly - will not allow for re-initialisation of new
+#: terminals through :func:`curses.setupterm`, so the value of cur_term cannot
+#: be changed once set: subsequent calls to :func:`curses.setupterm` have no
+#: effect.
+#:
+#: Therefore, the :attr:`Terminal.kind` of each :class:`Terminal` is
+#: essentially a singleton. This global variable reflects that, and a warning
+#: is emitted if somebody expects otherwise.
diff --git a/third_party/python/blessed/blessed/terminal.pyi b/third_party/python/blessed/blessed/terminal.pyi
new file mode 100644
index 0000000000..3d8eea4db7
--- /dev/null
+++ b/third_party/python/blessed/blessed/terminal.pyi
@@ -0,0 +1,106 @@
+# std imports
+from typing import IO, Any, List, Tuple, Union, Optional, OrderedDict, ContextManager
+
+# local
+from .keyboard import Keystroke
+from .sequences import Termcap
+from .formatters import (FormattingString,
+ NullCallableString,
+ ParameterizingString,
+ FormattingOtherString)
+
+HAS_TTY: bool
+
+class Terminal:
+ caps: OrderedDict[str, Termcap]
+ errors: List[str] = ...
+ def __init__(
+ self,
+ kind: Optional[str] = ...,
+ stream: Optional[IO[str]] = ...,
+ force_styling: bool = ...,
+ ) -> None: ...
+ def __getattr__(
+ self, attr: str
+ ) -> Union[NullCallableString, ParameterizingString, FormattingString]: ...
+ @property
+ def kind(self) -> str: ...
+ @property
+ def does_styling(self) -> bool: ...
+ @property
+ def is_a_tty(self) -> bool: ...
+ @property
+ def height(self) -> int: ...
+ @property
+ def width(self) -> int: ...
+ @property
+ def pixel_height(self) -> int: ...
+ @property
+ def pixel_width(self) -> int: ...
+ def location(
+ self, x: Optional[int] = ..., y: Optional[int] = ...
+ ) -> ContextManager[None]: ...
+ def get_location(self, timeout: Optional[float] = ...) -> Tuple[int, int]: ...
+ def fullscreen(self) -> ContextManager[None]: ...
+ def hidden_cursor(self) -> ContextManager[None]: ...
+ def move_xy(self, x: int, y: int) -> ParameterizingString: ...
+ def move_yx(self, y: int, x: int) -> ParameterizingString: ...
+ @property
+ def move_left(self) -> FormattingOtherString: ...
+ @property
+ def move_right(self) -> FormattingOtherString: ...
+ @property
+ def move_up(self) -> FormattingOtherString: ...
+ @property
+ def move_down(self) -> FormattingOtherString: ...
+ @property
+ def color(self) -> Union[NullCallableString, ParameterizingString]: ...
+ def color_rgb(self, red: int, green: int, blue: int) -> FormattingString: ...
+ @property
+ def on_color(self) -> Union[NullCallableString, ParameterizingString]: ...
+ def on_color_rgb(self, red: int, green: int, blue: int) -> FormattingString: ...
+ def formatter(self, value: str) -> Union[NullCallableString, FormattingString]: ...
+ def rgb_downconvert(self, red: int, green: int, blue: int) -> int: ...
+ @property
+ def normal(self) -> str: ...
+ def link(self, url: str, text: str, url_id: str = ...) -> str: ...
+ @property
+ def stream(self) -> IO[str]: ...
+ @property
+ def number_of_colors(self) -> int: ...
+ @number_of_colors.setter
+ def number_of_colors(self, value: int) -> None: ...
+ @property
+ def color_distance_algorithm(self) -> str: ...
+ @color_distance_algorithm.setter
+ def color_distance_algorithm(self, value: str) -> None: ...
+ def ljust(
+ self, text: str, width: Optional[int] = ..., fillchar: str = ...
+ ) -> str: ...
+ def rjust(
+ self, text: str, width: Optional[int] = ..., fillchar: str = ...
+ ) -> str: ...
+ def center(
+ self, text: str, width: Optional[int] = ..., fillchar: str = ...
+ ) -> str: ...
+ def truncate(self, text: str, width: Optional[int] = ...) -> str: ...
+ def length(self, text: str) -> int: ...
+ def strip(self, text: str, chars: Optional[str] = ...) -> str: ...
+ def rstrip(self, text: str, chars: Optional[str] = ...) -> str: ...
+ def lstrip(self, text: str, chars: Optional[str] = ...) -> str: ...
+ def strip_seqs(self, text: str) -> str: ...
+ def split_seqs(self, text: str, maxsplit: int) -> List[str]: ...
+ def wrap(
+ self, text: str, width: Optional[int] = ..., **kwargs: Any
+ ) -> List[str]: ...
+ def getch(self) -> str: ...
+ def ungetch(self, text: str) -> None: ...
+ def kbhit(self, timeout: Optional[float] = ...) -> bool: ...
+ def cbreak(self) -> ContextManager[None]: ...
+ def raw(self) -> ContextManager[None]: ...
+ def keypad(self) -> ContextManager[None]: ...
+ def inkey(
+ self, timeout: Optional[float] = ..., esc_delay: float = ...
+ ) -> Keystroke: ...
+
+class WINSZ: ...
diff --git a/third_party/python/blessed/blessed/win_terminal.py b/third_party/python/blessed/blessed/win_terminal.py
new file mode 100644
index 0000000000..267e028b96
--- /dev/null
+++ b/third_party/python/blessed/blessed/win_terminal.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+"""Module containing Windows version of :class:`Terminal`."""
+
+from __future__ import absolute_import
+
+# std imports
+import time
+import msvcrt # pylint: disable=import-error
+import contextlib
+
+# 3rd party
+from jinxed import win32 # pylint: disable=import-error
+
+# local
+from .terminal import WINSZ
+from .terminal import Terminal as _Terminal
+
+
+class Terminal(_Terminal):
+ """Windows subclass of :class:`Terminal`."""
+
+ def getch(self):
+ r"""
+ Read, decode, and return the next byte from the keyboard stream.
+
+ :rtype: unicode
+ :returns: a single unicode character, or ``u''`` if a multi-byte
+ sequence has not yet been fully received.
+
+ For versions of Windows 10.0.10586 and later, the console is expected
+ to be in ENABLE_VIRTUAL_TERMINAL_INPUT mode and the default method is
+ called.
+
+ For older versions of Windows, msvcrt.getwch() is used. If the received
+ character is ``\x00`` or ``\xe0``, the next character is
+ automatically retrieved.
+ """
+ if win32.VTMODE_SUPPORTED:
+ return super(Terminal, self).getch()
+
+ rtn = msvcrt.getwch()
+ if rtn in ('\x00', '\xe0'):
+ rtn += msvcrt.getwch()
+ return rtn
+
+ def kbhit(self, timeout=None):
+ """
+ Return whether a keypress has been detected on the keyboard.
+
+ This method is used by :meth:`inkey` to determine if a byte may
+ be read using :meth:`getch` without blocking. This is implemented
+ by wrapping msvcrt.kbhit() in a timeout.
+
+ :arg float timeout: When ``timeout`` is 0, this call is
+ non-blocking, otherwise blocking indefinitely until keypress
+ is detected when None (default). When ``timeout`` is a
+ positive number, returns after ``timeout`` seconds have
+ elapsed (float).
+ :rtype: bool
+ :returns: True if a keypress is awaiting to be read on the keyboard
+ attached to this terminal.
+ """
+ end = time.time() + (timeout or 0)
+ while True:
+
+ if msvcrt.kbhit():
+ return True
+
+ if timeout is not None and end < time.time():
+ break
+
+ time.sleep(0.01) # Sleep to reduce CPU load
+ return False
+
+ @staticmethod
+ def _winsize(fd):
+ """
+ Return named tuple describing size of the terminal by ``fd``.
+
+ :arg int fd: file descriptor queries for its window size.
+ :rtype: WINSZ
+ :returns: named tuple describing size of the terminal
+
+ WINSZ is a :class:`collections.namedtuple` instance, whose structure
+ directly maps to the return value of the :const:`termios.TIOCGWINSZ`
+ ioctl return value. The return parameters are:
+
+ - ``ws_row``: width of terminal by its number of character cells.
+ - ``ws_col``: height of terminal by its number of character cells.
+ - ``ws_xpixel``: width of terminal by pixels (not accurate).
+ - ``ws_ypixel``: height of terminal by pixels (not accurate).
+ """
+ window = win32.get_terminal_size(fd)
+ return WINSZ(ws_row=window.lines, ws_col=window.columns,
+ ws_xpixel=0, ws_ypixel=0)
+
+ @contextlib.contextmanager
+ def cbreak(self):
+ """
+ Allow each keystroke to be read immediately after it is pressed.
+
+ This is a context manager for ``jinxed.w32.setcbreak()``.
+
+ .. note:: You must explicitly print any user input you would like
+ displayed. If you provide any kind of editing, you must handle
+ backspace and other line-editing control functions in this mode
+ as well!
+
+ **Normally**, characters received from the keyboard cannot be read
+ by Python until the *Return* key is pressed. Also known as *cooked* or
+ *canonical input* mode, it allows the tty driver to provide
+ line-editing before shuttling the input to your program and is the
+ (implicit) default terminal mode set by most unix shells before
+ executing programs.
+ """
+ if self._keyboard_fd is not None:
+
+ filehandle = msvcrt.get_osfhandle(self._keyboard_fd)
+
+ # Save current terminal mode:
+ save_mode = win32.get_console_mode(filehandle)
+ save_line_buffered = self._line_buffered
+ win32.setcbreak(filehandle)
+ try:
+ self._line_buffered = False
+ yield
+ finally:
+ win32.set_console_mode(filehandle, save_mode)
+ self._line_buffered = save_line_buffered
+
+ else:
+ yield
+
+ @contextlib.contextmanager
+ def raw(self):
+ """
+ A context manager for ``jinxed.w32.setcbreak()``.
+
+ Although both :meth:`break` and :meth:`raw` modes allow each keystroke
+ to be read immediately after it is pressed, Raw mode disables
+ processing of input and output.
+
+ In cbreak mode, special input characters such as ``^C`` are
+ interpreted by the terminal driver and excluded from the stdin stream.
+ In raw mode these values are receive by the :meth:`inkey` method.
+ """
+ if self._keyboard_fd is not None:
+
+ filehandle = msvcrt.get_osfhandle(self._keyboard_fd)
+
+ # Save current terminal mode:
+ save_mode = win32.get_console_mode(filehandle)
+ save_line_buffered = self._line_buffered
+ win32.setraw(filehandle)
+ try:
+ self._line_buffered = False
+ yield
+ finally:
+ win32.set_console_mode(filehandle, save_mode)
+ self._line_buffered = save_line_buffered
+
+ else:
+ yield
diff --git a/third_party/python/blessed/blessed/win_terminal.pyi b/third_party/python/blessed/blessed/win_terminal.pyi
new file mode 100644
index 0000000000..275f16f9ee
--- /dev/null
+++ b/third_party/python/blessed/blessed/win_terminal.pyi
@@ -0,0 +1,11 @@
+# std imports
+from typing import Optional, ContextManager
+
+# local
+from .terminal import Terminal as _Terminal
+
+class Terminal(_Terminal):
+ def getch(self) -> str: ...
+ def kbhit(self, timeout: Optional[float] = ...) -> bool: ...
+ def cbreak(self) -> ContextManager[None]: ...
+ def raw(self) -> ContextManager[None]: ...