1use rowan::{TextRange, TextSize};
4
5use crate::ast::command_name;
6use crate::semantic::define::scan_definitions;
7use crate::semantic::signature::{
8 ArgKind, ArgumentDomain, Signatures, match_arg_slot, match_verbatim_arg_slot,
9};
10use crate::syntax::{SyntaxElement, SyntaxKind, SyntaxNode};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum Mode {
15 Math,
16 Text,
17 Unknown,
18}
19
20impl From<ArgumentDomain> for Mode {
21 fn from(domain: ArgumentDomain) -> Self {
22 match domain {
23 ArgumentDomain::Math => Self::Math,
24 ArgumentDomain::Text => Self::Text,
25 ArgumentDomain::Unknown => Self::Unknown,
26 }
27 }
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31struct ModeRange {
32 range: TextRange,
33 mode: Mode,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct ModeIndex {
40 ranges: Vec<ModeRange>,
41}
42
43pub fn argument_domain(group: &SyntaxNode) -> ArgumentDomain {
46 if !matches!(group.kind(), SyntaxKind::GROUP | SyntaxKind::OPTIONAL) {
47 return ArgumentDomain::Unknown;
48 }
49 let Some(owner) = group.parent() else {
50 return ArgumentDomain::Unknown;
51 };
52 let root = owner.ancestors().last().unwrap_or_else(|| owner.clone());
53 let user = scan_definitions(&root);
54 let signatures = Signatures::new(&user);
55 let args = match owner.kind() {
56 SyntaxKind::COMMAND => command_name(&owner)
57 .and_then(|name| signatures.command(&name))
58 .map(|sig| sig.args.as_ref()),
59 SyntaxKind::BEGIN => environment_name(&owner)
60 .as_deref()
61 .and_then(|name| signatures.environment(name))
62 .map(|sig| sig.args.as_ref()),
63 _ => None,
64 };
65 let mut slot = 0usize;
66 for candidate in owner.children_with_tokens() {
67 match candidate {
68 SyntaxElement::Token(token) if token.kind() == SyntaxKind::VERB => {
69 if let Some(args) = args
70 && token.text().starts_with('{')
71 {
72 match_verbatim_arg_slot(args, &mut slot);
73 }
74 }
75 SyntaxElement::Node(candidate)
76 if matches!(candidate.kind(), SyntaxKind::GROUP | SyntaxKind::OPTIONAL) =>
77 {
78 let kind = if candidate.kind() == SyntaxKind::OPTIONAL {
79 ArgKind::Bracket
80 } else {
81 ArgKind::Brace
82 };
83 let domain = args
84 .and_then(|args| match_arg_slot(args, &mut slot, kind))
85 .map_or(ArgumentDomain::Unknown, |spec| spec.domain);
86 if candidate == *group {
87 return domain;
88 }
89 }
90 _ => {}
91 }
92 }
93 ArgumentDomain::Unknown
94}
95
96impl ModeIndex {
97 pub fn build(root: &SyntaxNode) -> Self {
98 let mut ranges = Vec::new();
99 let user = scan_definitions(root);
100 let signatures = Signatures::new(&user);
101 walk(root, Mode::Text, &signatures, &mut ranges);
102 Self { ranges }
103 }
104
105 pub fn mode_at(&self, offset: usize) -> Mode {
110 let offset = TextSize::from(offset as u32);
111 match self
112 .ranges
113 .binary_search_by(|entry| entry.range.start().cmp(&offset))
114 {
115 Ok(i) => self.ranges[i].mode,
116 Err(0) => Mode::Unknown,
117 Err(i) => {
118 if self.ranges[i - 1].range.contains(offset) {
119 self.ranges[i - 1].mode
120 } else {
121 Mode::Unknown
122 }
123 }
124 }
125 }
126}
127
128fn walk(
129 node: &SyntaxNode,
130 inherited: Mode,
131 signatures: &Signatures<'_>,
132 ranges: &mut Vec<ModeRange>,
133) {
134 let mode = match node.kind() {
135 SyntaxKind::MATH => Mode::Math,
136 SyntaxKind::NAME_GROUP => Mode::Unknown,
137 _ => inherited,
138 };
139
140 let argument_modes = match node.kind() {
141 SyntaxKind::COMMAND => command_argument_modes(node, signatures),
142 SyntaxKind::BEGIN => environment_argument_modes(node, signatures),
143 _ => Vec::new(),
144 };
145 let mut argument = 0usize;
146
147 for element in node.children_with_tokens() {
148 match element {
149 SyntaxElement::Token(token) => push_range(ranges, token.text_range(), mode),
150 SyntaxElement::Node(child) => {
151 let child_mode = if matches!(child.kind(), SyntaxKind::GROUP | SyntaxKind::OPTIONAL)
152 && matches!(node.kind(), SyntaxKind::COMMAND | SyntaxKind::BEGIN)
153 {
154 let mode = argument_modes
155 .get(argument)
156 .copied()
157 .unwrap_or(Mode::Unknown);
158 argument += 1;
159 mode
160 } else {
161 mode
162 };
163 walk(&child, child_mode, signatures, ranges);
164 }
165 }
166 }
167}
168
169fn command_argument_modes(node: &SyntaxNode, signatures: &Signatures<'_>) -> Vec<Mode> {
170 let args = command_name(node)
171 .and_then(|name| signatures.command(&name))
172 .map(|sig| sig.args.as_ref());
173 match_groups(node, args, false)
174}
175
176fn environment_argument_modes(begin: &SyntaxNode, signatures: &Signatures<'_>) -> Vec<Mode> {
177 let name = environment_name(begin);
178 let args = name
179 .as_deref()
180 .and_then(|name| signatures.environment(name))
181 .map(|sig| sig.args.as_ref());
182 match_groups(begin, args, false)
183}
184
185fn environment_name(begin: &SyntaxNode) -> Option<String> {
186 begin
187 .children()
188 .find(|child| child.kind() == SyntaxKind::NAME_GROUP)
189 .map(|group| group.text().to_string())
190 .and_then(|text| text.strip_prefix('{')?.strip_suffix('}').map(str::to_owned))
191}
192
193fn match_groups(
194 node: &SyntaxNode,
195 args: Option<&[crate::semantic::ArgSpec]>,
196 skip_first: bool,
197) -> Vec<Mode> {
198 let mut slot = 0usize;
199 let mut first = skip_first;
200 let mut modes = Vec::new();
201 for child in node.children_with_tokens() {
202 match child {
203 SyntaxElement::Token(token) if token.kind() == SyntaxKind::VERB => {
204 if let Some(args) = args
205 && token.text().starts_with('{')
206 {
207 match_verbatim_arg_slot(args, &mut slot);
208 }
209 }
210 SyntaxElement::Node(group)
211 if matches!(group.kind(), SyntaxKind::GROUP | SyntaxKind::OPTIONAL) =>
212 {
213 if first {
214 first = false;
215 modes.push(Mode::Unknown);
216 continue;
217 }
218 let kind = if group.kind() == SyntaxKind::OPTIONAL {
219 ArgKind::Bracket
220 } else {
221 ArgKind::Brace
222 };
223 modes.push(
224 args.and_then(|args| match_arg_slot(args, &mut slot, kind))
225 .map_or(Mode::Unknown, |spec| spec.domain.into()),
226 );
227 }
228 _ => {}
229 }
230 }
231 modes
232}
233
234fn push_range(ranges: &mut Vec<ModeRange>, range: TextRange, mode: Mode) {
235 if range.is_empty() {
236 return;
237 }
238 if let Some(last) = ranges.last_mut()
239 && last.mode == mode
240 && last.range.end() == range.start()
241 {
242 last.range = TextRange::new(last.range.start(), range.end());
243 } else {
244 ranges.push(ModeRange { range, mode });
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use crate::parser::parse;
252
253 fn modes(source: &str, needles: &[&str]) -> Vec<Mode> {
254 let parsed = parse(source);
255 needles
256 .iter()
257 .scan(0usize, |from, needle| {
258 let offset = source[*from..].find(needle).unwrap() + *from;
259 *from = offset + needle.len();
260 Some(ModeIndex::build(&parsed.syntax()).mode_at(offset))
261 })
262 .collect()
263 }
264
265 #[test]
266 fn nested_domains_override_ambient_mode() {
267 assert_eq!(
268 modes(
269 r"$a \frac{b \text{c $d$}}{e} \unknown{f}$",
270 &["a", "b", "c", "d", "e", "f"]
271 ),
272 vec![
273 Mode::Math,
274 Mode::Math,
275 Mode::Text,
276 Mode::Math,
277 Mode::Math,
278 Mode::Unknown
279 ]
280 );
281 }
282
283 #[test]
284 fn omitted_optional_slots_keep_their_positions() {
285 assert_eq!(
286 modes(r"\sqrt{x}\sqrt[n]{y}", &["x", "n", "y"]),
287 vec![Mode::Math; 3]
288 );
289 }
290
291 #[test]
292 fn known_math_arguments_work_in_text_and_unknown_arguments_override_math() {
293 assert_eq!(
294 modes(
295 r"before \ensuremath{x_i} $\foo{x_i}$",
296 &["before", "x_i", "x_i"]
297 ),
298 vec![Mode::Text, Mode::Math, Mode::Unknown]
299 );
300 }
301
302 #[test]
303 fn redefined_builtin_arguments_are_unknown() {
304 assert_eq!(
305 modes(
306 r"\renewcommand{\text}[1]{\ensuremath{#1}} $\text{5-10}$",
307 &["5-10"]
308 ),
309 vec![Mode::Unknown]
310 );
311 }
312
313 #[test]
314 fn prose_arguments_establish_text_mode() {
315 assert_eq!(
316 modes(
317 r"$\textbf{pages 5-10} \section[short 5-10]{pages 5-10} \footnote{pages 5-10}$",
318 &["pages 5-10", "short 5-10", "pages 5-10", "pages 5-10"]
319 ),
320 vec![Mode::Text; 4]
321 );
322 }
323
324 #[test]
325 fn environment_name_and_header_are_not_body_math() {
326 assert_eq!(
327 modes(r"\begin{align}[t]x\end{align}", &["align", "t", "x"]),
328 vec![Mode::Unknown, Mode::Unknown, Mode::Math]
329 );
330 }
331
332 #[test]
333 fn boundaries_belong_to_the_token_starting_there() {
334 let parsed = parse(r"a\ensuremath{b}c");
335 let index = ModeIndex::build(&parsed.syntax());
336 assert_eq!(index.mode_at(0), Mode::Text);
337 assert_eq!(index.mode_at(13), Mode::Math);
338 assert_eq!(index.mode_at(15), Mode::Text);
339 assert_eq!(index.mode_at(16), Mode::Unknown);
340 }
341}