diff options
Diffstat (limited to 'blessed/terminal.py.bak')
-rw-r--r-- | blessed/terminal.py.bak | 785 |
1 files changed, 0 insertions, 785 deletions
diff --git a/blessed/terminal.py.bak b/blessed/terminal.py.bak deleted file mode 100644 index 1e17d73..0000000 --- a/blessed/terminal.py.bak +++ /dev/null @@ -1,785 +0,0 @@ -"This primary module provides the Terminal class." -# standard modules -import collections -import contextlib -import functools -import warnings -import platform -import codecs -import curses -import locale -import select -import struct -import time -import sys -import os - -try: - import termios - import fcntl - 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 '{0}'. The following methods " - "of Terminal are dummy/no-op unless a deriving class overrides " - "them: {1}".format(sys.platform.lower(), ', '.join(tty_methods))) - warnings.warn(msg_nosupport) - HAS_TTY = False -else: - HAS_TTY = True - -try: - from io import UnsupportedOperation as IOUnsupportedOperation -except ImportError: - class IOUnsupportedOperation(Exception): - """A dummy exception to take the place of Python 3's - ``io.UnsupportedOperation`` in Python 2.5""" - -try: - _ = InterruptedError - del _ -except NameError: - # alias py2 exception to py3 - InterruptedError = select.error - -# local imports -from .formatters import ( - ParameterizingString, - NullCallableString, - resolve_capability, - resolve_attribute, -) - -from .sequences import ( - init_sequence_patterns, - SequenceTextWrapper, - Sequence, -) - -from .keyboard import ( - get_keyboard_sequences, - get_keyboard_codes, - resolve_sequence, -) - - -class Terminal(object): - """A wrapper for curses and related terminfo(5) terminal capabilities. - - Instance attributes: - - ``stream`` - The stream the terminal outputs to. It's convenient to pass the stream - around with the terminal; it's almost always needed when the terminal - is and saves sticking lots of extra args on client functions in - practice. - """ - - #: Sugary names for commonly-used capabilities - _sugar = dict( - save='sc', - restore='rc', - # 'clear' clears the whole screen. - clear_eol='el', - clear_bol='el1', - clear_eos='ed', - position='cup', # deprecated - enter_fullscreen='smcup', - exit_fullscreen='rmcup', - move='cup', - move_x='hpa', - move_y='vpa', - move_left='cub1', - move_right='cuf1', - move_up='cuu1', - move_down='cud1', - hide_cursor='civis', - normal_cursor='cnorm', - reset_colors='op', # oc doesn't work on my OS X terminal. - 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') - - def __init__(self, kind=None, stream=None, force_styling=False): - """Initialize the terminal. - - If ``stream`` is not a tty, I will default to returning an empty - Unicode string for all capability values, so things like piping your - output to a file won't strew escape sequences all over the place. The - ``ls`` command sets a precedent for this: it defaults to columnar - output when being sent to a tty and one-item-per-line when not. - - :arg kind: A terminal string as taken by ``setupterm()``. Defaults to - the value of the ``TERM`` environment variable. - :arg stream: A file-like object representing the terminal. Defaults to - the original value of stdout, like ``curses.initscr()`` does. - :arg force_styling: Whether to force the emission of capabilities, even - if we don't seem to be in a terminal. This comes in handy if users - are trying to pipe your output through something like ``less -r``, - which supports terminal codes just fine but doesn't appear itself - to be a terminal. Just expose a command-line option, and set - ``force_styling`` based on it. Terminal initialization sequences - will be sent to ``stream`` if it has a file descriptor and to - ``sys.__stdout__`` otherwise. (``setupterm()`` demands to send them - somewhere, and stdout is probably where the output is ultimately - headed. If not, stderr is probably bound to the same terminal.) - - If you want to force styling to not happen, pass - ``force_styling=None``. - - """ - global _CUR_TERM - self.keyboard_fd = None - - # default stream is stdout, keyboard only valid as stdin when - # output stream is stdout and output stream is a tty - if stream is None or stream == sys.__stdout__: - stream = sys.__stdout__ - self.keyboard_fd = sys.__stdin__.fileno() - - try: - stream_fd = (stream.fileno() if hasattr(stream, 'fileno') - and callable(stream.fileno) else None) - except IOUnsupportedOperation: - stream_fd = None - - self._is_a_tty = stream_fd is not None and os.isatty(stream_fd) - self._does_styling = ((self.is_a_tty or force_styling) and - force_styling is not None) - - # keyboard_fd only non-None if both stdin and stdout is a tty. - self.keyboard_fd = (self.keyboard_fd - if self.keyboard_fd is not None and - self.is_a_tty and os.isatty(self.keyboard_fd) - else None) - self._normal = None # cache normal attr, preventing recursive lookups - - # The descriptor to direct terminal initialization sequences to. - # sys.__stdout__ seems to always have a descriptor of 1, even if output - # is redirected. - self._init_descriptor = (stream_fd is None and sys.__stdout__.fileno() - or stream_fd) - self._kind = kind or os.environ.get('TERM', 'unknown') - - if self.does_styling: - # Make things like tigetstr() work. Explicit args make setupterm() - # work even when -s is passed to nosetests. Lean toward sending - # init sequences to the stream if it has a file descriptor, and - # send them to stdout as a fallback, since they have to go - # somewhere. - try: - if (platform.python_implementation() == 'PyPy' and - isinstance(self._kind, unicode)): - # pypy/2.4.0_2/libexec/lib_pypy/_curses.py, line 1131 - # TypeError: initializer for ctype 'char *' must be a str - curses.setupterm(self._kind.encode('ascii'), self._init_descriptor) - else: - curses.setupterm(self._kind, self._init_descriptor) - except curses.error as err: - warnings.warn('Failed to setupterm(kind={0!r}): {1}' - .format(self._kind, err)) - self._kind = None - self._does_styling = False - else: - if _CUR_TERM is None or self._kind == _CUR_TERM: - _CUR_TERM = self._kind - else: - 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,)) - - for re_name, re_val in init_sequence_patterns(self).items(): - setattr(self, re_name, re_val) - - # 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) - - self._keyboard_buf = collections.deque() - if self.keyboard_fd is not None: - locale.setlocale(locale.LC_ALL, '') - self._encoding = locale.getpreferredencoding() or 'ascii' - try: - self._keyboard_decoder = codecs.getincrementaldecoder( - self._encoding)() - except LookupError as err: - warnings.warn('%s, fallback to ASCII for keyboard.' % (err,)) - self._encoding = 'ascii' - self._keyboard_decoder = codecs.getincrementaldecoder( - self._encoding)() - - self.stream = stream - - def __getattr__(self, attr): - """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 ``term.normal``. - - This capability is also 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 parametrized capability such as ``cup`` (cursor_address), pass - the parameters as arguments ``some_term.cup(line, column)``. See - manual page terminfo(5) for a complete list of capabilities. - """ - if not self.does_styling: - return NullCallableString() - val = resolve_attribute(self, attr) - # Cache capability codes. - setattr(self, attr, val) - return val - - @property - def kind(self): - """Name of this terminal type as string.""" - return self._kind - - @property - def does_styling(self): - """Whether this instance will emit terminal sequences (bool).""" - return self._does_styling - - @property - def is_a_tty(self): - """Whether the ``stream`` associated with this instance is a terminal - (bool).""" - return self._is_a_tty - - @property - def height(self): - """T.height -> int - - The height of the terminal in characters. - """ - return self._height_and_width().ws_row - - @property - def width(self): - """T.width -> int - - The width of the terminal in characters. - """ - return self._height_and_width().ws_col - - @staticmethod - def _winsize(fd): - """T._winsize -> WINSZ(ws_row, ws_col, ws_xpixel, ws_ypixel) - - The tty connected by file desriptor fd is queried for its window size, - and returned as a collections.namedtuple instance WINSZ. - - May raise exception IOError. - """ - if HAS_TTY: - data = fcntl.ioctl(fd, termios.TIOCGWINSZ, WINSZ._BUF) - return WINSZ(*struct.unpack(WINSZ._FMT, data)) - return WINSZ(ws_row=24, ws_col=80, ws_xpixel=0, ws_ypixel=0) - - def _height_and_width(self): - """Return a tuple of (terminal height, terminal width). - """ - # TODO(jquast): hey kids, even if stdout is redirected to a file, - # we can still query sys.__stdin__.fileno() for our terminal size. - # -- of course, if both are redirected, we have no use for this fd. - for fd in (self._init_descriptor, sys.__stdout__): - try: - if fd is not None: - return self._winsize(fd) - except IOError: - 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): - """Return a context manager for temporarily moving the cursor. - - Move the cursor to a certain position on entry, let you print stuff - there, then return the cursor to its original position:: - - term = Terminal() - with term.location(2, 5): - print 'Hello, world!' - for x in xrange(10): - print 'I can do it %i times!' % x - - 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. - - """ - # 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: - yield - finally: - # Restore original cursor position: - self.stream.write(self.restore) - - @contextlib.contextmanager - def fullscreen(self): - """Return a context manager that enters fullscreen mode while inside it - and restores normal mode on leaving. - - Fullscreen mode is characterized by instructing the terminal emulator - to store and save the current screen state (all screen output), switch - to "alternate screen". Upon exiting, the previous screen state is - returned. - - This call may not be tested; only one screen state may be saved at a - time. - """ - self.stream.write(self.enter_fullscreen) - try: - yield - finally: - self.stream.write(self.exit_fullscreen) - - @contextlib.contextmanager - def hidden_cursor(self): - """Return a context manager that hides the cursor upon entering, - and makes it visible again upon exiting.""" - self.stream.write(self.hide_cursor) - try: - yield - finally: - self.stream.write(self.normal_cursor) - - @property - def color(self): - """Returns capability that sets the foreground color. - - The capability is unparameterized until called and passed a number - (0-15), 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. - - :arg num: The number, 0-15, of the color - - """ - if not self.does_styling: - return NullCallableString() - return ParameterizingString(self._foreground_color, - self.normal, 'color') - - @property - def on_color(self): - "Returns capability that sets the background color." - if not self.does_styling: - return NullCallableString() - return ParameterizingString(self._background_color, - self.normal, 'on_color') - - @property - def normal(self): - "Returns sequence that resets video attribute." - if self._normal: - return self._normal - self._normal = resolve_capability(self, 'normal') - return self._normal - - @property - def number_of_colors(self): - """Return the number of colors the terminal supports. - - Common values are 0, 8, 16, 88, and 256. Most commonly - this may be used to test color capabilities at all:: - - if term.number_of_colors: - ...""" - # trim value to 0, as tigetnum('colors') returns -1 if no support, - # -2 if no such capability. - return max(0, self.does_styling and curses.tigetnum('colors') or -1) - - @property - def _foreground_color(self): - return self.setaf or self.setf - - @property - def _background_color(self): - return self.setab or self.setb - - def ljust(self, text, width=None, fillchar=u' '): - """T.ljust(text, [width], [fillchar]) -> unicode - - Return string ``text``, left-justified by printable length ``width``. - Padding is done using the specified fill character (default is a - space). Default ``width`` is the attached terminal's width. ``text`` - may contain terminal sequences.""" - if width is None: - width = self.width - return Sequence(text, self).ljust(width, fillchar) - - def rjust(self, text, width=None, fillchar=u' '): - """T.rjust(text, [width], [fillchar]) -> unicode - - Return string ``text``, right-justified by printable length ``width``. - Padding is done using the specified fill character (default is a - space). Default ``width`` is the attached terminal's width. ``text`` - may contain terminal sequences.""" - if width is None: - width = self.width - return Sequence(text, self).rjust(width, fillchar) - - def center(self, text, width=None, fillchar=u' '): - """T.center(text, [width], [fillchar]) -> unicode - - Return string ``text``, centered by printable length ``width``. - Padding is done using the specified fill character (default is a - space). Default ``width`` is the attached terminal's width. ``text`` - may contain terminal sequences.""" - if width is None: - width = self.width - return Sequence(text, self).center(width, fillchar) - - def length(self, text): - """T.length(text) -> int - - Return the printable length of string ``text``, which may contain - terminal sequences. Strings containing sequences such as 'clear', - which repositions the cursor, does not give accurate results, and - their printable length is evaluated *0*.. - """ - return Sequence(text, self).length() - - def strip(self, text, chars=None): - """T.strip(text) -> unicode - - Return string ``text`` with terminal sequences removed, and leading - and trailing whitespace removed. - """ - return Sequence(text, self).strip(chars) - - def rstrip(self, text, chars=None): - """T.rstrip(text) -> unicode - - Return string ``text`` with terminal sequences and trailing whitespace - removed. - """ - return Sequence(text, self).rstrip(chars) - - def lstrip(self, text, chars=None): - """T.lstrip(text) -> unicode - - Return string ``text`` with terminal sequences and leading whitespace - removed. - """ - return Sequence(text, self).lstrip(chars) - - def strip_seqs(self, text): - """T.strip_seqs(text) -> unicode - - Return string ``text`` stripped only of its sequences. - """ - return Sequence(text, self).strip_seqs() - - def wrap(self, text, width=None, **kwargs): - """T.wrap(text, [width=None, **kwargs ..]) -> list[unicode] - - Wrap paragraphs containing escape sequences ``text`` to the full - ``width`` of Terminal instance *T*, unless ``width`` is specified. - Wrapped by the virtual printable length, irregardless of the video - attribute sequences it may contain, allowing text containing colors, - bold, underline, etc. to be wrapped. - - Returns a list of strings that may contain escape sequences. See - ``textwrap.TextWrapper`` for all available additional kwargs to - customize wrapping behavior such as ``subsequent_indent``. - """ - width = self.width if width is None else width - lines = [] - for line in text.splitlines(): - lines.extend( - (_linewrap for _linewrap in SequenceTextWrapper( - width=width, term=self, **kwargs).wrap(text)) - if line.strip() else (u'',)) - - return lines - - def getch(self): - """T.getch() -> unicode - - Read and decode next byte from keyboard stream. May return u'' - if decoding is not yet complete, or completed unicode character. - Should always return bytes when self.kbhit() returns True. - - Implementors of input streams other than os.read() on the stdin fd - should derive and 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 kbhit(self, timeout=None, _intr_continue=True): - """T.kbhit([timeout=None]) -> bool - - Returns True if a keypress has been detected on keyboard. - - When ``timeout`` is 0, this call is non-blocking, Otherwise blocking - until keypress is detected (default). When ``timeout`` is a positive - number, returns after ``timeout`` seconds have elapsed. - - If input is not a terminal, False is always returned. - """ - # Special care is taken to handle a custom SIGWINCH handler, which - # causes select() to be interrupted with errno 4 (EAGAIN) -- - # it is ignored, and a new timeout value is derived from the previous, - # unless timeout becomes negative, because signal handler has blocked - # beyond timeout, then False is returned. Otherwise, when timeout is 0, - # we continue to block indefinitely (default). - stime = time.time() - check_w, check_x, ready_r = [], [], [None, ] - check_r = [self.keyboard_fd] if self.keyboard_fd is not None else [] - - while HAS_TTY and True: - try: - ready_r, ready_w, ready_x = select.select( - check_r, check_w, check_x, timeout) - except InterruptedError: - if not _intr_continue: - return u'' - 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 = [] - break - else: - break - - return False if self.keyboard_fd is None else check_r == ready_r - - @contextlib.contextmanager - def cbreak(self): - """Return a context manager that enters 'cbreak' mode: disabling line - buffering of keyboard input, making characters typed by the user - immediately available to the program. Also referred to as 'rare' - mode, this is the opposite of 'cooked' mode, the default for most - shells. - - In 'cbreak' mode, echo of input is also disabled: the application must - explicitly print any input received, if they so wish. - - More information can be found in the manual page for curses.h, - http://www.openbsd.org/cgi-bin/man.cgi?query=cbreak - - The python manual for curses, - http://docs.python.org/2/library/curses.html - - Note also that setcbreak sets VMIN = 1 and VTIME = 0, - 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) - tty.setcbreak(self.keyboard_fd, termios.TCSANOW) - try: - yield - finally: - # restore prior mode, - termios.tcsetattr(self.keyboard_fd, - termios.TCSAFLUSH, - save_mode) - else: - yield - - @contextlib.contextmanager - def raw(self): - """Return a context manager that enters *raw* mode. Raw mode is - similar to *cbreak* mode, in that characters typed are immediately - available to ``inkey()`` with one exception: the interrupt, quit, - suspend, and flow control characters are all passed through as their - raw character values instead of generating a signal. - """ - if HAS_TTY and self.keyboard_fd is not None: - # save current terminal mode, - save_mode = termios.tcgetattr(self.keyboard_fd) - tty.setraw(self.keyboard_fd, termios.TCSANOW) - try: - yield - finally: - # restore prior mode, - termios.tcsetattr(self.keyboard_fd, - termios.TCSAFLUSH, - save_mode) - else: - yield - - @contextlib.contextmanager - def keypad(self): - """ - Context manager that enables keypad input (*keyboard_transmit* mode). - - This enables the effect of calling the curses function keypad(3x): - display terminfo(5) capability `keypad_xmit` (smkx) upon entering, - and terminfo(5) capability `keypad_local` (rmkx) upon exiting. - - On an IBM-PC keypad of ttype *xterm*, with numlock off, the - lower-left diagonal key transmits sequence ``\\x1b[F``, ``KEY_END``. - - However, upon entering keypad mode, ``\\x1b[OF`` is transmitted, - translating to ``KEY_LL`` (lower-left key), allowing diagonal - direction keys to be determined. - """ - try: - self.stream.write(self.smkx) - yield - finally: - self.stream.write(self.rmkx) - - def inkey(self, timeout=None, esc_delay=0.35, _intr_continue=True): - """T.inkey(timeout=None, [esc_delay, [_intr_continue]]) -> Keypress() - - Receive next keystroke from keyboard (stdin), blocking until a - keypress is received or ``timeout`` elapsed, if specified. - - When used without the context manager ``cbreak``, stdin remains - line-buffered, and this function will block until return is pressed, - even though only one unicode character is returned at a time.. - - The value returned is an instance of ``Keystroke``, with properties - ``is_sequence``, and, when True, non-None values for attributes - ``code`` and ``name``. The value of ``code`` may be compared against - attributes of this terminal beginning with *KEY*, such as - ``KEY_ESCAPE``. - - To distinguish between ``KEY_ESCAPE``, and sequences beginning with - escape, the ``esc_delay`` specifies the amount of time after receiving - the escape character (chr(27)) to seek for the completion - of other application keys before returning ``KEY_ESCAPE``. - - Normally, when this function is interrupted by a signal, such as the - installment of SIGWINCH, this function will ignore this interruption - and continue to poll for input up to the ``timeout`` specified. If - you'd rather this function return ``u''`` early, specify a value - of ``False`` for ``_intr_continue``. - """ - # TODO(jquast): "meta sends escape", where alt+1 would send '\x1b1', - # what do we do with that? Surely, something useful. - # comparator to term.KEY_meta('x') ? - # TODO(jquast): Ctrl characters, KEY_CTRL_[A-Z], and the rest; - # KEY_CTRL_\, KEY_CTRL_{, etc. are not legitimate - # attributes. comparator to term.KEY_ctrl('z') ? - def _timeleft(stime, timeout): - """_timeleft(stime, timeout) -> float - - Returns time-relative time remaining before ``timeout`` - after time elapsed since ``stime``. - """ - if timeout is not None: - if timeout is 0: - return 0 - return max(0, timeout - (time.time() - stime)) - - 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(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(_timeleft(stime, timeout), _intr_continue): - ucs += self.getch() - ks = resolve(text=ucs) - - # handle escape key (KEY_ESCAPE) vs. escape sequence (which begins - # with KEY_ESCAPE, \x1b[, \x1bO, or \x1b?), up to esc_delay when - # received. This is not optimal, but causes least delay when - # (currently unhandled, and rare) "meta sends escape" is used, - # or when an unsupported sequence is sent. - if ks.code is self.KEY_ESCAPE: - esctime = time.time() - while (ks.code is self.KEY_ESCAPE and - self.kbhit(_timeleft(esctime, esc_delay))): - ucs += self.getch() - ks = resolve(text=ucs) - - # buffer any remaining text received - self._keyboard_buf.extendleft(ucs[len(ks):]) - return ks - -# 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 setupterm(), so the value of cur_term cannot be changed -# once set: subsequent calls to setupterm() have no effect. -# -# Therefore, the ``kind`` of each Terminal() is, in essence, a singleton. -# This global variable reflects that, and a warning is emitted if somebody -# expects otherwise. - -_CUR_TERM = None - -WINSZ = collections.namedtuple('WINSZ', ( - 'ws_row', # /* rows, in characters */ - 'ws_col', # /* columns, in characters */ - 'ws_xpixel', # /* horizontal size, pixels */ - 'ws_ypixel', # /* vertical size, pixels */ -)) -#: format of termios structure -WINSZ._FMT = 'hhhh' -#: buffer of termios structure appropriate for ioctl argument -WINSZ._BUF = '\x00' * struct.calcsize(WINSZ._FMT) |