https://github.com/python/cpython/commit/bc2fd447d20d6840f4d7feb3cb8c35ee4e2aaa06
commit: bc2fd447d20d6840f4d7feb3cb8c35ee4e2aaa06
branch: main
author: Bartosz Sławecki <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-07-18T13:17:51+02:00
summary:
gh-151822: Colorize commands separately in the REPL (#151823)
files:
A Misc/NEWS.d/next/Library/2026-06-21-00-00-57.gh-issue-151822.bOC2G56F.rst
M Lib/_colorize.py
M Lib/_pyrepl/simple_interact.py
M Lib/_pyrepl/utils.py
M Lib/test/test_pyrepl/test_utils.py
diff --git a/Lib/_colorize.py b/Lib/_colorize.py
index 27eb7f13baca971..5f44a3aa05eb8f6 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 c169d0191bd8333..da557b254e52c48 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 230dae35af665ab..8d68f4f66f2980e 100644
--- a/Lib/_pyrepl/utils.py
+++ b/Lib/_pyrepl/utils.py
@@ -24,6 +24,8 @@
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 ebbd06213c69aff..e87d14304c82257 100644
--- a/Lib/test/test_pyrepl/test_utils.py
+++ b/Lib/test/test_pyrepl/test_utils.py
@@ -159,3 +159,29 @@ def test_gen_colors_keyword_highlighting(self):
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 000000000000000..70e7323c7e2ef6a
--- /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.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]