From b0fa8ef108d0e6a3ae32af906e7cf815fe322045 Mon Sep 17 00:00:00 2001 From: Matias Fontanini Date: Fri, 3 Nov 2023 14:09:44 -0700 Subject: [PATCH] Offset source positions by hand when input has front matter --- src/builder.rs | 2 +- src/markdown/elements.rs | 26 +++++++++++++ src/markdown/parse.rs | 83 +++++++++++++++++++++++++++++++++------- 3 files changed, 97 insertions(+), 14 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 53aa0977..024280bf 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -225,7 +225,7 @@ impl<'a> PresentationBuilder<'a> { } let comment = match comment.parse::() { Ok(comment) => comment, - Err(error) => return Err(BuildError::CommandParse { line: source_position.line, error }), + Err(error) => return Err(BuildError::CommandParse { line: source_position.start.line + 1, error }), }; match comment { CommentCommand::Pause => self.process_pause(), diff --git a/src/markdown/elements.rs b/src/markdown/elements.rs index b8274b46..f7300dc5 100644 --- a/src/markdown/elements.rs +++ b/src/markdown/elements.rs @@ -47,7 +47,33 @@ pub(crate) enum MarkdownElement { #[derive(Clone, Debug, Default)] pub(crate) struct SourcePosition { + pub(crate) start: LineColumn, +} + +impl SourcePosition { + pub(crate) fn offset_lines(&self, offset: usize) -> SourcePosition { + let mut output = self.clone(); + output.start.line += offset; + output + } +} + +impl From for SourcePosition { + fn from(position: comrak::nodes::Sourcepos) -> Self { + Self { start: position.start.into() } + } +} + +#[derive(Clone, Debug, Default)] +pub(crate) struct LineColumn { pub(crate) line: usize, + pub(crate) column: usize, +} + +impl From for LineColumn { + fn from(position: comrak::nodes::LineColumn) -> Self { + Self { line: position.line, column: position.column } + } } /// The components that make up a paragraph. diff --git a/src/markdown/parse.rs b/src/markdown/parse.rs index 5ab21bf2..17a422ad 100644 --- a/src/markdown/parse.rs +++ b/src/markdown/parse.rs @@ -1,4 +1,3 @@ -use super::elements::SourcePosition; use crate::{ markdown::elements::{ Code, CodeFlags, CodeLanguage, ListItem, ListItemType, MarkdownElement, ParagraphElement, StyledText, Table, @@ -19,6 +18,8 @@ use std::{ mem, }; +use super::elements::SourcePosition; + /// The result of parsing a markdown file. pub(crate) type ParseResult = Result; @@ -52,13 +53,39 @@ impl<'a> MarkdownParser<'a> { pub(crate) fn parse(&self, contents: &str) -> ParseResult> { let node = parse_document(self.arena, contents, &self.options); let mut elements = Vec::new(); + let mut lines_offset = 0; for node in node.children() { - let element = Self::parse_node(node)?; - elements.extend(element); + let mut parsed_elements = + Self::parse_node(node).map_err(|e| ParseError::new(e.kind, e.sourcepos.offset_lines(lines_offset)))?; + if let Some(MarkdownElement::FrontMatter(contents)) = parsed_elements.first() { + lines_offset += contents.lines().count() + 2; + } + // comrak ignores the lines in the front matter so we need to offset this ourselves. + Self::adjust_source_positions(parsed_elements.iter_mut(), lines_offset); + elements.extend(parsed_elements); } Ok(elements) } + fn adjust_source_positions<'b>(elements: impl Iterator, lines_offset: usize) { + for element in elements { + let position = match element { + MarkdownElement::FrontMatter(_) + | MarkdownElement::SetexHeading { .. } + | MarkdownElement::Heading { .. } + | MarkdownElement::Paragraph(_) + | MarkdownElement::Image(_) + | MarkdownElement::List(_) + | MarkdownElement::Code(_) + | MarkdownElement::Table(_) + | MarkdownElement::ThematicBreak + | MarkdownElement::BlockQuote(_) => continue, + MarkdownElement::Comment { source_position, .. } => source_position, + }; + *position = position.offset_lines(lines_offset); + } + } + fn parse_node(node: &'a AstNode<'a>) -> ParseResult> { let data = node.data.borrow(); let element = match &data.value { @@ -98,10 +125,7 @@ impl<'a> MarkdownParser<'a> { } let block = &block[start_tag.len()..]; let block = &block[0..block.len() - end_tag.len()]; - Ok(MarkdownElement::Comment { - comment: block.into(), - source_position: SourcePosition { line: sourcepos.start.line }, - }) + Ok(MarkdownElement::Comment { comment: block.into(), source_position: sourcepos.into() }) } fn parse_block_quote(node: &'a AstNode<'a>) -> ParseResult { @@ -403,7 +427,7 @@ pub struct ParseError { pub(crate) kind: ParseErrorKind, /// The position in the source file this error originated from. - pub(crate) sourcepos: Sourcepos, + pub(crate) sourcepos: SourcePosition, } impl Display for ParseError { @@ -413,8 +437,8 @@ impl Display for ParseError { } impl ParseError { - fn new(kind: ParseErrorKind, sourcepos: Sourcepos) -> Self { - Self { kind, sourcepos } + fn new>(kind: ParseErrorKind, sourcepos: S) -> Self { + Self { kind, sourcepos: sourcepos.into() } } } @@ -448,7 +472,7 @@ impl Display for ParseErrorKind { } impl ParseErrorKind { - fn with_sourcepos(self, sourcepos: Sourcepos) -> ParseError { + fn with_sourcepos>(self, sourcepos: S) -> ParseError { ParseError::new(self, sourcepos) } } @@ -497,9 +521,8 @@ impl Identifier for NodeValue { #[cfg(test)] mod test { - use std::path::Path; - use super::*; + use std::path::Path; fn parse_single(input: &str) -> MarkdownElement { let arena = Arena::new(); @@ -761,4 +784,38 @@ bye assert_eq!(parsed.len(), 3); assert!(matches!(parsed[1], MarkdownElement::ThematicBreak)); } + + #[test] + fn error_lines_offset_by_front_matter() { + let input = r"--- +hi +mom +--- + +* ![](potato.png) +"; + let arena = Arena::new(); + let result = MarkdownParser::new(&arena).parse(input); + let Err(e) = result else { + panic!("parsing didn't fail"); + }; + assert_eq!(e.sourcepos.start.line, 5); + assert_eq!(e.sourcepos.start.column, 3); + } + + #[test] + fn comment_lines_offset_by_front_matter() { + let parsed = parse_all( + r"--- +hi +mom +--- + + +", + ); + let MarkdownElement::Comment { source_position, .. } = &parsed[1] else { panic!("not a comment") }; + assert_eq!(source_position.start.line, 5); + assert_eq!(source_position.start.column, 1); + } }