fix: broken tasks extraction
This commit is contained in:
parent
10f4ae282a
commit
e15e6f1053
1 changed files with 62 additions and 6 deletions
|
|
@ -12,6 +12,8 @@ struct BlockInfo {
|
||||||
end_line: usize,
|
end_line: usize,
|
||||||
block_type: BlockType,
|
block_type: BlockType,
|
||||||
events: Vec<Event<'static>>,
|
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)]
|
#[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> {
|
fn collect_blocks(content: &str, parser: Parser) -> Vec<BlockInfo> {
|
||||||
let mut blocks = Vec::new();
|
let mut blocks = Vec::new();
|
||||||
let mut current_block: Option<BlockInfo> = None;
|
let mut current_block: Option<BlockInfo> = None;
|
||||||
let _current_events: Vec<Event<'static>> = Vec::new();
|
|
||||||
let mut depth = 0;
|
let mut depth = 0;
|
||||||
let mut list_items: Vec<BlockInfo> = Vec::new();
|
let mut list_items: Vec<BlockInfo> = Vec::new();
|
||||||
let mut in_list = false;
|
let mut in_list = false;
|
||||||
let mut list_start_line = 0;
|
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
|
// Pre-compute line starts for offset-to-line mapping
|
||||||
let line_starts: Vec<usize> = std::iter::once(0)
|
let line_starts: Vec<usize> = std::iter::once(0)
|
||||||
.chain(content.match_indices('\n').map(|(i, _)| i + 1))
|
.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,
|
end_line: line,
|
||||||
block_type: BlockType::Paragraph,
|
block_type: BlockType::Paragraph,
|
||||||
events: Vec::new(),
|
events: Vec::new(),
|
||||||
|
nested_items: Vec::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
depth += 1;
|
depth += 1;
|
||||||
|
|
@ -166,6 +171,7 @@ fn collect_blocks(content: &str, parser: Parser) -> Vec<BlockInfo> {
|
||||||
end_line: line,
|
end_line: line,
|
||||||
block_type: BlockType::Heading(heading_level),
|
block_type: BlockType::Heading(heading_level),
|
||||||
events: Vec::new(),
|
events: Vec::new(),
|
||||||
|
nested_items: Vec::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
depth += 1;
|
depth += 1;
|
||||||
|
|
@ -186,7 +192,15 @@ fn collect_blocks(content: &str, parser: Parser) -> Vec<BlockInfo> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Start(Tag::List(_)) => {
|
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;
|
in_list = true;
|
||||||
list_start_line = line;
|
list_start_line = line;
|
||||||
list_items.clear();
|
list_items.clear();
|
||||||
|
|
@ -195,7 +209,18 @@ fn collect_blocks(content: &str, parser: Parser) -> Vec<BlockInfo> {
|
||||||
}
|
}
|
||||||
Event::End(TagEnd::List(_)) => {
|
Event::End(TagEnd::List(_)) => {
|
||||||
depth -= 1;
|
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;
|
in_list = false;
|
||||||
// Create a list block containing all list items
|
// Create a list block containing all list items
|
||||||
if !list_items.is_empty() {
|
if !list_items.is_empty() {
|
||||||
|
|
@ -204,6 +229,7 @@ fn collect_blocks(content: &str, parser: Parser) -> Vec<BlockInfo> {
|
||||||
end_line: line,
|
end_line: line,
|
||||||
block_type: BlockType::List,
|
block_type: BlockType::List,
|
||||||
events: vec![], // List events are handled through list_items
|
events: vec![], // List events are handled through list_items
|
||||||
|
nested_items: vec![],
|
||||||
});
|
});
|
||||||
// Store list items for later processing
|
// Store list items for later processing
|
||||||
for item in list_items.drain(..) {
|
for item in list_items.drain(..) {
|
||||||
|
|
@ -222,6 +248,7 @@ fn collect_blocks(content: &str, parser: Parser) -> Vec<BlockInfo> {
|
||||||
end_line: line,
|
end_line: line,
|
||||||
block_type: BlockType::ListItem,
|
block_type: BlockType::ListItem,
|
||||||
events: Vec::new(),
|
events: Vec::new(),
|
||||||
|
nested_items: Vec::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -240,6 +267,7 @@ fn collect_blocks(content: &str, parser: Parser) -> Vec<BlockInfo> {
|
||||||
end_line: line,
|
end_line: line,
|
||||||
block_type: BlockType::CodeBlock,
|
block_type: BlockType::CodeBlock,
|
||||||
events: Vec::new(),
|
events: Vec::new(),
|
||||||
|
nested_items: Vec::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
depth += 1;
|
depth += 1;
|
||||||
|
|
@ -507,13 +535,21 @@ fn parse_single_block_shard(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BlockType::List | BlockType::ListItem => {
|
BlockType::List | BlockType::ListItem => {
|
||||||
// List handling is complex - for now, extract any markers/tags
|
|
||||||
let (markers, tags) = extract_block_markers_and_tags(block);
|
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)
|
(None, tags)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
Some(build_shard(start_line, end_line, markers, tags, vec![])),
|
Some(build_shard(start_line, end_line, markers, tags, children)),
|
||||||
vec![],
|
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]
|
#[test]
|
||||||
fn test_parse_continues_looking_for_markers_after_first_link_marker() {
|
fn test_parse_continues_looking_for_markers_after_first_link_marker() {
|
||||||
let result = parse_markdown_file(
|
let result = parse_markdown_file(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue