mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Simplify go-to-definition
This commit is contained in:
parent
685b8c9dc9
commit
de59d64d10
@ -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)))
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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()));
|
||||
}
|
||||
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(),
|
||||
});
|
||||
}
|
||||
DerefTarget::Label(..) | DerefTarget::Code(..) => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
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);
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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))
|
||||
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));
|
||||
}
|
||||
}
|
||||
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(),
|
||||
)),
|
||||
}
|
||||
});
|
||||
|
||||
let src = src.or_else(|| {
|
||||
let in_math = matches!(
|
||||
leaf.parent_kind(),
|
||||
Some(SyntaxKind::Equation)
|
||||
| Some(SyntaxKind::Math)
|
||||
| Some(SyntaxKind::MathFrac)
|
||||
| Some(SyntaxKind::MathAttach)
|
||||
);
|
||||
|
||||
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()),
|
||||
));
|
||||
if let Some(value) = globals(world, &leaf).get(&name) {
|
||||
return Some(Definition::Std(value.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
})?;
|
||||
|
||||
(!has_path).then_some(src)
|
||||
}
|
||||
|
||||
/// 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 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,
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
// 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()));
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
fn with_kind(self, kind: DefinitionKind) -> Self {
|
||||
Self { kind, ..self }
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
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)
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ops::Range;
|
||||
|
||||
use typst::foundations::{IntoValue, Label, NativeElement, Value};
|
||||
use typst::foundations::{IntoValue, NativeElement};
|
||||
use typst::syntax::Side;
|
||||
use typst::WorldExt;
|
||||
|
||||
use super::{definition, DefinitionKind as Kind};
|
||||
use crate::tests::TestWorld;
|
||||
use super::{definition, Definition};
|
||||
use crate::tests::{SourceExt, TestWorld};
|
||||
|
||||
type Response = (TestWorld, Option<Definition>);
|
||||
|
||||
trait ResponseExt {
|
||||
fn must_be_at(&self, path: &str, range: Range<usize>) -> &Self;
|
||||
fn must_be_value(&self, value: impl IntoValue) -> &Self;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn test<T>(
|
||||
text: &str,
|
||||
cursor: usize,
|
||||
name: &str,
|
||||
kind: Kind,
|
||||
value: Option<T>,
|
||||
range: Option<Range<usize>>,
|
||||
) where
|
||||
T: IntoValue,
|
||||
{
|
||||
fn test(text: &str, cursor: isize, side: Side) -> Response {
|
||||
let world = TestWorld::new(text);
|
||||
test_with_world(world, cursor, side)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn test_with_world(world: TestWorld, cursor: isize, side: Side) -> Response {
|
||||
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)))
|
||||
);
|
||||
let source = &world.main;
|
||||
let def = definition(&world, doc.as_ref(), source, source.cursor(cursor), side);
|
||||
(world, def)
|
||||
}
|
||||
|
||||
#[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_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);
|
||||
}
|
||||
|
||||
#[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");
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_definition_ref() {
|
||||
test("#figure[] <hi> See @hi", 21, Side::After).must_be_at("main.typ", 1..9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_definition_std() {
|
||||
test("#table", 1, Side::After).must_be_value(typst::model::TableElem::elem());
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
|
@ -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)]
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user