@@ -112,12 +112,55 @@ def _get_indent(default_width: int) -> tuple[str, int]:
112112 return (" " * size , size )
113113
114114
115+ def _is_task_list_item (node : RenderTreeNode ) -> bool :
116+ """Check if a list item is a GFM task list item.
117+
118+ When mdformat-gfm is installed, the tasklists markdown-it plugin annotates
119+ task list items with a 'task-list-item' CSS class.
120+ """
121+ classes = node .attrs .get ("class" , "" )
122+ return isinstance (classes , str ) and "task-list-item" in classes
123+
124+
125+ def _extract_task_checkbox (node : RenderTreeNode ) -> str | None :
126+ """Extract and remove the task checkbox HTML from a task list item node.
127+
128+ The tasklists markdown-it plugin injects an HTML <input> element as the
129+ first child of the inline token. This function extracts the checked state
130+ and removes the HTML node so it doesn't appear in the rendered output.
131+
132+ Returns:
133+ "x" for checked, " " for unchecked, or None if not a task list item.
134+ """
135+ if not _is_task_list_item (node ):
136+ return None
137+
138+ paragraph_node = node .children [0 ]
139+ inline_node = paragraph_node .children [0 ]
140+ if inline_node .type != "inline" or not inline_node .children :
141+ return None
142+
143+ html_inline_node = inline_node .children [0 ]
144+ if 'class="task-list-item-checkbox"' not in getattr (html_inline_node , "content" , "" ):
145+ return None
146+
147+ # Remove the HTML checkbox node before rendering
148+ inline_node .children .remove (html_inline_node )
149+ return "x" if 'checked="checked"' in html_inline_node .content else " "
150+
151+
115152def _render_list_item (node : RenderTreeNode , context : RenderContext ) -> str :
116153 """Render a list item with appropriate tight/loose formatting.
117154
118155 For single-paragraph items in tight lists, use tight formatting.
119156 For multi-paragraph items, preserve loose formatting.
157+
158+ Handles GFM task list items when mdformat-gfm is installed: extracts the
159+ checkbox state from the HTML token and prepends [x] or [ ] to the output.
120160 """
161+ # Extract task checkbox before rendering (mutates the node tree)
162+ checkmark = _extract_task_checkbox (node )
163+
121164 # Check if this item has multiple paragraphs
122165 if has_multiple_paragraphs (node ):
123166 # Use loose list formatting for multi-paragraph items
@@ -136,6 +179,13 @@ def _render_list_item(node: RenderTreeNode, context: RenderContext) -> str:
136179
137180 if not text .strip ():
138181 return ""
182+
183+ if checkmark is not None :
184+ # Strip leading space entities left after removing the HTML checkbox node
185+ text = re .sub (r"^( )+" , "" , text )
186+ text = text .lstrip ()
187+ return f"[{ checkmark } ] { text } "
188+
139189 return text
140190
141191
0 commit comments