Skip to content

Commit f759c1d

Browse files
committed
Fixed the pager to strip OSC sequences before ANSI parsing, preserving visible hyperlink text, colors, and width calculations
When ppaged() receives Rich output containing hyperlinks, Rich emits OSC 8 sequences that prompt-toolkit's ANSI parser does not understand. Their contents become visible pager text: a linked click here renders as 8;id=...;https://example.comclick here8;;. This also corrupts width calculations and searchable text. This change strips unsupported OSC sequences before passing captured output to cmd2's new internal pager.
1 parent cc6ea8b commit f759c1d

2 files changed

Lines changed: 25 additions & 0 deletions

File tree

‎cmd2/pager.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""A pager view hosted by the main prompt-toolkit application."""
22

3+
import re
34
import threading
45
from collections.abc import Callable
56
from functools import partial
@@ -20,9 +21,14 @@
2021
from prompt_toolkit.utils import get_cwidth
2122
from prompt_toolkit.widgets import SearchToolbar, TextArea
2223

24+
_OSC_RE = re.compile(r"\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)")
25+
2326

2427
def _fragments(text: str) -> StyleAndTextTuples:
2528
"""Parse captured output, dropping the trailing newline that ends its last line."""
29+
# prompt-toolkit's ANSI parser does not handle OSC sequences, including Rich
30+
# hyperlinks. Remove their metadata while retaining visible text and SGR styles.
31+
text = _OSC_RE.sub("", text)
2632
return to_formatted_text(ANSI(text.removesuffix("\n")))
2733

2834

‎tests/test_command_toolbar.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from prompt_toolkit.layout import HSplit, Layout, Window
1818
from prompt_toolkit.output import DummyOutput
1919
from prompt_toolkit.shortcuts import PromptSession
20+
from rich.console import Console
2021

2122
from cmd2 import Cmd, command_toolbar
2223
from cmd2.pager import Pager, output_fits
@@ -808,6 +809,24 @@ def test_pager_styles_and_wrapping(chop) -> None:
808809
assert "ansired" in lexer(0)[0][0]
809810

810811

812+
@pytest.mark.parametrize("chop", [False, True])
813+
@pytest.mark.parametrize("terminator", ["\x1b\\", "\x07"], ids=["ST", "BEL"])
814+
def test_pager_rich_hyperlinks(chop, terminator) -> None:
815+
console = Console(force_terminal=True, color_system="standard", no_color=False)
816+
with console.capture() as capture:
817+
console.print("[link=https://example.com][red]click here[/red][/link] after")
818+
captured = capture.get()
819+
assert "\x1b]8;" in captured
820+
captured = captured.replace("\x1b\\", terminator)
821+
822+
pager = Pager(captured, chop=chop)
823+
assert pager.text.text == "click here after"
824+
lexer = pager.text.lexer.lex_document(pager.text.document)
825+
assert "ansired" in lexer(0)[0][0]
826+
assert output_fits(captured, len("click here after"), 1, chop=chop)
827+
assert not output_fits(captured, len("click here after") - 1, 1, chop=chop)
828+
829+
811830
@pytest.mark.parametrize("chop", [False, True])
812831
def test_output_fits_measures_styled_and_wide_text(chop) -> None:
813832
# Forty double-width characters occupy eighty columns, and styling them adds

0 commit comments

Comments
 (0)