aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/tools/python3/src/Lib/traceback.py
diff options
context:
space:
mode:
authorshadchin <shadchin@yandex-team.com>2023-12-13 02:43:57 +0300
committershadchin <shadchin@yandex-team.com>2023-12-13 03:08:48 +0300
commit5b48aabc614c6d407f885f3b228dc484ad4c5ba9 (patch)
tree602eb5cc5d85bf730c1de1fa50a13c2ee552830d /contrib/tools/python3/src/Lib/traceback.py
parent35d7049b38602e8cbfcd3f96257329a1abce947e (diff)
downloadydb-5b48aabc614c6d407f885f3b228dc484ad4c5ba9.tar.gz
Update Python 3 to 3.11.7
Diffstat (limited to 'contrib/tools/python3/src/Lib/traceback.py')
-rw-r--r--contrib/tools/python3/src/Lib/traceback.py80
1 files changed, 53 insertions, 27 deletions
diff --git a/contrib/tools/python3/src/Lib/traceback.py b/contrib/tools/python3/src/Lib/traceback.py
index d3edd3a63e..ea045e2761 100644
--- a/contrib/tools/python3/src/Lib/traceback.py
+++ b/contrib/tools/python3/src/Lib/traceback.py
@@ -145,14 +145,11 @@ def format_exception_only(exc, /, value=_sentinel):
The return value is a list of strings, each ending in a newline.
- Normally, the list contains a single string; however, for
- SyntaxError exceptions, it contains several lines that (when
- printed) display detailed information about where the syntax
- error occurred.
-
- The message indicating which exception occurred is always the last
- string in the list.
-
+ The list contains the exception's message, which is
+ normally a single string; however, for :exc:`SyntaxError` exceptions, it
+ contains several lines that (when printed) display detailed information
+ about where the syntax error occurred. Following the message, the list
+ contains the exception's ``__notes__``.
"""
if value is _sentinel:
value = exc
@@ -468,7 +465,8 @@ class StackSummary(list):
stripped_line = frame_summary.line.strip()
row.append(' {}\n'.format(stripped_line))
- orig_line_len = len(frame_summary._original_line)
+ line = frame_summary._original_line
+ orig_line_len = len(line)
frame_line_len = len(frame_summary.line.lstrip())
stripped_characters = orig_line_len - frame_line_len
if (
@@ -476,31 +474,40 @@ class StackSummary(list):
and frame_summary.end_colno is not None
):
start_offset = _byte_offset_to_character_offset(
- frame_summary._original_line, frame_summary.colno) + 1
+ line, frame_summary.colno)
end_offset = _byte_offset_to_character_offset(
- frame_summary._original_line, frame_summary.end_colno) + 1
+ line, frame_summary.end_colno)
+ code_segment = line[start_offset:end_offset]
anchors = None
if frame_summary.lineno == frame_summary.end_lineno:
with suppress(Exception):
- anchors = _extract_caret_anchors_from_line_segment(
- frame_summary._original_line[start_offset - 1:end_offset - 1]
- )
+ anchors = _extract_caret_anchors_from_line_segment(code_segment)
else:
- end_offset = stripped_characters + len(stripped_line)
+ # Don't count the newline since the anchors only need to
+ # go up until the last character of the line.
+ end_offset = len(line.rstrip())
# show indicators if primary char doesn't span the frame line
if end_offset - start_offset < len(stripped_line) or (
anchors and anchors.right_start_offset - anchors.left_end_offset > 0):
+ # When showing this on a terminal, some of the non-ASCII characters
+ # might be rendered as double-width characters, so we need to take
+ # that into account when calculating the length of the line.
+ dp_start_offset = _display_width(line, start_offset) + 1
+ dp_end_offset = _display_width(line, end_offset) + 1
+
row.append(' ')
- row.append(' ' * (start_offset - stripped_characters))
+ row.append(' ' * (dp_start_offset - stripped_characters))
if anchors:
- row.append(anchors.primary_char * (anchors.left_end_offset))
- row.append(anchors.secondary_char * (anchors.right_start_offset - anchors.left_end_offset))
- row.append(anchors.primary_char * (end_offset - start_offset - anchors.right_start_offset))
+ dp_left_end_offset = _display_width(code_segment, anchors.left_end_offset)
+ dp_right_start_offset = _display_width(code_segment, anchors.right_start_offset)
+ row.append(anchors.primary_char * dp_left_end_offset)
+ row.append(anchors.secondary_char * (dp_right_start_offset - dp_left_end_offset))
+ row.append(anchors.primary_char * (dp_end_offset - dp_start_offset - dp_right_start_offset))
else:
- row.append('^' * (end_offset - start_offset))
+ row.append('^' * (dp_end_offset - dp_start_offset))
row.append('\n')
@@ -621,6 +628,25 @@ def _extract_caret_anchors_from_line_segment(segment):
return None
+_WIDE_CHAR_SPECIFIERS = "WF"
+
+def _display_width(line, offset):
+ """Calculate the extra amount of width space the given source
+ code segment might take if it were to be displayed on a fixed
+ width output device. Supports wide unicode characters and emojis."""
+
+ # Fast track for ASCII-only strings
+ 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]
+ )
+
+
class _ExceptionPrintContext:
def __init__(self):
@@ -817,13 +843,13 @@ class TracebackException:
The return value is a generator of strings, each ending in a newline.
- Normally, the generator emits a single string; however, for
- SyntaxError exceptions, it emits several lines that (when
- printed) display detailed information about where the syntax
- error occurred.
-
- The message indicating which exception occurred is always the last
- string in the output.
+ Generator yields the exception message.
+ For :exc:`SyntaxError` exceptions, it
+ also yields (before the exception message)
+ several lines that (when printed)
+ display detailed information about where the syntax error occurred.
+ Following the message, generator also yields
+ all the exception's ``__notes__``.
"""
if self.exc_type is None:
yield _format_final_exc_line(None, self._str)