Skip to content

Commit bc2fd44

Browse files
authored
gh-151822: Colorize commands separately in the REPL (#151823)
1 parent 31c81e6 commit bc2fd44

5 files changed

Lines changed: 38 additions & 0 deletions

File tree

Lib/_colorize.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ class Syntax(ThemeSection):
405405
keyword: str = ANSIColors.BOLD_BLUE
406406
keyword_constant: str = ANSIColors.BOLD_BLUE
407407
builtin: str = ANSIColors.CYAN
408+
command: str = ANSIColors.BOLD_CYAN
408409
comment: str = ANSIColors.RED
409410
string: str = ANSIColors.GREEN
410411
number: str = ANSIColors.YELLOW

Lib/_pyrepl/simple_interact.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def _clear_screen():
6868
reader.scheduled_commands.append("clear_screen")
6969

7070

71+
# Keep this in sync with _pyrepl.utils.COMMANDS
7172
REPL_COMMANDS = {
7273
"exit": _sitebuiltins.Quitter('exit', ''),
7374
"quit": _sitebuiltins.Quitter('quit' ,''),

Lib/_pyrepl/utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
IDENTIFIERS_AFTER = frozenset({"def", "class"})
2525
KEYWORD_CONSTANTS = frozenset({"True", "False", "None"})
2626
BUILTINS = frozenset({str(name) for name in dir(builtins) if not name.startswith('_')})
27+
# Keep this in sync with _pyrepl.simple_interact.REPL_COMMANDS
28+
COMMANDS = frozenset({"exit", "quit", "copyright", "help", "clear"})
2729

2830

2931
def THEME(**kwargs):
@@ -235,6 +237,13 @@ def gen_colors_from_token_stream(
235237
):
236238
span = Span.from_token(token, line_lengths)
237239
yield ColorSpan(span, "soft_keyword")
240+
elif (
241+
token.string in COMMANDS
242+
and (not prev_token or prev_token.type == T.INDENT)
243+
and (not next_token or next_token.type == T.NEWLINE)
244+
):
245+
span = Span.from_token(token, line_lengths)
246+
yield ColorSpan(span, "command")
238247
elif (
239248
token.string in BUILTINS
240249
and not (prev_token and prev_token.exact_type == T.DOT)

Lib/test/test_pyrepl/test_utils.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,29 @@ def test_gen_colors_keyword_highlighting(self):
159159
span_text = code[color.span.start:color.span.end + 1]
160160
actual_highlights.append((span_text, color.tag))
161161
self.assertEqual(actual_highlights, expected_highlights)
162+
163+
def test_gen_colors_command_highlighting(self):
164+
cases = [
165+
# highlights bare command names (after stripping whitespaces)
166+
("exit", [("exit", "command")]),
167+
("quit", [("quit", "command")]),
168+
("copyright", [("copyright", "command")]),
169+
("help", [("help", "command")]),
170+
("clear", [("clear", "command")]),
171+
(" clear ", [("clear", "command")]),
172+
# no highlight when not the only token on the line
173+
("x = exit", [("=", "op"), ("exit", "builtin")]),
174+
("obj.exit", [(".", "op")]),
175+
# falls through to builtin when called as function or used in expression
176+
("exit()", [("exit", "builtin"), ("(", "op"), (")", "op")]),
177+
("quit(0)", [("quit", "builtin"), ("(", "op"), ("0", "number"), (")", "op")]),
178+
("print(exit)", [("print", "builtin"), ("(", "op"), ("exit", "builtin"), (")", "op")]),
179+
]
180+
for code, expected_highlights in cases:
181+
with self.subTest(code=code):
182+
colors = list(gen_colors(code))
183+
actual_highlights = []
184+
for color in colors:
185+
span_text = code[color.span.start:color.span.end + 1]
186+
actual_highlights.append((span_text, color.tag))
187+
self.assertEqual(actual_highlights, expected_highlights)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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.

0 commit comments

Comments
 (0)