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