fix: broken tasks extraction
All checks were successful
Release / Build and Release (push) Successful in 6s
Continuous Integration / Lint, Check & Test (push) Successful in 1m33s
Continuous Integration / Build Package (push) Successful in 1m43s

This commit is contained in:
Konstantin Fickel 2026-04-13 19:26:09 +02:00
parent 10f4ae282a
commit e15e6f1053
Signed by: kfickel
GPG key ID: A793722F9933C1A5

View file

@ -12,6 +12,8 @@ struct BlockInfo {
end_line: usize,
block_type: BlockType,
events: Vec<Event<'static>>,
/// Nested list items contained within this block (for ListItem blocks with sub-lists).
nested_items: Vec<BlockInfo>,
}
#[derive(Debug, Clone, PartialEq)]
@ -110,12 +112,14 @@ pub fn parse_markdown_file(file_name: &str, file_content: &str) -> StreamFile {
fn collect_blocks(content: &str, parser: Parser) -> Vec<BlockInfo> {
let mut blocks = Vec::new();
let mut current_block: Option<BlockInfo> = None;
let _current_events: Vec<Event<'static>> = Vec::new();
let mut depth = 0;
let mut list_items: Vec<BlockInfo> = Vec::new();
let mut in_list = false;
let mut list_start_line = 0;
// Stack for nested lists: (saved current_block, saved list_items, saved list_start_line)
let mut list_nesting_stack: Vec<(Option<BlockInfo>, Vec<BlockInfo>, usize)> = Vec::new();
// Pre-compute line starts for offset-to-line mapping
let line_starts: Vec<usize> = std::iter::once(0)
.chain(content.match_indices('\n').map(|(i, _)| i + 1))
@ -135,6 +139,7 @@ fn collect_blocks(content: &str, parser: Parser) -> Vec<BlockInfo> {
end_line: line,
block_type: BlockType::Paragraph,
events: Vec::new(),
nested_items: Vec::new(),
});
}
depth += 1;
@ -166,6 +171,7 @@ fn collect_blocks(content: &str, parser: Parser) -> Vec<BlockInfo> {
end_line: line,
block_type: BlockType::Heading(heading_level),
events: Vec::new(),
nested_items: Vec::new(),
});
}
depth += 1;
@ -186,7 +192,15 @@ fn collect_blocks(content: &str, parser: Parser) -> Vec<BlockInfo> {
}
}
Event::Start(Tag::List(_)) => {
if !in_list {
if in_list {
// Entering a nested list: save current list item and collected items
list_nesting_stack.push((
current_block.take(),
std::mem::take(&mut list_items),
list_start_line,
));
list_start_line = line;
} else {
in_list = true;
list_start_line = line;
list_items.clear();
@ -195,7 +209,18 @@ fn collect_blocks(content: &str, parser: Parser) -> Vec<BlockInfo> {
}
Event::End(TagEnd::List(_)) => {
depth -= 1;
if depth == 0 && in_list {
if let Some((parent_block, parent_items, parent_start_line)) =
list_nesting_stack.pop()
{
// Nested list ended: attach collected items as nested children of parent item
let nested = std::mem::take(&mut list_items);
list_start_line = parent_start_line;
list_items = parent_items;
current_block = parent_block.map(|mut item| {
item.nested_items = nested;
item
});
} else if depth == 0 && in_list {
in_list = false;
// Create a list block containing all list items
if !list_items.is_empty() {
@ -204,6 +229,7 @@ fn collect_blocks(content: &str, parser: Parser) -> Vec<BlockInfo> {
end_line: line,
block_type: BlockType::List,
events: vec![], // List events are handled through list_items
nested_items: vec![],
});
// Store list items for later processing
for item in list_items.drain(..) {
@ -222,6 +248,7 @@ fn collect_blocks(content: &str, parser: Parser) -> Vec<BlockInfo> {
end_line: line,
block_type: BlockType::ListItem,
events: Vec::new(),
nested_items: Vec::new(),
});
}
}
@ -240,6 +267,7 @@ fn collect_blocks(content: &str, parser: Parser) -> Vec<BlockInfo> {
end_line: line,
block_type: BlockType::CodeBlock,
events: Vec::new(),
nested_items: Vec::new(),
});
}
depth += 1;
@ -507,13 +535,21 @@ fn parse_single_block_shard(
}
}
BlockType::List | BlockType::ListItem => {
// List handling is complex - for now, extract any markers/tags
let (markers, tags) = extract_block_markers_and_tags(block);
if markers.is_empty() {
// Recursively build child shards from nested list items
let children: Vec<Shard> = block
.nested_items
.iter()
.filter_map(|item| {
let (child, _) = parse_single_block_shard(item, item.start_line, item.end_line);
child
})
.collect();
if markers.is_empty() && children.is_empty() {
(None, tags)
} else {
(
Some(build_shard(start_line, end_line, markers, tags, vec![])),
Some(build_shard(start_line, end_line, markers, tags, children)),
vec![],
)
}
@ -716,6 +752,26 @@ mod tests {
);
}
#[test]
fn test_parse_nested_list_creates_three_shards() {
let content = "* @Task 1\n * @Task 2\n* @Task 3";
let result = parse_markdown_file(&make_file_name(), content);
let root = result.shard.unwrap();
// The root shard should have two top-level children: @Task 1 and @Task 3
assert_eq!(root.children.len(), 2, "expected 2 top-level shards");
let task1 = &root.children[0];
let task3 = &root.children[1];
// @Task 1 must carry its marker and contain @Task 2 as a child
assert_eq!(task1.markers, vec!["Task"], "@Task 1 marker");
assert_eq!(task1.children.len(), 1, "@Task 1 should have one child");
let task2 = &task1.children[0];
assert_eq!(task2.markers, vec!["Task"], "@Task 2 marker");
assert!(task2.children.is_empty(), "@Task 2 should have no children");
// @Task 3 is a sibling of @Task 1
assert_eq!(task3.markers, vec!["Task"], "@Task 3 marker");
assert!(task3.children.is_empty(), "@Task 3 should have no children");
}
#[test]
fn test_parse_continues_looking_for_markers_after_first_link_marker() {
let result = parse_markdown_file(