From cf3ec1e859511f0fb4d9b5fc3da252ab20cd6aa7 Mon Sep 17 00:00:00 2001 From: mudiko Date: Mon, 7 Jul 2025 17:23:12 +0200 Subject: [PATCH 1/3] Allow file:// hyperlinks --- rich/markdown.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rich/markdown.py b/rich/markdown.py index 12496487b..453d8f9d2 100644 --- a/rich/markdown.py +++ b/rich/markdown.py @@ -542,6 +542,13 @@ def __init__( inline_code_theme: str | None = None, ) -> None: parser = MarkdownIt().enable("strikethrough").enable("table") + # Allow file:// URLs in links + original_validate = parser.validateLink + def validate_link(url: str) -> bool: + if url.lower().startswith("file://"): + return True + return original_validate(url) + parser.validateLink = validate_link self.markup = markup self.parsed = parser.parse(markup) self.code_theme = code_theme From 7d690d9481c894cc05c921f03562c99822795f97 Mon Sep 17 00:00:00 2001 From: mudiko Date: Mon, 7 Jul 2025 17:37:42 +0200 Subject: [PATCH 2/3] spacing edit by black --- rich/markdown.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rich/markdown.py b/rich/markdown.py index 453d8f9d2..7629fdc04 100644 --- a/rich/markdown.py +++ b/rich/markdown.py @@ -544,10 +544,12 @@ def __init__( parser = MarkdownIt().enable("strikethrough").enable("table") # Allow file:// URLs in links original_validate = parser.validateLink + def validate_link(url: str) -> bool: if url.lower().startswith("file://"): return True return original_validate(url) + parser.validateLink = validate_link self.markup = markup self.parsed = parser.parse(markup) From ee56581ac56c373d928422fd8b9293cc3b70a8e3 Mon Sep 17 00:00:00 2001 From: mudiko Date: Mon, 7 Jul 2025 17:39:38 +0200 Subject: [PATCH 3/3] Add new tests --- tests/test_markdown.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/test_markdown.py b/tests/test_markdown.py index 2ffbdd93d..6411f7f0f 100644 --- a/tests/test_markdown.py +++ b/tests/test_markdown.py @@ -199,6 +199,26 @@ def test_table_with_empty_cells() -> None: assert result == expected +def test_file_url_hyperlink(): + """Test that file:// URLs are rendered as hyperlinks.""" + markdown = Markdown("[test file](file:///path/to/file.txt)") + result = render(markdown) + # Check that the link is rendered with the hyperlink escape sequences + # The exact format is: \x1b]8;id=0;foo\x1b\\test file\x1b]8;;\x1b\\ + assert "\x1b]8;" in result # OSC 8 hyperlink start sequence + assert "test file" in result + assert "file:///path/to/file.txt" in result or "foo" in result # URL is either preserved or replaced by test helper + + +def test_file_url_vs_http_url(): + """Test that both file:// and http:// URLs are treated as hyperlinks.""" + markdown = Markdown("[http link](http://example.com) and [file link](file:///etc/hosts)") + result = render(markdown) + # Both should be rendered as hyperlinks with OSC 8 sequences + hyperlink_count = result.count("\x1b]8;") + assert hyperlink_count >= 2 # At least 2 hyperlinks (start sequences) + + if __name__ == "__main__": markdown = Markdown(MARKDOWN) rendered = render(markdown)