aboutsummaryrefslogtreecommitdiffstats
path: root/src/util/infer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/infer.rs')
-rw-r--r--src/util/infer.rs192
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
+}