use itertools::Itertools;
use typst_syntax::{SyntaxKind, SyntaxNode, ast::*};
use super::{
Context, Mode, PrettyPrinter,
context::AlignMode,
layout::{
flow::FlowItem,
list::{ListStyle, ListStylist},
plain::PlainStylist,
},
prelude::*,
style::FoldStyle,
table,
};
use crate::{ext::StrExt, pretty::args};
impl<'a> PrettyPrinter<'a> {
pub(super) fn convert_func_call(
&'a self,
ctx: Context,
func_call: FuncCall<'a>,
) -> ArenaDoc<'a> {
if func_call.callee().to_untyped().kind() == SyntaxKind::FieldAccess
&& let Some(res) = self.try_convert_dot_chain(ctx, func_call.to_untyped())
{
return res;
}
self.convert_expr(ctx, func_call.callee())
+ if ctx.mode.is_math() {
self.convert_args_in_math(ctx, func_call.args())
} else {
self.convert_args_of_func(ctx, func_call)
}
}
pub(super) fn convert_math_call(
&'a self,
ctx: Context,
math_call: MathCall<'a>,
) -> ArenaDoc<'a> {
self.convert_verbatim_untyped(math_call.callee().to_untyped())
+ self.convert_math_args(ctx, math_call.args().to_untyped())
}
pub(super) fn convert_args_of_func(
&'a self,
ctx: Context,
func_call: FuncCall<'a>,
) -> ArenaDoc<'a> {
self.convert_args(ctx, func_call.args(), |nodes| {
self.convert_parenthesized_args_of_func(ctx, func_call, nodes)
})
}
fn convert_parenthesized_args_of_func(
&'a self,
ctx: Context,
func_call: FuncCall<'a>,
paren_nodes: &'a [SyntaxNode],
) -> ArenaDoc<'a> {
if table::is_table(func_call) {
if let Some(table) = self.try_convert_table(ctx, func_call, paren_nodes) {
table
} else {
self.convert_parenthesized_args_as_list(ctx, paren_nodes)
}
} else {
self.convert_parenthesized_args_normal(ctx, func_call.args(), paren_nodes)
}
}
pub(super) fn convert_args(
&'a self,
ctx: Context,
args: Args<'a>,
parenthesized_nodes_handler: impl FnOnce(&'a [SyntaxNode]) -> ArenaDoc<'a>,
) -> ArenaDoc<'a> {
let (paren_nodes, trailing_nodes) = args::split_paren_args(args);
let paren_doc = match paren_nodes {
[] | [_] => self.arena.nil(), [_, _] => self.arena.text("()"), [_, inner @ .., _] => parenthesized_nodes_handler(inner), };
let trailing_doc = self.convert_trailing_content_args(ctx, trailing_nodes);
paren_doc + trailing_doc
}
fn convert_trailing_content_args(
&'a self,
ctx: Context,
nodes: &'a [SyntaxNode],
) -> ArenaDoc<'a> {
self.arena.concat(
nodes
.iter()
.filter_map(SyntaxNode::cast)
.map(|arg| self.convert_content_block(ctx, arg)),
)
}
pub(super) fn convert_parenthesized_args_normal(
&'a self,
ctx: Context,
args: Args<'a>,
paren_nodes: &'a [SyntaxNode],
) -> ArenaDoc<'a> {
let ctx = ctx.with_mode(Mode::CodeCont);
let pargs = paren_nodes
.iter()
.filter_map(SyntaxNode::cast)
.collect_vec();
let fold_style = match self.get_fold_style(ctx, args) {
_ if pargs.is_empty() => FoldStyle::Always,
FoldStyle::Always => FoldStyle::Always,
_ if ctx.break_suppressed && pargs.len() == 1 => FoldStyle::Always,
_ if ctx.break_suppressed => suggest_fold_style_for_args(&pargs, FoldStyle::Fit),
fold_style => suggest_fold_style_for_args(&pargs, fold_style),
};
ListStylist::new(self)
.keep_linebreak(self.config.blank_lines_upper_bound)
.with_fold_style(fold_style)
.process_iterable_impl(ctx, paren_nodes.iter(), |ctx, child| {
child.cast().map(|arg| self.convert_arg(ctx, arg))
})
.print_doc(ListStyle {
..Default::default()
})
}
fn convert_parenthesized_args_as_list(
&'a self,
ctx: Context,
paren_nodes: &'a [SyntaxNode],
) -> ArenaDoc<'a> {
let ctx = ctx.with_mode(Mode::CodeCont);
let inner = PlainStylist::new(self)
.process_iterable(ctx, paren_nodes.iter(), |ctx, child| {
self.convert_arg(ctx, child)
})
.print_doc();
self.indent(inner).parens()
}
fn convert_args_in_math(&'a self, ctx: Context, args: Args<'a>) -> ArenaDoc<'a> {
self.convert_math_args(ctx, args.to_untyped())
}
fn convert_math_args(&'a self, ctx: Context, args: &'a SyntaxNode) -> ArenaDoc<'a> {
let mut peek_linebreak = false;
let children = {
let children = args.children().as_slice();
let i = children.iter().position(|child| {
if child.kind() == SyntaxKind::Space {
peek_linebreak = child.leaf_text().has_linebreak();
}
!matches!(child.kind(), SyntaxKind::LeftParen | SyntaxKind::Space)
});
let j = children.iter().rposition(|child| {
!matches!(child.kind(), SyntaxKind::RightParen | SyntaxKind::Space)
});
match (i, j) {
(Some(i), Some(j)) if i <= j => children[i..=j].iter(),
_ => children[0..0].iter(),
}
};
let mut peek_hash = false;
let mut peek_arg = false;
let mut peek_hashed_arg = false;
let inner = self.convert_flow_like_iter(ctx, children, |ctx, child, state| {
let at_hash = state.at_hash || peek_hash;
let at_arg = peek_arg;
let at_hashed_arg = peek_hashed_arg;
let at_linebreak = peek_linebreak;
peek_hash = false;
peek_arg = false;
peek_hashed_arg = false;
peek_linebreak = false;
match child.kind() {
SyntaxKind::Comma => FlowItem::tight_spaced(self.arena.text(",")),
SyntaxKind::Semicolon => FlowItem::new(self.arena.text(";"), at_hashed_arg, true),
SyntaxKind::Space => {
peek_hash = at_hash;
peek_hashed_arg = at_hashed_arg || at_arg;
if child.leaf_text().has_linebreak() {
peek_linebreak = true;
FlowItem::tight(self.arena.hardline())
} else {
FlowItem::none()
}
}
SyntaxKind::Hash => {
peek_hash = true;
FlowItem::none()
}
_ => {
if let Some(arg) = child.cast::<Arg>() {
peek_arg = !arg.to_untyped().leaf_text().is_empty();
if at_hash || is_ends_with_hashed_expr(arg.to_untyped().children()) {
peek_hashed_arg = true;
}
let ctx = ctx.aligned(
if at_linebreak || arg.to_untyped().kind() == SyntaxKind::MathDelimited
{
AlignMode::Inner
} else {
AlignMode::Never
},
);
FlowItem::spaced(self.convert_arg(ctx, arg))
} else {
FlowItem::none()
}
}
}
});
if self.attr_store.is_multiline(args) {
self.block_indent(inner).group().parens()
} else {
inner.parens()
}
}
}
fn is_ends_with_hashed_expr(mut children: std::slice::Iter<'_, SyntaxNode>) -> bool {
children.next_back().is_some_and(|it| it.is::<Expr>())
&& children
.next_back()
.is_some_and(|it| it.kind() == SyntaxKind::Hash)
}
fn suggest_fold_style_for_args(pargs: &[Arg], hint: FoldStyle) -> FoldStyle {
let Some((&last, initials)) = pargs.split_last() else {
return FoldStyle::Always; };
let last_expr = args::unwrap_expr_deep(last);
if !args::is_combinable(last_expr) {
return hint;
}
if initials.is_empty() {
return if matches!(last_expr, Expr::CodeBlock(_)) {
FoldStyle::Always
} else if hint == FoldStyle::Never {
FoldStyle::Never
} else {
FoldStyle::Compact
};
}
if hint == FoldStyle::Never {
return hint;
}
let last_expr_kind = last_expr.to_untyped().kind();
if initials.iter().any(|&arg| {
let expr = args::unwrap_expr_deep(arg);
match expr {
expr if args::is_blocky(expr) => true,
_ if expr.to_untyped().kind() != last_expr_kind => false,
Expr::Array(array) if array.items().next().is_some() => true,
Expr::Dict(dict) if dict.items().next().is_some() => true,
_ => false,
}
}) {
return hint;
}
FoldStyle::Compact
}