typstyle_core/pretty/
code_list.rs

1use typst_syntax::{ast::*, SyntaxKind};
2
3use super::{
4    layout::list::{ListStyle, ListStylist},
5    prelude::*,
6    style::FoldStyle,
7    util::{has_comment_children, is_only_one_and},
8    Context, Mode, PrettyPrinter,
9};
10
11impl<'a> PrettyPrinter<'a> {
12    pub(super) fn convert_code_block(
13        &'a self,
14        ctx: Context,
15        code_block: CodeBlock<'a>,
16    ) -> ArenaDoc<'a> {
17        if self
18            .attr_store
19            .is_format_disabled(code_block.body().to_untyped())
20        {
21            return self.convert_verbatim(code_block);
22        }
23
24        let ctx = ctx.with_mode(Mode::Code);
25
26        let mut nodes = vec![];
27        for child in code_block.to_untyped().children() {
28            if let Some(code) = child.cast::<Code>() {
29                nodes.extend(code.to_untyped().children());
30            } else {
31                nodes.push(child);
32            }
33        }
34
35        let can_fold = code_block.body().exprs().count() <= 1
36            && !has_comment_children(code_block.to_untyped());
37        ListStylist::new(self)
38            .disallow_front_comment()
39            .with_fold_style(if can_fold {
40                self.get_fold_style(ctx, code_block)
41            } else {
42                FoldStyle::Never
43            })
44            .keep_linebreak(self.config.blank_lines_upper_bound)
45            .process_iterable(ctx, nodes.into_iter(), |ctx, expr| {
46                self.convert_expr(ctx, expr)
47            })
48            .print_doc(ListStyle {
49                separator: "",
50                delim: ("{", "}"),
51                add_delim_space: true,
52                ..Default::default()
53            })
54    }
55
56    pub(super) fn convert_parenthesized_impl(
57        &'a self,
58        ctx: Context,
59        parenthesized: Parenthesized<'a>,
60    ) -> ArenaDoc<'a> {
61        // NOTE: This is a safe cast. The parentheses for patterns are all optional.
62        // For safety, we don't remove parentheses around idents. See `paren-in-key.typ`.
63        let expr = parenthesized.expr();
64        let can_omit = (expr.is_literal()
65            || matches!(
66                expr.to_untyped().kind(),
67                SyntaxKind::Array
68                    | SyntaxKind::Dict
69                    | SyntaxKind::Destructuring
70                    | SyntaxKind::CodeBlock
71                    | SyntaxKind::ContentBlock
72            ))
73            && !has_comment_children(parenthesized.to_untyped());
74
75        ListStylist::new(self)
76            .with_fold_style(self.get_fold_style(ctx, parenthesized))
77            .process_list(ctx, parenthesized.to_untyped(), |ctx, node| {
78                self.convert_pattern(ctx, node)
79            })
80            .print_doc(ListStyle {
81                separator: "",
82                omit_delim_flat: can_omit,
83                ..Default::default()
84            })
85    }
86
87    /// In math mode, we have `$fun(1, 2; 3, 4)$ == $fun(#(1, 2), #(3, 4))$`.
88    pub(super) fn convert_array(&'a self, ctx: Context, array: Array<'a>) -> ArenaDoc<'a> {
89        let ctx = ctx.with_mode(Mode::CodeCont);
90
91        // Whether the array has parens.
92        // This is also used to determine whether we need to add a trailing comma.
93        // Note that we should not strip trailing commas in math.
94        let is_explicit = array
95            .to_untyped()
96            .children()
97            .next()
98            .is_some_and(|child| child.kind() == SyntaxKind::LeftParen);
99        let ends_with_comma = !is_explicit
100            && array
101                .to_untyped()
102                .children()
103                .last()
104                .is_some_and(|child| child.kind() == SyntaxKind::Comma);
105
106        ListStylist::new(self)
107            .with_fold_style(self.get_fold_style(ctx, array))
108            .process_list(ctx, array.to_untyped(), |ctx, node| {
109                self.convert_array_item(ctx, node)
110            })
111            .print_doc(ListStyle {
112                add_trailing_sep_single: is_explicit,
113                add_trailing_sep_always: ends_with_comma,
114                delim: if is_explicit { ("(", ")") } else { ("", "") },
115                tight_delim: !is_explicit,
116                no_indent: !is_explicit,
117                ..Default::default()
118            })
119    }
120
121    pub(super) fn convert_dict(&'a self, ctx: Context, dict: Dict<'a>) -> ArenaDoc<'a> {
122        let ctx = ctx.with_mode(Mode::CodeCont);
123
124        let all_spread = dict.items().all(|item| matches!(item, DictItem::Spread(_)));
125
126        ListStylist::new(self)
127            .with_fold_style(self.get_fold_style(ctx, dict))
128            .process_list(ctx, dict.to_untyped(), |ctx, node| {
129                self.convert_dict_item(ctx, node)
130            })
131            .print_doc(ListStyle {
132                delim: (if all_spread { "(:" } else { "(" }, ")"),
133                ..Default::default()
134            })
135    }
136
137    pub(super) fn convert_destructuring(
138        &'a self,
139        ctx: Context,
140        destructuring: Destructuring<'a>,
141    ) -> ArenaDoc<'a> {
142        let ctx = ctx.with_mode(Mode::CodeCont);
143
144        let only_one_pattern = is_only_one_and(destructuring.items(), |it| {
145            matches!(*it, DestructuringItem::Pattern(_))
146        });
147
148        ListStylist::new(self)
149            .with_fold_style(self.get_fold_style(ctx, destructuring))
150            .process_list(ctx, destructuring.to_untyped(), |ctx, node| {
151                self.convert_destructuring_item(ctx, node)
152            })
153            .always_fold_if(|| only_one_pattern)
154            .print_doc(ListStyle {
155                add_trailing_sep_single: only_one_pattern,
156                ..Default::default()
157            })
158    }
159
160    pub(super) fn convert_params(
161        &'a self,
162        ctx: Context,
163        params: Params<'a>,
164        is_unnamed: bool,
165    ) -> ArenaDoc<'a> {
166        // SAFETY: The param must be simple if the parens is optional.
167        let ctx = ctx.with_mode(Mode::CodeCont);
168
169        let is_single_simple = is_unnamed
170            && is_only_one_and(params.children(), |it| {
171                matches!(
172                    *it,
173                    Param::Pos(Pattern::Normal(_)) | Param::Pos(Pattern::Placeholder(_))
174                )
175            });
176
177        ListStylist::new(self)
178            .with_fold_style(self.get_fold_style(ctx, params))
179            .process_list(ctx, params.to_untyped(), |ctx, node| {
180                self.convert_param(ctx, node)
181            })
182            .always_fold_if(|| is_single_simple)
183            .print_doc(ListStyle {
184                omit_delim_single: is_single_simple,
185                ..Default::default()
186            })
187    }
188}