typstyle_core/
lib.rs

1pub mod attr;
2pub mod ext;
3pub mod liteval;
4pub mod partial;
5pub mod pretty;
6
7mod config;
8mod utils;
9
10pub use attr::AttrStore;
11pub use config::Config;
12use pretty::{prelude::*, PrettyPrinter};
13use thiserror::Error;
14use typst_syntax::{Source, SyntaxNode};
15
16use crate::utils::indent_4_to_2;
17
18#[derive(Error, Debug)]
19pub enum Error {
20    #[error("The document has syntax errors")]
21    SyntaxError,
22    #[error("An error occurred while rendering the document")]
23    RenderError,
24}
25
26/// Main struct for Typst formatting.
27#[derive(Debug, Clone, Default)]
28pub struct Typstyle {
29    config: Config,
30}
31
32impl Typstyle {
33    /// Creates a new `Typstyle` with the given style configuration.
34    pub fn new(config: Config) -> Self {
35        Self { config }
36    }
37
38    /// Prepares a text string for formatting.
39    pub fn format_text(&self, text: impl Into<String>) -> Formatter<'_> {
40        // We should ensure that the source tree is spanned.
41        self.format_source(Source::detached(text.into()))
42    }
43
44    /// Prepares a source for formatting.
45    pub fn format_source(&self, source: Source) -> Formatter<'_> {
46        Formatter::new(self.config.clone(), source)
47    }
48}
49
50/// Handles the formatting of a specific Typst source.
51pub struct Formatter<'a> {
52    source: Source,
53    printer: PrettyPrinter<'a>,
54}
55
56impl<'a> Formatter<'a> {
57    fn new(config: Config, source: Source) -> Self {
58        let attr_store = AttrStore::new(source.root());
59        let printer = PrettyPrinter::new(config, attr_store);
60        Self { source, printer }
61    }
62
63    /// Renders the document's pretty IR.
64    pub fn render_ir(&'a self) -> Result<String, Error> {
65        let doc = self.build_doc()?;
66        Ok(indent_4_to_2(&format!("{doc:#?}")))
67    }
68
69    /// Renders the formatted document to a string.
70    pub fn render(&'a self) -> Result<String, Error> {
71        let doc = self.build_doc()?;
72        let mut buf = String::new();
73        doc.render_fmt(self.printer.config().max_width, &mut buf)
74            .map_err(|_| Error::RenderError)?;
75        let result = utils::strip_trailing_whitespace(&buf);
76        Ok(result)
77    }
78
79    fn build_doc(&'a self) -> Result<ArenaDoc<'a>, Error> {
80        let root = self.source.root();
81        if root.erroneous() {
82            return Err(Error::SyntaxError);
83        }
84        let markup = root.cast().unwrap();
85        let doc = self.printer.convert_markup(Default::default(), markup);
86        Ok(doc)
87    }
88}
89
90/// Formats a `SyntaxNode` as a debug AST string with 2-space indentation.
91pub fn format_ast(root: &SyntaxNode) -> String {
92    indent_4_to_2(&format!("{root:#?}"))
93}