Skip to content

Commit

Permalink
Merge pull request #308 from devdanzin/master
Browse files Browse the repository at this point in the history
Fix column wrapping breaking ANSI escape codes (fixes #307)
  • Loading branch information
astanin authored Sep 27, 2024
2 parents 46b35b4 + e1c59c3 commit bbc5ff1
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 6 deletions.
17 changes: 14 additions & 3 deletions tabulate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2676,10 +2676,21 @@ def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
# take each charcter's width into account
chunk = reversed_chunks[-1]
i = 1
while self._len(chunk[:i]) <= space_left:
# Only count printable characters, so strip_ansi first, index later.
while len(_strip_ansi(chunk)[:i]) <= space_left:
i = i + 1
cur_line.append(chunk[: i - 1])
reversed_chunks[-1] = chunk[i - 1 :]
# Consider escape codes when breaking words up
total_escape_len = 0
last_group = 0
if _ansi_codes.search(chunk) is not None:
for group, _, _, _ in _ansi_codes.findall(chunk):
escape_len = len(group)
if group in chunk[last_group: i + total_escape_len + escape_len - 1]:
total_escape_len += escape_len
found = _ansi_codes.search(chunk[last_group:])
last_group += found.end()
cur_line.append(chunk[: i + total_escape_len - 1])
reversed_chunks[-1] = chunk[i + total_escape_len - 1 :]

# Otherwise, we have to preserve the long word intact. Only add
# it to the current line if there's nothing already there --
Expand Down
4 changes: 2 additions & 2 deletions test/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@


def assert_equal(expected, result):
print("Expected:\n%s\n" % expected)
print("Got:\n%s\n" % result)
print("Expected:\n%r\n" % expected)
print("Got:\n%r\n" % result)
assert expected == result


Expand Down
38 changes: 37 additions & 1 deletion test/test_textwrapper.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Discretely test functionality of our custom TextWrapper"""
import datetime

from tabulate import _CustomTextWrap as CTW, tabulate
from tabulate import _CustomTextWrap as CTW, tabulate, _strip_ansi
from textwrap import TextWrapper as OTW

from common import skip, assert_equal
Expand Down Expand Up @@ -157,6 +157,42 @@ def test_wrap_color_line_splillover():
assert_equal(expected, result)


def test_wrap_color_line_longword():
"""TextWrapper: Wrap a line - preserve internal color tags and wrap them to
other lines when required, requires adding the colors tags to other lines as appropriate
and avoiding splitting escape codes."""
data = "This_is_a_\033[31mtest_string_for_testing_TextWrap\033[0m_with_colors"

expected = [
"This_is_a_\033[31mte\033[0m",
"\033[31mst_string_fo\033[0m",
"\033[31mr_testing_Te\033[0m",
"\033[31mxtWrap\033[0m_with_",
"colors",
]
wrapper = CTW(width=12)
result = wrapper.wrap(data)
assert_equal(expected, result)


def test_wrap_color_line_multiple_escapes():
data = "012345(\x1b[32ma\x1b[0mbc\x1b[32mdefghij\x1b[0m)"
expected = [
"012345(\x1b[32ma\x1b[0mbc\x1b[32m\x1b[0m",
"\x1b[32mdefghij\x1b[0m)",
]
wrapper = CTW(width=10)
result = wrapper.wrap(data)
assert_equal(expected, result)

clean_data = _strip_ansi(data)
for width in range(2, len(clean_data)):
wrapper = CTW(width=width)
result = wrapper.wrap(data)
# Comparing after stripping ANSI should be enough to catch broken escape codes
assert_equal(clean_data, _strip_ansi("".join(result)))


def test_wrap_datetime():
"""TextWrapper: Show that datetimes can be wrapped without crashing"""
data = [
Expand Down

0 comments on commit bbc5ff1

Please sign in to comment.