mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 13:13:43 +00:00
dev: reimplements definition analysis (#43)
* dev: reimplements definition based on def use analysis * dev: reimplements lsp apis based on new definition api * fix: most cases of references * fix: scope of params
This commit is contained in:
parent
5fa4f8f94f
commit
da7028f59c
48 changed files with 746 additions and 1110 deletions
|
@ -1,5 +1,3 @@
|
||||||
pub mod definition;
|
|
||||||
pub use definition::*;
|
|
||||||
pub mod def_use;
|
pub mod def_use;
|
||||||
pub use def_use::*;
|
pub use def_use::*;
|
||||||
pub mod import;
|
pub mod import;
|
||||||
|
@ -10,8 +8,6 @@ pub mod matcher;
|
||||||
pub use matcher::*;
|
pub use matcher::*;
|
||||||
pub mod module;
|
pub mod module;
|
||||||
pub use module::*;
|
pub use module::*;
|
||||||
pub mod reference;
|
|
||||||
pub use reference::*;
|
|
||||||
pub mod track_values;
|
pub mod track_values;
|
||||||
pub use track_values::*;
|
pub use track_values::*;
|
||||||
|
|
||||||
|
|
|
@ -64,18 +64,43 @@ pub struct IdentDef {
|
||||||
pub range: Range<usize>,
|
pub range: Range<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExternalRefMap = HashMap<(TypstFileId, Option<String>), Vec<(Option<DefId>, IdentRef)>>;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct DefUseInfo {
|
pub struct DefUseInfo {
|
||||||
ident_defs: indexmap::IndexMap<(TypstFileId, IdentRef), IdentDef>,
|
ident_defs: indexmap::IndexMap<(TypstFileId, IdentRef), IdentDef>,
|
||||||
external_refs: HashMap<(TypstFileId, Option<String>), Vec<IdentRef>>,
|
external_refs: ExternalRefMap,
|
||||||
ident_refs: HashMap<IdentRef, DefId>,
|
ident_refs: HashMap<IdentRef, DefId>,
|
||||||
|
redefine_current: Option<TypstFileId>,
|
||||||
|
ident_redefines: HashMap<IdentRef, DefId>,
|
||||||
undefined_refs: Vec<IdentRef>,
|
undefined_refs: Vec<IdentRef>,
|
||||||
exports_refs: Vec<DefId>,
|
exports_refs: Vec<DefId>,
|
||||||
|
pub exports_defs: HashMap<String, DefId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DefUseInfo {
|
impl DefUseInfo {
|
||||||
|
pub fn get_ref(&self, ident: &IdentRef) -> Option<DefId> {
|
||||||
|
self.ident_refs.get(ident).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_def_by_id(&self, id: DefId) -> Option<(TypstFileId, &IdentDef)> {
|
||||||
|
let ((fid, _), def) = self.ident_defs.get_index(id.0 as usize)?;
|
||||||
|
Some((*fid, def))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_def(&self, fid: TypstFileId, ident: &IdentRef) -> Option<(DefId, &IdentDef)> {
|
pub fn get_def(&self, fid: TypstFileId, ident: &IdentRef) -> Option<(DefId, &IdentDef)> {
|
||||||
let (id, _, def) = self.ident_defs.get_full(&(fid, ident.clone()))?;
|
let (id, _, def) = self
|
||||||
|
.ident_defs
|
||||||
|
.get_full(&(fid, ident.clone()))
|
||||||
|
.or_else(|| {
|
||||||
|
if self.redefine_current == Some(fid) {
|
||||||
|
let def_id = self.ident_redefines.get(ident)?;
|
||||||
|
let kv = self.ident_defs.get_index(def_id.0 as usize)?;
|
||||||
|
Some((def_id.0 as usize, kv.0, kv.1))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})?;
|
||||||
Some((DefId(id as u64), def))
|
Some((DefId(id as u64), def))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +114,7 @@ impl DefUseInfo {
|
||||||
&self,
|
&self,
|
||||||
ext_id: TypstFileId,
|
ext_id: TypstFileId,
|
||||||
ext_name: Option<String>,
|
ext_name: Option<String>,
|
||||||
) -> impl Iterator<Item = &IdentRef> {
|
) -> impl Iterator<Item = &(Option<DefId>, IdentRef)> {
|
||||||
self.external_refs
|
self.external_refs
|
||||||
.get(&(ext_id, ext_name))
|
.get(&(ext_id, ext_name))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -107,10 +132,6 @@ pub fn get_def_use(ctx: &mut AnalysisContext, source: Source) -> Option<Arc<DefU
|
||||||
|
|
||||||
fn get_def_use_inner(ctx: &mut SearchCtx, source: Source) -> Option<Arc<DefUseInfo>> {
|
fn get_def_use_inner(ctx: &mut SearchCtx, source: Source) -> Option<Arc<DefUseInfo>> {
|
||||||
let current_id = source.id();
|
let current_id = source.id();
|
||||||
if !ctx.searched.insert(current_id) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.ctx.get_mut(current_id);
|
ctx.ctx.get_mut(current_id);
|
||||||
let c = ctx.ctx.get(current_id).unwrap();
|
let c = ctx.ctx.get(current_id).unwrap();
|
||||||
|
|
||||||
|
@ -118,6 +139,10 @@ fn get_def_use_inner(ctx: &mut SearchCtx, source: Source) -> Option<Arc<DefUseIn
|
||||||
return Some(info);
|
return Some(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !ctx.searched.insert(current_id) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
let e = get_lexical_hierarchy(source, LexicalScopeKind::DefUse)?;
|
let e = get_lexical_hierarchy(source, LexicalScopeKind::DefUse)?;
|
||||||
|
|
||||||
let mut collector = DefUseCollector {
|
let mut collector = DefUseCollector {
|
||||||
|
@ -130,6 +155,7 @@ fn get_def_use_inner(ctx: &mut SearchCtx, source: Source) -> Option<Arc<DefUseIn
|
||||||
ext_src: None,
|
ext_src: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
collector.info.redefine_current = Some(current_id);
|
||||||
collector.scan(&e);
|
collector.scan(&e);
|
||||||
collector.calc_exports();
|
collector.calc_exports();
|
||||||
let res = Some(Arc::new(collector.info));
|
let res = Some(Arc::new(collector.info));
|
||||||
|
@ -160,27 +186,101 @@ impl<'a, 'b, 'w> DefUseCollector<'a, 'b, 'w> {
|
||||||
|
|
||||||
fn calc_exports(&mut self) {
|
fn calc_exports(&mut self) {
|
||||||
self.info.exports_refs = self.id_scope.values().copied().collect();
|
self.info.exports_refs = self.id_scope.values().copied().collect();
|
||||||
|
self.info.exports_defs = self
|
||||||
|
.id_scope
|
||||||
|
.entries()
|
||||||
|
.map(|(k, v)| (k.clone(), *v))
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_name(&mut self, name: &str) -> Option<()> {
|
||||||
|
let source = self.ext_src.as_ref()?;
|
||||||
|
|
||||||
|
log::debug!("import for def use: {:?}, name: {name}", source.id());
|
||||||
|
let (_, external_info) =
|
||||||
|
Some(source.id()).zip(get_def_use_inner(self.ctx, source.clone()))?;
|
||||||
|
|
||||||
|
let ext_id = external_info.exports_defs.get(name)?;
|
||||||
|
self.import_from(&external_info, *ext_id);
|
||||||
|
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_from(&mut self, external_info: &DefUseInfo, v: DefId) {
|
||||||
|
// Use FileId in ident_defs map should lose stacked import
|
||||||
|
// information, but it is currently
|
||||||
|
// not a problem.
|
||||||
|
let ((ext_id, _), ext_sym) = external_info.ident_defs.get_index(v.0 as usize).unwrap();
|
||||||
|
|
||||||
|
let name = ext_sym.name.clone();
|
||||||
|
|
||||||
|
let ext_ref = IdentRef {
|
||||||
|
name: name.clone(),
|
||||||
|
range: ext_sym.range.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (id, ..) = self
|
||||||
|
.info
|
||||||
|
.ident_defs
|
||||||
|
.insert_full((*ext_id, ext_ref), ext_sym.clone());
|
||||||
|
|
||||||
|
let id = DefId(id as u64);
|
||||||
|
self.id_scope.insert(name, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scan(&mut self, e: &'a [LexicalHierarchy]) -> Option<()> {
|
fn scan(&mut self, e: &'a [LexicalHierarchy]) -> Option<()> {
|
||||||
for e in e {
|
for e in e {
|
||||||
match &e.info.kind {
|
match &e.info.kind {
|
||||||
LexicalKind::Heading(..) => unreachable!(),
|
LexicalKind::Heading(..) => unreachable!(),
|
||||||
LexicalKind::Var(LexicalVarKind::Label) => self.insert(Ns::Label, e),
|
LexicalKind::Var(LexicalVarKind::Label) => {
|
||||||
|
self.insert(Ns::Label, e);
|
||||||
|
}
|
||||||
LexicalKind::Var(LexicalVarKind::LabelRef) => self.insert_ref(Ns::Label, e),
|
LexicalKind::Var(LexicalVarKind::LabelRef) => self.insert_ref(Ns::Label, e),
|
||||||
LexicalKind::Var(LexicalVarKind::Function)
|
LexicalKind::Var(LexicalVarKind::Function)
|
||||||
| LexicalKind::Var(LexicalVarKind::Variable) => self.insert(Ns::Value, e),
|
| LexicalKind::Var(LexicalVarKind::Variable) => {
|
||||||
|
self.insert(Ns::Value, e);
|
||||||
|
}
|
||||||
LexicalKind::Mod(super::LexicalModKind::PathVar)
|
LexicalKind::Mod(super::LexicalModKind::PathVar)
|
||||||
| LexicalKind::Mod(super::LexicalModKind::ModuleAlias) => {
|
| LexicalKind::Mod(super::LexicalModKind::ModuleAlias) => {
|
||||||
self.insert_module(Ns::Value, e)
|
self.insert_module(Ns::Value, e)
|
||||||
}
|
}
|
||||||
LexicalKind::Mod(super::LexicalModKind::Ident) => {
|
LexicalKind::Mod(super::LexicalModKind::Ident) => {
|
||||||
self.insert(Ns::Value, e);
|
match self.import_name(&e.info.name) {
|
||||||
self.insert_extern(e.info.name.clone(), e.info.range.clone());
|
Some(()) => {
|
||||||
|
self.insert_ref(Ns::Value, e);
|
||||||
|
self.insert_redef(e);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let def_id = self.insert(Ns::Value, e);
|
||||||
|
self.insert_extern(
|
||||||
|
e.info.name.clone(),
|
||||||
|
e.info.range.clone(),
|
||||||
|
Some(def_id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
LexicalKind::Mod(super::LexicalModKind::Alias { target }) => {
|
LexicalKind::Mod(super::LexicalModKind::Alias { target }) => {
|
||||||
self.insert(Ns::Value, e);
|
match self.import_name(&target.name) {
|
||||||
self.insert_extern(target.name.clone(), target.range.clone());
|
Some(()) => {
|
||||||
|
self.insert_ident_ref(
|
||||||
|
Ns::Value,
|
||||||
|
IdentRef {
|
||||||
|
name: target.name.clone(),
|
||||||
|
range: target.range.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.insert(Ns::Value, e);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let def_id = self.insert(Ns::Value, e);
|
||||||
|
self.insert_extern(
|
||||||
|
target.name.clone(),
|
||||||
|
target.range.clone(),
|
||||||
|
Some(def_id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
LexicalKind::Var(LexicalVarKind::ValRef) => self.insert_ref(Ns::Value, e),
|
LexicalKind::Var(LexicalVarKind::ValRef) => self.insert_ref(Ns::Value, e),
|
||||||
LexicalKind::Block => {
|
LexicalKind::Block => {
|
||||||
|
@ -214,31 +314,11 @@ impl<'a, 'b, 'w> DefUseCollector<'a, 'b, 'w> {
|
||||||
let (_, external_info) =
|
let (_, external_info) =
|
||||||
Some(source.id()).zip(get_def_use_inner(self.ctx, source.clone()))?;
|
Some(source.id()).zip(get_def_use_inner(self.ctx, source.clone()))?;
|
||||||
|
|
||||||
for v in &external_info.exports_refs {
|
for ext_id in &external_info.exports_refs {
|
||||||
// Use FileId in ident_defs map should lose stacked import
|
self.import_from(&external_info, *ext_id);
|
||||||
// information, but it is currently
|
|
||||||
// not a problem.
|
|
||||||
let ((ext_id, _), ext_sym) =
|
|
||||||
external_info.ident_defs.get_index(v.0 as usize).unwrap();
|
|
||||||
|
|
||||||
let name = ext_sym.name.clone();
|
|
||||||
|
|
||||||
let ext_ref = IdentRef {
|
|
||||||
name: name.clone(),
|
|
||||||
range: ext_sym.range.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let (id, ..) = self
|
|
||||||
.info
|
|
||||||
.ident_defs
|
|
||||||
.insert_full((*ext_id, ext_ref), ext_sym.clone());
|
|
||||||
|
|
||||||
let id = DefId(id as u64);
|
|
||||||
self.id_scope.insert(name, id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LexicalKind::Mod(super::LexicalModKind::ExternResolved { .. }) => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,24 +330,27 @@ impl<'a, 'b, 'w> DefUseCollector<'a, 'b, 'w> {
|
||||||
if let Some(src) = &self.ext_src {
|
if let Some(src) = &self.ext_src {
|
||||||
self.info.external_refs.insert(
|
self.info.external_refs.insert(
|
||||||
(src.id(), None),
|
(src.id(), None),
|
||||||
vec![IdentRef {
|
vec![(
|
||||||
name: e.info.name.clone(),
|
None,
|
||||||
range: e.info.range.clone(),
|
IdentRef {
|
||||||
}],
|
name: e.info.name.clone(),
|
||||||
|
range: e.info.range.clone(),
|
||||||
|
},
|
||||||
|
)],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_extern(&mut self, name: String, range: Range<usize>) {
|
fn insert_extern(&mut self, name: String, range: Range<usize>, redefine_id: Option<DefId>) {
|
||||||
if let Some(src) = &self.ext_src {
|
if let Some(src) = &self.ext_src {
|
||||||
self.info.external_refs.insert(
|
self.info.external_refs.insert(
|
||||||
(src.id(), Some(name.clone())),
|
(src.id(), Some(name.clone())),
|
||||||
vec![IdentRef { name, range }],
|
vec![(redefine_id, IdentRef { name, range })],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert(&mut self, label: Ns, e: &LexicalHierarchy) {
|
fn insert(&mut self, label: Ns, e: &LexicalHierarchy) -> DefId {
|
||||||
let snap = match label {
|
let snap = match label {
|
||||||
Ns::Label => &mut self.label_scope,
|
Ns::Label => &mut self.label_scope,
|
||||||
Ns::Value => &mut self.id_scope,
|
Ns::Value => &mut self.id_scope,
|
||||||
|
@ -288,20 +371,16 @@ impl<'a, 'b, 'w> DefUseCollector<'a, 'b, 'w> {
|
||||||
|
|
||||||
let id = DefId(id as u64);
|
let id = DefId(id as u64);
|
||||||
snap.insert(e.info.name.clone(), id);
|
snap.insert(e.info.name.clone(), id);
|
||||||
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_ref(&mut self, label: Ns, e: &LexicalHierarchy) {
|
fn insert_ident_ref(&mut self, label: Ns, id_ref: IdentRef) {
|
||||||
let snap = match label {
|
let snap = match label {
|
||||||
Ns::Label => &mut self.label_scope,
|
Ns::Label => &mut self.label_scope,
|
||||||
Ns::Value => &mut self.id_scope,
|
Ns::Value => &mut self.id_scope,
|
||||||
};
|
};
|
||||||
|
|
||||||
let id_ref = IdentRef {
|
match snap.get(&id_ref.name) {
|
||||||
name: e.info.name.clone(),
|
|
||||||
range: e.info.range.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
match snap.get(&e.info.name) {
|
|
||||||
Some(id) => {
|
Some(id) => {
|
||||||
self.info.ident_refs.insert(id_ref, *id);
|
self.info.ident_refs.insert(id_ref, *id);
|
||||||
}
|
}
|
||||||
|
@ -310,6 +389,29 @@ impl<'a, 'b, 'w> DefUseCollector<'a, 'b, 'w> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn insert_ref(&mut self, label: Ns, e: &LexicalHierarchy) {
|
||||||
|
self.insert_ident_ref(
|
||||||
|
label,
|
||||||
|
IdentRef {
|
||||||
|
name: e.info.name.clone(),
|
||||||
|
range: e.info.range.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_redef(&mut self, e: &LexicalHierarchy) {
|
||||||
|
let snap = &mut self.id_scope;
|
||||||
|
|
||||||
|
let id_ref = IdentRef {
|
||||||
|
name: e.info.name.clone(),
|
||||||
|
range: e.info.range.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(id) = snap.get(&e.info.name) {
|
||||||
|
self.info.ident_redefines.insert(id_ref, *id);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DefUseSnapshot<'a>(pub &'a DefUseInfo);
|
pub struct DefUseSnapshot<'a>(pub &'a DefUseInfo);
|
||||||
|
|
|
@ -1,453 +0,0 @@
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use log::{debug, trace};
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use typst::syntax::ast::Ident;
|
|
||||||
use typst::World;
|
|
||||||
use typst::{
|
|
||||||
foundations::{Func, Value},
|
|
||||||
syntax::{
|
|
||||||
ast::{self, AstNode},
|
|
||||||
LinkedNode, Source, Span, SyntaxKind,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use typst_ts_core::TypstFileId;
|
|
||||||
|
|
||||||
use crate::analysis::{deref_lvalue, find_source_by_import};
|
|
||||||
use crate::{prelude::*, TypstSpan};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct VariableDefinition<'a> {
|
|
||||||
pub def_site: LinkedNode<'a>,
|
|
||||||
pub use_site: LinkedNode<'a>,
|
|
||||||
pub span: TypstSpan,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct FuncDefinition<'a> {
|
|
||||||
pub value: Func,
|
|
||||||
pub use_site: LinkedNode<'a>,
|
|
||||||
pub span: TypstSpan,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ModuleDefinition<'a> {
|
|
||||||
pub module: TypstFileId,
|
|
||||||
pub use_site: LinkedNode<'a>,
|
|
||||||
pub span: TypstSpan,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ExternalDefinition<'a> {
|
|
||||||
pub use_site: LinkedNode<'a>,
|
|
||||||
pub span: TypstSpan,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Definition<'a> {
|
|
||||||
Func(FuncDefinition<'a>),
|
|
||||||
Var(VariableDefinition<'a>),
|
|
||||||
Module(ModuleDefinition<'a>),
|
|
||||||
External(ExternalDefinition<'a>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Definition<'_> {
|
|
||||||
pub fn span(&self) -> TypstSpan {
|
|
||||||
match self {
|
|
||||||
Definition::Func(f) => f.span,
|
|
||||||
Definition::Var(v) => v.span,
|
|
||||||
Definition::Module(m) => m.span,
|
|
||||||
Definition::External(s) => s.span,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn use_site(&self) -> &LinkedNode {
|
|
||||||
match self {
|
|
||||||
Definition::Func(f) => &f.use_site,
|
|
||||||
Definition::Var(v) => &v.use_site,
|
|
||||||
Definition::Module(m) => &m.use_site,
|
|
||||||
Definition::External(s) => &s.use_site,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn advance_prev_adjacent(node: LinkedNode) -> Option<LinkedNode> {
|
|
||||||
// this is aworkaround for a bug in the parser
|
|
||||||
if node.len() == 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
match node.prev_sibling() {
|
|
||||||
Some(prev) => Some(prev),
|
|
||||||
None => {
|
|
||||||
let parent = node.parent()?;
|
|
||||||
debug!("no prev sibling parent: {parent:?}");
|
|
||||||
advance_prev_adjacent(parent.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[comemo::memoize]
|
|
||||||
fn find_definition_in_module<'a>(
|
|
||||||
search_ctx: &'a SearchCtx<'a>,
|
|
||||||
source: Source,
|
|
||||||
name: &str,
|
|
||||||
) -> Option<Span> {
|
|
||||||
{
|
|
||||||
let mut s = search_ctx.searched.lock();
|
|
||||||
if s.contains(&source.id()) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
s.insert(source.id());
|
|
||||||
}
|
|
||||||
let root = source.root();
|
|
||||||
let node = LinkedNode::new(root);
|
|
||||||
let last_expr = if let Some(m) = root.cast::<ast::Markup>() {
|
|
||||||
m.exprs().last()?
|
|
||||||
} else {
|
|
||||||
debug!("unexpected root kind {:?}", root.kind());
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
let last = node.find(last_expr.span())?;
|
|
||||||
let e = find_syntax_definition(search_ctx, last, name)?;
|
|
||||||
Some(e.span())
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ImportRef<'a> {
|
|
||||||
/// `import "foo" as bar;`
|
|
||||||
/// ^^^
|
|
||||||
ModuleAs(Ident<'a>),
|
|
||||||
/// `import "foo.typ"`
|
|
||||||
/// ^^^
|
|
||||||
Path(ast::Expr<'a>),
|
|
||||||
/// `import "foo": bar`
|
|
||||||
/// ^^^
|
|
||||||
Ident(Ident<'a>),
|
|
||||||
/// `import "foo": bar as baz`
|
|
||||||
/// ^^^
|
|
||||||
IdentAs(ast::RenamedImportItem<'a>),
|
|
||||||
/// `import "foo": *`
|
|
||||||
ExternalResolved(Span),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_ref_in_import<'b, 'a>(
|
|
||||||
ctx: &'b SearchCtx<'b>,
|
|
||||||
import_node: ast::ModuleImport<'a>,
|
|
||||||
name: &str,
|
|
||||||
) -> Option<ImportRef<'a>> {
|
|
||||||
if let Some(import_node) = import_node.new_name() {
|
|
||||||
if import_node.get() == name {
|
|
||||||
return Some(ImportRef::ModuleAs(import_node));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(imports) = import_node.imports() else {
|
|
||||||
let v = import_node.source();
|
|
||||||
match v {
|
|
||||||
ast::Expr::Str(e) => {
|
|
||||||
let e = e.get();
|
|
||||||
let e = Path::new(e.as_ref());
|
|
||||||
let Some(e) = e.file_name() else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
let e = e.to_string_lossy();
|
|
||||||
let e = e.as_ref();
|
|
||||||
let Some(e) = e.strip_suffix(".typ") else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
return (e == name).then_some(ImportRef::Path(v));
|
|
||||||
}
|
|
||||||
_ => return None,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match imports {
|
|
||||||
ast::Imports::Wildcard => {
|
|
||||||
let dep = find_source_by_import(ctx.world.deref(), ctx.current, import_node)?;
|
|
||||||
let res = find_definition_in_module(ctx, dep, name)?;
|
|
||||||
return Some(ImportRef::ExternalResolved(res));
|
|
||||||
}
|
|
||||||
ast::Imports::Items(items) => {
|
|
||||||
for handle in items.iter() {
|
|
||||||
match handle {
|
|
||||||
ast::ImportItem::Simple(e) => {
|
|
||||||
if e.get() == name {
|
|
||||||
return Some(ImportRef::Ident(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ast::ImportItem::Renamed(e) => {
|
|
||||||
let o = e.new_name();
|
|
||||||
if o.get() == name {
|
|
||||||
return Some(ImportRef::IdentAs(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_syntax_definition<'b, 'a>(
|
|
||||||
search_ctx: &'b SearchCtx<'b>,
|
|
||||||
node: LinkedNode<'a>,
|
|
||||||
name: &str,
|
|
||||||
) -> Option<Definition<'a>> {
|
|
||||||
struct SyntaxDefinitionWorker<'a, 'b, 'c> {
|
|
||||||
ctx: &'c SearchCtx<'c>,
|
|
||||||
name: &'b str,
|
|
||||||
use_site: LinkedNode<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b, 'c> SyntaxDefinitionWorker<'a, 'b, 'c> {
|
|
||||||
fn find(&mut self, mut node: LinkedNode<'a>) -> Option<Definition<'a>> {
|
|
||||||
loop {
|
|
||||||
if let Some(def) = self.check(node.clone()) {
|
|
||||||
return Some(def);
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(prev) = advance_prev_adjacent(node) else {
|
|
||||||
debug!("no prev sibling parent");
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
node = prev;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_as_var(&self, node: LinkedNode<'a>, name: ast::Ident) -> Option<Definition<'a>> {
|
|
||||||
if name.get() != self.name {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let def_site = node.find(name.span())?;
|
|
||||||
Some(Definition::Var(VariableDefinition {
|
|
||||||
def_site,
|
|
||||||
use_site: self.use_site.clone(),
|
|
||||||
span: node.span(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check(&mut self, node: LinkedNode<'a>) -> Option<Definition<'a>> {
|
|
||||||
let node = deref_lvalue(node)?;
|
|
||||||
match node.kind() {
|
|
||||||
SyntaxKind::LetBinding => {
|
|
||||||
let binding = node.cast::<ast::LetBinding>()?;
|
|
||||||
match binding.kind() {
|
|
||||||
ast::LetBindingKind::Closure(name) => {
|
|
||||||
if name.get() == self.name {
|
|
||||||
let values =
|
|
||||||
analyze_expr(self.ctx.world.deref(), &node.find(name.span())?);
|
|
||||||
let func = values.into_iter().find_map(|v| match v.0 {
|
|
||||||
Value::Func(f) => Some(f),
|
|
||||||
_ => None,
|
|
||||||
});
|
|
||||||
let Some(func) = func else {
|
|
||||||
debug!("no func found... {name:?}");
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
return Some(Definition::Func(FuncDefinition {
|
|
||||||
value: func,
|
|
||||||
use_site: self.use_site.clone(),
|
|
||||||
span: node.span(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
ast::LetBindingKind::Normal(ast::Pattern::Normal(ast::Expr::Ident(
|
|
||||||
name,
|
|
||||||
))) => {
|
|
||||||
return self.resolve_as_var(node.clone(), name);
|
|
||||||
}
|
|
||||||
ast::LetBindingKind::Normal(ast::Pattern::Parenthesized(e)) => {
|
|
||||||
let e = deref_lvalue(node.find(e.span())?)?;
|
|
||||||
if let Some(name) = e.cast::<ast::Ident>() {
|
|
||||||
return self.resolve_as_var(e.clone(), name);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
ast::LetBindingKind::Normal(ast::Pattern::Normal(e)) => {
|
|
||||||
let e = deref_lvalue(node.find(e.span())?)?;
|
|
||||||
if let Some(name) = e.cast::<ast::Ident>() {
|
|
||||||
return self.resolve_as_var(e.clone(), name);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
ast::LetBindingKind::Normal(ast::Pattern::Destructuring(n)) => {
|
|
||||||
for i in n.bindings() {
|
|
||||||
if i.get() == self.name {
|
|
||||||
return self.resolve_as_var(node.clone(), i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
ast::LetBindingKind::Normal(ast::Pattern::Placeholder(..)) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SyntaxKind::ModuleImport => {
|
|
||||||
let import_node = node.cast::<ast::ModuleImport>()?;
|
|
||||||
|
|
||||||
match find_ref_in_import(self.ctx, import_node, self.name)? {
|
|
||||||
ImportRef::ModuleAs(ident) => {
|
|
||||||
let m = find_source_by_import(
|
|
||||||
self.ctx.world.deref(),
|
|
||||||
self.ctx.current,
|
|
||||||
import_node,
|
|
||||||
)?;
|
|
||||||
return Some(Definition::Module(ModuleDefinition {
|
|
||||||
module: m.id(),
|
|
||||||
use_site: self.use_site.clone(),
|
|
||||||
span: ident.span(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
ImportRef::Path(s) => {
|
|
||||||
let m = find_source_by_import(
|
|
||||||
self.ctx.world.deref(),
|
|
||||||
self.ctx.current,
|
|
||||||
import_node,
|
|
||||||
)?;
|
|
||||||
return Some(Definition::Module(ModuleDefinition {
|
|
||||||
module: m.id(),
|
|
||||||
use_site: self.use_site.clone(),
|
|
||||||
span: s.span(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
ImportRef::Ident(ident) => {
|
|
||||||
return Some(Definition::Var(VariableDefinition {
|
|
||||||
def_site: node.find(ident.span())?,
|
|
||||||
use_site: self.use_site.clone(),
|
|
||||||
span: ident.span(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
ImportRef::IdentAs(item) => {
|
|
||||||
let ident = item.new_name();
|
|
||||||
return Some(Definition::Var(VariableDefinition {
|
|
||||||
def_site: node.find(ident.span())?,
|
|
||||||
use_site: self.use_site.clone(),
|
|
||||||
span: ident.span(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
ImportRef::ExternalResolved(def_span) => {
|
|
||||||
return Some(Definition::External(ExternalDefinition {
|
|
||||||
use_site: self.use_site.clone(),
|
|
||||||
span: def_span,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut worker = SyntaxDefinitionWorker {
|
|
||||||
ctx: search_ctx,
|
|
||||||
name,
|
|
||||||
use_site: node.clone(),
|
|
||||||
};
|
|
||||||
worker.find(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SearchCtx<'a> {
|
|
||||||
world: Tracked<'a, dyn World>,
|
|
||||||
current: TypstFileId,
|
|
||||||
searched: Mutex<HashSet<TypstFileId>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: field definition
|
|
||||||
pub(crate) fn find_definition<'a>(
|
|
||||||
world: Tracked<'a, dyn World>,
|
|
||||||
current: TypstFileId,
|
|
||||||
node: LinkedNode<'a>,
|
|
||||||
) -> Option<Definition<'a>> {
|
|
||||||
let mut search_ctx = SearchCtx {
|
|
||||||
world,
|
|
||||||
current,
|
|
||||||
searched: Mutex::new(HashSet::new()),
|
|
||||||
};
|
|
||||||
let search_ctx = &mut search_ctx;
|
|
||||||
search_ctx.searched.lock().insert(current);
|
|
||||||
|
|
||||||
let mut ancestor = node;
|
|
||||||
while !ancestor.is::<ast::Expr>() {
|
|
||||||
ancestor = ancestor.parent()?.clone();
|
|
||||||
}
|
|
||||||
let ancestor = deref_lvalue(ancestor)?;
|
|
||||||
|
|
||||||
let may_ident = ancestor.cast::<ast::Expr>()?;
|
|
||||||
if !may_ident.hash() && !matches!(may_ident, ast::Expr::MathIdent(_)) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut is_ident_only = false;
|
|
||||||
trace!("got ast_node kind {kind:?}", kind = ancestor.kind());
|
|
||||||
let ref_node = match may_ident {
|
|
||||||
// todo: label, reference
|
|
||||||
// todo: import
|
|
||||||
// todo: include
|
|
||||||
ast::Expr::FuncCall(call) => call.callee(),
|
|
||||||
ast::Expr::Set(set) => set.target(),
|
|
||||||
ast::Expr::Ident(..) | ast::Expr::MathIdent(..) | ast::Expr::FieldAccess(..) => {
|
|
||||||
is_ident_only = true;
|
|
||||||
may_ident
|
|
||||||
}
|
|
||||||
ast::Expr::Str(..) => {
|
|
||||||
if let Some(parent) = ancestor.parent() {
|
|
||||||
let e = parent.cast::<ast::ModuleImport>()?;
|
|
||||||
let source = find_source_by_import(world.deref(), current, e)?;
|
|
||||||
let src = ancestor.find(e.source().span())?;
|
|
||||||
return Some(Definition::Module(ModuleDefinition {
|
|
||||||
module: source.id(),
|
|
||||||
use_site: src,
|
|
||||||
span: source.root().span(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
ast::Expr::Import(..) => {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
debug!("unsupported kind {kind:?}", kind = ancestor.kind());
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let use_site = if is_ident_only {
|
|
||||||
ancestor.clone()
|
|
||||||
} else {
|
|
||||||
ancestor.find(ref_node.span())?
|
|
||||||
};
|
|
||||||
|
|
||||||
let values = analyze_expr(world.deref(), &use_site);
|
|
||||||
|
|
||||||
let func = values.into_iter().find_map(|v| match &v.0 {
|
|
||||||
Value::Func(..) => Some(v.0),
|
|
||||||
_ => None,
|
|
||||||
});
|
|
||||||
|
|
||||||
Some(match func {
|
|
||||||
Some(Value::Func(f)) => Definition::Func(FuncDefinition {
|
|
||||||
value: f.clone(),
|
|
||||||
span: f.span(),
|
|
||||||
use_site,
|
|
||||||
}),
|
|
||||||
_ => {
|
|
||||||
return match may_ident {
|
|
||||||
ast::Expr::Ident(e) => find_syntax_definition(search_ctx, use_site, e.get()),
|
|
||||||
ast::Expr::MathIdent(e) => find_syntax_definition(search_ctx, use_site, e.get()),
|
|
||||||
ast::Expr::FieldAccess(..) => {
|
|
||||||
debug!("find field access");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
debug!("unsupported kind {kind:?}", kind = ancestor.kind());
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -40,76 +40,18 @@ pub fn find_source_by_import(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_imports(
|
#[comemo::memoize]
|
||||||
source: &Source,
|
pub fn find_imports(source: &Source) -> EcoVec<TypstFileId> {
|
||||||
def_id: Option<TypstFileId>,
|
|
||||||
) -> EcoVec<(VirtualPath, LinkedNode<'_>)> {
|
|
||||||
let root = LinkedNode::new(source.root());
|
let root = LinkedNode::new(source.root());
|
||||||
if let Some(def_id) = def_id.as_ref() {
|
|
||||||
debug!("find imports for {def_id:?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ImportWorker<'a> {
|
|
||||||
current: TypstFileId,
|
|
||||||
def_id: Option<TypstFileId>,
|
|
||||||
imports: EcoVec<(VirtualPath, LinkedNode<'a>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ImportWorker<'a> {
|
|
||||||
fn analyze(&mut self, node: LinkedNode<'a>) -> Option<()> {
|
|
||||||
match node.kind() {
|
|
||||||
SyntaxKind::ModuleImport => {
|
|
||||||
let i = node.cast::<ast::ModuleImport>().unwrap();
|
|
||||||
let src = i.source();
|
|
||||||
match src {
|
|
||||||
ast::Expr::Str(s) => {
|
|
||||||
// todo: source in packages
|
|
||||||
let s = s.get();
|
|
||||||
let path = Path::new(s.as_str());
|
|
||||||
let vpath = if path.is_relative() {
|
|
||||||
self.current.vpath().join(path)
|
|
||||||
} else {
|
|
||||||
VirtualPath::new(path)
|
|
||||||
};
|
|
||||||
debug!("found import {vpath:?}");
|
|
||||||
|
|
||||||
if self.def_id.is_some_and(|e| e.vpath() != &vpath) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.imports.push((vpath, node));
|
|
||||||
}
|
|
||||||
// todo: handle dynamic import
|
|
||||||
ast::Expr::FieldAccess(..) | ast::Expr::Ident(..) => {}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
SyntaxKind::ModuleInclude => {}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
for child in node.children() {
|
|
||||||
self.analyze(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut worker = ImportWorker {
|
let mut worker = ImportWorker {
|
||||||
current: source.id(),
|
current: source.id(),
|
||||||
def_id,
|
|
||||||
imports: EcoVec::new(),
|
imports: EcoVec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
worker.analyze(root);
|
worker.analyze(root);
|
||||||
|
let res = worker.imports;
|
||||||
|
|
||||||
worker.imports
|
|
||||||
}
|
|
||||||
|
|
||||||
#[comemo::memoize]
|
|
||||||
pub fn find_imports2(source: &Source) -> EcoVec<TypstFileId> {
|
|
||||||
let res = find_imports(source, None);
|
|
||||||
let mut res: Vec<TypstFileId> = res
|
let mut res: Vec<TypstFileId> = res
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(vpath, _)| TypstFileId::new(None, vpath))
|
.map(|(vpath, _)| TypstFileId::new(None, vpath))
|
||||||
|
@ -118,3 +60,45 @@ pub fn find_imports2(source: &Source) -> EcoVec<TypstFileId> {
|
||||||
res.dedup();
|
res.dedup();
|
||||||
res.into_iter().collect()
|
res.into_iter().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ImportWorker<'a> {
|
||||||
|
current: TypstFileId,
|
||||||
|
imports: EcoVec<(VirtualPath, LinkedNode<'a>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ImportWorker<'a> {
|
||||||
|
fn analyze(&mut self, node: LinkedNode<'a>) -> Option<()> {
|
||||||
|
match node.kind() {
|
||||||
|
SyntaxKind::ModuleImport => {
|
||||||
|
let i = node.cast::<ast::ModuleImport>().unwrap();
|
||||||
|
let src = i.source();
|
||||||
|
match src {
|
||||||
|
ast::Expr::Str(s) => {
|
||||||
|
// todo: source in packages
|
||||||
|
let s = s.get();
|
||||||
|
let path = Path::new(s.as_str());
|
||||||
|
let vpath = if path.is_relative() {
|
||||||
|
self.current.vpath().join(path)
|
||||||
|
} else {
|
||||||
|
VirtualPath::new(path)
|
||||||
|
};
|
||||||
|
debug!("found import {vpath:?}");
|
||||||
|
|
||||||
|
self.imports.push((vpath, node));
|
||||||
|
}
|
||||||
|
// todo: handle dynamic import
|
||||||
|
ast::Expr::FieldAccess(..) | ast::Expr::Ident(..) => {}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
SyntaxKind::ModuleInclude => {}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
for child in node.children() {
|
||||||
|
self.analyze(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,10 +14,7 @@ use typst::{
|
||||||
},
|
},
|
||||||
util::LazyHash,
|
util::LazyHash,
|
||||||
};
|
};
|
||||||
use typst_ts_core::{
|
use typst_ts_core::typst::prelude::{eco_vec, EcoVec};
|
||||||
typst::prelude::{eco_vec, EcoVec},
|
|
||||||
TypstFileId,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(crate) fn get_lexical_hierarchy(
|
pub(crate) fn get_lexical_hierarchy(
|
||||||
source: Source,
|
source: Source,
|
||||||
|
@ -84,13 +81,6 @@ pub enum LexicalModKind {
|
||||||
/// `import "foo": *`
|
/// `import "foo": *`
|
||||||
/// ^
|
/// ^
|
||||||
Star,
|
Star,
|
||||||
/// the symbol inside of `import "foo": *`
|
|
||||||
/// ^
|
|
||||||
ExternResolved {
|
|
||||||
#[serde(skip_serializing, skip_deserializing)]
|
|
||||||
at: Option<TypstFileId>,
|
|
||||||
in_mod_kind: Box<LexicalModKind>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
@ -421,12 +411,6 @@ impl LexicalHierarchyWorker {
|
||||||
self.get_symbols_with(n, IdentContext::Func)?;
|
self.get_symbols_with(n, IdentContext::Func)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.g == LexicalScopeKind::DefUse {
|
|
||||||
let param = node.children().find(|n| n.kind() == SyntaxKind::Params);
|
|
||||||
if let Some(param) = param {
|
|
||||||
self.get_symbols_with(param, IdentContext::Params)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let body = node
|
let body = node
|
||||||
.children()
|
.children()
|
||||||
.rev()
|
.rev()
|
||||||
|
@ -440,6 +424,15 @@ impl LexicalHierarchyWorker {
|
||||||
};
|
};
|
||||||
self.stack.push((symbol, eco_vec![]));
|
self.stack.push((symbol, eco_vec![]));
|
||||||
let stack_height = self.stack.len();
|
let stack_height = self.stack.len();
|
||||||
|
|
||||||
|
if self.g == LexicalScopeKind::DefUse {
|
||||||
|
let param =
|
||||||
|
node.children().find(|n| n.kind() == SyntaxKind::Params);
|
||||||
|
if let Some(param) = param {
|
||||||
|
self.get_symbols_with(param, IdentContext::Params)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.get_symbols_with(body, IdentContext::Ref)?;
|
self.get_symbols_with(body, IdentContext::Ref)?;
|
||||||
while stack_height <= self.stack.len() {
|
while stack_height <= self.stack.len() {
|
||||||
self.symbreak();
|
self.symbreak();
|
||||||
|
|
|
@ -11,6 +11,7 @@ pub fn deref_lvalue(mut node: LinkedNode) -> Option<LinkedNode> {
|
||||||
Some(node)
|
Some(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub enum DerefTarget<'a> {
|
pub enum DerefTarget<'a> {
|
||||||
VarAccess(LinkedNode<'a>),
|
VarAccess(LinkedNode<'a>),
|
||||||
Callee(LinkedNode<'a>),
|
Callee(LinkedNode<'a>),
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::{collections::HashMap, sync::Once};
|
||||||
|
|
||||||
use typst_ts_core::{typst::prelude::EcoVec, TypstFileId};
|
use typst_ts_core::{typst::prelude::EcoVec, TypstFileId};
|
||||||
|
|
||||||
use super::{find_imports2, AnalysisContext};
|
use super::{find_imports, AnalysisContext};
|
||||||
|
|
||||||
pub struct ModuleDependency {
|
pub struct ModuleDependency {
|
||||||
pub dependencies: EcoVec<TypstFileId>,
|
pub dependencies: EcoVec<TypstFileId>,
|
||||||
|
@ -28,7 +28,7 @@ pub fn construct_module_dependencies(
|
||||||
};
|
};
|
||||||
|
|
||||||
let file_id = source.id();
|
let file_id = source.id();
|
||||||
let deps = find_imports2(&source);
|
let deps = find_imports(&source);
|
||||||
dependencies
|
dependencies
|
||||||
.entry(file_id)
|
.entry(file_id)
|
||||||
.or_insert_with(|| ModuleDependency {
|
.or_insert_with(|| ModuleDependency {
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
use typst::syntax::{
|
|
||||||
ast::{self, AstNode},
|
|
||||||
LinkedNode, SyntaxKind,
|
|
||||||
};
|
|
||||||
use typst_ts_core::typst::prelude::{eco_vec, EcoVec};
|
|
||||||
|
|
||||||
pub fn find_lexical_references_after<'a, 'b: 'a>(
|
|
||||||
parent: LinkedNode<'a>,
|
|
||||||
node: LinkedNode<'a>,
|
|
||||||
target: &'b str,
|
|
||||||
) -> EcoVec<LinkedNode<'a>> {
|
|
||||||
let mut worker = Worker {
|
|
||||||
idents: eco_vec![],
|
|
||||||
target,
|
|
||||||
};
|
|
||||||
worker.analyze_after(parent, node);
|
|
||||||
|
|
||||||
worker.idents
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Worker<'a> {
|
|
||||||
target: &'a str,
|
|
||||||
idents: EcoVec<LinkedNode<'a>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Worker<'a> {
|
|
||||||
fn analyze_after(&mut self, parent: LinkedNode<'a>, node: LinkedNode<'a>) -> Option<()> {
|
|
||||||
let mut after_node = false;
|
|
||||||
|
|
||||||
for child in parent.children() {
|
|
||||||
if child.offset() > node.offset() {
|
|
||||||
after_node = true;
|
|
||||||
}
|
|
||||||
if after_node {
|
|
||||||
self.analyze(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn analyze(&mut self, node: LinkedNode<'a>) -> Option<()> {
|
|
||||||
match node.kind() {
|
|
||||||
SyntaxKind::LetBinding => {
|
|
||||||
let lb = node.cast::<ast::LetBinding>().unwrap();
|
|
||||||
let name = lb.kind().bindings();
|
|
||||||
for n in name {
|
|
||||||
if n.get() == self.target {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(init) = lb.init() {
|
|
||||||
let init_expr = node.find(init.span())?;
|
|
||||||
self.analyze(init_expr);
|
|
||||||
}
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
// todo: analyze import effect
|
|
||||||
SyntaxKind::Import => {}
|
|
||||||
SyntaxKind::Ident | SyntaxKind::MathIdent => {
|
|
||||||
if self.target == node.text() {
|
|
||||||
self.idents.push(node.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
for child in node.children() {
|
|
||||||
self.analyze(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
#let term(term) = term;
|
||||||
|
#term(1)
|
|
@ -27,7 +27,7 @@ input_file: crates/tinymist-query/src/fixtures/def_use/import_alias_both.typ
|
||||||
},
|
},
|
||||||
"refs": []
|
"refs": []
|
||||||
},
|
},
|
||||||
"foo@54..62@s0.typ": {
|
"foo@59..62@s0.typ": {
|
||||||
"def": {
|
"def": {
|
||||||
"kind": {
|
"kind": {
|
||||||
"Mod": {
|
"Mod": {
|
||||||
|
@ -40,7 +40,7 @@ input_file: crates/tinymist-query/src/fixtures/def_use/import_alias_both.typ
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
"range": "54:62"
|
"range": "59:62"
|
||||||
},
|
},
|
||||||
"refs": [
|
"refs": [
|
||||||
"foo@72..75"
|
"foo@72..75"
|
||||||
|
|
|
@ -17,7 +17,7 @@ input_file: crates/tinymist-query/src/fixtures/def_use/import_ident_alias.typ
|
||||||
"base@58..62"
|
"base@58..62"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"foo@47..55@s0.typ": {
|
"foo@52..55@s0.typ": {
|
||||||
"def": {
|
"def": {
|
||||||
"kind": {
|
"kind": {
|
||||||
"Mod": {
|
"Mod": {
|
||||||
|
@ -30,7 +30,7 @@ input_file: crates/tinymist-query/src/fixtures/def_use/import_ident_alias.typ
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
"range": "47:55"
|
"range": "52:55"
|
||||||
},
|
},
|
||||||
"refs": [
|
"refs": [
|
||||||
"foo@65..68"
|
"foo@65..68"
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
source: crates/tinymist-query/src/analysis.rs
|
||||||
|
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||||
|
input_file: crates/tinymist-query/src/fixtures/def_use/param_scope.typ
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"term@10..14@s0.typ": {
|
||||||
|
"def": {
|
||||||
|
"kind": {
|
||||||
|
"Var": "Variable"
|
||||||
|
},
|
||||||
|
"name": "term",
|
||||||
|
"range": "10:14"
|
||||||
|
},
|
||||||
|
"refs": [
|
||||||
|
"term@18..22"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"term@5..9@s0.typ": {
|
||||||
|
"def": {
|
||||||
|
"kind": {
|
||||||
|
"Var": "Function"
|
||||||
|
},
|
||||||
|
"name": "term",
|
||||||
|
"range": "5:9"
|
||||||
|
},
|
||||||
|
"refs": [
|
||||||
|
"term@26..30"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
// path: base.typ
|
||||||
|
#let f() = 1;
|
||||||
|
-----
|
||||||
|
.
|
||||||
|
|
||||||
|
#import "base.typ": /* ident after */ f;
|
|
@ -0,0 +1,6 @@
|
||||||
|
// path: base.typ
|
||||||
|
#let f() = 1;
|
||||||
|
-----
|
||||||
|
.
|
||||||
|
|
||||||
|
#import "base.typ": /* ident after */ f as ff;
|
|
@ -0,0 +1,6 @@
|
||||||
|
// path: base.typ
|
||||||
|
#let f() = 1;
|
||||||
|
-----
|
||||||
|
.
|
||||||
|
|
||||||
|
#import "base.typ": f as /* ident after */ ff;
|
|
@ -6,7 +6,7 @@ input_file: crates/tinymist-query/src/fixtures/goto_definition/at_def.typ
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"originSelectionRange": "0:23:0:24",
|
"originSelectionRange": "0:23:0:24",
|
||||||
"targetRange": "0:24:0:26",
|
"targetRange": "0:23:0:24",
|
||||||
"targetSelectionRange": "0:24:0:26"
|
"targetSelectionRange": "0:23:0:24"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,7 +6,7 @@ input_file: crates/tinymist-query/src/fixtures/goto_definition/base.typ
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"originSelectionRange": "1:23:1:24",
|
"originSelectionRange": "1:23:1:24",
|
||||||
"targetRange": "0:6:0:8",
|
"targetRange": "0:5:0:6",
|
||||||
"targetSelectionRange": "0:6:0:8"
|
"targetSelectionRange": "0:5:0:6"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,7 +6,7 @@ input_file: crates/tinymist-query/src/fixtures/goto_definition/import_alias.typ
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"originSelectionRange": "1:23:1:26",
|
"originSelectionRange": "1:23:1:26",
|
||||||
"targetRange": "0:6:0:8",
|
"targetRange": "0:25:0:28",
|
||||||
"targetSelectionRange": "0:6:0:8"
|
"targetSelectionRange": "0:25:0:28"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,7 +6,7 @@ input_file: crates/tinymist-query/src/fixtures/goto_definition/import_ident.typ
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"originSelectionRange": "1:23:1:24",
|
"originSelectionRange": "1:23:1:24",
|
||||||
"targetRange": "0:6:0:8",
|
"targetRange": "0:5:0:6",
|
||||||
"targetSelectionRange": "0:6:0:8"
|
"targetSelectionRange": "0:5:0:6"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,7 +6,7 @@ input_file: crates/tinymist-query/src/fixtures/goto_definition/import_star.typ
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"originSelectionRange": "3:23:3:24",
|
"originSelectionRange": "3:23:3:24",
|
||||||
"targetRange": "0:6:0:8",
|
"targetRange": "0:5:0:6",
|
||||||
"targetSelectionRange": "0:6:0:8"
|
"targetSelectionRange": "0:5:0:6"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,7 +6,7 @@ input_file: crates/tinymist-query/src/fixtures/goto_definition/import_star_varia
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"originSelectionRange": "3:23:3:24",
|
"originSelectionRange": "3:23:3:24",
|
||||||
"targetRange": "0:1:0:10",
|
"targetRange": "0:5:0:6",
|
||||||
"targetSelectionRange": "0:1:0:10"
|
"targetSelectionRange": "0:5:0:6"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
source: crates/tinymist-query/src/goto_definition.rs
|
||||||
|
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||||
|
input_file: crates/tinymist-query/src/fixtures/goto_definition/inside_import.typ
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"originSelectionRange": "2:38:2:39",
|
||||||
|
"targetRange": "0:5:0:6",
|
||||||
|
"targetSelectionRange": "0:5:0:6"
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
source: crates/tinymist-query/src/goto_definition.rs
|
||||||
|
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||||
|
input_file: crates/tinymist-query/src/fixtures/goto_definition/inside_import_alias.typ
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"originSelectionRange": "2:38:2:39",
|
||||||
|
"targetRange": "0:5:0:6",
|
||||||
|
"targetSelectionRange": "0:5:0:6"
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
source: crates/tinymist-query/src/goto_definition.rs
|
||||||
|
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||||
|
input_file: crates/tinymist-query/src/fixtures/goto_definition/inside_import_alias2.typ
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"originSelectionRange": "2:43:2:45",
|
||||||
|
"targetRange": "2:43:2:45",
|
||||||
|
"targetSelectionRange": "2:43:2:45"
|
||||||
|
}
|
||||||
|
]
|
|
@ -6,7 +6,7 @@ input_file: crates/tinymist-query/src/fixtures/goto_definition/paren.typ
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"originSelectionRange": "1:23:1:24",
|
"originSelectionRange": "1:23:1:24",
|
||||||
"targetRange": "0:5:0:14",
|
"targetRange": "0:9:0:10",
|
||||||
"targetSelectionRange": "0:5:0:14"
|
"targetSelectionRange": "0:9:0:10"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,7 +6,7 @@ input_file: crates/tinymist-query/src/fixtures/goto_definition/variable.typ
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"originSelectionRange": "1:23:1:24",
|
"originSelectionRange": "1:23:1:24",
|
||||||
"targetRange": "0:1:0:10",
|
"targetRange": "0:5:0:6",
|
||||||
"targetSelectionRange": "0:1:0:10"
|
"targetSelectionRange": "0:5:0:6"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -18,15 +18,15 @@ input_file: crates/tinymist-query/src/fixtures/lexical_hierarchy/func.typ
|
||||||
"name": "f",
|
"name": "f",
|
||||||
"range": "18:19"
|
"range": "18:19"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"kind": {
|
|
||||||
"Var": "Variable"
|
|
||||||
},
|
|
||||||
"name": "a",
|
|
||||||
"range": "20:21"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"children": [
|
"children": [
|
||||||
|
{
|
||||||
|
"kind": {
|
||||||
|
"Var": "Variable"
|
||||||
|
},
|
||||||
|
"name": "a",
|
||||||
|
"range": "20:21"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"kind": {
|
"kind": {
|
||||||
"Var": "ValRef"
|
"Var": "ValRef"
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
#import "base2.typ": x
|
||||||
|
#x
|
||||||
|
-----
|
||||||
|
// path: base2.typ
|
||||||
|
#import "base.typ": x
|
||||||
|
#x
|
||||||
|
-----
|
||||||
|
// path: base.typ
|
||||||
|
#let /* ident after */ x = 1;
|
|
@ -0,0 +1,9 @@
|
||||||
|
#import "base2.typ": *
|
||||||
|
#x
|
||||||
|
-----
|
||||||
|
// path: base2.typ
|
||||||
|
#import "base.typ": *
|
||||||
|
#x
|
||||||
|
-----
|
||||||
|
// path: base.typ
|
||||||
|
#let /* ident after */ x = 1;
|
|
@ -0,0 +1,10 @@
|
||||||
|
// path: basic/writing.typ
|
||||||
|
#import "mod.typ": *
|
||||||
|
#exercise()
|
||||||
|
-----
|
||||||
|
// path: basic/mod.typ
|
||||||
|
#import "../mod.typ": exercise
|
||||||
|
#exercise()
|
||||||
|
-----
|
||||||
|
// path: mod.typ
|
||||||
|
#let /* ident after */ exercise() = [];
|
|
@ -1,13 +1,9 @@
|
||||||
---
|
---
|
||||||
source: crates/tinymist-query/src/references.rs
|
source: crates/tinymist-query/src/references.rs
|
||||||
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
expression: "JsonRepr::new_pure(result)"
|
||||||
input_file: crates/tinymist-query/src/fixtures/references/at_def.typ
|
input_file: crates/tinymist-query/src/fixtures/references/at_def.typ
|
||||||
---
|
---
|
||||||
[
|
[
|
||||||
{
|
"/s0.typ@1:2:1:3",
|
||||||
"range": "1:2:1:3"
|
"/s0.typ@2:2:2:3"
|
||||||
},
|
|
||||||
{
|
|
||||||
"range": "2:2:2:3"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
---
|
---
|
||||||
source: crates/tinymist-query/src/goto_declaration.rs
|
source: crates/tinymist-query/src/references.rs
|
||||||
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
expression: "JsonRepr::new_pure(result)"
|
||||||
input_file: crates/tinymist-query/src/fixtures/goto_declaration/base.typ
|
input_file: crates/tinymist-query/src/fixtures/references/base.typ
|
||||||
---
|
---
|
||||||
null
|
[
|
||||||
|
"/s0.typ@1:23:1:24"
|
||||||
|
]
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
---
|
---
|
||||||
source: crates/tinymist-query/src/references.rs
|
source: crates/tinymist-query/src/references.rs
|
||||||
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
expression: "JsonRepr::new_pure(result)"
|
||||||
input_file: crates/tinymist-query/src/fixtures/references/cross_module.typ
|
input_file: crates/tinymist-query/src/fixtures/references/cross_module.typ
|
||||||
---
|
---
|
||||||
[
|
[
|
||||||
{
|
"/s0.typ@1:1:1:2"
|
||||||
"range": "1:1:1:2"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
---
|
---
|
||||||
source: crates/tinymist-query/src/references.rs
|
source: crates/tinymist-query/src/references.rs
|
||||||
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
expression: "JsonRepr::new_pure(result)"
|
||||||
input_file: crates/tinymist-query/src/fixtures/references/cross_module2.typ
|
input_file: crates/tinymist-query/src/fixtures/references/cross_module2.typ
|
||||||
---
|
---
|
||||||
[
|
[
|
||||||
{
|
"/s0.typ@0:20:0:21",
|
||||||
"range": "0:20:0:21"
|
"/s0.typ@1:1:1:2"
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
---
|
---
|
||||||
source: crates/tinymist-query/src/references.rs
|
source: crates/tinymist-query/src/references.rs
|
||||||
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
expression: "JsonRepr::new_pure(result)"
|
||||||
input_file: crates/tinymist-query/src/fixtures/references/cross_module_absolute.typ
|
input_file: crates/tinymist-query/src/fixtures/references/cross_module_absolute.typ
|
||||||
---
|
---
|
||||||
[
|
[
|
||||||
{
|
"/out/main.typ@0:25:0:26",
|
||||||
"range": "0:25:0:26"
|
"/out/main.typ@1:1:1:2"
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
---
|
---
|
||||||
source: crates/tinymist-query/src/references.rs
|
source: crates/tinymist-query/src/references.rs
|
||||||
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
expression: "JsonRepr::new_pure(result)"
|
||||||
input_file: crates/tinymist-query/src/fixtures/references/cross_module_alias.typ
|
input_file: crates/tinymist-query/src/fixtures/references/cross_module_alias.typ
|
||||||
---
|
---
|
||||||
[
|
[
|
||||||
{
|
"/s1.typ@1:1:1:3"
|
||||||
"range": "1:1:1:3"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
---
|
---
|
||||||
source: crates/tinymist-query/src/references.rs
|
source: crates/tinymist-query/src/references.rs
|
||||||
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
expression: "JsonRepr::new_pure(result)"
|
||||||
input_file: crates/tinymist-query/src/fixtures/references/cross_module_alias2.typ
|
input_file: crates/tinymist-query/src/fixtures/references/cross_module_alias2.typ
|
||||||
---
|
---
|
||||||
[
|
[
|
||||||
{
|
"/s0.typ@0:20:0:21"
|
||||||
"range": "0:20:0:21"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
---
|
---
|
||||||
source: crates/tinymist-query/src/references.rs
|
source: crates/tinymist-query/src/references.rs
|
||||||
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
expression: "JsonRepr::new_pure(result)"
|
||||||
input_file: crates/tinymist-query/src/fixtures/references/cross_module_relative.typ
|
input_file: crates/tinymist-query/src/fixtures/references/cross_module_relative.typ
|
||||||
---
|
---
|
||||||
[
|
[
|
||||||
{
|
"/out/main.typ@0:20:0:21",
|
||||||
"range": "0:20:0:21"
|
"/out/main.typ@1:1:1:2"
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
source: crates/tinymist-query/src/references.rs
|
||||||
|
expression: "JsonRepr::new_pure(result)"
|
||||||
|
input_file: crates/tinymist-query/src/fixtures/references/recursive_import.typ
|
||||||
|
---
|
||||||
|
[
|
||||||
|
"/base2.typ@0:20:0:21",
|
||||||
|
"/s0.typ@0:21:0:22",
|
||||||
|
"/base2.typ@1:1:1:2",
|
||||||
|
"/s0.typ@1:1:1:2"
|
||||||
|
]
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
source: crates/tinymist-query/src/references.rs
|
||||||
|
expression: "JsonRepr::new_pure(result)"
|
||||||
|
input_file: crates/tinymist-query/src/fixtures/references/recursive_import_star.typ
|
||||||
|
---
|
||||||
|
[
|
||||||
|
"/base2.typ@1:1:1:2",
|
||||||
|
"/s0.typ@1:1:1:2"
|
||||||
|
]
|
|
@ -1,19 +1,11 @@
|
||||||
---
|
---
|
||||||
source: crates/tinymist-query/src/references.rs
|
source: crates/tinymist-query/src/references.rs
|
||||||
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
expression: "JsonRepr::new_pure(result)"
|
||||||
input_file: crates/tinymist-query/src/fixtures/references/redefine.typ
|
input_file: crates/tinymist-query/src/fixtures/references/redefine.typ
|
||||||
---
|
---
|
||||||
[
|
[
|
||||||
{
|
"/s0.typ@3:2:3:3",
|
||||||
"range": "3:2:3:3"
|
"/s0.typ@3:6:3:7",
|
||||||
},
|
"/s0.typ@6:12:6:13",
|
||||||
{
|
"/s0.typ@8:9:8:10"
|
||||||
"range": "3:6:3:7"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"range": "6:12:6:13"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"range": "8:9:8:10"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
source: crates/tinymist-query/src/references.rs
|
||||||
|
expression: "JsonRepr::new_pure(result)"
|
||||||
|
input_file: crates/tinymist-query/src/fixtures/references/rename_issue_exercise.typ
|
||||||
|
---
|
||||||
|
[
|
||||||
|
"/basic/mod.typ@0:22:0:30",
|
||||||
|
"/basic/mod.typ@1:1:1:9",
|
||||||
|
"/basic/writing.typ@1:1:1:9"
|
||||||
|
]
|
|
@ -1,8 +1,22 @@
|
||||||
use comemo::Track;
|
use std::ops::Range;
|
||||||
use log::debug;
|
|
||||||
use lsp_types::LocationLink;
|
|
||||||
|
|
||||||
use crate::{analysis::find_definition, prelude::*};
|
use log::debug;
|
||||||
|
use typst::{
|
||||||
|
foundations::Value,
|
||||||
|
syntax::{
|
||||||
|
ast::{self},
|
||||||
|
LinkedNode, Source,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use typst_ts_core::TypstFileId;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
analysis::{
|
||||||
|
find_source_by_import, get_def_use, get_deref_target, DerefTarget, IdentRef, LexicalKind,
|
||||||
|
LexicalModKind, LexicalVarKind,
|
||||||
|
},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GotoDefinitionRequest {
|
pub struct GotoDefinitionRequest {
|
||||||
|
@ -13,38 +27,29 @@ pub struct GotoDefinitionRequest {
|
||||||
impl GotoDefinitionRequest {
|
impl GotoDefinitionRequest {
|
||||||
pub fn request(
|
pub fn request(
|
||||||
self,
|
self,
|
||||||
world: &TypstSystemWorld,
|
ctx: &mut AnalysisContext,
|
||||||
position_encoding: PositionEncoding,
|
position_encoding: PositionEncoding,
|
||||||
) -> Option<GotoDefinitionResponse> {
|
) -> Option<GotoDefinitionResponse> {
|
||||||
let source = get_suitable_source_in_workspace(world, &self.path).ok()?;
|
let source = ctx.source_by_path(&self.path).ok()?;
|
||||||
let offset = lsp_to_typst::position(self.position, position_encoding, &source)?;
|
let offset = lsp_to_typst::position(self.position, position_encoding, &source)?;
|
||||||
let cursor = offset + 1;
|
let cursor = offset + 1;
|
||||||
|
|
||||||
let def = {
|
let ast_node = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
||||||
let ast_node = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
debug!("ast_node: {ast_node:?}", ast_node = ast_node);
|
||||||
let t: &dyn World = world;
|
|
||||||
find_definition(t.track(), source.id(), ast_node)?
|
|
||||||
};
|
|
||||||
let (span, use_site) = (def.span(), def.use_site());
|
|
||||||
|
|
||||||
if span.is_detached() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let Some(id) = span.id() else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
let deref_target = get_deref_target(ast_node)?;
|
||||||
|
let use_site = deref_target.node().clone();
|
||||||
let origin_selection_range =
|
let origin_selection_range =
|
||||||
typst_to_lsp::range(use_site.range(), &source, position_encoding);
|
typst_to_lsp::range(use_site.range(), &source, position_encoding);
|
||||||
|
|
||||||
let span_path = world.path_for_id(id).ok()?;
|
let def = find_definition(ctx, source.clone(), deref_target)?;
|
||||||
let span_source = world.source(id).ok()?;
|
|
||||||
let def_node = span_source.find(span)?;
|
|
||||||
let typst_range = def_node.range();
|
|
||||||
let range = typst_to_lsp::range(typst_range, &span_source, position_encoding);
|
|
||||||
|
|
||||||
|
let span_path = ctx.world.path_for_id(def.fid).ok()?;
|
||||||
let uri = Url::from_file_path(span_path).ok()?;
|
let uri = Url::from_file_path(span_path).ok()?;
|
||||||
|
|
||||||
|
let span_source = ctx.source_by_id(def.fid).ok()?;
|
||||||
|
let range = typst_to_lsp::range(def.def_range, &span_source, position_encoding);
|
||||||
|
|
||||||
let res = Some(GotoDefinitionResponse::Link(vec![LocationLink {
|
let res = Some(GotoDefinitionResponse::Link(vec![LocationLink {
|
||||||
origin_selection_range: Some(origin_selection_range),
|
origin_selection_range: Some(origin_selection_range),
|
||||||
target_uri: uri,
|
target_uri: uri,
|
||||||
|
@ -52,11 +57,154 @@ impl GotoDefinitionRequest {
|
||||||
target_selection_range: range,
|
target_selection_range: range,
|
||||||
}]));
|
}]));
|
||||||
|
|
||||||
debug!("goto_definition: {res:?}");
|
debug!("goto_definition: {:?} {res:?}", def.fid);
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct DefinitionLink {
|
||||||
|
pub value: Option<Value>,
|
||||||
|
pub fid: TypstFileId,
|
||||||
|
pub name: String,
|
||||||
|
pub def_range: Range<usize>,
|
||||||
|
pub name_range: Option<Range<usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: field definition
|
||||||
|
pub(crate) fn find_definition(
|
||||||
|
ctx: &mut AnalysisContext<'_>,
|
||||||
|
source: Source,
|
||||||
|
deref_target: DerefTarget<'_>,
|
||||||
|
) -> Option<DefinitionLink> {
|
||||||
|
let source_id = source.id();
|
||||||
|
|
||||||
|
let use_site = match deref_target {
|
||||||
|
// todi: field access
|
||||||
|
DerefTarget::VarAccess(node) | DerefTarget::Callee(node) => node,
|
||||||
|
// todo: better support (rename import path?)
|
||||||
|
DerefTarget::ImportPath(path) => {
|
||||||
|
let parent = path.parent()?;
|
||||||
|
let def_fid = parent.span().id()?;
|
||||||
|
let e = parent.cast::<ast::ModuleImport>()?;
|
||||||
|
let source = find_source_by_import(ctx.world, def_fid, e)?;
|
||||||
|
return Some(DefinitionLink {
|
||||||
|
name: String::new(),
|
||||||
|
value: None,
|
||||||
|
fid: source.id(),
|
||||||
|
def_range: (LinkedNode::new(source.root())).range(),
|
||||||
|
name_range: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// syntatic definition
|
||||||
|
let def_use = get_def_use(ctx, source)?;
|
||||||
|
let ident_ref = match use_site.cast::<ast::Expr>()? {
|
||||||
|
ast::Expr::Ident(e) => IdentRef {
|
||||||
|
name: e.get().to_string(),
|
||||||
|
range: use_site.range(),
|
||||||
|
},
|
||||||
|
ast::Expr::MathIdent(e) => IdentRef {
|
||||||
|
name: e.get().to_string(),
|
||||||
|
range: use_site.range(),
|
||||||
|
},
|
||||||
|
ast::Expr::FieldAccess(..) => {
|
||||||
|
debug!("find field access");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
debug!("unsupported kind {kind:?}", kind = use_site.kind());
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let def_id = def_use.get_ref(&ident_ref);
|
||||||
|
let def_id = def_id.or_else(|| Some(def_use.get_def(source_id, &ident_ref)?.0));
|
||||||
|
let def_info = def_id.and_then(|def_id| def_use.get_def_by_id(def_id));
|
||||||
|
|
||||||
|
let values = analyze_expr(ctx.world, &use_site);
|
||||||
|
for v in values {
|
||||||
|
// mostly builtin functions
|
||||||
|
if let Value::Func(f) = v.0 {
|
||||||
|
use typst::foundations::func::Repr;
|
||||||
|
match f.inner() {
|
||||||
|
// The with function should be resolved as the with position
|
||||||
|
Repr::Closure(..) | Repr::With(..) => continue,
|
||||||
|
Repr::Native(..) | Repr::Element(..) => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = f
|
||||||
|
.name()
|
||||||
|
.or_else(|| def_info.as_ref().map(|(_, r)| r.name.as_str()));
|
||||||
|
|
||||||
|
if let Some(name) = name {
|
||||||
|
let span = f.span();
|
||||||
|
let fid = span.id()?;
|
||||||
|
let source = ctx.source_by_id(fid).ok()?;
|
||||||
|
|
||||||
|
return Some(DefinitionLink {
|
||||||
|
name: name.to_owned(),
|
||||||
|
value: Some(Value::Func(f.clone())),
|
||||||
|
fid,
|
||||||
|
def_range: source.find(span)?.range(),
|
||||||
|
name_range: def_info.map(|(_, r)| r.range.clone()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (def_fid, def) = def_info?;
|
||||||
|
|
||||||
|
match def.kind {
|
||||||
|
LexicalKind::Heading(..) | LexicalKind::Block => unreachable!(),
|
||||||
|
LexicalKind::Var(
|
||||||
|
LexicalVarKind::Variable
|
||||||
|
| LexicalVarKind::ValRef
|
||||||
|
| LexicalVarKind::Label
|
||||||
|
| LexicalVarKind::LabelRef,
|
||||||
|
)
|
||||||
|
| LexicalKind::Mod(
|
||||||
|
LexicalModKind::Module(..)
|
||||||
|
| LexicalModKind::PathVar
|
||||||
|
| LexicalModKind::ModuleAlias
|
||||||
|
| LexicalModKind::Alias { .. }
|
||||||
|
| LexicalModKind::Ident,
|
||||||
|
) => Some(DefinitionLink {
|
||||||
|
name: def.name.clone(),
|
||||||
|
value: None,
|
||||||
|
fid: def_fid,
|
||||||
|
def_range: def.range.clone(),
|
||||||
|
name_range: Some(def.range.clone()),
|
||||||
|
}),
|
||||||
|
LexicalKind::Var(LexicalVarKind::Function) => {
|
||||||
|
let def_source = ctx.source_by_id(def_fid).ok()?;
|
||||||
|
let root = LinkedNode::new(def_source.root());
|
||||||
|
let def_name = root.leaf_at(def.range.start + 1)?;
|
||||||
|
log::info!("def_name for function: {def_name:?}", def_name = def_name);
|
||||||
|
let values = analyze_expr(ctx.world, &def_name);
|
||||||
|
let Some(func) = values.into_iter().find_map(|v| match v.0 {
|
||||||
|
Value::Func(f) => Some(f),
|
||||||
|
_ => None,
|
||||||
|
}) else {
|
||||||
|
log::info!("no func found... {:?}", def.name);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
log::info!("okay for function: {func:?}");
|
||||||
|
|
||||||
|
Some(DefinitionLink {
|
||||||
|
name: def.name.clone(),
|
||||||
|
value: Some(Value::Func(func.clone())),
|
||||||
|
fid: def_fid,
|
||||||
|
def_range: def.range.clone(),
|
||||||
|
name_range: Some(def.range.clone()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
LexicalKind::Mod(LexicalModKind::Star) => {
|
||||||
|
log::info!("unimplemented star import {:?}", ident_ref);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -64,9 +212,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test() {
|
fn test() {
|
||||||
// goto_definition
|
snapshot_testing2("goto_definition", &|world, path| {
|
||||||
snapshot_testing("goto_definition", &|world, path| {
|
let source = world.source_by_path(&path).unwrap();
|
||||||
let source = get_suitable_source_in_workspace(world, &path).unwrap();
|
|
||||||
|
|
||||||
let request = GotoDefinitionRequest {
|
let request = GotoDefinitionRequest {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
|
|
|
@ -5,16 +5,15 @@ pub use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use comemo::{Track, Tracked};
|
|
||||||
pub use itertools::{Format, Itertools};
|
pub use itertools::{Format, Itertools};
|
||||||
pub use log::{error, trace};
|
pub use log::{error, trace};
|
||||||
pub use lsp_types::{
|
pub use lsp_types::{
|
||||||
request::GotoDeclarationResponse, CodeLens, CompletionResponse, DiagnosticRelatedInformation,
|
request::GotoDeclarationResponse, CodeLens, CompletionResponse, DiagnosticRelatedInformation,
|
||||||
DocumentSymbol, DocumentSymbolResponse, Documentation, FoldingRange, GotoDefinitionResponse,
|
DocumentSymbol, DocumentSymbolResponse, Documentation, FoldingRange, GotoDefinitionResponse,
|
||||||
Hover, InlayHint, Location as LspLocation, MarkupContent, MarkupKind, Position as LspPosition,
|
Hover, InlayHint, Location as LspLocation, LocationLink, MarkupContent, MarkupKind,
|
||||||
PrepareRenameResponse, SelectionRange, SemanticTokens, SemanticTokensDelta,
|
Position as LspPosition, PrepareRenameResponse, SelectionRange, SemanticTokens,
|
||||||
SemanticTokensFullDeltaResult, SemanticTokensResult, SignatureHelp, SignatureInformation,
|
SemanticTokensDelta, SemanticTokensFullDeltaResult, SemanticTokensResult, SignatureHelp,
|
||||||
SymbolInformation, Url, WorkspaceEdit,
|
SignatureInformation, SymbolInformation, Url, WorkspaceEdit,
|
||||||
};
|
};
|
||||||
pub use serde_json::Value as JsonValue;
|
pub use serde_json::Value as JsonValue;
|
||||||
pub use typst::diag::{EcoString, FileError, FileResult, Tracepoint};
|
pub use typst::diag::{EcoString, FileError, FileResult, Tracepoint};
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
use crate::{
|
use crate::{analysis::get_deref_target, find_definition, prelude::*, DefinitionLink};
|
||||||
analysis::{find_definition, Definition},
|
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -10,6 +7,8 @@ pub struct PrepareRenameRequest {
|
||||||
pub position: LspPosition,
|
pub position: LspPosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: rename alias
|
||||||
|
// todo: rename import path?
|
||||||
impl PrepareRenameRequest {
|
impl PrepareRenameRequest {
|
||||||
/// See <https://github.com/microsoft/vscode-go/issues/2714>.
|
/// See <https://github.com/microsoft/vscode-go/issues/2714>.
|
||||||
/// The prepareRename feature is sent before a rename request. If the user
|
/// The prepareRename feature is sent before a rename request. If the user
|
||||||
|
@ -18,54 +17,68 @@ impl PrepareRenameRequest {
|
||||||
/// show the rename pop-up.
|
/// show the rename pop-up.
|
||||||
pub fn request(
|
pub fn request(
|
||||||
self,
|
self,
|
||||||
world: &TypstSystemWorld,
|
ctx: &mut AnalysisContext,
|
||||||
position_encoding: PositionEncoding,
|
position_encoding: PositionEncoding,
|
||||||
) -> Option<PrepareRenameResponse> {
|
) -> Option<PrepareRenameResponse> {
|
||||||
let source = get_suitable_source_in_workspace(world, &self.path).ok()?;
|
let source = ctx.source_by_path(&self.path).ok()?;
|
||||||
let typst_offset = lsp_to_typst::position(self.position, position_encoding, &source)?;
|
|
||||||
|
|
||||||
let ast_node = LinkedNode::new(source.root()).leaf_at(typst_offset + 1)?;
|
let offset = lsp_to_typst::position(self.position, position_encoding, &source)?;
|
||||||
|
let cursor = offset + 1;
|
||||||
|
|
||||||
let t: &dyn World = world;
|
let ast_node = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
||||||
let Definition::Func(func) = find_definition(t.track(), source.id(), ast_node)? else {
|
debug!("ast_node: {ast_node:?}", ast_node = ast_node);
|
||||||
// todo: handle other definitions
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
let deref_target = get_deref_target(ast_node)?;
|
||||||
|
let use_site = deref_target.node().clone();
|
||||||
|
let origin_selection_range =
|
||||||
|
typst_to_lsp::range(use_site.range(), &source, position_encoding);
|
||||||
|
|
||||||
|
let lnk = find_definition(ctx, source.clone(), deref_target)?;
|
||||||
|
validate_renaming_definition(&lnk)?;
|
||||||
|
|
||||||
|
debug!("prepare_rename: {}", lnk.name);
|
||||||
|
Some(PrepareRenameResponse::RangeWithPlaceholder {
|
||||||
|
range: origin_selection_range,
|
||||||
|
placeholder: lnk.name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn validate_renaming_definition(lnk: &DefinitionLink) -> Option<()> {
|
||||||
|
'check_func: {
|
||||||
use typst::foundations::func::Repr;
|
use typst::foundations::func::Repr;
|
||||||
let mut f = func.value.clone();
|
let mut f = match &lnk.value {
|
||||||
|
Some(Value::Func(f)) => f,
|
||||||
|
Some(..) => {
|
||||||
|
log::info!(
|
||||||
|
"prepare_rename: not a function on function definition site: {:?}",
|
||||||
|
lnk.value
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
break 'check_func;
|
||||||
|
}
|
||||||
|
};
|
||||||
loop {
|
loop {
|
||||||
match f.inner() {
|
match f.inner() {
|
||||||
// native functions can't be renamed
|
// native functions can't be renamed
|
||||||
Repr::Native(..) | Repr::Element(..) => return None,
|
Repr::Native(..) | Repr::Element(..) => return None,
|
||||||
// todo: rename with site
|
// todo: rename with site
|
||||||
Repr::With(w) => f = w.0.clone(),
|
Repr::With(w) => f = &w.0,
|
||||||
Repr::Closure(..) => break,
|
Repr::Closure(..) => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: unwrap parentheses
|
|
||||||
let ident = match func.use_site.kind() {
|
|
||||||
SyntaxKind::Ident | SyntaxKind::MathIdent => func.use_site.text(),
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
debug!("prepare_rename: {ident}");
|
|
||||||
|
|
||||||
let id = func.span.id()?;
|
|
||||||
if id.package().is_some() {
|
|
||||||
debug!(
|
|
||||||
"prepare_rename: {ident} is in a package {pkg:?}",
|
|
||||||
pkg = id.package()
|
|
||||||
);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let origin_selection_range =
|
|
||||||
typst_to_lsp::range(func.use_site.range(), &source, position_encoding);
|
|
||||||
|
|
||||||
Some(PrepareRenameResponse::RangeWithPlaceholder {
|
|
||||||
range: origin_selection_range,
|
|
||||||
placeholder: ident.to_string(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if lnk.fid.package().is_some() {
|
||||||
|
debug!(
|
||||||
|
"prepare_rename: {name} is in a package {pkg:?}",
|
||||||
|
name = lnk.name,
|
||||||
|
pkg = lnk.fid.package()
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
use typst_ts_core::vector::ir::DefId;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
analysis::{get_def_use, get_deref_target, DerefTarget, IdentRef},
|
analysis::{get_def_use, get_deref_target, DerefTarget, IdentRef},
|
||||||
|
@ -33,7 +34,7 @@ impl ReferencesRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_references(
|
pub(crate) fn find_references(
|
||||||
ctx: &mut AnalysisContext<'_>,
|
ctx: &mut AnalysisContext<'_>,
|
||||||
def_use: Arc<crate::analysis::DefUseInfo>,
|
def_use: Arc<crate::analysis::DefUseInfo>,
|
||||||
deref_target: DerefTarget<'_>,
|
deref_target: DerefTarget<'_>,
|
||||||
|
@ -76,17 +77,45 @@ fn find_references(
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
range: ident.range(),
|
range: ident.range(),
|
||||||
};
|
};
|
||||||
let def_fid = ident.span().id()?;
|
let cur_fid = ident.span().id()?;
|
||||||
|
|
||||||
|
let def_id = def_use.get_ref(&ident_ref);
|
||||||
|
let def_id = def_id.or_else(|| Some(def_use.get_def(cur_fid, &ident_ref)?.0));
|
||||||
|
let (def_fid, def) = def_id.and_then(|def_id| def_use.get_def_by_id(def_id))?;
|
||||||
|
let def_ident = IdentRef {
|
||||||
|
name: def.name.clone(),
|
||||||
|
range: def.range.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
let (id, _) = def_use.get_def(def_fid, &ident_ref)?;
|
|
||||||
let def_source = ctx.source_by_id(def_fid).ok()?;
|
let def_source = ctx.source_by_id(def_fid).ok()?;
|
||||||
|
let root_def_use = get_def_use(ctx, def_source)?;
|
||||||
|
let root_def_id = root_def_use.get_def(def_fid, &def_ident)?.0;
|
||||||
|
|
||||||
|
find_references_root(
|
||||||
|
ctx,
|
||||||
|
root_def_use,
|
||||||
|
def_fid,
|
||||||
|
root_def_id,
|
||||||
|
def_ident,
|
||||||
|
position_encoding,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn find_references_root(
|
||||||
|
ctx: &mut AnalysisContext<'_>,
|
||||||
|
def_use: Arc<crate::analysis::DefUseInfo>,
|
||||||
|
def_fid: TypstFileId,
|
||||||
|
def_id: DefId,
|
||||||
|
def_ident: IdentRef,
|
||||||
|
position_encoding: PositionEncoding,
|
||||||
|
) -> Option<Vec<LspLocation>> {
|
||||||
|
let def_source = ctx.source_by_id(def_fid).ok()?;
|
||||||
let def_path = ctx.world.path_for_id(def_fid).ok()?;
|
let def_path = ctx.world.path_for_id(def_fid).ok()?;
|
||||||
let uri = Url::from_file_path(def_path).ok()?;
|
let uri = Url::from_file_path(def_path).ok()?;
|
||||||
|
|
||||||
// todo: reuse uri, range to location
|
// todo: reuse uri, range to location
|
||||||
let mut references = def_use
|
let mut references = def_use
|
||||||
.get_refs(id)
|
.get_refs(def_id)
|
||||||
.map(|r| {
|
.map(|r| {
|
||||||
let range = typst_to_lsp::range(r.range.clone(), &def_source, position_encoding);
|
let range = typst_to_lsp::range(r.range.clone(), &def_source, position_encoding);
|
||||||
|
|
||||||
|
@ -97,7 +126,7 @@ fn find_references(
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if def_use.is_exported(id) {
|
if def_use.is_exported(def_id) {
|
||||||
// Find dependents
|
// Find dependents
|
||||||
let mut ctx = ctx.fork_for_search();
|
let mut ctx = ctx.fork_for_search();
|
||||||
ctx.push_dependents(def_fid);
|
ctx.push_dependents(def_fid);
|
||||||
|
@ -105,10 +134,13 @@ fn find_references(
|
||||||
let ref_source = ctx.ctx.source_by_id(ref_fid).ok()?;
|
let ref_source = ctx.ctx.source_by_id(ref_fid).ok()?;
|
||||||
let def_use = get_def_use(ctx.ctx, ref_source.clone())?;
|
let def_use = get_def_use(ctx.ctx, ref_source.clone())?;
|
||||||
|
|
||||||
|
log::info!("def_use for {ref_fid:?} => {:?}", def_use.exports_defs);
|
||||||
|
|
||||||
let uri = ctx.ctx.world.path_for_id(ref_fid).ok()?;
|
let uri = ctx.ctx.world.path_for_id(ref_fid).ok()?;
|
||||||
let uri = Url::from_file_path(uri).ok()?;
|
let uri = Url::from_file_path(uri).ok()?;
|
||||||
|
|
||||||
if let Some((id, _def)) = def_use.get_def(def_fid, &ident_ref) {
|
let mut redefines = vec![];
|
||||||
|
if let Some((id, _def)) = def_use.get_def(def_fid, &def_ident) {
|
||||||
references.extend(def_use.get_refs(id).map(|r| {
|
references.extend(def_use.get_refs(id).map(|r| {
|
||||||
let range =
|
let range =
|
||||||
typst_to_lsp::range(r.range.clone(), &ref_source, position_encoding);
|
typst_to_lsp::range(r.range.clone(), &ref_source, position_encoding);
|
||||||
|
@ -118,25 +150,12 @@ fn find_references(
|
||||||
range,
|
range,
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
redefines.push(id);
|
||||||
|
|
||||||
|
if def_use.is_exported(id) {
|
||||||
|
ctx.push_dependents(ref_fid);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
references.extend(
|
|
||||||
def_use
|
|
||||||
.get_external_refs(def_fid, Some(name.clone()))
|
|
||||||
.map(|r| {
|
|
||||||
let range =
|
|
||||||
typst_to_lsp::range(r.range.clone(), &ref_source, position_encoding);
|
|
||||||
|
|
||||||
LspLocation {
|
|
||||||
uri: uri.clone(),
|
|
||||||
range,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
if def_use.is_exported(id) {
|
|
||||||
ctx.push_dependents(ref_fid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,6 +164,8 @@ fn find_references(
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use typst_ts_core::path::unix_slash;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::tests::*;
|
use crate::tests::*;
|
||||||
|
|
||||||
|
@ -168,7 +189,24 @@ mod tests {
|
||||||
});
|
});
|
||||||
e
|
e
|
||||||
});
|
});
|
||||||
assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
|
|
||||||
|
let result = result.map(|v| {
|
||||||
|
v.into_iter()
|
||||||
|
.map(|l| {
|
||||||
|
let fp = unix_slash(&l.uri.to_file_path().unwrap());
|
||||||
|
let fp = fp.strip_prefix("C:").unwrap_or(&fp);
|
||||||
|
format!(
|
||||||
|
"{fp}@{}:{}:{}:{}",
|
||||||
|
l.range.start.line,
|
||||||
|
l.range.start.character,
|
||||||
|
l.range.end.line,
|
||||||
|
l.range.end.character
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_snapshot!(JsonRepr::new_pure(result));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use std::collections::HashSet;
|
use log::debug;
|
||||||
|
|
||||||
use log::{debug, warn};
|
|
||||||
use lsp_types::TextEdit;
|
use lsp_types::TextEdit;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
analysis::{find_definition, find_imports, find_lexical_references_after, Definition},
|
analysis::{get_def_use, get_deref_target},
|
||||||
|
find_definition, find_references,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
|
validate_renaming_definition,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -18,295 +18,59 @@ pub struct RenameRequest {
|
||||||
impl RenameRequest {
|
impl RenameRequest {
|
||||||
pub fn request(
|
pub fn request(
|
||||||
self,
|
self,
|
||||||
world: &TypstSystemWorld,
|
ctx: &mut AnalysisContext,
|
||||||
position_encoding: PositionEncoding,
|
position_encoding: PositionEncoding,
|
||||||
) -> Option<WorkspaceEdit> {
|
) -> Option<WorkspaceEdit> {
|
||||||
let source = get_suitable_source_in_workspace(world, &self.path).ok()?;
|
let source = ctx.source_by_path(&self.path).ok()?;
|
||||||
let typst_offset = lsp_to_typst::position(self.position, position_encoding, &source)?;
|
|
||||||
|
|
||||||
let ast_node = LinkedNode::new(source.root()).leaf_at(typst_offset + 1)?;
|
let offset = lsp_to_typst::position(self.position, position_encoding, &source)?;
|
||||||
|
let cursor = offset + 1;
|
||||||
|
|
||||||
let t: &dyn World = world;
|
let ast_node = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
||||||
let Definition::Func(func) = find_definition(t.track(), source.id(), ast_node)? else {
|
debug!("ast_node: {ast_node:?}", ast_node = ast_node);
|
||||||
// todo: handle other definitions
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
// todo: unwrap parentheses
|
let deref_target = get_deref_target(ast_node)?;
|
||||||
|
|
||||||
let ident = match func.use_site.kind() {
|
let lnk = find_definition(ctx, source.clone(), deref_target.clone())?;
|
||||||
SyntaxKind::Ident | SyntaxKind::MathIdent => func.use_site.text(),
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
debug!("prepare_rename: {ident}");
|
|
||||||
|
|
||||||
let def_id = func.span.id()?;
|
validate_renaming_definition(&lnk)?;
|
||||||
if def_id.package().is_some() {
|
|
||||||
debug!(
|
let def_use = get_def_use(ctx, source.clone())?;
|
||||||
"prepare_rename: {ident} is in a package {pkg:?}",
|
let references = find_references(ctx, def_use, deref_target, position_encoding)?;
|
||||||
pkg = def_id.package()
|
|
||||||
);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut editions = HashMap::new();
|
let mut editions = HashMap::new();
|
||||||
|
|
||||||
let def_source = world.source(def_id).ok()?;
|
let def_loc = {
|
||||||
let def_id = def_source.id();
|
let def_source = ctx.source_by_id(lnk.fid).ok()?;
|
||||||
let def_path = world.path_for_id(def_id).ok()?;
|
|
||||||
let def_node = def_source.find(func.span)?;
|
let span_path = ctx.world.path_for_id(lnk.fid).ok()?;
|
||||||
let mut def_node = &def_node;
|
let uri = Url::from_file_path(span_path).ok()?;
|
||||||
loop {
|
|
||||||
if def_node.kind() == SyntaxKind::LetBinding {
|
let Some(range) = lnk.name_range else {
|
||||||
break;
|
log::warn!("rename: no name range");
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
LspLocation {
|
||||||
|
uri,
|
||||||
|
range: typst_to_lsp::range(range, &def_source, position_encoding),
|
||||||
}
|
}
|
||||||
def_node = def_node.parent()?;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
debug!(
|
for i in (Some(def_loc).into_iter()).chain(references) {
|
||||||
"rename: def_node found: {def_node:?} in {path}",
|
let uri = i.uri;
|
||||||
path = def_path.display()
|
let range = i.range;
|
||||||
);
|
let edits = editions.entry(uri).or_insert_with(Vec::new);
|
||||||
|
edits.push(TextEdit {
|
||||||
let def_func = def_node.cast::<ast::LetBinding>()?;
|
range,
|
||||||
let def_names = def_func.kind().bindings();
|
new_text: self.new_name.clone(),
|
||||||
if def_names.len() != 1 {
|
});
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let def_name = def_names.first().unwrap();
|
|
||||||
let def_name_node = def_node.find(def_name.span())?;
|
|
||||||
|
|
||||||
// find after function definition
|
|
||||||
let def_root = LinkedNode::new(def_source.root());
|
|
||||||
let parent = def_node.parent().unwrap_or(&def_root).clone();
|
|
||||||
let idents = find_lexical_references_after(parent, def_node.clone(), ident);
|
|
||||||
debug!("rename: in file idents found: {idents:?}");
|
|
||||||
|
|
||||||
let def_uri = Url::from_file_path(def_path).unwrap();
|
|
||||||
for i in (Some(def_name_node).into_iter()).chain(idents) {
|
|
||||||
let range = typst_to_lsp::range(i.range(), &def_source, position_encoding);
|
|
||||||
|
|
||||||
editions.insert(
|
|
||||||
def_uri.clone(),
|
|
||||||
vec![TextEdit {
|
|
||||||
range,
|
|
||||||
new_text: self.new_name.clone(),
|
|
||||||
}],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check whether it is in a sub scope
|
|
||||||
if is_rooted_definition(def_node) {
|
|
||||||
let mut wq = WorkQueue::default();
|
|
||||||
wq.push(def_id);
|
|
||||||
while let Some(id) = wq.pop() {
|
|
||||||
search_in_workspace(
|
|
||||||
world,
|
|
||||||
id,
|
|
||||||
ident,
|
|
||||||
&self.new_name,
|
|
||||||
&mut editions,
|
|
||||||
&mut wq,
|
|
||||||
position_encoding,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: conflict analysis
|
// todo: conflict analysis
|
||||||
|
|
||||||
Some(WorkspaceEdit {
|
Some(WorkspaceEdit {
|
||||||
changes: Some(editions),
|
changes: Some(editions),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
struct WorkQueue {
|
|
||||||
searched: HashSet<TypstFileId>,
|
|
||||||
queue: Vec<TypstFileId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WorkQueue {
|
|
||||||
fn push(&mut self, id: TypstFileId) {
|
|
||||||
if self.searched.contains(&id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.searched.insert(id);
|
|
||||||
self.queue.push(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pop(&mut self) -> Option<TypstFileId> {
|
|
||||||
let id = self.queue.pop()?;
|
|
||||||
Some(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_rooted_definition(node: &LinkedNode) -> bool {
|
|
||||||
// check whether it is in a sub scope
|
|
||||||
let mut parent_has_block = false;
|
|
||||||
let mut parent = node.parent();
|
|
||||||
while let Some(p) = parent {
|
|
||||||
if matches!(p.kind(), SyntaxKind::CodeBlock | SyntaxKind::ContentBlock) {
|
|
||||||
parent_has_block = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
parent = p.parent();
|
|
||||||
}
|
|
||||||
|
|
||||||
!parent_has_block
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_in_workspace(
|
|
||||||
world: &TypstSystemWorld,
|
|
||||||
def_id: TypstFileId,
|
|
||||||
ident: &str,
|
|
||||||
new_name: &str,
|
|
||||||
editions: &mut HashMap<Url, Vec<TextEdit>>,
|
|
||||||
wq: &mut WorkQueue,
|
|
||||||
position_encoding: PositionEncoding,
|
|
||||||
) -> Option<()> {
|
|
||||||
for path in walkdir::WalkDir::new(world.root.clone())
|
|
||||||
.follow_links(false)
|
|
||||||
.into_iter()
|
|
||||||
{
|
|
||||||
let Ok(de) = path else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if !de.file_type().is_file() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if !de
|
|
||||||
.path()
|
|
||||||
.extension()
|
|
||||||
.is_some_and(|e| e == "typ" || e == "typc")
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Ok(source) = get_suitable_source_in_workspace(world, de.path()) else {
|
|
||||||
warn!("rename: failed to get source for {}", de.path().display());
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
let use_id = source.id();
|
|
||||||
// todo: whether we can rename identifiers in packages?
|
|
||||||
if use_id.package().is_some() || wq.searched.contains(&use_id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: find dynamically
|
|
||||||
let mut res = vec![];
|
|
||||||
|
|
||||||
if def_id != use_id {
|
|
||||||
// find import statement
|
|
||||||
let imports = find_imports(&source, Some(def_id));
|
|
||||||
debug!("rename: imports found: {imports:?}");
|
|
||||||
|
|
||||||
// todo: precise import analysis
|
|
||||||
if imports.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let root = LinkedNode::new(source.root());
|
|
||||||
|
|
||||||
for i in imports {
|
|
||||||
let stack_store = i.1.clone();
|
|
||||||
let Some(import_node) = stack_store.cast::<ast::ModuleImport>() else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
// todo: don't ignore import node
|
|
||||||
if import_node.new_name().is_some() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let Some(imports) = import_node.imports() else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut found = false;
|
|
||||||
let mut found_ident = None;
|
|
||||||
match imports {
|
|
||||||
ast::Imports::Wildcard => found = true,
|
|
||||||
ast::Imports::Items(items) => {
|
|
||||||
for handle in items.iter() {
|
|
||||||
match handle {
|
|
||||||
ast::ImportItem::Simple(e) => {
|
|
||||||
if e.get() == ident {
|
|
||||||
found = true;
|
|
||||||
found_ident = Some((e, false));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ast::ImportItem::Renamed(e) => {
|
|
||||||
let o = e.original_name();
|
|
||||||
if o.get() == ident {
|
|
||||||
found = true;
|
|
||||||
found_ident = Some((o, true));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
debug!("rename: import ident found in {:?}", de.path().display());
|
|
||||||
|
|
||||||
let is_renamed = found_ident.as_ref().map(|e| e.1).unwrap_or(false);
|
|
||||||
let found_ident = found_ident.map(|e| e.0);
|
|
||||||
|
|
||||||
if !is_renamed && is_rooted_definition(&i.1) {
|
|
||||||
wq.push(use_id);
|
|
||||||
debug!("rename: push {use_id:?} to work queue");
|
|
||||||
}
|
|
||||||
|
|
||||||
let idents = if !is_renamed {
|
|
||||||
let parent = i.1.parent().unwrap_or(&root).clone();
|
|
||||||
Some(find_lexical_references_after(parent, i.1.clone(), ident))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
debug!("rename: idents found: {idents:?}");
|
|
||||||
|
|
||||||
let found_ident = found_ident.map(|found_ident| {
|
|
||||||
let Some(found_ident) = i.1.find(found_ident.span()) else {
|
|
||||||
warn!(
|
|
||||||
"rename: found_ident not found: {found_ident:?} in {:?} in {}",
|
|
||||||
i.1,
|
|
||||||
de.path().display()
|
|
||||||
);
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(found_ident)
|
|
||||||
});
|
|
||||||
|
|
||||||
// we do early return because there may be some unreliability during
|
|
||||||
// analysis
|
|
||||||
if found_ident.as_ref().is_some_and(Option::is_none) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let found_ident = found_ident.flatten();
|
|
||||||
|
|
||||||
for i in idents.into_iter().flatten().chain(found_ident.into_iter()) {
|
|
||||||
let range = typst_to_lsp::range(i.range(), &source, position_encoding);
|
|
||||||
|
|
||||||
res.push(TextEdit {
|
|
||||||
range,
|
|
||||||
new_text: new_name.to_owned(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !res.is_empty() {
|
|
||||||
let use_path = world.path_for_id(use_id).unwrap();
|
|
||||||
let uri = Url::from_file_path(use_path).unwrap();
|
|
||||||
editions.insert(uri, res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(())
|
|
||||||
}
|
|
||||||
|
|
|
@ -686,15 +686,15 @@ impl CompileActor {
|
||||||
Ok(CompilerQueryResponse::OnSaveExport(()))
|
Ok(CompilerQueryResponse::OnSaveExport(()))
|
||||||
}
|
}
|
||||||
Hover(req) => query_state!(self, Hover, req),
|
Hover(req) => query_state!(self, Hover, req),
|
||||||
GotoDefinition(req) => query_world!(self, GotoDefinition, req),
|
GotoDefinition(req) => query_world2!(self, GotoDefinition, req),
|
||||||
GotoDeclaration(req) => query_world!(self, GotoDeclaration, req),
|
GotoDeclaration(req) => query_world!(self, GotoDeclaration, req),
|
||||||
References(req) => query_world2!(self, References, req),
|
References(req) => query_world2!(self, References, req),
|
||||||
InlayHint(req) => query_world!(self, InlayHint, req),
|
InlayHint(req) => query_world!(self, InlayHint, req),
|
||||||
CodeLens(req) => query_world!(self, CodeLens, req),
|
CodeLens(req) => query_world!(self, CodeLens, req),
|
||||||
Completion(req) => query_state!(self, Completion, req),
|
Completion(req) => query_state!(self, Completion, req),
|
||||||
SignatureHelp(req) => query_world!(self, SignatureHelp, req),
|
SignatureHelp(req) => query_world!(self, SignatureHelp, req),
|
||||||
Rename(req) => query_world!(self, Rename, req),
|
Rename(req) => query_world2!(self, Rename, req),
|
||||||
PrepareRename(req) => query_world!(self, PrepareRename, req),
|
PrepareRename(req) => query_world2!(self, PrepareRename, req),
|
||||||
Symbol(req) => query_world!(self, Symbol, req),
|
Symbol(req) => query_world!(self, Symbol, req),
|
||||||
FoldingRange(..)
|
FoldingRange(..)
|
||||||
| SelectionRange(..)
|
| SelectionRange(..)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue