mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Spans for cross-file go-to-definition (#4539)
This commit is contained in:
parent
02d0128dde
commit
7437352638
@ -52,7 +52,6 @@ pub fn analyze_expr(
|
||||
pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option<Value> {
|
||||
// Use span in the node for resolving imports with relative paths.
|
||||
let source_span = source.span();
|
||||
|
||||
let (source, _) = analyze_expr(world, source).into_iter().next()?;
|
||||
if source.scope().is_some() {
|
||||
return Some(source);
|
||||
@ -76,6 +75,7 @@ pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option<Value> {
|
||||
Scopes::new(Some(world.library())),
|
||||
Span::detached(),
|
||||
);
|
||||
|
||||
typst::eval::import(&mut vm, source, source_span, true)
|
||||
.ok()
|
||||
.map(Value::Module)
|
||||
|
@ -378,12 +378,12 @@ fn field_access_completions(
|
||||
value: &Value,
|
||||
styles: &Option<Styles>,
|
||||
) {
|
||||
for (name, value) in value.ty().scope().iter() {
|
||||
for (name, value, _) in value.ty().scope().iter() {
|
||||
ctx.value_completion(Some(name.clone()), value, true, None);
|
||||
}
|
||||
|
||||
if let Some(scope) = value.scope() {
|
||||
for (name, value) in scope.iter() {
|
||||
for (name, value, _) in scope.iter() {
|
||||
ctx.value_completion(Some(name.clone()), value, true, None);
|
||||
}
|
||||
}
|
||||
@ -549,7 +549,7 @@ fn import_item_completions<'a>(
|
||||
ctx.snippet_completion("*", "*", "Import everything.");
|
||||
}
|
||||
|
||||
for (name, value) in scope.iter() {
|
||||
for (name, value, _) in scope.iter() {
|
||||
if existing.iter().all(|item| item.original_name().as_str() != name) {
|
||||
ctx.value_completion(Some(name.clone()), value, false, None);
|
||||
}
|
||||
@ -1337,7 +1337,7 @@ impl<'a> CompletionContext<'a> {
|
||||
);
|
||||
|
||||
let scope = if in_math { self.math } else { self.global };
|
||||
for (name, value) in scope.iter() {
|
||||
for (name, value, _) in scope.iter() {
|
||||
if filter(value) && !defined.contains(name) {
|
||||
self.value_completion(Some(name.clone()), value, parens, None);
|
||||
}
|
||||
|
@ -22,9 +22,7 @@ pub fn definition(
|
||||
let root = LinkedNode::new(source.root());
|
||||
let leaf = root.leaf_at(cursor, side)?;
|
||||
|
||||
let target = deref_target(leaf.clone())?;
|
||||
|
||||
let mut use_site = match target {
|
||||
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 =
|
||||
@ -82,10 +80,10 @@ pub fn definition(
|
||||
.then_some(site.span())
|
||||
.unwrap_or_else(Span::detached),
|
||||
)),
|
||||
NamedItem::Import(name, span, value) => Some(Definition::item(
|
||||
NamedItem::Import(name, name_span, value) => Some(Definition::item(
|
||||
name.clone(),
|
||||
Span::detached(),
|
||||
span,
|
||||
name_span,
|
||||
value.cloned(),
|
||||
)),
|
||||
}
|
||||
@ -99,14 +97,14 @@ pub fn definition(
|
||||
| Some(SyntaxKind::MathFrac)
|
||||
| Some(SyntaxKind::MathAttach)
|
||||
);
|
||||
let library = world.library();
|
||||
|
||||
let library = world.library();
|
||||
let scope = if in_math { library.math.scope() } else { library.global.scope() };
|
||||
for (item_name, value) in scope.iter() {
|
||||
for (item_name, value, span) in scope.iter() {
|
||||
if *item_name == name {
|
||||
return Some(Definition::item(
|
||||
name,
|
||||
Span::detached(),
|
||||
span,
|
||||
Span::detached(),
|
||||
Some(value.clone()),
|
||||
));
|
||||
|
@ -77,12 +77,8 @@ pub fn named_items<T>(
|
||||
// ```
|
||||
Some(ast::Imports::Wildcard) => {
|
||||
if let Some(scope) = source.and_then(|(value, _)| value.scope()) {
|
||||
for (name, value) in scope.iter() {
|
||||
let item = NamedItem::Import(
|
||||
name,
|
||||
Span::detached(),
|
||||
Some(value),
|
||||
);
|
||||
for (name, value, span) in scope.iter() {
|
||||
let item = NamedItem::Import(name, span, Some(value));
|
||||
if let Some(res) = recv(item) {
|
||||
return Some(res);
|
||||
}
|
||||
@ -94,9 +90,17 @@ pub fn named_items<T>(
|
||||
// ```
|
||||
Some(ast::Imports::Items(items)) => {
|
||||
for item in items.iter() {
|
||||
let name = item.bound_name();
|
||||
let original = item.original_name();
|
||||
let bound = item.bound_name();
|
||||
let scope = source.and_then(|(value, _)| value.scope());
|
||||
let span = scope
|
||||
.and_then(|s| s.get_span(&original))
|
||||
.unwrap_or(Span::detached())
|
||||
.or(bound.span());
|
||||
|
||||
let value = scope.and_then(|s| s.get(&original));
|
||||
if let Some(res) =
|
||||
recv(NamedItem::Import(name.get(), name.span(), None))
|
||||
recv(NamedItem::Import(bound.get(), span, value))
|
||||
{
|
||||
return Some(res);
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ fn closure_tooltip(leaf: &LinkedNode) -> Option<Tooltip> {
|
||||
|
||||
let captures = visitor.finish();
|
||||
let mut names: Vec<_> =
|
||||
captures.iter().map(|(name, _)| eco_format!("`{name}`")).collect();
|
||||
captures.iter().map(|(name, ..)| eco_format!("`{name}`")).collect();
|
||||
if names.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
@ -83,6 +83,15 @@ impl Span {
|
||||
self.0.get() & ((1 << Self::BITS) - 1)
|
||||
}
|
||||
|
||||
/// Return `other` if `self` is detached and `self` otherwise.
|
||||
pub fn or(self, other: Self) -> Self {
|
||||
if self.is_detached() {
|
||||
other
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a file location relative to this span's source.
|
||||
pub fn resolve_path(self, path: &str) -> Result<FileId, EcoString> {
|
||||
let Some(file) = self.id() else {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use comemo::{Tracked, TrackedMut};
|
||||
use ecow::{eco_format, EcoVec};
|
||||
use ecow::{eco_format, EcoString, EcoVec};
|
||||
|
||||
use crate::diag::{bail, error, At, HintedStrResult, SourceResult, Trace, Tracepoint};
|
||||
use crate::engine::Engine;
|
||||
@ -410,9 +410,11 @@ impl<'a> CapturesVisitor<'a> {
|
||||
// Identifiers that shouldn't count as captures because they
|
||||
// actually bind a new name are handled below (individually through
|
||||
// the expressions that contain them).
|
||||
Some(ast::Expr::Ident(ident)) => self.capture(&ident, Scopes::get),
|
||||
Some(ast::Expr::Ident(ident)) => {
|
||||
self.capture(ident.get(), ident.span(), Scopes::get)
|
||||
}
|
||||
Some(ast::Expr::MathIdent(ident)) => {
|
||||
self.capture(&ident, Scopes::get_in_math)
|
||||
self.capture(ident.get(), ident.span(), Scopes::get_in_math)
|
||||
}
|
||||
|
||||
// Code and content blocks create a scope.
|
||||
@ -520,13 +522,14 @@ impl<'a> CapturesVisitor<'a> {
|
||||
|
||||
/// Bind a new internal variable.
|
||||
fn bind(&mut self, ident: ast::Ident) {
|
||||
self.internal.top.define(ident.get().clone(), Value::None);
|
||||
self.internal.top.define_ident(ident, Value::None);
|
||||
}
|
||||
|
||||
/// Capture a variable if it isn't internal.
|
||||
fn capture(
|
||||
&mut self,
|
||||
ident: &str,
|
||||
ident: &EcoString,
|
||||
span: Span,
|
||||
getter: impl FnOnce(&'a Scopes<'a>, &str) -> HintedStrResult<&'a Value>,
|
||||
) {
|
||||
if self.internal.get(ident).is_err() {
|
||||
@ -538,7 +541,12 @@ impl<'a> CapturesVisitor<'a> {
|
||||
return;
|
||||
};
|
||||
|
||||
self.captures.define_captured(ident, value.clone(), self.capturer);
|
||||
self.captures.define_captured(
|
||||
ident.clone(),
|
||||
value.clone(),
|
||||
self.capturer,
|
||||
span,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -561,7 +569,7 @@ mod tests {
|
||||
visitor.visit(&root);
|
||||
|
||||
let captures = visitor.finish();
|
||||
let mut names: Vec<_> = captures.iter().map(|(k, _)| k).collect();
|
||||
let mut names: Vec<_> = captures.iter().map(|(k, ..)| k).collect();
|
||||
names.sort();
|
||||
|
||||
assert_eq!(names, result);
|
||||
|
@ -31,7 +31,7 @@ impl Eval for ast::ModuleImport<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(new_name) = &new_name {
|
||||
if let Some(new_name) = new_name {
|
||||
if let ast::Expr::Ident(ident) = self.source() {
|
||||
if ident.as_str() == new_name.as_str() {
|
||||
// Warn on `import x as x`
|
||||
@ -43,7 +43,7 @@ impl Eval for ast::ModuleImport<'_> {
|
||||
}
|
||||
|
||||
// Define renamed module on the scope.
|
||||
vm.scopes.top.define(new_name.as_str(), source.clone());
|
||||
vm.scopes.top.define_ident(new_name, source.clone());
|
||||
}
|
||||
|
||||
let scope = source.scope().unwrap();
|
||||
@ -56,8 +56,8 @@ impl Eval for ast::ModuleImport<'_> {
|
||||
}
|
||||
}
|
||||
Some(ast::Imports::Wildcard) => {
|
||||
for (var, value) in scope.iter() {
|
||||
vm.scopes.top.define(var.clone(), value.clone());
|
||||
for (var, value, span) in scope.iter() {
|
||||
vm.scopes.top.define_spanned(var.clone(), value.clone(), span);
|
||||
}
|
||||
}
|
||||
Some(ast::Imports::Items(items)) => {
|
||||
|
@ -47,7 +47,7 @@ impl<'a> Vm<'a> {
|
||||
if self.inspected == Some(var.span()) {
|
||||
self.trace(value.clone());
|
||||
}
|
||||
self.scopes.top.define(var.get().clone(), value);
|
||||
self.scopes.top.define_ident(var, value);
|
||||
}
|
||||
|
||||
/// Trace a value.
|
||||
|
@ -261,7 +261,7 @@ pub struct ToDict(Dict);
|
||||
|
||||
cast! {
|
||||
ToDict,
|
||||
v: Module => Self(v.scope().iter().map(|(k, v)| (Str::from(k.clone()), v.clone())).collect()),
|
||||
v: Module => Self(v.scope().iter().map(|(k, v, _)| (Str::from(k.clone()), v.clone())).collect()),
|
||||
}
|
||||
|
||||
impl Debug for Dict {
|
||||
|
@ -290,7 +290,7 @@ pub fn eval(
|
||||
let dict = scope;
|
||||
let mut scope = Scope::new();
|
||||
for (key, value) in dict {
|
||||
scope.define(key, value);
|
||||
scope.define_spanned(key, value, span);
|
||||
}
|
||||
crate::eval::eval_string(engine.world, &text, span, mode, scope)
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ use crate::foundations::{
|
||||
Element, Func, IntoValue, Module, NativeElement, NativeFunc, NativeFuncData,
|
||||
NativeType, Type, Value,
|
||||
};
|
||||
use crate::syntax::ast::{self, AstNode};
|
||||
use crate::syntax::Span;
|
||||
use crate::util::Static;
|
||||
use crate::Library;
|
||||
|
||||
@ -129,6 +131,23 @@ impl Scope {
|
||||
/// Bind a value to a name.
|
||||
#[track_caller]
|
||||
pub fn define(&mut self, name: impl Into<EcoString>, value: impl IntoValue) {
|
||||
self.define_spanned(name, value, Span::detached())
|
||||
}
|
||||
|
||||
/// Bind a value to a name defined by an identifier.
|
||||
#[track_caller]
|
||||
pub fn define_ident(&mut self, ident: ast::Ident, value: impl IntoValue) {
|
||||
self.define_spanned(ident.get().clone(), value, ident.span())
|
||||
}
|
||||
|
||||
/// Bind a value to a name.
|
||||
#[track_caller]
|
||||
pub fn define_spanned(
|
||||
&mut self,
|
||||
name: impl Into<EcoString>,
|
||||
value: impl IntoValue,
|
||||
span: Span,
|
||||
) {
|
||||
let name = name.into();
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
@ -136,8 +155,24 @@ impl Scope {
|
||||
panic!("duplicate definition: {name}");
|
||||
}
|
||||
|
||||
self.map
|
||||
.insert(name, Slot::new(value.into_value(), Kind::Normal, self.category));
|
||||
self.map.insert(
|
||||
name,
|
||||
Slot::new(value.into_value(), span, Kind::Normal, self.category),
|
||||
);
|
||||
}
|
||||
|
||||
/// Define a captured, immutable binding.
|
||||
pub fn define_captured(
|
||||
&mut self,
|
||||
name: EcoString,
|
||||
value: Value,
|
||||
capturer: Capturer,
|
||||
span: Span,
|
||||
) {
|
||||
self.map.insert(
|
||||
name,
|
||||
Slot::new(value.into_value(), span, Kind::Captured(capturer), self.category),
|
||||
);
|
||||
}
|
||||
|
||||
/// Define a native function through a Rust type that shadows the function.
|
||||
@ -168,19 +203,6 @@ impl Scope {
|
||||
self.define(module.name().clone(), module);
|
||||
}
|
||||
|
||||
/// Define a captured, immutable binding.
|
||||
pub fn define_captured(
|
||||
&mut self,
|
||||
var: impl Into<EcoString>,
|
||||
value: impl IntoValue,
|
||||
capturer: Capturer,
|
||||
) {
|
||||
self.map.insert(
|
||||
var.into(),
|
||||
Slot::new(value.into_value(), Kind::Captured(capturer), self.category),
|
||||
);
|
||||
}
|
||||
|
||||
/// Try to access a variable immutably.
|
||||
pub fn get(&self, var: &str) -> Option<&Value> {
|
||||
self.map.get(var).map(Slot::read)
|
||||
@ -194,14 +216,19 @@ impl Scope {
|
||||
.map(|res| res.map_err(HintedString::from))
|
||||
}
|
||||
|
||||
/// Get the span of a definition.
|
||||
pub fn get_span(&self, var: &str) -> Option<Span> {
|
||||
Some(self.map.get(var)?.span)
|
||||
}
|
||||
|
||||
/// Get the category of a definition.
|
||||
pub fn get_category(&self, var: &str) -> Option<Category> {
|
||||
self.map.get(var)?.category
|
||||
}
|
||||
|
||||
/// Iterate over all definitions.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&EcoString, &Value)> {
|
||||
self.map.iter().map(|(k, v)| (k, v.read()))
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&EcoString, &Value, Span)> {
|
||||
self.map.iter().map(|(k, v)| (k, v.read(), v.span))
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,6 +268,8 @@ struct Slot {
|
||||
value: Value,
|
||||
/// The kind of slot, determines how the value can be accessed.
|
||||
kind: Kind,
|
||||
/// A span associated with the stored value.
|
||||
span: Span,
|
||||
/// The category of the slot.
|
||||
category: Option<Category>,
|
||||
}
|
||||
@ -265,8 +294,8 @@ pub enum Capturer {
|
||||
|
||||
impl Slot {
|
||||
/// Create a new slot.
|
||||
fn new(value: Value, kind: Kind, category: Option<Category>) -> Self {
|
||||
Self { value, kind, category }
|
||||
fn new(value: Value, span: Span, kind: Kind, category: Option<Category>) -> Self {
|
||||
Self { value, span, kind, category }
|
||||
}
|
||||
|
||||
/// Read the value.
|
||||
|
@ -48,8 +48,8 @@ static GROUPS: Lazy<Vec<GroupData>> = Lazy::new(|| {
|
||||
.module()
|
||||
.scope()
|
||||
.iter()
|
||||
.filter(|(_, v)| matches!(v, Value::Func(_)))
|
||||
.map(|(k, _)| k.clone())
|
||||
.filter(|(_, v, _)| matches!(v, Value::Func(_)))
|
||||
.map(|(k, _, _)| k.clone())
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
@ -253,7 +253,7 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel {
|
||||
|
||||
// Add values and types.
|
||||
let scope = module.scope();
|
||||
for (name, value) in scope.iter() {
|
||||
for (name, value, _) in scope.iter() {
|
||||
if scope.get_category(name) != Some(category) {
|
||||
continue;
|
||||
}
|
||||
@ -467,7 +467,7 @@ fn casts(
|
||||
fn scope_models(resolver: &dyn Resolver, name: &str, scope: &Scope) -> Vec<FuncModel> {
|
||||
scope
|
||||
.iter()
|
||||
.filter_map(|(_, value)| {
|
||||
.filter_map(|(_, value, _)| {
|
||||
let Value::Func(func) = value else { return None };
|
||||
Some(func_model(resolver, func, &[name], true))
|
||||
})
|
||||
@ -653,7 +653,7 @@ fn symbols_page(resolver: &dyn Resolver, parent: &str, group: &GroupData) -> Pag
|
||||
/// Produce a symbol list's model.
|
||||
fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel {
|
||||
let mut list = vec![];
|
||||
for (name, value) in group.module().scope().iter() {
|
||||
for (name, value, _) in group.module().scope().iter() {
|
||||
let Value::Symbol(symbol) = value else { continue };
|
||||
let complete = |variant: &str| {
|
||||
if variant.is_empty() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user