mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 05:05:00 +00:00
feat: go to definition of module members (#644)
* feat: go to definition of module members * dev: rename a bit * ovo
This commit is contained in:
parent
3aa8f1233d
commit
df5dafbf75
8 changed files with 139 additions and 37 deletions
|
@ -1,14 +1,13 @@
|
|||
//! Import analysis
|
||||
|
||||
use ecow::EcoVec;
|
||||
use typst::{
|
||||
foundations::Value,
|
||||
syntax::{LinkedNode, SyntaxKind},
|
||||
World,
|
||||
};
|
||||
use typst::{foundations::Value, syntax::LinkedNode, World};
|
||||
use typst_shim::syntax::LinkedNodeExt;
|
||||
|
||||
use crate::{analysis::analyze_import_, syntax::resolve_id_by_path};
|
||||
use crate::{
|
||||
analysis::analyze_import_,
|
||||
syntax::{find_expr_in_import, resolve_id_by_path},
|
||||
};
|
||||
|
||||
pub use super::prelude::*;
|
||||
|
||||
|
@ -92,7 +91,10 @@ impl<'a, 'w> ImportCollector<'a, 'w> {
|
|||
LexicalKind::Mod(LexicalModKind::Module(p)) => {
|
||||
let id = match p {
|
||||
ModSrc::Expr(exp) => {
|
||||
let exp = find_import_expr(self.root.leaf_at_compat(exp.range.end));
|
||||
let exp = self
|
||||
.root
|
||||
.leaf_at_compat(exp.range.end)
|
||||
.and_then(find_expr_in_import);
|
||||
let val = exp
|
||||
.as_ref()
|
||||
.and_then(|exp| analyze_import_(self.ctx.deref(), exp));
|
||||
|
@ -100,9 +102,8 @@ impl<'a, 'w> ImportCollector<'a, 'w> {
|
|||
match val {
|
||||
Some(Value::Module(m)) => {
|
||||
log::debug!(
|
||||
"current id {:?} exp {:?} => id: {:?}",
|
||||
"current id {:?} exp {exp:?} => id: {:?}",
|
||||
self.current_id,
|
||||
exp,
|
||||
m.file_id()
|
||||
);
|
||||
m.file_id()
|
||||
|
@ -132,17 +133,3 @@ impl<'a, 'w> ImportCollector<'a, 'w> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_import_expr(end: Option<LinkedNode>) -> Option<LinkedNode> {
|
||||
let mut node = end?;
|
||||
while let Some(parent) = node.parent() {
|
||||
if matches!(
|
||||
parent.kind(),
|
||||
SyntaxKind::ModuleImport | SyntaxKind::ModuleInclude
|
||||
) {
|
||||
return Some(node);
|
||||
}
|
||||
node = parent.clone();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ use typst::{foundations::Value, syntax::Span};
|
|||
use typst_shim::syntax::LinkedNodeExt;
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::syntax::find_expr_in_import;
|
||||
use crate::{
|
||||
prelude::*,
|
||||
syntax::{
|
||||
|
@ -93,10 +94,12 @@ pub fn find_definition(
|
|||
fn find_ident_definition(
|
||||
ctx: &mut AnalysisContext<'_>,
|
||||
source: Source,
|
||||
use_site: LinkedNode,
|
||||
mut use_site: LinkedNode,
|
||||
) -> Option<DefinitionLink> {
|
||||
let mut proj = vec![];
|
||||
// Lexical reference
|
||||
let ident_ref = match use_site.cast::<ast::Expr>()? {
|
||||
let ident_store = use_site.clone();
|
||||
let ident_ref = match ident_store.cast::<ast::Expr>()? {
|
||||
ast::Expr::Ident(e) => Some(IdentRef {
|
||||
name: e.get().clone(),
|
||||
range: use_site.range(),
|
||||
|
@ -105,10 +108,32 @@ fn find_ident_definition(
|
|||
name: e.get().clone(),
|
||||
range: use_site.range(),
|
||||
}),
|
||||
ast::Expr::FieldAccess(..) => {
|
||||
debug!("find field access");
|
||||
ast::Expr::FieldAccess(s) => {
|
||||
proj.push(s.field());
|
||||
|
||||
None
|
||||
let mut i = s.target();
|
||||
while let ast::Expr::FieldAccess(f) = i {
|
||||
proj.push(f.field());
|
||||
i = f.target();
|
||||
}
|
||||
|
||||
match i {
|
||||
ast::Expr::Ident(e) => {
|
||||
use_site = use_site.find(e.span())?;
|
||||
Some(IdentRef {
|
||||
name: e.get().clone(),
|
||||
range: use_site.range(),
|
||||
})
|
||||
}
|
||||
ast::Expr::MathIdent(e) => {
|
||||
use_site = use_site.find(e.span())?;
|
||||
Some(IdentRef {
|
||||
name: e.get().clone(),
|
||||
range: use_site.range(),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
debug!("unsupported kind {kind:?}", kind = use_site.kind());
|
||||
|
@ -136,10 +161,39 @@ fn find_ident_definition(
|
|||
});
|
||||
};
|
||||
|
||||
match def.kind {
|
||||
match &def.kind {
|
||||
LexicalKind::Var(LexicalVarKind::BibKey)
|
||||
| LexicalKind::Heading(..)
|
||||
| LexicalKind::Block => unreachable!(),
|
||||
LexicalKind::Mod(
|
||||
LexicalModKind::Module(..) | LexicalModKind::PathVar | LexicalModKind::ModuleAlias,
|
||||
) => {
|
||||
if !proj.is_empty() {
|
||||
proj.reverse();
|
||||
let def_src = ctx.source_by_id(def_fid).ok()?;
|
||||
let def_root = LinkedNode::new(def_src.root());
|
||||
let cursor = def.range.start + 1;
|
||||
let mod_exp = find_expr_in_import(def_root.leaf_at_compat(cursor)?)?;
|
||||
let mod_import = mod_exp.parent()?.clone();
|
||||
let mod_import_node = mod_import.cast::<ast::ModuleImport>()?;
|
||||
let import_path = mod_import.find(mod_import_node.source().span())?;
|
||||
|
||||
let m = ctx.analyze_import(&import_path)?;
|
||||
let val = project_value(&m, proj.as_slice())?;
|
||||
|
||||
// todo: name range
|
||||
let name = proj.last().map(|e| e.get().clone());
|
||||
return value_to_def(ctx, val.clone(), || name, None);
|
||||
}
|
||||
|
||||
Some(DefinitionLink {
|
||||
kind: def.kind.clone(),
|
||||
name: def.name.clone(),
|
||||
value: None,
|
||||
def_at: Some((def_fid, def.range.clone())),
|
||||
name_range: Some(def.range.clone()),
|
||||
})
|
||||
}
|
||||
LexicalKind::Var(
|
||||
LexicalVarKind::Variable
|
||||
| LexicalVarKind::ValRef
|
||||
|
@ -147,12 +201,7 @@ fn find_ident_definition(
|
|||
| LexicalVarKind::LabelRef,
|
||||
)
|
||||
| LexicalKind::Mod(
|
||||
LexicalModKind::Module(..)
|
||||
| LexicalModKind::PathVar
|
||||
| LexicalModKind::PathInclude
|
||||
| LexicalModKind::ModuleAlias
|
||||
| LexicalModKind::Alias { .. }
|
||||
| LexicalModKind::Ident,
|
||||
LexicalModKind::PathInclude | LexicalModKind::Alias { .. } | LexicalModKind::Ident,
|
||||
) => Some(DefinitionLink {
|
||||
kind: def.kind.clone(),
|
||||
name: def.name.clone(),
|
||||
|
@ -179,12 +228,22 @@ fn find_ident_definition(
|
|||
})
|
||||
}
|
||||
LexicalKind::Mod(LexicalModKind::Star) => {
|
||||
log::info!("unimplemented star import {:?}", ident_ref);
|
||||
log::info!("unimplemented star import {ident_ref:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn project_value<'a>(m: &'a Value, proj: &[ast::Ident<'_>]) -> Option<&'a Value> {
|
||||
if proj.is_empty() {
|
||||
return Some(m);
|
||||
}
|
||||
let scope = m.scope()?;
|
||||
let (ident, proj) = proj.split_first()?;
|
||||
let v = scope.get(ident.as_str())?;
|
||||
project_value(v, proj)
|
||||
}
|
||||
|
||||
fn find_bib_definition(
|
||||
ctx: &mut AnalysisContext,
|
||||
introspector: &Introspector,
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
// path: variable.typ
|
||||
#let f(x) = 2;
|
||||
-----
|
||||
|
||||
#import "variable.typ"
|
||||
#(variable.f /* position after */ );
|
|
@ -0,0 +1,6 @@
|
|||
// path: variable.typ
|
||||
#let f(x) = 2;
|
||||
-----
|
||||
|
||||
#import "variable.typ" as this-module
|
||||
#(this-module.f /* position after */ );
|
|
@ -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/module_select.typ
|
||||
---
|
||||
[
|
||||
{
|
||||
"originSelectionRange": "1:2:1:12",
|
||||
"targetRange": "0:6:0:9",
|
||||
"targetSelectionRange": "0:6:0:9"
|
||||
}
|
||||
]
|
|
@ -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/module_select_alias.typ
|
||||
---
|
||||
[
|
||||
{
|
||||
"originSelectionRange": "1:2:1:15",
|
||||
"targetRange": "0:6:0:9",
|
||||
"targetSelectionRange": "0:6:0:9"
|
||||
}
|
||||
]
|
|
@ -14,9 +14,29 @@ pub fn deref_lvalue(mut node: LinkedNode) -> Option<LinkedNode> {
|
|||
while let Some(e) = node.cast::<ast::Parenthesized>() {
|
||||
node = node.find(e.expr().span())?;
|
||||
}
|
||||
if let Some(e) = node.parent() {
|
||||
if let Some(f) = e.cast::<ast::FieldAccess>() {
|
||||
if node.span() == f.field().span() {
|
||||
return Some(e.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(node)
|
||||
}
|
||||
|
||||
pub(crate) fn find_expr_in_import(mut node: LinkedNode) -> Option<LinkedNode> {
|
||||
while let Some(parent) = node.parent() {
|
||||
if matches!(
|
||||
parent.kind(),
|
||||
SyntaxKind::ModuleImport | SyntaxKind::ModuleInclude
|
||||
) {
|
||||
return Some(node);
|
||||
}
|
||||
node = parent.clone();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn is_mark(sk: SyntaxKind) -> bool {
|
||||
use SyntaxKind::*;
|
||||
matches!(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue