Include urwid in the src.
Urwid is only be used for input, not display. Now adding the minimal set of urwid modules that vigil needs. These can be further pared down in future. Thank you to Ian Ward.
This commit is contained in:
parent
cf128bc0c3
commit
4ee9f57fb6
9 changed files with 3562 additions and 1 deletions
|
|
@ -6,7 +6,7 @@ set -e
|
|||
|
||||
echo "Install the dependencies of the vigil script..."
|
||||
sudo apt-get --yes install python3-minimal python3-pygments python3-pyinotify \
|
||||
python3-urwid python3-docopt util-linux
|
||||
python3-docopt util-linux
|
||||
echo
|
||||
echo "Install all the tools vigil may need..."
|
||||
./install-tools
|
||||
|
|
|
|||
21
urwid/__init__.py
Normal file
21
urwid/__init__.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/python
|
||||
#
|
||||
# Urwid __init__.py - all the stuff you're likely to care about
|
||||
#
|
||||
# Copyright (C) 2004-2012 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/
|
||||
37
urwid/compat.py
Normal file
37
urwid/compat.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#!/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
|
||||
893
urwid/display_common.py
Normal file
893
urwid/display_common.py
Normal file
|
|
@ -0,0 +1,893 @@
|
|||
#!/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
|
||||
|
||||
try:
|
||||
import termios
|
||||
except ImportError:
|
||||
pass # windows
|
||||
|
||||
from urwid.util import StoppingContext, int_scale
|
||||
from urwid import signals
|
||||
from urwid.compat import B, bytes3
|
||||
|
||||
# for replacing unprintable bytes with '?'
|
||||
UNPRINTABLE_TRANS_TABLE = B("?") * 32 + bytes3(list(range(32,256)))
|
||||
|
||||
|
||||
# signals sent by BaseScreen
|
||||
UPDATE_PALETTE_ENTRY = "update palette entry"
|
||||
INPUT_DESCRIPTORS_CHANGED = "input descriptors changed"
|
||||
|
||||
|
||||
# AttrSpec internal values
|
||||
_BASIC_START = 0 # first index of basic color aliases
|
||||
_CUBE_START = 16 # first index of color cube
|
||||
_CUBE_SIZE_256 = 6 # one side of the color cube
|
||||
_GRAY_SIZE_256 = 24
|
||||
_GRAY_START_256 = _CUBE_SIZE_256 ** 3 + _CUBE_START
|
||||
_CUBE_WHITE_256 = _GRAY_START_256 -1
|
||||
_CUBE_SIZE_88 = 4
|
||||
_GRAY_SIZE_88 = 8
|
||||
_GRAY_START_88 = _CUBE_SIZE_88 ** 3 + _CUBE_START
|
||||
_CUBE_WHITE_88 = _GRAY_START_88 -1
|
||||
_CUBE_BLACK = _CUBE_START
|
||||
|
||||
# values copied from xterm 256colres.h:
|
||||
_CUBE_STEPS_256 = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]
|
||||
_GRAY_STEPS_256 = [0x08, 0x12, 0x1c, 0x26, 0x30, 0x3a, 0x44, 0x4e, 0x58, 0x62,
|
||||
0x6c, 0x76, 0x80, 0x84, 0x94, 0x9e, 0xa8, 0xb2, 0xbc, 0xc6, 0xd0,
|
||||
0xda, 0xe4, 0xee]
|
||||
# values copied from xterm 88colres.h:
|
||||
_CUBE_STEPS_88 = [0x00, 0x8b, 0xcd, 0xff]
|
||||
_GRAY_STEPS_88 = [0x2e, 0x5c, 0x73, 0x8b, 0xa2, 0xb9, 0xd0, 0xe7]
|
||||
# values copied from X11/rgb.txt and XTerm-col.ad:
|
||||
_BASIC_COLOR_VALUES = [(0,0,0), (205, 0, 0), (0, 205, 0), (205, 205, 0),
|
||||
(0, 0, 238), (205, 0, 205), (0, 205, 205), (229, 229, 229),
|
||||
(127, 127, 127), (255, 0, 0), (0, 255, 0), (255, 255, 0),
|
||||
(0x5c, 0x5c, 0xff), (255, 0, 255), (0, 255, 255), (255, 255, 255)]
|
||||
|
||||
_COLOR_VALUES_256 = (_BASIC_COLOR_VALUES +
|
||||
[(r, g, b) for r in _CUBE_STEPS_256 for g in _CUBE_STEPS_256
|
||||
for b in _CUBE_STEPS_256] +
|
||||
[(gr, gr, gr) for gr in _GRAY_STEPS_256])
|
||||
_COLOR_VALUES_88 = (_BASIC_COLOR_VALUES +
|
||||
[(r, g, b) for r in _CUBE_STEPS_88 for g in _CUBE_STEPS_88
|
||||
for b in _CUBE_STEPS_88] +
|
||||
[(gr, gr, gr) for gr in _GRAY_STEPS_88])
|
||||
|
||||
assert len(_COLOR_VALUES_256) == 256
|
||||
assert len(_COLOR_VALUES_88) == 88
|
||||
|
||||
_FG_COLOR_MASK = 0x000000ff
|
||||
_BG_COLOR_MASK = 0x0000ff00
|
||||
_FG_BASIC_COLOR = 0x00010000
|
||||
_FG_HIGH_COLOR = 0x00020000
|
||||
_BG_BASIC_COLOR = 0x00040000
|
||||
_BG_HIGH_COLOR = 0x00080000
|
||||
_BG_SHIFT = 8
|
||||
_HIGH_88_COLOR = 0x00100000
|
||||
_STANDOUT = 0x02000000
|
||||
_UNDERLINE = 0x04000000
|
||||
_BOLD = 0x08000000
|
||||
_BLINK = 0x10000000
|
||||
_FG_MASK = (_FG_COLOR_MASK | _FG_BASIC_COLOR | _FG_HIGH_COLOR |
|
||||
_STANDOUT | _UNDERLINE | _BLINK | _BOLD)
|
||||
_BG_MASK = _BG_COLOR_MASK | _BG_BASIC_COLOR | _BG_HIGH_COLOR
|
||||
|
||||
DEFAULT = 'default'
|
||||
BLACK = 'black'
|
||||
DARK_RED = 'dark red'
|
||||
DARK_GREEN = 'dark green'
|
||||
BROWN = 'brown'
|
||||
DARK_BLUE = 'dark blue'
|
||||
DARK_MAGENTA = 'dark magenta'
|
||||
DARK_CYAN = 'dark cyan'
|
||||
LIGHT_GRAY = 'light gray'
|
||||
DARK_GRAY = 'dark gray'
|
||||
LIGHT_RED = 'light red'
|
||||
LIGHT_GREEN = 'light green'
|
||||
YELLOW = 'yellow'
|
||||
LIGHT_BLUE = 'light blue'
|
||||
LIGHT_MAGENTA = 'light magenta'
|
||||
LIGHT_CYAN = 'light cyan'
|
||||
WHITE = 'white'
|
||||
|
||||
_BASIC_COLORS = [
|
||||
BLACK,
|
||||
DARK_RED,
|
||||
DARK_GREEN,
|
||||
BROWN,
|
||||
DARK_BLUE,
|
||||
DARK_MAGENTA,
|
||||
DARK_CYAN,
|
||||
LIGHT_GRAY,
|
||||
DARK_GRAY,
|
||||
LIGHT_RED,
|
||||
LIGHT_GREEN,
|
||||
YELLOW,
|
||||
LIGHT_BLUE,
|
||||
LIGHT_MAGENTA,
|
||||
LIGHT_CYAN,
|
||||
WHITE,
|
||||
]
|
||||
|
||||
_ATTRIBUTES = {
|
||||
'bold': _BOLD,
|
||||
'underline': _UNDERLINE,
|
||||
'blink': _BLINK,
|
||||
'standout': _STANDOUT,
|
||||
}
|
||||
|
||||
def _value_lookup_table(values, size):
|
||||
"""
|
||||
Generate a lookup table for finding the closest item in values.
|
||||
Lookup returns (index into values)+1
|
||||
|
||||
values -- list of values in ascending order, all < size
|
||||
size -- size of lookup table and maximum value
|
||||
|
||||
>>> _value_lookup_table([0, 7, 9], 10)
|
||||
[0, 0, 0, 0, 1, 1, 1, 1, 2, 2]
|
||||
"""
|
||||
|
||||
middle_values = [0] + [(values[i] + values[i + 1] + 1) // 2
|
||||
for i in range(len(values) - 1)] + [size]
|
||||
lookup_table = []
|
||||
for i in range(len(middle_values)-1):
|
||||
count = middle_values[i + 1] - middle_values[i]
|
||||
lookup_table.extend([i] * count)
|
||||
return lookup_table
|
||||
|
||||
_CUBE_256_LOOKUP = _value_lookup_table(_CUBE_STEPS_256, 256)
|
||||
_GRAY_256_LOOKUP = _value_lookup_table([0] + _GRAY_STEPS_256 + [0xff], 256)
|
||||
_CUBE_88_LOOKUP = _value_lookup_table(_CUBE_STEPS_88, 256)
|
||||
_GRAY_88_LOOKUP = _value_lookup_table([0] + _GRAY_STEPS_88 + [0xff], 256)
|
||||
|
||||
# convert steps to values that will be used by string versions of the colors
|
||||
# 1 hex digit for rgb and 0..100 for grayscale
|
||||
_CUBE_STEPS_256_16 = [int_scale(n, 0x100, 0x10) for n in _CUBE_STEPS_256]
|
||||
_GRAY_STEPS_256_101 = [int_scale(n, 0x100, 101) for n in _GRAY_STEPS_256]
|
||||
_CUBE_STEPS_88_16 = [int_scale(n, 0x100, 0x10) for n in _CUBE_STEPS_88]
|
||||
_GRAY_STEPS_88_101 = [int_scale(n, 0x100, 101) for n in _GRAY_STEPS_88]
|
||||
|
||||
# create lookup tables for 1 hex digit rgb and 0..100 for grayscale values
|
||||
_CUBE_256_LOOKUP_16 = [_CUBE_256_LOOKUP[int_scale(n, 16, 0x100)]
|
||||
for n in range(16)]
|
||||
_GRAY_256_LOOKUP_101 = [_GRAY_256_LOOKUP[int_scale(n, 101, 0x100)]
|
||||
for n in range(101)]
|
||||
_CUBE_88_LOOKUP_16 = [_CUBE_88_LOOKUP[int_scale(n, 16, 0x100)]
|
||||
for n in range(16)]
|
||||
_GRAY_88_LOOKUP_101 = [_GRAY_88_LOOKUP[int_scale(n, 101, 0x100)]
|
||||
for n in range(101)]
|
||||
|
||||
|
||||
# The functions _gray_num_256() and _gray_num_88() do not include the gray
|
||||
# values from the color cube so that the gray steps are an even width.
|
||||
# The color cube grays are available by using the rgb functions. Pure
|
||||
# white and black are taken from the color cube, since the gray range does
|
||||
# not include them, and the basic colors are more likely to have been
|
||||
# customized by an end-user.
|
||||
|
||||
|
||||
def _gray_num_256(gnum):
|
||||
"""Return ths color number for gray number gnum.
|
||||
|
||||
Color cube black and white are returned for 0 and 25 respectively
|
||||
since those values aren't included in the gray scale.
|
||||
|
||||
"""
|
||||
# grays start from index 1
|
||||
gnum -= 1
|
||||
|
||||
if gnum < 0:
|
||||
return _CUBE_BLACK
|
||||
if gnum >= _GRAY_SIZE_256:
|
||||
return _CUBE_WHITE_256
|
||||
return _GRAY_START_256 + gnum
|
||||
|
||||
|
||||
def _gray_num_88(gnum):
|
||||
"""Return ths color number for gray number gnum.
|
||||
|
||||
Color cube black and white are returned for 0 and 9 respectively
|
||||
since those values aren't included in the gray scale.
|
||||
|
||||
"""
|
||||
# gnums start from index 1
|
||||
gnum -= 1
|
||||
|
||||
if gnum < 0:
|
||||
return _CUBE_BLACK
|
||||
if gnum >= _GRAY_SIZE_88:
|
||||
return _CUBE_WHITE_88
|
||||
return _GRAY_START_88 + gnum
|
||||
|
||||
|
||||
def _color_desc_256(num):
|
||||
"""
|
||||
Return a string description of color number num.
|
||||
0..15 -> 'h0'..'h15' basic colors (as high-colors)
|
||||
16..231 -> '#000'..'#fff' color cube colors
|
||||
232..255 -> 'g3'..'g93' grays
|
||||
|
||||
>>> _color_desc_256(15)
|
||||
'h15'
|
||||
>>> _color_desc_256(16)
|
||||
'#000'
|
||||
>>> _color_desc_256(17)
|
||||
'#006'
|
||||
>>> _color_desc_256(230)
|
||||
'#ffd'
|
||||
>>> _color_desc_256(233)
|
||||
'g7'
|
||||
>>> _color_desc_256(234)
|
||||
'g11'
|
||||
|
||||
"""
|
||||
assert num >= 0 and num < 256, num
|
||||
if num < _CUBE_START:
|
||||
return 'h%d' % num
|
||||
if num < _GRAY_START_256:
|
||||
num -= _CUBE_START
|
||||
b, num = num % _CUBE_SIZE_256, num // _CUBE_SIZE_256
|
||||
g, num = num % _CUBE_SIZE_256, num // _CUBE_SIZE_256
|
||||
r = num % _CUBE_SIZE_256
|
||||
return '#%x%x%x' % (_CUBE_STEPS_256_16[r], _CUBE_STEPS_256_16[g],
|
||||
_CUBE_STEPS_256_16[b])
|
||||
return 'g%d' % _GRAY_STEPS_256_101[num - _GRAY_START_256]
|
||||
|
||||
def _color_desc_88(num):
|
||||
"""
|
||||
Return a string description of color number num.
|
||||
0..15 -> 'h0'..'h15' basic colors (as high-colors)
|
||||
16..79 -> '#000'..'#fff' color cube colors
|
||||
80..87 -> 'g18'..'g90' grays
|
||||
|
||||
>>> _color_desc_88(15)
|
||||
'h15'
|
||||
>>> _color_desc_88(16)
|
||||
'#000'
|
||||
>>> _color_desc_88(17)
|
||||
'#008'
|
||||
>>> _color_desc_88(78)
|
||||
'#ffc'
|
||||
>>> _color_desc_88(81)
|
||||
'g36'
|
||||
>>> _color_desc_88(82)
|
||||
'g45'
|
||||
|
||||
"""
|
||||
assert num > 0 and num < 88
|
||||
if num < _CUBE_START:
|
||||
return 'h%d' % num
|
||||
if num < _GRAY_START_88:
|
||||
num -= _CUBE_START
|
||||
b, num = num % _CUBE_SIZE_88, num // _CUBE_SIZE_88
|
||||
g, r= num % _CUBE_SIZE_88, num // _CUBE_SIZE_88
|
||||
return '#%x%x%x' % (_CUBE_STEPS_88_16[r], _CUBE_STEPS_88_16[g],
|
||||
_CUBE_STEPS_88_16[b])
|
||||
return 'g%d' % _GRAY_STEPS_88_101[num - _GRAY_START_88]
|
||||
|
||||
def _parse_color_256(desc):
|
||||
"""
|
||||
Return a color number for the description desc.
|
||||
'h0'..'h255' -> 0..255 actual color number
|
||||
'#000'..'#fff' -> 16..231 color cube colors
|
||||
'g0'..'g100' -> 16, 232..255, 231 grays and color cube black/white
|
||||
'g#00'..'g#ff' -> 16, 232...255, 231 gray and color cube black/white
|
||||
|
||||
Returns None if desc is invalid.
|
||||
|
||||
>>> _parse_color_256('h142')
|
||||
142
|
||||
>>> _parse_color_256('#f00')
|
||||
196
|
||||
>>> _parse_color_256('g100')
|
||||
231
|
||||
>>> _parse_color_256('g#80')
|
||||
244
|
||||
"""
|
||||
if len(desc) > 4:
|
||||
# keep the length within reason before parsing
|
||||
return None
|
||||
try:
|
||||
if desc.startswith('h'):
|
||||
# high-color number
|
||||
num = int(desc[1:], 10)
|
||||
if num < 0 or num > 255:
|
||||
return None
|
||||
return num
|
||||
|
||||
if desc.startswith('#') and len(desc) == 4:
|
||||
# color-cube coordinates
|
||||
rgb = int(desc[1:], 16)
|
||||
if rgb < 0:
|
||||
return None
|
||||
b, rgb = rgb % 16, rgb // 16
|
||||
g, r = rgb % 16, rgb // 16
|
||||
# find the closest rgb values
|
||||
r = _CUBE_256_LOOKUP_16[r]
|
||||
g = _CUBE_256_LOOKUP_16[g]
|
||||
b = _CUBE_256_LOOKUP_16[b]
|
||||
return _CUBE_START + (r * _CUBE_SIZE_256 + g) * _CUBE_SIZE_256 + b
|
||||
|
||||
# Only remaining possibility is gray value
|
||||
if desc.startswith('g#'):
|
||||
# hex value 00..ff
|
||||
gray = int(desc[2:], 16)
|
||||
if gray < 0 or gray > 255:
|
||||
return None
|
||||
gray = _GRAY_256_LOOKUP[gray]
|
||||
elif desc.startswith('g'):
|
||||
# decimal value 0..100
|
||||
gray = int(desc[1:], 10)
|
||||
if gray < 0 or gray > 100:
|
||||
return None
|
||||
gray = _GRAY_256_LOOKUP_101[gray]
|
||||
else:
|
||||
return None
|
||||
if gray == 0:
|
||||
return _CUBE_BLACK
|
||||
gray -= 1
|
||||
if gray == _GRAY_SIZE_256:
|
||||
return _CUBE_WHITE_256
|
||||
return _GRAY_START_256 + gray
|
||||
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def _parse_color_88(desc):
|
||||
"""
|
||||
Return a color number for the description desc.
|
||||
'h0'..'h87' -> 0..87 actual color number
|
||||
'#000'..'#fff' -> 16..79 color cube colors
|
||||
'g0'..'g100' -> 16, 80..87, 79 grays and color cube black/white
|
||||
'g#00'..'g#ff' -> 16, 80...87, 79 gray and color cube black/white
|
||||
|
||||
Returns None if desc is invalid.
|
||||
|
||||
>>> _parse_color_88('h142')
|
||||
>>> _parse_color_88('h42')
|
||||
42
|
||||
>>> _parse_color_88('#f00')
|
||||
64
|
||||
>>> _parse_color_88('g100')
|
||||
79
|
||||
>>> _parse_color_88('g#80')
|
||||
83
|
||||
"""
|
||||
if len(desc) > 4:
|
||||
# keep the length within reason before parsing
|
||||
return None
|
||||
try:
|
||||
if desc.startswith('h'):
|
||||
# high-color number
|
||||
num = int(desc[1:], 10)
|
||||
if num < 0 or num > 87:
|
||||
return None
|
||||
return num
|
||||
|
||||
if desc.startswith('#') and len(desc) == 4:
|
||||
# color-cube coordinates
|
||||
rgb = int(desc[1:], 16)
|
||||
if rgb < 0:
|
||||
return None
|
||||
b, rgb = rgb % 16, rgb // 16
|
||||
g, r = rgb % 16, rgb // 16
|
||||
# find the closest rgb values
|
||||
r = _CUBE_88_LOOKUP_16[r]
|
||||
g = _CUBE_88_LOOKUP_16[g]
|
||||
b = _CUBE_88_LOOKUP_16[b]
|
||||
return _CUBE_START + (r * _CUBE_SIZE_88 + g) * _CUBE_SIZE_88 + b
|
||||
|
||||
# Only remaining possibility is gray value
|
||||
if desc.startswith('g#'):
|
||||
# hex value 00..ff
|
||||
gray = int(desc[2:], 16)
|
||||
if gray < 0 or gray > 255:
|
||||
return None
|
||||
gray = _GRAY_88_LOOKUP[gray]
|
||||
elif desc.startswith('g'):
|
||||
# decimal value 0..100
|
||||
gray = int(desc[1:], 10)
|
||||
if gray < 0 or gray > 100:
|
||||
return None
|
||||
gray = _GRAY_88_LOOKUP_101[gray]
|
||||
else:
|
||||
return None
|
||||
if gray == 0:
|
||||
return _CUBE_BLACK
|
||||
gray -= 1
|
||||
if gray == _GRAY_SIZE_88:
|
||||
return _CUBE_WHITE_88
|
||||
return _GRAY_START_88 + gray
|
||||
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
class AttrSpecError(Exception):
|
||||
pass
|
||||
|
||||
class AttrSpec(object):
|
||||
def __init__(self, fg, bg, colors=256):
|
||||
"""
|
||||
fg -- a string containing a comma-separated foreground color
|
||||
and settings
|
||||
|
||||
Color values:
|
||||
'default' (use the terminal's default foreground),
|
||||
'black', 'dark red', 'dark green', 'brown', 'dark blue',
|
||||
'dark magenta', 'dark cyan', 'light gray', 'dark gray',
|
||||
'light red', 'light green', 'yellow', 'light blue',
|
||||
'light magenta', 'light cyan', 'white'
|
||||
|
||||
High-color example values:
|
||||
'#009' (0% red, 0% green, 60% red, like HTML colors)
|
||||
'#fcc' (100% red, 80% green, 80% blue)
|
||||
'g40' (40% gray, decimal), 'g#cc' (80% gray, hex),
|
||||
'#000', 'g0', 'g#00' (black),
|
||||
'#fff', 'g100', 'g#ff' (white)
|
||||
'h8' (color number 8), 'h255' (color number 255)
|
||||
|
||||
Setting:
|
||||
'bold', 'underline', 'blink', 'standout'
|
||||
|
||||
Some terminals use 'bold' for bright colors. Most terminals
|
||||
ignore the 'blink' setting. If the color is not given then
|
||||
'default' will be assumed.
|
||||
|
||||
bg -- a string containing the background color
|
||||
|
||||
Color values:
|
||||
'default' (use the terminal's default background),
|
||||
'black', 'dark red', 'dark green', 'brown', 'dark blue',
|
||||
'dark magenta', 'dark cyan', 'light gray'
|
||||
|
||||
High-color exaples:
|
||||
see fg examples above
|
||||
|
||||
An empty string will be treated the same as 'default'.
|
||||
|
||||
colors -- the maximum colors available for the specification
|
||||
|
||||
Valid values include: 1, 16, 88 and 256. High-color
|
||||
values are only usable with 88 or 256 colors. With
|
||||
1 color only the foreground settings may be used.
|
||||
|
||||
>>> AttrSpec('dark red', 'light gray', 16)
|
||||
AttrSpec('dark red', 'light gray')
|
||||
>>> AttrSpec('yellow, underline, bold', 'dark blue')
|
||||
AttrSpec('yellow,bold,underline', 'dark blue')
|
||||
>>> AttrSpec('#ddb', '#004', 256) # closest colors will be found
|
||||
AttrSpec('#dda', '#006')
|
||||
>>> AttrSpec('#ddb', '#004', 88)
|
||||
AttrSpec('#ccc', '#000', colors=88)
|
||||
"""
|
||||
if colors not in (1, 16, 88, 256):
|
||||
raise AttrSpecError('invalid number of colors (%d).' % colors)
|
||||
self._value = 0 | _HIGH_88_COLOR * (colors == 88)
|
||||
self.foreground = fg
|
||||
self.background = bg
|
||||
if self.colors > colors:
|
||||
raise AttrSpecError(('foreground/background (%s/%s) require ' +
|
||||
'more colors than have been specified (%d).') %
|
||||
(repr(fg), repr(bg), colors))
|
||||
|
||||
foreground_basic = property(lambda s: s._value & _FG_BASIC_COLOR != 0)
|
||||
foreground_high = property(lambda s: s._value & _FG_HIGH_COLOR != 0)
|
||||
foreground_number = property(lambda s: s._value & _FG_COLOR_MASK)
|
||||
background_basic = property(lambda s: s._value & _BG_BASIC_COLOR != 0)
|
||||
background_high = property(lambda s: s._value & _BG_HIGH_COLOR != 0)
|
||||
background_number = property(lambda s: (s._value & _BG_COLOR_MASK)
|
||||
>> _BG_SHIFT)
|
||||
bold = property(lambda s: s._value & _BOLD != 0)
|
||||
underline = property(lambda s: s._value & _UNDERLINE != 0)
|
||||
blink = property(lambda s: s._value & _BLINK != 0)
|
||||
standout = property(lambda s: s._value & _STANDOUT != 0)
|
||||
|
||||
def _colors(self):
|
||||
"""
|
||||
Return the maximum colors required for this object.
|
||||
|
||||
Returns 256, 88, 16 or 1.
|
||||
"""
|
||||
if self._value & _HIGH_88_COLOR:
|
||||
return 88
|
||||
if self._value & (_BG_HIGH_COLOR | _FG_HIGH_COLOR):
|
||||
return 256
|
||||
if self._value & (_BG_BASIC_COLOR | _BG_BASIC_COLOR):
|
||||
return 16
|
||||
return 1
|
||||
colors = property(_colors)
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Return an executable python representation of the AttrSpec
|
||||
object.
|
||||
"""
|
||||
args = "%r, %r" % (self.foreground, self.background)
|
||||
if self.colors == 88:
|
||||
# 88-color mode is the only one that is handled differently
|
||||
args = args + ", colors=88"
|
||||
return "%s(%s)" % (self.__class__.__name__, args)
|
||||
|
||||
def _foreground_color(self):
|
||||
"""Return only the color component of the foreground."""
|
||||
if not (self.foreground_basic or self.foreground_high):
|
||||
return 'default'
|
||||
if self.foreground_basic:
|
||||
return _BASIC_COLORS[self.foreground_number]
|
||||
if self.colors == 88:
|
||||
return _color_desc_88(self.foreground_number)
|
||||
return _color_desc_256(self.foreground_number)
|
||||
|
||||
def _foreground(self):
|
||||
return (self._foreground_color() +
|
||||
',bold' * self.bold + ',standout' * self.standout +
|
||||
',blink' * self.blink + ',underline' * self.underline)
|
||||
|
||||
def _set_foreground(self, foreground):
|
||||
color = None
|
||||
flags = 0
|
||||
# handle comma-separated foreground
|
||||
for part in foreground.split(','):
|
||||
part = part.strip()
|
||||
if part in _ATTRIBUTES:
|
||||
# parse and store "settings"/attributes in flags
|
||||
if flags & _ATTRIBUTES[part]:
|
||||
raise AttrSpecError(("Setting %s specified more than" +
|
||||
"once in foreground (%s)") % (repr(part),
|
||||
repr(foreground)))
|
||||
flags |= _ATTRIBUTES[part]
|
||||
continue
|
||||
# past this point we must be specifying a color
|
||||
if part in ('', 'default'):
|
||||
scolor = 0
|
||||
elif part in _BASIC_COLORS:
|
||||
scolor = _BASIC_COLORS.index(part)
|
||||
flags |= _FG_BASIC_COLOR
|
||||
elif self._value & _HIGH_88_COLOR:
|
||||
scolor = _parse_color_88(part)
|
||||
flags |= _FG_HIGH_COLOR
|
||||
else:
|
||||
scolor = _parse_color_256(part)
|
||||
flags |= _FG_HIGH_COLOR
|
||||
# _parse_color_*() return None for unrecognised colors
|
||||
if scolor is None:
|
||||
raise AttrSpecError(("Unrecognised color specification %s " +
|
||||
"in foreground (%s)") % (repr(part), repr(foreground)))
|
||||
if color is not None:
|
||||
raise AttrSpecError(("More than one color given for " +
|
||||
"foreground (%s)") % (repr(foreground),))
|
||||
color = scolor
|
||||
if color is None:
|
||||
color = 0
|
||||
self._value = (self._value & ~_FG_MASK) | color | flags
|
||||
|
||||
foreground = property(_foreground, _set_foreground)
|
||||
|
||||
def _background(self):
|
||||
"""Return the background color."""
|
||||
if not (self.background_basic or self.background_high):
|
||||
return 'default'
|
||||
if self.background_basic:
|
||||
return _BASIC_COLORS[self.background_number]
|
||||
if self._value & _HIGH_88_COLOR:
|
||||
return _color_desc_88(self.background_number)
|
||||
return _color_desc_256(self.background_number)
|
||||
|
||||
def _set_background(self, background):
|
||||
flags = 0
|
||||
if background in ('', 'default'):
|
||||
color = 0
|
||||
elif background in _BASIC_COLORS:
|
||||
color = _BASIC_COLORS.index(background)
|
||||
flags |= _BG_BASIC_COLOR
|
||||
elif self._value & _HIGH_88_COLOR:
|
||||
color = _parse_color_88(background)
|
||||
flags |= _BG_HIGH_COLOR
|
||||
else:
|
||||
color = _parse_color_256(background)
|
||||
flags |= _BG_HIGH_COLOR
|
||||
if color is None:
|
||||
raise AttrSpecError(("Unrecognised color specification " +
|
||||
"in background (%s)") % (repr(background),))
|
||||
self._value = (self._value & ~_BG_MASK) | (color << _BG_SHIFT) | flags
|
||||
|
||||
background = property(_background, _set_background)
|
||||
|
||||
def get_rgb_values(self):
|
||||
"""
|
||||
Return (fg_red, fg_green, fg_blue, bg_red, bg_green, bg_blue) color
|
||||
components. Each component is in the range 0-255. Values are taken
|
||||
from the XTerm defaults and may not exactly match the user's terminal.
|
||||
|
||||
If the foreground or background is 'default' then all their compenents
|
||||
will be returned as None.
|
||||
|
||||
>>> AttrSpec('yellow', '#ccf', colors=88).get_rgb_values()
|
||||
(255, 255, 0, 205, 205, 255)
|
||||
>>> AttrSpec('default', 'g92').get_rgb_values()
|
||||
(None, None, None, 238, 238, 238)
|
||||
"""
|
||||
if not (self.foreground_basic or self.foreground_high):
|
||||
vals = (None, None, None)
|
||||
elif self.colors == 88:
|
||||
assert self.foreground_number < 88, "Invalid AttrSpec _value"
|
||||
vals = _COLOR_VALUES_88[self.foreground_number]
|
||||
else:
|
||||
vals = _COLOR_VALUES_256[self.foreground_number]
|
||||
|
||||
if not (self.background_basic or self.background_high):
|
||||
return vals + (None, None, None)
|
||||
elif self.colors == 88:
|
||||
assert self.background_number < 88, "Invalid AttrSpec _value"
|
||||
return vals + _COLOR_VALUES_88[self.background_number]
|
||||
else:
|
||||
return vals + _COLOR_VALUES_256[self.background_number]
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, AttrSpec) and self._value == other._value
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
__hash__ = object.__hash__
|
||||
|
||||
|
||||
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(object, metaclass=signals.MetaSignals):
|
||||
"""
|
||||
Base class for Screen classes (raw_display.Screen, .. etc)
|
||||
"""
|
||||
signals = [UPDATE_PALETTE_ENTRY, INPUT_DESCRIPTORS_CHANGED]
|
||||
|
||||
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 run_wrapper(self, fn, *args, **kwargs):
|
||||
"""Start the screen, call a function, then stop the screen. Extra
|
||||
arguments are passed to `start`.
|
||||
|
||||
Deprecated in favor of calling `start` as a context manager.
|
||||
"""
|
||||
with self.start(*args, **kwargs):
|
||||
return fn()
|
||||
|
||||
|
||||
def register_palette(self, palette):
|
||||
"""Register a set of palette entries.
|
||||
|
||||
palette -- a list of (name, like_other_name) or
|
||||
(name, foreground, background, mono, foreground_high,
|
||||
background_high) tuples
|
||||
|
||||
The (name, like_other_name) format will copy the settings
|
||||
from the palette entry like_other_name, which must appear
|
||||
before this tuple in the list.
|
||||
|
||||
The mono and foreground/background_high values are
|
||||
optional ie. the second tuple format may have 3, 4 or 6
|
||||
values. See register_palette_entry() for a description
|
||||
of the tuple values.
|
||||
"""
|
||||
|
||||
for item in palette:
|
||||
if len(item) in (3,4,6):
|
||||
self.register_palette_entry(*item)
|
||||
continue
|
||||
if len(item) != 2:
|
||||
raise ScreenError("Invalid register_palette entry: %s" %
|
||||
repr(item))
|
||||
name, like_name = item
|
||||
if like_name not in self._palette:
|
||||
raise ScreenError("palette entry '%s' doesn't exist"%like_name)
|
||||
self._palette[name] = self._palette[like_name]
|
||||
|
||||
def register_palette_entry(self, name, foreground, background,
|
||||
mono=None, foreground_high=None, background_high=None):
|
||||
"""Register a single palette entry.
|
||||
|
||||
name -- new entry/attribute name
|
||||
|
||||
foreground -- a string containing a comma-separated foreground
|
||||
color and settings
|
||||
|
||||
Color values:
|
||||
'default' (use the terminal's default foreground),
|
||||
'black', 'dark red', 'dark green', 'brown', 'dark blue',
|
||||
'dark magenta', 'dark cyan', 'light gray', 'dark gray',
|
||||
'light red', 'light green', 'yellow', 'light blue',
|
||||
'light magenta', 'light cyan', 'white'
|
||||
|
||||
Settings:
|
||||
'bold', 'underline', 'blink', 'standout'
|
||||
|
||||
Some terminals use 'bold' for bright colors. Most terminals
|
||||
ignore the 'blink' setting. If the color is not given then
|
||||
'default' will be assumed.
|
||||
|
||||
background -- a string containing the background color
|
||||
|
||||
Background color values:
|
||||
'default' (use the terminal's default background),
|
||||
'black', 'dark red', 'dark green', 'brown', 'dark blue',
|
||||
'dark magenta', 'dark cyan', 'light gray'
|
||||
|
||||
mono -- a comma-separated string containing monochrome terminal
|
||||
settings (see "Settings" above.)
|
||||
|
||||
None = no terminal settings (same as 'default')
|
||||
|
||||
foreground_high -- a string containing a comma-separated
|
||||
foreground color and settings, standard foreground
|
||||
colors (see "Color values" above) or high-colors may
|
||||
be used
|
||||
|
||||
High-color example values:
|
||||
'#009' (0% red, 0% green, 60% red, like HTML colors)
|
||||
'#fcc' (100% red, 80% green, 80% blue)
|
||||
'g40' (40% gray, decimal), 'g#cc' (80% gray, hex),
|
||||
'#000', 'g0', 'g#00' (black),
|
||||
'#fff', 'g100', 'g#ff' (white)
|
||||
'h8' (color number 8), 'h255' (color number 255)
|
||||
|
||||
None = use foreground parameter value
|
||||
|
||||
background_high -- a string containing the background color,
|
||||
standard background colors (see "Background colors" above)
|
||||
or high-colors (see "High-color example values" above)
|
||||
may be used
|
||||
|
||||
None = use background parameter value
|
||||
"""
|
||||
basic = AttrSpec(foreground, background, 16)
|
||||
|
||||
if type(mono) == tuple:
|
||||
# old style of specifying mono attributes was to put them
|
||||
# in a tuple. convert to comma-separated string
|
||||
mono = ",".join(mono)
|
||||
if mono is None:
|
||||
mono = DEFAULT
|
||||
mono = AttrSpec(mono, DEFAULT, 1)
|
||||
|
||||
if foreground_high is None:
|
||||
foreground_high = foreground
|
||||
if background_high is None:
|
||||
background_high = background
|
||||
high_256 = AttrSpec(foreground_high, background_high, 256)
|
||||
|
||||
# 'hX' where X > 15 are different in 88/256 color, use
|
||||
# basic colors for 88-color mode if high colors are specified
|
||||
# in this way (also avoids crash when X > 87)
|
||||
def large_h(desc):
|
||||
if not desc.startswith('h'):
|
||||
return False
|
||||
if ',' in desc:
|
||||
desc = desc.split(',',1)[0]
|
||||
num = int(desc[1:], 10)
|
||||
return num > 15
|
||||
if large_h(foreground_high) or large_h(background_high):
|
||||
high_88 = basic
|
||||
else:
|
||||
high_88 = AttrSpec(foreground_high, background_high, 88)
|
||||
|
||||
signals.emit_signal(self, UPDATE_PALETTE_ENTRY,
|
||||
name, basic, mono, high_88, high_256)
|
||||
self._palette[name] = (basic, mono, high_88, high_256)
|
||||
|
||||
|
||||
def _test():
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
if __name__=='__main__':
|
||||
_test()
|
||||
438
urwid/escape.py
Normal file
438
urwid/escape.py
Normal file
|
|
@ -0,0 +1,438 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Urwid escape sequences common to curses_display and raw_display
|
||||
# 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/
|
||||
|
||||
"""
|
||||
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"
|
||||
IBMPC_ON = "\x1b[11m"
|
||||
IBMPC_OFF = "\x1b[10m"
|
||||
|
||||
DEC_TAG = "0"
|
||||
DEC_SPECIAL_CHARS = '▮◆▒␉␌␍␊°±␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£·'
|
||||
ALT_DEC_SPECIAL_CHARS = "_`abcdefghijklmnopqrstuvwxyz{|}~"
|
||||
|
||||
DEC_SPECIAL_CHARMAP = {}
|
||||
assert len(DEC_SPECIAL_CHARS) == len(ALT_DEC_SPECIAL_CHARS), repr((DEC_SPECIAL_CHARS, ALT_DEC_SPECIAL_CHARS))
|
||||
for c, alt in zip(DEC_SPECIAL_CHARS, ALT_DEC_SPECIAL_CHARS):
|
||||
DEC_SPECIAL_CHARMAP[ord(c)] = SO + alt + SI
|
||||
|
||||
SAFE_ASCII_DEC_SPECIAL_RE = re.compile("^[ -~%s]*$" % DEC_SPECIAL_CHARS)
|
||||
DEC_SPECIAL_RE = re.compile("[%s]" % DEC_SPECIAL_CHARS)
|
||||
|
||||
|
||||
###################
|
||||
## Input sequences
|
||||
###################
|
||||
|
||||
class MoreInputRequired(Exception):
|
||||
pass
|
||||
|
||||
def escape_modifier( digit ):
|
||||
mode = ord(digit) - ord("1")
|
||||
return "shift "*(mode&1) + "meta "*((mode&2)//2) + "ctrl "*((mode&4)//4)
|
||||
|
||||
|
||||
input_sequences = [
|
||||
('[A','up'),('[B','down'),('[C','right'),('[D','left'),
|
||||
('[E','5'),('[F','end'),('[G','5'),('[H','home'),
|
||||
|
||||
('[1~','home'),('[2~','insert'),('[3~','delete'),('[4~','end'),
|
||||
('[5~','page up'),('[6~','page down'),
|
||||
('[7~','home'),('[8~','end'),
|
||||
|
||||
('[[A','f1'),('[[B','f2'),('[[C','f3'),('[[D','f4'),('[[E','f5'),
|
||||
|
||||
('[11~','f1'),('[12~','f2'),('[13~','f3'),('[14~','f4'),
|
||||
('[15~','f5'),('[17~','f6'),('[18~','f7'),('[19~','f8'),
|
||||
('[20~','f9'),('[21~','f10'),('[23~','f11'),('[24~','f12'),
|
||||
('[25~','f13'),('[26~','f14'),('[28~','f15'),('[29~','f16'),
|
||||
('[31~','f17'),('[32~','f18'),('[33~','f19'),('[34~','f20'),
|
||||
|
||||
('OA','up'),('OB','down'),('OC','right'),('OD','left'),
|
||||
('OH','home'),('OF','end'),
|
||||
('OP','f1'),('OQ','f2'),('OR','f3'),('OS','f4'),
|
||||
('Oo','/'),('Oj','*'),('Om','-'),('Ok','+'),
|
||||
|
||||
('[Z','shift tab'),
|
||||
('On', '.'),
|
||||
|
||||
('[200~', 'begin paste'), ('[201~', 'end paste'),
|
||||
] + [
|
||||
(prefix + letter, modifier + key)
|
||||
for prefix, modifier in zip('O[', ('meta ', 'shift '))
|
||||
for letter, key in zip('abcd', ('up', 'down', 'right', 'left'))
|
||||
] + [
|
||||
("[" + digit + symbol, modifier + key)
|
||||
for modifier, symbol in zip(('shift ', 'meta '), '$^')
|
||||
for digit, key in zip('235678',
|
||||
('insert', 'delete', 'page up', 'page down', 'home', 'end'))
|
||||
] + [
|
||||
('O' + chr(ord('p')+n), str(n)) for n in range(10)
|
||||
] + [
|
||||
# modified cursor keys + home, end, 5 -- [#X and [1;#X forms
|
||||
(prefix+digit+letter, escape_modifier(digit) + key)
|
||||
for prefix in ("[", "[1;")
|
||||
for digit in "12345678"
|
||||
for letter,key in zip("ABCDEFGH",
|
||||
('up','down','right','left','5','end','5','home'))
|
||||
] + [
|
||||
# modified F1-F4 keys -- O#X form
|
||||
("O"+digit+letter, escape_modifier(digit) + key)
|
||||
for digit in "12345678"
|
||||
for letter,key in zip("PQRS",('f1','f2','f3','f4'))
|
||||
] + [
|
||||
# modified F1-F13 keys -- [XX;#~ form
|
||||
("["+str(num)+";"+digit+"~", escape_modifier(digit) + key)
|
||||
for digit in "12345678"
|
||||
for num,key in zip(
|
||||
(3,5,6,11,12,13,14,15,17,18,19,20,21,23,24,25,26,28,29,31,32,33,34),
|
||||
('delete', 'page up', 'page down',
|
||||
'f1','f2','f3','f4','f5','f6','f7','f8','f9','f10','f11',
|
||||
'f12','f13','f14','f15','f16','f17','f18','f19','f20'))
|
||||
] + [
|
||||
# mouse reporting (special handling done in KeyqueueTrie)
|
||||
('[M', 'mouse'),
|
||||
# report status response
|
||||
('[0n', 'status ok')
|
||||
]
|
||||
|
||||
class KeyqueueTrie(object):
|
||||
def __init__( self, sequences ):
|
||||
self.data = {}
|
||||
for s, result in sequences:
|
||||
assert type(result) != dict
|
||||
self.add(self.data, s, result)
|
||||
|
||||
def add(self, root, s, result):
|
||||
assert type(root) == dict, "trie conflict detected"
|
||||
assert len(s) > 0, "trie conflict detected"
|
||||
|
||||
if ord(s[0]) in root:
|
||||
return self.add(root[ord(s[0])], s[1:], result)
|
||||
if len(s)>1:
|
||||
d = {}
|
||||
root[ord(s[0])] = d
|
||||
return self.add(d, s[1:], result)
|
||||
root[ord(s)] = result
|
||||
|
||||
def get(self, keys, more_available):
|
||||
result = self.get_recurse(self.data, keys, more_available)
|
||||
if not result:
|
||||
result = self.read_cursor_position(keys, more_available)
|
||||
return result
|
||||
|
||||
def get_recurse(self, root, keys, more_available):
|
||||
if type(root) != dict:
|
||||
if root == "mouse":
|
||||
return self.read_mouse_info(keys,
|
||||
more_available)
|
||||
return (root, keys)
|
||||
if not keys:
|
||||
# get more keys
|
||||
if more_available:
|
||||
raise MoreInputRequired()
|
||||
return None
|
||||
if keys[0] not in root:
|
||||
return None
|
||||
return self.get_recurse(root[keys[0]], keys[1:], more_available)
|
||||
|
||||
def read_mouse_info(self, keys, more_available):
|
||||
if len(keys) < 3:
|
||||
if more_available:
|
||||
raise MoreInputRequired()
|
||||
return None
|
||||
|
||||
b = keys[0] - 32
|
||||
x, y = (keys[1] - 33)%256, (keys[2] - 33)%256 # supports 0-255
|
||||
|
||||
prefix = ""
|
||||
if b & 4: prefix = prefix + "shift "
|
||||
if b & 8: prefix = prefix + "meta "
|
||||
if b & 16: prefix = prefix + "ctrl "
|
||||
if (b & MOUSE_MULTIPLE_CLICK_MASK)>>9 == 1: prefix = prefix + "double "
|
||||
if (b & MOUSE_MULTIPLE_CLICK_MASK)>>9 == 2: prefix = prefix + "triple "
|
||||
|
||||
# 0->1, 1->2, 2->3, 64->4, 65->5
|
||||
button = ((b&64)/64*3) + (b & 3) + 1
|
||||
|
||||
if b & 3 == 3:
|
||||
action = "release"
|
||||
button = 0
|
||||
elif b & MOUSE_RELEASE_FLAG:
|
||||
action = "release"
|
||||
elif b & MOUSE_DRAG_FLAG:
|
||||
action = "drag"
|
||||
elif b & MOUSE_MULTIPLE_CLICK_MASK:
|
||||
action = "click"
|
||||
else:
|
||||
action = "press"
|
||||
|
||||
return ( (prefix + "mouse " + action, button, x, y), keys[3:] )
|
||||
|
||||
def read_cursor_position(self, keys, more_available):
|
||||
"""
|
||||
Interpret cursor position information being sent by the
|
||||
user's terminal. Returned as ('cursor position', x, y)
|
||||
where (x, y) == (0, 0) is the top left of the screen.
|
||||
"""
|
||||
if not keys:
|
||||
if more_available:
|
||||
raise MoreInputRequired()
|
||||
return None
|
||||
if keys[0] != ord('['):
|
||||
return None
|
||||
# read y value
|
||||
y = 0
|
||||
i = 1
|
||||
for k in keys[i:]:
|
||||
i += 1
|
||||
if k == ord(';'):
|
||||
if not y:
|
||||
return None
|
||||
break
|
||||
if k < ord('0') or k > ord('9'):
|
||||
return None
|
||||
if not y and k == ord('0'):
|
||||
return None
|
||||
y = y * 10 + k - ord('0')
|
||||
if not keys[i:]:
|
||||
if more_available:
|
||||
raise MoreInputRequired()
|
||||
return None
|
||||
# read x value
|
||||
x = 0
|
||||
for k in keys[i:]:
|
||||
i += 1
|
||||
if k == ord('R'):
|
||||
if not x:
|
||||
return None
|
||||
return (("cursor position", x-1, y-1), keys[i:])
|
||||
if k < ord('0') or k > ord('9'):
|
||||
return None
|
||||
if not x and k == ord('0'):
|
||||
return None
|
||||
x = x * 10 + k - ord('0')
|
||||
if not keys[i:]:
|
||||
if more_available:
|
||||
raise MoreInputRequired()
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
||||
# This is added to button value to signal mouse release by curses_display
|
||||
# and raw_display when we know which button was released. NON-STANDARD
|
||||
MOUSE_RELEASE_FLAG = 2048
|
||||
|
||||
# This 2-bit mask is used to check if the mouse release from curses or gpm
|
||||
# is a double or triple release. 00 means single click, 01 double,
|
||||
# 10 triple. NON-STANDARD
|
||||
MOUSE_MULTIPLE_CLICK_MASK = 1536
|
||||
|
||||
# This is added to button value at mouse release to differentiate between
|
||||
# single, double and triple press. Double release adds this times one,
|
||||
# triple release adds this times two. NON-STANDARD
|
||||
MOUSE_MULTIPLE_CLICK_FLAG = 512
|
||||
|
||||
# xterm adds this to the button value to signal a mouse drag event
|
||||
MOUSE_DRAG_FLAG = 32
|
||||
|
||||
|
||||
#################################################
|
||||
# Build the input trie from input_sequences list
|
||||
input_trie = KeyqueueTrie(input_sequences)
|
||||
#################################################
|
||||
|
||||
_keyconv = {
|
||||
-1:None,
|
||||
8:'backspace',
|
||||
9:'tab',
|
||||
10:'enter',
|
||||
13:'enter',
|
||||
127:'backspace',
|
||||
# curses-only keycodes follow.. (XXX: are these used anymore?)
|
||||
258:'down',
|
||||
259:'up',
|
||||
260:'left',
|
||||
261:'right',
|
||||
262:'home',
|
||||
263:'backspace',
|
||||
265:'f1', 266:'f2', 267:'f3', 268:'f4',
|
||||
269:'f5', 270:'f6', 271:'f7', 272:'f8',
|
||||
273:'f9', 274:'f10', 275:'f11', 276:'f12',
|
||||
277:'shift f1', 278:'shift f2', 279:'shift f3', 280:'shift f4',
|
||||
281:'shift f5', 282:'shift f6', 283:'shift f7', 284:'shift f8',
|
||||
285:'shift f9', 286:'shift f10', 287:'shift f11', 288:'shift f12',
|
||||
330:'delete',
|
||||
331:'insert',
|
||||
338:'page down',
|
||||
339:'page up',
|
||||
343:'enter', # on numpad
|
||||
350:'5', # on numpad
|
||||
360:'end',
|
||||
}
|
||||
|
||||
|
||||
|
||||
def process_keyqueue(codes, more_available):
|
||||
"""
|
||||
codes -- list of key codes
|
||||
more_available -- if True then raise MoreInputRequired when in the
|
||||
middle of a character sequence (escape/utf8/wide) and caller
|
||||
will attempt to send more key codes on the next call.
|
||||
|
||||
returns (list of input, list of remaining key codes).
|
||||
"""
|
||||
code = codes[0]
|
||||
if code >= 32 and code <= 126:
|
||||
key = chr(code)
|
||||
return [key], codes[1:]
|
||||
if code in _keyconv:
|
||||
return [_keyconv[code]], codes[1:]
|
||||
if code >0 and code <27:
|
||||
return ["ctrl %s" % chr(ord('a')+code-1)], codes[1:]
|
||||
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:]
|
||||
if code != 27:
|
||||
return ["<%d>"%code], codes[1:]
|
||||
|
||||
result = input_trie.get(codes[1:], more_available)
|
||||
|
||||
if result is not None:
|
||||
result, remaining_codes = result
|
||||
return [result], remaining_codes
|
||||
|
||||
if codes[1:]:
|
||||
# Meta keys -- ESC+Key form
|
||||
run, remaining_codes = process_keyqueue(codes[1:],
|
||||
more_available)
|
||||
if run[0] == "esc" or run[0].find("meta ") >= 0:
|
||||
return ['esc']+run, remaining_codes
|
||||
return ['meta '+run[0]]+run[1:], remaining_codes
|
||||
|
||||
return ['esc'], codes[1:]
|
||||
|
||||
|
||||
####################
|
||||
## Output sequences
|
||||
####################
|
||||
|
||||
ESC = "\x1b"
|
||||
|
||||
CURSOR_HOME = ESC+"[H"
|
||||
CURSOR_HOME_COL = "\r"
|
||||
|
||||
APP_KEYPAD_MODE = ESC+"="
|
||||
NUM_KEYPAD_MODE = ESC+">"
|
||||
|
||||
SWITCH_TO_ALTERNATE_BUFFER = ESC+"7"+ESC+"[?47h"
|
||||
RESTORE_NORMAL_BUFFER = ESC+"[?47l"+ESC+"8"
|
||||
|
||||
#RESET_SCROLL_REGION = ESC+"[;r"
|
||||
#RESET = ESC+"c"
|
||||
|
||||
REPORT_STATUS = ESC + "[5n"
|
||||
REPORT_CURSOR_POSITION = ESC+"[6n"
|
||||
|
||||
INSERT_ON = ESC + "[4h"
|
||||
INSERT_OFF = ESC + "[4l"
|
||||
|
||||
def set_cursor_position( x, y ):
|
||||
assert type(x) == int
|
||||
assert type(y) == int
|
||||
return ESC+"[%d;%dH" %(y+1, x+1)
|
||||
|
||||
def move_cursor_right(x):
|
||||
if x < 1: return ""
|
||||
return ESC+"[%dC" % x
|
||||
|
||||
def move_cursor_up(x):
|
||||
if x < 1: return ""
|
||||
return ESC+"[%dA" % x
|
||||
|
||||
def move_cursor_down(x):
|
||||
if x < 1: return ""
|
||||
return ESC+"[%dB" % x
|
||||
|
||||
HIDE_CURSOR = ESC+"[?25l"
|
||||
SHOW_CURSOR = ESC+"[?25h"
|
||||
|
||||
MOUSE_TRACKING_ON = ESC+"[?1000h"+ESC+"[?1002h"
|
||||
MOUSE_TRACKING_OFF = ESC+"[?1002l"+ESC+"[?1000l"
|
||||
|
||||
DESIGNATE_G1_SPECIAL = ESC+")0"
|
||||
|
||||
ERASE_IN_LINE_RIGHT = ESC+"[K"
|
||||
368
urwid/old_str_util.py
Normal file
368
urwid/old_str_util.py
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
#!/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<end_offs and ord2(text[o])&0xc0 == 0x80:
|
||||
o += 1
|
||||
return o
|
||||
if _byte_encoding == "wide" and within_double_byte(text,
|
||||
start_offs, start_offs) == 1:
|
||||
return start_offs +2
|
||||
return start_offs+1
|
||||
|
||||
def within_double_byte(text, line_start, pos):
|
||||
"""Return whether pos is within a double-byte encoded character.
|
||||
|
||||
text -- byte string in question
|
||||
line_start -- offset of beginning of line (< pos)
|
||||
pos -- offset in question
|
||||
|
||||
Return values:
|
||||
0 -- not within dbe char, or double_byte_encoding == False
|
||||
1 -- pos is on the 1st half of a dbe char
|
||||
2 -- pos is on the 2nd half of a dbe char
|
||||
"""
|
||||
assert isinstance(text, bytes)
|
||||
v = ord2(text[pos])
|
||||
|
||||
if v >= 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","<control>"):
|
||||
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()
|
||||
|
||||
1028
urwid/raw_display.py
Normal file
1028
urwid/raw_display.py
Normal file
File diff suppressed because it is too large
Load diff
302
urwid/signals.py
Normal file
302
urwid/signals.py
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
#!/usr/bin/python
|
||||
#
|
||||
# Urwid signal dispatching
|
||||
# Copyright (C) 2004-2012 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 itertools
|
||||
import weakref
|
||||
|
||||
|
||||
class MetaSignals(type):
|
||||
"""
|
||||
register the list of signals in the class varable signals,
|
||||
including signals in superclasses.
|
||||
"""
|
||||
def __init__(cls, name, bases, d):
|
||||
signals = d.get("signals", [])
|
||||
for superclass in cls.__bases__:
|
||||
signals.extend(getattr(superclass, 'signals', []))
|
||||
signals = list(dict([(x,None) for x in signals]).keys())
|
||||
d["signals"] = signals
|
||||
register_signal(cls, signals)
|
||||
super(MetaSignals, cls).__init__(name, bases, d)
|
||||
|
||||
def setdefaultattr(obj, name, value):
|
||||
# like dict.setdefault() for object attributes
|
||||
if hasattr(obj, name):
|
||||
return getattr(obj, name)
|
||||
setattr(obj, name, value)
|
||||
return value
|
||||
|
||||
class Key(object):
|
||||
"""
|
||||
Minimal class, whose only purpose is to produce objects with a
|
||||
unique hash
|
||||
"""
|
||||
__slots__ = []
|
||||
|
||||
class Signals(object):
|
||||
_signal_attr = '_urwid_signals' # attribute to attach to signal senders
|
||||
|
||||
def __init__(self):
|
||||
self._supported = {}
|
||||
|
||||
def register(self, sig_cls, signals):
|
||||
"""
|
||||
:param sig_class: the class of an object that will be sending signals
|
||||
:type sig_class: class
|
||||
:param signals: a list of signals that may be sent, typically each
|
||||
signal is represented by a string
|
||||
:type signals: signal names
|
||||
|
||||
This function must be called for a class before connecting any
|
||||
signal callbacks or emiting any signals from that class' objects
|
||||
"""
|
||||
self._supported[sig_cls] = signals
|
||||
|
||||
def connect(self, obj, name, callback, user_arg=None, weak_args=None, user_args=None):
|
||||
"""
|
||||
:param obj: the object sending a signal
|
||||
:type obj: object
|
||||
:param name: the signal to listen for, typically a string
|
||||
:type name: signal name
|
||||
:param callback: the function to call when that signal is sent
|
||||
:type callback: function
|
||||
:param user_arg: deprecated additional argument to callback (appended
|
||||
after the arguments passed when the signal is
|
||||
emitted). If None no arguments will be added.
|
||||
Don't use this argument, use user_args instead.
|
||||
:param weak_args: additional arguments passed to the callback
|
||||
(before any arguments passed when the signal
|
||||
is emitted and before any user_args).
|
||||
|
||||
These arguments are stored as weak references
|
||||
(but converted back into their original value
|
||||
before passing them to callback) to prevent
|
||||
any objects referenced (indirectly) from
|
||||
weak_args from being kept alive just because
|
||||
they are referenced by this signal handler.
|
||||
|
||||
Use this argument only as a keyword argument,
|
||||
since user_arg might be removed in the future.
|
||||
:type weak_args: iterable
|
||||
:param user_args: additional arguments to pass to the callback,
|
||||
(before any arguments passed when the signal
|
||||
is emitted but after any weak_args).
|
||||
|
||||
Use this argument only as a keyword argument,
|
||||
since user_arg might be removed in the future.
|
||||
:type user_args: iterable
|
||||
|
||||
When a matching signal is sent, callback will be called. The
|
||||
arguments it receives will be the user_args passed at connect
|
||||
time (as individual arguments) followed by all the positional
|
||||
parameters sent with the signal.
|
||||
|
||||
As an example of using weak_args, consider the following snippet:
|
||||
|
||||
>>> import urwid
|
||||
>>> debug = urwid.Text('')
|
||||
>>> def handler(widget, newtext):
|
||||
... debug.set_text("Edit widget changed to %s" % newtext)
|
||||
>>> edit = urwid.Edit('')
|
||||
>>> key = urwid.connect_signal(edit, 'change', handler)
|
||||
|
||||
If you now build some interface using "edit" and "debug", the
|
||||
"debug" widget will show whatever you type in the "edit" widget.
|
||||
However, if you remove all references to the "debug" widget, it
|
||||
will still be kept alive by the signal handler. This because the
|
||||
signal handler is a closure that (implicitly) references the
|
||||
"edit" widget. If you want to allow the "debug" widget to be
|
||||
garbage collected, you can create a "fake" or "weak" closure
|
||||
(it's not really a closure, since it doesn't reference any
|
||||
outside variables, so it's just a dynamic function):
|
||||
|
||||
>>> debug = urwid.Text('')
|
||||
>>> def handler(weak_debug, widget, newtext):
|
||||
... weak_debug.set_text("Edit widget changed to %s" % newtext)
|
||||
>>> edit = urwid.Edit('')
|
||||
>>> key = urwid.connect_signal(edit, 'change', handler, weak_args=[debug])
|
||||
|
||||
Here the weak_debug parameter in print_debug is the value passed
|
||||
in the weak_args list to connect_signal. Note that the
|
||||
weak_debug value passed is not a weak reference anymore, the
|
||||
signals code transparently dereferences the weakref parameter
|
||||
before passing it to print_debug.
|
||||
|
||||
Returns a key associated by this signal handler, which can be
|
||||
used to disconnect the signal later on using
|
||||
urwid.disconnect_signal_by_key. Alternatively, the signal
|
||||
handler can also be disconnected by calling
|
||||
urwid.disconnect_signal, which doesn't need this key.
|
||||
"""
|
||||
|
||||
sig_cls = obj.__class__
|
||||
if not name in self._supported.get(sig_cls, []):
|
||||
raise NameError("No such signal %r for object %r" %
|
||||
(name, obj))
|
||||
|
||||
# Just generate an arbitrary (but unique) key
|
||||
key = Key()
|
||||
|
||||
signals = setdefaultattr(obj, self._signal_attr, {})
|
||||
handlers = signals.setdefault(name, [])
|
||||
|
||||
# Remove the signal handler when any of the weakref'd arguments
|
||||
# are garbage collected. Note that this means that the handlers
|
||||
# dictionary can be modified _at any time_, so it should never
|
||||
# be iterated directly (e.g. iterate only over .keys() and
|
||||
# .items(), never over .iterkeys(), .iteritems() or the object
|
||||
# itself).
|
||||
# We let the callback keep a weakref to the object as well, to
|
||||
# prevent a circular reference between the handler and the
|
||||
# object (via the weakrefs, which keep strong references to
|
||||
# their callbacks) from existing.
|
||||
obj_weak = weakref.ref(obj)
|
||||
def weakref_callback(weakref):
|
||||
o = obj_weak()
|
||||
if o:
|
||||
try:
|
||||
del getattr(o, self._signal_attr, {})[name][key]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
user_args = self._prepare_user_args(weak_args, user_args, weakref_callback)
|
||||
handlers.append((key, callback, user_arg, user_args))
|
||||
|
||||
return key
|
||||
|
||||
def _prepare_user_args(self, weak_args, user_args, callback = None):
|
||||
# Turn weak_args into weakrefs and prepend them to user_args
|
||||
return [weakref.ref(a, callback) for a in (weak_args or [])] + (user_args or [])
|
||||
|
||||
|
||||
def disconnect(self, obj, name, callback, user_arg=None, weak_args=None, user_args=None):
|
||||
"""
|
||||
:param obj: the object to disconnect the signal from
|
||||
:type obj: object
|
||||
:param name: the signal to disconnect, typically a string
|
||||
:type name: signal name
|
||||
:param callback: the callback function passed to connect_signal
|
||||
:type callback: function
|
||||
:param user_arg: the user_arg parameter passed to connect_signal
|
||||
:param weak_args: the weak_args parameter passed to connect_signal
|
||||
:param user_args: the weak_args parameter passed to connect_signal
|
||||
|
||||
This function will remove a callback from the list connected
|
||||
to a signal with connect_signal(). The arguments passed should
|
||||
be exactly the same as those passed to connect_signal().
|
||||
|
||||
If the callback is not connected or already disconnected, this
|
||||
function will simply do nothing.
|
||||
"""
|
||||
signals = setdefaultattr(obj, self._signal_attr, {})
|
||||
if name not in signals:
|
||||
return
|
||||
|
||||
handlers = signals[name]
|
||||
|
||||
# Do the same processing as in connect, so we can compare the
|
||||
# resulting tuple.
|
||||
user_args = self._prepare_user_args(weak_args, user_args)
|
||||
|
||||
# Remove the given handler
|
||||
for h in handlers:
|
||||
if h[1:] == (callback, user_arg, user_args):
|
||||
return self.disconnect_by_key(obj, name, h[0])
|
||||
|
||||
def disconnect_by_key(self, obj, name, key):
|
||||
"""
|
||||
:param obj: the object to disconnect the signal from
|
||||
:type obj: object
|
||||
:param name: the signal to disconnect, typically a string
|
||||
:type name: signal name
|
||||
:param key: the key for this signal handler, as returned by
|
||||
connect_signal().
|
||||
:type key: Key
|
||||
|
||||
This function will remove a callback from the list connected
|
||||
to a signal with connect_signal(). The key passed should be the
|
||||
value returned by connect_signal().
|
||||
|
||||
If the callback is not connected or already disconnected, this
|
||||
function will simply do nothing.
|
||||
"""
|
||||
signals = setdefaultattr(obj, self._signal_attr, {})
|
||||
handlers = signals.get(name, [])
|
||||
handlers[:] = [h for h in handlers if h[0] is not key]
|
||||
|
||||
def emit(self, obj, name, *args):
|
||||
"""
|
||||
:param obj: the object sending a signal
|
||||
:type obj: object
|
||||
:param name: the signal to send, typically a string
|
||||
:type name: signal name
|
||||
:param \*args: zero or more positional arguments to pass to the signal
|
||||
callback functions
|
||||
|
||||
This function calls each of the callbacks connected to this signal
|
||||
with the args arguments as positional parameters.
|
||||
|
||||
This function returns True if any of the callbacks returned True.
|
||||
"""
|
||||
result = False
|
||||
signals = getattr(obj, self._signal_attr, {})
|
||||
handlers = signals.get(name, [])
|
||||
for key, callback, user_arg, user_args in handlers:
|
||||
result |= self._call_callback(callback, user_arg, user_args, args)
|
||||
return result
|
||||
|
||||
def _call_callback(self, callback, user_arg, user_args, emit_args):
|
||||
if user_args:
|
||||
args_to_pass = []
|
||||
for arg in user_args:
|
||||
if isinstance(arg, weakref.ReferenceType):
|
||||
arg = arg()
|
||||
if arg is None:
|
||||
# If the weakref is None, the referenced object
|
||||
# was cleaned up. We just skip the entire
|
||||
# callback in this case. The weakref cleanup
|
||||
# handler will have removed the callback when
|
||||
# this happens, so no need to actually remove
|
||||
# the callback here.
|
||||
return False
|
||||
args_to_pass.append(arg)
|
||||
|
||||
args_to_pass.extend(emit_args)
|
||||
else:
|
||||
# Optimization: Don't create a new list when there are
|
||||
# no user_args
|
||||
args_to_pass = emit_args
|
||||
|
||||
# The deprecated user_arg argument was added to the end
|
||||
# instead of the beginning.
|
||||
if user_arg is not None:
|
||||
args_to_pass = itertools.chain(args_to_pass, (user_arg,))
|
||||
|
||||
return bool(callback(*args_to_pass))
|
||||
|
||||
_signals = Signals()
|
||||
emit_signal = _signals.emit
|
||||
register_signal = _signals.register
|
||||
connect_signal = _signals.connect
|
||||
disconnect_signal = _signals.disconnect
|
||||
disconnect_signal_by_key = _signals.disconnect_by_key
|
||||
|
||||
474
urwid/util.py
Normal file
474
urwid/util.py
Normal file
|
|
@ -0,0 +1,474 @@
|
|||
#!/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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue