Skip to content

Commit d19c2fb

Browse files
committed
WIP: Try to fix tabulate._CustomTextWrap._handle_long_word breaking up ANSI escape codes.
1 parent 95ae5eb commit d19c2fb

File tree

2 files changed

+48
-4
lines changed

2 files changed

+48
-4
lines changed

tabulate/__init__.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2540,10 +2540,18 @@ def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
25402540
# take each charcter's width into account
25412541
chunk = reversed_chunks[-1]
25422542
i = 1
2543-
while self._len(chunk[:i]) <= space_left:
2543+
while len(_strip_ansi(chunk)[:i]) <= space_left:
25442544
i = i + 1
2545-
cur_line.append(chunk[: i - 1])
2546-
reversed_chunks[-1] = chunk[i - 1 :]
2545+
# Consider escape codes when breaking words up
2546+
total_escape_len = 0
2547+
if _ansi_codes.search(chunk) is not None:
2548+
for group, _, _, _ in _ansi_codes.findall(chunk):
2549+
escape_len = len(group)
2550+
# FIXME: Needs to keep track of found groups and search from there
2551+
if group in chunk[: i + total_escape_len + escape_len - 1]:
2552+
total_escape_len += escape_len
2553+
cur_line.append(chunk[: i + total_escape_len - 1])
2554+
reversed_chunks[-1] = chunk[i + total_escape_len - 1 :]
25472555

25482556
# Otherwise, we have to preserve the long word intact. Only add
25492557
# it to the current line if there's nothing already there --

test/test_textwrapper.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import datetime
55

6-
from tabulate import _CustomTextWrap as CTW, tabulate
6+
from tabulate import _CustomTextWrap as CTW, tabulate, _strip_ansi
77
from textwrap import TextWrapper as OTW
88

99
from common import skip, assert_equal
@@ -158,6 +158,42 @@ def test_wrap_color_line_splillover():
158158
assert_equal(expected, result)
159159

160160

161+
def test_wrap_color_line_longword():
162+
"""TextWrapper: Wrap a line - preserve internal color tags and wrap them to
163+
other lines when required, requires adding the colors tags to other lines as appropriate
164+
and avoiding splitting escape codes."""
165+
data = "This_is_a_\033[31mtest_string_for_testing_TextWrap\033[0m_with_colors"
166+
167+
expected = [
168+
"This_is_a_\033[31mte\033[0m",
169+
"\033[31mst_string_fo\033[0m",
170+
"\033[31mr_testing_Te\033[0m",
171+
"\033[31mxtWrap\033[0m_with_",
172+
"colors",
173+
]
174+
wrapper = CTW(width=12)
175+
result = wrapper.wrap(data)
176+
assert_equal(expected, result)
177+
178+
179+
def test_wrap_color_line_multiple_escapes():
180+
data = '012345(\x1b[32ma\x1b[0mbc\x1b[32mdefghij\x1b[0m)'
181+
expected = [
182+
"012345(\x1b[32ma\x1b[0mbc\x1b[32mdefg\x1b[0m",
183+
"\x1b[32mhij\x1b[0m)",
184+
]
185+
wrapper = CTW(width=10)
186+
result = wrapper.wrap(data)
187+
assert_equal(expected, result)
188+
clean_data = _strip_ansi(data)
189+
for width in range(2, len(clean_data)):
190+
# Currently fails with 14, 15 and 16, because a escape code gets split at the end
191+
wrapper = CTW(width=width)
192+
result = wrapper.wrap(data)
193+
# print(width, result)
194+
# Comparing after stripping ANSI should be enough to catch broken escape codes
195+
assert_equal(clean_data, _strip_ansi("".join(result)))
196+
161197
def test_wrap_datetime():
162198
"""TextWrapper: Show that datetimes can be wrapped without crashing"""
163199
data = [

0 commit comments

Comments
 (0)