Skip to content

in textual 5.0, Markdown.append is broken #5988

@python-and-novella

Description

@python-and-novella

If I click the terminal and append 'ab' to markdown, it will shutdown:

from textual.app import App
from textual.widgets import Markdown

TEXT = '''\
### Hello
**World**
'''

class MyApp(App):
    def on_mount(self):
        self.widgets = [
            Markdown(TEXT),
        ]
        self.mount_all(self.widgets)
    async def on_click(self):
        await self.query_one(Markdown).append('ab')

if __name__ == '__main__':
    app = MyApp()
    app.run()

It reports:

╭────────────────────────────────────────────────────────────────────────────────── Traceback (most recent call last) ──────────────────────────────────────────────────────────────────────────────────╮
│ e:\PSF\textual_uv_app\src\textual_app\myapp.py:16 in on_click                                                                                                                                         │
│                                                                                                                                                                                                       │
│   13 │   │   ]                                                                                ╭─────────────────────────────────────── locals ────────────────────────────────────────╮               │
│   14 │   │   self.mount_all(self.widgets)                                                     │ self = MyApp(title='MyApp', classes={'-dark-mode'}, pseudo_classes={'dark', 'focus'}) │               │
│   15 │   async def on_click(self):                                                            ╰───────────────────────────────────────────────────────────────────────────────────────╯               │
│ ❱ 16 │   │   await self.query_one(Markdown).append('ab')                                                                                                                                              │
│   17                                                                                                                                                                                                  │
│   18 if __name__ == '__main__':                                                                                                                                                                       │
│   19 │   app = MyApp()                                                                                                                                                                                │
│                                                                                                                                                                                                       │
│ E:\PSF\textual_uv_app\.venv\Lib\site-packages\textual\widgets\_markdown.py:1403 in await_append                                                                                                       │
│                                                                                                                                                                                                       │
│   1400 │   │   │   │   │   │   last_block = existing_blocks[-1]                                                                                                                                       │
│   1401 │   │   │   │   │   │   last_block.source_range = new_blocks[0].source_range                                                                                                                   │
│   1402 │   │   │   │   │   │   try:                                                                                                                                                                   │
│ ❱ 1403 │   │   │   │   │   │   │   await last_block._update_from_block(new_blocks[0])                                                                                                                 │
│   1404 │   │   │   │   │   │   except IndexError:                                                                                                                                                     │
│   1405 │   │   │   │   │   │   │   pass                                                                                                                                                               │
│   1406 │   │   │   │   │   │   else:                                                                                                                                                                  │
│                                                                                                                                                                                                       │
│ ╭───────────────────────────────────────────────────────────────────────────────────────────── locals ──────────────────────────────────────────────────────────────────────────────────────────────╮ │
│ │             block = MarkdownParagraph(name='paragraph_open', classes='level-0')                                                                                                                   │ │
│ │               end = 3                                                                                                                                                                             │ │
│ │   existing_blocks = [MarkdownH3(id='hello-1', name='heading_open', classes='level-0'), MarkdownParagraph(name='paragraph_open', classes='level-0')]                                               │ │
│ │        last_block = MarkdownParagraph(name='paragraph_open', classes='level-0')                                                                                                                   │ │
│ │        new_blocks = [MarkdownH3(id='hello-1', name='heading_open', classes='level-0'), MarkdownParagraph(name='paragraph_open', classes='level-0')]                                               │ │
│ │            parser = markdown_it.main.MarkdownIt()                                                                                                                                                 │ │
│ │              self = Markdown()                                                                                                                                                                    │ │
│ │             start = 1                                                                                                                                                                             │ │
│ │        start_line = 0                                                                                                                                                                             │ │
│ │ table_of_contents = [(3, 'Hello', 'hello-1')]                                                                                                                                                     │ │
│ │             token = Token(type='paragraph_open', tag='p', nesting=1, attrs={}, map=[1, 3], level=0, children=None, content='', markup='', info='', meta={}, block=True, hidden=False)             │ │
│ │            tokens = [                                                                                                                                                                             │ │
│ │                     │   Token(type='heading_open', tag='h3', nesting=1, attrs={}, map=[0, 1], level=0, children=None, content='', markup='###', info='', meta={}, block=True, hidden=False),      │ │
│ │                     │   Token(                                                                                                                                                                    │ │
│ │                     │   │   type='inline',                                                                                                                                                        │ │
│ │                     │   │   tag='',                                                                                                                                                               │ │
│ │                     │   │   nesting=0,                                                                                                                                                            │ │
│ │                     │   │   attrs={},                                                                                                                                                             │ │
│ │                     │   │   map=[0, 1],                                                                                                                                                           │ │
│ │                     │   │   level=1,                                                                                                                                                              │ │
│ │                     │   │   children=[                                                                                                                                                            │ │
│ │                     │   │   │   Token(type='text', tag='', nesting=0, attrs={}, map=None, level=0, children=None, content='Hello', markup='', info='', meta={}, block=False, hidden=False)        │ │
│ │                     │   │   ],                                                                                                                                                                    │ │
│ │                     │   │   content='Hello',                                                                                                                                                      │ │
│ │                     │   │   markup='',                                                                                                                                                            │ │
│ │                     │   │   info='',                                                                                                                                                              │ │
│ │                     │   │   meta={},                                                                                                                                                              │ │
│ │                     │   │   block=True,                                                                                                                                                           │ │
│ │                     │   │   hidden=False                                                                                                                                                          │ │
│ │                     │   ),                                                                                                                                                                        │ │
│ │                     │   Token(type='heading_close', tag='h3', nesting=-1, attrs={}, map=None, level=0, children=None, content='', markup='###', info='', meta={}, block=True, hidden=False),      │ │
│ │                     │   Token(type='paragraph_open', tag='p', nesting=1, attrs={}, map=[1, 3], level=0, children=None, content='', markup='', info='', meta={}, block=True, hidden=False),        │ │
│ │                     │   Token(                                                                                                                                                                    │ │
│ │                     │   │   type='inline',                                                                                                                                                        │ │
│ │                     │   │   tag='',                                                                                                                                                               │ │
│ │                     │   │   nesting=0,                                                                                                                                                            │ │
│ │                     │   │   attrs={},                                                                                                                                                             │ │
│ │                     │   │   map=[1, 3],                                                                                                                                                           │ │
│ │                     │   │   level=1,                                                                                                                                                              │ │
│ │                     │   │   children=[                                                                                                                                                            │ │
│ │                     │   │   │   Token(type='text', tag='', nesting=0, attrs={}, map=None, level=0, children=None, content='', markup='', info='', meta={}, block=False, hidden=False),            │ │
│ │                     │   │   │   Token(                                                                                                                                                            │ │
│ │                     │   │   │   │   type='strong_open',                                                                                                                                           │ │
│ │                     │   │   │   │   tag='strong',                                                                                                                                                 │ │
│ │                     │   │   │   │   nesting=1,                                                                                                                                                    │ │
│ │                     │   │   │   │   attrs={},                                                                                                                                                     │ │
│ │                     │   │   │   │   map=None,                                                                                                                                                     │ │
│ │                     │   │   │   │   level=0,                                                                                                                                                      │ │
│ │                     │   │   │   │   children=None,                                                                                                                                                │ │
│ │                     │   │   │   │   content='',                                                                                                                                                   │ │
│ │                     │   │   │   │   markup='**',                                                                                                                                                  │ │
│ │                     │   │   │   │   info='',                                                                                                                                                      │ │
│ │                     │   │   │   │   meta={},                                                                                                                                                      │ │
│ │                     │   │   │   │   block=False,                                                                                                                                                  │ │
│ │                     │   │   │   │   hidden=False                                                                                                                                                  │ │
│ │                     │   │   │   ),                                                                                                                                                                │ │
│ │                     │   │   │   Token(type='text', tag='', nesting=0, attrs={}, map=None, level=1, children=None, content='World', markup='', info='', meta={}, block=False, hidden=False),       │ │
│ │                     │   │   │   Token(                                                                                                                                                            │ │
│ │                     │   │   │   │   type='strong_close',                                                                                                                                          │ │
│ │                     │   │   │   │   tag='strong',                                                                                                                                                 │ │
│ │                     │   │   │   │   nesting=-1,                                                                                                                                                   │ │
│ │                     │   │   │   │   attrs={},                                                                                                                                                     │ │
│ │                     │   │   │   │   map=None,                                                                                                                                                     │ │
│ │                     │   │   │   │   level=0,                                                                                                                                                      │ │
│ │                     │   │   │   │   children=None,                                                                                                                                                │ │
│ │                     │   │   │   │   content='',                                                                                                                                                   │ │
│ │                     │   │   │   │   markup='**',                                                                                                                                                  │ │
│ │                     │   │   │   │   info='',                                                                                                                                                      │ │
│ │                     │   │   │   │   meta={},                                                                                                                                                      │ │
│ │                     │   │   │   │   block=False,                                                                                                                                                  │ │
│ │                     │   │   │   │   hidden=False                                                                                                                                                  │ │
│ │                     │   │   │   ),                                                                                                                                                                │ │
│ │                     │   │   │   Token(type='text', tag='', nesting=0, attrs={}, map=None, level=0, children=None, content='', markup='', info='', meta={}, block=False, hidden=False),            │ │
│ │                     │   │   │   Token(                                                                                                                                                            │ │
│ │                     │   │   │   │   type='softbreak',                                                                                                                                             │ │
│ │                     │   │   │   │   tag='br',                                                                                                                                                     │ │
│ │                     │   │   │   │   nesting=0,                                                                                                                                                    │ │
│ │                     │   │   │   │   attrs={},                                                                                                                                                     │ │
│ │                     │   │   │   │   map=None,                                                                                                                                                     │ │
│ │                     │   │   │   │   level=0,                                                                                                                                                      │ │
│ │                     │   │   │   │   children=None,                                                                                                                                                │ │
│ │                     │   │   │   │   content='',                                                                                                                                                   │ │
│ │                     │   │   │   │   markup='',                                                                                                                                                    │ │
│ │                     │   │   │   │   info='',                                                                                                                                                      │ │
│ │                     │   │   │   │   meta={},                                                                                                                                                      │ │
│ │                     │   │   │   │   block=False,                                                                                                                                                  │ │
│ │                     │   │   │   │   hidden=False                                                                                                                                                  │ │
│ │                     │   │   │   ),                                                                                                                                                                │ │
│ │                     │   │   │   Token(type='text', tag='', nesting=0, attrs={}, map=None, level=0, children=None, content='ab', markup='', info='', meta={}, block=False, hidden=False)           │ │
│ │                     │   │   ],                                                                                                                                                                    │ │
│ │                     │   │   content='**World**\nab',                                                                                                                                              │ │
│ │                     │   │   markup='',                                                                                                                                                            │ │
│ │                     │   │   info='',                                                                                                                                                              │ │
│ │                     │   │   meta={},                                                                                                                                                              │ │
│ │                     │   │   block=True,                                                                                                                                                           │ │
│ │                     │   │   hidden=False                                                                                                                                                          │ │
│ │                     │   ),                                                                                                                                                                        │ │
│ │                     │   Token(type='paragraph_close', tag='p', nesting=-1, attrs={}, map=None, level=0, children=None, content='', markup='', info='', meta={}, block=True, hidden=False)         │ │
│ │                     ]                                                                                                                                                                             │ │
│ │    updated_source = '### Hello\n**World**\nab'                                                                                                                                                    │ │
│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                                                                                                                                       │
│ E:\PSF\textual_uv_app\.venv\Lib\site-packages\textual\widgets\_markdown.py:472 in _update_from_block                                                                                                  │
│                                                                                                                                                                                                       │
│    469 │   │   │   self.set_content(block._content)                                             ╭───────────────────────────────── locals ─────────────────────────────────╮                          │
│    470 │   │   │   self._copy_context(block)                                                    │ block = MarkdownH3(id='hello-1', name='heading_open', classes='level-0') │                          │
│    471 │   │   else:                                                                            │  self = MarkdownParagraph(name='paragraph_open', classes='level-0')      │                          │
│ ❱  472 │   │   │   await super()._update_from_block(block)                                      ╰──────────────────────────────────────────────────────────────────────────╯                          │
│    473                                                                                                                                                                                                │
│    474                                                                                                                                                                                                │
│    475 class MarkdownBlockQuote(MarkdownBlock):                                                                                                                                                       │
│                                                                                                                                                                                                       │
│ E:\PSF\textual_uv_app\.venv\Lib\site-packages\textual\widgets\_markdown.py:253 in _update_from_block                                                                                                  │
│                                                                                                                                                                                                       │
│    250 │                                                                                        ╭───────────────────────────────── locals ─────────────────────────────────╮                          │
│    251 │   async def _update_from_block(self, block: MarkdownBlock) -> None:                    │ block = MarkdownH3(id='hello-1', name='heading_open', classes='level-0') │                          │
│    252 │   │   await self.remove()                                                              │  self = MarkdownParagraph(name='paragraph_open', classes='level-0')      │                          │
│ ❱  253 │   │   await self._markdown.mount(block)                                                ╰──────────────────────────────────────────────────────────────────────────╯                          │
│    254 │                                                                                                                                                                                              │
│    255 │   async def action_link(self, href: str) -> None:                                                                                                                                            │
│    256 │   │   """Called on link click."""                                                                                                                                                            │
│                                                                                                                                                                                                       │
│ E:\PSF\textual_uv_app\.venv\Lib\site-packages\textual\widget.py:1370 in mount                                                                                                                         │
│                                                                                                                                                                                                       │
│   1367 │   │   else:                                                                            ╭────────────────────────────────────── locals ───────────────────────────────────────╮               │
│   1368 │   │   │   parent = self                                                                │         after = None                                                                │               │
│   1369 │   │                                                                                    │        before = None                                                                │               │
│ ❱ 1370 │   │   mounted = self.app._register(                                                    │  ids_to_mount = ['hello-1']                                                         │               │
│   1371 │   │   │   parent, *widgets, before=insert_before, after=insert_after                   │  insert_after = None                                                                │               │
│   1372 │   │   )                                                                                │ insert_before = None                                                                │               │
│   1373                                                                                          │        parent = Markdown()                                                          │               │
│                                                                                                 │          self = Markdown()                                                          │               │
│                                                                                                 │     widget_id = 'hello-1'                                                           │               │
│                                                                                                 │       widgets = (MarkdownH3(id='hello-1', name='heading_open', classes='level-0'),) │               │
│                                                                                                 ╰─────────────────────────────────────────────────────────────────────────────────────╯               │
│                                                                                                                                                                                                       │
│ E:\PSF\textual_uv_app\.venv\Lib\site-packages\textual\app.py:3459 in _register                                                                                                                        │
│                                                                                                                                                                                                       │
│   3456 │   │   │   │   raise AppError(f"Can't register {widget!r}; expected a Widget instance")                                                                                                       │
│   3457 │   │   │   if widget not in self._registry:                                                                                                                                                   │
│   3458 │   │   │   │   add_new_widget(widget)                                                                                                                                                         │
│ ❱ 3459 │   │   │   │   self._register_child(parent, widget, before, after)                                                                                                                            │
│   3460 │   │   │   │   if widget._nodes:                                                                                                                                                              │
│   3461 │   │   │   │   │   self._register(widget, *widget._nodes, cache=cache)                                                                                                                        │
│   3462 │   │   for widget in new_widgets:                                                                                                                                                             │
│                                                                                                                                                                                                       │
│ ╭───────────────────────────────────────────────────────────────────────────────────────────── locals ──────────────────────────────────────────────────────────────────────────────────────────────╮ │
│ │   add_new_widget = <built-in method append of list object at 0x000001F561A9AAC0>                                                                                                                  │ │
│ │            after = None                                                                                                                                                                           │ │
│ │ apply_stylesheet = <bound method Stylesheet.apply of <Stylesheet [('E:\\PSF\\textual_uv_app\\.venv\\Lib\\site-packages\\textual\\app.py', 'App.DEFAULT_CSS'),                                     │ │
│ │                    ('E:\\PSF\\textual_uv_app\\.venv\\Lib\\site-packages\\textual\\screen.py', 'Screen.DEFAULT_CSS'), ('E:\\PSF\\textual_uv_app\\.venv\\Lib\\site-packages\\textual\\widget.py',   │ │
│ │                    'Widget.DEFAULT_CSS'), ('E:\\PSF\\textual_uv_app\\.venv\\Lib\\site-packages\\textual\\widgets\\_markdown.py', 'Markdown.DEFAULT_CSS'),                                         │ │
│ │                    ('E:\\PSF\\textual_uv_app\\.venv\\Lib\\site-packages\\textual\\widgets\\_toast.py', 'ToastRack.DEFAULT_CSS'),                                                                  │ │
│ │                    ('E:\\PSF\\textual_uv_app\\.venv\\Lib\\site-packages\\textual\\widgets\\_tooltip.py', 'Tooltip.DEFAULT_CSS'),                                                                  │ │
│ │                    ('E:\\PSF\\textual_uv_app\\.venv\\Lib\\site-packages\\textual\\widgets\\_markdown.py', 'MarkdownH3.DEFAULT_CSS'),                                                              │ │
│ │                    ('E:\\PSF\\textual_uv_app\\.venv\\Lib\\site-packages\\textual\\widgets\\_markdown.py', 'MarkdownHeader.DEFAULT_CSS'),                                                          │ │
│ │                    ('E:\\PSF\\textual_uv_app\\.venv\\Lib\\site-packages\\textual\\widgets\\_markdown.py', 'MarkdownBlock.DEFAULT_CSS'),                                                           │ │
│ │                    ('E:\\PSF\\textual_uv_app\\.venv\\Lib\\site-packages\\textual\\widgets\\_static.py', 'Static.DEFAULT_CSS'),                                                                    │ │
│ │                    ('E:\\PSF\\textual_uv_app\\.venv\\Lib\\site-packages\\textual\\widgets\\_markdown.py', 'MarkdownParagraph.DEFAULT_CSS')]>>                                                     │ │
│ │           before = None                                                                                                                                                                           │ │
│ │            cache = {}                                                                                                                                                                             │ │
│ │      new_widgets = [MarkdownH3(id='hello-1', name='heading_open', classes='level-0')]                                                                                                             │ │
│ │           parent = Markdown()                                                                                                                                                                     │ │
│ │             self = MyApp(title='MyApp', classes={'-dark-mode'}, pseudo_classes={'dark', 'focus'})                                                                                                 │ │
│ │           widget = MarkdownH3(id='hello-1', name='heading_open', classes='level-0')                                                                                                               │ │
│ │      widget_list = (MarkdownH3(id='hello-1', name='heading_open', classes='level-0'),)                                                                                                            │ │
│ │          widgets = (MarkdownH3(id='hello-1', name='heading_open', classes='level-0'),)                                                                                                            │ │
│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                                                                                                                                       │
│ E:\PSF\textual_uv_app\.venv\Lib\site-packages\textual\app.py:3404 in _register_child                                                                                                                  │
│                                                                                                                                                                                                       │
│   3401 │   │   │   │   # At this point we appear to not be adding before or after,              ╭──────────────────────────────────────── locals ─────────────────────────────────────────╮           │
│   3402 │   │   │   │   # or we've got a before/after value that really means                    │  after = None                                                                           │           │
│   3403 │   │   │   │   # "please append". So...                                                 │ before = None                                                                           │           │
│ ❱ 3404 │   │   │   │   parent._nodes._append(child)                                             │  child = MarkdownH3(id='hello-1', name='heading_open', classes='level-0')               │           │
│   3405 │   │   │                                                                                │ parent = Markdown()                                                                     │           │
│   3406 │   │   │   # Now that the widget is in the NodeList of its parent, sort out             │   self = MyApp(title='MyApp', classes={'-dark-mode'}, pseudo_classes={'dark', 'focus'}) │           │
│   3407 │   │   │   # the rest of the admin.                                                     ╰─────────────────────────────────────────────────────────────────────────────────────────╯           │
│                                                                                                                                                                                                       │
│ E:\PSF\textual_uv_app\.venv\Lib\site-packages\textual\_node_list.py:125 in _append                                                                                                                    │
│                                                                                                                                                                                                       │
│   122 │   │   │   self._nodes_set.add(widget)                                                                                                                                                         │
│   123 │   │   │   widget_id = widget.id                                                                                                                                                               │
│   124 │   │   │   if widget_id is not None:                                                                                                                                                           │
│ ❱ 125 │   │   │   │   self._ensure_unique_id(widget_id)                                                                                                                                               │
│   126 │   │   │   │   self._nodes_by_id[widget_id] = widget                                                                                                                                           │
│   127 │   │   │   self.updated()                                                                                                                                                                      │
│   128                                                                                                                                                                                                 │
│                                                                                                                                                                                                       │
│ ╭────────────────────────────────────────────────────────────────────────── locals ───────────────────────────────────────────────────────────────────────────╮                                       │
│ │      self = <NodeList [MarkdownH3(id='hello-1', name='heading_open', classes='level-0'), MarkdownH3(id='hello-1', name='heading_open', classes='level-0')]> │                                       │
│ │    widget = MarkdownH3(id='hello-1', name='heading_open', classes='level-0')                                                                                │                                       │
│ │ widget_id = 'hello-1'                                                                                                                                       │                                       │
│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯                                       │
│                                                                                                                                                                                                       │
│ E:\PSF\textual_uv_app\.venv\Lib\site-packages\textual\_node_list.py:154 in _ensure_unique_id                                                                                                          │
│                                                                                                                                                                                                       │
│   151 │   │   │   DuplicateIds: If the given ID is not unique.                                                                                                                                        │
│   152 │   │   """                                                                                                                                                                                     │
│   153 │   │   if widget_id in self._nodes_by_id:                                                                                                                                                      │
│ ❱ 154 │   │   │   raise DuplicateIds(                                                                                                                                                                 │
│   155 │   │   │   │   f"Tried to insert a widget with ID {widget_id!r}, but a widget already e                                                                                                        │
│   156 │   │   │   │   "ensure all child widgets have a unique ID."                                                                                                                                    │
│   157 │   │   │   )                                                                                                                                                                                   │
│                                                                                                                                                                                                       │
│ ╭────────────────────────────────────────────────────────────────────────── locals ───────────────────────────────────────────────────────────────────────────╮                                       │
│ │      self = <NodeList [MarkdownH3(id='hello-1', name='heading_open', classes='level-0'), MarkdownH3(id='hello-1', name='heading_open', classes='level-0')]> │                                       │
│ │ widget_id = 'hello-1'                                                                                                                                       │                                       │
│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯                                       │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
DuplicateIds: Tried to insert a widget with ID 'hello-1', but a widget already exists with that ID (MarkdownH3(id='hello-1', name='heading_open', classes='level-0')); ensure all child widgets have a 
unique ID.

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