Skip to content

Commit 5f846ce

Browse files
WIP: Add Doc.align that aligns subsequent line breaks to the current line position
1 parent 40a9cdf commit 5f846ce

File tree

2 files changed

+139
-4
lines changed
  • src
    • main/java/com/opencastsoftware/prettier4j
    • test/java/com/opencastsoftware/prettier4j

2 files changed

+139
-4
lines changed

src/main/java/com/opencastsoftware/prettier4j/Doc.java

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -446,10 +446,10 @@ public String text() {
446446
@Override
447447
public Doc append(Doc other) {
448448
// By string concat equivalency law
449-
if (other instanceof Text) {
449+
/*if (other instanceof Text) {
450450
Text otherText = (Text) other;
451451
return text(this.text() + otherText.text());
452-
} else if (other instanceof Empty) {
452+
} else*/ if (other instanceof Empty) {
453453
// By left unit law
454454
return this;
455455
}
@@ -849,6 +849,67 @@ public String toString() {
849849
}
850850
}
851851

852+
/**
853+
* Represents an aligned {@link Doc}.
854+
*
855+
* Sets the indentation for line breaks within its inner {@link Doc} at the current line position.
856+
*/
857+
public static class Align extends Doc {
858+
private final Doc doc;
859+
860+
Align(Doc doc) {
861+
this.doc = doc;
862+
}
863+
864+
public Doc doc() {
865+
return doc;
866+
}
867+
868+
@Override
869+
Doc flatten() {
870+
return new Align(doc.flatten());
871+
}
872+
873+
@Override
874+
boolean hasParams() {
875+
return doc.hasParams();
876+
}
877+
878+
@Override
879+
boolean hasLineSeparators() {
880+
return doc.hasLineSeparators();
881+
}
882+
883+
@Override
884+
public Doc bind(String name, Doc value) {
885+
return new Align(doc.bind(name, value));
886+
}
887+
888+
@Override
889+
public Doc bind(Map<String, Doc> bindings) {
890+
return new Align(doc.bind(bindings));
891+
}
892+
893+
@Override
894+
public boolean equals(Object o) {
895+
if (o == null || getClass() != o.getClass()) return false;
896+
Align align = (Align) o;
897+
return Objects.equals(doc, align.doc);
898+
}
899+
900+
@Override
901+
public int hashCode() {
902+
return Objects.hashCode(doc);
903+
}
904+
905+
@Override
906+
public String toString() {
907+
return "Align[" +
908+
"doc=" + doc +
909+
']';
910+
}
911+
}
912+
852913
/**
853914
* Represents a line break which cannot be flattened into a more compact layout.
854915
*/
@@ -1615,6 +1676,16 @@ public static Doc indent(int indent, Doc doc) {
16151676
return new Indent(indent, doc);
16161677
}
16171678

1679+
/**
1680+
* Align subsequent lines of the current {@link Doc} to the current position in the line.
1681+
*
1682+
* @param doc the input document
1683+
* @return the aligned document.
1684+
*/
1685+
public static Doc align(Doc doc) {
1686+
return new Align(doc);
1687+
}
1688+
16181689
/**
16191690
* Apply the margin document {@code margin} to the current {@link Doc}, emitting the
16201691
* margin at the start of every new line from the start of this document until the
@@ -2073,6 +2144,11 @@ private static int layoutEntry(RenderOptions options, Deque<Entry> inQueue, Queu
20732144
Indent indentDoc = (Indent) entryDoc;
20742145
int newIndent = entryIndent + indentDoc.indent();
20752146
inQueue.addFirst(entry(newIndent, entryMargin, indentDoc.doc()));
2147+
} else if (entryDoc instanceof Align) {
2148+
// Eliminate Align
2149+
Align alignDoc = (Align) entryDoc;
2150+
int newIndent = Math.max(0, position - entryIndent);
2151+
inQueue.addFirst(entry(newIndent, entryMargin, alignDoc.doc()));
20762152
} else if (entryDoc instanceof Margin) {
20772153
// Eliminate Margin
20782154
Margin marginDoc = (Margin) entryDoc;

src/test/java/com/opencastsoftware/prettier4j/DocTest.java

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
import java.io.StringWriter;
2222
import java.io.Writer;
2323
import java.net.URI;
24-
import java.util.Arrays;
25-
import java.util.Collections;
24+
import java.util.*;
2625
import java.util.function.UnaryOperator;
2726
import java.util.stream.Collectors;
27+
import java.util.stream.Stream;
2828

2929
import static com.opencastsoftware.prettier4j.Doc.*;
3030
import static org.hamcrest.MatcherAssert.assertThat;
@@ -113,6 +113,15 @@ void testAppendLineFlattening() {
113113
assertThat(actual, is(equalTo(expected)));
114114
}
115115

116+
@Test
117+
void testAppendLineWithAlign() {
118+
String expected = "one two\n three";
119+
String actual = text("one")
120+
.appendSpace(group(align(text("two").appendLine(text("three")))))
121+
.render(30);
122+
assertThat(actual, is(equalTo(expected)));
123+
}
124+
116125
@Test
117126
void testAppendLineOrSpace() {
118127
String expected = "one two three";
@@ -123,6 +132,15 @@ void testAppendLineOrSpace() {
123132
assertThat(actual, is(equalTo(expected)));
124133
}
125134

135+
@Test
136+
void testAppendLineOrSpaceWithAlign() {
137+
String expected = "one two three";
138+
String actual = text("one")
139+
.appendSpace(group(align(text("two").appendLineOrSpace(text("three")))))
140+
.render(30);
141+
assertThat(actual, is(equalTo(expected)));
142+
}
143+
126144
@Test
127145
void testAppendLineOrSpaceFlattening() {
128146
String expected = "one\ntwo\nthree";
@@ -133,6 +151,15 @@ void testAppendLineOrSpaceFlattening() {
133151
assertThat(actual, is(equalTo(expected)));
134152
}
135153

154+
@Test
155+
void testAppendLineOrSpaceWithAlignFlattening() {
156+
String expected = "one two\n three";
157+
String actual = text("one")
158+
.appendSpace(group(align(text("two").appendLineOrSpace(text("three")))))
159+
.render(10);
160+
assertThat(actual, is(equalTo(expected)));
161+
}
162+
136163
@Test
137164
void testAppendLineOrEmpty() {
138165
String expected = "onetwothree";
@@ -217,6 +244,38 @@ void testBracketFlattening() {
217244
assertThat(actual, is(equalTo(expected)));
218245
}
219246

247+
@Test
248+
void testNestedBracketFlattening() {
249+
String expectedWidth80 = "let x = functionCall(with, args, nestedFunctionCall(with, more, args))";
250+
String expectedWidth40 = "let x = functionCall(\n with,\n args,\n nestedFunctionCall(with, more, args)\n)";
251+
String expectedWidth20 = "let x = functionCall(\n with,\n args,\n nestedFunctionCall(\n with,\n more,\n args\n )\n)";
252+
253+
Doc inputDoc = text("let")
254+
.appendSpace(text("x"))
255+
.appendSpace(text("="))
256+
.appendSpace(text("functionCall")
257+
.append(
258+
intersperse(
259+
text(",").append(lineOrSpace()),
260+
Stream.concat(
261+
Stream.of("with", "args").map(Doc::text),
262+
Stream.of(text("nestedFunctionCall")
263+
.append(
264+
intersperse(
265+
text(",").append(lineOrSpace()),
266+
Stream.of("with", "more", "args").map(Doc::text)
267+
).bracket(2, lineOrEmpty(), text("("), text(")"))
268+
))
269+
)
270+
).bracket(2, lineOrEmpty(), text("("), text(")"))
271+
)
272+
);
273+
274+
assertThat(inputDoc.render(80), is(equalTo(expectedWidth80)));
275+
assertThat(inputDoc.render(40), is(equalTo(expectedWidth40)));
276+
assertThat(inputDoc.render(20), is(equalTo(expectedWidth20)));
277+
}
278+
220279
@Test
221280
void testMarginWithLineSeparator() {
222281
assertThrows(IllegalArgumentException.class, () -> {

0 commit comments

Comments
 (0)