summaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/typst-eval/src/import.rs72
-rw-r--r--crates/typst-ide/src/complete.rs25
-rw-r--r--crates/typst-ide/src/definition.rs356
-rw-r--r--crates/typst-ide/src/lib.rs2
-rw-r--r--crates/typst-ide/src/matchers.rs30
-rw-r--r--crates/typst-ide/src/utils.rs20
6 files changed, 221 insertions, 284 deletions
diff --git a/crates/typst-eval/src/import.rs b/crates/typst-eval/src/import.rs
index aee632bf..5b67c060 100644
--- a/crates/typst-eval/src/import.rs
+++ b/crates/typst-eval/src/import.rs
@@ -163,52 +163,15 @@ pub fn import(engine: &mut Engine, from: &str, span: Span) -> SourceResult<Modul
let spec = from.parse::<PackageSpec>().at(span)?;
import_package(engine, spec, span)
} else {
- import_file(engine, from, span)
+ let id = span.resolve_path(from).at(span)?;
+ import_file(engine, id, span)
}
}
-/// Import an external package.
-fn import_package(
- engine: &mut Engine,
- spec: PackageSpec,
- span: Span,
-) -> SourceResult<Module> {
- // Evaluate the manifest.
- let manifest_id = FileId::new(Some(spec.clone()), VirtualPath::new("typst.toml"));
- let bytes = engine.world.file(manifest_id).at(span)?;
- let string = std::str::from_utf8(&bytes).map_err(FileError::from).at(span)?;
- let manifest: PackageManifest = toml::from_str(string)
- .map_err(|err| eco_format!("package manifest is malformed ({})", err.message()))
- .at(span)?;
- manifest.validate(&spec).at(span)?;
-
- // Evaluate the entry point.
- let entrypoint_id = manifest_id.join(&manifest.package.entrypoint);
- let source = engine.world.source(entrypoint_id).at(span)?;
-
- // Prevent cyclic importing.
- if engine.route.contains(source.id()) {
- bail!(span, "cyclic import");
- }
-
- let point = || Tracepoint::Import;
- Ok(eval(
- engine.routines,
- engine.world,
- engine.traced,
- TrackedMut::reborrow_mut(&mut engine.sink),
- engine.route.track(),
- &source,
- )
- .trace(engine.world, point, span)?
- .with_name(manifest.package.name))
-}
-
/// Import a file from a path. The path is resolved relative to the given
/// `span`.
-fn import_file(engine: &mut Engine, path: &str, span: Span) -> SourceResult<Module> {
+fn import_file(engine: &mut Engine, id: FileId, span: Span) -> SourceResult<Module> {
// Load the source file.
- let id = span.resolve_path(path).at(span)?;
let source = engine.world.source(id).at(span)?;
// Prevent cyclic importing.
@@ -228,3 +191,32 @@ fn import_file(engine: &mut Engine, path: &str, span: Span) -> SourceResult<Modu
)
.trace(engine.world, point, span)
}
+
+/// Import an external package.
+fn import_package(
+ engine: &mut Engine,
+ spec: PackageSpec,
+ span: Span,
+) -> SourceResult<Module> {
+ let (name, id) = resolve_package(engine, spec, span)?;
+ import_file(engine, id, span).map(|module| module.with_name(name))
+}
+
+/// Resolve the name and entrypoint of a package.
+fn resolve_package(
+ engine: &mut Engine,
+ spec: PackageSpec,
+ span: Span,
+) -> SourceResult<(EcoString, FileId)> {
+ // Evaluate the manifest.
+ let manifest_id = FileId::new(Some(spec.clone()), VirtualPath::new("typst.toml"));
+ let bytes = engine.world.file(manifest_id).at(span)?;
+ let string = std::str::from_utf8(&bytes).map_err(FileError::from).at(span)?;
+ let manifest: PackageManifest = toml::from_str(string)
+ .map_err(|err| eco_format!("package manifest is malformed ({})", err.message()))
+ .at(span)?;
+ manifest.validate(&spec).at(span)?;
+
+ // Evaluate the entry point.
+ Ok((manifest.package.name, manifest_id.join(&manifest.package.entrypoint)))
+}
diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs
index fba1177f..f25e40c6 100644
--- a/crates/typst-ide/src/complete.rs
+++ b/crates/typst-ide/src/complete.rs
@@ -6,7 +6,7 @@ use ecow::{eco_format, EcoString};
use if_chain::if_chain;
use serde::{Deserialize, Serialize};
use typst::foundations::{
- fields_on, repr, AutoValue, CastInfo, Func, Label, NoneValue, ParamInfo, Repr, Scope,
+ fields_on, repr, AutoValue, CastInfo, Func, Label, NoneValue, ParamInfo, Repr,
StyleChain, Styles, Type, Value,
};
use typst::model::Document;
@@ -19,7 +19,7 @@ use typst::text::RawElem;
use typst::visualize::Color;
use unscanny::Scanner;
-use crate::utils::{plain_docs_sentence, summarize_font_family};
+use crate::utils::{globals, plain_docs_sentence, summarize_font_family};
use crate::{analyze_expr, analyze_import, analyze_labels, named_items, IdeWorld};
/// Autocomplete a cursor position in a source file.
@@ -839,10 +839,11 @@ fn resolve_global_callee<'a>(
ctx: &CompletionContext<'a>,
callee: ast::Expr<'a>,
) -> Option<&'a Func> {
+ let globals = globals(ctx.world, ctx.leaf);
let value = match callee {
- ast::Expr::Ident(ident) => ctx.global.get(&ident)?,
+ ast::Expr::Ident(ident) => globals.get(&ident)?,
ast::Expr::FieldAccess(access) => match access.target() {
- ast::Expr::Ident(target) => match ctx.global.get(&target)? {
+ ast::Expr::Ident(target) => match globals.get(&target)? {
Value::Module(module) => module.field(&access.field()).ok()?,
Value::Func(func) => func.field(&access.field()).ok()?,
_ => return None,
@@ -1051,8 +1052,6 @@ fn code_completions(ctx: &mut CompletionContext, hash: bool) {
struct CompletionContext<'a> {
world: &'a (dyn IdeWorld + 'a),
document: Option<&'a Document>,
- global: &'a Scope,
- math: &'a Scope,
text: &'a str,
before: &'a str,
after: &'a str,
@@ -1075,12 +1074,9 @@ impl<'a> CompletionContext<'a> {
explicit: bool,
) -> Option<Self> {
let text = source.text();
- let library = world.library();
Some(Self {
world,
document,
- global: library.global.scope(),
- math: library.math.scope(),
text,
before: &text[..cursor],
after: &text[cursor..],
@@ -1433,16 +1429,7 @@ impl<'a> CompletionContext<'a> {
None::<()>
});
- let in_math = matches!(
- self.leaf.parent_kind(),
- Some(SyntaxKind::Equation)
- | Some(SyntaxKind::Math)
- | Some(SyntaxKind::MathFrac)
- | Some(SyntaxKind::MathAttach)
- );
-
- let scope = if in_math { self.math } else { self.global };
- for (name, value, _) in scope.iter() {
+ for (name, value, _) in globals(self.world, self.leaf).iter() {
if filter(value) && !defined.contains(name) {
self.value_completion_full(Some(name.clone()), value, parens, None, None);
}
diff --git a/crates/typst-ide/src/definition.rs b/crates/typst-ide/src/definition.rs
index a8286554..94def1c1 100644
--- a/crates/typst-ide/src/definition.rs
+++ b/crates/typst-ide/src/definition.rs
@@ -1,13 +1,22 @@
-use ecow::EcoString;
-use typst::foundations::{Label, Module, Selector, Value};
+use typst::foundations::{Label, Selector, Value};
use typst::model::Document;
-use typst::syntax::ast::AstNode;
-use typst::syntax::{ast, LinkedNode, Side, Source, Span, SyntaxKind};
+use typst::syntax::{ast, LinkedNode, Side, Source, Span};
+use crate::utils::globals;
use crate::{
- analyze_import, deref_target, named_items, DerefTarget, IdeWorld, NamedItem,
+ analyze_expr, analyze_import, deref_target, named_items, DerefTarget, IdeWorld,
+ NamedItem,
};
+/// A definition of some item.
+#[derive(Debug, Clone)]
+pub enum Definition {
+ /// The item is defined at the given span.
+ Span(Span),
+ /// The item is defined in the standard library.
+ Std(Value),
+}
+
/// Find the definition of the item under the cursor.
///
/// Passing a `document` (from a previous compilation) is optional, but enhances
@@ -23,241 +32,162 @@ pub fn definition(
let root = LinkedNode::new(source.root());
let leaf = root.leaf_at(cursor, side)?;
- let mut use_site = match deref_target(leaf.clone())? {
- DerefTarget::VarAccess(node) | DerefTarget::Callee(node) => node,
- DerefTarget::IncludePath(path) | DerefTarget::ImportPath(path) => {
- let import_item =
- analyze_import(world, &path).and_then(|v| v.cast::<Module>().ok())?;
- return Some(Definition::module(&import_item, path.span(), Span::detached()));
+ match deref_target(leaf.clone())? {
+ // Try to find a named item (defined in this file or an imported file)
+ // or fall back to a standard library item.
+ DerefTarget::VarAccess(node) | DerefTarget::Callee(node) => {
+ let name = node.cast::<ast::Ident>()?.get().clone();
+ if let Some(src) = named_items(world, node.clone(), |item: NamedItem| {
+ (*item.name() == name).then(|| Definition::Span(item.span()))
+ }) {
+ return Some(src);
+ };
+
+ if let Some((value, _)) = analyze_expr(world, &node).first() {
+ let span = match value {
+ Value::Content(content) => content.span(),
+ Value::Func(func) => func.span(),
+ _ => Span::detached(),
+ };
+ if !span.is_detached() && span != node.span() {
+ return Some(Definition::Span(span));
+ }
+ }
+
+ if let Some(value) = globals(world, &leaf).get(&name) {
+ return Some(Definition::Std(value.clone()));
+ }
}
- DerefTarget::Ref(r) => {
- let label = Label::new(r.cast::<ast::Ref>()?.target());
- let sel = Selector::Label(label);
- let elem = document?.introspector.query_first(&sel)?;
- let span = elem.span();
- return Some(Definition {
- kind: DefinitionKind::Label,
- name: label.as_str().into(),
- value: Some(Value::Label(label)),
- span,
- name_span: Span::detached(),
- });
+
+ // Try to jump to the an imported file or package.
+ DerefTarget::ImportPath(node) | DerefTarget::IncludePath(node) => {
+ let Some(Value::Module(module)) = analyze_import(world, &node) else {
+ return None;
+ };
+ let id = module.file_id()?;
+ let span = Span::from_range(id, 0..0);
+ return Some(Definition::Span(span));
}
- DerefTarget::Label(..) | DerefTarget::Code(..) => {
- return None;
+
+ // Try to jump to the referenced content.
+ DerefTarget::Ref(node) => {
+ let label = Label::new(node.cast::<ast::Ref>()?.target());
+ let selector = Selector::Label(label);
+ let elem = document?.introspector.query_first(&selector)?;
+ return Some(Definition::Span(elem.span()));
}
- };
- let mut has_path = false;
- while let Some(node) = use_site.cast::<ast::FieldAccess>() {
- has_path = true;
- use_site = use_site.find(node.target().span())?;
+ _ => {}
}
- let name = use_site.cast::<ast::Ident>()?.get().clone();
- let src = named_items(world, use_site, |item: NamedItem| {
- if *item.name() != name {
- return None;
- }
+ None
+}
- match item {
- NamedItem::Var(name) => {
- let name_span = name.span();
- let span = find_let_binding(source, name_span);
- Some(Definition::item(name.get().clone(), span, name_span, None))
- }
- NamedItem::Fn(name) => {
- let name_span = name.span();
- let span = find_let_binding(source, name_span);
- Some(
- Definition::item(name.get().clone(), span, name_span, None)
- .with_kind(DefinitionKind::Function),
- )
- }
- NamedItem::Module(item, site) => Some(Definition::module(
- item,
- site.span(),
- matches!(site.kind(), SyntaxKind::Ident)
- .then_some(site.span())
- .unwrap_or_else(Span::detached),
- )),
- NamedItem::Import(name, name_span, value) => Some(Definition::item(
- name.clone(),
- Span::detached(),
- name_span,
- value.cloned(),
- )),
- }
- });
+#[cfg(test)]
+mod tests {
+ use std::ops::Range;
- let src = src.or_else(|| {
- let in_math = matches!(
- leaf.parent_kind(),
- Some(SyntaxKind::Equation)
- | Some(SyntaxKind::Math)
- | Some(SyntaxKind::MathFrac)
- | Some(SyntaxKind::MathAttach)
- );
+ use typst::foundations::{IntoValue, NativeElement};
+ use typst::syntax::Side;
+ use typst::WorldExt;
- let library = world.library();
- let scope = if in_math { library.math.scope() } else { library.global.scope() };
- for (item_name, value, span) in scope.iter() {
- if *item_name == name {
- return Some(Definition::item(
- name,
- span,
- Span::detached(),
- Some(value.clone()),
- ));
- }
- }
+ use super::{definition, Definition};
+ use crate::tests::{SourceExt, TestWorld};
- None
- })?;
+ type Response = (TestWorld, Option<Definition>);
- (!has_path).then_some(src)
-}
+ trait ResponseExt {
+ fn must_be_at(&self, path: &str, range: Range<usize>) -> &Self;
+ fn must_be_value(&self, value: impl IntoValue) -> &Self;
+ }
-/// A definition of some item.
-#[derive(Debug, Clone)]
-pub struct Definition {
- /// The name of the definition.
- pub name: EcoString,
- /// The kind of the definition.
- pub kind: DefinitionKind,
- /// An instance of the definition, if available.
- pub value: Option<Value>,
- /// The source span of the entire definition. May be detached if unknown.
- pub span: Span,
- /// The span of the definition's name. May be detached if unknown.
- pub name_span: Span,
-}
+ impl ResponseExt for Response {
+ #[track_caller]
+ fn must_be_at(&self, path: &str, expected: Range<usize>) -> &Self {
+ match self.1 {
+ Some(Definition::Span(span)) => {
+ let range = self.0.range(span);
+ assert_eq!(
+ span.id().unwrap().vpath().as_rootless_path().to_string_lossy(),
+ path
+ );
+ assert_eq!(range, Some(expected));
+ }
+ _ => panic!("expected span definition"),
+ }
+ self
+ }
-impl Definition {
- fn item(name: EcoString, span: Span, name_span: Span, value: Option<Value>) -> Self {
- Self {
- name,
- kind: match value {
- Some(Value::Func(_)) => DefinitionKind::Function,
- _ => DefinitionKind::Variable,
- },
- value,
- span,
- name_span,
+ #[track_caller]
+ fn must_be_value(&self, expected: impl IntoValue) -> &Self {
+ match &self.1 {
+ Some(Definition::Std(value)) => {
+ assert_eq!(*value, expected.into_value())
+ }
+ _ => panic!("expected std definition"),
+ }
+ self
}
}
- fn module(module: &Module, span: Span, name_span: Span) -> Self {
- Definition {
- name: module.name().clone(),
- kind: DefinitionKind::Module,
- value: Some(Value::Module(module.clone())),
- span,
- name_span,
- }
+ #[track_caller]
+ fn test(text: &str, cursor: isize, side: Side) -> Response {
+ let world = TestWorld::new(text);
+ test_with_world(world, cursor, side)
}
- fn with_kind(self, kind: DefinitionKind) -> Self {
- Self { kind, ..self }
+ #[track_caller]
+ fn test_with_world(world: TestWorld, cursor: isize, side: Side) -> Response {
+ let doc = typst::compile(&world).output.ok();
+ let source = &world.main;
+ let def = definition(&world, doc.as_ref(), source, source.cursor(cursor), side);
+ (world, def)
}
-}
-/// A kind of item that is definition.
-#[derive(Debug, Clone, PartialEq, Hash)]
-pub enum DefinitionKind {
- /// ```plain
- /// let foo;
- /// ^^^^^^^^ span
- /// ^^^ name_span
- /// ```
- Variable,
- /// ```plain
- /// let foo(it) = it;
- /// ^^^^^^^^^^^^^^^^^ span
- /// ^^^ name_span
- /// ```
- Function,
- /// Case 1
- /// ```plain
- /// import "foo.typ": *
- /// ^^^^^^^^^ span
- /// name_span is detached
- /// ```
- ///
- /// Case 2
- /// ```plain
- /// import "foo.typ" as bar: *
- /// span ^^^
- /// name_span ^^^
- /// ```
- Module,
- /// ```plain
- /// <foo>
- /// ^^^^^ span
- /// name_span is detached
- /// ```
- Label,
-}
+ #[test]
+ fn test_definition_let() {
+ test("#let x; #x", 9, Side::After).must_be_at("main.typ", 5..6);
+ test("#let x() = {}; #x", 16, Side::After).must_be_at("main.typ", 5..6);
+ }
-fn find_let_binding(source: &Source, name_span: Span) -> Span {
- let node = LinkedNode::new(source.root());
- std::iter::successors(node.find(name_span).as_ref(), |n| n.parent())
- .find(|n| matches!(n.kind(), SyntaxKind::LetBinding))
- .map(|s| s.span())
- .unwrap_or_else(Span::detached)
-}
+ #[test]
+ fn test_definition_field_access_function() {
+ let world = TestWorld::new("#import \"other.typ\"; #other.foo")
+ .with_source("other.typ", "#let foo(x) = x + 1");
-#[cfg(test)]
-mod tests {
- use std::ops::Range;
+ // The span is at the args here because that's what the function value's
+ // span is. Not ideal, but also not too big of a big deal.
+ test_with_world(world, -1, Side::Before).must_be_at("other.typ", 8..11);
+ }
- use typst::foundations::{IntoValue, Label, NativeElement, Value};
- use typst::syntax::Side;
- use typst::WorldExt;
+ #[test]
+ fn test_definition_cross_file() {
+ let world = TestWorld::new("#import \"other.typ\": x; #x")
+ .with_source("other.typ", "#let x = 1");
+ test_with_world(world, -1, Side::After).must_be_at("other.typ", 5..6);
+ }
+
+ #[test]
+ fn test_definition_import() {
+ let world = TestWorld::new("#import \"other.typ\" as o: x")
+ .with_source("other.typ", "#let x = 1");
+ test_with_world(world, 14, Side::Before).must_be_at("other.typ", 0..0);
+ }
- use super::{definition, DefinitionKind as Kind};
- use crate::tests::TestWorld;
+ #[test]
+ fn test_definition_include() {
+ let world = TestWorld::new("#include \"other.typ\"")
+ .with_source("other.typ", "Hello there");
+ test_with_world(world, 14, Side::Before).must_be_at("other.typ", 0..0);
+ }
- #[track_caller]
- fn test<T>(
- text: &str,
- cursor: usize,
- name: &str,
- kind: Kind,
- value: Option<T>,
- range: Option<Range<usize>>,
- ) where
- T: IntoValue,
- {
- let world = TestWorld::new(text);
- let doc = typst::compile(&world).output.ok();
- let actual = definition(&world, doc.as_ref(), &world.main, cursor, Side::After)
- .map(|d| (d.kind, d.name, world.range(d.span), d.value));
- assert_eq!(
- actual,
- Some((kind, name.into(), range, value.map(IntoValue::into_value)))
- );
+ #[test]
+ fn test_definition_ref() {
+ test("#figure[] <hi> See @hi", 21, Side::After).must_be_at("main.typ", 1..9);
}
#[test]
- fn test_definition() {
- test("#let x; #x", 9, "x", Kind::Variable, None::<Value>, Some(1..6));
- test("#let x() = {}; #x", 16, "x", Kind::Function, None::<Value>, Some(1..13));
- test(
- "#table",
- 1,
- "table",
- Kind::Function,
- Some(typst::model::TableElem::elem()),
- None,
- );
- test(
- "#figure[] <hi> See @hi",
- 21,
- "hi",
- Kind::Label,
- Some(Label::new("hi")),
- Some(1..9),
- );
+ fn test_definition_std() {
+ test("#table", 1, Side::After).must_be_value(typst::model::TableElem::elem());
}
}
diff --git a/crates/typst-ide/src/lib.rs b/crates/typst-ide/src/lib.rs
index 038589c0..c0edcce9 100644
--- a/crates/typst-ide/src/lib.rs
+++ b/crates/typst-ide/src/lib.rs
@@ -10,7 +10,7 @@ mod utils;
pub use self::analyze::{analyze_expr, analyze_import, analyze_labels};
pub use self::complete::{autocomplete, Completion, CompletionKind};
-pub use self::definition::{definition, Definition, DefinitionKind};
+pub use self::definition::{definition, Definition};
pub use self::jump::{jump_from_click, jump_from_cursor, Jump};
pub use self::matchers::{deref_target, named_items, DerefTarget, NamedItem};
pub use self::tooltip::{tooltip, Tooltip};
diff --git a/crates/typst-ide/src/matchers.rs b/crates/typst-ide/src/matchers.rs
index dd7dfd1f..4aeba29b 100644
--- a/crates/typst-ide/src/matchers.rs
+++ b/crates/typst-ide/src/matchers.rs
@@ -162,6 +162,14 @@ impl<'a> NamedItem<'a> {
NamedItem::Import(_, _, value) => value.cloned(),
}
}
+
+ pub(crate) fn span(&self) -> Span {
+ match *self {
+ NamedItem::Var(name) | NamedItem::Fn(name) => name.span(),
+ NamedItem::Module(_, site) => site.span(),
+ NamedItem::Import(_, span, _) => span,
+ }
+ }
}
/// Categorize an expression into common classes IDE functionality can operate
@@ -177,29 +185,29 @@ pub fn deref_target(node: LinkedNode) -> Option<DerefTarget<'_>> {
let expr_node = ancestor;
let expr = expr_node.cast::<ast::Expr>()?;
Some(match expr {
- ast::Expr::Label(..) => DerefTarget::Label(expr_node),
- ast::Expr::Ref(..) => DerefTarget::Ref(expr_node),
+ ast::Expr::Label(_) => DerefTarget::Label(expr_node),
+ ast::Expr::Ref(_) => DerefTarget::Ref(expr_node),
ast::Expr::FuncCall(call) => {
DerefTarget::Callee(expr_node.find(call.callee().span())?)
}
ast::Expr::Set(set) => DerefTarget::Callee(expr_node.find(set.target().span())?),
- ast::Expr::Ident(..) | ast::Expr::MathIdent(..) | ast::Expr::FieldAccess(..) => {
+ ast::Expr::Ident(_) | ast::Expr::MathIdent(_) | ast::Expr::FieldAccess(_) => {
DerefTarget::VarAccess(expr_node)
}
- ast::Expr::Str(..) => {
+ ast::Expr::Str(_) => {
let parent = expr_node.parent()?;
if parent.kind() == SyntaxKind::ModuleImport {
DerefTarget::ImportPath(expr_node)
} else if parent.kind() == SyntaxKind::ModuleInclude {
DerefTarget::IncludePath(expr_node)
} else {
- DerefTarget::Code(expr_node.kind(), expr_node)
+ DerefTarget::Code(expr_node)
}
}
_ if expr.hash()
|| matches!(expr_node.kind(), SyntaxKind::MathIdent | SyntaxKind::Error) =>
{
- DerefTarget::Code(expr_node.kind(), expr_node)
+ DerefTarget::Code(expr_node)
}
_ => return None,
})
@@ -208,10 +216,6 @@ pub fn deref_target(node: LinkedNode) -> Option<DerefTarget<'_>> {
/// Classes of expressions that can be operated on by IDE functionality.
#[derive(Debug, Clone)]
pub enum DerefTarget<'a> {
- /// A label expression.
- Label(LinkedNode<'a>),
- /// A reference expression.
- Ref(LinkedNode<'a>),
/// A variable access expression.
///
/// It can be either an identifier or a field access.
@@ -223,7 +227,11 @@ pub enum DerefTarget<'a> {
/// An include path expression.
IncludePath(LinkedNode<'a>),
/// Any code expression.
- Code(SyntaxKind, LinkedNode<'a>),
+ Code(LinkedNode<'a>),
+ /// A label expression.
+ Label(LinkedNode<'a>),
+ /// A reference expression.
+ Ref(LinkedNode<'a>),
}
#[cfg(test)]
diff --git a/crates/typst-ide/src/utils.rs b/crates/typst-ide/src/utils.rs
index 903fb2f3..ad8ed6b5 100644
--- a/crates/typst-ide/src/utils.rs
+++ b/crates/typst-ide/src/utils.rs
@@ -3,7 +3,9 @@ use std::fmt::Write;
use comemo::Track;
use ecow::{eco_format, EcoString};
use typst::engine::{Engine, Route, Sink, Traced};
+use typst::foundations::Scope;
use typst::introspection::Introspector;
+use typst::syntax::{LinkedNode, SyntaxKind};
use typst::text::{FontInfo, FontStyle};
use crate::IdeWorld;
@@ -105,3 +107,21 @@ pub fn summarize_font_family<'a>(
detail
}
+
+/// The global definitions at the given node.
+pub fn globals<'a>(world: &'a dyn IdeWorld, leaf: &LinkedNode) -> &'a Scope {
+ let in_math = matches!(
+ leaf.parent_kind(),
+ Some(SyntaxKind::Equation)
+ | Some(SyntaxKind::Math)
+ | Some(SyntaxKind::MathFrac)
+ | Some(SyntaxKind::MathAttach)
+ );
+
+ let library = world.library();
+ if in_math {
+ library.math.scope()
+ } else {
+ library.global.scope()
+ }
+}