Skip to content

Commit 176f503

Browse files
committed
fix(converters): show checked/unchecked status for checklists
1 parent 8ba2e06 commit 176f503

File tree

2 files changed

+76
-29
lines changed

2 files changed

+76
-29
lines changed

converters/html/src/list.rs

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use std::io::Write;
22

3-
use acdc_parser::{DescriptionList, DescriptionListItem, ListItem, OrderedList, UnorderedList};
3+
use acdc_parser::{
4+
DescriptionList, DescriptionListItem, ListItem, ListItemCheckedStatus, OrderedList,
5+
UnorderedList,
6+
};
47

58
use crate::{Processor, Render, RenderOptions};
69

@@ -41,6 +44,7 @@ impl Render for OrderedList {
4144
}
4245

4346
/// Render nested list items hierarchically
47+
#[tracing::instrument(skip(w, processor))]
4448
fn render_nested_list_items<W: Write>(
4549
items: &[ListItem],
4650
w: &mut W,
@@ -62,21 +66,45 @@ fn render_nested_list_items<W: Write>(
6266
// Render item at current level
6367
writeln!(w, "<li>")?;
6468
writeln!(w, "<p>")?;
69+
render_checked_status(item.checked.as_ref(), w)?;
6570
crate::inlines::render_inlines(&item.content, w, processor, options)?;
6671
writeln!(w, "</p>")?;
6772

6873
// Check if next items are nested (higher level)
6974
if i + 1 < items.len() && items[i + 1].level > expected_level {
7075
// Find all items at the next level
7176
let next_level = items[i + 1].level;
77+
let inner_item = &items[i + 1];
7278

7379
// Open nested list
7480
if is_ordered {
75-
writeln!(w, "<div class=\"olist arabic\">")?;
76-
writeln!(w, "<ol class=\"arabic\">")?;
81+
writeln!(w, "<div class=\"olist arabic")?;
82+
if inner_item.checked.is_some() {
83+
writeln!(w, " checklist\">")?;
84+
} else {
85+
writeln!(w, "\">")?;
86+
}
87+
88+
write!(w, "<ol class=\"arabic")?;
89+
if inner_item.checked.is_some() {
90+
writeln!(w, " checklist\">")?;
91+
} else {
92+
writeln!(w, "\">")?;
93+
}
7794
} else {
78-
writeln!(w, "<div class=\"ulist\">")?;
79-
writeln!(w, "<ul>")?;
95+
// check if the item is a checkbox item
96+
write!(w, "<div class=\"ulist")?;
97+
if inner_item.checked.is_some() {
98+
writeln!(w, " checklist\">")?;
99+
} else {
100+
writeln!(w, "\">")?;
101+
}
102+
write!(w, "<ul")?;
103+
if inner_item.checked.is_some() {
104+
writeln!(w, " class=\"checklist\">")?;
105+
} else {
106+
writeln!(w, ">")?;
107+
}
80108
}
81109

82110
// Recursively render nested items
@@ -111,11 +139,7 @@ fn render_nested_list_items<W: Write>(
111139
} else {
112140
// Item at higher level than expected, shouldn't happen in well-formed input
113141
// but handle gracefully by treating as same level
114-
writeln!(w, "<li>")?;
115-
writeln!(w, "<p>")?;
116-
crate::inlines::render_inlines(&item.content, w, processor, options)?;
117-
writeln!(w, "</p>")?;
118-
writeln!(w, "</li>")?;
142+
item.render(w, processor, options)?;
119143
i += 1;
120144
}
121145
}
@@ -133,6 +157,7 @@ impl Render for ListItem {
133157
) -> Result<(), Self::Error> {
134158
writeln!(w, "<li>")?;
135159
writeln!(w, "<p>")?;
160+
render_checked_status(self.checked.as_ref(), w)?;
136161
crate::inlines::render_inlines(&self.content, w, processor, options)?;
137162
writeln!(w, "</p>")?;
138163
writeln!(w, "</li>")?;
@@ -185,3 +210,20 @@ impl Render for DescriptionListItem {
185210
Ok(())
186211
}
187212
}
213+
214+
#[tracing::instrument(skip(w))]
215+
fn render_checked_status<W: Write>(
216+
checked: Option<&ListItemCheckedStatus>,
217+
w: &mut W,
218+
) -> Result<(), crate::Error> {
219+
match checked {
220+
Some(ListItemCheckedStatus::Checked) => {
221+
write!(w, "&#10003; ")?; // Checked box
222+
}
223+
Some(ListItemCheckedStatus::Unchecked) => {
224+
write!(w, "&#10063; ")?; // Unchecked box
225+
}
226+
None => {}
227+
}
228+
Ok(())
229+
}

converters/terminal/src/list.rs

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ use crate::{Processor, Render};
1616
///
1717
/// This helper function renders a collection of inline nodes, inserting a space
1818
/// between each node. It uses a peekable iterator to avoid adding a trailing space.
19+
#[tracing::instrument(skip(w, processor))]
1920
fn render_nodes_with_spaces<W: Write, N>(
2021
nodes: &[N],
2122
w: &mut W,
2223
processor: &Processor,
2324
) -> Result<(), crate::Error>
2425
where
25-
N: Render<Error = crate::Error>,
26+
N: Render<Error = crate::Error> + std::fmt::Debug,
2627
{
2728
let mut iter = nodes.iter().peekable();
2829
while let Some(node) = iter.next() {
@@ -38,13 +39,14 @@ where
3839
///
3940
/// This helper function renders inline nodes to a buffer, converts to a string,
4041
/// trims whitespace, and applies italic styling for terminal output.
42+
#[tracing::instrument(skip(w, processor))]
4143
fn render_styled_title<W: Write, N>(
4244
title: &[N],
4345
w: &mut W,
4446
processor: &Processor,
4547
) -> Result<(), crate::Error>
4648
where
47-
N: Render<Error = crate::Error>,
49+
N: Render<Error = crate::Error> + std::fmt::Debug,
4850
{
4951
if !title.is_empty() {
5052
let mut inner = std::io::BufWriter::new(Vec::new());
@@ -63,6 +65,7 @@ where
6365
}
6466

6567
/// Render nested list items with proper indentation based on their level
68+
#[tracing::instrument(skip(w, processor))]
6669
fn render_nested_list_items<W: Write>(
6770
items: &[ListItem],
6871
w: &mut W,
@@ -124,6 +127,7 @@ fn render_nested_list_items<W: Write>(
124127
}
125128

126129
/// Render a single list item with the specified indentation
130+
#[tracing::instrument(skip(w, processor))]
127131
fn render_list_item_with_indent<W: Write>(
128132
item: &ListItem,
129133
w: &mut W,
@@ -142,15 +146,7 @@ fn render_list_item_with_indent<W: Write>(
142146
write!(w, "*")?;
143147
}
144148

145-
if let Some(checked) = &item.checked {
146-
write!(w, " ")?;
147-
if checked == &ListItemCheckedStatus::Checked {
148-
w.queue(PrintStyledContent("✔".bold()))?;
149-
} else {
150-
w.queue(PrintStyledContent("✘".bold()))?;
151-
}
152-
}
153-
149+
render_checked_status(item.checked.as_ref(), w)?;
154150
write!(w, " ")?;
155151

156152
// Render each node with a space between them
@@ -159,6 +155,22 @@ fn render_list_item_with_indent<W: Write>(
159155
Ok(())
160156
}
161157

158+
#[tracing::instrument(skip(w))]
159+
fn render_checked_status<W: Write>(
160+
checked: Option<&ListItemCheckedStatus>,
161+
w: &mut W,
162+
) -> Result<(), crate::Error> {
163+
if let Some(checked) = checked {
164+
write!(w, " ")?;
165+
if checked == &ListItemCheckedStatus::Checked {
166+
w.queue(PrintStyledContent("[✔]".bold()))?;
167+
} else {
168+
w.queue(PrintStyledContent("[ ]".bold()))?;
169+
}
170+
}
171+
Ok(())
172+
}
173+
162174
impl Render for UnorderedList {
163175
type Error = crate::Error;
164176

@@ -198,14 +210,7 @@ impl Render for ListItem {
198210

199211
fn render<W: Write>(&self, w: &mut W, processor: &Processor) -> Result<(), Self::Error> {
200212
write!(w, "{}", self.marker)?;
201-
if let Some(checked) = &self.checked {
202-
write!(w, " ")?;
203-
if checked == &ListItemCheckedStatus::Checked {
204-
w.queue(PrintStyledContent("✔".bold()))?;
205-
} else {
206-
w.queue(PrintStyledContent("✘".bold()))?;
207-
}
208-
}
213+
render_checked_status(self.checked.as_ref(), w)?;
209214
write!(w, " ")?;
210215
// render each node with a space between them
211216
render_nodes_with_spaces(&self.content, w, processor)?;

0 commit comments

Comments
 (0)