diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 8466d99f6..307874232 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -164,8 +164,9 @@ fn unicode_escape(p: &mut Parser, token: UnicodeEscapeToken) -> EcoString { /// Handle a raw block. fn raw(p: &mut Parser, token: RawToken) -> MarkupNode { + let column = p.column(p.next_start()); let span = p.peek_span(); - let raw = resolve::resolve_raw(span, token.text, token.backticks); + let raw = resolve::resolve_raw(span, column, token.backticks, token.text); if !token.terminated { p.error(span.end, "expected backtick(s)"); } diff --git a/src/parse/resolve.rs b/src/parse/resolve.rs index 80e3bbc00..51ddac8f3 100644 --- a/src/parse/resolve.rs +++ b/src/parse/resolve.rs @@ -48,10 +48,10 @@ pub fn resolve_hex(sequence: &str) -> Option { } /// Resolve the language tag and trims the raw text. -pub fn resolve_raw(span: Span, text: &str, backticks: usize) -> RawNode { +pub fn resolve_raw(span: Span, column: usize, backticks: usize, text: &str) -> RawNode { if backticks > 1 { let (tag, inner) = split_at_lang_tag(text); - let (text, block) = trim_and_split_raw(inner); + let (text, block) = trim_and_split_raw(column, inner); RawNode { span, lang: Ident::new(tag, span.with_end(span.start + tag.len())), @@ -80,7 +80,7 @@ fn split_at_lang_tag(raw: &str) -> (&str, &str) { /// Trim raw text and splits it into lines. /// /// Returns whether at least one newline was contained in `raw`. -fn trim_and_split_raw(mut raw: &str) -> (String, bool) { +fn trim_and_split_raw(column: usize, mut raw: &str) -> (String, bool) { // Trims one space at the start. raw = raw.strip_prefix(' ').unwrap_or(raw); @@ -90,8 +90,17 @@ fn trim_and_split_raw(mut raw: &str) -> (String, bool) { } let mut lines = split_lines(raw); - let is_whitespace = |line: &String| line.chars().all(char::is_whitespace); + + // Dedent based on column, but not for the first line. + for line in lines.iter_mut().skip(1) { + let offset = line.chars().take(column).take_while(|c| c.is_whitespace()).count(); + if offset > 0 { + line.drain(.. offset); + } + } + let had_newline = lines.len() > 1; + let is_whitespace = |line: &String| line.chars().all(char::is_whitespace); // Trims a sequence of whitespace followed by a newline at the start. if lines.first().map_or(false, is_whitespace) { @@ -174,39 +183,43 @@ mod tests { fn test_resolve_raw() { #[track_caller] fn test( - raw: &str, + column: usize, backticks: usize, + raw: &str, lang: Option<&str>, text: &str, block: bool, ) { - let node = resolve_raw(Span::detached(), raw, backticks); + let node = resolve_raw(Span::detached(), column, backticks, raw); assert_eq!(node.lang.as_deref(), lang); assert_eq!(node.text, text); assert_eq!(node.block, block); } // Just one backtick. - test("py", 1, None, "py", false); - test("1\n2", 1, None, "1\n2", false); - test("1\r\n2", 1, None, "1\n2", false); + test(0, 1, "py", None, "py", false); + test(0, 1, "1\n2", None, "1\n2", false); + test(0, 1, "1\r\n2", None, "1\n2", false); // More than one backtick with lang tag. - test("js alert()", 2, Some("js"), "alert()", false); - test("py quit(\n\n)", 3, Some("py"), "quit(\n\n)", true); - test("♥", 2, None, "", false); + test(0, 2, "js alert()", Some("js"), "alert()", false); + test(0, 3, "py quit(\n\n)", Some("py"), "quit(\n\n)", true); + test(0, 2, "♥", None, "", false); // Trimming of whitespace (tested more thoroughly in separate test). - test(" a", 2, None, "a", false); - test(" a", 2, None, " a", false); - test(" \na", 2, None, "a", true); + test(0, 2, " a", None, "a", false); + test(0, 2, " a", None, " a", false); + test(0, 2, " \na", None, "a", true); + + // Dedenting + test(2, 3, " def foo():\n bar()", None, "def foo():\n bar()", true); } #[test] fn test_trim_raw() { #[track_caller] fn test(text: &str, expected: &str) { - assert_eq!(trim_and_split_raw(text).0, expected); + assert_eq!(trim_and_split_raw(0, text).0, expected); } test(" hi", "hi"); diff --git a/tests/ref/markup/raw.png b/tests/ref/markup/raw.png index 4da6a43e1..bd8b811ad 100644 Binary files a/tests/ref/markup/raw.png and b/tests/ref/markup/raw.png differ diff --git a/tests/typ/markup/raw.typ b/tests/typ/markup/raw.typ index 005d413ed..d48432f73 100644 --- a/tests/typ/markup/raw.typ +++ b/tests/typ/markup/raw.typ @@ -37,13 +37,21 @@ The keyword ```rust let```. <``` trimmed ```> \ <``` trimmed```> -// Multiline trimming. -```py -import this +// Multiline trimming and dedenting. +#block[ + ```py + import this -def hi(): - print("Hi!") -``` + def hi(): + print("Hi!") + ``` +] + +--- +// First line is not dedented and leading space is still possible. + ``` A + B + C``` --- // Unterminated.