mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 13:13:43 +00:00
docs: refactor and documenting analyzer code (#44)
* dev: refactor and documenting analyzer code * dev: documenting some lsp api
This commit is contained in:
parent
da7028f59c
commit
2d2857e6f3
24 changed files with 381 additions and 259 deletions
|
@ -1,13 +1,5 @@
|
|||
pub mod def_use;
|
||||
pub use def_use::*;
|
||||
pub mod import;
|
||||
pub use import::*;
|
||||
pub mod lexical_hierarchy;
|
||||
pub(crate) use lexical_hierarchy::*;
|
||||
pub mod matcher;
|
||||
pub use matcher::*;
|
||||
pub mod module;
|
||||
pub use module::*;
|
||||
pub mod track_values;
|
||||
pub use track_values::*;
|
||||
|
||||
|
@ -20,8 +12,8 @@ mod module_tests {
|
|||
use typst_ts_core::path::unix_slash;
|
||||
use typst_ts_core::typst::prelude::EcoVec;
|
||||
|
||||
use crate::analysis::module::*;
|
||||
use crate::prelude::*;
|
||||
use crate::syntax::module::*;
|
||||
use crate::tests::*;
|
||||
|
||||
#[test]
|
||||
|
@ -71,12 +63,11 @@ mod module_tests {
|
|||
|
||||
#[cfg(test)]
|
||||
mod lexical_hierarchy_tests {
|
||||
use def_use::get_def_use;
|
||||
use def_use::DefUseSnapshot;
|
||||
|
||||
use crate::analysis::def_use;
|
||||
use crate::analysis::lexical_hierarchy;
|
||||
use crate::prelude::*;
|
||||
use crate::syntax::lexical_hierarchy;
|
||||
use crate::tests::*;
|
||||
|
||||
#[test]
|
||||
|
@ -96,10 +87,10 @@ mod lexical_hierarchy_tests {
|
|||
#[test]
|
||||
fn test_def_use() {
|
||||
fn def_use(set: &str) {
|
||||
snapshot_testing(set, &|world, path| {
|
||||
let source = get_suitable_source_in_workspace(world, &path).unwrap();
|
||||
snapshot_testing2(set, &|ctx, path| {
|
||||
let source = ctx.source_by_path(&path).unwrap();
|
||||
|
||||
let result = get_def_use(&mut AnalysisContext::new(world), source);
|
||||
let result = ctx.def_use(source);
|
||||
let result = result.as_deref().map(DefUseSnapshot);
|
||||
|
||||
assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use core::fmt;
|
||||
//! Static analysis for def-use relations.
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ops::{Deref, Range},
|
||||
|
@ -10,106 +11,77 @@ use serde::Serialize;
|
|||
use typst::syntax::Source;
|
||||
use typst_ts_core::{path::unix_slash, TypstFileId};
|
||||
|
||||
use crate::{adt::snapshot_map::SnapshotMap, analysis::find_source_by_import_path};
|
||||
|
||||
use super::{
|
||||
get_lexical_hierarchy, AnalysisContext, LexicalHierarchy, LexicalKind, LexicalScopeKind,
|
||||
LexicalVarKind, ModSrc, SearchCtx,
|
||||
use super::SearchCtx;
|
||||
use crate::syntax::{
|
||||
find_source_by_import_path, get_lexical_hierarchy, IdentRef, LexicalHierarchy, LexicalKind,
|
||||
LexicalScopeKind, LexicalVarKind, ModSrc,
|
||||
};
|
||||
use crate::{adt::snapshot_map::SnapshotMap, syntax::LexicalModKind};
|
||||
|
||||
pub use typst_ts_core::vector::ir::DefId;
|
||||
|
||||
/// The type namespace of def-use relations
|
||||
///
|
||||
/// The symbols from different namespaces are not visible to each other.
|
||||
enum Ns {
|
||||
/// Def-use for labels
|
||||
Label,
|
||||
/// Def-use for values
|
||||
Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct IdentRef {
|
||||
pub name: String,
|
||||
pub range: Range<usize>,
|
||||
}
|
||||
|
||||
impl PartialOrd for IdentRef {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for IdentRef {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.name
|
||||
.cmp(&other.name)
|
||||
.then_with(|| self.range.start.cmp(&other.range.start))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IdentRef {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}@{:?}", self.name, self.range)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for IdentRef {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let s = self.to_string();
|
||||
serializer.serialize_str(&s)
|
||||
}
|
||||
}
|
||||
|
||||
/// A flat and transient reference to some symbol in a source file.
|
||||
///
|
||||
/// See [`IdentRef`] for definition of a "transient" reference.
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct IdentDef {
|
||||
/// The name of the symbol.
|
||||
pub name: String,
|
||||
/// The kind of the symbol.
|
||||
pub kind: LexicalKind,
|
||||
/// The byte range of the symbol in the source file.
|
||||
pub range: Range<usize>,
|
||||
}
|
||||
|
||||
type ExternalRefMap = HashMap<(TypstFileId, Option<String>), Vec<(Option<DefId>, IdentRef)>>;
|
||||
|
||||
/// The def-use information of a source file.
|
||||
#[derive(Default)]
|
||||
pub struct DefUseInfo {
|
||||
ident_defs: indexmap::IndexMap<(TypstFileId, IdentRef), IdentDef>,
|
||||
external_refs: ExternalRefMap,
|
||||
ident_refs: HashMap<IdentRef, DefId>,
|
||||
redefine_current: Option<TypstFileId>,
|
||||
ident_redefines: HashMap<IdentRef, DefId>,
|
||||
undefined_refs: Vec<IdentRef>,
|
||||
exports_refs: Vec<DefId>,
|
||||
pub exports_defs: HashMap<String, DefId>,
|
||||
exports_defs: HashMap<String, DefId>,
|
||||
}
|
||||
|
||||
impl DefUseInfo {
|
||||
/// Get the definition id of a symbol by its name reference.
|
||||
pub fn get_ref(&self, ident: &IdentRef) -> Option<DefId> {
|
||||
self.ident_refs.get(ident).copied()
|
||||
}
|
||||
|
||||
/// Get the definition of a symbol by its unique id.
|
||||
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))
|
||||
}
|
||||
|
||||
/// Get the definition of a symbol by its name reference.
|
||||
pub fn get_def(&self, fid: TypstFileId, ident: &IdentRef) -> Option<(DefId, &IdentDef)> {
|
||||
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
|
||||
}
|
||||
})?;
|
||||
let (id, _, def) = self.ident_defs.get_full(&(fid, ident.clone()))?;
|
||||
Some((DefId(id as u64), def))
|
||||
}
|
||||
|
||||
/// Get the references of a symbol by its unique id.
|
||||
pub fn get_refs(&self, id: DefId) -> impl Iterator<Item = &IdentRef> {
|
||||
self.ident_refs
|
||||
.iter()
|
||||
.filter_map(move |(k, v)| if *v == id { Some(k) } else { None })
|
||||
}
|
||||
|
||||
/// Get external references of a symbol by its name reference.
|
||||
pub fn get_external_refs(
|
||||
&self,
|
||||
ext_id: TypstFileId,
|
||||
|
@ -121,16 +93,13 @@ impl DefUseInfo {
|
|||
.flatten()
|
||||
}
|
||||
|
||||
/// Check if a symbol is exported.
|
||||
pub fn is_exported(&self, id: DefId) -> bool {
|
||||
self.exports_refs.contains(&id)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_def_use(ctx: &mut AnalysisContext, source: Source) -> Option<Arc<DefUseInfo>> {
|
||||
get_def_use_inner(&mut ctx.fork_for_search(), source)
|
||||
}
|
||||
|
||||
fn get_def_use_inner(ctx: &mut SearchCtx, source: Source) -> Option<Arc<DefUseInfo>> {
|
||||
pub(super) fn get_def_use_inner(ctx: &mut SearchCtx, source: Source) -> Option<Arc<DefUseInfo>> {
|
||||
let current_id = source.id();
|
||||
ctx.ctx.get_mut(current_id);
|
||||
let c = ctx.ctx.get(current_id).unwrap();
|
||||
|
@ -155,7 +124,6 @@ fn get_def_use_inner(ctx: &mut SearchCtx, source: Source) -> Option<Arc<DefUseIn
|
|||
ext_src: None,
|
||||
};
|
||||
|
||||
collector.info.redefine_current = Some(current_id);
|
||||
collector.scan(&e);
|
||||
collector.calc_exports();
|
||||
let res = Some(Arc::new(collector.info));
|
||||
|
@ -240,27 +208,18 @@ impl<'a, 'b, 'w> DefUseCollector<'a, 'b, 'w> {
|
|||
| LexicalKind::Var(LexicalVarKind::Variable) => {
|
||||
self.insert(Ns::Value, e);
|
||||
}
|
||||
LexicalKind::Mod(super::LexicalModKind::PathVar)
|
||||
| LexicalKind::Mod(super::LexicalModKind::ModuleAlias) => {
|
||||
self.insert_module(Ns::Value, e)
|
||||
}
|
||||
LexicalKind::Mod(super::LexicalModKind::Ident) => {
|
||||
match self.import_name(&e.info.name) {
|
||||
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(LexicalModKind::PathVar)
|
||||
| LexicalKind::Mod(LexicalModKind::ModuleAlias) => self.insert_module(Ns::Value, e),
|
||||
LexicalKind::Mod(LexicalModKind::Ident) => match self.import_name(&e.info.name) {
|
||||
Some(()) => {
|
||||
self.insert_ref(Ns::Value, e);
|
||||
}
|
||||
}
|
||||
LexicalKind::Mod(super::LexicalModKind::Alias { target }) => {
|
||||
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(LexicalModKind::Alias { target }) => {
|
||||
match self.import_name(&target.name) {
|
||||
Some(()) => {
|
||||
self.insert_ident_ref(
|
||||
|
@ -288,7 +247,7 @@ impl<'a, 'b, 'w> DefUseCollector<'a, 'b, 'w> {
|
|||
self.enter(|this| this.scan(e.as_slice()))?;
|
||||
}
|
||||
}
|
||||
LexicalKind::Mod(super::LexicalModKind::Module(p)) => {
|
||||
LexicalKind::Mod(LexicalModKind::Module(p)) => {
|
||||
match p {
|
||||
ModSrc::Expr(_) => {}
|
||||
ModSrc::Path(p) => {
|
||||
|
@ -308,7 +267,7 @@ impl<'a, 'b, 'w> DefUseCollector<'a, 'b, 'w> {
|
|||
|
||||
self.ext_src = None;
|
||||
}
|
||||
LexicalKind::Mod(super::LexicalModKind::Star) => {
|
||||
LexicalKind::Mod(LexicalModKind::Star) => {
|
||||
if let Some(source) = &self.ext_src {
|
||||
info!("diving source for def use: {:?}", source.id());
|
||||
let (_, external_info) =
|
||||
|
@ -399,21 +358,9 @@ impl<'a, 'b, 'w> DefUseCollector<'a, 'b, 'w> {
|
|||
},
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A snapshot of the def-use information for testing.
|
||||
pub struct DefUseSnapshot<'a>(pub &'a DefUseInfo);
|
||||
|
||||
impl<'a> Serialize for DefUseSnapshot<'a> {
|
||||
|
|
|
@ -13,25 +13,36 @@ use typst::{
|
|||
use typst_ts_compiler::{service::WorkspaceProvider, TypstSystemWorld};
|
||||
use typst_ts_core::{cow_mut::CowMut, ImmutPath, TypstFileId};
|
||||
|
||||
use super::{construct_module_dependencies, DefUseInfo, ModuleDependency};
|
||||
use super::{get_def_use_inner, DefUseInfo};
|
||||
use crate::{
|
||||
lsp_to_typst,
|
||||
syntax::{construct_module_dependencies, scan_workspace_files, ModuleDependency},
|
||||
typst_to_lsp, LspPosition, LspRange, PositionEncoding, TypstRange,
|
||||
};
|
||||
|
||||
/// A cache for module-level analysis results of a module.
|
||||
///
|
||||
/// You should not holds across requests, because source code may change.
|
||||
pub struct ModuleAnalysisCache {
|
||||
source: OnceCell<FileResult<Source>>,
|
||||
def_use: OnceCell<Option<Arc<DefUseInfo>>>,
|
||||
}
|
||||
|
||||
impl ModuleAnalysisCache {
|
||||
/// Get the source of a file.
|
||||
pub fn source(&self, ctx: &AnalysisContext, file_id: TypstFileId) -> FileResult<Source> {
|
||||
self.source
|
||||
.get_or_init(|| ctx.world.source(file_id))
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Try to get the def-use information of a file.
|
||||
pub fn def_use(&self) -> Option<Arc<DefUseInfo>> {
|
||||
self.def_use.get().cloned().flatten()
|
||||
}
|
||||
|
||||
pub fn compute_def_use(
|
||||
/// Compute the def-use information of a file.
|
||||
pub(crate) fn compute_def_use(
|
||||
&self,
|
||||
f: impl FnOnce() -> Option<Arc<DefUseInfo>>,
|
||||
) -> Option<Arc<DefUseInfo>> {
|
||||
|
@ -39,28 +50,40 @@ impl ModuleAnalysisCache {
|
|||
}
|
||||
}
|
||||
|
||||
/// The analysis data holds globally.
|
||||
pub struct Analysis {
|
||||
/// The root of the workspace.
|
||||
/// This means that the analysis result won't be valid if the root directory
|
||||
/// changes.
|
||||
pub root: ImmutPath,
|
||||
/// The position encoding for the workspace.
|
||||
position_encoding: PositionEncoding,
|
||||
}
|
||||
|
||||
/// A cache for all level of analysis results of a module.
|
||||
pub struct AnalysisCaches {
|
||||
modules: HashMap<TypstFileId, ModuleAnalysisCache>,
|
||||
root_files: OnceCell<Vec<TypstFileId>>,
|
||||
module_deps: OnceCell<HashMap<TypstFileId, ModuleDependency>>,
|
||||
}
|
||||
|
||||
/// The context for analyzers.
|
||||
pub struct AnalysisContext<'a> {
|
||||
/// The world surface for Typst compiler
|
||||
pub world: &'a TypstSystemWorld,
|
||||
/// The analysis data
|
||||
pub analysis: CowMut<'a, Analysis>,
|
||||
caches: AnalysisCaches,
|
||||
}
|
||||
|
||||
impl<'w> AnalysisContext<'w> {
|
||||
pub fn new(world: &'w TypstSystemWorld) -> Self {
|
||||
/// Create a new analysis context.
|
||||
pub fn new(world: &'w TypstSystemWorld, encoding: PositionEncoding) -> Self {
|
||||
Self {
|
||||
world,
|
||||
analysis: CowMut::Owned(Analysis {
|
||||
root: world.workspace_root(),
|
||||
position_encoding: encoding,
|
||||
}),
|
||||
caches: AnalysisCaches {
|
||||
modules: HashMap::new(),
|
||||
|
@ -75,10 +98,14 @@ impl<'w> AnalysisContext<'w> {
|
|||
self.caches.root_files.get_or_init(f)
|
||||
}
|
||||
|
||||
/// Get all the files in the workspace.
|
||||
pub fn files(&mut self) -> &Vec<TypstFileId> {
|
||||
self.caches.root_files.get_or_init(|| self.search_files())
|
||||
self.caches
|
||||
.root_files
|
||||
.get_or_init(|| scan_workspace_files(&self.analysis.root))
|
||||
}
|
||||
|
||||
/// Get the module dependencies of the workspace.
|
||||
pub fn module_dependencies(&mut self) -> &HashMap<TypstFileId, ModuleDependency> {
|
||||
if self.caches.module_deps.get().is_some() {
|
||||
return self.caches.module_deps.get().unwrap();
|
||||
|
@ -90,31 +117,13 @@ impl<'w> AnalysisContext<'w> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn fork_for_search<'s>(&'s mut self) -> SearchCtx<'s, 'w> {
|
||||
SearchCtx {
|
||||
ctx: self,
|
||||
searched: Default::default(),
|
||||
worklist: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, file_id: TypstFileId) -> &ModuleAnalysisCache {
|
||||
self.caches.modules.entry(file_id).or_insert_with(|| {
|
||||
let source = OnceCell::new();
|
||||
let def_use = OnceCell::new();
|
||||
ModuleAnalysisCache { source, def_use }
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get(&self, file_id: TypstFileId) -> Option<&ModuleAnalysisCache> {
|
||||
self.caches.modules.get(&file_id)
|
||||
}
|
||||
|
||||
/// Get the source of a file by file id.
|
||||
pub fn source_by_id(&mut self, id: TypstFileId) -> FileResult<Source> {
|
||||
self.get_mut(id);
|
||||
self.get(id).unwrap().source(self, id)
|
||||
}
|
||||
|
||||
/// Get the source of a file by file path.
|
||||
pub fn source_by_path(&mut self, p: &Path) -> FileResult<Source> {
|
||||
// todo: source in packages
|
||||
let relative_path = p.strip_prefix(&self.analysis.root).map_err(|_| {
|
||||
|
@ -128,48 +137,63 @@ impl<'w> AnalysisContext<'w> {
|
|||
self.source_by_id(id)
|
||||
}
|
||||
|
||||
fn search_files(&self) -> Vec<TypstFileId> {
|
||||
let root = self.analysis.root.clone();
|
||||
/// Get the module-level analysis cache of a file.
|
||||
pub fn get(&self, file_id: TypstFileId) -> Option<&ModuleAnalysisCache> {
|
||||
self.caches.modules.get(&file_id)
|
||||
}
|
||||
|
||||
let mut res = vec![];
|
||||
for path in walkdir::WalkDir::new(&root).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;
|
||||
}
|
||||
/// Get the module-level analysis cache of a file.
|
||||
pub fn get_mut(&mut self, file_id: TypstFileId) -> &ModuleAnalysisCache {
|
||||
self.caches.modules.entry(file_id).or_insert_with(|| {
|
||||
let source = OnceCell::new();
|
||||
let def_use = OnceCell::new();
|
||||
ModuleAnalysisCache { source, def_use }
|
||||
})
|
||||
}
|
||||
|
||||
let path = de.path();
|
||||
let relative_path = match path.strip_prefix(&root) {
|
||||
Ok(p) => p,
|
||||
Err(err) => {
|
||||
log::warn!("failed to strip prefix, path: {path:?}, root: {root:?}: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
/// Get the def-use information of a source file.
|
||||
pub fn def_use(&mut self, source: Source) -> Option<Arc<DefUseInfo>> {
|
||||
get_def_use_inner(&mut self.fork_for_search(), source)
|
||||
}
|
||||
|
||||
res.push(TypstFileId::new(None, VirtualPath::new(relative_path)));
|
||||
/// Fork a new context for searching in the workspace.
|
||||
pub fn fork_for_search<'s>(&'s mut self) -> SearchCtx<'s, 'w> {
|
||||
SearchCtx {
|
||||
ctx: self,
|
||||
searched: Default::default(),
|
||||
worklist: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
pub fn to_typst_pos(&self, position: LspPosition, src: &Source) -> Option<usize> {
|
||||
lsp_to_typst::position(position, self.analysis.position_encoding, src)
|
||||
}
|
||||
|
||||
pub fn to_typst_range(&self, position: LspRange, src: &Source) -> Option<TypstRange> {
|
||||
lsp_to_typst::range(position, self.analysis.position_encoding, src)
|
||||
}
|
||||
|
||||
pub fn to_lsp_range(&self, position: TypstRange, src: &Source) -> LspRange {
|
||||
typst_to_lsp::range(position, src, self.analysis.position_encoding)
|
||||
}
|
||||
|
||||
pub(crate) fn position_encoding(&self) -> PositionEncoding {
|
||||
self.analysis.position_encoding
|
||||
}
|
||||
}
|
||||
|
||||
/// The context for searching in the workspace.
|
||||
pub struct SearchCtx<'b, 'w> {
|
||||
/// The inner analysis context.
|
||||
pub ctx: &'b mut AnalysisContext<'w>,
|
||||
/// The set of files that have been searched.
|
||||
pub searched: HashSet<TypstFileId>,
|
||||
/// The files that need to be searched.
|
||||
pub worklist: Vec<TypstFileId>,
|
||||
}
|
||||
|
||||
impl SearchCtx<'_, '_> {
|
||||
/// Push a file to the worklist.
|
||||
pub fn push(&mut self, id: TypstFileId) -> bool {
|
||||
if self.searched.insert(id) {
|
||||
self.worklist.push(id);
|
||||
|
@ -179,6 +203,7 @@ impl SearchCtx<'_, '_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Push the dependents of a file to the worklist.
|
||||
pub fn push_dependents(&mut self, id: TypstFileId) {
|
||||
let deps = self.ctx.module_dependencies().get(&id);
|
||||
let dependents = deps.map(|e| e.dependents.clone()).into_iter().flatten();
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! Dynamic analysis of an expression or import statement.
|
||||
|
||||
use comemo::Track;
|
||||
use typst::engine::{Engine, Route};
|
||||
use typst::eval::{Tracer, Vm};
|
||||
|
|
|
@ -2,8 +2,13 @@ use lsp_types::Command;
|
|||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// The [`textDocument/codeLens`] request is sent from the client to the server
|
||||
/// to compute code lenses for a given text document.
|
||||
///
|
||||
/// [`textDocument/codeLens`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_codeLens
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CodeLensRequest {
|
||||
/// The path of the document to request for.
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
/// Stores diagnostics for files.
|
||||
pub type DiagnosticsMap = HashMap<Url, Vec<LspDiagnostic>>;
|
||||
|
||||
/// Converts a list of Typst diagnostics to LSP diagnostics.
|
||||
pub fn convert_diagnostics<'a>(
|
||||
project: &TypstSystemWorld,
|
||||
errors: impl IntoIterator<Item = &'a TypstDiagnostic>,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
analysis::{get_lexical_hierarchy, LexicalHierarchy, LexicalScopeKind},
|
||||
prelude::*,
|
||||
syntax::{get_lexical_hierarchy, LexicalHierarchy, LexicalScopeKind},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
@ -32,10 +32,7 @@ input_file: crates/tinymist-query/src/fixtures/def_use/import_alias_both.typ
|
|||
"kind": {
|
||||
"Mod": {
|
||||
"Alias": {
|
||||
"target": {
|
||||
"name": "x",
|
||||
"range": "54:55"
|
||||
}
|
||||
"target": "x@54..55"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -22,10 +22,7 @@ input_file: crates/tinymist-query/src/fixtures/def_use/import_ident_alias.typ
|
|||
"kind": {
|
||||
"Mod": {
|
||||
"Alias": {
|
||||
"target": {
|
||||
"name": "x",
|
||||
"range": "47:48"
|
||||
}
|
||||
"target": "x@47..48"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
analysis::{get_lexical_hierarchy, LexicalHierarchy, LexicalKind, LexicalScopeKind},
|
||||
prelude::*,
|
||||
syntax::{get_lexical_hierarchy, LexicalHierarchy, LexicalKind, LexicalScopeKind},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
@ -4,8 +4,8 @@ use log::debug;
|
|||
use lsp_types::LocationLink;
|
||||
|
||||
use crate::{
|
||||
analysis::{get_def_use, get_deref_target, DerefTarget},
|
||||
prelude::*,
|
||||
syntax::{get_deref_target, DerefTarget},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -20,7 +20,7 @@ impl GotoDeclarationRequest {
|
|||
world: &TypstSystemWorld,
|
||||
position_encoding: PositionEncoding,
|
||||
) -> Option<GotoDeclarationResponse> {
|
||||
let mut ctx = AnalysisContext::new(world);
|
||||
let mut ctx = AnalysisContext::new(world, position_encoding);
|
||||
let source = get_suitable_source_in_workspace(world, &self.path).ok()?;
|
||||
let offset = lsp_to_typst::position(self.position, position_encoding, &source)?;
|
||||
let cursor = offset + 1;
|
||||
|
@ -34,7 +34,7 @@ impl GotoDeclarationRequest {
|
|||
let origin_selection_range =
|
||||
typst_to_lsp::range(use_site.range(), &source, position_encoding);
|
||||
|
||||
let def_use = get_def_use(&mut ctx, source.clone())?;
|
||||
let def_use = ctx.def_use(source.clone())?;
|
||||
let ref_spans = find_declarations(w, def_use, deref_target)?;
|
||||
|
||||
let mut links = vec![];
|
||||
|
|
|
@ -1,37 +1,47 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use log::debug;
|
||||
use typst::{
|
||||
foundations::Value,
|
||||
syntax::{
|
||||
ast::{self},
|
||||
LinkedNode, Source,
|
||||
},
|
||||
};
|
||||
use typst::foundations::Value;
|
||||
use typst_ts_core::TypstFileId;
|
||||
|
||||
use crate::{
|
||||
analysis::{
|
||||
find_source_by_import, get_def_use, get_deref_target, DerefTarget, IdentRef, LexicalKind,
|
||||
prelude::*,
|
||||
syntax::{
|
||||
find_source_by_import, get_deref_target, DerefTarget, IdentRef, LexicalKind,
|
||||
LexicalModKind, LexicalVarKind,
|
||||
},
|
||||
prelude::*,
|
||||
SyntaxRequest,
|
||||
};
|
||||
|
||||
/// The [`textDocument/definition`] request asks the server for the definition
|
||||
/// location of a symbol at a given text document position.
|
||||
///
|
||||
/// [`textDocument/definition`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_definition
|
||||
///
|
||||
/// # Compatibility
|
||||
///
|
||||
/// The [`GotoDefinitionResponse::Link`](lsp_types::GotoDefinitionResponse::Link) return value
|
||||
/// was introduced in specification version 3.14.0 and requires client-side
|
||||
/// support in order to be used. It can be returned if the client set the
|
||||
/// following field to `true` in the [`initialize`](Self::initialize) method:
|
||||
///
|
||||
/// ```text
|
||||
/// InitializeParams::capabilities::text_document::definition::link_support
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GotoDefinitionRequest {
|
||||
/// The path of the document to request for.
|
||||
pub path: PathBuf,
|
||||
/// The source code position to request for.
|
||||
pub position: LspPosition,
|
||||
}
|
||||
|
||||
impl GotoDefinitionRequest {
|
||||
pub fn request(
|
||||
self,
|
||||
ctx: &mut AnalysisContext,
|
||||
position_encoding: PositionEncoding,
|
||||
) -> Option<GotoDefinitionResponse> {
|
||||
impl SyntaxRequest for GotoDefinitionRequest {
|
||||
type Response = GotoDefinitionResponse;
|
||||
|
||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||
let source = ctx.source_by_path(&self.path).ok()?;
|
||||
let offset = lsp_to_typst::position(self.position, position_encoding, &source)?;
|
||||
let offset = ctx.to_typst_pos(self.position, &source)?;
|
||||
let cursor = offset + 1;
|
||||
|
||||
let ast_node = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
||||
|
@ -39,8 +49,7 @@ impl GotoDefinitionRequest {
|
|||
|
||||
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 origin_selection_range = ctx.to_lsp_range(use_site.range(), &source);
|
||||
|
||||
let def = find_definition(ctx, source.clone(), deref_target)?;
|
||||
|
||||
|
@ -48,7 +57,7 @@ impl GotoDefinitionRequest {
|
|||
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 range = ctx.to_lsp_range(def.def_range, &span_source);
|
||||
|
||||
let res = Some(GotoDefinitionResponse::Link(vec![LocationLink {
|
||||
origin_selection_range: Some(origin_selection_range),
|
||||
|
@ -98,7 +107,7 @@ pub(crate) fn find_definition(
|
|||
};
|
||||
|
||||
// syntatic definition
|
||||
let def_use = get_def_use(ctx, source)?;
|
||||
let def_use = ctx.def_use(source)?;
|
||||
let ident_ref = match use_site.cast::<ast::Expr>()? {
|
||||
ast::Expr::Ident(e) => IdentRef {
|
||||
name: e.get().to_string(),
|
||||
|
@ -220,7 +229,7 @@ mod tests {
|
|||
position: find_test_position(&source),
|
||||
};
|
||||
|
||||
let result = request.request(world, PositionEncoding::Utf16);
|
||||
let result = request.request(world);
|
||||
assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
mod adt;
|
||||
pub mod analysis;
|
||||
pub mod syntax;
|
||||
|
||||
pub(crate) mod diagnostics;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use analysis::AnalysisContext;
|
||||
use typst_ts_core::TypstDocument;
|
||||
|
||||
pub use diagnostics::*;
|
||||
|
@ -54,6 +56,12 @@ pub struct VersionedDocument {
|
|||
pub document: Arc<TypstDocument>,
|
||||
}
|
||||
|
||||
pub trait SyntaxRequest {
|
||||
type Response;
|
||||
|
||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response>;
|
||||
}
|
||||
|
||||
mod polymorphic {
|
||||
use super::prelude::*;
|
||||
use super::*;
|
||||
|
|
|
@ -1,28 +1,38 @@
|
|||
use crate::{analysis::get_deref_target, find_definition, prelude::*, DefinitionLink};
|
||||
use crate::{find_definition, prelude::*, syntax::get_deref_target, DefinitionLink, SyntaxRequest};
|
||||
use log::debug;
|
||||
|
||||
/// The [`textDocument/prepareRename`] request is sent from the client to the
|
||||
/// server to setup and test the validity of a rename operation at a given
|
||||
/// location.
|
||||
///
|
||||
/// [`textDocument/prepareRename`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_prepareRename
|
||||
///
|
||||
/// # Compatibility
|
||||
///
|
||||
/// This request was introduced in specification version 3.12.0.
|
||||
///
|
||||
/// See <https://github.com/microsoft/vscode-go/issues/2714>.
|
||||
/// The prepareRename feature is sent before a rename request. If the user
|
||||
/// is trying to rename a symbol that should not be renamed (inside a
|
||||
/// string or comment, on a builtin identifier, etc.), VSCode won't even
|
||||
/// show the rename pop-up.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PrepareRenameRequest {
|
||||
/// The path of the document to request for.
|
||||
pub path: PathBuf,
|
||||
/// The source code position to request for.
|
||||
pub position: LspPosition,
|
||||
}
|
||||
|
||||
// todo: rename alias
|
||||
// todo: rename import path?
|
||||
impl PrepareRenameRequest {
|
||||
/// See <https://github.com/microsoft/vscode-go/issues/2714>.
|
||||
/// The prepareRename feature is sent before a rename request. If the user
|
||||
/// is trying to rename a symbol that should not be renamed (inside a
|
||||
/// string or comment, on a builtin identifier, etc.), VSCode won't even
|
||||
/// show the rename pop-up.
|
||||
pub fn request(
|
||||
self,
|
||||
ctx: &mut AnalysisContext,
|
||||
position_encoding: PositionEncoding,
|
||||
) -> Option<PrepareRenameResponse> {
|
||||
impl SyntaxRequest for PrepareRenameRequest {
|
||||
type Response = PrepareRenameResponse;
|
||||
|
||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||
let source = ctx.source_by_path(&self.path).ok()?;
|
||||
|
||||
let offset = lsp_to_typst::position(self.position, position_encoding, &source)?;
|
||||
let offset = ctx.to_typst_pos(self.position, &source)?;
|
||||
let cursor = offset + 1;
|
||||
|
||||
let ast_node = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
||||
|
@ -30,8 +40,7 @@ impl PrepareRenameRequest {
|
|||
|
||||
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 origin_selection_range = ctx.to_lsp_range(use_site.range(), &source);
|
||||
|
||||
let lnk = find_definition(ctx, source.clone(), deref_target)?;
|
||||
validate_renaming_definition(&lnk)?;
|
||||
|
|
|
@ -2,32 +2,38 @@ use log::debug;
|
|||
use typst_ts_core::vector::ir::DefId;
|
||||
|
||||
use crate::{
|
||||
analysis::{get_def_use, get_deref_target, DerefTarget, IdentRef},
|
||||
prelude::*,
|
||||
syntax::{get_deref_target, DerefTarget, IdentRef},
|
||||
SyntaxRequest,
|
||||
};
|
||||
|
||||
/// The [`textDocument/references`] request is sent from the client to the
|
||||
/// server to resolve project-wide references for the symbol denoted by the
|
||||
/// given text document position.
|
||||
///
|
||||
/// [`textDocument/references`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_references
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ReferencesRequest {
|
||||
/// The path of the document to request for.
|
||||
pub path: PathBuf,
|
||||
/// The source code position to request for.
|
||||
pub position: LspPosition,
|
||||
}
|
||||
|
||||
impl ReferencesRequest {
|
||||
pub fn request(
|
||||
self,
|
||||
ctx: &mut AnalysisContext,
|
||||
position_encoding: PositionEncoding,
|
||||
) -> Option<Vec<LspLocation>> {
|
||||
impl SyntaxRequest for ReferencesRequest {
|
||||
type Response = Vec<LspLocation>;
|
||||
|
||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||
let source = ctx.source_by_path(&self.path).ok()?;
|
||||
let offset = lsp_to_typst::position(self.position, position_encoding, &source)?;
|
||||
let offset = ctx.to_typst_pos(self.position, &source)?;
|
||||
let cursor = offset + 1;
|
||||
|
||||
let ast_node = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
||||
debug!("ast_node: {ast_node:?}", ast_node = ast_node);
|
||||
let deref_target = get_deref_target(ast_node)?;
|
||||
|
||||
let def_use = get_def_use(ctx, source.clone())?;
|
||||
let locations = find_references(ctx, def_use, deref_target, position_encoding)?;
|
||||
let def_use = ctx.def_use(source.clone())?;
|
||||
let locations = find_references(ctx, def_use, deref_target, ctx.position_encoding())?;
|
||||
|
||||
debug!("references: {locations:?}");
|
||||
Some(locations)
|
||||
|
@ -88,7 +94,7 @@ pub(crate) fn find_references(
|
|||
};
|
||||
|
||||
let def_source = ctx.source_by_id(def_fid).ok()?;
|
||||
let root_def_use = get_def_use(ctx, def_source)?;
|
||||
let root_def_use = ctx.def_use(def_source)?;
|
||||
let root_def_id = root_def_use.get_def(def_fid, &def_ident)?.0;
|
||||
|
||||
find_references_root(
|
||||
|
@ -132,9 +138,7 @@ pub(crate) fn find_references_root(
|
|||
ctx.push_dependents(def_fid);
|
||||
while let Some(ref_fid) = ctx.worklist.pop() {
|
||||
let ref_source = ctx.ctx.source_by_id(ref_fid).ok()?;
|
||||
let def_use = get_def_use(ctx.ctx, ref_source.clone())?;
|
||||
|
||||
log::info!("def_use for {ref_fid:?} => {:?}", def_use.exports_defs);
|
||||
let def_use = ctx.ctx.def_use(ref_source.clone())?;
|
||||
|
||||
let uri = ctx.ctx.world.path_for_id(ref_fid).ok()?;
|
||||
let uri = Url::from_file_path(uri).ok()?;
|
||||
|
@ -180,7 +184,7 @@ mod tests {
|
|||
position: find_test_position(&source),
|
||||
};
|
||||
|
||||
let result = request.request(world, PositionEncoding::Utf16);
|
||||
let result = request.request(world);
|
||||
// sort
|
||||
let result = result.map(|mut e| {
|
||||
e.sort_by(|a, b| match a.range.start.cmp(&b.range.start) {
|
||||
|
|
|
@ -2,28 +2,32 @@ use log::debug;
|
|||
use lsp_types::TextEdit;
|
||||
|
||||
use crate::{
|
||||
analysis::{get_def_use, get_deref_target},
|
||||
find_definition, find_references,
|
||||
prelude::*,
|
||||
validate_renaming_definition,
|
||||
find_definition, find_references, prelude::*, syntax::get_deref_target,
|
||||
validate_renaming_definition, SyntaxRequest,
|
||||
};
|
||||
|
||||
/// The [`textDocument/rename`] request is sent from the client to the server to
|
||||
/// ask the server to compute a workspace change so that the client can perform
|
||||
/// a workspace-wide rename of a symbol.
|
||||
///
|
||||
/// [`textDocument/rename`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_rename
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RenameRequest {
|
||||
/// The path of the document to request for.
|
||||
pub path: PathBuf,
|
||||
/// The source code position to request for.
|
||||
pub position: LspPosition,
|
||||
/// The new name to rename to.
|
||||
pub new_name: String,
|
||||
}
|
||||
|
||||
impl RenameRequest {
|
||||
pub fn request(
|
||||
self,
|
||||
ctx: &mut AnalysisContext,
|
||||
position_encoding: PositionEncoding,
|
||||
) -> Option<WorkspaceEdit> {
|
||||
impl SyntaxRequest for RenameRequest {
|
||||
type Response = WorkspaceEdit;
|
||||
|
||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||
let source = ctx.source_by_path(&self.path).ok()?;
|
||||
|
||||
let offset = lsp_to_typst::position(self.position, position_encoding, &source)?;
|
||||
let offset = ctx.to_typst_pos(self.position, &source)?;
|
||||
let cursor = offset + 1;
|
||||
|
||||
let ast_node = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
||||
|
@ -35,8 +39,8 @@ impl RenameRequest {
|
|||
|
||||
validate_renaming_definition(&lnk)?;
|
||||
|
||||
let def_use = get_def_use(ctx, source.clone())?;
|
||||
let references = find_references(ctx, def_use, deref_target, position_encoding)?;
|
||||
let def_use = ctx.def_use(source.clone())?;
|
||||
let references = find_references(ctx, def_use, deref_target, ctx.position_encoding())?;
|
||||
|
||||
let mut editions = HashMap::new();
|
||||
|
||||
|
@ -53,7 +57,7 @@ impl RenameRequest {
|
|||
|
||||
LspLocation {
|
||||
uri,
|
||||
range: typst_to_lsp::range(range, &def_source, position_encoding),
|
||||
range: ctx.to_lsp_range(range, &def_source),
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use typst_ts_compiler::NotifyApi;
|
||||
|
||||
use crate::{
|
||||
analysis::{get_lexical_hierarchy, LexicalHierarchy, LexicalScopeKind},
|
||||
prelude::*,
|
||||
syntax::{get_lexical_hierarchy, LexicalHierarchy, LexicalScopeKind},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
@ -6,6 +6,7 @@ use typst_ts_core::{typst::prelude::EcoVec, TypstFileId};
|
|||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Find a source instance by its import path.
|
||||
pub fn find_source_by_import_path(
|
||||
world: &dyn World,
|
||||
current: TypstFileId,
|
||||
|
@ -27,6 +28,7 @@ pub fn find_source_by_import_path(
|
|||
world.source(id).ok()
|
||||
}
|
||||
|
||||
/// Find a source instance by its import node.
|
||||
pub fn find_source_by_import(
|
||||
world: &dyn World,
|
||||
current: TypstFileId,
|
||||
|
@ -40,6 +42,7 @@ pub fn find_source_by_import(
|
|||
}
|
||||
}
|
||||
|
||||
/// Find all static imports in a source.
|
||||
#[comemo::memoize]
|
||||
pub fn find_imports(source: &Source) -> EcoVec<TypstFileId> {
|
||||
let root = LinkedNode::new(source.root());
|
|
@ -16,6 +16,8 @@ use typst::{
|
|||
};
|
||||
use typst_ts_core::typst::prelude::{eco_vec, EcoVec};
|
||||
|
||||
use super::IdentRef;
|
||||
|
||||
pub(crate) fn get_lexical_hierarchy(
|
||||
source: Source,
|
||||
g: LexicalScopeKind,
|
||||
|
@ -46,17 +48,11 @@ pub(crate) fn get_lexical_hierarchy(
|
|||
res.map(|_| worker.stack.pop().unwrap().1)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ImportAlias {
|
||||
pub name: String,
|
||||
pub range: Range<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ModSrc {
|
||||
/// `import cetz.draw ...`
|
||||
/// ^^^^^^^^^^^^^^^^^^^^
|
||||
Expr(Box<ImportAlias>),
|
||||
Expr(Box<IdentRef>),
|
||||
/// `import "" ...`
|
||||
/// ^^^^^^^^^^^^^
|
||||
Path(Box<str>),
|
||||
|
@ -77,7 +73,7 @@ pub enum LexicalModKind {
|
|||
Ident,
|
||||
/// `import "foo": bar as baz`
|
||||
/// ^^^^^^^^^^
|
||||
Alias { target: Box<ImportAlias> },
|
||||
Alias { target: Box<IdentRef> },
|
||||
/// `import "foo": *`
|
||||
/// ^
|
||||
Star,
|
||||
|
@ -147,7 +143,7 @@ impl LexicalKind {
|
|||
LexicalKind::Mod(LexicalModKind::Star)
|
||||
}
|
||||
|
||||
fn module_expr(path: Box<ImportAlias>) -> LexicalKind {
|
||||
fn module_expr(path: Box<IdentRef>) -> LexicalKind {
|
||||
LexicalKind::Mod(LexicalModKind::Module(ModSrc::Expr(path)))
|
||||
}
|
||||
|
||||
|
@ -155,7 +151,7 @@ impl LexicalKind {
|
|||
LexicalKind::Mod(LexicalModKind::Module(ModSrc::Path(path)))
|
||||
}
|
||||
|
||||
fn module_import_alias(alias: ImportAlias) -> LexicalKind {
|
||||
fn module_import_alias(alias: IdentRef) -> LexicalKind {
|
||||
LexicalKind::Mod(LexicalModKind::Alias {
|
||||
target: Box::new(alias),
|
||||
})
|
||||
|
@ -455,7 +451,7 @@ impl LexicalHierarchyWorker {
|
|||
|
||||
self.push_leaf(LexicalInfo {
|
||||
name: origin_name.get().to_string(),
|
||||
kind: LexicalKind::module_import_alias(ImportAlias {
|
||||
kind: LexicalKind::module_import_alias(IdentRef {
|
||||
name: target_name.get().to_string(),
|
||||
range: target_name_node.range(),
|
||||
}),
|
||||
|
@ -574,7 +570,7 @@ impl LexicalHierarchyWorker {
|
|||
let e = node
|
||||
.find(src.span())
|
||||
.ok_or_else(|| anyhow!("find expression failed: {:?}", src))?;
|
||||
let e = ImportAlias {
|
||||
let e = IdentRef {
|
||||
name: String::new(),
|
||||
range: e.range(),
|
||||
};
|
87
crates/tinymist-query/src/syntax/mod.rs
Normal file
87
crates/tinymist-query/src/syntax/mod.rs
Normal file
|
@ -0,0 +1,87 @@
|
|||
pub mod import;
|
||||
pub use import::*;
|
||||
pub mod lexical_hierarchy;
|
||||
pub(crate) use lexical_hierarchy::*;
|
||||
pub mod matcher;
|
||||
pub use matcher::*;
|
||||
pub mod module;
|
||||
pub use module::*;
|
||||
|
||||
use core::fmt;
|
||||
use std::ops::Range;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A flat and transient reference to some symbol in a source file.
|
||||
///
|
||||
/// It is transient because it is not guaranteed to be valid after the source
|
||||
/// file is modified.
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct IdentRef {
|
||||
/// The name of the symbol.
|
||||
pub name: String,
|
||||
/// The byte range of the symbol in the source file.
|
||||
pub range: Range<usize>,
|
||||
}
|
||||
|
||||
impl PartialOrd for IdentRef {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for IdentRef {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.name
|
||||
.cmp(&other.name)
|
||||
.then_with(|| self.range.start.cmp(&other.range.start))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IdentRef {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}@{:?}", self.name, self.range)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for IdentRef {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let s = self.to_string();
|
||||
serializer.serialize_str(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for IdentRef {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let s = String::deserialize(deserializer)?;
|
||||
let (name, range) = {
|
||||
let mut parts = s.split('@');
|
||||
let name = parts.next().ok_or_else(|| {
|
||||
serde::de::Error::custom("expected name@range, but found empty string")
|
||||
})?;
|
||||
let range = parts.next().ok_or_else(|| {
|
||||
serde::de::Error::custom("expected name@range, but found no range")
|
||||
})?;
|
||||
// let range = range
|
||||
// .parse()
|
||||
// .map_err(|e| serde::de::Error::custom(format!("failed to parse range:
|
||||
// {}", e)))?;
|
||||
let st_ed = range
|
||||
.split("..")
|
||||
.map(|s| {
|
||||
s.parse().map_err(|e| {
|
||||
serde::de::Error::custom(format!("failed to parse range: {}", e))
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<usize>, _>>()?;
|
||||
if st_ed.len() != 2 {
|
||||
return Err(serde::de::Error::custom("expected range to have 2 parts"));
|
||||
}
|
||||
(name, st_ed[0]..st_ed[1])
|
||||
};
|
||||
Ok(IdentRef {
|
||||
name: name.to_string(),
|
||||
range,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,8 +1,11 @@
|
|||
use std::{collections::HashMap, sync::Once};
|
||||
use std::{collections::HashMap, path::Path, sync::Once};
|
||||
|
||||
use typst::syntax::VirtualPath;
|
||||
use typst_ts_core::{typst::prelude::EcoVec, TypstFileId};
|
||||
|
||||
use super::{find_imports, AnalysisContext};
|
||||
use crate::prelude::AnalysisContext;
|
||||
|
||||
use super::find_imports;
|
||||
|
||||
pub struct ModuleDependency {
|
||||
pub dependencies: EcoVec<TypstFileId>,
|
||||
|
@ -51,3 +54,35 @@ pub fn construct_module_dependencies(
|
|||
|
||||
dependencies
|
||||
}
|
||||
|
||||
pub fn scan_workspace_files(root: &Path) -> Vec<TypstFileId> {
|
||||
let mut res = vec![];
|
||||
for path in walkdir::WalkDir::new(root).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 path = de.path();
|
||||
let relative_path = match path.strip_prefix(root) {
|
||||
Ok(p) => p,
|
||||
Err(err) => {
|
||||
log::warn!("failed to strip prefix, path: {path:?}, root: {root:?}: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
res.push(TypstFileId::new(None, VirtualPath::new(relative_path)));
|
||||
}
|
||||
|
||||
res
|
||||
}
|
|
@ -48,7 +48,7 @@ pub fn snapshot_testing2(name: &str, f: &impl Fn(&mut AnalysisContext, PathBuf))
|
|||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut ctx = AnalysisContext::new(w);
|
||||
let mut ctx = AnalysisContext::new(w, PositionEncoding::Utf16);
|
||||
ctx.test_files(|| paths);
|
||||
f(&mut ctx, p);
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue