1use rustc_hash::FxHashMap;
2use typst_syntax::{ast, Span, SyntaxKind, SyntaxNode};
3
4use crate::ext::StrExt;
5
6#[derive(Debug, Clone, Default)]
7pub struct Attributes {
8 pub(self) is_format_disabled: bool,
10
11 pub(self) has_comment: bool,
13
14 pub(self) has_multiline_str: bool,
16
17 pub(self) has_math_align_point: bool,
19
20 pub(self) is_multiline: bool,
23}
24
25#[derive(Debug, Default)]
27pub struct AttrStore {
28 attr_map: FxHashMap<Span, Attributes>,
30}
31
32impl AttrStore {
33 pub fn new(node: &SyntaxNode) -> AttrStore {
36 if node.erroneous() {
37 return Default::default(); }
39 let mut store = AttrStore::default();
40 store.compute_no_format(node);
41 store.compute_multiline(node);
42 store.compute_math_align_point(node);
43 store
44 }
45
46 pub fn has_comment(&self, node: &SyntaxNode) -> bool {
48 self.check_node_attr(node, |attr| attr.has_comment)
49 }
50
51 pub fn has_multiline_str(&self, node: &SyntaxNode) -> bool {
52 self.check_node_attr(node, |attr| attr.has_multiline_str)
53 }
54
55 pub fn has_math_align_point(&self, node: &SyntaxNode) -> bool {
56 self.check_node_attr(node, |attr| attr.has_math_align_point)
57 }
58
59 pub fn can_align_in_math(&self, node: &SyntaxNode) -> bool {
60 self.check_node_attr(node, |attr| {
61 attr.has_math_align_point && !attr.has_multiline_str
62 })
63 }
64
65 pub fn is_multiline(&self, node: &SyntaxNode) -> bool {
67 self.check_node_attr(node, |attr| attr.is_multiline)
68 }
69
70 pub fn is_format_disabled(&self, node: &SyntaxNode) -> bool {
72 self.check_node_attr(node, |attr| attr.is_format_disabled)
73 }
74
75 fn check_node_attr(&self, node: &SyntaxNode, pred: impl FnOnce(&Attributes) -> bool) -> bool {
76 self.attr_map.get(&node.span()).is_some_and(pred)
77 }
78}
79
80impl AttrStore {
81 fn compute_multiline(&mut self, root: &SyntaxNode) {
82 self.compute_multiline_impl(root);
83 }
84
85 fn compute_multiline_impl(&mut self, node: &SyntaxNode) -> (bool, bool) {
86 let mut is_multiline = false;
87 let mut has_multiline_str = false;
88 for child in node.children() {
89 match child.kind() {
90 SyntaxKind::Space => {
91 if child.text().has_linebreak() {
92 is_multiline = true;
93 }
94 }
95 SyntaxKind::BlockComment => {
96 is_multiline |= child.text().has_linebreak();
97 }
98 SyntaxKind::Str => {
99 has_multiline_str |= child.text().has_linebreak();
100 }
101 SyntaxKind::Raw => {
102 let raw = child.cast::<ast::Raw>().expect("raw");
103 has_multiline_str |= !raw.block() && raw.lines().nth(1).is_some();
104 }
105 _ => {}
106 }
107 let res = self.compute_multiline_impl(child);
108 is_multiline |= res.0;
109 has_multiline_str |= res.1;
110 }
111 if is_multiline {
112 self.attrs_mut_of(node).is_multiline = true;
113 }
114 if has_multiline_str {
115 self.attrs_mut_of(node).has_multiline_str = true;
116 }
117 (is_multiline, has_multiline_str)
118 }
119
120 fn compute_no_format(&mut self, root: &SyntaxNode) {
121 self.compute_no_format_impl(root);
122 }
123
124 fn compute_no_format_impl(&mut self, node: &SyntaxNode) {
125 let mut disable_next = false;
126 let mut commented = false;
127 for child in node.children() {
128 match child.kind() {
129 SyntaxKind::LineComment | SyntaxKind::BlockComment => {
130 commented = true;
131 disable_next = child.text().contains("@typstyle off");
133 }
134 SyntaxKind::Space | SyntaxKind::Hash => {}
135 SyntaxKind::Code | SyntaxKind::Math if disable_next => {
136 self.disable_first_nontrivial_child(child);
138 disable_next = false;
139 }
140 _ if disable_next => {
141 if !child.kind().is_trivia() {
143 self.attrs_mut_of(child).is_format_disabled = true;
144 }
145 disable_next = false;
146 }
147 _ => {
148 if !child.kind().is_trivia() {
149 self.compute_no_format_impl(child);
150 }
151 }
152 }
153 }
154 if commented {
155 self.attrs_mut_of(node).has_comment = true;
156 }
157 }
158
159 fn disable_first_nontrivial_child(&mut self, node: &SyntaxNode) {
160 node.children()
161 .find(|it| !matches!(it.kind(), SyntaxKind::Space | SyntaxKind::Hash))
162 .inspect(|it| self.attrs_mut_of(it).is_format_disabled = true);
163 }
164
165 fn compute_math_align_point(&mut self, root: &SyntaxNode) {
166 self.compute_math_align_point_impl(root);
167 }
168
169 fn compute_math_align_point_impl(&mut self, node: &SyntaxNode) -> bool {
170 let node_kind = node.kind();
171 if node_kind == SyntaxKind::MathAlignPoint {
172 return true;
173 }
174 if node_kind.is_trivia() {
175 return false;
176 }
177 let mut has_math_align_point = false;
178 for child in node.children() {
179 has_math_align_point |= self.compute_math_align_point_impl(child);
180 }
181 if has_math_align_point && matches!(node_kind, SyntaxKind::Math | SyntaxKind::MathDelimited)
182 {
183 self.attrs_mut_of(node).has_math_align_point = true;
184 true
185 } else {
186 false
187 }
188 }
189
190 fn attrs_mut_of(&mut self, node: &SyntaxNode) -> &mut Attributes {
191 self.attr_map.entry(node.span()).or_default()
192 }
193}