Skip to content

Commit f89adc2

Browse files
jdmonacoclaude
andcommitted
Add GFM task list checkbox handling to list_item renderer
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 21168a3 commit f89adc2

1 file changed

Lines changed: 50 additions & 0 deletions

File tree

mdformat_space_control/plugin.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
115152
def _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"^(&#32;)+", "", text)
186+
text = text.lstrip()
187+
return f"[{checkmark}] {text}"
188+
139189
return text
140190

141191

0 commit comments

Comments
 (0)