tinymist_analysis/
track_values.rs1use comemo::Track;
4use ecow::*;
5use tinymist_std::typst::{TypstDocument, TypstPagedDocument};
6use typst::World;
7use typst::engine::{Engine, Route, Sink, Traced};
8use typst::foundations::{Context, Label, Scopes, Styles, Value};
9use typst::introspection::Introspector;
10use typst::model::BibliographyElem;
11use typst::syntax::{LinkedNode, Span, SyntaxKind, SyntaxNode, ast};
12use typst_shim::eval::Vm;
13
14pub fn analyze_expr(world: &dyn World, node: &LinkedNode) -> EcoVec<(Value, Option<Styles>)> {
16 if let Some(parent) = node.parent()
17 && parent.kind() == SyntaxKind::FieldAccess
18 && node.index() > 0
19 {
20 return analyze_expr(world, parent);
21 }
22
23 analyze_expr_(world, node.get())
24}
25
26#[typst_macros::time(span = node.span())]
28pub fn analyze_expr_(world: &dyn World, node: &SyntaxNode) -> EcoVec<(Value, Option<Styles>)> {
29 let Some(expr) = node.cast::<ast::Expr>() else {
30 return eco_vec![];
31 };
32
33 let val = match expr {
34 ast::Expr::None(_) => Value::None,
35 ast::Expr::Auto(_) => Value::Auto,
36 ast::Expr::Bool(v) => Value::Bool(v.get()),
37 ast::Expr::Int(v) => Value::Int(v.get()),
38 ast::Expr::Float(v) => Value::Float(v.get()),
39 ast::Expr::Numeric(v) => Value::numeric(v.get()),
40 ast::Expr::Str(v) => Value::Str(v.get().into()),
41 _ => {
42 if node.kind() == SyntaxKind::Contextual
43 && let Some(child) = node.children().last()
44 {
45 return analyze_expr_(world, child);
46 }
47
48 return typst::trace::<TypstPagedDocument>(world, node.span());
49 }
50 };
51
52 eco_vec![(val, None)]
53}
54
55#[typst_macros::time(span = source.span())]
57pub fn analyze_import_(world: &dyn World, source: &SyntaxNode) -> (Option<Value>, Option<Value>) {
58 let source_span = source.span();
59 let Some((source, _)) = analyze_expr_(world, source).into_iter().next() else {
60 return (None, None);
61 };
62 if source.scope().is_some() {
63 return (Some(source.clone()), Some(source));
64 }
65
66 let introspector = Introspector::default();
67 let traced = Traced::default();
68 let mut sink = Sink::new();
69 let engine = Engine {
70 routines: &typst::ROUTINES,
71 world: world.track(),
72 route: Route::default(),
73 introspector: introspector.track(),
74 traced: traced.track(),
75 sink: sink.track_mut(),
76 };
77
78 let context = Context::none();
79 let mut vm = Vm::new(
80 engine,
81 context.track(),
82 Scopes::new(Some(world.library())),
83 Span::detached(),
84 );
85 let module = match source.clone() {
86 Value::Str(path) => typst_shim::eval::import(&mut vm.engine, &path, source_span)
87 .ok()
88 .map(Value::Module),
89 Value::Module(module) => Some(Value::Module(module)),
90 _ => None,
91 };
92
93 (Some(source), module)
94}
95
96pub struct DynLabel {
98 pub label: Label,
100 pub label_desc: Option<EcoString>,
102 pub detail: Option<EcoString>,
104 pub bib_title: Option<EcoString>,
107}
108
109#[typst_macros::time]
116pub fn analyze_labels(document: &TypstDocument) -> (Vec<DynLabel>, usize) {
117 let mut output = vec![];
118
119 for elem in document.introspector().all() {
121 let Some(label) = elem.label() else { continue };
122 let (is_derived, details) = {
123 let derived = elem
124 .get_by_name("caption")
125 .or_else(|_| elem.get_by_name("body"));
126
127 match derived {
128 Ok(Value::Content(content)) => (true, content.plain_text()),
129 Ok(Value::Str(s)) => (true, s.into()),
130 Ok(_) => (false, elem.plain_text()),
131 Err(_) => (false, elem.plain_text()),
132 }
133 };
134 output.push(DynLabel {
135 label,
136 label_desc: Some(if is_derived {
137 details.clone()
138 } else {
139 eco_format!("{}(..)", elem.func().name())
140 }),
141 detail: Some(details),
142 bib_title: None,
143 });
144 }
145
146 let split = output.len();
147
148 for (label, detail) in BibliographyElem::keys(document.introspector().track()) {
150 output.push(DynLabel {
151 label,
152 label_desc: detail.clone(),
153 detail: detail.clone(),
154 bib_title: detail,
155 });
156 }
157
158 (output, split)
159}