Skip to content

Commit 462c759

Browse files
committed
Add sourcepos for the task item symbol
1 parent 6ea7235 commit 462c759

File tree

8 files changed

+92
-30
lines changed

8 files changed

+92
-30
lines changed

src/cm.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::str;
66
use crate::ctype::{isalpha, isdigit, ispunct, ispunct_char, isspace, isspace_char};
77
use crate::nodes::{
88
ListDelimType, ListType, Node, NodeAlert, NodeCodeBlock, NodeHeading, NodeHtmlBlock, NodeLink,
9-
NodeList, NodeMath, NodeValue, NodeWikiLink, TableAlignment,
9+
NodeList, NodeMath, NodeTaskItem, NodeValue, NodeWikiLink, TableAlignment,
1010
};
1111
use crate::parser::options::{Options, Plugins, WikiLinksMode};
1212
#[cfg(feature = "phoenix_heex")]
@@ -471,7 +471,7 @@ impl<'a, 'o, 'c, 'w> CommonMarkFormatter<'a, 'o, 'c, 'w> {
471471
}
472472
}
473473
NodeValue::Emph => self.format_emph(node)?,
474-
NodeValue::TaskItem(symbol) => self.format_task_item(symbol, node, entering)?,
474+
NodeValue::TaskItem(ref nti) => self.format_task_item(nti, node, entering)?,
475475
NodeValue::Strikethrough => self.format_strikethrough()?,
476476
NodeValue::Highlight => self.format_highlight()?,
477477
NodeValue::Superscript => self.format_superscript()?,
@@ -855,7 +855,7 @@ impl<'a, 'o, 'c, 'w> CommonMarkFormatter<'a, 'o, 'c, 'w> {
855855

856856
fn format_task_item(
857857
&mut self,
858-
symbol: Option<char>,
858+
nti: &NodeTaskItem,
859859
node: Node<'a>,
860860
entering: bool,
861861
) -> fmt::Result {
@@ -867,7 +867,7 @@ impl<'a, 'o, 'c, 'w> CommonMarkFormatter<'a, 'o, 'c, 'w> {
867867
self.format_item(node, entering)?;
868868
}
869869
if entering {
870-
write!(self, "[{}] ", symbol.unwrap_or(' '))?;
870+
write!(self, "[{}] ", nti.symbol.unwrap_or(' '))?;
871871
}
872872
Ok(())
873873
}

src/html.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ use crate::ctype::isspace;
2020
use crate::nodes::NodeShortCode;
2121
use crate::nodes::{
2222
ListType, Node, NodeAlert, NodeCode, NodeCodeBlock, NodeFootnoteDefinition,
23-
NodeFootnoteReference, NodeHeading, NodeHtmlBlock, NodeLink, NodeList, NodeMath, NodeValue,
24-
NodeWikiLink, TableAlignment,
23+
NodeFootnoteReference, NodeHeading, NodeHtmlBlock, NodeLink, NodeList, NodeMath, NodeTaskItem,
24+
NodeValue, NodeWikiLink, TableAlignment,
2525
};
2626
use crate::parser::options::{Options, Plugins};
2727
use crate::{node_matches, scanners};
@@ -444,7 +444,7 @@ pub fn format_node_default<T>(
444444
NodeValue::Table(_) => render_table(context, node, entering),
445445
NodeValue::TableCell => render_table_cell(context, node, entering),
446446
NodeValue::TableRow(thead) => render_table_row(context, node, entering, thead),
447-
NodeValue::TaskItem(symbol) => render_task_item(context, node, entering, symbol),
447+
NodeValue::TaskItem(ref nti) => render_task_item(context, node, entering, nti),
448448

449449
// Extensions
450450
NodeValue::Alert(ref alert) => render_alert(context, node, entering, alert),
@@ -1204,7 +1204,7 @@ fn render_task_item<T>(
12041204
context: &mut Context<T>,
12051205
node: Node<'_>,
12061206
entering: bool,
1207-
symbol: Option<char>,
1207+
nti: &NodeTaskItem,
12081208
) -> Result<ChildRendering, fmt::Error> {
12091209
let write_li = node
12101210
.parent()
@@ -1228,7 +1228,7 @@ fn render_task_item<T>(
12281228
if context.options.render.tasklist_classes {
12291229
context.write_str(" class=\"task-list-item-checkbox\"")?;
12301230
}
1231-
if symbol.is_some() {
1231+
if nti.symbol.is_some() {
12321232
context.write_str(" checked=\"\"")?;
12331233
}
12341234
context.write_str(" disabled=\"\" /> ")?;

src/nodes.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ pub enum NodeValue {
135135
/// **Block**. [Task list item](https://github.github.com/gfm/#task-list-items-extension-).
136136
/// The value is the symbol that was used in the brackets to mark a task item as checked, or
137137
/// `None` if the item is unchecked.
138-
TaskItem(Option<char>),
138+
TaskItem(NodeTaskItem),
139139

140140
/// **Inline**. A [soft line break](https://github.github.com/gfm/#soft-line-breaks). If
141141
/// the `hardbreaks` option is set in `Options` during formatting, it will be formatted
@@ -294,6 +294,17 @@ pub struct NodeTable {
294294
pub num_nonempty_cells: usize,
295295
}
296296

297+
/// A task list item's contents, and where it was found
298+
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
299+
pub struct NodeTaskItem {
300+
/// The symbol within the brackets used to mark the task item as checked,
301+
/// or `None` if unchecked.
302+
pub symbol: Option<char>,
303+
304+
/// The source position of the symbol itself.
305+
pub symbol_sourcepos: Sourcepos,
306+
}
307+
297308
/// An inline [code span](https://github.github.com/gfm/#code-spans).
298309
#[derive(Default, Debug, Clone, PartialEq, Eq)]
299310
pub struct NodeCode {

src/parser/mod.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::node_matches;
2020
use crate::nodes::{
2121
self, AlertType, Ast, ListDelimType, ListType, Node, NodeAlert, NodeCodeBlock,
2222
NodeDescriptionItem, NodeFootnoteDefinition, NodeHeading, NodeHtmlBlock, NodeList,
23-
NodeMultilineBlockQuote, NodeValue, Sourcepos,
23+
NodeMultilineBlockQuote, NodeTaskItem, NodeValue, Sourcepos,
2424
};
2525
use crate::parser::inlines::RefMap;
2626
pub use crate::parser::options::Options;
@@ -2308,7 +2308,7 @@ where
23082308
sourcepos: &mut Sourcepos,
23092309
spx: &mut Spx,
23102310
) {
2311-
let (end, matched) = match scanners::tasklist(text) {
2311+
let (end, matched, symbol_range) = match scanners::tasklist(text) {
23122312
Some(p) => p,
23132313
None => return,
23142314
};
@@ -2328,6 +2328,13 @@ where
23282328
return;
23292329
}
23302330

2331+
let symbol_sourcepos: Sourcepos = (
2332+
sourcepos.start.column_add(symbol_range.start as isize),
2333+
// `- 1`: symbol_range is end-exclusive, but sourcepos are inclusive.
2334+
sourcepos.start.column_add(symbol_range.end as isize - 1),
2335+
)
2336+
.into();
2337+
23312338
let parent = node.parent().unwrap();
23322339

23332340
if node_matches!(parent, NodeValue::TableCell) {
@@ -2349,7 +2356,10 @@ where
23492356
parent.prepend(
23502357
self.arena.alloc(
23512358
Ast::new_with_sourcepos(
2352-
NodeValue::TaskItem(if symbol == ' ' { None } else { Some(symbol) }),
2359+
NodeValue::TaskItem(NodeTaskItem {
2360+
symbol: if symbol == ' ' { None } else { Some(symbol) },
2361+
symbol_sourcepos,
2362+
}),
23532363
*sourcepos,
23542364
)
23552365
.into(),
@@ -2387,8 +2397,10 @@ where
23872397
parent.data_mut().sourcepos.start.column = adjust;
23882398
}
23892399

2390-
grandparent.data_mut().value =
2391-
NodeValue::TaskItem(if symbol == ' ' { None } else { Some(symbol) });
2400+
grandparent.data_mut().value = NodeValue::TaskItem(NodeTaskItem {
2401+
symbol: if symbol == ' ' { None } else { Some(symbol) },
2402+
symbol_sourcepos,
2403+
});
23922404

23932405
if let NodeValue::List(ref mut list) = &mut great_grandparent.data_mut().value {
23942406
list.is_task_list = true;

src/scanners.re

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -470,10 +470,11 @@ pub fn close_multiline_block_quote_fence(s: &str) -> Option<usize> {
470470
*/
471471
}
472472

473-
// Returns both the length of the match, and the tasklist item contents.
474-
// It is not guaranteed to be one byte, or one "character" long; the caller must ascertain
475-
// its fitness for purpose.
476-
pub fn tasklist(s: &str) -> Option<(usize, &str)> {
473+
// Returns the length of the match, the tasklist item contents, and the range of
474+
// the tasklist item match.
475+
// The match is not guaranteed to be one byte or one "character" long; the
476+
// caller must ascertain its fitness for purpose.
477+
pub fn tasklist(s: &str) -> Option<(usize, &str, std::ops::Range<usize>)> {
477478
let mut cursor = 0;
478479
let mut marker = 0;
479480
let len = s.len();
@@ -491,7 +492,7 @@ pub fn tasklist(s: &str) -> Option<(usize, &str)> {
491492
if cursor == len + 1 {
492493
cursor -= 1;
493494
}
494-
return Some((cursor, &s[t1..t2]));
495+
return Some((cursor, &s[t1..t2], t1..t2));
495496
}
496497
* { return None; }
497498
*/

src/scanners.rs

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/tests/tasklist.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::*;
2+
use pretty_assertions::assert_eq;
23

34
#[test]
45
fn tasklist() {
@@ -516,3 +517,38 @@ fn tasklist_relaxed_unicode() {
516517
])
517518
);
518519
}
520+
521+
#[test]
522+
fn tasklist_symbol_sourcepos() {
523+
let mut options = Options::default();
524+
options.extension.tasklist = true;
525+
526+
let arena = Arena::new();
527+
let md = r#"Henlo!
528+
529+
> - [x] well
530+
>
531+
> 1. * > 3. [ ] true
532+
533+
+ [x] ok
534+
"#;
535+
536+
let root = parse_document(&arena, md, &options);
537+
let sourceposes = root
538+
.descendants()
539+
.filter_map(|n| {
540+
let NodeValue::TaskItem(ref nti) = n.data().value else {
541+
return None;
542+
};
543+
Some(nti.symbol_sourcepos)
544+
})
545+
.collect::<Vec<_>>();
546+
assert_eq!(
547+
sourceposes,
548+
vec![
549+
(3, 6, 3, 6).into(),
550+
(5, 16, 5, 16).into(),
551+
(7, 4, 7, 4).into()
552+
]
553+
);
554+
}

src/xml.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -271,11 +271,12 @@ impl<'o, 'c> XmlFormatter<'o, 'c> {
271271
self.escape(&nfr.name)?;
272272
self.output.write_str("\"")?;
273273
}
274-
NodeValue::TaskItem(Some(_)) => {
275-
self.output.write_str(" completed=\"true\"")?;
276-
}
277-
NodeValue::TaskItem(None) => {
278-
self.output.write_str(" completed=\"false\"")?;
274+
NodeValue::TaskItem(ref nti) => {
275+
if nti.symbol.is_some() {
276+
self.output.write_str(" completed=\"true\"")?;
277+
} else {
278+
self.output.write_str(" completed=\"false\"")?;
279+
}
279280
}
280281
#[cfg(feature = "shortcodes")]
281282
NodeValue::ShortCode(ref nsc) => {

0 commit comments

Comments
 (0)