diff options
Diffstat (limited to 'src/util/infer.rs')
-rw-r--r-- | src/util/infer.rs | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/src/util/infer.rs b/src/util/infer.rs new file mode 100644 index 0000000..0973913 --- /dev/null +++ b/src/util/infer.rs @@ -0,0 +1,192 @@ +//! Infer things from events. +//! +//! Used to share between `to_html` and `to_mdast`. + +use crate::event::{Event, Kind, Name}; +use crate::mdast::AlignKind; +use alloc::{vec, vec::Vec}; + +/// Figure out if a list is spread or not. +/// +/// When `include_items: true` is passed, infers whether the list as a whole +/// is “loose”. +pub fn list_loose(events: &[Event], mut index: usize, include_items: bool) -> bool { + let mut balance = 0; + let name = &events[index].name; + debug_assert!( + matches!(name, Name::ListOrdered | Name::ListUnordered), + "expected list" + ); + + while index < events.len() { + let event = &events[index]; + + if event.kind == Kind::Enter { + balance += 1; + + if include_items + && balance == 2 + && event.name == Name::ListItem + && list_item_loose(events, index) + { + return true; + } + } else { + balance -= 1; + + if balance == 1 && event.name == Name::BlankLineEnding { + // Blank line directly after item, which is just a prefix. + // + // ```markdown + // > | -␊ + // ^ + // | - a + // ``` + let mut at_empty_list_item = false; + // Blank line at block quote prefix: + // + // ```markdown + // > | * >␊ + // ^ + // | * a + // ``` + let mut at_empty_block_quote = false; + + // List. + let mut before = index - 2; + + if events[before].name == Name::ListItem { + before -= 1; + + if events[before].name == Name::SpaceOrTab { + before -= 2; + } + + if events[before].name == Name::BlockQuote + && events[before - 1].name == Name::BlockQuotePrefix + { + at_empty_block_quote = true; + } else if events[before].name == Name::ListItemPrefix { + at_empty_list_item = true; + } + } + + if !at_empty_list_item && !at_empty_block_quote { + return true; + } + } + + // Done. + if balance == 0 && event.name == *name { + break; + } + } + + index += 1; + } + + false +} + +/// Figure out if an item is spread or not. +pub fn list_item_loose(events: &[Event], mut index: usize) -> bool { + debug_assert!( + matches!(events[index].name, Name::ListItem), + "expected list item" + ); + let mut balance = 0; + + while index < events.len() { + let event = &events[index]; + + if event.kind == Kind::Enter { + balance += 1; + } else { + balance -= 1; + + if balance == 1 && event.name == Name::BlankLineEnding { + // Blank line directly after a prefix: + // + // ```markdown + // > | -␊ + // ^ + // | a + // ``` + let mut at_prefix = false; + + // List item. + let mut before = index - 2; + + if events[before].name == Name::SpaceOrTab { + before -= 2; + } + + if events[before].name == Name::ListItemPrefix { + at_prefix = true; + } + + if !at_prefix { + return true; + } + } + + // Done. + if balance == 0 && event.name == Name::ListItem { + break; + } + } + + index += 1; + } + + false +} + +/// Figure out the alignment of a GFM table. +pub fn gfm_table_align(events: &[Event], mut index: usize) -> Vec<AlignKind> { + debug_assert!( + matches!(events[index].name, Name::GfmTable), + "expected table" + ); + let mut in_delimiter_row = false; + let mut align = vec![]; + + while index < events.len() { + let event = &events[index]; + + if in_delimiter_row { + if event.kind == Kind::Enter { + // Start of alignment value: set a new column. + if event.name == Name::GfmTableDelimiterCellValue { + align.push(if events[index + 1].name == Name::GfmTableDelimiterMarker { + AlignKind::Left + } else { + AlignKind::None + }); + } + } else { + // End of alignment value: change the column. + if event.name == Name::GfmTableDelimiterCellValue { + if events[index - 1].name == Name::GfmTableDelimiterMarker { + let align_index = align.len() - 1; + align[align_index] = if align[align_index] == AlignKind::Left { + AlignKind::Center + } else { + AlignKind::Right + } + } + } + // Done! + else if event.name == Name::GfmTableDelimiterRow { + break; + } + } + } else if event.kind == Kind::Enter && event.name == Name::GfmTableDelimiterRow { + in_delimiter_row = true; + } + + index += 1; + } + + align +} |