typstyle_core/
partial.rs

1use std::ops::Range;
2
3use typst_syntax::{
4    ast::{Expr, Markup, Pattern},
5    LinkedNode, Source, Span, SyntaxKind,
6};
7
8use crate::{
9    pretty::{Context, Mode},
10    utils, AttrStore, Error, PrettyPrinter, Typstyle,
11};
12
13impl Typstyle {
14    /// Format the node with minimal span that covering the given range.
15    pub fn format_source_range(
16        &self,
17        source: &Source,
18        utf8_range: Range<usize>,
19    ) -> Result<(Range<usize>, String), Error> {
20        // Trim the give range to ensure no space aside.
21        let range = utils::trim_range(source.text(), utf8_range);
22
23        let Some((node, mode)) =
24            get_node_cover_range(source, range.clone()).filter(|(node, _)| !node.erroneous())
25        else {
26            return Err(Error::SyntaxError);
27        };
28
29        let attrs = AttrStore::new(node.get()); // Here we only compute the attributes of that subtree.
30        let printer = PrettyPrinter::new(self.config.clone(), attrs);
31        let ctx = Context::default().with_mode(mode);
32        let doc = if let Some(markup) = node.cast() {
33            printer.convert_markup(ctx, markup)
34        } else if let Some(expr) = node.cast() {
35            printer.convert_expr(ctx, expr)
36        } else if let Some(pattern) = node.cast() {
37            printer.convert_pattern(ctx, pattern)
38        } else {
39            return Err(Error::SyntaxError);
40        };
41        // Infer indent from context.
42        let indent = utils::count_spaces_after_last_newline(source.text(), range.start);
43        let res = doc
44            .nest(indent as isize)
45            .pretty(self.config.max_width)
46            .to_string();
47        Ok((node.range(), res))
48    }
49}
50
51/// Get a Markup/Expr/Pattern node from source with minimal span that covering the given range.
52fn get_node_cover_range(source: &Source, range: Range<usize>) -> Option<(LinkedNode, Mode)> {
53    let range = range.start..range.end.min(source.len_bytes());
54    get_node_cover_range_impl(range, LinkedNode::new(source.root()), Mode::Markup)
55        .and_then(|(span, mode)| source.find(span).map(|node| (node, mode)))
56}
57
58fn get_node_cover_range_impl(
59    range: Range<usize>,
60    node: LinkedNode<'_>,
61    mode: Mode,
62) -> Option<(Span, Mode)> {
63    let mode = match node.kind() {
64        SyntaxKind::Markup => Mode::Markup,
65        SyntaxKind::CodeBlock => Mode::Code,
66        SyntaxKind::Equation => Mode::Math,
67        _ => mode,
68    };
69    for child in node.children() {
70        if let Some(res) = get_node_cover_range_impl(range.clone(), child, mode) {
71            return Some(res);
72        }
73    }
74    let node_range = node.range();
75    (node_range.start <= range.start
76        && node_range.end >= range.end
77        && (node.is::<Markup>() || node.is::<Expr>() || node.is::<Pattern>()))
78    .then(|| (node.span(), mode))
79    // It returns span to avoid problems with borrowing.
80}