From: Bartosz Sławecki Date: Sat, 18 Jul 2026 11:17:51 +0000 (+0200) Subject: gh-151822: Colorize commands separately in the REPL (#151823) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bc2fd447d20d6840f4d7feb3cb8c35ee4e2aaa06;p=thirdparty%2FPython%2Fcpython.git gh-151822: Colorize commands separately in the REPL (#151823) --- diff --git a/Lib/_colorize.py b/Lib/_colorize.py index 27eb7f13baca..5f44a3aa05eb 100644 --- a/Lib/_colorize.py +++ b/Lib/_colorize.py @@ -405,6 +405,7 @@ class Syntax(ThemeSection): keyword: str = ANSIColors.BOLD_BLUE keyword_constant: str = ANSIColors.BOLD_BLUE builtin: str = ANSIColors.CYAN + command: str = ANSIColors.BOLD_CYAN comment: str = ANSIColors.RED string: str = ANSIColors.GREEN number: str = ANSIColors.YELLOW diff --git a/Lib/_pyrepl/simple_interact.py b/Lib/_pyrepl/simple_interact.py index c169d0191bd8..da557b254e52 100644 --- a/Lib/_pyrepl/simple_interact.py +++ b/Lib/_pyrepl/simple_interact.py @@ -68,6 +68,7 @@ def _clear_screen(): reader.scheduled_commands.append("clear_screen") +# Keep this in sync with _pyrepl.utils.COMMANDS REPL_COMMANDS = { "exit": _sitebuiltins.Quitter('exit', ''), "quit": _sitebuiltins.Quitter('quit' ,''), diff --git a/Lib/_pyrepl/utils.py b/Lib/_pyrepl/utils.py index 230dae35af66..8d68f4f66f29 100644 --- a/Lib/_pyrepl/utils.py +++ b/Lib/_pyrepl/utils.py @@ -24,6 +24,8 @@ ZERO_WIDTH_TRANS = str.maketrans({"\x01": "", "\x02": ""}) IDENTIFIERS_AFTER = frozenset({"def", "class"}) KEYWORD_CONSTANTS = frozenset({"True", "False", "None"}) BUILTINS = frozenset({str(name) for name in dir(builtins) if not name.startswith('_')}) +# Keep this in sync with _pyrepl.simple_interact.REPL_COMMANDS +COMMANDS = frozenset({"exit", "quit", "copyright", "help", "clear"}) def THEME(**kwargs): @@ -235,6 +237,13 @@ def gen_colors_from_token_stream( ): span = Span.from_token(token, line_lengths) yield ColorSpan(span, "soft_keyword") + elif ( + token.string in COMMANDS + and (not prev_token or prev_token.type == T.INDENT) + and (not next_token or next_token.type == T.NEWLINE) + ): + span = Span.from_token(token, line_lengths) + yield ColorSpan(span, "command") elif ( token.string in BUILTINS and not (prev_token and prev_token.exact_type == T.DOT) diff --git a/Lib/test/test_pyrepl/test_utils.py b/Lib/test/test_pyrepl/test_utils.py index ebbd06213c69..e87d14304c82 100644 --- a/Lib/test/test_pyrepl/test_utils.py +++ b/Lib/test/test_pyrepl/test_utils.py @@ -159,3 +159,29 @@ class TestUtils(TestCase): span_text = code[color.span.start:color.span.end + 1] actual_highlights.append((span_text, color.tag)) self.assertEqual(actual_highlights, expected_highlights) + + def test_gen_colors_command_highlighting(self): + cases = [ + # highlights bare command names (after stripping whitespaces) + ("exit", [("exit", "command")]), + ("quit", [("quit", "command")]), + ("copyright", [("copyright", "command")]), + ("help", [("help", "command")]), + ("clear", [("clear", "command")]), + (" clear ", [("clear", "command")]), + # no highlight when not the only token on the line + ("x = exit", [("=", "op"), ("exit", "builtin")]), + ("obj.exit", [(".", "op")]), + # falls through to builtin when called as function or used in expression + ("exit()", [("exit", "builtin"), ("(", "op"), (")", "op")]), + ("quit(0)", [("quit", "builtin"), ("(", "op"), ("0", "number"), (")", "op")]), + ("print(exit)", [("print", "builtin"), ("(", "op"), ("exit", "builtin"), (")", "op")]), + ] + for code, expected_highlights in cases: + with self.subTest(code=code): + colors = list(gen_colors(code)) + actual_highlights = [] + for color in colors: + span_text = code[color.span.start:color.span.end + 1] + actual_highlights.append((span_text, color.tag)) + self.assertEqual(actual_highlights, expected_highlights) diff --git a/Misc/NEWS.d/next/Library/2026-06-21-00-00-57.gh-issue-151822.bOC2G56F.rst b/Misc/NEWS.d/next/Library/2026-06-21-00-00-57.gh-issue-151822.bOC2G56F.rst new file mode 100644 index 000000000000..70e7323c7e2e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-21-00-00-57.gh-issue-151822.bOC2G56F.rst @@ -0,0 +1 @@ +Colorize ``exit``, ``quit``, ``copyright``, ``help``, and ``clear`` as commands in the :term:`REPL` when typed alone on a line. Patch by Bartosz Sławecki.