Tracing-based expression tooltips

This commit is contained in:
Laurenz 2023-01-27 12:05:00 +01:00
parent c56299c6bd
commit 43ef60c09c
13 changed files with 222 additions and 51 deletions

View File

@ -112,6 +112,7 @@ pub fn eval(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
let Spanned { v: text, span } = args.expect::<Spanned<String>>("source")?; let Spanned { v: text, span } = args.expect::<Spanned<String>>("source")?;
let source = Source::synthesized(text, span); let source = Source::synthesized(text, span);
let route = model::Route::default(); let route = model::Route::default();
let module = model::eval(vm.world(), route.track(), &source)?; let mut tracer = model::Tracer::default();
let module = model::eval(vm.world(), route.track(), tracer.track_mut(), &source)?;
Ok(Value::Content(module.content())) Ok(Value::Content(module.content()))
} }

38
src/ide/analyze.rs Normal file
View File

@ -0,0 +1,38 @@
use comemo::Track;
use crate::model::{eval, Route, Tracer, Value};
use crate::syntax::{ast, LinkedNode, SyntaxKind};
use crate::World;
/// Try to determine a set of possible values for an expression.
pub fn analyze(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Value> {
match node.cast::<ast::Expr>() {
Some(ast::Expr::Ident(_) | ast::Expr::MathIdent(_)) => {
if let Some(parent) = node.parent() {
if parent.kind() == SyntaxKind::FieldAccess && node.index() > 0 {
return analyze(world, parent);
}
}
let span = node.span();
let source = world.source(span.source());
let route = Route::default();
let mut tracer = Tracer::new(Some(span));
eval(world.track(), route.track(), tracer.track_mut(), source).ok();
return tracer.finish();
}
Some(ast::Expr::FieldAccess(access)) => {
if let Some(child) = node.children().next() {
return analyze(world, &child)
.into_iter()
.filter_map(|target| target.field(&access.field()).ok())
.collect();
}
}
_ => {}
}
vec![]
}

View File

@ -1,9 +1,11 @@
//! Capabilities for IDE support. //! Capabilities for IDE support.
mod analyze;
mod complete; mod complete;
mod highlight; mod highlight;
mod tooltip; mod tooltip;
pub use analyze::*;
pub use complete::*; pub use complete::*;
pub use highlight::*; pub use highlight::*;
pub use tooltip::*; pub use tooltip::*;

View File

@ -1,40 +1,71 @@
use if_chain::if_chain; use if_chain::if_chain;
use unicode_segmentation::UnicodeSegmentation;
use super::{plain_docs_sentence, summarize_font_family}; use super::{analyze, plain_docs_sentence, summarize_font_family};
use crate::model::{CastInfo, Value}; use crate::model::{CastInfo, Tracer, Value};
use crate::syntax::ast; use crate::syntax::ast;
use crate::syntax::{LinkedNode, Source, SyntaxKind}; use crate::syntax::{LinkedNode, Source, SyntaxKind};
use crate::World; use crate::World;
/// Describe the item under the cursor. /// Describe the item under the cursor.
pub fn tooltip(world: &dyn World, source: &Source, cursor: usize) -> Option<String> { pub fn tooltip(
world: &(dyn World + 'static),
source: &Source,
cursor: usize,
) -> Option<String> {
let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?; let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?;
function_tooltip(world, &leaf) named_param_tooltip(world, &leaf)
.or_else(|| named_param_tooltip(world, &leaf))
.or_else(|| font_family_tooltip(world, &leaf)) .or_else(|| font_family_tooltip(world, &leaf))
.or_else(|| expr_tooltip(world, &leaf))
} }
/// Tooltip for a function or set rule name. /// Tooltip for a hovered expression.
fn function_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> { fn expr_tooltip(world: &(dyn World + 'static), leaf: &LinkedNode) -> Option<String> {
if_chain! { if !leaf.is::<ast::Expr>() {
if let Some(ident) = leaf.cast::<ast::Ident>(); return None;
if matches!( }
leaf.parent_kind(),
Some(SyntaxKind::FuncCall | SyntaxKind::SetRule), let values = analyze(world, leaf);
); if let [value] = values.as_slice() {
if let Some(Value::Func(func)) = world.library().global.scope().get(&ident); if let Some(docs) = value.docs() {
if let Some(info) = func.info(); return Some(plain_docs_sentence(docs));
then {
return Some(plain_docs_sentence(info.docs));
} }
} }
None let mut tooltip = String::new();
let mut iter = values.into_iter().enumerate();
for (i, value) in (&mut iter).take(Tracer::MAX - 1) {
if i > 0 && !tooltip.is_empty() {
tooltip.push_str(", ");
}
let repr = value.repr();
let repr = repr.as_str();
let len = repr.len();
if len <= 40 {
tooltip.push_str(repr);
} else {
let mut graphemes = repr.graphemes(true);
let r = graphemes.next_back().map_or(0, str::len);
let l = graphemes.take(40).map(str::len).sum();
tooltip.push_str(&repr[..l]);
tooltip.push_str("...");
tooltip.push_str(&repr[len - r..]);
}
}
if iter.next().is_some() {
tooltip.push_str(", ...");
}
(!tooltip.is_empty()).then(|| tooltip)
} }
/// Tooltips for components of a named parameter. /// Tooltips for components of a named parameter.
fn named_param_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> { fn named_param_tooltip(
world: &(dyn World + 'static),
leaf: &LinkedNode,
) -> Option<String> {
let (info, named) = if_chain! { let (info, named) = if_chain! {
// Ensure that we are in a named pair in the arguments to a function // Ensure that we are in a named pair in the arguments to a function
// call or set rule. // call or set rule.
@ -92,7 +123,10 @@ fn find_string_doc(info: &CastInfo, string: &str) -> Option<&'static str> {
} }
/// Tooltip for font family. /// Tooltip for font family.
fn font_family_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<String> { fn font_family_tooltip(
world: &(dyn World + 'static),
leaf: &LinkedNode,
) -> Option<String> {
if_chain! { if_chain! {
// Ensure that we are on top of a string. // Ensure that we are on top of a string.
if let Some(string) = leaf.cast::<ast::Str>(); if let Some(string) = leaf.cast::<ast::Str>();

View File

@ -51,6 +51,7 @@ pub mod syntax;
use std::path::Path; use std::path::Path;
use comemo::{Prehashed, Track}; use comemo::{Prehashed, Track};
use model::Tracer;
use crate::diag::{FileResult, SourceResult}; use crate::diag::{FileResult, SourceResult};
use crate::doc::Document; use crate::doc::Document;
@ -63,7 +64,8 @@ use crate::util::Buffer;
pub fn compile(world: &(dyn World + 'static), source: &Source) -> SourceResult<Document> { pub fn compile(world: &(dyn World + 'static), source: &Source) -> SourceResult<Document> {
// Evaluate the source file into a module. // Evaluate the source file into a module.
let route = Route::default(); let route = Route::default();
let module = model::eval(world.track(), route.track(), source)?; let mut tracer = Tracer::default();
let module = model::eval(world.track(), route.track(), tracer.track_mut(), source)?;
// Typeset the module's contents. // Typeset the module's contents.
model::typeset(world.track(), &module.content()) model::typeset(world.track(), &module.content())

View File

@ -136,7 +136,7 @@ impl Array {
} }
/// Return the first matching element. /// Return the first matching element.
pub fn find(&self, vm: &Vm, func: Func) -> SourceResult<Option<Value>> { pub fn find(&self, vm: &mut Vm, func: Func) -> SourceResult<Option<Value>> {
if func.argc().map_or(false, |count| count != 1) { if func.argc().map_or(false, |count| count != 1) {
bail!(func.span(), "function must have exactly one parameter"); bail!(func.span(), "function must have exactly one parameter");
} }
@ -151,7 +151,7 @@ impl Array {
} }
/// Return the index of the first matching element. /// Return the index of the first matching element.
pub fn position(&self, vm: &Vm, func: Func) -> SourceResult<Option<i64>> { pub fn position(&self, vm: &mut Vm, func: Func) -> SourceResult<Option<i64>> {
if func.argc().map_or(false, |count| count != 1) { if func.argc().map_or(false, |count| count != 1) {
bail!(func.span(), "function must have exactly one parameter"); bail!(func.span(), "function must have exactly one parameter");
} }
@ -167,7 +167,7 @@ impl Array {
/// Return a new array with only those elements for which the function /// Return a new array with only those elements for which the function
/// returns true. /// returns true.
pub fn filter(&self, vm: &Vm, func: Func) -> SourceResult<Self> { pub fn filter(&self, vm: &mut Vm, func: Func) -> SourceResult<Self> {
if func.argc().map_or(false, |count| count != 1) { if func.argc().map_or(false, |count| count != 1) {
bail!(func.span(), "function must have exactly one parameter"); bail!(func.span(), "function must have exactly one parameter");
} }
@ -182,7 +182,7 @@ impl Array {
} }
/// Transform each item in the array with a function. /// Transform each item in the array with a function.
pub fn map(&self, vm: &Vm, func: Func) -> SourceResult<Self> { pub fn map(&self, vm: &mut Vm, func: Func) -> SourceResult<Self> {
if func.argc().map_or(false, |count| !(1..=2).contains(&count)) { if func.argc().map_or(false, |count| !(1..=2).contains(&count)) {
bail!(func.span(), "function must have one or two parameters"); bail!(func.span(), "function must have one or two parameters");
} }
@ -201,7 +201,7 @@ impl Array {
} }
/// Fold all of the array's elements into one with a function. /// Fold all of the array's elements into one with a function.
pub fn fold(&self, vm: &Vm, init: Value, func: Func) -> SourceResult<Value> { pub fn fold(&self, vm: &mut Vm, init: Value, func: Func) -> SourceResult<Value> {
if func.argc().map_or(false, |count| count != 2) { if func.argc().map_or(false, |count| count != 2) {
bail!(func.span(), "function must have exactly two parameters"); bail!(func.span(), "function must have exactly two parameters");
} }
@ -214,7 +214,7 @@ impl Array {
} }
/// Whether any element matches. /// Whether any element matches.
pub fn any(&self, vm: &Vm, func: Func) -> SourceResult<bool> { pub fn any(&self, vm: &mut Vm, func: Func) -> SourceResult<bool> {
if func.argc().map_or(false, |count| count != 1) { if func.argc().map_or(false, |count| count != 1) {
bail!(func.span(), "function must have exactly one parameter"); bail!(func.span(), "function must have exactly one parameter");
} }
@ -229,7 +229,7 @@ impl Array {
} }
/// Whether all elements match. /// Whether all elements match.
pub fn all(&self, vm: &Vm, func: Func) -> SourceResult<bool> { pub fn all(&self, vm: &mut Vm, func: Func) -> SourceResult<bool> {
if func.argc().map_or(false, |count| count != 1) { if func.argc().map_or(false, |count| count != 1) {
bail!(func.span(), "function must have exactly one parameter"); bail!(func.span(), "function must have exactly one parameter");
} }

View File

@ -105,7 +105,7 @@ impl Dict {
} }
/// Transform each pair in the dictionary with a function. /// Transform each pair in the dictionary with a function.
pub fn map(&self, vm: &Vm, func: Func) -> SourceResult<Array> { pub fn map(&self, vm: &mut Vm, func: Func) -> SourceResult<Array> {
if func.argc().map_or(false, |count| count != 2) { if func.argc().map_or(false, |count| count != 2) {
bail!(func.span(), "function must have exactly two parameters"); bail!(func.span(), "function must have exactly two parameters");
} }

View File

@ -4,7 +4,7 @@ use std::collections::BTreeMap;
use std::mem; use std::mem;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use comemo::{Track, Tracked}; use comemo::{Track, Tracked, TrackedMut};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use super::{ use super::{
@ -17,7 +17,7 @@ use crate::diag::{
use crate::geom::{Abs, Angle, Em, Fr, Ratio}; use crate::geom::{Abs, Angle, Em, Fr, Ratio};
use crate::syntax::ast::AstNode; use crate::syntax::ast::AstNode;
use crate::syntax::{ast, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode}; use crate::syntax::{ast, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode};
use crate::util::{EcoString, PathExt}; use crate::util::PathExt;
use crate::World; use crate::World;
const MAX_ITERATIONS: usize = 10_000; const MAX_ITERATIONS: usize = 10_000;
@ -28,6 +28,7 @@ const MAX_CALL_DEPTH: usize = 256;
pub fn eval( pub fn eval(
world: Tracked<dyn World>, world: Tracked<dyn World>,
route: Tracked<Route>, route: Tracked<Route>,
tracer: TrackedMut<Tracer>,
source: &Source, source: &Source,
) -> SourceResult<Module> { ) -> SourceResult<Module> {
// Prevent cyclic evaluation. // Prevent cyclic evaluation.
@ -44,7 +45,7 @@ pub fn eval(
// Evaluate the module. // Evaluate the module.
let route = unsafe { Route::insert(route, id) }; let route = unsafe { Route::insert(route, id) };
let scopes = Scopes::new(Some(library)); let scopes = Scopes::new(Some(library));
let mut vm = Vm::new(world, route.track(), id, scopes, 0); let mut vm = Vm::new(world, route.track(), tracer, id, scopes, 0);
let result = source.ast()?.eval(&mut vm); let result = source.ast()?.eval(&mut vm);
// Handle control flow. // Handle control flow.
@ -68,6 +69,8 @@ pub struct Vm<'a> {
pub(super) items: LangItems, pub(super) items: LangItems,
/// The route of source ids the VM took to reach its current location. /// The route of source ids the VM took to reach its current location.
pub(super) route: Tracked<'a, Route>, pub(super) route: Tracked<'a, Route>,
/// The tracer for inspection of the values an expression produces.
pub(super) tracer: TrackedMut<'a, Tracer>,
/// The current location. /// The current location.
pub(super) location: SourceId, pub(super) location: SourceId,
/// A control flow event that is currently happening. /// A control flow event that is currently happening.
@ -76,6 +79,8 @@ pub struct Vm<'a> {
pub(super) scopes: Scopes<'a>, pub(super) scopes: Scopes<'a>,
/// The current call depth. /// The current call depth.
pub(super) depth: usize, pub(super) depth: usize,
/// A span that is currently traced.
pub(super) traced: Option<Span>,
} }
impl<'a> Vm<'a> { impl<'a> Vm<'a> {
@ -83,18 +88,22 @@ impl<'a> Vm<'a> {
pub(super) fn new( pub(super) fn new(
world: Tracked<'a, dyn World>, world: Tracked<'a, dyn World>,
route: Tracked<'a, Route>, route: Tracked<'a, Route>,
tracer: TrackedMut<'a, Tracer>,
location: SourceId, location: SourceId,
scopes: Scopes<'a>, scopes: Scopes<'a>,
depth: usize, depth: usize,
) -> Self { ) -> Self {
let traced = tracer.span(location);
Self { Self {
world, world,
items: world.library().items.clone(), items: world.library().items.clone(),
route, route,
tracer,
location, location,
flow: None, flow: None,
scopes, scopes,
depth, depth,
traced,
} }
} }
@ -103,6 +112,15 @@ impl<'a> Vm<'a> {
self.world self.world
} }
/// Define a variable in the current scope.
pub fn define(&mut self, var: ast::Ident, value: impl Into<Value>) {
let value = value.into();
if self.traced == Some(var.span()) {
self.tracer.trace(value.clone());
}
self.scopes.top.define(var.take(), value);
}
/// Resolve a user-entered path to be relative to the compilation /// Resolve a user-entered path to be relative to the compilation
/// environment's root. /// environment's root.
pub fn locate(&self, path: &str) -> StrResult<PathBuf> { pub fn locate(&self, path: &str) -> StrResult<PathBuf> {
@ -182,6 +200,47 @@ impl Route {
} }
} }
/// Traces which values existed for the expression with the given span.
#[derive(Default, Clone)]
pub struct Tracer {
span: Option<Span>,
values: Vec<Value>,
}
impl Tracer {
/// The maximum number of traced items.
pub const MAX: usize = 10;
/// Create a new tracer, possibly with a span under inspection.
pub fn new(span: Option<Span>) -> Self {
Self { span, values: vec![] }
}
/// Get the traced values.
pub fn finish(self) -> Vec<Value> {
self.values
}
}
#[comemo::track]
impl Tracer {
/// The traced span if it is part of the given source file.
fn span(&self, id: SourceId) -> Option<Span> {
if self.span.map(Span::source) == Some(id) {
self.span
} else {
None
}
}
/// Trace a value for the span.
fn trace(&mut self, v: Value) {
if self.values.len() < Self::MAX {
self.values.push(v);
}
}
}
/// Evaluate an expression. /// Evaluate an expression.
pub(super) trait Eval { pub(super) trait Eval {
/// The output of evaluating the expression. /// The output of evaluating the expression.
@ -259,12 +318,11 @@ impl Eval for ast::Expr {
error!(span, "{} is only allowed directly in code and content blocks", name) error!(span, "{} is only allowed directly in code and content blocks", name)
}; };
match self { let v = match self {
Self::Text(v) => v.eval(vm).map(Value::Content), Self::Text(v) => v.eval(vm).map(Value::Content),
Self::Space(v) => v.eval(vm).map(Value::Content), Self::Space(v) => v.eval(vm).map(Value::Content),
Self::Linebreak(v) => v.eval(vm).map(Value::Content), Self::Linebreak(v) => v.eval(vm).map(Value::Content),
Self::Parbreak(v) => v.eval(vm).map(Value::Content), Self::Parbreak(v) => v.eval(vm).map(Value::Content),
Self::Symbol(v) => v.eval(vm).map(Value::Content),
Self::Escape(v) => v.eval(vm), Self::Escape(v) => v.eval(vm),
Self::Shorthand(v) => v.eval(vm), Self::Shorthand(v) => v.eval(vm),
Self::SmartQuote(v) => v.eval(vm).map(Value::Content), Self::SmartQuote(v) => v.eval(vm).map(Value::Content),
@ -319,6 +377,8 @@ impl Eval for ast::Expr {
}? }?
.spanned(span); .spanned(span);
if vm.traced == Some(span) {
vm.tracer.trace(v.clone());
} }
Ok(v) Ok(v)
@ -1049,7 +1109,7 @@ impl Eval for ast::LetBinding {
Some(expr) => expr.eval(vm)?, Some(expr) => expr.eval(vm)?,
None => Value::None, None => Value::None,
}; };
vm.scopes.top.define(self.binding().take(), value); vm.define(self.binding(), value);
Ok(Value::None) Ok(Value::None)
} }
} }
@ -1183,7 +1243,7 @@ impl Eval for ast::ForLoop {
#[allow(unused_parens)] #[allow(unused_parens)]
for ($($value),*) in $iter { for ($($value),*) in $iter {
$(vm.scopes.top.define($binding.clone(), $value);)* $(vm.define($binding.clone(), $value);)*
let body = self.body(); let body = self.body();
let value = body.eval(vm)?; let value = body.eval(vm)?;
@ -1206,8 +1266,8 @@ impl Eval for ast::ForLoop {
let iter = self.iter().eval(vm)?; let iter = self.iter().eval(vm)?;
let pattern = self.pattern(); let pattern = self.pattern();
let key = pattern.key().map(ast::Ident::take); let key = pattern.key();
let value = pattern.value().take(); let value = pattern.value();
match (key, value, iter) { match (key, value, iter) {
(None, v, Value::Str(string)) => { (None, v, Value::Str(string)) => {
@ -1271,7 +1331,7 @@ impl Eval for ast::ModuleImport {
let mut errors = vec![]; let mut errors = vec![];
for ident in idents { for ident in idents {
if let Some(value) = module.scope().get(&ident) { if let Some(value) = module.scope().get(&ident) {
vm.scopes.top.define(ident.take(), value.clone()); vm.define(ident, value.clone());
} else { } else {
errors.push(error!(ident.span(), "unresolved import")); errors.push(error!(ident.span(), "unresolved import"));
} }

View File

@ -2,11 +2,11 @@ use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::sync::Arc; use std::sync::Arc;
use comemo::{Track, Tracked}; use comemo::{Track, Tracked, TrackedMut};
use super::{ use super::{
Args, CastInfo, Dict, Eval, Flow, Node, NodeId, Route, Scope, Scopes, Selector, Args, CastInfo, Dict, Eval, Flow, Node, NodeId, Route, Scope, Scopes, Selector,
StyleMap, Value, Vm, StyleMap, Tracer, Value, Vm,
}; };
use crate::diag::{bail, SourceResult, StrResult}; use crate::diag::{bail, SourceResult, StrResult};
use crate::syntax::ast::{self, AstNode, Expr}; use crate::syntax::ast::{self, AstNode, Expr};
@ -110,7 +110,7 @@ impl Func {
} }
/// Call the function with the given arguments. /// Call the function with the given arguments.
pub fn call(&self, vm: &Vm, mut args: Args) -> SourceResult<Value> { pub fn call(&self, vm: &mut Vm, mut args: Args) -> SourceResult<Value> {
let value = match self.0.as_ref() { let value = match self.0.as_ref() {
Repr::Native(native) => (native.func)(vm, &mut args)?, Repr::Native(native) => (native.func)(vm, &mut args)?,
Repr::Closure(closure) => closure.call(vm, self, &mut args)?, Repr::Closure(closure) => closure.call(vm, self, &mut args)?,
@ -132,8 +132,9 @@ impl Func {
let route = Route::default(); let route = Route::default();
let id = SourceId::detached(); let id = SourceId::detached();
let scopes = Scopes::new(None); let scopes = Scopes::new(None);
let vm = Vm::new(world, route.track(), id, scopes, 0); let mut tracer = Tracer::default();
self.call(&vm, args) let mut vm = Vm::new(world, route.track(), tracer.track_mut(), id, scopes, 0);
self.call(&mut vm, args)
} }
/// Apply the given arguments to the function. /// Apply the given arguments to the function.
@ -292,7 +293,7 @@ pub(super) struct Closure {
impl Closure { impl Closure {
/// Call the function in the context with the arguments. /// Call the function in the context with the arguments.
fn call(&self, vm: &Vm, this: &Func, args: &mut Args) -> SourceResult<Value> { fn call(&self, vm: &mut Vm, this: &Func, args: &mut Args) -> SourceResult<Value> {
// Don't leak the scopes from the call site. Instead, we use the scope // Don't leak the scopes from the call site. Instead, we use the scope
// of captured variables we collected earlier. // of captured variables we collected earlier.
let mut scopes = Scopes::new(None); let mut scopes = Scopes::new(None);
@ -327,7 +328,14 @@ impl Closure {
let route = if detached { fresh.track() } else { vm.route }; let route = if detached { fresh.track() } else { vm.route };
// Evaluate the body. // Evaluate the body.
let mut sub = Vm::new(vm.world, route, self.location, scopes, vm.depth + 1); let mut sub = Vm::new(
vm.world,
route,
TrackedMut::reborrow_mut(&mut vm.tracer),
self.location,
scopes,
vm.depth + 1,
);
let result = self.body.eval(&mut sub); let result = self.body.eval(&mut sub);
// Handle control flow. // Handle control flow.

View File

@ -7,7 +7,7 @@ use crate::util::EcoString;
/// Call a method on a value. /// Call a method on a value.
pub fn call( pub fn call(
vm: &Vm, vm: &mut Vm,
value: Value, value: Value,
method: &str, method: &str,
mut args: Args, mut args: Args,

View File

@ -155,6 +155,14 @@ impl Value {
_ => self.display(), _ => self.display(),
} }
} }
/// Try to extract documentation for the value.
pub fn docs(&self) -> Option<&'static str> {
match self {
Self::Func(func) => func.info().map(|info| info.docs),
_ => None,
}
}
} }
impl Default for Value { impl Default for Value {

View File

@ -60,13 +60,29 @@ fn bench_edit(iai: &mut Iai) {
fn bench_eval(iai: &mut Iai) { fn bench_eval(iai: &mut Iai) {
let world = BenchWorld::new(); let world = BenchWorld::new();
let route = typst::model::Route::default(); let route = typst::model::Route::default();
iai.run(|| typst::model::eval(world.track(), route.track(), &world.source).unwrap()); let mut tracer = typst::model::Tracer::default();
iai.run(|| {
typst::model::eval(
world.track(),
route.track(),
tracer.track_mut(),
&world.source,
)
.unwrap()
});
} }
fn bench_typeset(iai: &mut Iai) { fn bench_typeset(iai: &mut Iai) {
let world = BenchWorld::new(); let world = BenchWorld::new();
let route = typst::model::Route::default(); let route = typst::model::Route::default();
let module = typst::model::eval(world.track(), route.track(), &world.source).unwrap(); let mut tracer = typst::model::Tracer::default();
let module = typst::model::eval(
world.track(),
route.track(),
tracer.track_mut(),
&world.source,
)
.unwrap();
let content = module.content(); let content = module.content();
iai.run(|| typst::model::typeset(world.track(), &content)); iai.run(|| typst::model::typeset(world.track(), &content));
} }

View File

@ -435,7 +435,9 @@ fn test_part(
if world.print.model { if world.print.model {
let world = (world as &dyn World).track(); let world = (world as &dyn World).track();
let route = typst::model::Route::default(); let route = typst::model::Route::default();
let module = typst::model::eval(world, route.track(), source).unwrap(); let mut tracer = typst::model::Tracer::default();
let module =
typst::model::eval(world, route.track(), tracer.track_mut(), source).unwrap();
println!("Model:\n{:#?}\n", module.content()); println!("Model:\n{:#?}\n", module.content());
} }