typstyle_core/pretty/
mod.rs

1pub mod context;
2pub mod prelude;
3pub mod style;
4
5mod args;
6mod code_chain;
7mod code_flow;
8mod code_list;
9mod code_misc;
10mod comment;
11mod func_call;
12mod import;
13mod layout;
14mod markup;
15mod math;
16mod math_align;
17mod parened_expr;
18mod table;
19mod text;
20mod util;
21
22pub use context::{Context, Mode};
23use prelude::*;
24use style::{is_multiline_flavored, FoldStyle};
25use typst_syntax::{ast::*, SyntaxNode};
26
27use crate::{ext::StrExt, AttrStore, Config, Error};
28
29pub struct PrettyPrinter<'a> {
30    config: Config,
31    attr_store: AttrStore,
32    arena: Arena<'a>,
33}
34
35impl<'a> PrettyPrinter<'a> {
36    pub fn new(config: Config, attr_store: AttrStore) -> Self {
37        Self {
38            config,
39            attr_store,
40            arena: Arena::new(),
41        }
42    }
43
44    pub fn config(&self) -> &Config {
45        &self.config
46    }
47
48    fn get_fold_style(&self, ctx: Context, node: impl AstNode<'a>) -> FoldStyle {
49        self.get_fold_style_untyped(ctx, node.to_untyped())
50    }
51
52    fn get_fold_style_untyped(&self, ctx: Context, node: &'a SyntaxNode) -> FoldStyle {
53        let is_multiline = is_multiline_flavored(node);
54        if ctx.break_suppressed {
55            return if is_multiline {
56                FoldStyle::Fit
57            } else {
58                FoldStyle::Always
59            };
60        }
61        if is_multiline {
62            FoldStyle::Never
63        } else {
64            FoldStyle::Fit
65        }
66    }
67}
68
69/// Utilities
70impl<'a> PrettyPrinter<'a> {
71    pub(crate) fn indent(&'a self, doc: ArenaDoc<'a>) -> ArenaDoc<'a> {
72        doc.nest(self.config.tab_spaces as isize)
73    }
74
75    pub(crate) fn block_indent(&'a self, doc: ArenaDoc<'a>) -> ArenaDoc<'a> {
76        self.indent(self.arena.line_() + doc) + self.arena.line_()
77    }
78}
79
80impl<'a> PrettyPrinter<'a> {
81    fn check_disabled(&'a self, node: &'a SyntaxNode) -> Option<ArenaDoc<'a>> {
82        if self.attr_store.is_format_disabled(node) {
83            Some(self.convert_verbatim_untyped(node))
84        } else {
85            None
86        }
87    }
88
89    /// For inner or lead nodes.
90    fn convert_verbatim(&'a self, node: impl AstNode<'a>) -> ArenaDoc<'a> {
91        self.convert_verbatim_untyped(node.to_untyped())
92    }
93
94    /// For inner or lead nodes.
95    fn convert_verbatim_untyped(&'a self, node: &'a SyntaxNode) -> ArenaDoc<'a> {
96        let text = node.clone().into_text();
97        if !text.has_linebreak() {
98            return self.arena.text(text.to_string());
99        }
100        // When the text spans multiple lines, we should split it to ensure proper fitting.
101        self.arena
102            .intersperse(
103                node.clone().into_text().lines().map(str::to_string),
104                self.arena.hardline(),
105            )
106            .dedent_to_root()
107    }
108
109    /// For leaf only.
110    fn convert_trivia(&'a self, node: impl AstNode<'a>) -> ArenaDoc<'a> {
111        self.convert_trivia_untyped(node.to_untyped())
112    }
113
114    /// For leaf only.
115    fn convert_trivia_untyped(&'a self, node: &'a SyntaxNode) -> ArenaDoc<'a> {
116        self.arena.text(node.text().as_str())
117    }
118
119    pub fn try_convert_with_mode(
120        &'a self,
121        node: &'a SyntaxNode,
122        mode: Mode,
123    ) -> Result<ArenaDoc<'a>, Error> {
124        let ctx = Context::default().with_mode(mode);
125        let doc = if let Some(markup) = node.cast() {
126            self.convert_markup(ctx, markup)
127        } else if let Some(code) = node.cast() {
128            self.convert_code(ctx, code)
129        } else if let Some(math) = node.cast() {
130            self.convert_math(ctx, math)
131        } else if let Some(expr) = node.cast() {
132            self.convert_expr(ctx, expr)
133        } else if let Some(pattern) = node.cast() {
134            self.convert_pattern(ctx, pattern)
135        } else {
136            return Err(Error::SyntaxError);
137        };
138        Ok(doc)
139    }
140
141    pub fn convert_expr(&'a self, ctx: Context, expr: Expr<'a>) -> ArenaDoc<'a> {
142        if let Some(res) = self.check_disabled(expr.to_untyped()) {
143            return res;
144        }
145        self.convert_expr_impl(ctx, expr)
146    }
147
148    fn convert_expr_impl(&'a self, ctx: Context, expr: Expr<'a>) -> ArenaDoc<'a> {
149        match expr {
150            Expr::Text(t) => self.convert_text(t),
151            Expr::Space(s) => self.convert_space(ctx, s),
152            Expr::Linebreak(b) => self.convert_trivia(b),
153            Expr::Parbreak(b) => self.convert_parbreak(b),
154            Expr::Escape(e) => self.convert_trivia(e),
155            Expr::Shorthand(s) => self.convert_trivia(s),
156            Expr::SmartQuote(s) => self.convert_trivia(s),
157            Expr::Strong(s) => self.convert_strong(ctx, s),
158            Expr::Emph(e) => self.convert_emph(ctx, e),
159            Expr::Raw(r) => self.convert_raw(ctx, r),
160            Expr::Link(l) => self.convert_trivia(l),
161            Expr::Label(l) => self.convert_trivia(l),
162            Expr::Ref(r) => self.convert_ref(ctx, r),
163            Expr::Heading(h) => self.convert_heading(ctx, h),
164            Expr::List(l) => self.convert_list_item(ctx, l),
165            Expr::Enum(e) => self.convert_enum_item(ctx, e),
166            Expr::Term(t) => self.convert_term_item(ctx, t),
167            Expr::Equation(e) => self.convert_equation(ctx, e),
168            Expr::Math(m) => self.convert_math(ctx, m),
169            Expr::MathText(math_text) => self.convert_trivia(math_text),
170            Expr::MathIdent(mi) => self.convert_trivia(mi),
171            Expr::MathAlignPoint(map) => self.convert_trivia(map),
172            Expr::MathDelimited(md) => self.convert_math_delimited(ctx, md),
173            Expr::MathAttach(ma) => self.convert_math_attach(ctx, ma),
174            Expr::MathPrimes(mp) => self.convert_math_primes(ctx, mp),
175            Expr::MathFrac(mf) => self.convert_math_frac(ctx, mf),
176            Expr::MathRoot(mr) => self.convert_math_root(ctx, mr),
177            Expr::MathShorthand(ms) => self.convert_trivia(ms),
178            Expr::Ident(i) => self.convert_ident(i),
179            Expr::None(_) => self.convert_literal("none"),
180            Expr::Auto(_) => self.convert_literal("auto"),
181            Expr::Bool(b) => self.convert_trivia(b),
182            Expr::Int(i) => self.convert_trivia(i),
183            Expr::Float(f) => self.convert_trivia(f),
184            Expr::Numeric(n) => self.convert_trivia(n),
185            Expr::Str(s) => self.convert_trivia(s),
186            Expr::Code(c) => self.convert_code_block(ctx, c),
187            Expr::Content(c) => self.convert_content_block(ctx, c),
188            Expr::Parenthesized(p) => self.convert_parenthesized(ctx, p),
189            Expr::Array(a) => self.convert_array(ctx, a),
190            Expr::Dict(d) => self.convert_dict(ctx, d),
191            Expr::Unary(u) => self.convert_unary(ctx, u),
192            Expr::Binary(b) => self.convert_binary(ctx, b),
193            Expr::FieldAccess(fa) => self.convert_field_access(ctx, fa),
194            Expr::FuncCall(fc) => self.convert_func_call(ctx, fc),
195            Expr::Closure(c) => self.convert_closure(ctx, c),
196            Expr::Let(l) => self.convert_let_binding(ctx, l),
197            Expr::DestructAssign(da) => self.convert_destruct_assignment(ctx, da),
198            Expr::Set(s) => self.convert_set_rule(ctx, s),
199            Expr::Show(s) => self.convert_show_rule(ctx, s),
200            Expr::Contextual(c) => self.convert_contextual(ctx, c),
201            Expr::Conditional(c) => self.convert_conditional(ctx, c),
202            Expr::While(w) => self.convert_while_loop(ctx, w),
203            Expr::For(f) => self.convert_for_loop(ctx, f),
204            Expr::Import(i) => self.convert_import(ctx, i),
205            Expr::Include(i) => self.convert_include(ctx, i),
206            Expr::Break(_) => self.convert_literal("break"),
207            Expr::Continue(_) => self.convert_literal("continue"),
208            Expr::Return(r) => self.convert_return(ctx, r),
209        }
210    }
211
212    fn convert_literal(&'a self, literal: &'a str) -> ArenaDoc<'a> {
213        self.arena.text(literal)
214    }
215}