Skip to content
Open
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
1 change: 1 addition & 0 deletions Lib/_colorize.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ class LiveProfiler(ThemeSection):
@dataclass(frozen=True, kw_only=True)
class Syntax(ThemeSection):
prompt: str = ANSIColors.BOLD_MAGENTA
error: str = ANSIColors.BOLD_RED
keyword: str = ANSIColors.BOLD_BLUE
keyword_constant: str = ANSIColors.BOLD_BLUE
builtin: str = ANSIColors.CYAN
Expand Down
6 changes: 4 additions & 2 deletions Lib/_pyrepl/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,10 @@ def do(self) -> None:
r.select_item(r.historyi + 1)
r.pos = r.eol(0)
return
r.pos = len(b)
r.error("end of buffer")
if r.pos == len(b):
r.error("end of buffer")
else:
r.pos = len(b)
Comment on lines +299 to +302
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have tests that cover both branches here?

return

if (
Expand Down
9 changes: 7 additions & 2 deletions Lib/_pyrepl/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ class Reader:
ps3: str = "|.. "
ps4: str = R"\__ "
kill_ring: list[list[str]] = field(default_factory=list)
error_prefix: str = "! "
msg: str = ""
arg: int | None = None
finished: bool = False
Expand Down Expand Up @@ -875,9 +876,13 @@ def finish(self) -> None:
pass

def error(self, msg: str = "none") -> None:
self.msg = "! " + msg + " "
self.invalidate_message()
error_prefix = self.error_prefix
if self.can_colorize:
t = THEME()
error_prefix = f"{t.error}{error_prefix}{t.reset}"
self.console.beep()
self.msg = error_prefix + msg
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I am misreading this flow but if I am not wrong, the error() now embeds the escape sequences into self.msg. The message renderer at _render_message_lines slices by string length, not visible width. Then, len(message_line) counts the ANSI bytes (\x1b[1;31m ... \x1b[0m), so on narrow terminals (or long error messages) the slice boundary can land mid-escape, producing a truncated \x1b[ on one line and stray m on the next leaving the whole line un-colored or visually corrupted.

self.invalidate_message()

def update_screen(self) -> None:
if self.invalidation.is_cursor_only:
Expand Down
3 changes: 0 additions & 3 deletions Lib/_pyrepl/readline.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,6 @@ def __post_init__(self) -> None:
self.commands["backspace_dedent"] = backspace_dedent
self.commands["backspace-dedent"] = backspace_dedent

def error(self, msg: str = "none") -> None:
pass # don't show error messages by default

def get_stem(self) -> str:
b = self.buffer
p = self.pos - 1
Expand Down
8 changes: 6 additions & 2 deletions Lib/test/test_pyrepl/test_pyrepl.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,15 @@ def test_down_arrow_end_of_input(self):
events = itertools.chain(
code_to_events(code),
[
# Go left first to avoid end of buffer error.
Event(evt="key", data="left", raw=bytearray(b"\x1bOD")),
Event(evt="key", data="down", raw=bytearray(b"\x1bOB")),
],
)

reader, console = handle_all_events(events)
self.assertEqual(reader.cxy, (0, 2))
console.move_cursor.assert_called_once_with(0, 2)
console.move_cursor.assert_called_with(0, 2)

def test_left_arrow_simple(self):
events = itertools.chain(
Expand All @@ -237,13 +239,15 @@ def test_right_arrow_end_of_line(self):
events = itertools.chain(
code_to_events("11+11"),
[
# Go left first to avoid end of buffer error.
Event(evt="key", data="left", raw=bytearray(b"\x1bOD")),
Event(evt="key", data="right", raw=bytearray(b"\x1bOC")),
],
)

reader, console = handle_all_events(events)
self.assertEqual(reader.cxy, (5, 0))
console.move_cursor.assert_called_once_with(5, 0)
console.move_cursor.assert_called_with(5, 0)

def test_cursor_position_simple_character(self):
events = itertools.chain(code_to_events("k"))
Expand Down
Loading