Dedent code blocks

This commit is contained in:
Laurenz 2021-10-29 16:26:47 +02:00
parent de6786eb28
commit 1af194f383
4 changed files with 45 additions and 23 deletions

View File

@ -164,8 +164,9 @@ fn unicode_escape(p: &mut Parser, token: UnicodeEscapeToken) -> EcoString {
/// Handle a raw block. /// Handle a raw block.
fn raw(p: &mut Parser, token: RawToken) -> MarkupNode { fn raw(p: &mut Parser, token: RawToken) -> MarkupNode {
let column = p.column(p.next_start());
let span = p.peek_span(); 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 { if !token.terminated {
p.error(span.end, "expected backtick(s)"); p.error(span.end, "expected backtick(s)");
} }

View File

@ -48,10 +48,10 @@ pub fn resolve_hex(sequence: &str) -> Option<char> {
} }
/// Resolve the language tag and trims the raw text. /// 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 { if backticks > 1 {
let (tag, inner) = split_at_lang_tag(text); 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 { RawNode {
span, span,
lang: Ident::new(tag, span.with_end(span.start + tag.len())), 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. /// Trim raw text and splits it into lines.
/// ///
/// Returns whether at least one newline was contained in `raw`. /// 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. // Trims one space at the start.
raw = raw.strip_prefix(' ').unwrap_or(raw); 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 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 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. // Trims a sequence of whitespace followed by a newline at the start.
if lines.first().map_or(false, is_whitespace) { if lines.first().map_or(false, is_whitespace) {
@ -174,39 +183,43 @@ mod tests {
fn test_resolve_raw() { fn test_resolve_raw() {
#[track_caller] #[track_caller]
fn test( fn test(
raw: &str, column: usize,
backticks: usize, backticks: usize,
raw: &str,
lang: Option<&str>, lang: Option<&str>,
text: &str, text: &str,
block: bool, 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.lang.as_deref(), lang);
assert_eq!(node.text, text); assert_eq!(node.text, text);
assert_eq!(node.block, block); assert_eq!(node.block, block);
} }
// Just one backtick. // Just one backtick.
test("py", 1, None, "py", false); test(0, 1, "py", None, "py", false);
test("1\n2", 1, None, "1\n2", false); test(0, 1, "1\n2", None, "1\n2", false);
test("1\r\n2", 1, None, "1\n2", false); test(0, 1, "1\r\n2", None, "1\n2", false);
// More than one backtick with lang tag. // More than one backtick with lang tag.
test("js alert()", 2, Some("js"), "alert()", false); test(0, 2, "js alert()", Some("js"), "alert()", false);
test("py quit(\n\n)", 3, Some("py"), "quit(\n\n)", true); test(0, 3, "py quit(\n\n)", Some("py"), "quit(\n\n)", true);
test("", 2, None, "", false); test(0, 2, "", None, "", false);
// Trimming of whitespace (tested more thoroughly in separate test). // Trimming of whitespace (tested more thoroughly in separate test).
test(" a", 2, None, "a", false); test(0, 2, " a", None, "a", false);
test(" a", 2, None, " a", false); test(0, 2, " a", None, " a", false);
test(" \na", 2, None, "a", true); test(0, 2, " \na", None, "a", true);
// Dedenting
test(2, 3, " def foo():\n bar()", None, "def foo():\n bar()", true);
} }
#[test] #[test]
fn test_trim_raw() { fn test_trim_raw() {
#[track_caller] #[track_caller]
fn test(text: &str, expected: &str) { 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"); test(" hi", "hi");

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -37,13 +37,21 @@ The keyword ```rust let```.
<``` trimmed ```> \ <``` trimmed ```> \
<``` trimmed```> <``` trimmed```>
// Multiline trimming. // Multiline trimming and dedenting.
```py #block[
import this ```py
import this
def hi(): def hi():
print("Hi!") print("Hi!")
``` ```
]
---
// First line is not dedented and leading space is still possible.
``` A
B
C```
--- ---
// Unterminated. // Unterminated.