From 93dd613c394634c301c61ca3d1dadb237375cfd9 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Tue, 20 Oct 2026 17:25:49 -0700 Subject: [PATCH 1/4] Render REPL error overlays in red without a prefix --- Lib/_pyrepl/reader.py | 8 +++----- Lib/test/test_pyrepl/test_reader.py | 10 +++++----- .../2026-10-20-20-00-00.gh-issue-148228.X7mQp2.rst | 2 ++ 3 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-20-20-00-00.gh-issue-148228.X7mQp2.rst diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py index d552214f322d76d..ac8db5407a96103 100644 --- a/Lib/_pyrepl/reader.py +++ b/Lib/_pyrepl/reader.py @@ -288,7 +288,6 @@ 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 @@ -883,12 +882,11 @@ def debug(self, msg: str) -> None: pass def error(self, msg: str = "none") -> None: - error_prefix = self.error_prefix if self.can_colorize: - t = THEME() - error_prefix = f"{t.error}{error_prefix}{t.reset}" + colors = _colorize.ANSIColors + msg = f"{colors.RED}{msg}{colors.RESET}" self.console.beep() - self.msg = error_prefix + msg + self.msg = msg self.invalidate_message() def update_screen(self) -> None: diff --git a/Lib/test/test_pyrepl/test_reader.py b/Lib/test/test_pyrepl/test_reader.py index fd9900d9a32b3d2..df74a3b97bfdd74 100644 --- a/Lib/test/test_pyrepl/test_reader.py +++ b/Lib/test/test_pyrepl/test_reader.py @@ -14,7 +14,7 @@ from _pyrepl.layout import LayoutMap from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig from _pyrepl.reader import Reader -from _colorize import default_theme +from _colorize import ANSIColors, default_theme overrides = {"reset": "z", "soft_keyword": "K"} @@ -467,11 +467,11 @@ def test_colorized_error_message(self): reader.handle1() - theme = default_theme.syntax self.assertEqual( reader.msg, - f"{theme.error}! {theme.reset}nothing to yank", + f"{ANSIColors.RED}nothing to yank{ANSIColors.RESET}", ) + self.assertEqual(reader.screen[-1], reader.msg) def _test_error_message( self, @@ -490,8 +490,8 @@ def _test_error_message( while not reader.msg: reader.handle1() - self.assertEqual(reader.msg, f"! {expected}") - self.assertEqual(reader.screen[-1], f"! {expected}") + self.assertEqual(reader.msg, expected) + self.assertEqual(reader.screen[-1], expected) console.beep.assert_called_once() def test_invalid_key_message(self): diff --git a/Misc/NEWS.d/next/Library/2026-10-20-20-00-00.gh-issue-148228.X7mQp2.rst b/Misc/NEWS.d/next/Library/2026-10-20-20-00-00.gh-issue-148228.X7mQp2.rst new file mode 100644 index 000000000000000..2de72d4d5fc7d19 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-20-20-00-00.gh-issue-148228.X7mQp2.rst @@ -0,0 +1,2 @@ +Display error overlays in the new interactive shell without the ``!`` prefix +and in non-bold red when color is enabled. Patch by Bartosz Sławecki. From 8f151e6f9c67884b41549d019e9b030a3a1dad64 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Wed, 21 Oct 2026 13:41:55 -0700 Subject: [PATCH 2/4] Bracket REPL error overlay messages --- Lib/_pyrepl/reader.py | 1 + Lib/test/test_pyrepl/test_reader.py | 28 +++++++++---------- ...-10-20-20-00-00.gh-issue-148228.X7mQp2.rst | 2 +- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py index ac8db5407a96103..1195e13a3e70b82 100644 --- a/Lib/_pyrepl/reader.py +++ b/Lib/_pyrepl/reader.py @@ -882,6 +882,7 @@ def debug(self, msg: str) -> None: pass def error(self, msg: str = "none") -> None: + msg = f"[ {msg} ]" if self.can_colorize: colors = _colorize.ANSIColors msg = f"{colors.RED}{msg}{colors.RESET}" diff --git a/Lib/test/test_pyrepl/test_reader.py b/Lib/test/test_pyrepl/test_reader.py index df74a3b97bfdd74..8975677534d8473 100644 --- a/Lib/test/test_pyrepl/test_reader.py +++ b/Lib/test/test_pyrepl/test_reader.py @@ -469,7 +469,7 @@ def test_colorized_error_message(self): self.assertEqual( reader.msg, - f"{ANSIColors.RED}nothing to yank{ANSIColors.RESET}", + f"{ANSIColors.RED}[ nothing to yank ]{ANSIColors.RESET}", ) self.assertEqual(reader.screen[-1], reader.msg) @@ -495,37 +495,37 @@ def _test_error_message( console.beep.assert_called_once() def test_invalid_key_message(self): - self._test_error_message("\x1bo", "no command is bound to this key") + self._test_error_message("\x1bo", "[ no command is bound to this key ]") def test_unimplemented_command_message(self): self._test_error_message( "x", - "command 'missing-command' is not implemented", + "[ command 'missing-command' is not implemented ]", command="missing-command", ) def test_nothing_to_yank_message(self): - self._test_error_message("\x19", "nothing to yank") + self._test_error_message("\x19", "[ nothing to yank ]") def test_yank_pop_with_empty_kill_ring_message(self): - self._test_error_message("\x1by", "nothing to yank") + self._test_error_message("\x1by", "[ nothing to yank ]") def test_yank_pop_after_other_command_message(self): self._test_error_message( "x\x01\x0b\x1by", - "previous command was not a yank", + "[ previous command was not a yank ]", ) def test_start_of_history_message(self): - self._test_error_message("\x10", "start of history list") + self._test_error_message("\x10", "[ start of history list ]") def test_end_of_history_message(self): - self._test_error_message("\x0e", "end of history list") + self._test_error_message("\x0e", "[ end of history list ]") def test_yank_arg_at_beginning_of_history_message(self): self._test_error_message( "\x1b.\x1b.\x1b.", - "beginning of history list", + "[ beginning of history list ]", history=["one"], historyi=1, ) @@ -533,7 +533,7 @@ def test_yank_arg_at_beginning_of_history_message(self): def test_yank_arg_with_no_argument_message(self): self._test_error_message( "\x1b.", - "no such arg", + "[ no such arg ]", history=[""], historyi=1, ) @@ -541,23 +541,23 @@ def test_yank_arg_with_no_argument_message(self): def test_empty_incremental_search_backspace_message(self): self._test_error_message( "x", - "nothing to rubout", + "[ nothing to rubout ]", command="isearch-backspace", ) def test_history_search_not_found_message(self): self._test_error_message( "missingx", - "not found", + "[ not found ]", command="history-search-backward", binding="x", ) def test_incremental_search_not_found_message(self): - self._test_error_message("\x12x", "not found") + self._test_error_message("\x12x", "[ not found ]") def test_no_completion_matches_message(self): - self._test_error_message("does_not_exist\t", "no matches") + self._test_error_message("does_not_exist\t", "[ no matches ]") @force_colorized_test_class diff --git a/Misc/NEWS.d/next/Library/2026-10-20-20-00-00.gh-issue-148228.X7mQp2.rst b/Misc/NEWS.d/next/Library/2026-10-20-20-00-00.gh-issue-148228.X7mQp2.rst index 2de72d4d5fc7d19..bd43bdcbb0801bd 100644 --- a/Misc/NEWS.d/next/Library/2026-10-20-20-00-00.gh-issue-148228.X7mQp2.rst +++ b/Misc/NEWS.d/next/Library/2026-10-20-20-00-00.gh-issue-148228.X7mQp2.rst @@ -1,2 +1,2 @@ -Display error overlays in the new interactive shell without the ``!`` prefix +Display error overlays in the new interactive shell in square brackets and in non-bold red when color is enabled. Patch by Bartosz Sławecki. From 63a8b7e146a63dd0ba6c7fdaf186dd5e0e085ca8 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Fri, 23 Oct 2026 14:01:10 -0700 Subject: [PATCH 3/4] Use THEME properly --- Lib/_pyrepl/reader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/_pyrepl/reader.py b/Lib/_pyrepl/reader.py index 1195e13a3e70b82..6238c88206bee85 100644 --- a/Lib/_pyrepl/reader.py +++ b/Lib/_pyrepl/reader.py @@ -884,8 +884,8 @@ def debug(self, msg: str) -> None: def error(self, msg: str = "none") -> None: msg = f"[ {msg} ]" if self.can_colorize: - colors = _colorize.ANSIColors - msg = f"{colors.RED}{msg}{colors.RESET}" + t = THEME() + msg = f"{t.error}{msg}{t.reset}" self.console.beep() self.msg = msg self.invalidate_message() From 67e71e84d2835d35e6fd2e649aa1a639a6b24666 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Fri, 23 Oct 2026 14:06:53 -0700 Subject: [PATCH 4/4] fix tests too --- Lib/test/test_pyrepl/test_reader.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_pyrepl/test_reader.py b/Lib/test/test_pyrepl/test_reader.py index 8975677534d8473..44c4be17e77e7d7 100644 --- a/Lib/test/test_pyrepl/test_reader.py +++ b/Lib/test/test_pyrepl/test_reader.py @@ -14,7 +14,7 @@ from _pyrepl.layout import LayoutMap from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig from _pyrepl.reader import Reader -from _colorize import ANSIColors, default_theme +from _colorize import default_theme overrides = {"reset": "z", "soft_keyword": "K"} @@ -467,9 +467,10 @@ def test_colorized_error_message(self): reader.handle1() + theme = default_theme.syntax self.assertEqual( reader.msg, - f"{ANSIColors.RED}[ nothing to yank ]{ANSIColors.RESET}", + f"{theme.error}[ nothing to yank ]{theme.reset}", ) self.assertEqual(reader.screen[-1], reader.msg)