Skip to content
26 changes: 26 additions & 0 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -5321,6 +5321,32 @@ def expected(t, m, fn, l, f, E, e, z):
]
self.assertEqual(actual, expected(**colors))

def test_colorized_traceback_unicode(self):
try:
啊哈=1; 啊哈/0####
except Exception as e:
exc = traceback.TracebackException.from_exception(e)

actual = "".join(exc.format(colorize=True)).splitlines()
def expected(t, m, fn, l, f, E, e, z):
return [
f" 啊哈=1; {e}啊哈{z}{E}/{z}{e}0{z}####",
f" {e}~~~~{z}{E}^{z}{e}~{z}",
]
self.assertEqual(actual[2:4], expected(**colors))

try:
ééééé/0
except Exception as e:
exc = traceback.TracebackException.from_exception(e)

actual = "".join(exc.format(colorize=True)).splitlines()
def expected(t, m, fn, l, f, E, e, z):
return [
f" {E}ééééé{z}/0",
f" {E}^^^^^{z}",
]
self.assertEqual(actual[2:4], expected(**colors))

class TestLazyImportSuggestions(unittest.TestCase):
"""Test that lazy imports are not reified when computing AttributeError suggestions."""
Expand Down
24 changes: 14 additions & 10 deletions Lib/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,12 +681,12 @@ def output_line(lineno):
colorized_line_parts = []
colorized_carets_parts = []

for color, group in itertools.groupby(itertools.zip_longest(line, carets, fillvalue=""), key=lambda x: x[1]):
for color, group in itertools.groupby(_zip_display_width(line, carets), key=lambda x: x[1]):
caret_group = list(group)
if color == "^":
if "^" in color:
colorized_line_parts.append(theme.error_highlight + "".join(char for char, _ in caret_group) + theme.reset)
colorized_carets_parts.append(theme.error_highlight + "".join(caret for _, caret in caret_group) + theme.reset)
elif color == "~":
elif "~" in color:
colorized_line_parts.append(theme.error_range + "".join(char for char, _ in caret_group) + theme.reset)
colorized_carets_parts.append(theme.error_range + "".join(caret for _, caret in caret_group) + theme.reset)
else:
Expand Down Expand Up @@ -968,7 +968,15 @@ def setup_positions(expr, force_valid=True):

return None

_WIDE_CHAR_SPECIFIERS = "WF"

def _zip_display_width(line, carets):
import unicodedata
carets = iter(carets)
for char in unicodedata.iter_graphemes(line):
char = str(char)
char_width = _display_width(char)
yield char, "".join(itertools.islice(carets, char_width))


def _display_width(line, offset=None):
"""Calculate the extra amount of width space the given source
Expand All @@ -982,13 +990,9 @@ def _display_width(line, offset=None):
if line.isascii():
return offset

import unicodedata

return sum(
2 if unicodedata.east_asian_width(char) in _WIDE_CHAR_SPECIFIERS else 1
for char in line[:offset]
)
from _pyrepl.utils import wlen

return wlen(line[:offset])


class _ExceptionPrintContext:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix traceback color output with unicode characters
Loading