From cc1f418423453dff68c7013957c3740a45b6ae4b Mon Sep 17 00:00:00 2001
From: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com>
Date: Mon, 18 Mar 2024 23:00:27 +0800
Subject: [PATCH] dev: reduce a bundle of ts usage in query crate (#72)

---
 Cargo.lock                                    |  5 ++
 Cargo.toml                                    | 14 ++++++
 crates/tinymist-query/Cargo.toml              |  6 ++-
 crates/tinymist-query/src/analysis.rs         | 14 +++---
 crates/tinymist-query/src/analysis/def_use.rs |  6 +--
 crates/tinymist-query/src/analysis/global.rs  | 49 ++++++++++++------
 .../src/analysis/track_values.rs              |  2 +-
 crates/tinymist-query/src/completion.rs       | 23 +++++----
 crates/tinymist-query/src/diagnostics.rs      |  2 +-
 crates/tinymist-query/src/document_symbol.rs  |  4 +-
 crates/tinymist-query/src/folding_range.rs    |  2 +-
 crates/tinymist-query/src/goto_declaration.rs |  2 +-
 crates/tinymist-query/src/goto_definition.rs  |  6 +--
 crates/tinymist-query/src/hover.rs            | 21 ++++----
 crates/tinymist-query/src/inlay_hint.rs       |  8 +--
 crates/tinymist-query/src/lib.rs              | 12 ++++-
 crates/tinymist-query/src/prelude.rs          | 15 ++----
 crates/tinymist-query/src/references.rs       |  7 ++-
 crates/tinymist-query/src/rename.rs           |  2 +-
 crates/tinymist-query/src/syntax/import.rs    | 12 +++--
 .../src/syntax/lexical_hierarchy.rs           |  8 ++-
 crates/tinymist-query/src/syntax/module.rs    |  4 +-
 crates/tinymist-query/src/tests.rs            | 46 +++++++++--------
 crates/tinymist/Cargo.toml                    |  1 +
 crates/tinymist/src/actor/typst.rs            | 50 ++++++++++++++++---
 25 files changed, 204 insertions(+), 117 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index cfe749738..e59fbf14d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -414,8 +414,10 @@ checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a"
 dependencies = [
  "android-tzdata",
  "iana-time-zone",
+ "js-sys",
  "num-traits",
  "serde",
+ "wasm-bindgen",
  "windows-targets 0.52.4",
 ]
 
@@ -3616,6 +3618,7 @@ dependencies = [
  "anyhow",
  "async-trait",
  "cargo_metadata",
+ "chrono",
  "clap",
  "clap_builder",
  "clap_complete",
@@ -3656,6 +3659,7 @@ version = "0.11.0"
 dependencies = [
  "anyhow",
  "comemo",
+ "ecow",
  "ena",
  "fxhash",
  "indexmap 2.2.5",
@@ -3666,6 +3670,7 @@ dependencies = [
  "lsp-types",
  "once_cell",
  "parking_lot",
+ "reflexo",
  "regex",
  "serde",
  "serde_json",
diff --git a/Cargo.toml b/Cargo.toml
index 87a17e68c..9b6959cde 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -44,10 +44,23 @@ typst = "0.11.0"
 typst-ide = "0.11.0"
 typst-pdf = "0.11.0"
 typst-assets = { git = "https://github.com/typst/typst-assets", rev = "4d1211a" }
+reflexo = { version = "0.4.2-rc8", default-features = false, features = [
+    "flat-vector",
+] }
 typst-ts-core = { version = "0.4.2-rc8" }
 typst-ts-compiler = { version = "0.4.2-rc8" }
 typst-preview = { git = "https://github.com/Enter-tainer/typst-preview", rev = "18630ebda22339109ef675a885787f4fc8731ba8" }
 
+# [features]
+# rkyv = ["dep:rkyv", "rkyv/alloc", "rkyv/archive_le"]
+# rkyv-validation = ["dep:rkyv", "rkyv/validation"]
+# flat-vector = ["rkyv", "rkyv-validation"]
+
+# __web = ["dep:wasm-bindgen", "dep:js-sys"]
+# web = ["__web"]
+# full = ["web", "flat-vector"]
+# default = ["full"]
+
 lsp-server = "0.7.6"
 lsp-types = { version = "=0.95.0", features = ["proposed"] }
 crossbeam-channel = "0.5.12"
@@ -113,6 +126,7 @@ typst-syntax = { git = "https://github.com/Myriad-Dreamin/typst.git", branch = "
 # typst-syntax = { path = "../typst/crates/typst-syntax" }
 
 typst-ts-svg-exporter = { git = "https://github.com/Myriad-Dreamin/typst.ts", rev = "2fc877de0a4bcd7a8f057933546a97348e9621c7", package = "typst-ts-svg-exporter" }
+reflexo = { git = "https://github.com/Myriad-Dreamin/typst.ts", rev = "2fc877de0a4bcd7a8f057933546a97348e9621c7", package = "reflexo" }
 typst-ts-core = { git = "https://github.com/Myriad-Dreamin/typst.ts", rev = "2fc877de0a4bcd7a8f057933546a97348e9621c7", package = "typst-ts-core" }
 typst-ts-compiler = { git = "https://github.com/Myriad-Dreamin/typst.ts", rev = "2fc877de0a4bcd7a8f057933546a97348e9621c7", package = "typst-ts-compiler" }
 
diff --git a/crates/tinymist-query/Cargo.toml b/crates/tinymist-query/Cargo.toml
index 39017f8ef..84fba87b9 100644
--- a/crates/tinymist-query/Cargo.toml
+++ b/crates/tinymist-query/Cargo.toml
@@ -28,16 +28,18 @@ fxhash.workspace = true
 toml.workspace = true
 walkdir.workspace = true
 indexmap.workspace = true
+ecow.workspace = true
 
 typst.workspace = true
 typst-ide.workspace = true
 
-typst-ts-core = { version = "0.4.2-rc6", default-features = false, features = [
+reflexo.workspace = true
+typst-ts-compiler.workspace = true
+typst-ts-core = { version = "0.4.2-rc8", default-features = false, features = [
     "flat-vector",
     "vector-bbox",
     "no-content-hint",
 ] }
-typst-ts-compiler.workspace = true
 
 lsp-types.workspace = true
 
diff --git a/crates/tinymist-query/src/analysis.rs b/crates/tinymist-query/src/analysis.rs
index 71f16290e..60884f7b4 100644
--- a/crates/tinymist-query/src/analysis.rs
+++ b/crates/tinymist-query/src/analysis.rs
@@ -8,9 +8,9 @@ pub use global::*;
 
 #[cfg(test)]
 mod module_tests {
+    use ecow::EcoVec;
+    use reflexo::path::unix_slash;
     use serde_json::json;
-    use typst_ts_core::path::unix_slash;
-    use typst_ts_core::typst::prelude::EcoVec;
 
     use crate::prelude::*;
     use crate::syntax::module::*;
@@ -18,7 +18,7 @@ mod module_tests {
 
     #[test]
     fn test() {
-        snapshot_testing2("modules", &|ctx, _| {
+        snapshot_testing("modules", &|ctx, _| {
             fn ids(ids: EcoVec<TypstFileId>) -> Vec<String> {
                 let mut ids: Vec<String> = ids
                     .into_iter()
@@ -66,14 +66,14 @@ mod lexical_hierarchy_tests {
     use def_use::DefUseSnapshot;
 
     use crate::analysis::def_use;
-    use crate::prelude::*;
+    // use crate::prelude::*;
     use crate::syntax::lexical_hierarchy;
     use crate::tests::*;
 
     #[test]
     fn scope() {
-        snapshot_testing("lexical_hierarchy", &|world, path| {
-            let source = get_suitable_source_in_workspace(world, &path).unwrap();
+        snapshot_testing("lexical_hierarchy", &|ctx, path| {
+            let source = ctx.source_by_path(&path).unwrap();
 
             let result = lexical_hierarchy::get_lexical_hierarchy(
                 source,
@@ -87,7 +87,7 @@ mod lexical_hierarchy_tests {
     #[test]
     fn test_def_use() {
         fn def_use(set: &str) {
-            snapshot_testing2(set, &|ctx, path| {
+            snapshot_testing(set, &|ctx, path| {
                 let source = ctx.source_by_path(&path).unwrap();
 
                 let result = ctx.def_use(source);
diff --git a/crates/tinymist-query/src/analysis/def_use.rs b/crates/tinymist-query/src/analysis/def_use.rs
index 0af602cff..2a5e52891 100644
--- a/crates/tinymist-query/src/analysis/def_use.rs
+++ b/crates/tinymist-query/src/analysis/def_use.rs
@@ -7,9 +7,11 @@ use std::{
 };
 
 use log::info;
+use reflexo::path::unix_slash;
+pub use reflexo::vector::ir::DefId;
 use serde::Serialize;
+use typst::syntax::FileId as TypstFileId;
 use typst::syntax::Source;
-use typst_ts_core::{path::unix_slash, TypstFileId};
 
 use super::SearchCtx;
 use crate::syntax::{
@@ -18,8 +20,6 @@ use crate::syntax::{
 };
 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.
diff --git a/crates/tinymist-query/src/analysis/global.rs b/crates/tinymist-query/src/analysis/global.rs
index 435893eb1..67949e413 100644
--- a/crates/tinymist-query/src/analysis/global.rs
+++ b/crates/tinymist-query/src/analysis/global.rs
@@ -1,17 +1,22 @@
 use std::{
     collections::{HashMap, HashSet},
-    path::Path,
+    path::{Path, PathBuf},
     sync::Arc,
 };
 
 use once_cell::sync::OnceCell;
+use reflexo::{cow_mut::CowMut, ImmutPath};
+use typst::syntax::FileId as TypstFileId;
 use typst::{
     diag::{eco_format, FileError, FileResult},
     syntax::{Source, VirtualPath},
     World,
 };
-use typst_ts_compiler::{service::WorkspaceProvider, TypstSystemWorld};
-use typst_ts_core::{cow_mut::CowMut, ImmutPath, TypstFileId};
+use typst_ts_compiler::package::Registry;
+use typst_ts_compiler::TypstSystemWorld;
+// use typst_ts_compiler::TypstSystemWorld;
+// use typst_ts_compiler::{service::WorkspaceProvider, TypstSystemWorld};
+// use typst_ts_core::{cow_mut::CowMut, ImmutPath};
 
 use super::{get_def_use_inner, DefUseInfo};
 use crate::{
@@ -57,10 +62,11 @@ pub struct Analysis {
     /// changes.
     pub root: ImmutPath,
     /// The position encoding for the workspace.
-    position_encoding: PositionEncoding,
+    pub position_encoding: PositionEncoding,
 }
 
 /// A cache for all level of analysis results of a module.
+#[derive(Default)]
 pub struct AnalysisCaches {
     modules: HashMap<TypstFileId, ModuleAnalysisCache>,
     root_files: OnceCell<Vec<TypstFileId>>,
@@ -78,18 +84,11 @@ pub struct AnalysisContext<'a> {
 
 impl<'w> AnalysisContext<'w> {
     /// Create a new analysis context.
-    pub fn new(world: &'w TypstSystemWorld, encoding: PositionEncoding) -> Self {
+    pub fn new(world: &'w TypstSystemWorld, a: Analysis) -> Self {
         Self {
             world,
-            analysis: CowMut::Owned(Analysis {
-                root: world.workspace_root(),
-                position_encoding: encoding,
-            }),
-            caches: AnalysisCaches {
-                modules: HashMap::new(),
-                root_files: OnceCell::new(),
-                module_deps: OnceCell::new(),
-            },
+            analysis: CowMut::Owned(a),
+            caches: AnalysisCaches::default(),
         }
     }
 
@@ -117,6 +116,24 @@ impl<'w> AnalysisContext<'w> {
         }
     }
 
+    /// Resolve the real path for a file id.
+    pub fn path_for_id(&self, id: TypstFileId) -> Result<PathBuf, FileError> {
+        if id.vpath().as_rootless_path() == Path::new("-") {
+            return Ok(PathBuf::from("-"));
+        }
+
+        // Determine the root path relative to which the file path
+        // will be resolved.
+        let root = match id.package() {
+            Some(spec) => self.world.registry.resolve(spec)?,
+            None => self.analysis.root.clone(),
+        };
+
+        // Join the path to the root. If it tries to escape, deny
+        // access. Note: It can still escape via symlinks.
+        id.vpath().resolve(&root).ok_or(FileError::AccessDenied)
+    }
+
     /// Get the source of a file by file id.
     pub fn source_by_id(&mut self, id: TypstFileId) -> FileResult<Source> {
         self.get_mut(id);
@@ -173,6 +190,10 @@ impl<'w> AnalysisContext<'w> {
         lsp_to_typst::range(position, self.analysis.position_encoding, src)
     }
 
+    pub fn to_lsp_pos(&self, typst_offset: usize, src: &Source) -> LspPosition {
+        typst_to_lsp::offset_to_position(typst_offset, 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)
     }
diff --git a/crates/tinymist-query/src/analysis/track_values.rs b/crates/tinymist-query/src/analysis/track_values.rs
index 80342edb7..3612b5d29 100644
--- a/crates/tinymist-query/src/analysis/track_values.rs
+++ b/crates/tinymist-query/src/analysis/track_values.rs
@@ -1,6 +1,7 @@
 //! Dynamic analysis of an expression or import statement.
 
 use comemo::Track;
+use ecow::*;
 use typst::engine::{Engine, Route};
 use typst::eval::{Tracer, Vm};
 use typst::foundations::{Context, Label, Scopes, Styles, Value};
@@ -8,7 +9,6 @@ use typst::introspection::{Introspector, Locator};
 use typst::model::{BibliographyElem, Document};
 use typst::syntax::{ast, LinkedNode, Span, SyntaxKind};
 use typst::World;
-use typst_ts_core::typst::prelude::*;
 
 /// Try to determine a set of possible values for an expression.
 pub fn analyze_expr(world: &dyn World, node: &LinkedNode) -> EcoVec<(Value, Option<Styles>)> {
diff --git a/crates/tinymist-query/src/completion.rs b/crates/tinymist-query/src/completion.rs
index 378373948..367081c5c 100644
--- a/crates/tinymist-query/src/completion.rs
+++ b/crates/tinymist-query/src/completion.rs
@@ -1,4 +1,4 @@
-use crate::prelude::*;
+use crate::{prelude::*, StatefulRequest};
 
 #[derive(Debug, Clone)]
 pub struct CompletionRequest {
@@ -7,16 +7,17 @@ pub struct CompletionRequest {
     pub explicit: bool,
 }
 
-impl CompletionRequest {
-    pub fn request(
+impl StatefulRequest for CompletionRequest {
+    type Response = CompletionResponse;
+
+    fn request(
         self,
-        world: &TypstSystemWorld,
+        ctx: &mut AnalysisContext,
         doc: Option<VersionedDocument>,
-        position_encoding: PositionEncoding,
-    ) -> Option<CompletionResponse> {
+    ) -> Option<Self::Response> {
         let doc = doc.as_ref().map(|doc| doc.document.as_ref());
-        let source = get_suitable_source_in_workspace(world, &self.path).ok()?;
-        let cursor = lsp_to_typst::position(self.position, position_encoding, &source)?;
+        let source = ctx.source_by_path(&self.path).ok()?;
+        let cursor = ctx.to_typst_pos(self.position, &source)?;
 
         // Please see <https://github.com/nvarner/typst-lsp/commit/2d66f26fb96ceb8e485f492e5b81e9db25c3e8ec>
         //
@@ -33,10 +34,10 @@ impl CompletionRequest {
         // assume that the completion is not explicit.
         let explicit = false;
 
-        let (offset, completions) = typst_ide::autocomplete(world, doc, &source, cursor, explicit)?;
+        let (offset, completions) =
+            typst_ide::autocomplete(ctx.world, doc, &source, cursor, explicit)?;
 
-        let lsp_start_position =
-            typst_to_lsp::offset_to_position(offset, position_encoding, &source);
+        let lsp_start_position = ctx.to_lsp_pos(offset, &source);
         let replace_range = LspRange::new(lsp_start_position, self.position);
         Some(typst_to_lsp::completions(&completions, replace_range).into())
     }
diff --git a/crates/tinymist-query/src/diagnostics.rs b/crates/tinymist-query/src/diagnostics.rs
index 588c9b185..c5e685e52 100644
--- a/crates/tinymist-query/src/diagnostics.rs
+++ b/crates/tinymist-query/src/diagnostics.rs
@@ -101,7 +101,7 @@ fn diagnostic_related_information(
     Ok(tracepoints)
 }
 
-fn diagnostic_span_id(typst_diagnostic: &TypstDiagnostic) -> Option<(FileId, TypstSpan)> {
+fn diagnostic_span_id(typst_diagnostic: &TypstDiagnostic) -> Option<(TypstFileId, TypstSpan)> {
     iter::once(typst_diagnostic.span)
         .chain(typst_diagnostic.trace.iter().map(|trace| trace.span))
         .find_map(|span| Some((span.id()?, span)))
diff --git a/crates/tinymist-query/src/document_symbol.rs b/crates/tinymist-query/src/document_symbol.rs
index badc70e72..200369132 100644
--- a/crates/tinymist-query/src/document_symbol.rs
+++ b/crates/tinymist-query/src/document_symbol.rs
@@ -57,10 +57,10 @@ mod tests {
 
     #[test]
     fn test() {
-        snapshot_testing("document_symbols", &|world, path| {
+        snapshot_testing("document_symbols", &|ctx, path| {
             let request = DocumentSymbolRequest { path: path.clone() };
 
-            let source = get_suitable_source_in_workspace(world, &path).unwrap();
+            let source = ctx.source_by_path(&path).unwrap();
 
             let result = request.request(source, PositionEncoding::Utf16);
             assert_snapshot!(JsonRepr::new_redacted(result.unwrap(), &REDACT_LOC));
diff --git a/crates/tinymist-query/src/folding_range.rs b/crates/tinymist-query/src/folding_range.rs
index 50ceb0168..0d2a8e571 100644
--- a/crates/tinymist-query/src/folding_range.rs
+++ b/crates/tinymist-query/src/folding_range.rs
@@ -124,7 +124,7 @@ mod tests {
                 line_folding_only: true,
             };
 
-            let source = get_suitable_source_in_workspace(world, &path).unwrap();
+            let source = world.source_by_path(&path).unwrap();
 
             let result = request.request(source, PositionEncoding::Utf16);
             assert_snapshot!(JsonRepr::new_pure(result.unwrap()));
diff --git a/crates/tinymist-query/src/goto_declaration.rs b/crates/tinymist-query/src/goto_declaration.rs
index 8be4ad7f9..957bf290f 100644
--- a/crates/tinymist-query/src/goto_declaration.rs
+++ b/crates/tinymist-query/src/goto_declaration.rs
@@ -38,7 +38,7 @@ impl SyntaxRequest for GotoDeclarationRequest {
             let ref_id = source.id();
             let ref_source = &source;
 
-            let span_path = ctx.world.path_for_id(ref_id).ok()?;
+            let span_path = ctx.path_for_id(ref_id).ok()?;
             let range = ctx.to_lsp_range(ref_range, ref_source);
 
             let uri = Url::from_file_path(span_path).ok()?;
diff --git a/crates/tinymist-query/src/goto_definition.rs b/crates/tinymist-query/src/goto_definition.rs
index 026d4af79..ee0f61474 100644
--- a/crates/tinymist-query/src/goto_definition.rs
+++ b/crates/tinymist-query/src/goto_definition.rs
@@ -2,7 +2,7 @@ use std::ops::Range;
 
 use log::debug;
 use typst::foundations::Value;
-use typst_ts_core::TypstFileId;
+use typst::syntax::FileId as TypstFileId;
 
 use crate::{
     prelude::*,
@@ -53,7 +53,7 @@ impl SyntaxRequest for GotoDefinitionRequest {
 
         let def = find_definition(ctx, source.clone(), deref_target)?;
 
-        let span_path = ctx.world.path_for_id(def.fid).ok()?;
+        let span_path = ctx.path_for_id(def.fid).ok()?;
         let uri = Url::from_file_path(span_path).ok()?;
 
         let span_source = ctx.source_by_id(def.fid).ok()?;
@@ -221,7 +221,7 @@ mod tests {
 
     #[test]
     fn test() {
-        snapshot_testing2("goto_definition", &|world, path| {
+        snapshot_testing("goto_definition", &|world, path| {
             let source = world.source_by_path(&path).unwrap();
 
             let request = GotoDefinitionRequest {
diff --git a/crates/tinymist-query/src/hover.rs b/crates/tinymist-query/src/hover.rs
index 96b86617b..cac8b76fb 100644
--- a/crates/tinymist-query/src/hover.rs
+++ b/crates/tinymist-query/src/hover.rs
@@ -1,4 +1,4 @@
-use crate::prelude::*;
+use crate::{prelude::*, StatefulRequest};
 
 #[derive(Debug, Clone)]
 pub struct HoverRequest {
@@ -6,24 +6,25 @@ pub struct HoverRequest {
     pub position: LspPosition,
 }
 
-impl HoverRequest {
-    pub fn request(
+impl StatefulRequest for HoverRequest {
+    type Response = Hover;
+
+    fn request(
         self,
-        world: &TypstSystemWorld,
+        ctx: &mut AnalysisContext,
         doc: Option<VersionedDocument>,
-        position_encoding: PositionEncoding,
-    ) -> Option<Hover> {
+    ) -> Option<Self::Response> {
         let doc = doc.as_ref().map(|doc| doc.document.as_ref());
 
-        let source = get_suitable_source_in_workspace(world, &self.path).ok()?;
-        let offset = lsp_to_typst::position(self.position, position_encoding, &source)?;
+        let source = ctx.source_by_path(&self.path).ok()?;
+        let offset = ctx.to_typst_pos(self.position, &source)?;
         // the typst's cursor is 1-based, so we need to add 1 to the offset
         let cursor = offset + 1;
 
-        let typst_tooltip = typst_ide::tooltip(world, doc, &source, cursor)?;
+        let typst_tooltip = typst_ide::tooltip(ctx.world, doc, &source, cursor)?;
 
         let ast_node = LinkedNode::new(source.root()).leaf_at(cursor)?;
-        let range = typst_to_lsp::range(ast_node.range(), &source, position_encoding);
+        let range = ctx.to_lsp_range(ast_node.range(), &source);
 
         Some(Hover {
             contents: typst_to_lsp::tooltip(&typst_tooltip),
diff --git a/crates/tinymist-query/src/inlay_hint.rs b/crates/tinymist-query/src/inlay_hint.rs
index 9892c1611..936ab132d 100644
--- a/crates/tinymist-query/src/inlay_hint.rs
+++ b/crates/tinymist-query/src/inlay_hint.rs
@@ -1,5 +1,6 @@
 use std::{borrow::Cow, ops::Range};
 
+use ecow::eco_vec;
 use log::debug;
 use lsp_types::{InlayHintKind, InlayHintLabel};
 use typst::{
@@ -7,7 +8,6 @@ use typst::{
     syntax::SyntaxNode,
     util::LazyHash,
 };
-use typst_ts_core::typst::prelude::eco_vec;
 
 use crate::{prelude::*, SyntaxRequest};
 
@@ -68,7 +68,7 @@ impl SyntaxRequest for InlayHintRequest {
 }
 
 fn inlay_hint(
-    world: &TypstSystemWorld,
+    world: &dyn World,
     source: &Source,
     range: Range<usize>,
     encoding: PositionEncoding,
@@ -76,7 +76,7 @@ fn inlay_hint(
     const SMART: InlayHintConfig = InlayHintConfig::smart();
 
     struct InlayHintWorker<'a> {
-        world: &'a TypstSystemWorld,
+        world: &'a dyn World,
         source: &'a Source,
         range: Range<usize>,
         encoding: PositionEncoding,
@@ -661,7 +661,7 @@ mod tests {
 
     #[test]
     fn smart() {
-        snapshot_testing2("inlay_hints", &|ctx, path| {
+        snapshot_testing("inlay_hints", &|ctx, path| {
             let source = ctx.source_by_path(&path).unwrap();
 
             let request = InlayHintRequest {
diff --git a/crates/tinymist-query/src/lib.rs b/crates/tinymist-query/src/lib.rs
index 5182b5965..059c00a0d 100644
--- a/crates/tinymist-query/src/lib.rs
+++ b/crates/tinymist-query/src/lib.rs
@@ -7,7 +7,7 @@ pub(crate) mod diagnostics;
 use std::sync::Arc;
 
 pub use analysis::AnalysisContext;
-use typst_ts_core::TypstDocument;
+use typst::model::Document as TypstDocument;
 
 pub use diagnostics::*;
 pub(crate) mod code_lens;
@@ -62,6 +62,16 @@ pub trait SyntaxRequest {
     fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response>;
 }
 
+pub trait StatefulRequest {
+    type Response;
+
+    fn request(
+        self,
+        ctx: &mut AnalysisContext,
+        v: Option<VersionedDocument>,
+    ) -> Option<Self::Response>;
+}
+
 mod polymorphic {
     use super::prelude::*;
     use super::*;
diff --git a/crates/tinymist-query/src/prelude.rs b/crates/tinymist-query/src/prelude.rs
index 73f189d31..525f7002b 100644
--- a/crates/tinymist-query/src/prelude.rs
+++ b/crates/tinymist-query/src/prelude.rs
@@ -15,17 +15,18 @@ pub use lsp_types::{
     SemanticTokensDelta, SemanticTokensFullDeltaResult, SemanticTokensResult, SignatureHelp,
     SignatureInformation, SymbolInformation, Url, WorkspaceEdit,
 };
+pub use reflexo::vector::ir::DefId;
 pub use serde_json::Value as JsonValue;
 pub use typst::diag::{EcoString, FileError, FileResult, Tracepoint};
 pub use typst::foundations::{Func, ParamInfo, Value};
+pub use typst::syntax::FileId as TypstFileId;
 pub use typst::syntax::{
     ast::{self, AstNode},
-    FileId, LinkedNode, Source, Spanned, SyntaxKind, VirtualPath,
+    LinkedNode, Source, Spanned, SyntaxKind,
 };
 pub use typst::World;
-use typst_ts_compiler::service::WorkspaceProvider;
+// use typst_ts_compiler::service::WorkspaceProvider;
 pub use typst_ts_compiler::TypstSystemWorld;
-pub use typst_ts_core::TypstFileId;
 
 pub use crate::analysis::{analyze_expr, AnalysisContext};
 pub use crate::lsp_typst_boundary::{
@@ -33,11 +34,3 @@ pub use crate::lsp_typst_boundary::{
     TypstDiagnostic, TypstSeverity, TypstSpan,
 };
 pub use crate::VersionedDocument;
-
-pub fn get_suitable_source_in_workspace(w: &TypstSystemWorld, p: &Path) -> FileResult<Source> {
-    // todo: source in packages
-    let relative_path = p
-        .strip_prefix(&w.workspace_root())
-        .map_err(|_| FileError::NotFound(p.to_owned()))?;
-    w.source(TypstFileId::new(None, VirtualPath::new(relative_path)))
-}
diff --git a/crates/tinymist-query/src/references.rs b/crates/tinymist-query/src/references.rs
index 8c34ee62b..ca5f1620a 100644
--- a/crates/tinymist-query/src/references.rs
+++ b/crates/tinymist-query/src/references.rs
@@ -1,5 +1,4 @@
 use log::debug;
-use typst_ts_core::vector::ir::DefId;
 
 use crate::{
     prelude::*,
@@ -116,7 +115,7 @@ pub(crate) fn find_references_root(
     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.path_for_id(def_fid).ok()?;
     let uri = Url::from_file_path(def_path).ok()?;
 
     // todo: reuse uri, range to location
@@ -140,7 +139,7 @@ pub(crate) fn find_references_root(
             let ref_source = ctx.ctx.source_by_id(ref_fid).ok()?;
             let def_use = ctx.ctx.def_use(ref_source.clone())?;
 
-            let uri = ctx.ctx.world.path_for_id(ref_fid).ok()?;
+            let uri = ctx.ctx.path_for_id(ref_fid).ok()?;
             let uri = Url::from_file_path(uri).ok()?;
 
             let mut redefines = vec![];
@@ -176,7 +175,7 @@ mod tests {
     #[test]
     fn test() {
         // goto_definition
-        snapshot_testing2("references", &|world, path| {
+        snapshot_testing("references", &|world, path| {
             let source = world.source_by_path(&path).unwrap();
 
             let request = ReferencesRequest {
diff --git a/crates/tinymist-query/src/rename.rs b/crates/tinymist-query/src/rename.rs
index 79e5256cf..075925836 100644
--- a/crates/tinymist-query/src/rename.rs
+++ b/crates/tinymist-query/src/rename.rs
@@ -47,7 +47,7 @@ impl SyntaxRequest for RenameRequest {
         let def_loc = {
             let def_source = ctx.source_by_id(lnk.fid).ok()?;
 
-            let span_path = ctx.world.path_for_id(lnk.fid).ok()?;
+            let span_path = ctx.path_for_id(lnk.fid).ok()?;
             let uri = Url::from_file_path(span_path).ok()?;
 
             let Some(range) = lnk.name_range else {
diff --git a/crates/tinymist-query/src/syntax/import.rs b/crates/tinymist-query/src/syntax/import.rs
index 052659403..a9f8e1114 100644
--- a/crates/tinymist-query/src/syntax/import.rs
+++ b/crates/tinymist-query/src/syntax/import.rs
@@ -1,7 +1,11 @@
 use std::path::Path;
 
-use typst::syntax::{ast, package::PackageManifest, LinkedNode, Source, SyntaxKind, VirtualPath};
-use typst_ts_core::{package::PackageSpec, typst::prelude::EcoVec, TypstFileId};
+use ecow::EcoVec;
+use typst::syntax::{
+    ast,
+    package::{PackageManifest, PackageSpec},
+    FileId as TypstFileId, LinkedNode, Source, SyntaxKind, VirtualPath,
+};
 
 use crate::prelude::*;
 
@@ -13,7 +17,7 @@ fn resolve_id_by_path(
     if import_path.starts_with('@') {
         let spec = import_path.parse::<PackageSpec>().ok()?;
         // Evaluate the manifest.
-        let manifest_id = FileId::new(Some(spec.clone()), VirtualPath::new("typst.toml"));
+        let manifest_id = TypstFileId::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()?;
@@ -80,7 +84,7 @@ pub fn find_imports(world: &dyn World, source: &Source) -> EcoVec<TypstFileId> {
 struct ImportWorker<'a> {
     world: &'a dyn World,
     current: TypstFileId,
-    imports: EcoVec<(FileId, LinkedNode<'a>)>,
+    imports: EcoVec<(TypstFileId, LinkedNode<'a>)>,
 }
 
 impl<'a> ImportWorker<'a> {
diff --git a/crates/tinymist-query/src/syntax/lexical_hierarchy.rs b/crates/tinymist-query/src/syntax/lexical_hierarchy.rs
index a1a9076b8..23986ef26 100644
--- a/crates/tinymist-query/src/syntax/lexical_hierarchy.rs
+++ b/crates/tinymist-query/src/syntax/lexical_hierarchy.rs
@@ -4,21 +4,19 @@ use std::{
 };
 
 use anyhow::{anyhow, Context};
+use ecow::{eco_vec, EcoVec};
 use log::info;
 use lsp_types::SymbolKind;
+use reflexo::error::prelude::*;
 use serde::{Deserialize, Serialize};
 use typst::{
     syntax::{
         ast::{self, AstNode},
+        package::PackageSpec,
         LinkedNode, Source, SyntaxKind,
     },
     util::LazyHash,
 };
-use typst_ts_core::{
-    error::prelude::WithContext,
-    package::PackageSpec,
-    typst::prelude::{eco_vec, EcoVec},
-};
 
 use super::IdentRef;
 
diff --git a/crates/tinymist-query/src/syntax/module.rs b/crates/tinymist-query/src/syntax/module.rs
index b24db6557..3b6af4f10 100644
--- a/crates/tinymist-query/src/syntax/module.rs
+++ b/crates/tinymist-query/src/syntax/module.rs
@@ -1,7 +1,7 @@
 use std::{collections::HashMap, path::Path, sync::Once};
 
-use typst::syntax::VirtualPath;
-use typst_ts_core::{typst::prelude::EcoVec, TypstFileId};
+use ecow::EcoVec;
+use typst::syntax::{FileId as TypstFileId, VirtualPath};
 
 use crate::prelude::AnalysisContext;
 
diff --git a/crates/tinymist-query/src/tests.rs b/crates/tinymist-query/src/tests.rs
index bb732e94a..92ae2911c 100644
--- a/crates/tinymist-query/src/tests.rs
+++ b/crates/tinymist-query/src/tests.rs
@@ -20,9 +20,11 @@ use typst_ts_core::{config::CompileOpts, Bytes, TypstFileId};
 pub use insta::assert_snapshot;
 pub use typst_ts_compiler::TypstSystemWorld;
 
-use crate::{prelude::AnalysisContext, typst_to_lsp, LspPosition, PositionEncoding};
+use crate::{
+    analysis::Analysis, prelude::AnalysisContext, typst_to_lsp, LspPosition, PositionEncoding,
+};
 
-pub fn snapshot_testing(name: &str, f: &impl Fn(&mut TypstSystemWorld, PathBuf)) {
+pub fn snapshot_testing(name: &str, f: &impl Fn(&mut AnalysisContext, PathBuf)) {
     let mut settings = insta::Settings::new();
     settings.set_prepend_module_to_snapshot(false);
     settings.set_snapshot_path(format!("fixtures/{name}/snaps"));
@@ -31,29 +33,31 @@ pub fn snapshot_testing(name: &str, f: &impl Fn(&mut TypstSystemWorld, PathBuf))
         insta::glob!(&glob_path, |path| {
             let contents = std::fs::read_to_string(path).unwrap();
 
-            run_with_sources(&contents, f);
+            run_with_sources(&contents, |w: &mut TypstSystemWorld, p| {
+                let paths = w
+                    .shadow_paths()
+                    .into_iter()
+                    .map(|p| {
+                        TypstFileId::new(
+                            None,
+                            VirtualPath::new(p.strip_prefix(w.workspace_root()).unwrap()),
+                        )
+                    })
+                    .collect::<Vec<_>>();
+                let mut ctx = AnalysisContext::new(
+                    w,
+                    Analysis {
+                        root: w.workspace_root(),
+                        position_encoding: PositionEncoding::Utf16,
+                    },
+                );
+                ctx.test_files(|| paths);
+                f(&mut ctx, p);
+            });
         });
     });
 }
 
-pub fn snapshot_testing2(name: &str, f: &impl Fn(&mut AnalysisContext, PathBuf)) {
-    snapshot_testing(name, &|w, p| {
-        let paths = w
-            .shadow_paths()
-            .into_iter()
-            .map(|p| {
-                TypstFileId::new(
-                    None,
-                    VirtualPath::new(p.strip_prefix(w.workspace_root()).unwrap()),
-                )
-            })
-            .collect::<Vec<_>>();
-        let mut ctx = AnalysisContext::new(w, PositionEncoding::Utf16);
-        ctx.test_files(|| paths);
-        f(&mut ctx, p);
-    });
-}
-
 pub fn run_with_sources<T>(source: &str, f: impl FnOnce(&mut TypstSystemWorld, PathBuf) -> T) -> T {
     let root = if cfg!(windows) {
         PathBuf::from("C:\\")
diff --git a/crates/tinymist/Cargo.toml b/crates/tinymist/Cargo.toml
index 51bcd2469..52a0eff3d 100644
--- a/crates/tinymist/Cargo.toml
+++ b/crates/tinymist/Cargo.toml
@@ -53,6 +53,7 @@ lsp-server.workspace = true
 crossbeam-channel.workspace = true
 lsp-types.workspace = true
 dhat = { version = "0.3.3", optional = true }
+chrono = { version = "0.4" }
 
 [features]
 default = ["cli", "preview"]
diff --git a/crates/tinymist/src/actor/typst.rs b/crates/tinymist/src/actor/typst.rs
index 2d09c6eba..962a80d1f 100644
--- a/crates/tinymist/src/actor/typst.rs
+++ b/crates/tinymist/src/actor/typst.rs
@@ -11,8 +11,9 @@ use log::{debug, error, info, trace, warn};
 use once_cell::sync::OnceCell;
 use parking_lot::Mutex;
 use tinymist_query::{
-    analysis::AnalysisContext, CompilerQueryRequest, CompilerQueryResponse, DiagnosticsMap,
-    FoldRequestFeature, OnExportRequest, OnSaveExportRequest, PositionEncoding, SyntaxRequest,
+    analysis::{Analysis, AnalysisContext},
+    CompilerQueryRequest, CompilerQueryResponse, DiagnosticsMap, FoldRequestFeature,
+    OnExportRequest, OnSaveExportRequest, PositionEncoding, StatefulRequest, SyntaxRequest,
     VersionedDocument,
 };
 use tokio::sync::{broadcast, mpsc, oneshot, watch};
@@ -204,8 +205,7 @@ impl<'a> fmt::Debug for ShowOpts<'a> {
 
 macro_rules! query_state {
     ($self:ident, $method:ident, $req:expr) => {{
-        let enc = $self.position_encoding;
-        let res = $self.steal_state(move |w, doc| $req.request(w, doc, enc));
+        let res = $self.steal_state(move |w, doc| $req.request(w, doc));
         res.map(CompilerQueryResponse::$method)
     }};
 }
@@ -664,11 +664,39 @@ impl CompileActor {
 
     fn steal_state<T: Send + Sync + 'static>(
         &self,
-        f: impl FnOnce(&TypstSystemWorld, Option<VersionedDocument>) -> T + Send + Sync + 'static,
+        f: impl FnOnce(&mut AnalysisContext, Option<VersionedDocument>) -> T + Send + Sync + 'static,
     ) -> anyhow::Result<T> {
-        let fut = self.steal(move |compiler| f(compiler.compiler.world(), compiler.success_doc()));
+        let enc = self.position_encoding;
+
+        self.steal(move |compiler| {
+            // todo: record analysis
+            let doc = compiler.success_doc();
+            let w = compiler.compiler.world_mut();
+
+            let Some(main) = w.main else {
+                log::error!("TypstActor: main file is not set");
+                return Err(anyhow!("main file is not set"));
+            };
+            w.source(main).map_err(|err| {
+                log::info!("TypstActor: failed to prepare main file: {:?}", err);
+                anyhow!("failed to get source: {err}")
+            })?;
+            w.prepare_env(&mut Default::default()).map_err(|err| {
+                log::error!("TypstActor: failed to prepare env: {:?}", err);
+                anyhow!("failed to prepare env")
+            })?;
 
-        Ok(fut?)
+            Ok(f(
+                &mut AnalysisContext::new(
+                    w,
+                    Analysis {
+                        root: w.root.clone(),
+                        position_encoding: enc,
+                    },
+                ),
+                doc,
+            ))
+        })?
     }
 
     fn steal_world<T: Send + Sync + 'static>(
@@ -694,7 +722,13 @@ impl CompileActor {
                 anyhow!("failed to prepare env")
             })?;
 
-            Ok(f(&mut AnalysisContext::new(compiler.compiler.world(), enc)))
+            Ok(f(&mut AnalysisContext::new(
+                w,
+                Analysis {
+                    root: w.root.clone(),
+                    position_encoding: enc,
+                },
+            )))
         })?
     }
 }