mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 13:13:43 +00:00
feat: support syntax analysis on packages (#47)
* feat: support syntax analysis on packages * dev: add more tests
This commit is contained in:
parent
f683426753
commit
d5c7bcdd18
11 changed files with 121 additions and 35 deletions
|
@ -27,6 +27,10 @@ once_cell.workspace = true
|
|||
fxhash.workspace = true
|
||||
walkdir = "2"
|
||||
indexmap = "2.1.0"
|
||||
toml = { version = "0.8", default-features = false, features = [
|
||||
"parse",
|
||||
"display",
|
||||
] }
|
||||
|
||||
typst.workspace = true
|
||||
typst-ide.workspace = true
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
// path: base.typ
|
||||
#import "@preview/example:0.1.0";
|
||||
#(/* ident after */ example.add(1, 1))
|
|
@ -0,0 +1,3 @@
|
|||
// path: base.typ
|
||||
#import "@preview/example:0.1.0";
|
||||
#(/* ident after */ example)
|
|
@ -0,0 +1,12 @@
|
|||
// path: base.typ
|
||||
#let f() = 1;
|
||||
-----
|
||||
.
|
||||
|
||||
#import "/base.typ": *;
|
||||
|
||||
#let conf() = {
|
||||
import "@preview/example:0.1.0";
|
||||
|
||||
set text(size: /* ident after */ f());
|
||||
}
|
|
@ -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/import_package.typ
|
||||
---
|
||||
[
|
||||
{
|
||||
"originSelectionRange": "1:20:1:27",
|
||||
"targetRange": "0:8:0:32",
|
||||
"targetSelectionRange": "0:8:0:32"
|
||||
}
|
||||
]
|
|
@ -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/import_package_self.typ
|
||||
---
|
||||
[
|
||||
{
|
||||
"originSelectionRange": "1:20:1:27",
|
||||
"targetRange": "0:8:0:32",
|
||||
"targetSelectionRange": "0:8:0:32"
|
||||
}
|
||||
]
|
|
@ -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_block.typ
|
||||
---
|
||||
[
|
||||
{
|
||||
"originSelectionRange": "7:35:7:36",
|
||||
"targetRange": "0:5:0:6",
|
||||
"targetSelectionRange": "0:5:0:6"
|
||||
}
|
||||
]
|
|
@ -1,20 +1,26 @@
|
|||
use std::path::Path;
|
||||
|
||||
use log::debug;
|
||||
use typst::syntax::{ast, LinkedNode, Source, SyntaxKind, VirtualPath};
|
||||
use typst_ts_core::{typst::prelude::EcoVec, TypstFileId};
|
||||
use typst::syntax::{ast, package::PackageManifest, LinkedNode, Source, SyntaxKind, VirtualPath};
|
||||
use typst_ts_core::{package::PackageSpec, typst::prelude::EcoVec, TypstFileId};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Find a source instance by its import path.
|
||||
pub fn find_source_by_import_path(
|
||||
fn resolve_id_by_path(
|
||||
world: &dyn World,
|
||||
current: TypstFileId,
|
||||
import_path: &str,
|
||||
) -> Option<Source> {
|
||||
) -> Option<TypstFileId> {
|
||||
if import_path.starts_with('@') {
|
||||
// todo: import from package
|
||||
return None;
|
||||
let spec = import_path.parse::<PackageSpec>().ok()?;
|
||||
// Evaluate the manifest.
|
||||
let manifest_id = FileId::new(Some(spec.clone()), VirtualPath::new("typst.toml"));
|
||||
let bytes = world.file(manifest_id).ok()?;
|
||||
let string = std::str::from_utf8(&bytes).map_err(FileError::from).ok()?;
|
||||
let manifest: PackageManifest = toml::from_str(string).ok()?;
|
||||
manifest.validate(&spec).ok()?;
|
||||
|
||||
// Evaluate the entry point.
|
||||
return Some(manifest_id.join(&manifest.package.entrypoint));
|
||||
}
|
||||
|
||||
let path = Path::new(import_path);
|
||||
|
@ -24,8 +30,18 @@ pub fn find_source_by_import_path(
|
|||
VirtualPath::new(path)
|
||||
};
|
||||
|
||||
let id = TypstFileId::new(current.package().cloned(), vpath);
|
||||
world.source(id).ok()
|
||||
Some(TypstFileId::new(current.package().cloned(), vpath))
|
||||
}
|
||||
|
||||
/// Find a source instance by its import path.
|
||||
pub fn find_source_by_import_path(
|
||||
world: &dyn World,
|
||||
current: TypstFileId,
|
||||
import_path: &str,
|
||||
) -> Option<Source> {
|
||||
world
|
||||
.source(resolve_id_by_path(world, current, import_path)?)
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Find a source instance by its import node.
|
||||
|
@ -43,11 +59,11 @@ pub fn find_source_by_import(
|
|||
}
|
||||
|
||||
/// Find all static imports in a source.
|
||||
#[comemo::memoize]
|
||||
pub fn find_imports(source: &Source) -> EcoVec<TypstFileId> {
|
||||
pub fn find_imports(world: &dyn World, source: &Source) -> EcoVec<TypstFileId> {
|
||||
let root = LinkedNode::new(source.root());
|
||||
|
||||
let mut worker = ImportWorker {
|
||||
world,
|
||||
current: source.id(),
|
||||
imports: EcoVec::new(),
|
||||
};
|
||||
|
@ -55,18 +71,16 @@ pub fn find_imports(source: &Source) -> EcoVec<TypstFileId> {
|
|||
worker.analyze(root);
|
||||
let res = worker.imports;
|
||||
|
||||
let mut res: Vec<TypstFileId> = res
|
||||
.into_iter()
|
||||
.map(|(vpath, _)| TypstFileId::new(None, vpath))
|
||||
.collect();
|
||||
let mut res: Vec<TypstFileId> = res.into_iter().map(|(id, _)| id).collect();
|
||||
res.sort();
|
||||
res.dedup();
|
||||
res.into_iter().collect()
|
||||
}
|
||||
|
||||
struct ImportWorker<'a> {
|
||||
world: &'a dyn World,
|
||||
current: TypstFileId,
|
||||
imports: EcoVec<(VirtualPath, LinkedNode<'a>)>,
|
||||
imports: EcoVec<(FileId, LinkedNode<'a>)>,
|
||||
}
|
||||
|
||||
impl<'a> ImportWorker<'a> {
|
||||
|
@ -79,15 +93,9 @@ impl<'a> ImportWorker<'a> {
|
|||
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:?}");
|
||||
let id = resolve_id_by_path(self.world, self.current, s.as_str())?;
|
||||
|
||||
self.imports.push((vpath, node));
|
||||
self.imports.push((id, node));
|
||||
}
|
||||
// todo: handle dynamic import
|
||||
ast::Expr::FieldAccess(..) | ast::Expr::Ident(..) => {}
|
||||
|
|
|
@ -14,7 +14,11 @@ use typst::{
|
|||
},
|
||||
util::LazyHash,
|
||||
};
|
||||
use typst_ts_core::typst::prelude::{eco_vec, EcoVec};
|
||||
use typst_ts_core::{
|
||||
error::prelude::WithContext,
|
||||
package::PackageSpec,
|
||||
typst::prelude::{eco_vec, EcoVec},
|
||||
};
|
||||
|
||||
use super::IdentRef;
|
||||
|
||||
|
@ -37,7 +41,13 @@ pub(crate) fn get_lexical_hierarchy(
|
|||
},
|
||||
eco_vec![],
|
||||
));
|
||||
let res = worker.get_symbols(root).ok();
|
||||
let res = match worker.get_symbols(root) {
|
||||
Ok(()) => Some(()),
|
||||
Err(e) => {
|
||||
log::error!("lexical hierarchy analysis failed: {:?}", e);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
while worker.stack.len() > 1 {
|
||||
worker.symbreak();
|
||||
|
@ -641,15 +651,24 @@ impl LexicalHierarchyWorker {
|
|||
match v {
|
||||
ast::Expr::Str(e) => {
|
||||
let e = e.get();
|
||||
let e = Path::new(e.as_ref())
|
||||
.file_name()
|
||||
.context("no file name")?
|
||||
.to_string_lossy();
|
||||
let e = e.as_ref();
|
||||
let e = e.strip_suffix(".typ").context("no suffix")?;
|
||||
|
||||
let name = if e.starts_with('@') {
|
||||
let spec = e
|
||||
.parse::<PackageSpec>()
|
||||
.context("parse package spec failed for name")?;
|
||||
spec.name.to_string()
|
||||
} else {
|
||||
let e = Path::new(e.as_ref())
|
||||
.file_name()
|
||||
.context("no file name")?
|
||||
.to_string_lossy();
|
||||
let e = e.as_ref();
|
||||
e.strip_suffix(".typ").context("no suffix")?.to_owned()
|
||||
};
|
||||
|
||||
// return (e == name).then_some(ImportRef::Path(v));
|
||||
self.push_leaf(LexicalInfo {
|
||||
name: e.to_string(),
|
||||
name,
|
||||
kind: LexicalKind::module_path(),
|
||||
range: v_linked.range(),
|
||||
});
|
||||
|
|
|
@ -31,7 +31,7 @@ pub fn construct_module_dependencies(
|
|||
};
|
||||
|
||||
let file_id = source.id();
|
||||
let deps = find_imports(&source);
|
||||
let deps = find_imports(ctx.world, &source);
|
||||
dependencies
|
||||
.entry(file_id)
|
||||
.or_insert_with(|| ModuleDependency {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue