11/*
2- * SPDX-FileCopyrightText: © 2022-2024 Opencast Software Europe Ltd <https://opencastsoftware.com>
2+ * SPDX-FileCopyrightText: © 2022-2025 Opencast Software Europe Ltd <https://opencastsoftware.com>
33 * SPDX-License-Identifier: Apache-2.0
44 */
55package com .opencastsoftware .prettier4j ;
2121import java .io .StringWriter ;
2222import java .io .Writer ;
2323import java .net .URI ;
24- import java .util .Arrays ;
25- import java .util .Collections ;
24+ import java .util .*;
2625import java .util .function .UnaryOperator ;
2726import java .util .stream .Collectors ;
27+ import java .util .stream .Stream ;
2828
2929import static com .opencastsoftware .prettier4j .Doc .*;
3030import 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\n two\n three" ;
@@ -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,132 @@ void testBracketFlattening() {
217244 assertThat (actual , is (equalTo (expected )));
218245 }
219246
247+ @ Test
248+ void testBracketFlatteningWithAlign () {
249+ // Note: the arguments are aligned with the "functionCall" element because bracket doesn't support alignment.
250+ // TODO: Consider adding a `hangingBracket` combinator that aligns the bracket docs with the starting line position
251+ // and the arguments with the opening bracket doc.
252+ String expected = "functionCall(\n " +
253+ Indents .get (12 )+"a,\n " +
254+ Indents .get (12 )+"b,\n " +
255+ Indents .get (12 )+"c\n " +
256+ Indents .get (12 )+")" ;
257+ String actual = text ("functionCall" )
258+ .append (
259+ Doc .intersperse (
260+ Doc .text ("," ).append (Doc .lineOrSpace ()),
261+ Stream .of ("a" , "b" , "c" ).map (Doc ::text ))
262+ .bracket (0 , Doc .lineOrEmpty (), Doc .text ("(" ), Doc .text (")" ))
263+ .align ())
264+ .render (10 );
265+
266+ assertThat (actual , is (equalTo (expected )));
267+ }
268+
269+ @ Test
270+ void testNestedBracketFlattening () {
271+ String expectedWidth80 = "let x = functionCall(with, args, nestedFunctionCall(with, more, args))" ;
272+ String expectedWidth40 = "let x = functionCall(\n with,\n args,\n nestedFunctionCall(with, more, args)\n )" ;
273+ String expectedWidth20 = "let x = functionCall(\n with,\n args,\n nestedFunctionCall(\n with,\n more,\n args\n )\n )" ;
274+
275+ Doc inputDoc = text ("let" )
276+ .appendSpace (text ("x" ))
277+ .appendSpace (text ("=" ))
278+ .appendSpace (text ("functionCall" )
279+ .append (
280+ intersperse (
281+ text ("," ).append (lineOrSpace ()),
282+ Stream .concat (
283+ Stream .of ("with" , "args" ).map (Doc ::text ),
284+ Stream .of (text ("nestedFunctionCall" )
285+ .append (
286+ intersperse (
287+ text ("," ).append (lineOrSpace ()),
288+ Stream .of ("with" , "more" , "args" ).map (Doc ::text )
289+ ).bracket (2 , lineOrEmpty (), text ("(" ), text (")" ))
290+ ))
291+ )
292+ ).bracket (2 , lineOrEmpty (), text ("(" ), text (")" ))
293+ )
294+ );
295+
296+ assertThat (inputDoc .render (80 ), is (equalTo (expectedWidth80 )));
297+ assertThat (inputDoc .render (40 ), is (equalTo (expectedWidth40 )));
298+ assertThat (inputDoc .render (20 ), is (equalTo (expectedWidth20 )));
299+ }
300+
301+ @ Test
302+ void testNestedBracketFlatteningWithAlign () {
303+ String expectedWidth80 = "let x = functionCall(with, args, nestedFunctionCall(with, more, args))" ;
304+ String expectedWidth40 =
305+ "let x = functionCall(\n " +
306+ Indents .get (20 )+"with,\n " +
307+ Indents .get (20 )+"args,\n " +
308+ Indents .get (20 )+"nestedFunctionCall(\n " +
309+ Indents .get (38 )+"with,\n " +
310+ Indents .get (38 )+"more,\n " +
311+ Indents .get (38 )+ "args\n " +
312+ Indents .get (38 )+")\n " +
313+ Indents .get (20 )+")" ;
314+
315+ Doc inputDoc = text ("let" )
316+ .appendSpace (text ("x" ))
317+ .appendSpace (text ("=" ))
318+ .appendSpace (text ("functionCall" )
319+ .append (align (
320+ intersperse (
321+ text ("," ).append (lineOrSpace ()),
322+ Stream .concat (
323+ Stream .of ("with" , "args" ).map (Doc ::text ),
324+ Stream .of (text ("nestedFunctionCall" )
325+ .append (align (
326+ intersperse (
327+ text ("," ).append (lineOrSpace ()),
328+ Stream .of ("with" , "more" , "args" ).map (Doc ::text )
329+ ).bracket (0 , lineOrEmpty (), text ("(" ), text (")" ))
330+ )))
331+ )
332+ ).bracket (0 , lineOrEmpty (), text ("(" ), text (")" ))
333+ ))
334+ );
335+
336+ assertThat (inputDoc .render (80 ), is (equalTo (expectedWidth80 )));
337+ assertThat (inputDoc .render (40 ), is (equalTo (expectedWidth40 )));
338+ }
339+
340+ @ Test
341+ void testAlignWithMultipleLines () {
342+ String expected =
343+ "∧ ∨ A ∨ B\n " +
344+ " ∨ C\n " +
345+ "∧ ∨ D\n " +
346+ " ∨ E ∧ F\n " +
347+ " ∨ G" ;
348+
349+ // (A ∨ B) ∨ C
350+ List <Doc > left = List .of (
351+ text ("A" ).appendSpace (text ("∨" )).appendSpace (text ("B" )),
352+ text ("C" )
353+ );
354+
355+ // D ∨ (E ∧ F) ∨ G
356+ List <Doc > right = List .of (
357+ text ("D" ),
358+ text ("E" ).appendSpace (text ("∧" )).appendSpace (text ("F" )),
359+ text ("G" )
360+ );
361+
362+ Doc alignedLeft = align (text ("∨" ).appendSpace (intersperse (line ().append (text ("∨ " )), left )));
363+ Doc leftJunctions = text ("∧" ).appendSpace (alignedLeft );
364+
365+ Doc alignedRight = align (text ("∨" ).appendSpace (intersperse (line ().append (text ("∨ " )), right )));
366+ Doc rightJunctions = text ("∧" ).appendSpace (alignedRight );
367+
368+ String result = leftJunctions .appendLine (rightJunctions ).render (80 );
369+
370+ assertThat (result , is (equalTo (expected )));
371+ }
372+
220373 @ Test
221374 void testMarginWithLineSeparator () {
222375 assertThrows (IllegalArgumentException .class , () -> {
@@ -1549,7 +1702,7 @@ void testEquals() {
15491702 EqualsVerifier
15501703 .forClasses (
15511704 Text .class , Append .class , Param .class , WrapText .class ,
1552- Alternatives .class , Indent .class , Margin .class , Link .class ,
1705+ Alternatives .class , Indent .class , Align . class , Margin .class , Link .class ,
15531706 LineOr .class , Escape .class , Styled .class , OpenLink .class )
15541707 .usingGetClass ()
15551708 .withPrefabValues (Doc .class , left , right )
@@ -1562,7 +1715,7 @@ void testToString() {
15621715 .forClasses (
15631716 Text .class , Append .class , Margin .class ,
15641717 WrapText .class , Alternatives .class , Indent .class ,
1565- LineOr .class , Empty .class , Escape .class ,
1718+ LineOr .class , Empty .class , Escape .class , Align . class ,
15661719 Link .class , OpenLink .class , CloseLink .class ,
15671720 Reset .class , Styled .class , Param .class )
15681721 .withPrefabValue (Doc .class , docsWithParams ().sample ())
0 commit comments