⭐️ A friendly language for building type-safe, scalable systems!

Change formatting of lists

To allow forcing a list on multiple lines based on the presence of a
trailing comma

authored by giacomocavalieri.me and committed by Louis Pilfold 26587999 776251d1

Changed files
+152 -32
compiler-core
src
+148 -30
compiler-core/src/format.rs
··· 578 578 }; 579 579 } 580 580 581 - let comma = match elements.first() { 582 - // If the list is made of non-simple constants and it gets too long we want to 583 - // have each record on its own line instead of trying to fit as much 584 - // as possible in each line. For example: 585 - // 586 - // [ 587 - // Some("wibble wobble"), 588 - // None, 589 - // Some("wobble wibble"), 590 - // ] 591 - // 592 - Some(el) if !el.is_simple() => break_(",", ", "), 593 - // For simple constants(String, Int, Float), if we have to break the list we still try to 594 - // fit as much as possible into a single line instead of putting 595 - // each item on its own separate line. For example: 596 - // 597 - // [ 598 - // 1, 2, 3, 4, 599 - // 5, 6, 7, 600 - // ] 601 - // 602 - Some(_) | None => flex_break(",", ", "), 581 + let list_packing = 582 + self.list_items_packing(elements, None, |element| element.is_simple(), *location); 583 + let comma = match list_packing { 584 + ListItemsPacking::FitMultiplePerLine => flex_break(",", ", "), 585 + ListItemsPacking::FitOnePerLine | ListItemsPacking::BreakOnePerLine => { 586 + break_(",", ", ") 587 + } 603 588 }; 604 589 605 590 let elements = join( ··· 615 600 // of moving those out of the list. 616 601 // Otherwise those would be moved out of the list. 617 602 let comments = self.pop_comments(location.end); 618 - match printed_comments(comments, false) { 619 - None => doc.append(break_(",", "")).append("]").group(), 603 + let doc = match printed_comments(comments, false) { 604 + None => doc.append(break_(",", "")).append("]"), 620 605 Some(comment) => doc 621 606 .append(break_(",", "").nest(INDENT)) 622 607 // ^ See how here we're adding the missing indentation to the ··· 626 611 .append(line()) 627 612 .append("]") 628 613 .force_break(), 614 + }; 615 + 616 + match list_packing { 617 + ListItemsPacking::FitOnePerLine | ListItemsPacking::FitMultiplePerLine => doc.group(), 618 + ListItemsPacking::BreakOnePerLine => doc.force_break(), 629 619 } 630 620 } 631 621 ··· 1488 1478 .is_ok() 1489 1479 } 1490 1480 1481 + /// Returns true if there's a trailing comma between `start` and `end`. 1482 + /// 1483 + fn has_trailing_comma(&self, start: u32, end: u32) -> bool { 1484 + dbg!(self.trailing_commas) 1485 + .binary_search_by(|comma| { 1486 + if *comma < start { 1487 + Ordering::Less 1488 + } else if *comma > end { 1489 + Ordering::Greater 1490 + } else { 1491 + Ordering::Equal 1492 + } 1493 + }) 1494 + .is_ok() 1495 + } 1496 + 1491 1497 fn pipeline<'a>( 1492 1498 &mut self, 1493 1499 expressions: &'a Vec1<UntypedExpr>, ··· 1977 1983 }; 1978 1984 } 1979 1985 1980 - let comma = if tail.is_none() && elements.iter().all(UntypedExpr::is_simple_constant) { 1981 - flex_break(",", ", ") 1982 - } else { 1983 - break_(",", ", ") 1986 + let list_packing = 1987 + self.list_items_packing(elements, tail, UntypedExpr::is_simple_constant, *location); 1988 + 1989 + let comma = match list_packing { 1990 + ListItemsPacking::FitMultiplePerLine => flex_break(",", ", "), 1991 + ListItemsPacking::FitOnePerLine | ListItemsPacking::BreakOnePerLine => { 1992 + break_(",", ", ") 1993 + } 1984 1994 }; 1985 1995 1986 1996 let list_size = elements.len() ··· 2021 2031 // of moving those out of the list. 2022 2032 // Otherwise those would be moved out of the list. 2023 2033 let comments = self.pop_comments(location.end); 2024 - match printed_comments(comments, false) { 2025 - None => doc.append(last_break).append("]").group(), 2034 + let doc = match printed_comments(comments, false) { 2035 + None => doc.append(last_break).append("]"), 2026 2036 Some(comment) => doc 2027 2037 .append(last_break.nest(INDENT)) 2028 2038 // ^ See how here we're adding the missing indentation to the ··· 2032 2042 .append(line()) 2033 2043 .append("]") 2034 2044 .force_break(), 2045 + }; 2046 + 2047 + match list_packing { 2048 + ListItemsPacking::FitOnePerLine | ListItemsPacking::FitMultiplePerLine => doc.group(), 2049 + ListItemsPacking::BreakOnePerLine => doc.force_break(), 2035 2050 } 2036 2051 } 2037 2052 2053 + fn list_items_packing<'a, T: HasLocation>( 2054 + &self, 2055 + items: &'a [T], 2056 + tail: Option<&'a T>, 2057 + is_simple_constant: impl Fn(&'a T) -> bool, 2058 + list_location: SrcSpan, 2059 + ) -> ListItemsPacking { 2060 + let ends_with_trailing_comma = tail 2061 + .map(|tail| tail.location().end) 2062 + .or_else(|| items.last().map(|last| last.location().end)) 2063 + .is_some_and(|last_element_end| { 2064 + self.has_trailing_comma(last_element_end, list_location.end) 2065 + }); 2066 + 2067 + let is_simple_constant_list = tail.is_none() && items.iter().all(is_simple_constant); 2068 + let has_multiple_elements_per_line = 2069 + self.has_items_on_the_same_line(items.iter().chain(tail)); 2070 + 2071 + if !ends_with_trailing_comma { 2072 + // If the list doesn't end with a trailing comma we try and pack it in 2073 + // a single line; if we can't we'll put one item per line, no matter 2074 + // the content of the list. 2075 + ListItemsPacking::FitOnePerLine 2076 + } else if is_simple_constant_list 2077 + && has_multiple_elements_per_line 2078 + && self.spans_multiple_lines(list_location.start, list_location.end) 2079 + { 2080 + // If there's a trailing comma, the list is only made of simple 2081 + // constants and there's already multiple items per line, we try 2082 + // and pack as many items as possible on each line. 2083 + ListItemsPacking::FitMultiplePerLine 2084 + } else { 2085 + // If it ends with a trailing comma we will force the list on 2086 + // multiple lines, with one item per line. 2087 + ListItemsPacking::BreakOnePerLine 2088 + } 2089 + } 2090 + 2091 + fn has_items_on_the_same_line<'a, L: HasLocation + 'a, T: Iterator<Item = &'a L>>( 2092 + &self, 2093 + items: T, 2094 + ) -> bool { 2095 + let mut previous: Option<SrcSpan> = None; 2096 + for item in items { 2097 + let item_location = item.location(); 2098 + // A list has multiple items on the same line if two consecutive 2099 + // ones do not span multiple lines. 2100 + if let Some(previous) = previous { 2101 + if !self.spans_multiple_lines(previous.end, item_location.start) { 2102 + return true; 2103 + } 2104 + } 2105 + previous = Some(item_location); 2106 + } 2107 + false 2108 + } 2109 + 2038 2110 /// Pretty prints an expression to be used in a comma separated list; for 2039 2111 /// example as a list item, a tuple item or as an argument of a function call. 2040 2112 fn comma_separated_item<'a>( ··· 2761 2833 } 2762 2834 .to_doc() 2763 2835 } 2836 + } 2837 + 2838 + enum ListItemsPacking { 2839 + /// Try and fit everything on a single line; if the items don't fit, break 2840 + /// the list putting each item into its own line. 2841 + /// 2842 + /// ```gleam 2843 + /// // unbroken 2844 + /// [1, 2, 3] 2845 + /// 2846 + /// // broken 2847 + /// [ 2848 + /// 1, 2849 + /// 2, 2850 + /// 3, 2851 + /// ] 2852 + /// ``` 2853 + /// 2854 + FitOnePerLine, 2855 + 2856 + /// Try and fit everything on a single line; if the items don't fit, break 2857 + /// the list putting as many items as possible in a single line. 2858 + /// 2859 + /// ```gleam 2860 + /// // unbroken 2861 + /// [1, 2, 3] 2862 + /// 2863 + /// // broken 2864 + /// [ 2865 + /// 1, 2, 3, ... 2866 + /// 4, 100, 2867 + /// ] 2868 + /// ``` 2869 + /// 2870 + FitMultiplePerLine, 2871 + 2872 + /// Always break the list, putting each item into its own line: 2873 + /// 2874 + /// ```gleam 2875 + /// [ 2876 + /// 1, 2877 + /// 2, 2878 + /// 3, 2879 + /// ] 2880 + /// ``` 2881 + BreakOnePerLine, 2764 2882 } 2765 2883 2766 2884 pub fn break_block(doc: Document<'_>) -> Document<'_> {
+4 -2
compiler-core/src/format/tests.rs
··· 5992 5992 r#"const x = "some long string 1" 5993 5993 <> "some long string 2" 5994 5994 <> [ 5995 - "here is a list", "with several elements", 5995 + "here is a list", 5996 + "with several elements", 5996 5997 "in order to make it be too long to fit on one line", 5997 - "so we can see how it breaks", "onto multiple lines", 5998 + "so we can see how it breaks", 5999 + "onto multiple lines", 5998 6000 ] 5999 6001 <> "and a last string" 6000 6002 "#,