mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Optimized labels & introspector (#2801)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
parent
79c2d1f29e
commit
5bdec9e1d8
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2644,6 +2644,7 @@ dependencies = [
|
||||
"ciborium",
|
||||
"comemo",
|
||||
"csv",
|
||||
"dashmap",
|
||||
"ecow",
|
||||
"fontdb",
|
||||
"hayagriva",
|
||||
|
@ -38,6 +38,7 @@ clap_mangen = "0.2.10"
|
||||
codespan-reporting = "0.11"
|
||||
comemo = "0.3.1"
|
||||
csv = "1"
|
||||
dashmap = "5.5"
|
||||
dirs = "5"
|
||||
ecow = { version = "0.2", features = ["serde"] }
|
||||
env_proxy = "0.4"
|
||||
|
@ -4,7 +4,6 @@ use serde::Serialize;
|
||||
use typst::diag::{bail, StrResult};
|
||||
use typst::eval::{eval_string, EvalMode, Tracer};
|
||||
use typst::foundations::{Content, IntoValue, LocatableSelector, Scope};
|
||||
use typst::introspection::Introspector;
|
||||
use typst::model::Document;
|
||||
use typst::syntax::Span;
|
||||
use typst::World;
|
||||
@ -76,7 +75,8 @@ fn retrieve(
|
||||
})?
|
||||
.cast::<LocatableSelector>()?;
|
||||
|
||||
Ok(Introspector::new(&document.pages)
|
||||
Ok(document
|
||||
.introspector
|
||||
.query(&selector.0)
|
||||
.into_iter()
|
||||
.map(|x| x.into_inner())
|
||||
|
@ -4,8 +4,7 @@ use typst::engine::{Engine, Route};
|
||||
use typst::eval::{Tracer, Vm};
|
||||
use typst::foundations::{Label, Scopes, Value};
|
||||
use typst::introspection::{Introspector, Locator};
|
||||
use typst::layout::Frame;
|
||||
use typst::model::BibliographyElem;
|
||||
use typst::model::{BibliographyElem, Document};
|
||||
use typst::syntax::{ast, LinkedNode, Span, SyntaxKind};
|
||||
use typst::World;
|
||||
|
||||
@ -75,12 +74,11 @@ pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option<Value> {
|
||||
/// - 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) {
|
||||
pub fn analyze_labels(document: &Document) -> (Vec<(Label, Option<EcoString>)>, usize) {
|
||||
let mut output = vec![];
|
||||
let introspector = Introspector::new(frames);
|
||||
|
||||
// Labels in the document.
|
||||
for elem in introspector.all() {
|
||||
for elem in document.introspector.all() {
|
||||
let Some(label) = elem.label() else { continue };
|
||||
let details = elem
|
||||
.get_by_name("caption")
|
||||
@ -98,7 +96,7 @@ pub fn analyze_labels(frames: &[Frame]) -> (Vec<(Label, Option<EcoString>)>, usi
|
||||
let split = output.len();
|
||||
|
||||
// Bibliography keys.
|
||||
for (key, detail) in BibliographyElem::keys(introspector.track()) {
|
||||
for (key, detail) in BibliographyElem::keys(document.introspector.track()) {
|
||||
output.push((Label::new(&key), detail));
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ use typst::foundations::{
|
||||
fields_on, format_str, mutable_methods_on, repr, AutoValue, CastInfo, Func, Label,
|
||||
NoneValue, Repr, Scope, Type, Value,
|
||||
};
|
||||
use typst::layout::Frame;
|
||||
use typst::model::Document;
|
||||
use typst::syntax::{
|
||||
ast, is_id_continue, is_id_start, is_ident, LinkedNode, Source, SyntaxKind,
|
||||
};
|
||||
@ -27,14 +27,18 @@ use crate::{plain_docs_sentence, summarize_font_family};
|
||||
///
|
||||
/// When `explicit` is `true`, the user requested the completion by pressing
|
||||
/// control and space or something similar.
|
||||
///
|
||||
/// Passing a `document` (from a previous compilation) is optional, but enhances
|
||||
/// the autocompletions. Label completions, for instance, are only generated
|
||||
/// when the document is available.
|
||||
pub fn autocomplete(
|
||||
world: &dyn World,
|
||||
frames: &[Frame],
|
||||
document: Option<&Document>,
|
||||
source: &Source,
|
||||
cursor: usize,
|
||||
explicit: bool,
|
||||
) -> Option<(usize, Vec<Completion>)> {
|
||||
let mut ctx = CompletionContext::new(world, frames, source, cursor, explicit)?;
|
||||
let mut ctx = CompletionContext::new(world, document, source, cursor, explicit)?;
|
||||
|
||||
let _ = complete_comments(&mut ctx)
|
||||
|| complete_field_accesses(&mut ctx)
|
||||
@ -966,7 +970,7 @@ fn code_completions(ctx: &mut CompletionContext, hash: bool) {
|
||||
/// Context for autocompletion.
|
||||
struct CompletionContext<'a> {
|
||||
world: &'a (dyn World + 'a),
|
||||
frames: &'a [Frame],
|
||||
document: Option<&'a Document>,
|
||||
global: &'a Scope,
|
||||
math: &'a Scope,
|
||||
text: &'a str,
|
||||
@ -984,7 +988,7 @@ impl<'a> CompletionContext<'a> {
|
||||
/// Create a new autocompletion context.
|
||||
fn new(
|
||||
world: &'a (dyn World + 'a),
|
||||
frames: &'a [Frame],
|
||||
document: Option<&'a Document>,
|
||||
source: &'a Source,
|
||||
cursor: usize,
|
||||
explicit: bool,
|
||||
@ -994,7 +998,7 @@ impl<'a> CompletionContext<'a> {
|
||||
let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
||||
Some(Self {
|
||||
world,
|
||||
frames,
|
||||
document,
|
||||
global: library.global.scope(),
|
||||
math: library.math.scope(),
|
||||
text,
|
||||
@ -1094,7 +1098,8 @@ impl<'a> CompletionContext<'a> {
|
||||
|
||||
/// Add completions for labels and references.
|
||||
fn label_completions(&mut self) {
|
||||
let (labels, split) = analyze_labels(self.frames);
|
||||
let Some(document) = self.document else { return };
|
||||
let (labels, split) = analyze_labels(document);
|
||||
|
||||
let head = &self.text[..self.from];
|
||||
let at = head.ends_with('@');
|
||||
|
@ -1,9 +1,9 @@
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use ecow::EcoString;
|
||||
use typst::introspection::{Introspector, Meta};
|
||||
use typst::introspection::Meta;
|
||||
use typst::layout::{Frame, FrameItem, Point, Position, Size};
|
||||
use typst::model::Destination;
|
||||
use typst::model::{Destination, Document};
|
||||
use typst::syntax::{FileId, LinkedNode, Source, Span, SyntaxKind};
|
||||
use typst::visualize::Geometry;
|
||||
use typst::World;
|
||||
@ -31,12 +31,10 @@ impl Jump {
|
||||
/// Determine where to jump to based on a click in a frame.
|
||||
pub fn jump_from_click(
|
||||
world: &dyn World,
|
||||
frames: &[Frame],
|
||||
document: &Document,
|
||||
frame: &Frame,
|
||||
click: Point,
|
||||
) -> Option<Jump> {
|
||||
let mut introspector = None;
|
||||
|
||||
// Try to find a link first.
|
||||
for (pos, item) in frame.items() {
|
||||
if let FrameItem::Meta(Meta::Link(dest), size) = item {
|
||||
@ -44,11 +42,9 @@ pub fn jump_from_click(
|
||||
return Some(match dest {
|
||||
Destination::Url(url) => Jump::Url(url.clone()),
|
||||
Destination::Position(pos) => Jump::Position(*pos),
|
||||
Destination::Location(loc) => Jump::Position(
|
||||
introspector
|
||||
.get_or_insert_with(|| Introspector::new(frames))
|
||||
.position(*loc),
|
||||
),
|
||||
Destination::Location(loc) => {
|
||||
Jump::Position(document.introspector.position(*loc))
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -60,7 +56,7 @@ pub fn jump_from_click(
|
||||
FrameItem::Group(group) => {
|
||||
// TODO: Handle transformation.
|
||||
if let Some(span) =
|
||||
jump_from_click(world, frames, &group.frame, click - pos)
|
||||
jump_from_click(world, document, &group.frame, click - pos)
|
||||
{
|
||||
return Some(span);
|
||||
}
|
||||
|
@ -4,7 +4,8 @@ use ecow::{eco_format, EcoString};
|
||||
use if_chain::if_chain;
|
||||
use typst::eval::{CapturesVisitor, Tracer};
|
||||
use typst::foundations::{repr, CastInfo, Repr, Value};
|
||||
use typst::layout::{Frame, Length};
|
||||
use typst::layout::Length;
|
||||
use typst::model::Document;
|
||||
use typst::syntax::{ast, LinkedNode, Source, SyntaxKind};
|
||||
use typst::util::{round_2, Numeric};
|
||||
use typst::World;
|
||||
@ -13,9 +14,13 @@ use crate::analyze::{analyze_expr, analyze_labels};
|
||||
use crate::{plain_docs_sentence, summarize_font_family};
|
||||
|
||||
/// Describe the item under the cursor.
|
||||
///
|
||||
/// Passing a `document` (from a previous compilation) is optional, but enhances
|
||||
/// the autocompletions. Label completions, for instance, are only generated
|
||||
/// when the document is available.
|
||||
pub fn tooltip(
|
||||
world: &dyn World,
|
||||
frames: &[Frame],
|
||||
document: Option<&Document>,
|
||||
source: &Source,
|
||||
cursor: usize,
|
||||
) -> Option<Tooltip> {
|
||||
@ -26,7 +31,7 @@ pub fn tooltip(
|
||||
|
||||
named_param_tooltip(world, &leaf)
|
||||
.or_else(|| font_tooltip(world, &leaf))
|
||||
.or_else(|| label_tooltip(frames, &leaf))
|
||||
.or_else(|| document.and_then(|doc| label_tooltip(doc, &leaf)))
|
||||
.or_else(|| expr_tooltip(world, &leaf))
|
||||
.or_else(|| closure_tooltip(&leaf))
|
||||
}
|
||||
@ -145,14 +150,14 @@ fn length_tooltip(length: Length) -> Option<Tooltip> {
|
||||
}
|
||||
|
||||
/// Tooltip for a hovered reference or label.
|
||||
fn label_tooltip(frames: &[Frame], leaf: &LinkedNode) -> Option<Tooltip> {
|
||||
fn label_tooltip(document: &Document, leaf: &LinkedNode) -> Option<Tooltip> {
|
||||
let target = match leaf.kind() {
|
||||
SyntaxKind::RefMarker => leaf.text().trim_start_matches('@'),
|
||||
SyntaxKind::Label => leaf.text().trim_start_matches('<').trim_end_matches('>'),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
for (label, detail) in analyze_labels(frames).0 {
|
||||
for (label, detail) in analyze_labels(document).0 {
|
||||
if label.as_str() == target {
|
||||
return Some(Tooltip::Text(detail?));
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ use ecow::{eco_format, EcoString};
|
||||
use pdf_writer::types::Direction;
|
||||
use pdf_writer::{Finish, Name, Pdf, Ref, TextStr};
|
||||
use typst::foundations::Datetime;
|
||||
use typst::introspection::Introspector;
|
||||
use typst::layout::{Abs, Dir, Em, Transform};
|
||||
use typst::model::Document;
|
||||
use typst::text::{Font, Lang};
|
||||
@ -70,10 +69,6 @@ pub fn pdf(
|
||||
struct PdfContext<'a> {
|
||||
/// The document that we're currently exporting.
|
||||
document: &'a Document,
|
||||
/// An introspector for the document, used to resolve locations links and
|
||||
/// the document outline.
|
||||
introspector: Introspector,
|
||||
|
||||
/// The writer we are writing the PDF into.
|
||||
pdf: Pdf,
|
||||
/// Content of exported pages.
|
||||
@ -128,7 +123,6 @@ impl<'a> PdfContext<'a> {
|
||||
let page_tree_ref = alloc.bump();
|
||||
Self {
|
||||
document,
|
||||
introspector: Introspector::new(&document.pages),
|
||||
pdf: Pdf::new(),
|
||||
pages: vec![],
|
||||
glyph_sets: HashMap::new(),
|
||||
|
@ -18,7 +18,7 @@ pub(crate) fn write_outline(ctx: &mut PdfContext) -> Option<Ref> {
|
||||
// Therefore, its next descendant must be added at its level, which is
|
||||
// enforced in the manner shown below.
|
||||
let mut last_skipped_level = None;
|
||||
for heading in ctx.introspector.query(&HeadingElem::elem().select()).iter() {
|
||||
for heading in ctx.document.introspector.query(&HeadingElem::elem().select()).iter() {
|
||||
let leaf = HeadingNode::leaf((**heading).clone());
|
||||
|
||||
if leaf.bookmarked {
|
||||
@ -163,7 +163,7 @@ fn write_outline_item(
|
||||
outline.title(TextStr(body.plain_text().trim()));
|
||||
|
||||
let loc = node.element.location().unwrap();
|
||||
let pos = ctx.introspector.position(loc);
|
||||
let pos = ctx.document.introspector.position(loc);
|
||||
let index = pos.page.get() - 1;
|
||||
if let Some(page) = ctx.pages.get(index) {
|
||||
let y = (pos.point.y - Abs::pt(10.0)).max(Abs::zero());
|
||||
|
@ -180,7 +180,7 @@ fn write_page(ctx: &mut PdfContext, i: usize) {
|
||||
continue;
|
||||
}
|
||||
Destination::Position(pos) => *pos,
|
||||
Destination::Location(loc) => ctx.introspector.position(*loc),
|
||||
Destination::Location(loc) => ctx.document.introspector.position(*loc),
|
||||
};
|
||||
|
||||
let index = pos.page.get() - 1;
|
||||
|
@ -24,6 +24,7 @@ chinese-number = { workspace = true }
|
||||
ciborium = { workspace = true }
|
||||
comemo = { workspace = true }
|
||||
csv = { workspace = true }
|
||||
dashmap = { workspace = true }
|
||||
ecow = { workspace = true}
|
||||
fontdb = { workspace = true }
|
||||
hayagriva = { workspace = true }
|
||||
|
@ -1,11 +1,13 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::Hash;
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use comemo::Prehashed;
|
||||
use dashmap::DashMap;
|
||||
use ecow::{eco_format, EcoVec};
|
||||
use indexmap::IndexMap;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::diag::{bail, StrResult};
|
||||
use crate::foundations::{Content, Label, Repr, Selector};
|
||||
@ -15,35 +17,38 @@ use crate::model::Numbering;
|
||||
use crate::util::NonZeroExt;
|
||||
|
||||
/// Can be queried for elements and their positions.
|
||||
#[derive(Clone)]
|
||||
pub struct Introspector {
|
||||
/// The number of pages in the document.
|
||||
pages: usize,
|
||||
/// All introspectable elements.
|
||||
elems: IndexMap<Location, (Prehashed<Content>, Position)>,
|
||||
/// Maps labels to their indices in the element list. We use a smallvec such
|
||||
/// that if the label is unique, we don't need to allocate.
|
||||
labels: HashMap<Label, SmallVec<[usize; 1]>>,
|
||||
/// The page numberings, indexed by page number minus 1.
|
||||
page_numberings: Vec<Option<Numbering>>,
|
||||
/// Caches queries done on the introspector. This is important because
|
||||
/// even if all top-level queries are distinct, they often have shared
|
||||
/// subqueries. Example: Individual counter queries with `before` that
|
||||
/// all depend on a global counter query.
|
||||
queries: RefCell<HashMap<u128, EcoVec<Prehashed<Content>>>>,
|
||||
queries: DashMap<u128, EcoVec<Prehashed<Content>>>,
|
||||
}
|
||||
|
||||
impl Introspector {
|
||||
/// Create a new introspector.
|
||||
#[tracing::instrument(skip(frames))]
|
||||
pub fn new(frames: &[Frame]) -> Self {
|
||||
let mut introspector = Self {
|
||||
pages: frames.len(),
|
||||
elems: IndexMap::new(),
|
||||
page_numberings: vec![],
|
||||
queries: RefCell::default(),
|
||||
};
|
||||
/// Applies new frames in-place, reusing the existing allocations.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn rebuild(&mut self, frames: &[Frame]) {
|
||||
self.pages = frames.len();
|
||||
self.elems.clear();
|
||||
self.labels.clear();
|
||||
self.page_numberings.clear();
|
||||
self.queries.clear();
|
||||
|
||||
for (i, frame) in frames.iter().enumerate() {
|
||||
let page = NonZeroUsize::new(1 + i).unwrap();
|
||||
introspector.extract(frame, page, Transform::identity());
|
||||
self.extract(frame, page, Transform::identity());
|
||||
}
|
||||
introspector
|
||||
}
|
||||
|
||||
/// Extract metadata from a frame.
|
||||
@ -61,11 +66,17 @@ impl Introspector {
|
||||
if !self.elems.contains_key(&content.location().unwrap()) =>
|
||||
{
|
||||
let pos = pos.transform(ts);
|
||||
let content = Prehashed::new(content.clone());
|
||||
let ret = self.elems.insert(
|
||||
content.location().unwrap(),
|
||||
(Prehashed::new(content.clone()), Position { page, point: pos }),
|
||||
(content.clone(), Position { page, point: pos }),
|
||||
);
|
||||
assert!(ret.is_none(), "duplicate locations");
|
||||
|
||||
// Build the label cache.
|
||||
if let Some(label) = content.label() {
|
||||
self.labels.entry(label).or_default().push(self.elems.len() - 1);
|
||||
}
|
||||
}
|
||||
FrameItem::Meta(Meta::PageNumbering(numbering), _) => {
|
||||
self.page_numberings.push(numbering.clone());
|
||||
@ -107,15 +118,19 @@ impl Introspector {
|
||||
/// Query for all matching elements.
|
||||
pub fn query(&self, selector: &Selector) -> EcoVec<Prehashed<Content>> {
|
||||
let hash = crate::util::hash128(selector);
|
||||
if let Some(output) = self.queries.borrow().get(&hash) {
|
||||
if let Some(output) = self.queries.get(&hash) {
|
||||
return output.clone();
|
||||
}
|
||||
|
||||
let output = match selector {
|
||||
Selector::Elem(..)
|
||||
| Selector::Label(_)
|
||||
| Selector::Regex(_)
|
||||
| Selector::Can(_) => {
|
||||
Selector::Label(label) => self
|
||||
.labels
|
||||
.get(label)
|
||||
.map(|indices| {
|
||||
indices.iter().map(|&index| self.elems[index].0.clone()).collect()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
Selector::Elem(..) | Selector::Regex(_) | Selector::Can(_) => {
|
||||
self.all().filter(|elem| selector.matches(elem)).cloned().collect()
|
||||
}
|
||||
Selector::Location(location) => {
|
||||
@ -182,7 +197,7 @@ impl Introspector {
|
||||
.collect(),
|
||||
};
|
||||
|
||||
self.queries.borrow_mut().insert(hash, output.clone());
|
||||
self.queries.insert(hash, output.clone());
|
||||
output
|
||||
}
|
||||
|
||||
@ -196,16 +211,15 @@ impl Introspector {
|
||||
|
||||
/// Query for a unique element with the label.
|
||||
pub fn query_label(&self, label: Label) -> StrResult<&Prehashed<Content>> {
|
||||
let mut found = None;
|
||||
for elem in self.all().filter(|elem| elem.label() == Some(label)) {
|
||||
if found.is_some() {
|
||||
bail!("label `{}` occurs multiple times in the document", label.repr());
|
||||
}
|
||||
found = Some(elem);
|
||||
}
|
||||
found.ok_or_else(|| {
|
||||
let indices = self.labels.get(&label).ok_or_else(|| {
|
||||
eco_format!("label `{}` does not exist in the document", label.repr())
|
||||
})
|
||||
})?;
|
||||
|
||||
if indices.len() > 1 {
|
||||
bail!("label `{}` occurs multiple times in the document", label.repr());
|
||||
}
|
||||
|
||||
Ok(&self.elems[indices[0]].0)
|
||||
}
|
||||
|
||||
/// The total number pages.
|
||||
@ -237,6 +251,18 @@ impl Introspector {
|
||||
|
||||
impl Default for Introspector {
|
||||
fn default() -> Self {
|
||||
Self::new(&[])
|
||||
Self {
|
||||
pages: 0,
|
||||
elems: IndexMap::new(),
|
||||
labels: HashMap::new(),
|
||||
page_numberings: vec![],
|
||||
queries: DashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Introspector {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad("Introspector(..)")
|
||||
}
|
||||
}
|
||||
|
@ -111,8 +111,7 @@ fn typeset(
|
||||
let styles = StyleChain::new(&library.styles);
|
||||
|
||||
let mut iter = 0;
|
||||
let mut document;
|
||||
let mut introspector = Introspector::new(&[]);
|
||||
let mut document = Document::default();
|
||||
|
||||
// Relayout until all introspections stabilize.
|
||||
// If that doesn't happen within five attempts, we give up.
|
||||
@ -129,16 +128,15 @@ fn typeset(
|
||||
route: Route::default(),
|
||||
tracer: tracer.track_mut(),
|
||||
locator: &mut locator,
|
||||
introspector: introspector.track_with(&constraint),
|
||||
introspector: document.introspector.track_with(&constraint),
|
||||
};
|
||||
|
||||
// Layout!
|
||||
document = content.layout_root(&mut engine, styles)?;
|
||||
|
||||
introspector = Introspector::new(&document.pages);
|
||||
document.introspector.rebuild(&document.pages);
|
||||
iter += 1;
|
||||
|
||||
if introspector.validate(&constraint) {
|
||||
if document.introspector.validate(&constraint) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ use crate::engine::Engine;
|
||||
use crate::foundations::{
|
||||
cast, elem, Args, Array, Construct, Content, Datetime, Smart, StyleChain, Value,
|
||||
};
|
||||
use crate::introspection::ManualPageCounter;
|
||||
use crate::introspection::{Introspector, ManualPageCounter};
|
||||
use crate::layout::{Frame, LayoutRoot, PageElem};
|
||||
|
||||
/// The root element of a document and its metadata.
|
||||
@ -110,6 +110,7 @@ impl LayoutRoot for DocumentElem {
|
||||
author: self.author(styles).0,
|
||||
keywords: self.keywords(styles).0,
|
||||
date: self.date(styles),
|
||||
introspector: Introspector::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -137,7 +138,7 @@ cast! {
|
||||
}
|
||||
|
||||
/// A finished document with metadata and page frames.
|
||||
#[derive(Debug, Default, Clone, Hash)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Document {
|
||||
/// The page frames.
|
||||
pub pages: Vec<Frame>,
|
||||
@ -149,6 +150,8 @@ pub struct Document {
|
||||
pub keywords: Vec<EcoString>,
|
||||
/// The document's creation date.
|
||||
pub date: Smart<Option<Datetime>>,
|
||||
/// Provides the ability to execute queries on the document.
|
||||
pub introspector: Introspector,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -156,8 +159,8 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_document_is_send() {
|
||||
fn ensure_send<T: Send>() {}
|
||||
ensure_send::<Document>();
|
||||
fn test_document_is_send_and_sync() {
|
||||
fn ensure_send_and_sync<T: Send + Sync>() {}
|
||||
ensure_send_and_sync::<Document>();
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user