Skip to content

Commit b2f149a

Browse files
committed
Normalize tabs in multilines to four spaces when formatting
1 parent fd544cb commit b2f149a

16 files changed

Lines changed: 355 additions & 138 deletions

File tree

src/domain/engine.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -273,12 +273,8 @@ fn render_expression_parts(expr: &Expression) -> (String, Vec<String>) {
273273
if let Expression::Execution(func, _) = expr {
274274
let mut body = Vec::new();
275275
for param in &func.parameters {
276-
if let Expression::Multiline(_, lines, _) = param {
277-
body.extend(
278-
lines
279-
.iter()
280-
.map(|s| s.to_string()),
281-
);
276+
if let Expression::Multiline(multiline, _) = param {
277+
body.push(multiline.content());
282278
}
283279
}
284280
if !body.is_empty() {
@@ -350,7 +346,7 @@ fn render_expression(expr: &Expression) -> String {
350346
args.join(", ")
351347
)
352348
}
353-
Expression::Multiline(_, lines, _) => lines.join("\n"),
349+
Expression::Multiline(multiline, _) => multiline.content(),
354350
Expression::Variable(id, _) => id
355351
.value
356352
.to_string(),

src/formatting/formatter.rs

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ impl<'i> Formatter<'i> {
480480
return Vec::new();
481481
}
482482
match expr {
483-
Expression::Multiline(_, _, _) => {
483+
Expression::Multiline(_, _) => {
484484
// These are not inline, caller should handle specially
485485
Vec::new()
486486
}
@@ -913,7 +913,7 @@ impl<'i> Formatter<'i> {
913913
line = self.builder();
914914
line.add_word(Syntax::Structure, "}");
915915
}
916-
Expression::Multiline(_, _, _) => {
916+
Expression::Multiline(_, _) => {
917917
line.flush();
918918
self.add_fragment_reference(Syntax::Structure, "{");
919919
self.increase(4);
@@ -930,7 +930,7 @@ impl<'i> Formatter<'i> {
930930
.parameters
931931
.iter()
932932
.any(|p| {
933-
if let Expression::Multiline(_, _, _) = p {
933+
if let Expression::Multiline(_, _) = p {
934934
true
935935
} else {
936936
false
@@ -1284,28 +1284,21 @@ impl<'i> Formatter<'i> {
12841284
self.add_fragment_reference(Syntax::Quote, "'");
12851285
}
12861286
Expression::Number(numeric, _) => self.append_numeric(numeric),
1287-
Expression::Multiline(lang, lines, _) => {
1287+
Expression::Multiline(multiline, _) => {
12881288
self.append_char('\n');
12891289

12901290
self.indent();
12911291
self.add_fragment_reference(Syntax::Quote, "```");
1292-
if let Some(which) = lang {
1292+
if let Some(which) = multiline.language {
12931293
self.add_fragment_reference(Syntax::Language, which);
12941294
}
12951295
self.append_char('\n');
12961296

12971297
self.increase(4);
1298-
for line in lines {
1299-
self.indent();
1300-
// Break multiline content into words for wrapping
1301-
for (i, word) in line
1302-
.split_ascii_whitespace()
1303-
.enumerate()
1304-
{
1305-
if i > 0 {
1306-
self.add_fragment_reference(Syntax::Multiline, " ");
1307-
}
1308-
self.add_fragment_reference(Syntax::Multiline, word);
1298+
for line in multiline.lines() {
1299+
if !line.is_empty() {
1300+
self.indent();
1301+
self.add_fragment(Syntax::Multiline, line);
13091302
}
13101303
self.append_char('\n');
13111304
}
@@ -1482,7 +1475,7 @@ impl<'i> Formatter<'i> {
14821475

14831476
let mut has_multiline = false;
14841477
for parameter in &function.parameters {
1485-
if let Expression::Multiline(_, _, _) = parameter {
1478+
if let Expression::Multiline(_, _) = parameter {
14861479
has_multiline = true;
14871480
break;
14881481
}

src/language/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// Types representing the Technique language surface syntax
22

33
mod error;
4+
mod multiline;
45
mod quantity;
56
mod types;
67

78
// Re-export all public symbols
89
pub use error::*;
10+
pub use multiline::*;
911
pub use quantity::*;
1012
pub use types::*;

src/language/multiline.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//! The content of a ``` fence
2+
3+
use std::borrow::Cow;
4+
5+
#[derive(Debug, Eq, PartialEq)]
6+
pub struct Multiline<'i> {
7+
pub language: Option<&'i str>,
8+
pub lines: Vec<&'i str>,
9+
}
10+
11+
impl<'i> Multiline<'i> {
12+
/// The lines as they were present in the input source but with the leading block
13+
/// indentation removed.
14+
pub fn lines(&self) -> impl Iterator<Item = Cow<'i, str>> + '_ {
15+
let common = self
16+
.lines
17+
.iter()
18+
.filter(|line| {
19+
!line
20+
.trim_ascii()
21+
.is_empty()
22+
})
23+
.map(|line| indent(line))
24+
.min()
25+
.unwrap_or(0);
26+
27+
self.lines
28+
.iter()
29+
.map(move |line| strip(expand(line), common))
30+
}
31+
32+
/// The lines, joined, suitable for use by builtin functions.
33+
pub fn content(&self) -> String {
34+
self.lines()
35+
.collect::<Vec<Cow<'i, str>>>()
36+
.join("\n")
37+
}
38+
}
39+
40+
fn indent(line: &str) -> usize {
41+
let mut column = 0;
42+
43+
for c in line.chars() {
44+
match c {
45+
' ' => column += 1,
46+
'\t' => column = (column / 4 + 1) * 4,
47+
_ => break,
48+
}
49+
}
50+
51+
column
52+
}
53+
54+
/// Expand any tabs present into 4 spaces. If the author needs an actual tab
55+
/// character in a sting literal they can use the `\t` escape.
56+
fn expand(line: &str) -> Cow<'_, str> {
57+
if !line.contains('\t') {
58+
return Cow::Borrowed(line);
59+
}
60+
61+
let mut result = String::with_capacity(line.len() + 8);
62+
let mut column = 0;
63+
64+
for c in line.chars() {
65+
if c == '\t' {
66+
let stop = (column / 4 + 1) * 4;
67+
result.push_str(&" ".repeat(stop - column));
68+
column = stop;
69+
} else {
70+
result.push(c);
71+
column += 1;
72+
}
73+
}
74+
75+
result.into()
76+
}
77+
78+
/// Strip leading block indendation from a line.
79+
fn strip(line: Cow<'_, str>, common: usize) -> Cow<'_, str> {
80+
match line {
81+
Cow::Borrowed(text) => Cow::Borrowed(&text[common.min(text.len())..]),
82+
Cow::Owned(mut text) => {
83+
text.drain(..common.min(text.len()));
84+
Cow::Owned(text)
85+
}
86+
}
87+
}

src/language/types.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Types representing an Abstract Syntax Tree for the Technique language
22
3+
use crate::language::multiline::Multiline;
4+
use crate::language::quantity::Quantity;
35
use crate::regex::*;
46

57
/// Byte range within the original source. `length` excludes trailing whitespace.
@@ -523,7 +525,7 @@ pub enum Expression<'i> {
523525
String(Vec<Piece<'i>>, Span),
524526
Response(&'i str, Span),
525527
Number(Numeric<'i>, Span),
526-
Multiline(Option<&'i str>, Vec<&'i str>, Span),
528+
Multiline(Multiline<'i>, Span),
527529
Repeat(Box<Expression<'i>>, Span),
528530
Foreach(Vec<Identifier<'i>>, Box<Expression<'i>>, Span),
529531
Within(Box<Expression<'i>>, Span),
@@ -547,9 +549,7 @@ impl PartialEq for Expression<'_> {
547549
(Expression::String(a, _), Expression::String(b, _)) => a == b,
548550
(Expression::Response(a, _), Expression::Response(b, _)) => a == b,
549551
(Expression::Number(a, _), Expression::Number(b, _)) => a == b,
550-
(Expression::Multiline(a1, a2, _), Expression::Multiline(b1, b2, _)) => {
551-
a1 == b1 && a2 == b2
552-
}
552+
(Expression::Multiline(a, _), Expression::Multiline(b, _)) => a == b,
553553
(Expression::Repeat(a, _), Expression::Repeat(b, _)) => a == b,
554554
(Expression::Foreach(a1, a2, _), Expression::Foreach(b1, b2, _)) => {
555555
a1 == b1 && a2 == b2
@@ -579,8 +579,6 @@ pub enum Numeric<'i> {
579579
Scientific(Quantity<'i>),
580580
}
581581

582-
pub use crate::language::quantity::Quantity;
583-
584582
// the validate functions all need to have start and end anchors, which seems
585583
// like it should be abstracted away.
586584

src/linking/linker.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ fn link_operation<'i>(
125125
Operation::Variable(_, _)
126126
| Operation::Number(_, _)
127127
| Operation::Response(_, _)
128-
| Operation::Multiline(_, _, _)
128+
| Operation::Verbatim(_, _)
129129
| Operation::Prose(_, _)
130130
| Operation::Hole(_)
131131
| Operation::Unit(_) => {}

0 commit comments

Comments
 (0)