From 7fa19c7e74d17fe7435ec12f6daa3a22742cdc42 Mon Sep 17 00:00:00 2001 From: Andrew Hamilton Date: Sat, 3 Dec 2016 15:17:19 +0100 Subject: [PATCH] Paring down urwid. --- urwid/compat.py | 37 ---- urwid/display_common.py | 131 ----------- urwid/escape.py | 44 ---- urwid/old_str_util.py | 368 ------------------------------- urwid/raw_display.py | 21 +- urwid/util.py | 474 ---------------------------------------- 6 files changed, 3 insertions(+), 1072 deletions(-) delete mode 100644 urwid/compat.py delete mode 100644 urwid/display_common.py delete mode 100644 urwid/old_str_util.py delete mode 100644 urwid/util.py diff --git a/urwid/compat.py b/urwid/compat.py deleted file mode 100644 index cefee0b..0000000 --- a/urwid/compat.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Urwid python compatibility definitions -# Copyright (C) 2011 Ian Ward -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# -# Urwid web site: http://excess.org/urwid/ - -import sys - - -PYTHON3 = sys.version_info > (3, 0) - -# for iterating over byte strings: -# ord2 calls ord in python2 only -# chr2 converts an ordinal value to a length-1 byte string -# B returns a byte string in all supported python versions -# bytes3 creates a byte string from a list of ordinal values -ord2 = lambda x: x -chr2 = lambda x: bytes([x]) -B = lambda x: x.encode('iso8859-1') -bytes = bytes -bytes3 = bytes diff --git a/urwid/display_common.py b/urwid/display_common.py deleted file mode 100644 index fda18f9..0000000 --- a/urwid/display_common.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/python -# Urwid common display code -# Copyright (C) 2004-2011 Ian Ward -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# -# Urwid web site: http://excess.org/urwid/ - -import os -import sys -import termios - -from urwid.util import StoppingContext - - -class RealTerminal(object): - def __init__(self): - super(RealTerminal,self).__init__() - self._signal_keys_set = False - self._old_signal_keys = None - - def tty_signal_keys(self, intr=None, quit=None, start=None, - stop=None, susp=None, fileno=None): - """ - Read and/or set the tty's signal character settings. - This function returns the current settings as a tuple. - - Use the string 'undefined' to unmap keys from their signals. - The value None is used when no change is being made. - Setting signal keys is done using the integer ascii - code for the key, eg. 3 for CTRL+C. - - If this function is called after start() has been called - then the original settings will be restored when stop() - is called. - """ - if fileno is None: - fileno = sys.stdin.fileno() - if not os.isatty(fileno): - return - - tattr = termios.tcgetattr(fileno) - sattr = tattr[6] - skeys = (sattr[termios.VINTR], sattr[termios.VQUIT], - sattr[termios.VSTART], sattr[termios.VSTOP], - sattr[termios.VSUSP]) - - if intr == 'undefined': intr = 0 - if quit == 'undefined': quit = 0 - if start == 'undefined': start = 0 - if stop == 'undefined': stop = 0 - if susp == 'undefined': susp = 0 - - if intr is not None: tattr[6][termios.VINTR] = intr - if quit is not None: tattr[6][termios.VQUIT] = quit - if start is not None: tattr[6][termios.VSTART] = start - if stop is not None: tattr[6][termios.VSTOP] = stop - if susp is not None: tattr[6][termios.VSUSP] = susp - - if intr is not None or quit is not None or \ - start is not None or stop is not None or \ - susp is not None: - termios.tcsetattr(fileno, termios.TCSADRAIN, tattr) - self._signal_keys_set = True - - return skeys - - -class ScreenError(Exception): - pass - -class BaseScreen(): - """ - Base class for Screen classes (raw_display.Screen, .. etc) - """ - - def __init__(self): - super(BaseScreen,self).__init__() - self._palette = {} - self._started = False - - started = property(lambda self: self._started) - - def start(self, *args, **kwargs): - """Set up the screen. If the screen has already been started, does - nothing. - - May be used as a context manager, in which case :meth:`stop` will - automatically be called at the end of the block: - - with screen.start(): - ... - - You shouldn't override this method in a subclass; instead, override - :meth:`_start`. - """ - if not self._started: - self._start(*args, **kwargs) - self._started = True - return StoppingContext(self) - - def _start(self): - pass - - def stop(self): - if self._started: - self._stop() - self._started = False - - def _stop(self): - pass - - -def _test(): - import doctest - doctest.testmod() - -if __name__=='__main__': - _test() diff --git a/urwid/escape.py b/urwid/escape.py index 90deb80..6e73051 100644 --- a/urwid/escape.py +++ b/urwid/escape.py @@ -26,11 +26,6 @@ Terminal Escape Sequences for input and display import re -from urwid import old_str_util as str_util - -from urwid.compat import bytes, bytes3 - -within_double_byte = str_util.within_double_byte SO = "\x0e" SI = "\x0f" @@ -324,45 +319,6 @@ def process_keyqueue(codes, more_available): if code >27 and code <32: return ["ctrl %s" % chr(ord('A')+code-1)], codes[1:] - em = str_util.get_byte_encoding() - - if (em == 'wide' and code < 256 and - within_double_byte(chr(code),0,0)): - if not codes[1:]: - if more_available: - raise MoreInputRequired() - if codes[1:] and codes[1] < 256: - db = chr(code)+chr(codes[1]) - if within_double_byte(db, 0, 1): - return [db], codes[2:] - if em == 'utf8' and code>127 and code<256: - if code & 0xe0 == 0xc0: # 2-byte form - need_more = 1 - elif code & 0xf0 == 0xe0: # 3-byte form - need_more = 2 - elif code & 0xf8 == 0xf0: # 4-byte form - need_more = 3 - else: - return ["<%d>"%code], codes[1:] - - for i in range(need_more): - if len(codes)-1 <= i: - if more_available: - raise MoreInputRequired() - else: - return ["<%d>"%code], codes[1:] - k = codes[i+1] - if k>256 or k&0xc0 != 0x80: - return ["<%d>"%code], codes[1:] - - s = bytes3(codes[:need_more+1]) - - assert isinstance(s, bytes) - try: - return [s.decode("utf-8")], codes[need_more+1:] - except UnicodeDecodeError: - return ["<%d>"%code], codes[1:] - if code >127 and code <256: key = chr(code) return [key], codes[1:] diff --git a/urwid/old_str_util.py b/urwid/old_str_util.py deleted file mode 100644 index 86cfe04..0000000 --- a/urwid/old_str_util.py +++ /dev/null @@ -1,368 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Urwid unicode character processing tables -# Copyright (C) 2004-2011 Ian Ward -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# -# Urwid web site: http://excess.org/urwid/ - - -import re - -from urwid.compat import bytes, B, ord2 - -SAFE_ASCII_RE = re.compile("^[ -~]*$") -SAFE_ASCII_BYTES_RE = re.compile(B("^[ -~]*$")) - -_byte_encoding = None - -# GENERATED DATA -# generated from -# http://www.unicode.org/Public/4.0-Update/EastAsianWidth-4.0.0.txt - -widths = [ - (126, 1), - (159, 0), - (687, 1), - (710, 0), - (711, 1), - (727, 0), - (733, 1), - (879, 0), - (1154, 1), - (1161, 0), - (4347, 1), - (4447, 2), - (7467, 1), - (7521, 0), - (8369, 1), - (8426, 0), - (9000, 1), - (9002, 2), - (11021, 1), - (12350, 2), - (12351, 1), - (12438, 2), - (12442, 0), - (19893, 2), - (19967, 1), - (55203, 2), - (63743, 1), - (64106, 2), - (65039, 1), - (65059, 0), - (65131, 2), - (65279, 1), - (65376, 2), - (65500, 1), - (65510, 2), - (120831, 1), - (262141, 2), - (1114109, 1), -] - -# ACCESSOR FUNCTIONS - -def get_width( o ): - """Return the screen column width for unicode ordinal o.""" - global widths - if o == 0xe or o == 0xf: - return 0 - for num, wid in widths: - if o <= num: - return wid - return 1 - -def decode_one( text, pos ): - """ - Return (ordinal at pos, next position) for UTF-8 encoded text. - """ - assert isinstance(text, bytes), text - b1 = ord2(text[pos]) - if not b1 & 0x80: - return b1, pos+1 - error = ord("?"), pos+1 - lt = len(text) - lt = lt-pos - if lt < 2: - return error - if b1 & 0xe0 == 0xc0: - b2 = ord2(text[pos+1]) - if b2 & 0xc0 != 0x80: - return error - o = ((b1&0x1f)<<6)|(b2&0x3f) - if o < 0x80: - return error - return o, pos+2 - if lt < 3: - return error - if b1 & 0xf0 == 0xe0: - b2 = ord2(text[pos+1]) - if b2 & 0xc0 != 0x80: - return error - b3 = ord2(text[pos+2]) - if b3 & 0xc0 != 0x80: - return error - o = ((b1&0x0f)<<12)|((b2&0x3f)<<6)|(b3&0x3f) - if o < 0x800: - return error - return o, pos+3 - if lt < 4: - return error - if b1 & 0xf8 == 0xf0: - b2 = ord2(text[pos+1]) - if b2 & 0xc0 != 0x80: - return error - b3 = ord2(text[pos+2]) - if b3 & 0xc0 != 0x80: - return error - b4 = ord2(text[pos+2]) - if b4 & 0xc0 != 0x80: - return error - o = ((b1&0x07)<<18)|((b2&0x3f)<<12)|((b3&0x3f)<<6)|(b4&0x3f) - if o < 0x10000: - return error - return o, pos+4 - return error - -def decode_one_uni(text, i): - """ - decode_one implementation for unicode strings - """ - return ord(text[i]), i+1 - -def decode_one_right(text, pos): - """ - Return (ordinal at pos, next position) for UTF-8 encoded text. - pos is assumed to be on the trailing byte of a utf-8 sequence. - """ - assert isinstance(text, bytes), text - error = ord("?"), pos-1 - p = pos - while p >= 0: - if ord2(text[p])&0xc0 != 0x80: - o, next = decode_one( text, p ) - return o, p-1 - p -=1 - if p == p-4: - return error - -def set_byte_encoding(enc): - assert enc in ('utf8', 'narrow', 'wide') - global _byte_encoding - _byte_encoding = enc - -def get_byte_encoding(): - return _byte_encoding - -def calc_text_pos(text, start_offs, end_offs, pref_col): - """ - Calculate the closest position to the screen column pref_col in text - where start_offs is the offset into text assumed to be screen column 0 - and end_offs is the end of the range to search. - - text may be unicode or a byte string in the target _byte_encoding - - Returns (position, actual_col). - """ - assert start_offs <= end_offs, repr((start_offs, end_offs)) - utfs = isinstance(text, bytes) and _byte_encoding == "utf8" - unis = not isinstance(text, bytes) - if unis or utfs: - decode = [decode_one, decode_one_uni][unis] - i = start_offs - sc = 0 - n = 1 # number to advance by - while i < end_offs: - o, n = decode(text, i) - w = get_width(o) - if w+sc > pref_col: - return i, sc - i = n - sc += w - return i, sc - assert type(text) == bytes, repr(text) - # "wide" and "narrow" - i = start_offs+pref_col - if i >= end_offs: - return end_offs, end_offs-start_offs - if _byte_encoding == "wide": - if within_double_byte(text, start_offs, i) == 2: - i -= 1 - return i, i-start_offs - -def calc_width(text, start_offs, end_offs): - """ - Return the screen column width of text between start_offs and end_offs. - - text may be unicode or a byte string in the target _byte_encoding - - Some characters are wide (take two columns) and others affect the - previous character (take zero columns). Use the widths table above - to calculate the screen column width of text[start_offs:end_offs] - """ - - assert start_offs <= end_offs, repr((start_offs, end_offs)) - - utfs = isinstance(text, bytes) and _byte_encoding == "utf8" - unis = not isinstance(text, bytes) - if (unis and not SAFE_ASCII_RE.match(text) - ) or (utfs and not SAFE_ASCII_BYTES_RE.match(text)): - decode = [decode_one, decode_one_uni][unis] - i = start_offs - sc = 0 - n = 1 # number to advance by - while i < end_offs: - o, n = decode(text, i) - w = get_width(o) - i = n - sc += w - return sc - # "wide", "narrow" or all printable ASCII, just return the character count - return end_offs - start_offs - -def is_wide_char(text, offs): - """ - Test if the character at offs within text is wide. - - text may be unicode or a byte string in the target _byte_encoding - """ - if isinstance(text, str): - o = ord(text[offs]) - return get_width(o) == 2 - assert isinstance(text, bytes) - if _byte_encoding == "utf8": - o, n = decode_one(text, offs) - return get_width(o) == 2 - if _byte_encoding == "wide": - return within_double_byte(text, offs, offs) == 1 - return False - -def move_prev_char(text, start_offs, end_offs): - """ - Return the position of the character before end_offs. - """ - assert start_offs < end_offs - if isinstance(text, str): - return end_offs-1 - assert isinstance(text, bytes) - if _byte_encoding == "utf8": - o = end_offs-1 - while ord2(text[o])&0xc0 == 0x80: - o -= 1 - return o - if _byte_encoding == "wide" and within_double_byte(text, - start_offs, end_offs-1) == 2: - return end_offs-2 - return end_offs-1 - -def move_next_char(text, start_offs, end_offs): - """ - Return the position of the character after start_offs. - """ - assert start_offs < end_offs - if isinstance(text, str): - return start_offs+1 - assert isinstance(text, bytes) - if _byte_encoding == "utf8": - o = start_offs+1 - while o= 0x40 and v < 0x7f: - # might be second half of big5, uhc or gbk encoding - if pos == line_start: return 0 - - if ord2(text[pos-1]) >= 0x81: - if within_double_byte(text, line_start, pos-1) == 1: - return 2 - return 0 - - if v < 0x80: return 0 - - i = pos -1 - while i >= line_start: - if ord2(text[i]) < 0x80: - break - i -= 1 - - if (pos - i) & 1: - return 1 - return 2 - -# TABLE GENERATION CODE - -def process_east_asian_width(): - import sys - out = [] - last = None - for line in sys.stdin.readlines(): - if line[:1] == "#": continue - line = line.strip() - hex,rest = line.split(";",1) - wid,rest = rest.split(" # ",1) - word1 = rest.split(" ",1)[0] - - if "." in hex: - hex = hex.split("..")[1] - num = int(hex, 16) - - if word1 in ("COMBINING","MODIFIER",""): - l = 0 - elif wid in ("W", "F"): - l = 2 - else: - l = 1 - - if last is None: - out.append((0, l)) - last = l - - if last == l: - out[-1] = (num, l) - else: - out.append( (num, l) ) - last = l - - print("widths = [") - for o in out[1:]: # treat control characters same as ascii - print("\t%r," % (o,)) - print("]") - -if __name__ == "__main__": - process_east_asian_width() - diff --git a/urwid/raw_display.py b/urwid/raw_display.py index 8c12fbe..1f21c0e 100644 --- a/urwid/raw_display.py +++ b/urwid/raw_display.py @@ -31,16 +31,13 @@ import tty from urwid import escape -from urwid.display_common import BaseScreen, RealTerminal +class Screen: -class Screen(BaseScreen, RealTerminal): def __init__(self, input=sys.stdin, output=sys.stdout): """Initialize a screen that directly prints escape codes to an output terminal. """ - super(Screen, self).__init__() - self._keyqueue = [] self.prev_input_resize = 0 self.set_input_timeouts() self._mouse_tracking_enabled = False @@ -95,7 +92,7 @@ class Screen(BaseScreen, RealTerminal): else: self.write(escape.MOUSE_TRACKING_OFF) - def _start(self, alternate_buffer=True): + def start(self, alternate_buffer=True): """ Initialize the screen and input mode. @@ -115,14 +112,9 @@ class Screen(BaseScreen, RealTerminal): self._alternate_buffer = alternate_buffer self._next_timeout = self.max_wait - if not self._signal_keys_set: - self._old_signal_keys = self.tty_signal_keys(fileno=fd) - self._mouse_tracking(self._mouse_tracking_enabled) - return super(Screen, self)._start() - - def _stop(self): + def stop(self): """ Restore the screen. """ @@ -144,12 +136,6 @@ class Screen(BaseScreen, RealTerminal): + escape.SHOW_CURSOR) self.flush() - if self._old_signal_keys: - self.tty_signal_keys(*(self._old_signal_keys + (fd,))) - - super(Screen, self)._stop() - - def write(self, data): """Write some data to the terminal. @@ -210,7 +196,6 @@ class Screen(BaseScreen, RealTerminal): * Mouse button release: ('mouse release', 0, 18, 13), ('ctrl mouse release', 0, 17, 23) """ - assert self._started self._wait_for_input_ready(self._next_timeout) keys, raw = self.parse_input(None, None, self.get_available_raw_input()) diff --git a/urwid/util.py b/urwid/util.py deleted file mode 100644 index 2375e47..0000000 --- a/urwid/util.py +++ /dev/null @@ -1,474 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Urwid utility functions -# Copyright (C) 2004-2011 Ian Ward -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# -# Urwid web site: http://excess.org/urwid/ - -from urwid import escape -from urwid.compat import bytes - -import codecs - -str_util = escape.str_util - -# bring str_util functions into our namespace -calc_text_pos = str_util.calc_text_pos -calc_width = str_util.calc_width -is_wide_char = str_util.is_wide_char -move_next_char = str_util.move_next_char -move_prev_char = str_util.move_prev_char -within_double_byte = str_util.within_double_byte - - -def detect_encoding(): - # Try to determine if using a supported double-byte encoding - import locale - try: - try: - locale.setlocale(locale.LC_ALL, "") - except locale.Error: - pass - return locale.getlocale()[1] or "" - except ValueError as e: - # with invalid LANG value python will throw ValueError - if e.args and e.args[0].startswith("unknown locale"): - return "" - else: - raise - -if 'detected_encoding' not in locals(): - detected_encoding = detect_encoding() -else: - assert 0, "It worked!" - -_target_encoding = None -_use_dec_special = True - - -def set_encoding( encoding ): - """ - Set the byte encoding to assume when processing strings and the - encoding to use when converting unicode strings. - """ - encoding = encoding.lower() - - global _target_encoding, _use_dec_special - - if encoding in ( 'utf-8', 'utf8', 'utf' ): - str_util.set_byte_encoding("utf8") - - _use_dec_special = False - elif encoding in ( 'euc-jp' # JISX 0208 only - , 'euc-kr', 'euc-cn', 'euc-tw' # CNS 11643 plain 1 only - , 'gb2312', 'gbk', 'big5', 'cn-gb', 'uhc' - # these shouldn't happen, should they? - , 'eucjp', 'euckr', 'euccn', 'euctw', 'cncb' ): - str_util.set_byte_encoding("wide") - - _use_dec_special = True - else: - str_util.set_byte_encoding("narrow") - _use_dec_special = True - - # if encoding is valid for conversion from unicode, remember it - _target_encoding = 'ascii' - try: - if encoding: - "".encode(encoding) - _target_encoding = encoding - except LookupError: pass - - -def get_encoding_mode(): - """ - Get the mode Urwid is using when processing text strings. - Returns 'narrow' for 8-bit encodings, 'wide' for CJK encodings - or 'utf8' for UTF-8 encodings. - """ - return str_util.get_byte_encoding() - - -def apply_target_encoding( s ): - """ - Return (encoded byte string, character set rle). - """ - if _use_dec_special and type(s) == str: - # first convert drawing characters - try: - s = s.translate( escape.DEC_SPECIAL_CHARMAP ) - except NotImplementedError: - # python < 2.4 needs to do this the hard way.. - for c, alt in zip(escape.DEC_SPECIAL_CHARS, - escape.ALT_DEC_SPECIAL_CHARS): - s = s.replace( c, escape.SO+alt+escape.SI ) - - if type(s) == str: - s = s.replace(escape.SI+escape.SO, "") # remove redundant shifts - s = codecs.encode(s, _target_encoding, 'replace') - - assert isinstance(s, bytes) - SO = escape.SO.encode('ascii') - SI = escape.SI.encode('ascii') - - sis = s.split(SO) - - assert isinstance(sis[0], bytes) - - sis0 = sis[0].replace(SI, bytes()) - sout = [] - cout = [] - if sis0: - sout.append( sis0 ) - cout.append( (None,len(sis0)) ) - - if len(sis)==1: - return sis0, cout - - for sn in sis[1:]: - assert isinstance(sn, bytes) - assert isinstance(SI, bytes) - sl = sn.split(SI, 1) - if len(sl) == 1: - sin = sl[0] - assert isinstance(sin, bytes) - sout.append(sin) - rle_append_modify(cout, (escape.DEC_TAG.encode('ascii'), len(sin))) - continue - sin, son = sl - son = son.replace(SI, bytes()) - if sin: - sout.append(sin) - rle_append_modify(cout, (escape.DEC_TAG, len(sin))) - if son: - sout.append(son) - rle_append_modify(cout, (None, len(son))) - - outstr = bytes().join(sout) - return outstr, cout - - -###################################################################### -# Try to set the encoding using the one detected by the locale module -set_encoding( detected_encoding ) -###################################################################### - - -def supports_unicode(): - """ - Return True if python is able to convert non-ascii unicode strings - to the current encoding. - """ - return _target_encoding and _target_encoding != 'ascii' - - - - - -def calc_trim_text( text, start_offs, end_offs, start_col, end_col ): - """ - Calculate the result of trimming text. - start_offs -- offset into text to treat as screen column 0 - end_offs -- offset into text to treat as the end of the line - start_col -- screen column to trim at the left - end_col -- screen column to trim at the right - - Returns (start, end, pad_left, pad_right), where: - start -- resulting start offset - end -- resulting end offset - pad_left -- 0 for no pad or 1 for one space to be added - pad_right -- 0 for no pad or 1 for one space to be added - """ - spos = start_offs - pad_left = pad_right = 0 - if start_col > 0: - spos, sc = calc_text_pos( text, spos, end_offs, start_col ) - if sc < start_col: - pad_left = 1 - spos, sc = calc_text_pos( text, start_offs, - end_offs, start_col+1 ) - run = end_col - start_col - pad_left - pos, sc = calc_text_pos( text, spos, end_offs, run ) - if sc < run: - pad_right = 1 - return ( spos, pos, pad_left, pad_right ) - - - - -def trim_text_attr_cs( text, attr, cs, start_col, end_col ): - """ - Return ( trimmed text, trimmed attr, trimmed cs ). - """ - spos, epos, pad_left, pad_right = calc_trim_text( - text, 0, len(text), start_col, end_col ) - attrtr = rle_subseg( attr, spos, epos ) - cstr = rle_subseg( cs, spos, epos ) - if pad_left: - al = rle_get_at( attr, spos-1 ) - rle_append_beginning_modify( attrtr, (al, 1) ) - rle_append_beginning_modify( cstr, (None, 1) ) - if pad_right: - al = rle_get_at( attr, epos ) - rle_append_modify( attrtr, (al, 1) ) - rle_append_modify( cstr, (None, 1) ) - - return (bytes().rjust(pad_left) + text[spos:epos] + - bytes().rjust(pad_right), attrtr, cstr) - - -def rle_get_at( rle, pos ): - """ - Return the attribute at offset pos. - """ - x = 0 - if pos < 0: - return None - for a, run in rle: - if x+run > pos: - return a - x += run - return None - - -def rle_subseg( rle, start, end ): - """Return a sub segment of an rle list.""" - l = [] - x = 0 - for a, run in rle: - if start: - if start >= run: - start -= run - x += run - continue - x += start - run -= start - start = 0 - if x >= end: - break - if x+run > end: - run = end-x - x += run - l.append( (a, run) ) - return l - - -def rle_len( rle ): - """ - Return the number of characters covered by a run length - encoded attribute list. - """ - - run = 0 - for v in rle: - assert type(v) == tuple, repr(rle) - a, r = v - run += r - return run - -def rle_append_beginning_modify(rle, a_r): - """ - Append (a, r) (unpacked from *a_r*) to BEGINNING of rle. - Merge with first run when possible - - MODIFIES rle parameter contents. Returns None. - """ - a, r = a_r - if not rle: - rle[:] = [(a, r)] - else: - al, run = rle[0] - if a == al: - rle[0] = (a,run+r) - else: - rle[0:0] = [(al, r)] - - -def rle_append_modify(rle, a_r): - """ - Append (a, r) (unpacked from *a_r*) to the rle list rle. - Merge with last run when possible. - - MODIFIES rle parameter contents. Returns None. - """ - a, r = a_r - if not rle or rle[-1][0] != a: - rle.append( (a,r) ) - return - la,lr = rle[-1] - rle[-1] = (a, lr+r) - -def rle_join_modify( rle, rle2 ): - """ - Append attribute list rle2 to rle. - Merge last run of rle with first run of rle2 when possible. - - MODIFIES attr parameter contents. Returns None. - """ - if not rle2: - return - rle_append_modify(rle, rle2[0]) - rle += rle2[1:] - -def rle_product( rle1, rle2 ): - """ - Merge the runs of rle1 and rle2 like this: - eg. - rle1 = [ ("a", 10), ("b", 5) ] - rle2 = [ ("Q", 5), ("P", 10) ] - rle_product: [ (("a","Q"), 5), (("a","P"), 5), (("b","P"), 5) ] - - rle1 and rle2 are assumed to cover the same total run. - """ - i1 = i2 = 1 # rle1, rle2 indexes - if not rle1 or not rle2: return [] - a1, r1 = rle1[0] - a2, r2 = rle2[0] - - l = [] - while r1 and r2: - r = min(r1, r2) - rle_append_modify( l, ((a1,a2),r) ) - r1 -= r - if r1 == 0 and i1< len(rle1): - a1, r1 = rle1[i1] - i1 += 1 - r2 -= r - if r2 == 0 and i2< len(rle2): - a2, r2 = rle2[i2] - i2 += 1 - return l - - -def rle_factor( rle ): - """ - Inverse of rle_product. - """ - rle1 = [] - rle2 = [] - for (a1, a2), r in rle: - rle_append_modify( rle1, (a1, r) ) - rle_append_modify( rle2, (a2, r) ) - return rle1, rle2 - - -class TagMarkupException(Exception): pass - -def decompose_tagmarkup(tm): - """Return (text string, attribute list) for tagmarkup passed.""" - - tl, al = _tagmarkup_recurse(tm, None) - # join as unicode or bytes based on type of first element - text = tl[0][:0].join(tl) - - if al and al[-1][0] is None: - del al[-1] - - return text, al - -def _tagmarkup_recurse( tm, attr ): - """Return (text list, attribute list) for tagmarkup passed. - - tm -- tagmarkup - attr -- current attribute or None""" - - if type(tm) == list: - # for lists recurse to process each subelement - rtl = [] - ral = [] - for element in tm: - tl, al = _tagmarkup_recurse( element, attr ) - if ral: - # merge attributes when possible - last_attr, last_run = ral[-1] - top_attr, top_run = al[0] - if last_attr == top_attr: - ral[-1] = (top_attr, last_run + top_run) - del al[-1] - rtl += tl - ral += al - return rtl, ral - - if type(tm) == tuple: - # tuples mark a new attribute boundary - if len(tm) != 2: - raise TagMarkupException("Tuples must be in the form (attribute, tagmarkup): %r" % (tm,)) - - attr, element = tm - return _tagmarkup_recurse( element, attr ) - - if not isinstance(tm,(str, bytes)): - raise TagMarkupException("Invalid markup element: %r" % tm) - - # text - return [tm], [(attr, len(tm))] - - - -def is_mouse_event( ev ): - return type(ev) == tuple and len(ev)==4 and ev[0].find("mouse")>=0 - -def is_mouse_press( ev ): - return ev.find("press")>=0 - - - -class MetaSuper(type): - """adding .__super""" - def __init__(cls, name, bases, d): - super(MetaSuper, cls).__init__(name, bases, d) - if hasattr(cls, "_%s__super" % name): - raise AttributeError("Class has same name as one of its super classes") - setattr(cls, "_%s__super" % name, super(cls)) - - - -def int_scale(val, val_range, out_range): - """ - Scale val in the range [0, val_range-1] to an integer in the range - [0, out_range-1]. This implementation uses the "round-half-up" rounding - method. - - >>> "%x" % int_scale(0x7, 0x10, 0x10000) - '7777' - >>> "%x" % int_scale(0x5f, 0x100, 0x10) - '6' - >>> int_scale(2, 6, 101) - 40 - >>> int_scale(1, 3, 4) - 2 - """ - num = int(val * (out_range-1) * 2 + (val_range-1)) - dem = ((val_range-1) * 2) - # if num % dem == 0 then we are exactly half-way and have rounded up. - return num // dem - - -class StoppingContext(object): - """Context manager that calls ``stop`` on a given object on exit. Used to - make the ``start`` method on `MainLoop` and `BaseScreen` optionally act as - context managers. - """ - def __init__(self, wrapped): - self._wrapped = wrapped - - def __enter__(self): - return self - - def __exit__(self, *exc_info): - self._wrapped.stop()