Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rich/default_styles.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
"traceback.exc_type": Style(color="bright_red", bold=True),
"traceback.exc_value": Style.null(),
"traceback.offset": Style(color="bright_red", bold=True),
"traceback.error_range": Style(underline=True, bold=True, dim=False),
"traceback.error_range": Style(underline=True, bold=True),
"traceback.note": Style(color="green", bold=True),
"bar.back": Style(color="grey23"),
"bar.complete": Style(color="rgb(249,38,114)"),
Expand Down
72 changes: 52 additions & 20 deletions rich/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@
from pygments.util import ClassNotFound

from . import pretty
from ._loop import loop_last
from ._loop import loop_first_last, loop_last
from .columns import Columns
from .console import Console, ConsoleOptions, ConsoleRenderable, RenderResult, group
from .constrain import Constrain
from .highlighter import RegexHighlighter, ReprHighlighter
from .panel import Panel
from .scope import render_scope
from .style import Style
from .syntax import Syntax
from .syntax import Syntax, SyntaxPosition
from .text import Text
from .theme import Theme

Expand All @@ -44,6 +44,34 @@
LOCALS_MAX_STRING = 80


def _iter_syntax_lines(
start: SyntaxPosition, end: SyntaxPosition
) -> Iterable[Tuple[int, int, int]]:
"""Yield start and end positions per line.

Args:
start: Start position.
end: End position.

Returns:
Iterable of (LINE, COLUMN1, COLUMN2).
"""

line1, column1 = start
line2, column2 = end

if line1 == line2:
yield line1, column1, column2
else:
for first, last, line_no in loop_first_last(range(line1, line2 + 1)):
if first:
yield line_no, column1, -1
elif last:
yield line_no, 0, column2
else:
yield line_no, 0, -1


def install(
*,
console: Optional[Console] = None,
Expand Down Expand Up @@ -658,17 +686,6 @@ def _render_stack(self, stack: Stack) -> RenderResult:
path_highlighter = PathHighlighter()
theme = self.theme

def read_code(filename: str) -> str:
"""Read files, and cache results on filename.

Args:
filename (str): Filename to read

Returns:
str: Contents of file
"""
return "".join(linecache.getlines(filename))

def render_locals(frame: Frame) -> Iterable[ConsoleRenderable]:
if frame.locals:
yield render_scope(
Expand Down Expand Up @@ -730,7 +747,8 @@ def render_locals(frame: Frame) -> Iterable[ConsoleRenderable]:
continue
if not suppressed:
try:
code = read_code(frame.filename)
code_lines = linecache.getlines(frame.filename)
code = "".join(code_lines)
if not code:
# code may be an empty string if the file doesn't exist, OR
# if the traceback filename is generated dynamically
Expand Down Expand Up @@ -759,12 +777,26 @@ def render_locals(frame: Frame) -> Iterable[ConsoleRenderable]:
else:
if frame.last_instruction is not None:
start, end = frame.last_instruction
syntax.stylize_range(
style="traceback.error_range",
start=start,
end=end,
style_before=True,
)

# Stylize a line at a time
# So that indentation isn't underlined (which looks bad)
for line1, column1, column2 in _iter_syntax_lines(start, end):
try:
if column1 == 0:
line = code_lines[line1 - 1]
column1 = len(line) - len(line.lstrip())
if column2 == -1:
column2 = len(code_lines[line1 - 1])
except IndexError:
# Being defensive here
# If last_instruction reports a line out-of-bounds, we don't want to crash
continue

syntax.stylize_range(
style="traceback.error_range",
start=(line1, column1),
end=(line1, column2),
)
yield (
Columns(
[
Expand Down
Loading