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())