From dc8ec2df32efbfbf5526fc25118e878c8f5c4c13 Mon Sep 17 00:00:00 2001 From: jquast Date: Sat, 17 May 2014 22:36:29 -0700 Subject: [PATCH] better ecma-48 detection in aixterm found that the printable length of \x1b[32;4m was not detected by xterm ... why is that? because xterm's own would generate \x1b(B\x1b[32;4m. Rather problematic, we use an ordered list to add this various alternate types as a 'fall through', that is, at the end. --- MANIFEST.in | 1 + README.rst | 2 ++ blessed/sequences.py | 27 +++++++++++++++++++++------ blessed/tests/test_length_sequence.py | 25 +++++++++++++++++++++++-- blessed/tests/wall.ans | 7 +++++++ docs/conf.py | 2 +- setup.py | 2 +- 7 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 blessed/tests/wall.ans diff --git a/MANIFEST.in b/MANIFEST.in index 3f4fbd70..8891ea07 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include README.rst include LICENSE include tox.ini +include blessed/tests/wall.ans diff --git a/README.rst b/README.rst index d7618847..0c329dee 100644 --- a/README.rst +++ b/README.rst @@ -678,6 +678,8 @@ Version History * enhancement: include wcwidth_ library support for ``length()``, the printable width of many kinds of CJK (Chinese, Japanese, Korean) ideographs are more correctly determined. + * enhancement: better support for detecting the length or sequences of + externally-generated *ecma-48* codes when using ``xterm`` or ``aixterm``. * bugfix: if ``locale.getpreferredencoding()`` returns empty string or an encoding that is not a valid codec for ``codecs.getincrementaldecoder``, fallback to ascii and emit a warning. diff --git a/blessed/sequences.py b/blessed/sequences.py index 96bb8d03..a08c9261 100644 --- a/blessed/sequences.py +++ b/blessed/sequences.py @@ -39,8 +39,8 @@ def _build_numeric_capability(term, cap, optional=False, if _cap: args = (base_num,) * nparams cap_re = re.escape(_cap(*args)) - for num in range(base_num-1, base_num+2): - # search for matching ascii, n-1 through n+2 + for num in range(base_num - 1, base_num + 2): + # search for matching ascii, n-1 through n+1 if str(num) in cap_re: # modify & return n to matching digit expression cap_re = cap_re.replace(str(num), r'(\d+)%s' % (opt,)) @@ -261,13 +261,28 @@ def init_sequence_patterns(term): # Build will_move, a list of terminal capabilities that have # indeterminate effects on the terminal cursor position. - _will_move = _merge_sequences(get_movement_sequence_patterns(term) - ) if term.does_styling else set() + _will_move = set() + if term.does_styling: + _will_move = _merge_sequences(get_movement_sequence_patterns(term)) # Build wont_move, a list of terminal capabilities that mainly affect # video attributes, for use with measure_length(). - _wont_move = _merge_sequences(get_wontmove_sequence_patterns(term) - ) if term.does_styling else set() + _wont_move = set() + if term.does_styling: + _wont_move = _merge_sequences(get_wontmove_sequence_patterns(term)) + _wont_move += [ + # some last-ditch match efforts; well, xterm and aixterm is going + # to throw \x1b(B and other oddities all around, so, when given + # input such as ansi art (see test using wall.ans), and well, + # theres no reason a vt220 terminal shouldn't be able to recognize + # blue_on_red, even if it didn't cause it to be generated. these + # are final "ok, i will match this, anyway" + re.escape(u'\x1b') + r'\[(\d+)m', + re.escape(u'\x1b') + r'\[(\d+)\;(\d+)m', + re.escape(u'\x1b') + r'\[(\d+)\;(\d+)\;(\d+)m', + re.escape(u'\x1b') + r'\[(\d+)\;(\d+)\;(\d+)\;(\d+)m', + re.escape(u'\x1b(B'), + ] # compile as regular expressions, OR'd. _re_will_move = re.compile('(%s)' % ('|'.join(_will_move))) diff --git a/blessed/tests/test_length_sequence.py b/blessed/tests/test_length_sequence.py index 0bbb8bff..e93c5f22 100644 --- a/blessed/tests/test_length_sequence.py +++ b/blessed/tests/test_length_sequence.py @@ -24,7 +24,6 @@ def test_length_cjk(): - @as_subprocess def child(): term = TestTerminal(kind='xterm-256color') @@ -32,17 +31,39 @@ def child(): # given, given = term.bold_red(u'コンニチハ, セカイ!') expected = sum((2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 1,)) + + # exercise, assert term.length(given) == expected child() +def test_length_ansiart(): + @as_subprocess + def child(): + import codecs + from blessed.sequences import Sequence + term = TestTerminal(kind='xterm-256color') + # this 'ansi' art contributed by xzip!impure for another project, + # unlike most CP-437 DOS ansi art, this is actually utf-8 encoded. + fname = os.path.join(os.path.dirname(__file__), 'wall.ans') + lines = codecs.open(fname, 'r', 'utf-8').readlines() + assert term.length(lines[0]) == 67 # ^[[64C^[[34m▄▓▄ + assert term.length(lines[1]) == 75 + assert term.length(lines[2]) == 78 + assert term.length(lines[3]) == 78 + assert term.length(lines[4]) == 78 + assert term.length(lines[5]) == 78 + assert term.length(lines[6]) == 77 + child() + + def test_sequence_length(all_terms): """Ensure T.length(string containing sequence) is correct.""" @as_subprocess def child(kind): t = TestTerminal(kind=kind) - # Create a list of ascii characters, to be seperated + # Create a list of ascii characters, to be separated # by word, to be zipped up with a cycling list of # terminal sequences. Then, compare the length of # each, the basic plain_text.__len__ vs. the Terminal diff --git a/blessed/tests/wall.ans b/blessed/tests/wall.ans new file mode 100644 index 00000000..081b4d28 --- /dev/null +++ b/blessed/tests/wall.ans @@ -0,0 +1,7 @@ +▄▓▄ + ░░ █▀▀█▀██▀████████▀█▀▀█ █▀▀█▀██▀▀█▀█xz(imp) + ▄▄▄ ▓█████ ▄▄ █▀▀█░ ▀▀▀▀▀ █████▓ ▓█████ ░▓ █▀▀█░ ▓█████ ░▓ █▀▀█░ ░▄ ░░ + ▐▄░▄▀ ▀▀▀▀▀▀ █▓ ▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀ ██ ▀▀▀▀▀ ▀▀▀▀▀▀ ██ ▀▀▀▀▀ █▄▌▌▄▄ + █▓██ ░▓████ ▐ ████░ ░████ ▄█ ████▓░ ░▓████ ██ ████░ ░▓████ ██ ████░ █▐▄▄█░ + ██▓▀ ░▓████ █ ████░ ░████ ▐ ████▓░ ░▓████ ▄█▌████░ ░▓████ ▄█▌████░ ▌█████ + ▀ ░▓████▄█▄▄███ ░ ░ ███▄▄▄▄████▓░ ░▓████▄▄▄▄███ ░ ░▓████▄▄▄▄███ ░ ▀▀▀▓ diff --git a/docs/conf.py b/docs/conf.py index b6193419..bb5c999d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -53,7 +53,7 @@ # built documents. # # The short X.Y version. -version = '1.8.7' +version = '1.8.8' # The full version, including alpha/beta/rc tags. release = version diff --git a/setup.py b/setup.py index b3cfd23b..b5abb414 100755 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ def main(): here = os.path.dirname(__file__) setuptools.setup( name='blessed', - version='1.8.7', + version='1.8.8', description="A feature-filled fork of Erik Rose's blessings project", long_description=open(os.path.join(here, 'README.rst')).read(), author='Jeff Quast',