Skip to content

Commit a1d84ae

Browse files
ambvclaude
andcommitted
Syntax-highlight perchance in the new REPL
The REPL highlights soft keywords only in contexts recognized by `is_soft_keyword_used()` in Lib/_pyrepl/utils.py, which knew about `match`, `case`, `type`, and `lazy` but not `perchance`. Add patterns for both forms: the expression form highlights when the previous token can end an expression (name, number, string, closing bracket, `...`, or a constant keyword) and the next token can start one, via a new `_starts_expression()` helper; the statement form highlights at line start. Identifier uses (`perchance = 5`, `f(perchance=3)`, `perchance.method()`) stay uncolored, and in `x = perchance perchance 0` only the second occurrence — the operator — is highlighted. Also relax the `bracket_level == 0` gate for `perchance` alone: the other soft keywords can only start statements and thus cannot legally appear inside brackets, but the perchance expression form is most at home there, e.g. `[int(v) perchance None for v in vs]`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent eeeb42c commit a1d84ae

1 file changed

Lines changed: 41 additions & 1 deletion

File tree

Lib/_pyrepl/utils.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,10 @@ def gen_colors_from_token_stream(
230230
is_def_name = True
231231
elif (
232232
keyword.issoftkeyword(token.string)
233-
and bracket_level == 0
233+
# perchance expressions are common inside brackets
234+
# (comprehensions); the other soft keywords only start
235+
# statements and cannot appear there
236+
and (bracket_level == 0 or token.string == "perchance")
234237
and is_soft_keyword_used(prev_token, token, next_token)
235238
):
236239
span = Span.from_token(token, line_lengths)
@@ -247,6 +250,18 @@ def gen_colors_from_token_stream(
247250
keyword_first_sets_case = frozenset({"False", "None", "True"})
248251

249252

253+
def _starts_expression(token: TI | None) -> bool:
254+
match token:
255+
case TI(T.NUMBER | T.STRING | T.FSTRING_START | T.TSTRING_START):
256+
return True
257+
case TI(T.OP, string="(" | "[" | "{" | "-" | "+" | "~" | "..."):
258+
return True
259+
case TI(T.NAME, string=s):
260+
return not keyword.iskeyword(s) or s in keyword_first_sets_match
261+
case _:
262+
return False
263+
264+
250265
def is_soft_keyword_used(*tokens: TI | None) -> bool:
251266
"""Returns True if the current token is a keyword in this context.
252267
@@ -299,6 +314,31 @@ def is_soft_keyword_used(*tokens: TI | None) -> bool:
299314
TI(string="import") | TI(string="from")
300315
):
301316
return True
317+
# expression form: <expr> perchance <fallback>; the previous token
318+
# must be able to end an expression and the next one to start one
319+
case (
320+
TI(T.NUMBER | T.STRING | T.FSTRING_END | T.TSTRING_END)
321+
| TI(T.OP, string=")" | "]" | "}" | "..."),
322+
TI(string="perchance"),
323+
next_tok
324+
) if _starts_expression(next_tok):
325+
return True
326+
case (
327+
TI(T.NAME, string=p),
328+
TI(string="perchance"),
329+
next_tok
330+
) if (
331+
(not keyword.iskeyword(p) or p in keyword_first_sets_case)
332+
and _starts_expression(next_tok)
333+
):
334+
return True
335+
# statement form: perchance E1, E2: <block>
336+
case (
337+
None | TI(T.NEWLINE) | TI(T.INDENT) | TI(T.DEDENT),
338+
TI(string="perchance"),
339+
next_tok
340+
) if _starts_expression(next_tok):
341+
return True
302342
case _:
303343
return False
304344

0 commit comments

Comments
 (0)