From 7d65829ed7cebb3c4a9025b29c092b49ec03b17f Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com> Date: Sat, 22 Jun 2024 15:06:42 +0800 Subject: [PATCH] refactor: combine typst-preview and tinymist compiler (#337) * refactor: combine typst-preview and tinymist compiler * dev: update link * fix: bad changes * dev: sync snapshot compiler --- Cargo.lock | 87 ++++++++- Cargo.toml | 11 +- crates/tinymist-query/src/analysis/global.rs | 5 +- crates/tinymist-query/src/symbol.rs | 6 +- crates/tinymist-query/src/tests.rs | 35 ++-- crates/tinymist/src/actor/mod.rs | 16 +- crates/tinymist/src/actor/typ_client.rs | 180 ++++++++++-------- crates/tinymist/src/actor/typ_server.rs | 116 ++++++----- crates/tinymist/src/lib.rs | 4 +- crates/tinymist/src/main.rs | 29 +-- crates/tinymist/src/resource/symbols.rs | 22 ++- crates/tinymist/src/server/lsp.rs | 16 +- crates/tinymist/src/server/preview.rs | 7 +- .../tinymist/src/server/preview_compiler.rs | 175 ++++------------- crates/tinymist/src/tools/package/init.rs | 8 +- crates/tinymist/src/tools/package/mod.rs | 4 +- crates/tinymist/src/tools/preview.rs | 25 ++- crates/tinymist/src/world.rs | 60 ++---- crates/typst-preview/src/lib.rs | 9 +- 19 files changed, 397 insertions(+), 418 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba0bd87e..796ee3aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -140,6 +140,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "archery" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8967cd1cc9e9e1954f644e14fbd6042fe9a37da96c52a67e44a2ac18261f8561" +dependencies = [ + "static_assertions", + "triomphe", +] + [[package]] name = "arraydeque" version = "0.5.1" @@ -2889,8 +2899,7 @@ dependencies = [ [[package]] name = "reflexo" version = "0.5.0-rc4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "803cbdda7d5beefc048966f342ba9a13dab586f136a8fe7f6d915b2f073a9151" +source = "git+https://github.com/Myriad-Dreamin/typst.ts/?rev=36b969e2d5743544dca8679c53c89129b989ec80#36b969e2d5743544dca8679c53c89129b989ec80" dependencies = [ "base64 0.22.1", "bitvec", @@ -2898,10 +2907,12 @@ dependencies = [ "dashmap", "ecow 0.2.2", "fxhash", + "instant", "once_cell", "parking_lot", "path-clean", "rkyv", + "rustc-hash", "serde", "serde_json", "serde_repr", @@ -2910,6 +2921,55 @@ dependencies = [ "tiny-skia-path", ] +[[package]] +name = "reflexo-vfs" +version = "0.5.0-rc4" +source = "git+https://github.com/Myriad-Dreamin/typst.ts/?rev=36b969e2d5743544dca8679c53c89129b989ec80#36b969e2d5743544dca8679c53c89129b989ec80" +dependencies = [ + "append-only-vec", + "indexmap 2.2.6", + "log", + "nohash-hasher", + "once_cell", + "parking_lot", + "reflexo", + "rpds", + "rustc-hash", + "typst", +] + +[[package]] +name = "reflexo-world" +version = "0.5.0-rc4" +source = "git+https://github.com/Myriad-Dreamin/typst.ts/?rev=36b969e2d5743544dca8679c53c89129b989ec80#36b969e2d5743544dca8679c53c89129b989ec80" +dependencies = [ + "append-only-vec", + "chrono", + "codespan-reporting", + "comemo 0.4.0", + "dashmap", + "dirs", + "flate2", + "fontdb", + "hex", + "indexmap 2.2.6", + "log", + "nohash-hasher", + "once_cell", + "parking_lot", + "reflexo", + "reflexo-vfs", + "reqwest", + "rustc-hash", + "serde", + "serde_json", + "sha2", + "strum 0.25.0", + "tar", + "typst", + "typst-ts-core", +] + [[package]] name = "regex" version = "1.10.4" @@ -3075,6 +3135,15 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" +[[package]] +name = "rpds" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0e15515d3ce3313324d842629ea4905c25a13f81953eadb88f85516f59290a4" +dependencies = [ + "archery", +] + [[package]] name = "rust_iso3166" version = "0.1.13" @@ -4425,12 +4494,10 @@ dependencies = [ [[package]] name = "typst-ts-compiler" version = "0.5.0-rc4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d68fc30324347a294155cc4691defa4e01aaff23d7e3c1c40a3c8e5d22e3f" +source = "git+https://github.com/Myriad-Dreamin/typst.ts/?rev=36b969e2d5743544dca8679c53c89129b989ec80#36b969e2d5743544dca8679c53c89129b989ec80" dependencies = [ "append-only-vec", "base64 0.22.1", - "chrono", "codespan-reporting", "comemo 0.4.0", "dirs", @@ -4446,7 +4513,8 @@ dependencies = [ "once_cell", "parking_lot", "pathdiff", - "reqwest", + "reflexo-vfs", + "reflexo-world", "rustc-hash", "serde", "serde_json", @@ -4463,8 +4531,7 @@ dependencies = [ [[package]] name = "typst-ts-core" version = "0.5.0-rc4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9355a7f3b2f687088524fa1d5c0d0cbe45878e9672d7a321aec491ae10b1dd" +source = "git+https://github.com/Myriad-Dreamin/typst.ts/?rev=36b969e2d5743544dca8679c53c89129b989ec80#36b969e2d5743544dca8679c53c89129b989ec80" dependencies = [ "base64 0.22.1", "base64-serde", @@ -4491,6 +4558,7 @@ dependencies = [ "serde_with", "sha2", "siphasher 1.0.1", + "svgtypes", "tiny-skia", "tiny-skia-path", "ttf-parser", @@ -4501,8 +4569,7 @@ dependencies = [ [[package]] name = "typst-ts-svg-exporter" version = "0.5.0-rc4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b1e7baba2453d072cbc1e97a43d5a8ecfe36299fc103e9156bb9470abd35e0" +source = "git+https://github.com/Myriad-Dreamin/typst.ts/?rev=36b969e2d5743544dca8679c53c89129b989ec80#36b969e2d5743544dca8679c53c89129b989ec80" dependencies = [ "base64 0.22.1", "comemo 0.4.0", diff --git a/Cargo.toml b/Cargo.toml index a9a02335..534026c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ typst-assets = "0.11.1" reflexo = { version = "0.5.0-rc4", default-features = false, features = [ "flat-vector", ] } +reflexo-world = { version = "0.5.0-rc4", features = ["system"] } typst-ts-core = { version = "0.5.0-rc4", default-features = false } typst-ts-compiler = { version = "0.5.0-rc4" } typst-ts-svg-exporter = { version = "0.5.0-rc4" } @@ -143,13 +144,15 @@ typst-syntax = { git = "https://github.com/Myriad-Dreamin/typst.git", branch = " # typst-render = { path = "../typst/crates/typst-render" } # typst-syntax = { path = "../typst/crates/typst-syntax" } -# typst-ts-svg-exporter = { git = "https://github.com/Myriad-Dreamin/typst.ts/", rev = "9cced415e29e5e341ad4bdcc32ab3e67ffad74db" } -# reflexo = { git = "https://github.com/Myriad-Dreamin/typst.ts/", rev = "9cced415e29e5e341ad4bdcc32ab3e67ffad74db" } -# typst-ts-core = { git = "https://github.com/Myriad-Dreamin/typst.ts/", rev = "9cced415e29e5e341ad4bdcc32ab3e67ffad74db" } -# typst-ts-compiler = { git = "https://github.com/Myriad-Dreamin/typst.ts/", rev = "9cced415e29e5e341ad4bdcc32ab3e67ffad74db" } +typst-ts-svg-exporter = { git = "https://github.com/Myriad-Dreamin/typst.ts/", rev = "36b969e2d5743544dca8679c53c89129b989ec80" } +reflexo = { git = "https://github.com/Myriad-Dreamin/typst.ts/", rev = "36b969e2d5743544dca8679c53c89129b989ec80" } +reflexo-world = { git = "https://github.com/Myriad-Dreamin/typst.ts/", rev = "36b969e2d5743544dca8679c53c89129b989ec80" } +typst-ts-core = { git = "https://github.com/Myriad-Dreamin/typst.ts/", rev = "36b969e2d5743544dca8679c53c89129b989ec80" } +typst-ts-compiler = { git = "https://github.com/Myriad-Dreamin/typst.ts/", rev = "36b969e2d5743544dca8679c53c89129b989ec80" } # typst-ts-svg-exporter = { path = "../typst.ts/exporter/svg" } # reflexo = { path = "../typst.ts/crates/reflexo/" } +# reflexo-world = { path = "../typst.ts/crates/reflexo-world/" } # typst-ts-core = { path = "../typst.ts/core" } # typst-ts-compiler = { path = "../typst.ts/compiler" } # typstyle = { path = "../typstyle" } diff --git a/crates/tinymist-query/src/analysis/global.rs b/crates/tinymist-query/src/analysis/global.rs index f8d7cbe9..960b9070 100644 --- a/crates/tinymist-query/src/analysis/global.rs +++ b/crates/tinymist-query/src/analysis/global.rs @@ -420,10 +420,7 @@ pub trait AnalysisResources { fn resolve(&self, spec: &PackageSpec) -> Result, PackageError>; /// Get all the files in the workspace. - fn iter_dependencies<'a>( - &'a self, - f: &mut dyn FnMut(&'a ImmutPath, FileResult<&std::time::SystemTime>), - ); + fn iter_dependencies(&self, f: &mut dyn FnMut(ImmutPath)); /// Resolve extra font information. fn font_info(&self, _font: Font) -> Option> { diff --git a/crates/tinymist-query/src/symbol.rs b/crates/tinymist-query/src/symbol.rs index 0b93d8e5..24c6c5df 100644 --- a/crates/tinymist-query/src/symbol.rs +++ b/crates/tinymist-query/src/symbol.rs @@ -36,11 +36,11 @@ impl SemanticRequest for SymbolRequest { let mut symbols = vec![]; - ctx.resources.iter_dependencies(&mut |path, _| { - let Ok(source) = ctx.source_by_path(path) else { + ctx.resources.iter_dependencies(&mut |path| { + let Ok(source) = ctx.source_by_path(&path) else { return; }; - let uri = path_to_url(path).unwrap(); + let uri = path_to_url(&path).unwrap(); let res = get_lexical_hierarchy(source.clone(), LexicalScopeKind::Symbol).and_then( |symbols| { self.pattern.as_ref().map(|pattern| { diff --git a/crates/tinymist-query/src/tests.rs b/crates/tinymist-query/src/tests.rs index 056904db..091ca01d 100644 --- a/crates/tinymist-query/src/tests.rs +++ b/crates/tinymist-query/src/tests.rs @@ -8,17 +8,13 @@ use std::{ use once_cell::sync::Lazy; pub use serde::Serialize; use serde_json::{ser::PrettyFormatter, Serializer, Value}; -use typst::{ - diag::FileResult, - syntax::{ - ast::{self, AstNode}, - FileId as TypstFileId, LinkedNode, Source, SyntaxKind, VirtualPath, - }, +use typst::syntax::{ + ast::{self, AstNode}, + FileId as TypstFileId, LinkedNode, Source, SyntaxKind, VirtualPath, }; use typst::{diag::PackageError, foundations::Bytes}; use typst_ts_compiler::{ - service::{CompileDriver, Compiler, EntryManager}, - NotifyApi, ShadowApi, + service::CompileDriver, EntryManager, EntryReader, ShadowApi, TypstSystemUniverse, WorldDeps, }; use typst_ts_core::{ config::compiler::{EntryOpts, EntryState}, @@ -46,10 +42,7 @@ impl<'a> AnalysisResources for WrapWorld<'a> { self.0.registry.resolve(spec) } - fn iter_dependencies<'b>( - &'b self, - f: &mut dyn FnMut(&'b reflexo::ImmutPath, FileResult<&typst_ts_compiler::Time>), - ) { + fn iter_dependencies(&self, f: &mut dyn FnMut(reflexo::ImmutPath)) { self.0.iter_dependencies(f) } } @@ -65,7 +58,7 @@ pub fn snapshot_testing(name: &str, f: &impl Fn(&mut AnalysisContext, PathBuf)) #[cfg(windows)] let contents = contents.replace("\r\n", "\n"); - run_with_sources(&contents, |w: &mut TypstSystemWorld, p| { + run_with_sources(&contents, |w: &mut TypstSystemUniverse, p| { let root = w.workspace_root().unwrap(); let paths = w .shadow_paths() @@ -74,7 +67,8 @@ pub fn snapshot_testing(name: &str, f: &impl Fn(&mut AnalysisContext, PathBuf)) TypstFileId::new(None, VirtualPath::new(p.strip_prefix(&root).unwrap())) }) .collect::>(); - let w = WrapWorld(w); + let mut w = w.spawn(); + let w = WrapWorld(&mut w); let mut ctx = AnalysisContext::new( &w, Analysis { @@ -103,13 +97,16 @@ pub fn get_test_properties(s: &str) -> HashMap<&'_ str, &'_ str> { props } -pub fn run_with_sources(source: &str, f: impl FnOnce(&mut TypstSystemWorld, PathBuf) -> T) -> T { +pub fn run_with_sources( + source: &str, + f: impl FnOnce(&mut TypstSystemUniverse, PathBuf) -> T, +) -> T { let root = if cfg!(windows) { PathBuf::from("C:\\") } else { PathBuf::from("/") }; - let mut world = TypstSystemWorld::new(CompileOpts { + let mut world = TypstSystemUniverse::new(CompileOpts { entry: EntryOpts::new_rooted(root.as_path().into(), None), ..Default::default() }) @@ -145,12 +142,12 @@ pub fn run_with_sources(source: &str, f: impl FnOnce(&mut TypstSystemWorld, P } world.mutate_entry(EntryState::new_detached()).unwrap(); - let mut driver = CompileDriver::new(world); + let mut driver = CompileDriver::new(std::marker::PhantomData, world); let _ = driver.compile(&mut Default::default()); let pw = last_pw.unwrap(); driver - .world_mut() + .universe_mut() .mutate_entry(EntryState::new_rooted( root.as_path().into(), Some(TypstFileId::new( @@ -159,7 +156,7 @@ pub fn run_with_sources(source: &str, f: impl FnOnce(&mut TypstSystemWorld, P )), )) .unwrap(); - f(driver.world_mut(), pw) + f(driver.universe_mut(), pw) } pub fn find_test_range(s: &Source) -> Range { diff --git a/crates/tinymist/src/actor/mod.rs b/crates/tinymist/src/actor/mod.rs index edf2bc30..6c5b76a1 100644 --- a/crates/tinymist/src/actor/mod.rs +++ b/crates/tinymist/src/actor/mod.rs @@ -13,10 +13,7 @@ use tinymist_query::analysis::Analysis; use tinymist_query::ExportKind; use tinymist_render::PeriscopeRenderer; use tokio::sync::{mpsc, watch}; -use typst_ts_compiler::{ - service::CompileDriverImpl, - vfs::notify::{FileChangeSet, MemoryEvent}, -}; +use typst_ts_compiler::vfs::notify::{FileChangeSet, MemoryEvent}; use typst_ts_core::config::compiler::EntryState; use self::{ @@ -28,12 +25,10 @@ use self::{ }; use crate::{ compiler::CompileServer, - world::{ImmutDict, LspWorld, LspWorldBuilder}, + world::{ImmutDict, LspWorldBuilder}, TypstLanguageServer, }; -type CompileDriverInner = CompileDriverImpl; - impl CompileServer { pub fn restart_server(&mut self, group: &str) { let server = self.server( @@ -102,13 +97,12 @@ impl CompileServer { self.handle.spawn_blocking(move || { // Create the world let font_resolver = font_resolver.wait().clone(); - let world = LspWorldBuilder::build(entry_.clone(), font_resolver, inputs) + let verse = LspWorldBuilder::build(entry_.clone(), font_resolver, inputs) .expect("incorrect options"); // Create the compiler - let driver = CompileDriverInner::new(world); let driver = CompileDriver { - inner: driver, + inner: std::marker::PhantomData, handler, analysis: Analysis { position_encoding, @@ -121,7 +115,7 @@ impl CompileServer { // Create the actor tokio::spawn( - CompileServerActor::new(driver, entry_, intr_tx, intr_rx) + CompileServerActor::new(driver, verse, entry_, intr_tx, intr_rx) .with_watch(true) .spawn(), ); diff --git a/crates/tinymist/src/actor/typ_client.rs b/crates/tinymist/src/actor/typ_client.rs index 6afbdf37..f8f49036 100644 --- a/crates/tinymist/src/actor/typ_client.rs +++ b/crates/tinymist/src/actor/typ_client.rs @@ -42,16 +42,16 @@ use tinymist_query::{ use tinymist_render::PeriscopeRenderer; use tokio::sync::{mpsc, oneshot, watch}; use typst::{ - diag::{FileResult, PackageError, SourceDiagnostic, SourceResult}, + diag::{PackageError, SourceDiagnostic, SourceResult}, layout::Position, model::Document as TypstDocument, syntax::package::PackageSpec, World as TypstWorld, }; use typst_ts_compiler::{ - service::{CompileDriverImpl, CompileEnv, CompileMiddleware, Compiler, EntryManager, EnvWorld}, + service::{CompileEnv, CompileMiddleware, Compiler, PureCompiler}, vfs::notify::MemoryEvent, - Time, + EntryManager, EntryReader, }; use typst_ts_core::{ config::compiler::EntryState, debug_loc::DataSource, error::prelude::*, typst::prelude::EcoVec, @@ -61,18 +61,18 @@ use typst_ts_core::{ use super::{ editor::{EditorRequest, TinymistCompileStatusEnum}, export::ExportConfig, - typ_server::{is_inactive, CompileServerActor, Interrupt}, + typ_server::{CompileServerActor, Interrupt}, }; use crate::{ actor::export::ExportRequest, compiler_init::CompileConfig, tools::preview::{CompilationHandle, CompileStatus}, utils, - world::LspWorld, + world::{LspCompilerFeat, LspWorld}, }; -type CompileDriverInner = CompileDriverImpl; -type CompileService = CompileServerActor; +type CompileService = CompileServerActor; +pub type CompileClientActor = CompileClientActorImpl; type EditorSender = mpsc::UnboundedSender; @@ -131,7 +131,7 @@ impl CompileHandler { } pub struct CompileDriver { - pub(super) inner: CompileDriverInner, + pub(super) inner: PureCompiler, #[allow(unused)] pub(super) handler: CompileHandler, pub(super) analysis: Analysis, @@ -139,7 +139,7 @@ pub struct CompileDriver { } impl CompileMiddleware for CompileDriver { - type Compiler = CompileDriverInner; + type Compiler = PureCompiler; fn inner(&self) -> &Self::Compiler { &self.inner @@ -149,7 +149,11 @@ impl CompileMiddleware for CompileDriver { &mut self.inner } - fn wrap_compile(&mut self, env: &mut CompileEnv) -> SourceResult> { + fn wrap_compile( + &mut self, + world: &LspWorld, + env: &mut CompileEnv, + ) -> SourceResult> { self.handler .editor_tx .send(EditorRequest::Status( @@ -158,10 +162,14 @@ impl CompileMiddleware for CompileDriver { )) .unwrap(); self.handler.status(CompileStatus::Compiling); - match self.inner_mut().compile(env) { + match self + .ensure_main(world) + .and_then(|_| self.inner_mut().compile(world, env)) + { Ok(doc) => { self.handler.notify_compile(Ok(doc.clone())); self.notify_diagnostics( + world, EcoVec::new(), env.tracer.as_ref().map(|e| e.clone().warnings()), ); @@ -170,7 +178,11 @@ impl CompileMiddleware for CompileDriver { Err(err) => { self.handler .notify_compile(Err(CompileStatus::CompileError)); - self.notify_diagnostics(err, env.tracer.as_ref().map(|e| e.clone().warnings())); + self.notify_diagnostics( + world, + err, + env.tracer.as_ref().map(|e| e.clone().warnings()), + ); Err(EcoVec::new()) } } @@ -180,20 +192,22 @@ impl CompileMiddleware for CompileDriver { impl CompileDriver { fn notify_diagnostics( &mut self, + world: &LspWorld, errors: EcoVec, warnings: Option>, ) { trace!("notify diagnostics: {errors:#?} {warnings:#?}"); - let diagnostics = self.run_analysis(|ctx| { + let diagnostics = self.run_analysis(world, |ctx| { tinymist_query::convert_diagnostics(ctx, errors.iter().chain(warnings.iter().flatten())) }); match diagnostics { Ok(diagnostics) => { + let entry = world.entry_state(); // todo: better way to remove diagnostics // todo: check all errors in this file - let detached = is_inactive(&self.inner.world().entry); + let detached = entry.is_inactive(); let valid = !detached; self.handler.push_diagnostics(valid.then_some(diagnostics)); } @@ -206,15 +220,14 @@ impl CompileDriver { pub fn run_analysis( &mut self, + w: &LspWorld, f: impl FnOnce(&mut AnalysisContext<'_>) -> T, ) -> anyhow::Result { - let w = self.inner.world_mut(); - let Some(main) = w.main_id() else { error!("TypstActor: main file is not set"); bail!("main file is not set"); }; - let Some(root) = w.entry.root() else { + let Some(root) = w.entry_state().root() else { error!("TypstActor: root is not set"); bail!("root is not set"); }; @@ -222,12 +235,8 @@ impl CompileDriver { info!("TypstActor: failed to prepare main file: {err:?}"); anyhow!("failed to get source: {err}") })?; - w.prepare_env(&mut Default::default()).map_err(|err| { - error!("TypstActor: failed to prepare env: {err:?}"); - anyhow!("failed to prepare env") - })?; - struct WrapWorld<'a>(&'a mut LspWorld, &'a PeriscopeRenderer); + struct WrapWorld<'a>(&'a LspWorld, &'a PeriscopeRenderer); impl<'a> AnalysisResources for WrapWorld<'a> { fn world(&self) -> &dyn typst::World { @@ -239,17 +248,14 @@ impl CompileDriver { self.0.registry.resolve(spec) } - fn iter_dependencies<'b>( - &'b self, - f: &mut dyn FnMut(&'b ImmutPath, FileResult<&Time>), - ) { - use typst_ts_compiler::NotifyApi; + fn iter_dependencies(&self, f: &mut dyn FnMut(ImmutPath)) { + use typst_ts_compiler::WorldDeps; self.0.iter_dependencies(f) } /// Resolve extra font information. fn font_info(&self, font: TypstFont) -> Option> { - self.0.font_resolver.inner.describe_font(&font) + self.0.font_resolver.describe_font(&font) } /// Resolve periscope image at the given position. @@ -270,20 +276,20 @@ impl CompileDriver { } } -pub struct CompileClientActor { +pub struct CompileClientActorImpl { pub diag_group: String, pub config: CompileConfig, entry: EntryState, - intr_tx: mpsc::UnboundedSender>, + intr_tx: mpsc::UnboundedSender>>, export_tx: mpsc::UnboundedSender, } -impl CompileClientActor { +impl + Send> CompileClientActorImpl { pub(crate) fn new( diag_group: String, config: CompileConfig, entry: EntryState, - intr_tx: mpsc::UnboundedSender>, + intr_tx: mpsc::UnboundedSender>>, export_tx: mpsc::UnboundedSender, ) -> Self { Self { @@ -297,11 +303,11 @@ impl CompileClientActor { fn steal_inner( &self, - f: impl FnOnce(&mut CompileService) -> Ret + Send + 'static, + f: impl FnOnce(&mut CompileService) -> Ret + Send + 'static, ) -> ZResult> { let (tx, rx) = oneshot::channel(); - let task = Box::new(move |this: &mut CompileService| { + let task = Box::new(move |this: &mut CompileService| { if tx.send(f(this)).is_err() { // Receiver was dropped. The main thread may have exited, or the request may // have been cancelled. @@ -319,7 +325,7 @@ impl CompileClientActor { /// Steal the compiler thread and run the given function. pub fn steal( &self, - f: impl FnOnce(&mut CompileService) -> Ret + Send + 'static, + f: impl FnOnce(&mut CompileService) -> Ret + Send + 'static, ) -> ZResult { utils::threaded_receive(self.steal_inner(f)?) } @@ -327,31 +333,46 @@ impl CompileClientActor { /// Steal the compiler thread and run the given function. pub async fn steal_async( &self, - f: impl FnOnce(&mut CompileService) -> Ret + Send + 'static, + f: impl FnOnce(&mut CompileService) -> Ret + Send + 'static, ) -> ZResult { self.steal_inner(f)? .await .map_err(map_string_err("failed to call steal_async")) } - pub fn steal_state( - &self, - f: impl FnOnce(&mut AnalysisContext, Option) -> T + Send + Sync + 'static, - ) -> anyhow::Result { - self.steal(move |compiler| { - let doc = compiler.success_doc(); - let c = &mut compiler.compiler.compiler; - c.run_analysis(move |ctx| f(ctx, doc)) - })? + pub fn sync_config(&mut self, config: CompileConfig) { + self.config = config; } - pub fn steal_world( - &self, - f: impl FnOnce(&mut AnalysisContext) -> T + Send + Sync + 'static, - ) -> anyhow::Result { - self.steal(move |compiler| compiler.compiler.compiler.run_analysis(f))? + pub fn add_memory_changes(&self, event: MemoryEvent) { + let _ = self.intr_tx.send(Interrupt::Memory(event)); } + pub(crate) fn change_export_pdf(&mut self, config: ExportConfig) { + let _ = self.export_tx.send(ExportRequest::ChangeConfig(config)); + } + + pub fn on_export(&self, kind: ExportKind, path: PathBuf) -> anyhow::Result> { + // todo: we currently doesn't respect the path argument... + info!("CompileActor: on export: {}", path.display()); + + let (tx, rx) = oneshot::channel(); + let _ = self.export_tx.send(ExportRequest::Oneshot(Some(kind), tx)); + let res: Option = utils::threaded_receive(rx)?; + + info!("CompileActor: on export end: {path:?} as {res:?}"); + Ok(res) + } + + pub fn on_save_export(&self, path: PathBuf) -> anyhow::Result<()> { + info!("CompileActor: on save export: {}", path.display()); + let _ = self.export_tx.send(ExportRequest::OnSaved); + + Ok(()) + } +} + +impl CompileClientActorImpl { pub fn settle(&mut self) { let _ = self.change_entry(None); info!("TypstActor({}): settle requested", self.diag_group); @@ -363,10 +384,6 @@ impl CompileClientActor { } } - pub fn sync_config(&mut self, config: CompileConfig) { - self.config = config; - } - pub fn change_entry(&mut self, path: Option) -> Result { if path .as_deref() @@ -388,8 +405,8 @@ impl CompileClientActor { self.steal(move |compiler| { compiler.change_entry(next.clone()); - let next_is_inactive = is_inactive(&next); - let res = compiler.compiler.world_mut().mutate_entry(next); + let next_is_inactive = next.is_inactive(); + let res = compiler.verse.mutate_entry(next); if next_is_inactive { info!("TypstActor: removing diag"); @@ -408,12 +425,26 @@ impl CompileClientActor { Ok(true) } - pub fn add_memory_changes(&self, event: MemoryEvent) { - let _ = self.intr_tx.send(Interrupt::Memory(event)); + pub fn steal_state( + &self, + f: impl FnOnce(&mut AnalysisContext, Option) -> T + Send + Sync + 'static, + ) -> anyhow::Result { + self.steal(move |compiler| { + let doc = compiler.success_doc(); + let w = compiler.verse.spawn(); + let c = &mut compiler.compiler.compiler; + c.run_analysis(&w, move |ctx| f(ctx, doc)) + })? } - pub(crate) fn change_export_pdf(&mut self, config: ExportConfig) { - let _ = self.export_tx.send(ExportRequest::ChangeConfig(config)); + pub fn steal_world( + &self, + f: impl FnOnce(&mut AnalysisContext) -> T + Send + Sync + 'static, + ) -> anyhow::Result { + self.steal(move |compiler| { + let w = compiler.verse.spawn(); + compiler.compiler.compiler.run_analysis(&w, f) + })? } pub fn clear_cache(&self) { @@ -426,13 +457,15 @@ impl CompileClientActor { let dg = self.diag_group.clone(); self.steal(move |c| { let cc = &c.compiler.compiler; + let w = c.verse.spawn(); let info = ServerInfoResponse { - root: cc.world().entry.root().map(|e| e.as_ref().to_owned()), - font_paths: cc.world().font_resolver.font_paths().to_owned(), - inputs: cc.world().inputs.as_ref().deref().clone(), + root: w.entry_state().root().map(|e| e.as_ref().to_owned()), + font_paths: w.font_resolver.font_paths().to_owned(), + inputs: c.verse.inputs().as_ref().deref().clone(), estimated_memory_usage: HashMap::from_iter([ - ("vfs".to_owned(), cc.world().vfs.memory_usage()), + // todo: vfs memory usage + // ("vfs".to_owned(), w.vfs.read().memory_usage()), ("analysis".to_owned(), cc.analysis.estimated_memory()), ]), }; @@ -441,23 +474,4 @@ impl CompileClientActor { }) .map_err(|e| e.into()) } - - pub fn on_export(&self, kind: ExportKind, path: PathBuf) -> anyhow::Result> { - // todo: we currently doesn't respect the path argument... - info!("CompileActor: on export: {}", path.display()); - - let (tx, rx) = oneshot::channel(); - let _ = self.export_tx.send(ExportRequest::Oneshot(Some(kind), tx)); - let res: Option = utils::threaded_receive(rx)?; - - info!("CompileActor: on export end: {path:?} as {res:?}"); - Ok(res) - } - - pub fn on_save_export(&self, path: PathBuf) -> anyhow::Result<()> { - info!("CompileActor: on save export: {}", path.display()); - let _ = self.export_tx.send(ExportRequest::OnSaved); - - Ok(()) - } } diff --git a/crates/tinymist/src/actor/typ_server.rs b/crates/tinymist/src/actor/typ_server.rs index 792e4ee1..9301df81 100644 --- a/crates/tinymist/src/actor/typ_server.rs +++ b/crates/tinymist/src/actor/typ_server.rs @@ -7,20 +7,28 @@ use std::{collections::HashSet, path::Path, sync::Arc, thread::JoinHandle}; use tinymist_query::VersionedDocument; use tokio::sync::{mpsc, oneshot}; -use typst_ts_compiler::service::{ - features::{FeatureSet, WITH_COMPILING_STATUS_FEATURE}, - watch_deps, CompileEnv, CompileReporter, Compiler, ConsoleDiagReporter, +use typst_ts_compiler::{ + service::{ + features::{FeatureSet, WITH_COMPILING_STATUS_FEATURE}, + watch_deps, CompileEnv, CompileReporter, Compiler, ConsoleDiagReporter, + }, + vfs::notify::{FilesystemEvent, MemoryEvent, NotifyMessage}, + world::{CompilerFeat, CompilerUniverse, CompilerWorld}, + Revising, WorldDeps, }; -use typst_ts_compiler::vfs::notify::{FilesystemEvent, MemoryEvent, NotifyMessage}; -use typst_ts_compiler::ShadowApi; -use typst_ts_core::{config::compiler::EntryState, TypstDocument, TypstFileId}; +use typst_ts_core::{config::compiler::EntryState, TypstDocument}; + +/// A task that can be sent to the context (compiler thread) +/// +/// The internal function will be dereferenced and called on the context. +type BorrowTask = Box; pub enum Interrupt { /// Compile anyway. Compile, /// Borrow the compiler thread and run the task. /// - /// See [`CompileClient::steal`] for more information. + /// See [`CompileClient::steal_async`] for more information. Task(BorrowTask), /// Memory file changes. Memory(MemoryEvent), @@ -30,11 +38,6 @@ pub enum Interrupt { Settle(oneshot::Sender<()>), } -/// A task that can be sent to the context (compiler/render thread) -/// -/// The internal function will be dereferenced and called on the context. -pub type BorrowTask = Box; - /// Responses from the compiler thread. enum CompilerResponse { /// Response to the file watcher @@ -55,9 +58,11 @@ struct SuspendState { } /// The compiler thread. -pub struct CompileServerActor { +pub struct CompileServerActor { + /// The underlying universe. + pub verse: CompilerUniverse, /// The underlying compiler. - pub compiler: CompileReporter, + pub compiler: CompileReporter>, /// Whether to enable file system watching. pub enable_watch: bool, @@ -85,12 +90,12 @@ pub struct CompileServerActor { suspend_state: SuspendState, } -impl CompileServerActor -where - C::World: for<'files> codespan_reporting::files::Files<'files, FileId = TypstFileId>, +impl> + Send + 'static> + CompileServerActor { pub fn new_with_features( compiler: C, + verse: CompilerUniverse, entry: EntryState, feature_set: FeatureSet, intr_tx: mpsc::UnboundedSender>, @@ -99,6 +104,7 @@ where Self { compiler: CompileReporter::new(compiler) .with_generic_reporter(ConsoleDiagReporter::default()), + verse, logical_tick: 1, enable_watch: false, @@ -116,7 +122,7 @@ where intr_rx, suspend_state: SuspendState { - suspended: is_inactive(&entry), + suspended: entry.is_inactive(), dirty: false, }, } @@ -125,18 +131,29 @@ where /// Create a new compiler thread. pub fn new( compiler: C, + world: CompilerUniverse, entry: EntryState, intr_tx: mpsc::UnboundedSender>, intr_rx: mpsc::UnboundedReceiver>, ) -> Self { - Self::new_with_features(compiler, entry, FeatureSet::default(), intr_tx, intr_rx) + Self::new_with_features( + compiler, + world, + entry, + FeatureSet::default(), + intr_tx, + intr_rx, + ) } - pub fn with_watch(mut self, enable_watch: bool) -> Self { self.enable_watch = enable_watch; self } + pub fn intr_tx(&self) -> mpsc::UnboundedSender> { + self.intr_tx.clone() + } + pub fn success_doc(&self) -> Option { self.latest_success_doc .clone() @@ -174,7 +191,8 @@ where async fn block_run_inner(mut self) -> bool { if !self.enable_watch { let mut env = self.make_env(self.once_feature_set.clone()); - let compiled = self.compiler.compile(&mut env); + let w = self.verse.spawn(); + let compiled = self.compiler.compile(&w, &mut env); return compiled.is_ok(); } @@ -191,7 +209,8 @@ where pub async fn spawn(mut self) -> Option> { if !self.enable_watch { let mut env = self.make_env(self.once_feature_set.clone()); - self.compiler.compile(&mut env).ok(); + let w = self.verse.spawn(); + self.compiler.compile(&w, &mut env).ok(); return None; } @@ -268,8 +287,8 @@ where Some(compile_thread.unwrap()) } - pub(crate) fn change_entry(&mut self, entry: EntryState) { - self.suspend_state.suspended = is_inactive(&entry); + pub fn change_entry(&mut self, entry: EntryState) { + self.suspend_state.suspended = entry.is_inactive(); if !self.suspend_state.suspended && self.suspend_state.dirty { self.intr_tx.send(Interrupt::Compile).ok(); } @@ -288,9 +307,11 @@ where return; } + let w = self.verse.spawn(); + // Compile the document. let mut env = self.make_env(self.watch_feature_set.clone()); - self.latest_doc = self.compiler.compile(&mut env).ok(); + self.latest_doc = self.compiler.compile(&w, &mut env).ok(); if self.latest_doc.is_some() { self.latest_success_doc.clone_from(&self.latest_doc); } @@ -303,8 +324,7 @@ where // Notify the new file dependencies. let mut deps = vec![]; - self.compiler - .iter_dependencies(&mut |dep, _| deps.push(dep.clone())); + w.iter_dependencies(&mut |dep| deps.push(dep.clone())); send(Notify(NotifyMessage::SyncDependency(deps))); } @@ -340,7 +360,8 @@ where // If there is no invalidation happening, apply memory changes directly. if files.is_empty() && self.dirty_shadow_logical_tick == 0 { - self.apply_memory_changes(event); + self.verse + .increment_revision(|verse| Self::apply_memory_changes(verse, event)); return true; } @@ -362,13 +383,15 @@ where Interrupt::Fs(mut event) => { log::debug!("CompileServerActor: fs event incoming {event:?}"); - // Handle delayed upstream update event before applying file system changes - if self.apply_delayed_memory_changes(&mut event).is_none() { - log::warn!("CompileServerActor: unknown upstream update event"); - } - // Apply file system changes. - self.compiler.notify_fs_event(event); + let dirty_tick = &mut self.dirty_shadow_logical_tick; + self.verse.increment_revision(|verse| { + // Handle delayed upstream update event before applying file system changes + if Self::apply_delayed_memory_changes(verse, dirty_tick, &mut event).is_none() { + log::warn!("CompileServerActor: unknown upstream update event"); + } + verse.notify_fs_event(event) + }); true } @@ -377,7 +400,11 @@ where } /// Apply delayed memory changes to underlying compiler. - fn apply_delayed_memory_changes(&mut self, event: &mut FilesystemEvent) -> Option<()> { + fn apply_delayed_memory_changes( + verse: &mut Revising>, + dirty_shadow_logical_tick: &mut usize, + event: &mut FilesystemEvent, + ) -> Option<()> { // Handle delayed upstream update event before applying file system changes if let FilesystemEvent::UpstreamUpdate { upstream_event, .. } = event { let event = upstream_event.take()?.opaque; @@ -387,25 +414,25 @@ where } = *event.downcast().ok()?; // Recovery from dirty shadow state. - if logical_tick == self.dirty_shadow_logical_tick { - self.dirty_shadow_logical_tick = 0; + if logical_tick == *dirty_shadow_logical_tick { + *dirty_shadow_logical_tick = 0; } - self.apply_memory_changes(event); + Self::apply_memory_changes(verse, event); } Some(()) } /// Apply memory changes to underlying compiler. - fn apply_memory_changes(&mut self, event: MemoryEvent) { + fn apply_memory_changes(verse: &mut Revising>, event: MemoryEvent) { if matches!(event, MemoryEvent::Sync(..)) { - self.compiler.reset_shadow(); + verse.reset_shadow(); } match event { MemoryEvent::Update(event) | MemoryEvent::Sync(event) => { for removes in event.removes { - let _ = self.compiler.unmap_shadow(&removes); + let _ = verse.unmap_shadow(&removes); } for (p, t) in event.inserts { let insert_file = match t.content().cloned() { @@ -420,18 +447,13 @@ where } }; - let _ = self.compiler.map_shadow(&p, insert_file); + let _ = verse.map_shadow(&p, insert_file); } } } } } -pub fn is_inactive(entry: &EntryState) -> bool { - use EntryState::*; - matches!(entry, Detached | Workspace { main: None, .. }) -} - #[inline] fn log_send_error(chan: &'static str, res: Result<(), mpsc::error::SendError>) -> bool { res.map_err(|err| log::warn!("CompileServerActor: send to {chan} error: {err}")) diff --git a/crates/tinymist/src/lib.rs b/crates/tinymist/src/lib.rs index fd10df2c..d0367fc9 100644 --- a/crates/tinymist/src/lib.rs +++ b/crates/tinymist/src/lib.rs @@ -42,7 +42,9 @@ pub use server::compiler_init; pub use server::lsp::*; pub use server::lsp_init::*; pub use server::preview; -pub use world::{CompileFontOpts, CompileOnceOpts, CompileOpts, LspWorld, LspWorldBuilder}; +pub use world::{ + CompileFontOpts, CompileOnceOpts, CompileOpts, LspUniverse, LspWorld, LspWorldBuilder, +}; use lsp_server::ResponseError; diff --git a/crates/tinymist/src/main.rs b/crates/tinymist/src/main.rs index 9c04e44f..fd10597e 100644 --- a/crates/tinymist/src/main.rs +++ b/crates/tinymist/src/main.rs @@ -10,12 +10,6 @@ use comemo::Prehashed; use lsp_types::{InitializeParams, InitializedParams}; use once_cell::sync::Lazy; use parking_lot::RwLock; -use tokio::sync::mpsc; -use typst::{eval::Tracer, foundations::IntoValue, syntax::Span}; -use typst_ts_compiler::service::{CompileEnv, Compiler, EntryManager}; -use typst_ts_core::{typst::prelude::EcoVec, TypstDict}; - -use crate::args::{CliArguments, Commands, CompileArgs, LspArgs}; use tinymist::{ compiler_init::{CompileInit, CompileInitializeParams}, harness::{lsp_harness, InitializedLspDriver, LspDriver, LspHost}, @@ -23,6 +17,13 @@ use tinymist::{ transport::with_stdio_transport, CompileFontOpts, Init, LspWorld, TypstLanguageServer, }; +use tokio::sync::mpsc; +use typst::World; +use typst::{eval::Tracer, foundations::IntoValue, syntax::Span}; +use typst_ts_compiler::service::{CompileEnv, Compiler}; +use typst_ts_core::{typst::prelude::EcoVec, TypstDict}; + +use crate::args::{CliArguments, Commands, CompileArgs, LspArgs}; #[cfg(feature = "dhat-heap")] #[global_allocator] @@ -183,8 +184,12 @@ pub fn compiler_main(args: CompileArgs) -> anyhow::Result<()> { let (timings, _doc, diagnostics) = service .compiler() .steal(|c| { - c.compiler.world_mut().mutate_entry(entry).unwrap(); - c.compiler.world_mut().inputs = inputs; + c.verse.increment_revision(|verse| { + verse.mutate_entry(entry).unwrap(); + verse.set_inputs(inputs); + }); + + let w = c.verse.spawn(); let mut env = CompileEnv { tracer: Some(Tracer::default()), @@ -192,24 +197,23 @@ pub fn compiler_main(args: CompileArgs) -> anyhow::Result<()> { }; typst_timing::enable(); let mut errors = EcoVec::new(); - let res = match c.compiler.pure_compile(&mut env) { + let res = match c.compiler.pure_compile(&w, &mut env) { Ok(doc) => Some(doc), Err(e) => { errors = e; None } }; - let world = c.compiler.world(); let mut writer = std::io::BufWriter::new(Vec::new()); let _ = typst_timing::export_json(&mut writer, |span| { - resolve_span(world, span).unwrap_or_else(|| ("unknown".to_string(), 0)) + resolve_span(&w, span).unwrap_or_else(|| ("unknown".to_string(), 0)) }); let s = String::from_utf8(writer.into_inner().unwrap()).unwrap(); let warnings = env.tracer.map(|e| e.warnings()); - let diagnostics = c.compiler.compiler.run_analysis(|ctx| { + let diagnostics = c.compiler.compiler.run_analysis(&w, |ctx| { tinymist_query::convert_diagnostics( ctx, warnings.iter().flatten().chain(errors.iter()), @@ -261,7 +265,6 @@ impl Drop for ForceDrop { /// Turns a span into a (file, line) pair. fn resolve_span(world: &LspWorld, span: Span) -> Option<(String, u32)> { - use typst::World; let id = span.id()?; let source = world.source(id).ok()?; let range = source.range(span)?; diff --git a/crates/tinymist/src/resource/symbols.rs b/crates/tinymist/src/resource/symbols.rs index 1aba7ebf..a135dec6 100644 --- a/crates/tinymist/src/resource/symbols.rs +++ b/crates/tinymist/src/resource/symbols.rs @@ -1,6 +1,5 @@ use std::{collections::BTreeMap, path::Path, sync::Arc}; -use typst_ts_compiler::{service::EntryManager, ShadowApi}; use typst_ts_core::{config::compiler::EntryState, font::GlyphId, TypstDocument, TypstFont}; pub use super::prelude::*; @@ -188,16 +187,23 @@ impl TypstLanguageServer { let font = self .primary() .steal(move |e| { + let verse = &mut e.verse; let entry_path: Arc = Path::new("/._sym_.typ").into(); let new_entry = EntryState::new_rootless(entry_path.clone())?; - let old_entry = e.compiler.world_mut().mutate_entry(new_entry).ok()?; - let prepared = e - .compiler - .map_shadow(&entry_path, math_shaping_text.into_bytes().into()) - .is_ok(); - let sym_doc = prepared.then(|| e.compiler.pure_compile(&mut Default::default())); - e.compiler.world_mut().mutate_entry(old_entry).ok()?; + let (old_entry, prepared) = verse.increment_revision(|verse| { + let old_entry = verse.mutate_entry(new_entry).ok()?; + let prepared = verse + .map_shadow(&entry_path, math_shaping_text.into_bytes().into()) + .is_ok(); + + Some((old_entry, prepared)) + })?; + + let w = verse.spawn(); + let sym_doc = + prepared.then(|| e.compiler.pure_compile(&w, &mut Default::default())); + verse.increment_revision(|verse| verse.mutate_entry(old_entry).ok())?; log::debug!( "sym doc: {doc:?}", diff --git a/crates/tinymist/src/server/lsp.rs b/crates/tinymist/src/server/lsp.rs index e14e1154..18071df2 100644 --- a/crates/tinymist/src/server/lsp.rs +++ b/crates/tinymist/src/server/lsp.rs @@ -23,7 +23,6 @@ use tinymist_query::{ use tokio::sync::mpsc; use typst::diag::StrResult; use typst::syntax::package::{PackageSpec, VersionlessPackageSpec}; -use typst_ts_compiler::service::Compiler; use typst_ts_core::path::PathClean; use typst_ts_core::{error::prelude::*, ImmutPath}; @@ -646,7 +645,7 @@ impl TypstLanguageServer { let res = self .primary() .steal(move |c| { - let cc = &c.compiler; + let verse = &c.verse; // todo: rootless file // todo: memory dirty file @@ -665,8 +664,8 @@ impl TypstLanguageServer { compiler_program: self_path, root: root.as_ref().to_owned(), main, - inputs: cc.world().inputs.as_ref().deref().clone(), - font_paths: cc.world().font_resolver.font_paths().to_owned(), + inputs: verse.inputs().as_ref().deref().clone(), + font_paths: verse.font_resolver.font_paths().to_owned(), }, )) .context("cannot send trace request")?; @@ -770,6 +769,7 @@ impl TypstLanguageServer { let res = self .primary() .steal(move |c| { + let world = c.verse.spawn(); // Parse the package specification. If the user didn't specify the version, // we try to figure it out automatically by downloading the package index // or searching the disk. @@ -779,7 +779,7 @@ impl TypstLanguageServer { // Try to parse without version, but prefer the error message of the // normal package spec parsing if it fails. let spec: VersionlessPackageSpec = from_source.parse().map_err(|_| err)?; - let version = determine_latest_version(c.compiler.world(), &spec)?; + let version = determine_latest_version(&c.verse, &spec)?; StrResult::Ok(spec.at(version)) }) .map_err(map_string_err("failed to parse package spec"))?; @@ -787,7 +787,7 @@ impl TypstLanguageServer { let from_source = TemplateSource::Package(spec); let entry_path = package::init( - c.compiler.world(), + &world, InitTask { tmpl: from_source.clone(), dir: to_path.clone(), @@ -827,14 +827,14 @@ impl TypstLanguageServer { // Try to parse without version, but prefer the error message of the // normal package spec parsing if it fails. let spec: VersionlessPackageSpec = from_source.parse().map_err(|_| err)?; - let version = determine_latest_version(c.compiler.world(), &spec)?; + let version = determine_latest_version(&c.verse, &spec)?; StrResult::Ok(spec.at(version)) }) .map_err(map_string_err("failed to parse package spec"))?; let from_source = TemplateSource::Package(spec); - let entry = package::get_entry(c.compiler.world(), from_source) + let entry = package::get_entry(&c.verse, from_source) .map_err(map_string_err("failed to get template entry"))?; ZResult::Ok(entry) diff --git a/crates/tinymist/src/server/preview.rs b/crates/tinymist/src/server/preview.rs index 5e4693dd..b56ba68a 100644 --- a/crates/tinymist/src/server/preview.rs +++ b/crates/tinymist/src/server/preview.rs @@ -5,8 +5,7 @@ use await_tree::InstrumentAwait; use log::{error, info}; use typst::foundations::{Str, Value}; -use typst_ts_compiler::service::CompileDriver; -use typst_ts_compiler::TypstSystemWorld; +use typst_ts_compiler::{service::CompileDriver, TypstSystemUniverse}; use typst_ts_core::config::{compiler::EntryOpts, CompileOpts}; use hyper::{ @@ -139,7 +138,7 @@ pub async fn preview_main(args: PreviewCliArgs) -> anyhow::Result<()> { } let compiler_driver = { - let world = TypstSystemWorld::new(CompileOpts { + let world = TypstSystemUniverse::new(CompileOpts { entry: EntryOpts::new_rooted(root.clone(), Some(entry.clone())), inputs, no_system_fonts: args.compile.font.ignore_system_fonts, @@ -149,7 +148,7 @@ pub async fn preview_main(args: PreviewCliArgs) -> anyhow::Result<()> { }) .expect("incorrect options"); - CompileDriver::new(world).with_entry_file(entry) + CompileDriver::new(std::marker::PhantomData, world.with_entry_file(entry)) }; tokio::spawn(async move { diff --git a/crates/tinymist/src/server/preview_compiler.rs b/crates/tinymist/src/server/preview_compiler.rs index 8f8f703a..d4916ea0 100644 --- a/crates/tinymist/src/server/preview_compiler.rs +++ b/crates/tinymist/src/server/preview_compiler.rs @@ -1,32 +1,31 @@ -use std::path::Path; use std::sync::Arc; use await_tree::InstrumentAwait; -use log::error; +use tokio::sync::mpsc; use typst::diag::SourceResult; -use typst::layout::Position; use typst::model::Document; -use typst::syntax::Span; -use typst_ts_compiler::service::{ - CompileActor, CompileClient as TsCompileClient, CompileExporter, Compiler, WorldExporter, -}; +use typst::World; use typst_ts_compiler::service::{CompileDriver, CompileMiddleware}; -use typst_ts_compiler::vfs::notify::{FileChangeSet, MemoryEvent}; -use typst_ts_core::debug_loc::SourceSpanOffset; +use typst_ts_compiler::service::{CompileExporter, Compiler, PureCompiler, WorldExporter}; +use typst_ts_compiler::{EntryReader, TypstSystemWorld}; use typst_ts_core::Error; use typst_preview::{CompilationHandle, CompileStatus}; -use typst_preview::{CompileHost, EditorServer, MemoryFiles, MemoryFilesShort, SourceFileServer}; -use typst_preview::{DocToSrcJumpInfo, Location}; -pub type CompileService = CompileActor, H>>; -pub type CompileClient = TsCompileClient>; +use crate::actor::typ_client::CompileClientActorImpl; +use crate::actor::typ_server::CompileServerActor; +use crate::compiler_init::CompileConfig; +use crate::world::{LspCompilerFeat, LspWorld}; + +pub type CompileService = + CompileServerActor>, H>, LspCompilerFeat>; +pub type CompileClient = + CompileClientActorImpl>, H>>; pub struct CompileServer { inner: CompileService, - client: TypstClient, } pub struct Reporter { @@ -47,10 +46,11 @@ impl CompileMiddleware for Reporter { fn wrap_compile( &mut self, + world: &::W, env: &mut typst_ts_compiler::service::CompileEnv, ) -> SourceResult> { self.cb.status(CompileStatus::Compiling); - match self.inner_mut().compile(env) { + match self.inner_mut().compile(world, env) { Ok(doc) => { self.cb.notify_compile(Ok(doc.clone())); Ok(doc) @@ -63,138 +63,45 @@ impl CompileMiddleware for Reporter { } } -impl WorldExporter for Reporter { - fn export(&mut self, output: Arc) -> SourceResult<()> { - self.inner.export(output) +impl + WorldExporter, H> WorldExporter for Reporter { + fn export(&mut self, world: &W, output: Arc) -> SourceResult<()> { + self.inner.export(world, output) } } impl CompileServer { pub fn new( - compiler_driver: CompileDriver, + compiler_driver: CompileDriver>, cb: H, // renderer_sender: broadcast::Sender, // editor_conn_sender: mpsc::UnboundedSender, ) -> Self { + let (intr_tx, intr_rx) = mpsc::unbounded_channel(); + let CompileDriver { compiler, universe } = compiler_driver; + let entry = universe.entry_state(); + // CompileExporter + DynamicLayoutCompiler + WatchDriver - let driver = CompileExporter::new(compiler_driver); + let driver = CompileExporter::new(compiler); let driver = Reporter { inner: driver, cb }; - let inner = CompileActor::new(driver).with_watch(true); + let inner = + CompileServerActor::new(driver, universe, entry, intr_tx, intr_rx).with_watch(true); - Self { - inner, - client: TypstClient { - inner: once_cell::sync::OnceCell::new(), - }, - } + Self { inner } } - pub fn spawn(self) -> Result, Error> { - let (server, client) = self.inner.split(); - tokio::spawn(server.spawn().instrument_await("spawn typst server")); - - self.client.inner.set(client).ok().unwrap(); - - Ok(self.client) + pub fn spawn(self) -> Result, Error> { + let (export_tx, mut export_rx) = mpsc::unbounded_channel(); + let intr_tx = self.inner.intr_tx(); + let entry = self.inner.verse.entry_state(); + tokio::spawn(self.inner.spawn().instrument_await("spawn typst server")); + // drop all export events + tokio::spawn(async move { while export_rx.recv().await.is_some() {} }); + Ok(CompileClient::new( + "main".to_owned(), + CompileConfig::default(), + entry, + intr_tx, + export_tx, + )) } } - -pub struct TypstClient { - inner: once_cell::sync::OnceCell>, -} - -impl TypstClient { - fn inner(&mut self) -> &mut CompileClient { - self.inner.get_mut().unwrap() - } -} - -impl SourceFileServer for TypstClient { - async fn resolve_source_span( - &mut self, - loc: Location, - ) -> Result, Error> { - let Location::Src(src_loc) = loc; - self.inner() - .resolve_src_location(src_loc) - .instrument_await("resolve src location") - .await - } - - async fn resolve_document_position( - &mut self, - loc: Location, - ) -> Result, Error> { - let Location::Src(src_loc) = loc; - - let path = Path::new(&src_loc.filepath).to_owned(); - let line = src_loc.pos.line; - let column = src_loc.pos.column; - - self.inner() - .resolve_src_to_doc_jump(path, line, column) - .instrument_await("resolve src to doc jump") - .await - } - - async fn resolve_source_location( - &mut self, - s: Span, - offset: Option, - ) -> Result, Error> { - Ok(self - .inner() - .resolve_span_and_offset(s, offset) - .instrument_await("resolve span offset") - .await - .map_err(|err| { - error!("TypstActor: failed to resolve doc to src jump: {:#}", err); - }) - .ok() - .flatten() - .map(|e| DocToSrcJumpInfo { - filepath: e.filepath, - start: e.start, - end: e.end, - })) - } -} - -impl EditorServer for TypstClient { - async fn update_memory_files( - &mut self, - files: MemoryFiles, - reset_shadow: bool, - ) -> Result<(), Error> { - // todo: is it safe to believe that the path is normalized? - let now = std::time::SystemTime::now(); - let files = FileChangeSet::new_inserts( - files - .files - .into_iter() - .map(|(path, content)| { - let content = content.as_bytes().into(); - // todo: cloning PathBuf -> Arc - (path.into(), Ok((now, content)).into()) - }) - .collect(), - ); - self.inner().add_memory_changes(if reset_shadow { - MemoryEvent::Sync(files) - } else { - MemoryEvent::Update(files) - }); - - Ok(()) - } - - async fn remove_shadow_files(&mut self, files: MemoryFilesShort) -> Result<(), Error> { - // todo: is it safe to believe that the path is normalized? - let files = FileChangeSet::new_removes(files.files.into_iter().map(From::from).collect()); - self.inner().add_memory_changes(MemoryEvent::Update(files)); - - Ok(()) - } -} - -impl CompileHost for TypstClient {} diff --git a/crates/tinymist/src/tools/package/init.rs b/crates/tinymist/src/tools/package/init.rs index b9fc2e6d..71f944f3 100644 --- a/crates/tinymist/src/tools/package/init.rs +++ b/crates/tinymist/src/tools/package/init.rs @@ -7,7 +7,7 @@ use typst::syntax::VirtualPath; use typst::World; use typst_ts_core::{Bytes, ImmutPath, TypstFileId}; -use crate::world::LspWorld; +use crate::world::{LspUniverse, LspWorld}; #[derive(Debug, Clone)] pub enum TemplateSource { @@ -20,13 +20,15 @@ pub struct InitTask { } /// Execute an initialization command. -pub fn get_entry(world: &LspWorld, tmpl: TemplateSource) -> StrResult { +pub fn get_entry(verse: &LspUniverse, tmpl: TemplateSource) -> StrResult { let TemplateSource::Package(spec) = tmpl; let toml_id = TypstFileId::new(Some(spec.clone()), VirtualPath::new("typst.toml")); + let world = verse.spawn(); + // Parse the manifest. - let manifest = parse_manifest(world, toml_id)?; + let manifest = parse_manifest(&world, toml_id)?; manifest.validate(&spec)?; // Ensure that it is indeed a template. diff --git a/crates/tinymist/src/tools/package/mod.rs b/crates/tinymist/src/tools/package/mod.rs index 4c31e9a6..e821224d 100644 --- a/crates/tinymist/src/tools/package/mod.rs +++ b/crates/tinymist/src/tools/package/mod.rs @@ -2,14 +2,14 @@ use typst::diag::{eco_format, StrResult}; use typst::syntax::package::{PackageVersion, VersionlessPackageSpec}; use typst_ts_compiler::package::Registry; -use crate::world::LspWorld; +use crate::world::LspUniverse; mod init; pub use init::*; /// Try to determine the latest version of a package. pub fn determine_latest_version( - world: &LspWorld, + world: &LspUniverse, spec: &VersionlessPackageSpec, ) -> StrResult { if spec.namespace == "preview" { diff --git a/crates/tinymist/src/tools/preview.rs b/crates/tinymist/src/tools/preview.rs index 4db282f6..f87b901c 100644 --- a/crates/tinymist/src/tools/preview.rs +++ b/crates/tinymist/src/tools/preview.rs @@ -9,14 +9,15 @@ use typst_preview::{ CompileHost, DocToSrcJumpInfo, EditorServer, Location, MemoryFiles, MemoryFilesShort, SourceFileServer, }; -use typst_ts_compiler::service::{Compiler, EntryManager}; use typst_ts_compiler::vfs::notify::{FileChangeSet, MemoryEvent}; +use typst_ts_compiler::{service::Compiler, EntryReader}; use typst_ts_core::debug_loc::SourceSpanOffset; use typst_ts_core::{Error, TypstDocument, TypstFileId}; -use crate::actor::typ_client::CompileClientActor; +use crate::actor::typ_client::CompileClientActorImpl; +use crate::world::LspWorld; -impl SourceFileServer for CompileClientActor { +impl + Send> SourceFileServer for CompileClientActorImpl { /// fixme: character is 0-based, UTF-16 code unit. /// We treat it as UTF-8 now. async fn resolve_source_span( @@ -25,12 +26,10 @@ impl SourceFileServer for CompileClientActor { ) -> Result, Error> { let Location::Src(loc) = loc; self.steal_async(move |this| { - let world = this.compiler.world(); + let world = this.verse.spawn(); let filepath = Path::new(&loc.filepath); - let relative_path = filepath - .strip_prefix(&this.compiler.world().workspace_root()?) - .ok()?; + let relative_path = filepath.strip_prefix(&world.workspace_root()?).ok()?; let source_id = TypstFileId::new(None, VirtualPath::new(relative_path)); let source = world.source(source_id).ok()?; @@ -64,11 +63,9 @@ impl SourceFileServer for CompileClientActor { self.steal_async(move |this| { let doc = this.latest_doc.as_deref()?; - let world = this.compiler.world(); + let world = this.verse.spawn(); - let relative_path = path - .strip_prefix(&this.compiler.world().workspace_root()?) - .ok()?; + let relative_path = path.strip_prefix(&world.workspace_root()?).ok()?; let source_id = TypstFileId::new(None, VirtualPath::new(relative_path)); let source = world.source(source_id).ok()?; @@ -89,7 +86,7 @@ impl SourceFileServer for CompileClientActor { let ret = self .steal_async(move |this| { - let world = this.compiler.world(); + let world = this.verse.spawn(); let src_id = span.id()?; let source = world.source(src_id).ok()?; let mut range = source.find(span)?.range(); @@ -181,7 +178,7 @@ fn find_in_frame(frame: &Frame, span: Span, min_dis: &mut u64, p: &mut Point) -> None } -impl EditorServer for CompileClientActor { +impl + Send> EditorServer for CompileClientActorImpl { async fn update_memory_files( &mut self, files: MemoryFiles, @@ -218,4 +215,4 @@ impl EditorServer for CompileClientActor { } } -impl CompileHost for CompileClientActor {} +impl + Send> CompileHost for CompileClientActorImpl {} diff --git a/crates/tinymist/src/world.rs b/crates/tinymist/src/world.rs index a316ee97..eaaa90fe 100644 --- a/crates/tinymist/src/world.rs +++ b/crates/tinymist/src/world.rs @@ -13,7 +13,7 @@ use typst_ts_compiler::{ font::system::SystemFontSearcher, package::http::HttpRegistry, vfs::{system::SystemAccessModel, Vfs}, - world::CompilerWorld, + SystemCompilerFeat, TypstSystemUniverse, TypstSystemWorld, }; #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] @@ -49,7 +49,6 @@ pub struct CompileFontOpts { #[derive(Debug, Clone)] pub struct SharedFontResolver { - font_paths: Vec, pub inner: Arc, } @@ -63,58 +62,24 @@ impl FontResolver for SharedFontResolver { } impl SharedFontResolver { - pub fn new(mut opts: CompileFontOpts) -> ZResult { - // todo: relative paths? - let mut has_relative_path = false; - for p in &opts.font_paths { - if p.is_relative() { - has_relative_path = true; - break; - } - } - if has_relative_path { - let current_dir = std::env::current_dir() - .context("failed to get current directory for relative font paths")?; - for p in &mut opts.font_paths { - if p.is_relative() { - *p = current_dir.join(&p); - } - } - } - - let font_paths = opts.font_paths.clone(); - let res = crate::world::LspWorldBuilder::resolve_fonts(opts)?; + pub fn new(opts: CompileFontOpts) -> ZResult { Ok(Self { - font_paths, - inner: Arc::new(res), + inner: Arc::new(crate::world::LspWorldBuilder::resolve_fonts(opts)?), }) } pub fn font_paths(&self) -> &[PathBuf] { - &self.font_paths + self.inner.font_paths() } } -/// type trait of [`LspWorld`]. -#[derive(Debug, Clone, Copy)] -pub struct SystemCompilerFeat; - -impl typst_ts_compiler::world::CompilerFeat for SystemCompilerFeat { - /// Uses [`SharedFontResolver`] directly. - type FontResolver = SharedFontResolver; - /// It accesses a physical file system. - type AccessModel = SystemAccessModel; - /// It performs native HTTP requests for fetching package data. - type Registry = HttpRegistry; -} - -/// The compiler world in system environment. -pub type LspWorld = CompilerWorld; +pub type LspCompilerFeat = SystemCompilerFeat; +pub type LspUniverse = TypstSystemUniverse; +pub type LspWorld = TypstSystemWorld; pub type ImmutDict = Arc>; pub struct LspWorldBuilder; -// Self::resolve_fonts(opts)?, impl LspWorldBuilder { /// Create [`LspWorld`] with the given options. @@ -124,15 +89,14 @@ impl LspWorldBuilder { entry: EntryState, font_resolver: SharedFontResolver, inputs: ImmutDict, - ) -> ZResult { - let mut res = CompilerWorld::new_raw( + ) -> ZResult { + Ok(LspUniverse::new_raw( entry, + Some(inputs), Vfs::new(SystemAccessModel {}), HttpRegistry::default(), - font_resolver, - ); - res.inputs = inputs; - Ok(res) + font_resolver.inner, + )) } /// Resolve fonts from given options. diff --git a/crates/typst-preview/src/lib.rs b/crates/typst-preview/src/lib.rs index 0b253d9c..0c7b8dcd 100644 --- a/crates/typst-preview/src/lib.rs +++ b/crates/typst-preview/src/lib.rs @@ -13,7 +13,7 @@ use ::await_tree::InstrumentAwait; use debug_loc::SpanInterner; use futures::SinkExt; use log::info; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use tokio::net::{TcpListener, TcpStream}; use tokio_tungstenite::tungstenite::Message; use tokio_tungstenite::WebSocketStream; @@ -22,7 +22,12 @@ use typst_ts_core::debug_loc::SourceSpanOffset; use typst_ts_core::Error; use typst_ts_core::{ImmutStr, TypstDocument as Document}; -pub use typst_ts_compiler::service::DocToSrcJumpInfo; +#[derive(Debug, Serialize)] +pub struct DocToSrcJumpInfo { + pub filepath: String, + pub start: Option<(usize, usize)>, // row, column + pub end: Option<(usize, usize)>, +} use actor::editor::EditorActor; use actor::typst::TypstActor;