summaryrefslogtreecommitdiff
path: root/crates/typst-ide/src/analyze.rs
blob: 2d48039b6b90543b223de5f84200087857f026fa (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
use comemo::Track;
use ecow::{eco_vec, EcoString, EcoVec};
use typst::diag::DelayedErrors;
use typst::eval::{Route, Tracer, Vm};
use typst::foundations::{Label, Scopes, Value};
use typst::introspection::{Introspector, Locator};
use typst::layout::{Frame, Vt};
use typst::model::BibliographyElem;
use typst::syntax::{ast, LinkedNode, Span, SyntaxKind};
use typst::World;

/// Try to determine a set of possible values for an expression.
pub fn analyze_expr(world: &dyn World, node: &LinkedNode) -> EcoVec<Value> {
    match node.cast::<ast::Expr>() {
        Some(ast::Expr::None(_)) => eco_vec![Value::None],
        Some(ast::Expr::Auto(_)) => eco_vec![Value::Auto],
        Some(ast::Expr::Bool(v)) => eco_vec![Value::Bool(v.get())],
        Some(ast::Expr::Int(v)) => eco_vec![Value::Int(v.get())],
        Some(ast::Expr::Float(v)) => eco_vec![Value::Float(v.get())],
        Some(ast::Expr::Numeric(v)) => eco_vec![Value::numeric(v.get())],
        Some(ast::Expr::Str(v)) => eco_vec![Value::Str(v.get().into())],

        Some(ast::Expr::FieldAccess(access)) => {
            let Some(child) = node.children().next() else { return eco_vec![] };
            analyze_expr(world, &child)
                .into_iter()
                .filter_map(|target| target.field(&access.field()).ok())
                .collect()
        }

        Some(_) => {
            if let Some(parent) = node.parent() {
                if parent.kind() == SyntaxKind::FieldAccess && node.index() > 0 {
                    return analyze_expr(world, parent);
                }
            }

            let mut tracer = Tracer::new();
            tracer.inspect(node.span());
            typst::compile(world, &mut tracer).ok();
            tracer.values()
        }

        _ => eco_vec![],
    }
}

/// Try to load a module from the current source file.
pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option<Value> {
    let id = source.span().id()?;
    let source = analyze_expr(world, source).into_iter().next()?;
    if source.scope().is_some() {
        return Some(source);
    }

    let mut locator = Locator::default();
    let introspector = Introspector::default();
    let mut delayed = DelayedErrors::new();
    let mut tracer = Tracer::new();
    let vt = Vt {
        world: world.track(),
        introspector: introspector.track(),
        locator: &mut locator,
        delayed: delayed.track_mut(),
        tracer: tracer.track_mut(),
    };

    let route = Route::default();
    let mut vm = Vm::new(vt, route.track(), Some(id), Scopes::new(Some(world.library())));
    typst::eval::import(&mut vm, source, Span::detached(), true)
        .ok()
        .map(Value::Module)
}

/// Find all labels and details for them.
///
/// Returns:
/// - All labels and descriptions for them, if available
/// - A split offset: All labels before this offset belong to nodes, all after
///   belong to a bibliography.
pub fn analyze_labels(frames: &[Frame]) -> (Vec<(Label, Option<EcoString>)>, usize) {
    let mut output = vec![];
    let introspector = Introspector::new(frames);

    // Labels in the document.
    for elem in introspector.all() {
        let Some(label) = elem.label() else { continue };
        let details = elem
            .get_by_name("caption")
            .or_else(|| elem.get_by_name("body"))
            .and_then(|field| match field {
                Value::Content(content) => Some(content),
                _ => None,
            })
            .as_ref()
            .unwrap_or(elem)
            .plain_text();
        output.push((label, Some(details)));
    }

    let split = output.len();

    // Bibliography keys.
    for (key, detail) in BibliographyElem::keys(introspector.track()) {
        output.push((Label::new(&key), detail));
    }

    (output, split)
}