1use comemo::Track;
2use ecow::{EcoString, EcoVec, eco_vec};
3use rustc_hash::FxHashSet;
4use typst::foundations::{AsOutput, Label, Styles, Value};
5use typst::model::{BibliographyElem, FigureElem};
6use typst::syntax::{LinkedNode, SyntaxKind, ast};
7use typst_layout::PagedDocument;
8
9use crate::IdeWorld;
10
11pub fn analyze_expr(
13 world: &dyn IdeWorld,
14 node: &LinkedNode,
15) -> EcoVec<(Value, Option<Styles>)> {
16 let Some(expr) = node.cast::<ast::Expr>() else {
17 return eco_vec![];
18 };
19
20 let val = match expr {
21 ast::Expr::None(_) => Value::None,
22 ast::Expr::Auto(_) => Value::Auto,
23 ast::Expr::Bool(v) => Value::Bool(v.get()),
24 ast::Expr::Int(v) => Value::Int(v.get()),
25 ast::Expr::Float(v) => Value::Float(v.get()),
26 ast::Expr::Numeric(v) => Value::numeric(v.get()),
27 ast::Expr::Str(v) => Value::Str(v.get().into()),
28 _ => {
29 if node.kind() == SyntaxKind::Contextual
30 && let Some(child) = node.children().next_back()
31 {
32 return analyze_expr(world, &child);
33 }
34
35 if let Some(parent) = node.parent()
36 && matches!(
37 parent.kind(),
38 SyntaxKind::FieldAccess | SyntaxKind::MathFieldAccess
39 )
40 && node.index() > 0
41 {
42 return analyze_expr(world, parent);
43 }
44
45 return typst::trace::<PagedDocument>(world.upcast(), node.span());
46 }
47 };
48
49 eco_vec![(val, None)]
50}
51
52pub fn analyze_expr_with_fallback(
57 world: &dyn IdeWorld,
58 node: &LinkedNode,
59) -> Option<Value> {
60 if let Some((value, _)) = analyze_expr(world, node).into_iter().next() {
61 return Some(value);
62 }
63
64 let globals = crate::utils::globals(world, node);
65 let value = match node.cast::<ast::Expr>()? {
66 ast::Expr::Ident(ident) => globals.get(&ident)?.read(),
67 ast::Expr::FieldAccess(access) => match access.target() {
68 ast::Expr::Ident(target) => {
69 globals.get(&target)?.read().scope()?.get(&access.field())?.read()
70 }
71 _ => return None,
72 },
73 _ => return None,
74 };
75
76 Some(value.clone())
77}
78
79pub fn analyze_import(world: &dyn IdeWorld, source: &LinkedNode) -> Option<Value> {
81 let source_span = source.span();
83 let (source, _) = analyze_expr(world, source).into_iter().next()?;
84 if source.scope().is_some() {
85 return Some(source);
86 }
87
88 let Value::Str(path) = source else { return None };
89
90 crate::utils::with_engine(world, |engine| {
91 typst_eval::import(engine, &path, source_span).ok().map(Value::Module)
92 })
93}
94
95pub fn analyze_labels(output: impl AsOutput) -> (Vec<(Label, Option<EcoString>)>, usize) {
105 let introspector = output.as_output().introspector();
106
107 let mut output = vec![];
108 let mut seen_labels = FxHashSet::default();
109
110 for elem in introspector.query_labelled() {
112 let Some(label) = elem.label() else { continue };
113 if !seen_labels.insert(label) {
114 continue;
115 }
116
117 let details = elem
118 .to_packed::<FigureElem>()
119 .and_then(|figure| match figure.caption.as_option() {
120 Some(Some(caption)) => Some(caption.pack_ref()),
121 _ => None,
122 })
123 .unwrap_or(&elem)
124 .get_by_name("body")
125 .ok()
126 .and_then(|field| match field {
127 Value::Content(content) => Some(content),
128 _ => None,
129 })
130 .as_ref()
131 .unwrap_or(&elem)
132 .plain_text();
133 output.push((label, Some(details)));
134 }
135
136 let split = output.len();
137
138 output.extend(BibliographyElem::keys(introspector.track()));
140
141 (output, split)
142}