From 9294ea800fd352683699f981a44b2dcd05ceeff2 Mon Sep 17 00:00:00 2001 From: yuwk <1729065730@qq.com> Date: Wed, 9 Sep 2026 23:08:17 +0800 Subject: [PATCH] Keep exception message continuation lines as text The catch-all rule in IPythonPartialTracebackLexer tagged every remaining line as Other. IPythonTracebackLexer delegates Other tokens to the Python lexer, so plain-text lines of a multiline exception message were re-lexed as Python code, emitting Error tokens when the message contained characters invalid in Python, such as backticks. Emit Text instead: numbered code-frame lines are matched by earlier rules and still delegate to the Python lexer. Fixes ipython/ipython#14142. --- ipython_pygments_lexers.py | 7 +++++-- test_ipython_pygments_lexers.py | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/ipython_pygments_lexers.py b/ipython_pygments_lexers.py index c655e23..e569fbb 100644 --- a/ipython_pygments_lexers.py +++ b/ipython_pygments_lexers.py @@ -232,8 +232,11 @@ class IPythonPartialTracebackLexer(RegexLexer): ), # (Exception Identifier)(Message) (r"(?u)(^[^\d\W]\w*)(:.*?\n)", bygroups(Name.Exception, Text)), - # Tag everything else as Other, will be handled later. - (r".*\n", Other), + # Tag everything else as text. Using Other here would make the + # delegating IPythonTracebackLexer hand these lines to the Python + # lexer, which emits Error tokens for text that is not valid Python + # (e.g. exception messages containing backticks). + (r".*\n", Text), ], } diff --git a/test_ipython_pygments_lexers.py b/test_ipython_pygments_lexers.py index dd95359..5b6c91c 100644 --- a/test_ipython_pygments_lexers.py +++ b/test_ipython_pygments_lexers.py @@ -202,3 +202,28 @@ def test_cell_magics(): (Token.Text, "\n$foo\n"), ] assert tokens_2 == list(lexer.get_tokens(fragment_2)) + + +def test_traceback_multiline_message_backticks(): + """A multiline exception message must not produce Error tokens. + + Continuation lines of an exception message are plain text, not Python. + If they were emitted as Other, the delegating IPythonTracebackLexer would + re-lex them as Python, and backticks in the message would become Error + tokens (see ipython/ipython#14142). + """ + from pygments import highlight + from pygments.formatters import HtmlFormatter + from pygments.filters import RaiseOnErrorTokenFilter + from pygments.token import Error + + from ipython_pygments_lexers import IPythonTracebackLexer + + code = "TypeError: `foo` is deprecated.\nUse `bar` instead.\n" + lexer = IPythonTracebackLexer() + + assert Error not in {t for t, _ in lexer.get_tokens(code)} + + # RaiseOnErrorTokenFilter would raise if any Error token is produced. + filtered = IPythonTracebackLexer(filters=[RaiseOnErrorTokenFilter()]) + highlight(code, filtered, HtmlFormatter())