Skip to content

FileNotFoundError when clicking markdown links #6039

@mxmlnkn

Description

@mxmlnkn

Following this example, I have added a link inside the markdown.

import textual.app
import textual.widgets

class MarkdownExampleApp(textual.app.App):
    def compose(self):
        yield textual.widgets.MarkdownViewer('[LINK](https://pypi.org/)')

MarkdownExampleApp().run()

Starting this and clicking the LINK text, opens my browser, but the app also crashes with:

╭───────────────────────────────────────── Traceback (most recent call last) ─────────────────────────────────────────╮
│ /home/user123/.local/lib/python3.12/site-packages/textual/widgets/_markdown.py:1632 in _on_markdown_link_clicked    │
│                                                                                                                     │
│   1629 │                                                                                                            │
│   1630 │   async def _on_markdown_link_clicked(self, message: Markdown.LinkClicked) -> None:                        │
│   1631 │   │   message.stop()                                                                                       │
│ ❱ 1632 │   │   await self.go(message.href)                                                                          │
│   1633 │                                                                                                            │
│   1634 │   def watch_show_table_of_contents(self, show_table_of_contents: bool) -> None:                            │
│   1635 │   │   self.set_class(show_table_of_contents, "-show-table-of-contents")                                    │
│                                                                                                                     │
│ ╭────────────────────────── locals ───────────────────────────╮                                                     │
│ │ message = LinkClicked()                                     │                                                     │
│ │    self = MarkdownViewer(classes='-show-table-of-contents') │                                                     │
│ ╰─────────────────────────────────────────────────────────────╯                                                     │
│                                                                                                                     │
│ /home/user123/.local/lib/python3.12/site-packages/textual/widgets/_markdown.py:1615 in go                           │
│                                                                                                                     │
│   1612 │   │   │   self.document.goto_anchor(anchor)                                                                │
│   1613 │   │   else:                                                                                                │
│   1614 │   │   │   # We've been asked to go to a file, optionally with an anchor.                                   │
│ ❱ 1615 │   │   │   await self.document.load(self.navigator.go(location))                                            │
│   1616 │   │   │   self.post_message(self.NavigatorUpdated())                                                       │
│   1617 │                                                                                                            │
│   1618 │   async def back(self) -> None:                                                                            │
│                                                                                                                     │
│ ╭─────────────────────────── locals ───────────────────────────╮                                                    │
│ │   anchor = ''                                                │                                                    │
│ │ location = 'https://pypi.org/'                               │                                                    │
│ │     path = PosixPath('https:/pypi.org')                      │                                                    │
│ │     self = MarkdownViewer(classes='-show-table-of-contents') │                                                    │
│ ╰──────────────────────────────────────────────────────────────╯                                                    │
│                                                                                                                     │
│ /home/user123/.local/lib/python3.12/site-packages/textual/widgets/_markdown.py:1188 in load                         │
│                                                                                                                     │
│   1185 │   │   │   those that can be raised by calling [`Path.read_text`][pathlib.Path.read_tex                     │
│   1186 │   │   """                                                                                                  │
│   1187 │   │   path, anchor = self.sanitize_location(str(path))                                                     │
│ ❱ 1188 │   │   data = await asyncio.get_running_loop().run_in_executor(                                             │
│   1189 │   │   │   None, partial(path.read_text, encoding="utf-8")                                                  │
│   1190 │   │   )                                                                                                    │
│   1191 │   │   await self.update(data)                                                                              │
│                                                                                                                     │
│ ╭───────────────────────────────── locals ──────────────────────────────────╮                                       │
│ │ anchor = ''                                                               │                                       │
│ │   path = PosixPath('/projects/projects-10001/ratarmount/https:/pypi.org') │                                       │
│ │   self = Markdown()                                                       │                                       │
│ ╰───────────────────────────────────────────────────────────────────────────╯                                       │
│                                                                                                                     │
│ /usr/lib/python3.12/concurrent/futures/thread.py:58 in run                                                          │
│                                                                                                                     │
│    55 │   │   │   return                                                                       ╭── locals ───╮      │
│    56 │   │                                                                                    │ self = None │      │
│    57 │   │   try:                                                                             ╰─────────────╯      │
│ ❱  58 │   │   │   result = self.fn(*self.args, **self.kwargs)                                                       │
│    59 │   │   except BaseException as exc:                                                                          │
│    60 │   │   │   self.future.set_exception(exc)                                                                    │
│    61 │   │   │   # Break a reference cycle with the exception 'exc'                                                │
│                                                                                                                     │
│ /usr/lib/python3.12/pathlib.py:1029 in read_text                                                                    │
│                                                                                                                     │
│   1026 │   │   Open the file in text mode, read it, and close the file.                                             │
│   1027 │   │   """                                                                                                  │
│   1028 │   │   encoding = io.text_encoding(encoding)                                                                │
│ ❱ 1029 │   │   with self.open(mode='r', encoding=encoding, errors=errors) as f:                                     │
│   1030 │   │   │   return f.read()                                                                                  │
│   1031 │                                                                                                            │
│   1032 │   def write_bytes(self, data):                                                                             │
│                                                                                                                     │
│ ╭────────────────────────────────── locals ───────────────────────────────────╮                                     │
│ │ encoding = 'utf-8'                                                          │                                     │
│ │   errors = None                                                             │                                     │
│ │     self = PosixPath('/projects/projects-10001/ratarmount/https:/pypi.org') │                                     │
│ ╰─────────────────────────────────────────────────────────────────────────────╯                                     │
│                                                                                                                     │
│ /usr/lib/python3.12/pathlib.py:1015 in open                                                                         │
│                                                                                                                     │
│   1012 │   │   """                                                                                                  │
│   1013 │   │   if "b" not in mode:                                                                                  │
│   1014 │   │   │   encoding = io.text_encoding(encoding)                                                            │
│ ❱ 1015 │   │   return io.open(self, mode, buffering, encoding, errors, newline)                                     │
│   1016 │                                                                                                            │
│   1017 │   def read_bytes(self):                                                                                    │
│   1018 │   │   """                                                                                                  │
│                                                                                                                     │
│ ╭─────────────────────────────────── locals ───────────────────────────────────╮                                    │
│ │ buffering = -1                                                               │                                    │
│ │  encoding = 'utf-8'                                                          │                                    │
│ │    errors = None                                                             │                                    │
│ │      mode = 'r'                                                              │                                    │
│ │   newline = None                                                             │                                    │
│ │      self = PosixPath('/projects/projects-10001/ratarmount/https:/pypi.org') │                                    │
│ ╰──────────────────────────────────────────────────────────────────────────────╯                                    │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
FileNotFoundError: [Errno 2] No such file or directory: '/projects/projects-10001/ratarmount/https:/pypi.org'
  • When calling MarkdownViewer with open_links=False, I get only the crash, without any browser being opened.
  • When using an anchor link to a section [LINK](#section), it scrolls to that section but also tries to open it as a URL, so that I get a popup dialog "Unable to detect the URI-scheme of "#section"
  • When using an anchor to a section and open_links=False, it works, i.e., simply scrolls to that section and no error system dialog pops up.

Textual Diagnostics

Versions

Name Value
Textual 5.3.0
Rich 13.7.1

Python

Name Value
Version 3.12.3
Implementation CPython
Compiler GCC 13.3.0
Executable /usr/bin/python3

Operating System

Name Value
System Linux
Release 6.8.0-64-generic
Version #67-Ubuntu SMP PREEMPT_DYNAMIC Sun Jun 15 20:23:31 UTC 2025

Terminal

Name Value
Terminal Application Unknown
TERM xterm-256color
COLORTERM truecolor
FORCE_COLOR Not set
NO_COLOR Not set

Rich Console options

Name Value
size width=177, height=50
legacy_windows False
min_width 1
max_width 177
is_terminal True
encoding utf-8
max_height 50
justify None
overflow None
no_wrap False
highlight None
markup None
height None

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions