typst_ide/
analyze.rs

1use comemo::Track;
2use ecow::{eco_vec, EcoString, EcoVec};
3use typst::foundations::{Label, Styles, Value};
4use typst::layout::PagedDocument;
5use typst::model::BibliographyElem;
6use typst::syntax::{ast, LinkedNode, SyntaxKind};
7
8use crate::IdeWorld;
9
10/// Try to determine a set of possible values for an expression.
11pub fn analyze_expr(
12    world: &dyn IdeWorld,
13    node: &LinkedNode,
14) -> EcoVec<(Value, Option<Styles>)> {
15    let Some(expr) = node.cast::<ast::Expr>() else {
16        return eco_vec![];
17    };
18
19    let val = match expr {
20        ast::Expr::None(_) => Value::None,
21        ast::Expr::Auto(_) => Value::Auto,
22        ast::Expr::Bool(v) => Value::Bool(v.get()),
23        ast::Expr::Int(v) => Value::Int(v.get()),
24        ast::Expr::Float(v) => Value::Float(v.get()),
25        ast::Expr::Numeric(v) => Value::numeric(v.get()),
26        ast::Expr::Str(v) => Value::Str(v.get().into()),
27        _ => {
28            if node.kind() == SyntaxKind::Contextual {
29                if let Some(child) = node.children().last() {
30                    return analyze_expr(world, &child);
31                }
32            }
33
34            if let Some(parent) = node.parent() {
35                if parent.kind() == SyntaxKind::FieldAccess && node.index() > 0 {
36                    return analyze_expr(world, parent);
37                }
38            }
39
40            return typst::trace::<PagedDocument>(world.upcast(), node.span());
41        }
42    };
43
44    eco_vec![(val, None)]
45}
46
47/// Tries to load a module from the given `source` node.
48pub fn analyze_import(world: &dyn IdeWorld, source: &LinkedNode) -> Option<Value> {
49    // Use span in the node for resolving imports with relative paths.
50    let source_span = source.span();
51    let (source, _) = analyze_expr(world, source).into_iter().next()?;
52    if source.scope().is_some() {
53        return Some(source);
54    }
55
56    let Value::Str(path) = source else { return None };
57
58    crate::utils::with_engine(world, |engine| {
59        typst_eval::import(engine, &path, source_span).ok().map(Value::Module)
60    })
61}
62
63/// Find all labels and details for them.
64///
65/// Returns:
66/// - All labels and descriptions for them, if available
67/// - A split offset: All labels before this offset belong to nodes, all after
68///   belong to a bibliography.
69pub fn analyze_labels(
70    document: &PagedDocument,
71) -> (Vec<(Label, Option<EcoString>)>, usize) {
72    let mut output = vec![];
73
74    // Labels in the document.
75    for elem in document.introspector.all() {
76        let Some(label) = elem.label() else { continue };
77        let details = elem
78            .get_by_name("caption")
79            .or_else(|_| elem.get_by_name("body"))
80            .ok()
81            .and_then(|field| match field {
82                Value::Content(content) => Some(content),
83                _ => None,
84            })
85            .as_ref()
86            .unwrap_or(elem)
87            .plain_text();
88        output.push((label, Some(details)));
89    }
90
91    let split = output.len();
92
93    // Bibliography keys.
94    output.extend(BibliographyElem::keys(document.introspector.track()));
95
96    (output, split)
97}