oak_pretty_print/formatter/
mod.rs1use crate::{CommentProcessor, Document, FormatConfig, FormatResult, RuleSet, create_builtin_rules};
2use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec};
3use oak_core::{
4 language::Language,
5 tree::{RedLeaf, RedNode, RedTree},
6};
7
8#[derive(Debug, Clone, serde::Serialize)]
10#[serde(rename_all = "camelCase")]
11pub struct FormatOutput {
12 pub content: String,
14 pub changed: bool,
16}
17
18impl FormatOutput {
19 pub fn new(content: String, changed: bool) -> Self {
21 Self { content, changed }
22 }
23}
24
25#[derive(Debug)]
27pub struct PathNode<L: Language> {
28 pub kind: L::ElementType,
29 pub parent: Option<Arc<PathNode<L>>>,
30}
31
32#[derive(Debug, Clone)]
34pub struct FormatContext<L: Language> {
35 pub config: Arc<FormatConfig>,
37 pub comment_processor: Arc<CommentProcessor>,
39 pub source: Option<Arc<str>>,
41 pub depth: usize,
43 pub path: Option<Arc<PathNode<L>>>,
45}
46
47impl<L: Language> FormatContext<L> {
48 pub fn new(config: FormatConfig) -> Self {
50 let config = Arc::new(config);
51 Self { config: config.clone(), comment_processor: Arc::new(CommentProcessor::new().with_preserve_comments(config.format_comments).with_format_comments(config.format_comments)), source: None, depth: 0, path: None }
52 }
53
54 pub fn enter(&self, kind: L::ElementType) -> Self {
56 let path = Some(Arc::new(PathNode { kind, parent: self.path.clone() }));
57 Self { config: self.config.clone(), comment_processor: self.comment_processor.clone(), source: self.source.clone(), depth: self.depth + 1, path }
58 }
59
60 pub fn is_inside(&self, kind: L::ElementType) -> bool {
62 let mut current = self.path.as_ref();
63 while let Some(node) = current {
64 if node.kind == kind {
65 return true;
66 }
67 current = node.parent.as_ref();
68 }
69 false
70 }
71
72 pub fn parent_kind(&self) -> Option<L::ElementType> {
74 self.path.as_ref().map(|n| n.kind.clone())
75 }
76}
77
78pub struct Formatter<L: Language + 'static> {
80 rules: RuleSet<L>,
82 pub context: FormatContext<L>,
84}
85
86impl<L: Language + 'static> Formatter<L>
87where
88 L::ElementType: oak_core::language::TokenType,
89{
90 pub fn new(config: FormatConfig) -> Self {
92 let mut formatter = Self { rules: RuleSet::new(), context: FormatContext::new(config) };
93
94 for rule in create_builtin_rules::<L>() {
96 let _ = formatter.rules.add_rule(rule);
97 }
98
99 formatter
100 }
101
102 pub fn add_rule(&mut self, rule: Box<dyn crate::FormatRule<L>>) -> FormatResult<()> {
104 self.rules.add_rule(rule)
105 }
106
107 pub fn format<'a>(&mut self, root: &RedNode<L>, source: &'a str) -> FormatResult<FormatOutput> {
109 self.context.source = Some(Arc::from(source));
110 let doc = self.format_node(root, &self.context, source)?;
111 let content = doc.render((*self.context.config).clone());
112 let changed = content != source;
113 Ok(FormatOutput::new(content, changed))
114 }
115
116 fn format_node<'a>(&self, node: &RedNode<L>, context: &FormatContext<L>, source: &'a str) -> FormatResult<Document<'a>> {
118 let new_context = context.enter(node.green.kind.clone());
120
121 let format_children = |n: &RedNode<L>| {
123 let mut children_docs = Vec::new();
124 for child in n.children() {
125 match child {
126 RedTree::Node(child_node) => children_docs.push(self.format_node(&child_node, &new_context, source)?),
127 RedTree::Leaf(child_token) => children_docs.push(self.format_token(&child_token, &new_context, source)?),
128 }
129 }
130 Ok(Document::Concat(children_docs))
131 };
132
133 if let Some(doc) = self.rules.apply_node_rules(node, &new_context, source, &format_children)? {
135 return Ok(doc);
136 }
137
138 format_children(node)
140 }
141
142 fn format_token<'a>(&self, token: &RedLeaf<L>, context: &FormatContext<L>, source: &'a str) -> FormatResult<Document<'a>> {
144 if let Some(doc) = self.rules.apply_token_rules(token, context, source)? {
146 return Ok(doc);
147 }
148
149 let text = &source[token.span.start..token.span.end];
151 Ok(Document::Text(text.into()))
152 }
153}