diff --git a/crates/tinymist-world/src/lib.rs b/crates/tinymist-world/src/lib.rs index 14b08417c..dc52c6203 100644 --- a/crates/tinymist-world/src/lib.rs +++ b/crates/tinymist-world/src/lib.rs @@ -6,7 +6,7 @@ pub use reflexo_typst::config::CompileFontOpts; pub use reflexo_typst::error::prelude; pub use reflexo_typst::world as base; pub use reflexo_typst::{entry::*, vfs, EntryOpts, EntryState}; -use typ_server::ProjectInterrupt; +use typ_server::{ProjectCompiler, ProjectInterrupt}; use std::path::Path; use std::{borrow::Cow, path::PathBuf, sync::Arc}; @@ -204,6 +204,8 @@ pub type LspWorld = TypstSystemWorldExtend; pub type ImmutDict = Arc<LazyHash<TypstDict>>; /// LSP interrupt. pub type LspInterrupt = ProjectInterrupt<LspCompilerFeat>; +/// LSP project compiler. +pub type LspProjectCompiler = ProjectCompiler<LspCompilerFeat>; /// Builder for LSP universe. pub struct LspUniverseBuilder; diff --git a/crates/tinymist-world/src/typ_server.rs b/crates/tinymist-world/src/typ_server.rs index ca52574f3..3681f4780 100644 --- a/crates/tinymist-world/src/typ_server.rs +++ b/crates/tinymist-world/src/typ_server.rs @@ -11,7 +11,7 @@ use std::{ }; use sync_lsp::LspClient; -use tinymist_project::{Id, ProjectInput, ResourcePath}; +use tinymist_project::{Id, ProjectInput, ProjectTask, ResourcePath}; use tokio::sync::{mpsc, oneshot}; use reflexo_typst::{ @@ -20,12 +20,12 @@ use reflexo_typst::{ typst::prelude::EcoVec, vfs::notify::{FilesystemEvent, MemoryEvent, NotifyMessage, UpstreamUpdateEvent}, watch_deps, CompileEnv, CompileReport, Compiler, CompilerFeat, CompilerUniverse, CompilerWorld, - ConsoleDiagReporter, EntryReader, EntryState, GenericExporter, Revising, TaskInputs, - TypstDocument, WorldDeps, + ConsoleDiagReporter, EntryReader, GenericExporter, Revising, TaskInputs, TypstDocument, + WorldDeps, }; use typst::diag::{SourceDiagnostic, SourceResult}; -use crate::LspCompilerFeat; +use crate::{LspCompilerFeat, LspWorld}; /// A signal that possibly triggers an export. /// @@ -528,56 +528,91 @@ impl<F: CompilerFeat + Send + Sync + 'static> ProjectCompiler<F> { } impl ProjectCompiler<LspCompilerFeat> { - pub fn record_compile(&mut self, entry: EntryState) { + /// Make a new project lock updater. + pub fn update_lock(world: &LspWorld) -> Option<ProjectLockUpdater> { + let root = world.workspace_root()?; + Some(ProjectLockUpdater { + root, + updates: vec![], + }) + } +} + +enum LockUpdate { + Input(ProjectInput), + Task(ProjectTask), +} + +pub struct ProjectLockUpdater { + root: Arc<Path>, + updates: Vec<LockUpdate>, +} + +impl ProjectLockUpdater { + pub fn compile(&mut self, world: &LspWorld) -> Option<Id> { + let entry = world.entry_state(); log::info!("ProjectCompiler: record compile for {entry:?}"); // todo: correct root - let Some(root) = entry.workspace_root() else { - return; - }; - let Some(id) = entry - .main() - .map(|main| unix_slash(main.vpath().as_rootless_path())) - else { - return; - }; + let root = entry.workspace_root()?; + let id = unix_slash(entry.main()?.vpath().as_rootless_path()); log::info!("ProjectCompiler: record compile for id {id} at {root:?}"); - let err = tinymist_project::LockFile::update(&root, |l| { - let path = &ResourcePath::from_user_sys(Path::new(&id)); - let id: Id = path.into(); - - let root = ResourcePath::from_user_sys(Path::new(".")); - - let font_resolver = &self.wrapper.verse.font_resolver; - let font_paths = font_resolver - .font_paths() - .iter() - .map(|p| ResourcePath::from_user_sys(p)) - .collect::<Vec<_>>(); - - // let system_font = font_resolver.system_font(); - - let registry = &self.wrapper.verse.registry; - let package_path = registry - .package_path() - .map(|p| ResourcePath::from_user_sys(p)); - let package_cache_path = registry - .package_cache_path() - .map(|p| ResourcePath::from_user_sys(p)); - - // todo: freeze the package paths - let _ = package_cache_path; - let _ = package_path; - - let input = ProjectInput { - id: id.clone(), - root: Some(root), - font_paths, - system_fonts: true, // !args.font.ignore_system_fonts, - package_path: None, - package_cache_path: None, - }; - l.replace_document(input); + let path = &ResourcePath::from_user_sys(Path::new(&id)); + let id: Id = path.into(); + + let root = ResourcePath::from_user_sys(Path::new(".")); + + let font_resolver = &world.font_resolver; + let font_paths = font_resolver + .font_paths() + .iter() + .map(|p| ResourcePath::from_user_sys(p)) + .collect::<Vec<_>>(); + + // let system_font = font_resolver.system_font(); + + let registry = &world.registry; + let package_path = registry + .package_path() + .map(|p| ResourcePath::from_user_sys(p)); + let package_cache_path = registry + .package_cache_path() + .map(|p| ResourcePath::from_user_sys(p)); + + // todo: freeze the package paths + let _ = package_cache_path; + let _ = package_path; + + let input = ProjectInput { + id: id.clone(), + root: Some(root), + font_paths, + system_fonts: true, // !args.font.ignore_system_fonts, + package_path: None, + package_cache_path: None, + }; + + self.updates.push(LockUpdate::Input(input)); + + Some(id) + } + + pub fn task(&mut self, task: ProjectTask) { + self.updates.push(LockUpdate::Task(task)); + } + + pub fn commit(self) { + let err = tinymist_project::LockFile::update(&self.root, |l| { + for update in self.updates { + match update { + LockUpdate::Input(input) => { + l.replace_document(input); + } + LockUpdate::Task(task) => { + l.replace_task(task); + } + } + } Ok(()) }); diff --git a/crates/tinymist/src/actor/typ_client.rs b/crates/tinymist/src/actor/typ_client.rs index fa880a8a8..627df915c 100644 --- a/crates/tinymist/src/actor/typ_client.rs +++ b/crates/tinymist/src/actor/typ_client.rs @@ -184,10 +184,6 @@ impl LocalCompileHandler { // false todo!() } - - fn record_compile(&mut self, entry: EntryState) { - self.wrapper.record_compile(entry); - } } pub struct CompileHandler { @@ -482,7 +478,6 @@ impl CompileClientActor { let snap = self.snapshot()?; let entry = self.entry_resolver().resolve(Some(path.as_path().into())); - self.handle.record_compile(entry.clone()); let export = self.handle.export.factory.oneshot(snap, Some(entry), kind); just_future(async move { let res = export.await?; diff --git a/crates/tinymist/src/task/export.rs b/crates/tinymist/src/task/export.rs index e7725a76f..a666b13f1 100644 --- a/crates/tinymist/src/task/export.rs +++ b/crates/tinymist/src/task/export.rs @@ -5,8 +5,13 @@ use std::{path::PathBuf, sync::Arc}; use anyhow::{bail, Context}; use reflexo_typst::{EntryReader, EntryState, TaskInputs, TypstDatetime}; +use tinymist_project::{ + ExportHtmlTask, ExportMarkdownTask, ExportPdfTask, ExportPngTask, ExportTextTask, ProjectTask, + TaskWhen, +}; use tinymist_query::{ExportKind, PageSelection}; use tinymist_world::typ_server::{CompiledArtifact, ExportSignal}; +use tinymist_world::LspProjectCompiler; use tokio::sync::mpsc; use typlite::Typlite; use typst::foundations::IntoValue; @@ -77,7 +82,7 @@ impl SyncTaskFactory<ExportConfig> { }); let artifact = snap.compile(); - export.do_export(&kind, artifact).await + export.do_export(&kind, artifact, false).await } } } @@ -127,7 +132,7 @@ impl ExportConfig { let this = self.clone(); let artifact = artifact.clone(); Box::pin(async move { - log_err(this.do_export(&this.kind, artifact).await); + log_err(this.do_export(&this.kind, artifact, true).await); Some(()) }) }); @@ -167,6 +172,7 @@ impl ExportConfig { &self, kind: &ExportKind, artifact: CompiledArtifact<LspCompilerFeat>, + passive: bool, ) -> anyhow::Result<Option<PathBuf>> { use reflexo_vec2svg::DefaultExportFeature; use ExportKind::*; @@ -195,6 +201,70 @@ impl ExportConfig { } } + if !passive { + let updater = LspProjectCompiler::update_lock(&snap.world); + + let _ = updater.and_then(|mut updater| { + let doc_id = updater.compile(&snap.world)?; + let task_id = doc_id.clone(); + + let when = match self.config.mode { + ExportMode::Never => TaskWhen::Never, + ExportMode::OnType => TaskWhen::OnType, + ExportMode::OnSave => TaskWhen::OnSave, + ExportMode::OnDocumentHasTitle => TaskWhen::OnSave, + }; + + // todo: page transforms + let transforms = vec![]; + + use tinymist_project::ExportTask as ProjectExportTask; + + let export = ProjectExportTask { + document: doc_id, + id: task_id, + when, + transform: transforms, + }; + + let task = match kind { + Pdf { creation_timestamp } => { + let _ = creation_timestamp; + ProjectTask::ExportPdf(ExportPdfTask { + export, + pdf_standards: Default::default(), + }) + } + Html {} => ProjectTask::ExportHtml(ExportHtmlTask { export }), + Markdown {} => ProjectTask::ExportMarkdown(ExportMarkdownTask { export }), + Text {} => ProjectTask::ExportText(ExportTextTask { export }), + Query { .. } => { + // todo: ignoring query task. + return None; + } + Svg { page } => { + // todo: ignoring page selection. + let _ = page; + return None; + } + Png { ppi, fill, page } => { + // todo: ignoring page fill. + let _ = fill; + // todo: ignoring page selection. + let _ = page; + + let ppi = ppi.unwrap_or(144.) as f32; + ProjectTask::ExportPng(ExportPngTask { export, ppi }) + } + }; + + updater.task(task); + updater.commit(); + + Some(()) + }); + } + // Prepare the document. let doc = doc.map_err(|_| anyhow::anyhow!("no document"))?;