refactor: tidy up compiler server/client actors (#100)

* refactor: split construct/preview code

* refactor: clean server-client actors

* fix: infinite loop

* fix: compile error
This commit is contained in:
Myriad-Dreamin 2024-03-26 11:15:32 +08:00 committed by GitHub
parent 90484cb8e6
commit e649ad308f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 711 additions and 761 deletions

View file

@ -91,14 +91,23 @@ pub struct AnalysisContext<'a> {
impl<'w> AnalysisContext<'w> { impl<'w> AnalysisContext<'w> {
/// Create a new analysis context. /// Create a new analysis context.
pub fn new(world: &'w dyn AnaylsisResources, a: Analysis) -> Self { pub fn new(resources: &'w dyn AnaylsisResources, a: Analysis) -> Self {
Self { Self {
resources: world, resources,
analysis: CowMut::Owned(a), analysis: CowMut::Owned(a),
caches: AnalysisCaches::default(), caches: AnalysisCaches::default(),
} }
} }
/// Create a new analysis context with borrowing the analysis data.
pub fn new_borrow(resources: &'w dyn AnaylsisResources, a: &'w mut Analysis) -> Self {
Self {
resources,
analysis: CowMut::Borrowed(a),
caches: AnalysisCaches::default(),
}
}
/// Get the world surface for Typst compiler. /// Get the world surface for Typst compiler.
pub fn world(&self) -> &dyn World { pub fn world(&self) -> &dyn World {
self.resources.world() self.resources.world()

View file

@ -5,14 +5,13 @@ pub type DiagnosticsMap = HashMap<Url, Vec<LspDiagnostic>>;
/// Converts a list of Typst diagnostics to LSP diagnostics. /// Converts a list of Typst diagnostics to LSP diagnostics.
pub fn convert_diagnostics<'a>( pub fn convert_diagnostics<'a>(
project: &AnalysisContext, ctx: &AnalysisContext,
errors: impl IntoIterator<Item = &'a TypstDiagnostic>, errors: impl IntoIterator<Item = &'a TypstDiagnostic>,
position_encoding: PositionEncoding,
) -> DiagnosticsMap { ) -> DiagnosticsMap {
errors errors
.into_iter() .into_iter()
.flat_map(|error| { .flat_map(|error| {
convert_diagnostic(project, error, position_encoding) convert_diagnostic(ctx, error)
.map_err(move |conversion_err| { .map_err(move |conversion_err| {
error!("could not convert Typst error to diagnostic: {conversion_err:?} error to convert: {error:?}"); error!("could not convert Typst error to diagnostic: {conversion_err:?} error to convert: {error:?}");
}) })
@ -23,26 +22,25 @@ pub fn convert_diagnostics<'a>(
} }
fn convert_diagnostic( fn convert_diagnostic(
project: &AnalysisContext, ctx: &AnalysisContext,
typst_diagnostic: &TypstDiagnostic, typst_diagnostic: &TypstDiagnostic,
position_encoding: PositionEncoding,
) -> anyhow::Result<(Url, LspDiagnostic)> { ) -> anyhow::Result<(Url, LspDiagnostic)> {
let uri; let uri;
let lsp_range; let lsp_range;
if let Some((id, span)) = diagnostic_span_id(typst_diagnostic) { if let Some((id, span)) = diagnostic_span_id(typst_diagnostic) {
uri = Url::from_file_path(project.path_for_id(id)?).map_err(|e| { uri = Url::from_file_path(ctx.path_for_id(id)?).map_err(|e| {
let _: () = e; let _: () = e;
anyhow::anyhow!( anyhow::anyhow!(
"could not convert path to URI: id: {id:?}, context: {:?}", "could not convert path to URI: id: {id:?}, context: {:?}",
project.analysis.root ctx.analysis.root
) )
})?; })?;
let source = project.world().source(id)?; let source = ctx.world().source(id)?;
lsp_range = diagnostic_range(&source, span, position_encoding); lsp_range = diagnostic_range(&source, span, ctx.position_encoding());
} else { } else {
uri = Url::from_file_path(project.analysis.root.clone()).map_err(|e| { uri = Url::from_file_path(ctx.analysis.root.clone()).map_err(|e| {
let _: () = e; let _: () = e;
anyhow::anyhow!("could not convert path to URI: {:?}", project.analysis.root) anyhow::anyhow!("could not convert path to URI: {:?}", ctx.analysis.root)
})?; })?;
lsp_range = LspRange::default(); lsp_range = LspRange::default();
}; };
@ -53,7 +51,8 @@ fn convert_diagnostic(
let typst_hints = &typst_diagnostic.hints; let typst_hints = &typst_diagnostic.hints;
let lsp_message = format!("{typst_message}{}", diagnostic_hints(typst_hints)); let lsp_message = format!("{typst_message}{}", diagnostic_hints(typst_hints));
let tracepoints = diagnostic_related_information(project, typst_diagnostic, position_encoding)?; let tracepoints =
diagnostic_related_information(ctx, typst_diagnostic, ctx.position_encoding())?;
let diagnostic = LspDiagnostic { let diagnostic = LspDiagnostic {
range: lsp_range, range: lsp_range,

View file

@ -1,23 +1,32 @@
//! Bootstrap actors for Tinymist. //! Bootstrap actors for Tinymist.
pub mod cluster; pub mod cluster;
pub mod compile;
pub mod render; pub mod render;
pub mod typst; pub mod typ_client;
pub mod typ_server;
use ::typst::diag::FileResult;
use tokio::sync::{broadcast, watch}; use tokio::sync::{broadcast, watch};
use typst_ts_compiler::vfs::notify::FileChangeSet; use typst::{diag::FileResult, util::Deferred};
use typst_ts_compiler::{
service::CompileDriverImpl,
vfs::notify::{FileChangeSet, MemoryEvent},
};
use typst_ts_core::config::compiler::EntryState; use typst_ts_core::config::compiler::EntryState;
use self::{ use self::{
render::{PdfExportActor, PdfExportConfig}, render::{PdfExportActor, PdfExportConfig},
typst::{create_server, CompileActor}, typ_client::{CompileClientActor, CompileDriver, CompileHandler},
typ_server::CompileServerActor,
}; };
use crate::TypstLanguageServer; use crate::{
world::{LspWorld, LspWorldBuilder},
TypstLanguageServer,
};
type CompileDriverInner = CompileDriverImpl<LspWorld>;
impl TypstLanguageServer { impl TypstLanguageServer {
pub fn server(&self, name: String, entry: EntryState) -> CompileActor { pub fn server(&self, diag_group: String, entry: EntryState) -> CompileClientActor {
let (doc_tx, doc_rx) = watch::channel(None); let (doc_tx, doc_rx) = watch::channel(None);
let (render_tx, _) = broadcast::channel(10); let (render_tx, _) = broadcast::channel(10);
@ -47,16 +56,52 @@ impl TypstLanguageServer {
); );
// Create the server // Create the server
create_server( let inner = Deferred::new({
name, let current_runtime = tokio::runtime::Handle::current();
&self.config, let handler = CompileHandler {
self.const_config(), #[cfg(feature = "preview")]
self.font.clone(), inner: std::sync::Arc::new(parking_lot::Mutex::new(None)),
entry, diag_group: diag_group.clone(),
snapshot, doc_tx,
self.diag_tx.clone(), render_tx: render_tx.clone(),
doc_tx, diag_tx: self.diag_tx.clone(),
render_tx, };
)
let position_encoding = self.const_config().position_encoding;
let diag_group = diag_group.clone();
let entry = entry.clone();
let font_resolver = self.font.clone();
move || {
log::info!("TypstActor: creating server for {diag_group}");
// Create the world
let font_resolver = font_resolver.wait().clone();
let world = LspWorldBuilder::build(entry.clone(), font_resolver)
.expect("incorrect options");
// Create the compiler
let driver = CompileDriverInner::new(world);
let driver = CompileDriver {
inner: driver,
handler,
position_encoding,
};
// Create the actor
let actor = CompileServerActor::new(driver, entry).with_watch(true);
let (server, client) = actor.split();
// We do send memory changes instead of initializing compiler with them.
// This is because there are state recorded inside of the compiler actor, and we
// must update them.
client.add_memory_changes(MemoryEvent::Update(snapshot));
current_runtime.spawn(server.spawn());
client
}
});
CompileClientActor::new(diag_group, self.config.clone(), entry, inner, render_tx)
} }
} }

View file

@ -0,0 +1,476 @@
//! The typst actors running compilations.
//!
//! ```ascii
//! ┌────────────────────────────────┐
//! │ main::compile_actor (client)│
//! └─────┬────────────────────▲─────┘
//! │ │
//! ┌─────▼────────────────────┴─────┐ ┌────────────┐
//! │compiler::compile_actor (server)│◄───────►│notify_actor│
//! └─────┬────────────────────▲─────┘ └────────────┘
//! │ │
//! ┌─────▼────────────────────┴─────┐ handler ┌────────────┐
//! │compiler::compile_driver ├────────►│ rest actors│
//! └────────────────────────────────┘ └────────────┘
//! ```
//!
//! We generally use typst in two ways.
//! + creates a [`CompileDriver`] and run compilation in fly.
//! + creates a [`CompileServerActor`], wraps the drvier, and runs
//! [`CompileDriver`] incrementally.
//!
//! For latter case, an additional [`CompileClientActor`] is created to
//! control the [`CompileServerActor`].
//!
//! The [`CompileDriver`] will also keep a [`CompileHandler`] to push
//! information to other actors.
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use anyhow::anyhow;
use log::{error, info, trace};
use parking_lot::Mutex;
use tinymist_query::{
analysis::{Analysis, AnalysisContext, AnaylsisResources},
CompilerQueryRequest, CompilerQueryResponse, DiagnosticsMap, FoldRequestFeature,
OnExportRequest, OnSaveExportRequest, PositionEncoding, SemanticRequest, StatefulRequest,
VersionedDocument,
};
use tokio::sync::{broadcast, mpsc, oneshot, watch};
use typst::{
diag::{PackageError, SourceDiagnostic, SourceResult},
model::Document as TypstDocument,
syntax::package::PackageSpec,
util::Deferred,
World as TypstWorld,
};
use typst_ts_compiler::{
service::{CompileDriverImpl, CompileEnv, CompileMiddleware, Compiler, EntryManager, EnvWorld},
vfs::notify::{FileChangeSet, MemoryEvent},
Time,
};
use typst_ts_core::{
config::compiler::EntryState, error::prelude::*, typst::prelude::EcoVec, Error, ImmutPath,
};
use super::typ_server::CompileClient as TsCompileClient;
use super::{render::PdfExportConfig, typ_server::CompileServerActor};
use crate::{
actor::render::{PdfPathVars, RenderActorRequest},
utils,
};
use crate::{
actor::typ_server::EntryStateExt,
tools::preview::{CompilationHandle, CompileStatus},
};
use crate::{world::LspWorld, Config};
type CompileDriverInner = CompileDriverImpl<LspWorld>;
type CompileService = CompileServerActor<CompileDriver>;
type CompileClient = TsCompileClient<CompileService>;
type DiagnosticsSender = mpsc::UnboundedSender<(String, Option<DiagnosticsMap>)>;
pub struct CompileHandler {
pub(super) diag_group: String,
#[cfg(feature = "preview")]
pub(super) inner: Arc<Mutex<Option<typst_preview::CompilationHandleImpl>>>,
pub(super) doc_tx: watch::Sender<Option<Arc<TypstDocument>>>,
pub(super) render_tx: broadcast::Sender<RenderActorRequest>,
pub(super) diag_tx: DiagnosticsSender,
}
impl CompilationHandle for CompileHandler {
fn status(&self, _status: CompileStatus) {
#[cfg(feature = "preview")]
{
let inner = self.inner.lock();
if let Some(inner) = inner.as_ref() {
inner.status(_status);
}
}
}
fn notify_compile(&self, res: Result<Arc<TypstDocument>, CompileStatus>) {
if let Ok(doc) = res.clone() {
let _ = self.doc_tx.send(Some(doc.clone()));
// todo: is it right that ignore zero broadcast receiver?
let _ = self.render_tx.send(RenderActorRequest::OnTyped);
}
#[cfg(feature = "preview")]
{
let inner = self.inner.lock();
if let Some(inner) = inner.as_ref() {
inner.notify_compile(res);
}
}
}
}
impl CompileHandler {
fn push_diagnostics(&mut self, diagnostics: Option<DiagnosticsMap>) {
let err = self.diag_tx.send((self.diag_group.clone(), diagnostics));
if let Err(err) = err {
error!("failed to send diagnostics: {:#}", err);
}
}
}
pub struct CompileDriver {
pub(super) inner: CompileDriverInner,
#[allow(unused)]
pub(super) handler: CompileHandler,
pub(super) position_encoding: PositionEncoding,
}
impl CompileMiddleware for CompileDriver {
type Compiler = CompileDriverInner;
fn inner(&self) -> &Self::Compiler {
&self.inner
}
fn inner_mut(&mut self) -> &mut Self::Compiler {
&mut self.inner
}
fn wrap_compile(&mut self, env: &mut CompileEnv) -> SourceResult<Arc<typst::model::Document>> {
self.handler.status(CompileStatus::Compiling);
match self.inner_mut().compile(env) {
Ok(doc) => {
self.handler.notify_compile(Ok(doc.clone()));
self.notify_diagnostics(EcoVec::new());
Ok(doc)
}
Err(err) => {
self.handler
.notify_compile(Err(CompileStatus::CompileError));
self.notify_diagnostics(err);
Err(EcoVec::new())
}
}
}
}
impl CompileDriver {
fn notify_diagnostics(&mut self, diagnostics: EcoVec<SourceDiagnostic>) {
trace!("notify diagnostics: {:#?}", diagnostics);
let diagnostics =
self.run_analysis(|ctx| tinymist_query::convert_diagnostics(ctx, diagnostics.as_ref()));
match diagnostics {
Ok(diagnostics) => {
// todo: better way to remove diagnostics
// todo: check all errors in this file
let detached = self.inner.world().entry.is_inactive();
let valid = !detached;
self.handler.push_diagnostics(valid.then_some(diagnostics));
}
Err(err) => {
log::error!("TypstActor: failed to convert diagnostics: {:#}", err);
self.handler.push_diagnostics(None);
}
}
}
fn run_analysis<T>(
&mut self,
f: impl FnOnce(&mut AnalysisContext<'_>) -> T,
) -> anyhow::Result<T> {
let enc = self.position_encoding;
let w = self.inner.world_mut();
let Some(main) = w.main_id() else {
log::error!("TypstActor: main file is not set");
return Err(anyhow!("main file is not set"));
};
let Some(root) = w.entry.root() else {
log::error!("TypstActor: root is not set");
return Err(anyhow!("root 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")
})?;
struct WrapWorld<'a>(&'a mut LspWorld);
impl<'a> AnaylsisResources for WrapWorld<'a> {
fn world(&self) -> &dyn typst::World {
self.0
}
fn resolve(&self, spec: &PackageSpec) -> Result<Arc<Path>, PackageError> {
use typst_ts_compiler::package::Registry;
self.0.registry.resolve(spec)
}
fn iter_dependencies(&self, f: &mut dyn FnMut(&ImmutPath, Time)) {
use typst_ts_compiler::NotifyApi;
self.0.iter_dependencies(f)
}
}
let w = WrapWorld(w);
Ok(f(&mut AnalysisContext::new(
&w,
Analysis {
root,
position_encoding: enc,
},
)))
}
}
pub struct CompileClientActor {
diag_group: String,
config: Config,
entry: Arc<Mutex<EntryState>>,
inner: Deferred<CompileClient>,
render_tx: broadcast::Sender<RenderActorRequest>,
}
impl CompileClientActor {
pub(crate) fn new(
diag_group: String,
config: Config,
entry: EntryState,
inner: Deferred<CompileClient>,
render_tx: broadcast::Sender<RenderActorRequest>,
) -> Self {
Self {
diag_group,
config,
entry: Arc::new(Mutex::new(entry)),
inner,
render_tx,
}
}
pub fn inner(&self) -> &CompileClient {
self.inner.wait()
}
/// Steal the compiler thread and run the given function.
pub fn steal<Ret: Send + 'static>(
&self,
f: impl FnOnce(&mut CompileService) -> Ret + Send + 'static,
) -> ZResult<Ret> {
self.inner().steal(f)
}
/// Steal the compiler thread and run the given function.
pub async fn steal_async<Ret: Send + 'static>(
&self,
f: impl FnOnce(&mut CompileService, tokio::runtime::Handle) -> Ret + Send + 'static,
) -> ZResult<Ret> {
self.inner().steal_async(f).await
}
pub fn settle(&self) {
let _ = self.change_entry(None);
info!("TypstActor({}): settle requested", self.diag_group);
let res = self.inner().settle();
match res {
Ok(()) => info!("TypstActor({}): settled", self.diag_group),
Err(err) => {
error!(
"TypstActor({}): failed to settle: {:#}",
self.diag_group, err
);
}
}
}
pub fn change_entry(&self, path: Option<ImmutPath>) -> Result<(), Error> {
if path.as_deref().is_some_and(|p| !p.is_absolute()) {
return Err(error_once!("entry file must be absolute", path: path.unwrap().display()));
}
let next_entry = self.config.determine_entry(path);
// todo: more robust rollback logic
let entry = self.entry.clone();
let should_change = {
let prev_entry = entry.lock();
let should_change = next_entry != *prev_entry;
should_change.then(|| prev_entry.clone())
};
if let Some(prev) = should_change {
let next = next_entry.clone();
info!(
"the entry file of TypstActor({}) is changing to {next:?}",
self.diag_group,
);
self.render_tx
.send(RenderActorRequest::ChangeExportPath(PdfPathVars {
entry: next.clone(),
}))
.unwrap();
// todo
let res = self.steal(move |compiler| {
compiler.change_entry(next.clone());
let next_is_inactive = next.is_inactive();
let res = compiler.compiler.world_mut().mutate_entry(next);
if next_is_inactive {
info!("TypstActor: removing diag");
compiler.compiler.compiler.handler.push_diagnostics(None);
}
res.map(|_| ())
.map_err(|err| error_once!("failed to change entry", err: format!("{err:?}")))
});
let res = match res {
Ok(res) => res,
Err(res) => Err(res),
};
if res.is_err() {
self.render_tx
.send(RenderActorRequest::ChangeExportPath(PdfPathVars {
entry: prev.clone(),
}))
.unwrap();
let mut entry = entry.lock();
// todo: the rollback is actually not atomic
if *entry == next_entry {
*entry = prev;
}
return res;
}
// todo: trigger recompile
let files = FileChangeSet::new_inserts(vec![]);
self.inner().add_memory_changes(MemoryEvent::Update(files));
}
Ok(())
}
pub fn add_memory_changes(&self, event: MemoryEvent) {
self.inner.wait().add_memory_changes(event);
}
pub(crate) fn change_export_pdf(&self, config: PdfExportConfig) {
let entry = self.entry.lock().clone();
let _ = self
.render_tx
.send(RenderActorRequest::ChangeConfig(PdfExportConfig {
substitute_pattern: config.substitute_pattern,
// root: self.root.get().cloned().flatten(),
entry,
mode: config.mode,
}))
.unwrap();
}
}
impl CompileClientActor {
pub fn query(&self, query: CompilerQueryRequest) -> anyhow::Result<CompilerQueryResponse> {
use CompilerQueryRequest::*;
assert!(query.fold_feature() != FoldRequestFeature::ContextFreeUnique);
macro_rules! query_state {
($self:ident, $method:ident, $req:expr) => {{
let res = $self.steal_state(move |w, doc| $req.request(w, doc));
res.map(CompilerQueryResponse::$method)
}};
}
macro_rules! query_world {
($self:ident, $method:ident, $req:expr) => {{
let res = $self.steal_world(move |w| $req.request(w));
res.map(CompilerQueryResponse::$method)
}};
}
match query {
CompilerQueryRequest::OnExport(OnExportRequest { path }) => {
Ok(CompilerQueryResponse::OnExport(self.on_export(path)?))
}
CompilerQueryRequest::OnSaveExport(OnSaveExportRequest { path }) => {
self.on_save_export(path)?;
Ok(CompilerQueryResponse::OnSaveExport(()))
}
Hover(req) => query_state!(self, Hover, req),
GotoDefinition(req) => query_world!(self, GotoDefinition, req),
GotoDeclaration(req) => query_world!(self, GotoDeclaration, req),
References(req) => query_world!(self, References, req),
InlayHint(req) => query_world!(self, InlayHint, req),
CodeLens(req) => query_world!(self, CodeLens, req),
Completion(req) => query_state!(self, Completion, req),
SignatureHelp(req) => query_world!(self, SignatureHelp, req),
Rename(req) => query_world!(self, Rename, req),
PrepareRename(req) => query_world!(self, PrepareRename, req),
Symbol(req) => query_world!(self, Symbol, req),
FoldingRange(..)
| SelectionRange(..)
| SemanticTokensDelta(..)
| DocumentSymbol(..)
| SemanticTokensFull(..) => unreachable!(),
}
}
fn on_export(&self, path: PathBuf) -> anyhow::Result<Option<PathBuf>> {
info!("CompileActor: on export: {}", path.display());
let (tx, rx) = oneshot::channel();
let task = Arc::new(Mutex::new(Some(tx)));
self.render_tx
.send(RenderActorRequest::DoExport(task))
.map_err(map_string_err("failed to send to sync_render"))?;
let res: Option<PathBuf> = utils::threaded_receive(rx)?;
info!("CompileActor: on export end: {path:?} as {res:?}");
Ok(res)
}
fn on_save_export(&self, path: PathBuf) -> anyhow::Result<()> {
info!("CompileActor: on save export: {}", path.display());
let _ = self.render_tx.send(RenderActorRequest::OnSaved(path));
Ok(())
}
fn steal_state<T: Send + Sync + 'static>(
&self,
f: impl FnOnce(&mut AnalysisContext, Option<VersionedDocument>) -> T + Send + Sync + 'static,
) -> anyhow::Result<T> {
self.steal(move |compiler| {
let doc = compiler.success_doc();
let c = &mut compiler.compiler.compiler;
c.run_analysis(move |ctx| f(ctx, doc))
})?
}
fn steal_world<T: Send + Sync + 'static>(
&self,
f: impl FnOnce(&mut AnalysisContext) -> T + Send + Sync + 'static,
) -> anyhow::Result<T> {
self.steal(move |compiler| compiler.compiler.compiler.run_analysis(f))?
}
}

View file

@ -1,3 +1,7 @@
//! The [`CompileServerActor`] implementation borrowed from typst.ts.
//!
//! Please check `tinymist::actor::typ_client` for architecture details.
use std::{ use std::{
collections::HashSet, collections::HashSet,
num::NonZeroUsize, num::NonZeroUsize,
@ -99,7 +103,7 @@ struct SuspendState {
} }
/// The compiler thread. /// The compiler thread.
pub struct CompileActor<C: Compiler> { pub struct CompileServerActor<C: Compiler> {
/// The underlying compiler. /// The underlying compiler.
pub compiler: CompileReporter<C>, pub compiler: CompileReporter<C>,
/// Whether to enable file system watching. /// Whether to enable file system watching.
@ -128,7 +132,7 @@ pub struct CompileActor<C: Compiler> {
suspend_state: SuspendState, suspend_state: SuspendState,
} }
impl<C: Compiler + ShadowApi + Send + 'static> CompileActor<C> impl<C: Compiler + ShadowApi + Send + 'static> CompileServerActor<C>
where where
C::World: for<'files> codespan_reporting::files::Files<'files, FileId = TypstFileId>, C::World: for<'files> codespan_reporting::files::Files<'files, FileId = TypstFileId>,
{ {
@ -258,7 +262,7 @@ where
// Spawn compiler thread. // Spawn compiler thread.
let compile_thread = ensure_single_thread("typst-compiler", async move { let compile_thread = ensure_single_thread("typst-compiler", async move {
log::debug!("CompileActor: initialized"); log::debug!("CompileServerActor: initialized");
// Wait for first events. // Wait for first events.
'event_loop: while let Some(event) = tokio::select! { 'event_loop: while let Some(event) = tokio::select! {
@ -268,7 +272,7 @@ where
ExternalInterrupt::Task(task) => Some(CompilerInterrupt::Task(task)), ExternalInterrupt::Task(task) => Some(CompilerInterrupt::Task(task)),
ExternalInterrupt::Memory(task) => Some(CompilerInterrupt::Memory(task)), ExternalInterrupt::Memory(task) => Some(CompilerInterrupt::Memory(task)),
ExternalInterrupt::Settle(e) => { ExternalInterrupt::Settle(e) => {
log::info!("CompileActor: requested stop"); log::info!("CompileServerActor: requested stop");
e.send(()).ok(); e.send(()).ok();
break 'event_loop; break 'event_loop;
} }
@ -287,7 +291,7 @@ where
need_recompile = true; need_recompile = true;
} }
ExternalInterrupt::Settle(e) => { ExternalInterrupt::Settle(e) => {
log::info!("CompileActor: requested stop"); log::info!("CompileServerActor: requested stop");
e.send(()).ok(); e.send(()).ok();
break 'event_loop; break 'event_loop;
} }
@ -324,7 +328,7 @@ where
} }
settle_notify(); settle_notify();
log::info!("CompileActor: exited"); log::info!("CompileServerActor: exited");
}) })
.unwrap(); .unwrap();
@ -370,7 +374,7 @@ where
let evict_start = std::time::Instant::now(); let evict_start = std::time::Instant::now();
comemo::evict(30); comemo::evict(30);
log::info!( log::info!(
"CompileActor: evict compilation cache in {:?}", "CompileServerActor: evict compilation cache in {:?}",
evict_start.elapsed() evict_start.elapsed()
); );
@ -397,7 +401,7 @@ where
// //
// See [`CompileClient::steal`] for more information. // See [`CompileClient::steal`] for more information.
CompilerInterrupt::Task(task) => { CompilerInterrupt::Task(task) => {
log::debug!("CompileActor: execute task"); log::debug!("CompileServerActor: execute task");
task(self); task(self);
@ -406,7 +410,7 @@ where
} }
// Handle memory events. // Handle memory events.
CompilerInterrupt::Memory(event) => { CompilerInterrupt::Memory(event) => {
log::debug!("CompileActor: memory event incoming"); log::debug!("CompileServerActor: memory event incoming");
// Emulate memory changes. // Emulate memory changes.
let mut files = HashSet::new(); let mut files = HashSet::new();
@ -453,13 +457,13 @@ where
} }
// Handle file system events. // Handle file system events.
CompilerInterrupt::Fs(event) => { CompilerInterrupt::Fs(event) => {
log::debug!("CompileActor: fs event incoming {:?}", event); log::debug!("CompileServerActor: fs event incoming {:?}", event);
// Handle file system event if any. // Handle file system event if any.
if let Some(mut event) = event { if let Some(mut event) = event {
// Handle delayed upstream update event before applying file system changes // Handle delayed upstream update event before applying file system changes
if self.apply_delayed_memory_changes(&mut event).is_none() { if self.apply_delayed_memory_changes(&mut event).is_none() {
log::warn!("CompileActor: unknown upstream update event"); log::warn!("CompileServerActor: unknown upstream update event");
} }
// Apply file system changes. // Apply file system changes.
@ -508,7 +512,7 @@ where
Ok(content) => content, Ok(content) => content,
Err(err) => { Err(err) => {
log::error!( log::error!(
"CompileActor: read memory file at {}: {}", "CompileServerActor: read memory file at {}: {}",
p.display(), p.display(),
err, err,
); );
@ -523,7 +527,7 @@ where
} }
} }
impl<C: Compiler> CompileActor<C> { impl<C: Compiler> CompileServerActor<C> {
pub fn with_watch(mut self, enable_watch: bool) -> Self { pub fn with_watch(mut self, enable_watch: bool) -> Self {
self.enable_watch = enable_watch; self.enable_watch = enable_watch;
self self
@ -630,7 +634,8 @@ pub struct DocToSrcJumpInfo {
} }
// todo: remove constraint to CompilerWorld // todo: remove constraint to CompilerWorld
impl<F: CompilerFeat, Ctx: Compiler<World = CompilerWorld<F>>> CompileClient<CompileActor<Ctx>> impl<F: CompilerFeat, Ctx: Compiler<World = CompilerWorld<F>>>
CompileClient<CompileServerActor<Ctx>>
where where
Ctx::World: EntryManager, Ctx::World: EntryManager,
{ {
@ -811,6 +816,6 @@ fn find_in_frame(frame: &Frame, span: Span, min_dis: &mut u64, p: &mut Point) ->
#[inline] #[inline]
fn log_send_error<T>(chan: &'static str, res: Result<(), mpsc::error::SendError<T>>) -> bool { fn log_send_error<T>(chan: &'static str, res: Result<(), mpsc::error::SendError<T>>) -> bool {
res.map_err(|err| log::warn!("CompileActor: send to {chan} error: {err}")) res.map_err(|err| log::warn!("CompileServerActor: send to {chan} error: {err}"))
.is_ok() .is_ok()
} }

View file

@ -1,710 +0,0 @@
//! The typst actors running compilations.
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use anyhow::anyhow;
use log::{error, info, trace};
use parking_lot::Mutex;
use tinymist_query::{
analysis::{Analysis, AnalysisContext, AnaylsisResources},
CompilerQueryRequest, CompilerQueryResponse, DiagnosticsMap, FoldRequestFeature,
OnExportRequest, OnSaveExportRequest, PositionEncoding, SemanticRequest, StatefulRequest,
VersionedDocument,
};
use tokio::sync::{broadcast, mpsc, oneshot, watch};
use typst::{
diag::{SourceDiagnostic, SourceResult},
util::Deferred,
};
#[cfg(feature = "preview")]
use typst_preview::{CompilationHandle, CompilationHandleImpl, CompileStatus};
use typst_ts_compiler::{
service::{CompileDriverImpl, CompileEnv, CompileMiddleware, Compiler, EntryManager, EnvWorld},
vfs::notify::{FileChangeSet, MemoryEvent},
};
use typst_ts_core::{
config::compiler::EntryState, error::prelude::*, typst::prelude::EcoVec, Error, ImmutPath,
TypstDocument, TypstWorld,
};
use super::compile::CompileClient as TsCompileClient;
use super::{compile::CompileActor as CompileActorInner, render::PdfExportConfig};
use crate::{actor::compile::EntryStateExt, ConstConfig};
use crate::{
actor::render::{PdfPathVars, RenderActorRequest},
utils,
};
use crate::{
world::{LspWorld, LspWorldBuilder, SharedFontResolver},
Config,
};
#[cfg(not(feature = "preview"))]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "kind", content = "data")]
pub enum CompileStatus {
Compiling,
CompileSuccess,
CompileError,
}
#[cfg(not(feature = "preview"))]
pub trait CompilationHandle: Send + 'static {
fn status(&self, status: CompileStatus);
fn notify_compile(&self, res: Result<Arc<TypstDocument>, CompileStatus>);
}
type CompileDriverInner = CompileDriverImpl<LspWorld>;
type CompileService = CompileActorInner<CompileDriver>;
type CompileClient = TsCompileClient<CompileService>;
type DiagnosticsSender = mpsc::UnboundedSender<(String, Option<DiagnosticsMap>)>;
#[allow(clippy::too_many_arguments)]
pub fn create_server(
diag_group: String,
config: &Config,
cfg: &ConstConfig,
// opts: OptsState,
font_resolver: Deferred<SharedFontResolver>,
entry: EntryState,
snapshot: FileChangeSet,
diag_tx: DiagnosticsSender,
doc_sender: watch::Sender<Option<Arc<TypstDocument>>>,
render_tx: broadcast::Sender<RenderActorRequest>,
) -> CompileActor {
let pos_encoding = cfg.position_encoding;
let inner = Deferred::new({
let current_runtime = tokio::runtime::Handle::current();
let handler = CompileHandler {
#[cfg(feature = "preview")]
inner: Arc::new(Mutex::new(None)),
};
let diag_group = diag_group.clone();
let entry = entry.clone();
let render_tx = render_tx.clone();
move || {
info!("TypstActor: creating server for {diag_group}");
let font_resolver = font_resolver.wait().clone();
let world =
LspWorldBuilder::build(entry.clone(), font_resolver).expect("incorrect options");
let driver = CompileDriverInner::new(world);
let driver = CompileDriver {
inner: driver,
handler,
doc_sender,
render_tx: render_tx.clone(),
diag_group: diag_group.clone(),
position_encoding: pos_encoding,
diag_tx,
};
let actor = CompileActorInner::new(driver, entry).with_watch(true);
let (server, client) = actor.split();
// We do send memory changes instead of initializing compiler with them.
// This is because there are state recorded inside of the compiler actor, and we
// must update them.
client.add_memory_changes(MemoryEvent::Update(snapshot));
current_runtime.spawn(server.spawn());
client
}
});
CompileActor::new(
diag_group,
config.clone(),
entry,
pos_encoding,
inner,
render_tx,
)
}
macro_rules! query_state {
($self:ident, $method:ident, $req:expr) => {{
let res = $self.steal_state(move |w, doc| $req.request(w, doc));
res.map(CompilerQueryResponse::$method)
}};
}
macro_rules! query_world {
($self:ident, $method:ident, $req:expr) => {{
let res = $self.steal_world(move |w| $req.request(w));
res.map(CompilerQueryResponse::$method)
}};
}
#[derive(Clone)]
pub struct CompileHandler {
#[cfg(feature = "preview")]
inner: Arc<Mutex<Option<CompilationHandleImpl>>>,
}
impl CompilationHandle for CompileHandler {
fn status(&self, _status: CompileStatus) {
#[cfg(feature = "preview")]
{
let inner = self.inner.lock();
if let Some(inner) = inner.as_ref() {
inner.status(_status);
}
}
}
fn notify_compile(&self, _result: Result<Arc<TypstDocument>, CompileStatus>) {
#[cfg(feature = "preview")]
{
let inner = self.inner.lock();
if let Some(inner) = inner.as_ref() {
inner.notify_compile(_result.clone());
}
}
}
}
pub struct CompileDriver {
inner: CompileDriverInner,
#[allow(unused)]
handler: CompileHandler,
doc_sender: watch::Sender<Option<Arc<TypstDocument>>>,
render_tx: broadcast::Sender<RenderActorRequest>,
diag_group: String,
position_encoding: PositionEncoding,
diag_tx: DiagnosticsSender,
}
impl CompileMiddleware for CompileDriver {
type Compiler = CompileDriverInner;
fn inner(&self) -> &Self::Compiler {
&self.inner
}
fn inner_mut(&mut self) -> &mut Self::Compiler {
&mut self.inner
}
fn wrap_compile(&mut self, env: &mut CompileEnv) -> SourceResult<Arc<typst::model::Document>> {
#[cfg(feature = "preview")]
self.handler.status(CompileStatus::Compiling);
match self.inner_mut().compile(env) {
Ok(doc) => {
#[cfg(feature = "preview")]
self.handler.notify_compile(Ok(doc.clone()));
let _ = self.doc_sender.send(Some(doc.clone()));
// todo: is it right that ignore zero broadcast receiver?
let _ = self.render_tx.send(RenderActorRequest::OnTyped);
self.notify_diagnostics(EcoVec::new());
Ok(doc)
}
Err(err) => {
#[cfg(feature = "preview")]
self.handler
.notify_compile(Err(CompileStatus::CompileError));
self.notify_diagnostics(err);
Err(EcoVec::new())
}
}
}
}
impl CompileDriver {
fn push_diagnostics(&mut self, diagnostics: Option<DiagnosticsMap>) {
let err = self.diag_tx.send((self.diag_group.clone(), diagnostics));
if let Err(err) = err {
error!("failed to send diagnostics: {:#}", err);
}
}
fn notify_diagnostics(&mut self, diagnostics: EcoVec<SourceDiagnostic>) {
trace!("notify diagnostics: {:#?}", diagnostics);
// todo encoding
let w = self.inner.world_mut();
// todo: root
let root = w.entry.root().clone().unwrap();
let diagnostics = tinymist_query::convert_diagnostics(
&AnalysisContext::new(
&WrapWorld(w),
Analysis {
root,
position_encoding: self.position_encoding,
},
),
diagnostics.as_ref(),
self.position_encoding,
);
// todo: better way to remove diagnostics
// todo: check all errors in this file
let detached = self.inner.world().entry.is_inactive();
let valid = !detached;
self.push_diagnostics(valid.then_some(diagnostics));
}
}
pub struct CompileActor {
diag_group: String,
position_encoding: PositionEncoding,
config: Config,
// root_tx: Mutex<Option<oneshot::Sender<Option<ImmutPath>>>>,
// root: OnceCell<Option<ImmutPath>>,
entry: Arc<Mutex<EntryState>>,
inner: Deferred<CompileClient>,
render_tx: broadcast::Sender<RenderActorRequest>,
}
impl CompileActor {
#[allow(clippy::too_many_arguments)]
fn new(
diag_group: String,
config: Config,
entry: EntryState,
position_encoding: PositionEncoding,
inner: Deferred<CompileClient>,
render_tx: broadcast::Sender<RenderActorRequest>,
) -> Self {
Self {
diag_group,
config,
// root_tx: Mutex::new(root.is_none().then_some(root_tx)),
// root: match root {
// Some(root) => OnceCell::from(Some(root)),
// None => OnceCell::new(),
// },
position_encoding,
entry: Arc::new(Mutex::new(entry)),
inner,
render_tx,
}
}
fn inner(&self) -> &CompileClient {
self.inner.wait()
}
/// Steal the compiler thread and run the given function.
pub fn steal<Ret: Send + 'static>(
&self,
f: impl FnOnce(&mut CompileService) -> Ret + Send + 'static,
) -> ZResult<Ret> {
self.inner().steal(f)
}
/// Steal the compiler thread and run the given function.
pub async fn steal_async<Ret: Send + 'static>(
&self,
f: impl FnOnce(&mut CompileService, tokio::runtime::Handle) -> Ret + Send + 'static,
) -> ZResult<Ret> {
self.inner().steal_async(f).await
}
pub fn settle(&self) {
let _ = self.change_entry(None);
info!("TypstActor({}): settle requested", self.diag_group);
let res = self.inner().settle();
match res {
Ok(()) => info!("TypstActor({}): settled", self.diag_group),
Err(err) => {
error!(
"TypstActor({}): failed to settle: {:#}",
self.diag_group, err
);
}
}
}
pub fn change_entry(&self, path: Option<ImmutPath>) -> Result<(), Error> {
if path.as_deref().is_some_and(|p| !p.is_absolute()) {
return Err(error_once!("entry file must be absolute", path: path.unwrap().display()));
}
let next_entry = self.config.determine_entry(path);
// todo: more robust rollback logic
let entry = self.entry.clone();
let should_change = {
let prev_entry = entry.lock();
let should_change = next_entry != *prev_entry;
should_change.then(|| prev_entry.clone())
};
if let Some(prev) = should_change {
let next = next_entry.clone();
info!(
"the entry file of TypstActor({}) is changing to {next:?}",
self.diag_group,
);
self.render_tx
.send(RenderActorRequest::ChangeExportPath(PdfPathVars {
entry: next.clone(),
}))
.unwrap();
// todo
let res = self.steal(move |compiler| {
compiler.change_entry(next.clone());
let next_is_inactive = next.is_inactive();
let res = compiler.compiler.world_mut().mutate_entry(next);
if next_is_inactive {
info!("TypstActor: removing diag");
compiler.compiler.compiler.push_diagnostics(None);
}
res.map(|_| ())
.map_err(|err| error_once!("failed to change entry", err: format!("{err:?}")))
});
let res = match res {
Ok(res) => res,
Err(res) => Err(res),
};
if res.is_err() {
self.render_tx
.send(RenderActorRequest::ChangeExportPath(PdfPathVars {
entry: prev.clone(),
}))
.unwrap();
let mut entry = entry.lock();
// todo: the rollback is actually not atomic
if *entry == next_entry {
*entry = prev;
}
return res;
}
// todo: trigger recompile
let files = FileChangeSet::new_inserts(vec![]);
self.inner().add_memory_changes(MemoryEvent::Update(files));
}
Ok(())
}
pub fn add_memory_changes(&self, event: MemoryEvent) {
self.inner.wait().add_memory_changes(event);
}
pub(crate) fn change_export_pdf(&self, config: PdfExportConfig) {
let entry = self.entry.lock().clone();
let _ = self
.render_tx
.send(RenderActorRequest::ChangeConfig(PdfExportConfig {
substitute_pattern: config.substitute_pattern,
// root: self.root.get().cloned().flatten(),
entry,
mode: config.mode,
}))
.unwrap();
}
}
struct WrapWorld<'a>(&'a mut LspWorld);
impl<'a> AnaylsisResources for WrapWorld<'a> {
fn world(&self) -> &dyn typst::World {
self.0
}
fn resolve(
&self,
spec: &typst_ts_core::package::PackageSpec,
) -> Result<Arc<Path>, typst::diag::PackageError> {
use typst_ts_compiler::package::Registry;
self.0.registry.resolve(spec)
}
fn iter_dependencies(&self, f: &mut dyn FnMut(&ImmutPath, typst_ts_compiler::Time)) {
use typst_ts_compiler::NotifyApi;
self.0.iter_dependencies(f)
}
}
impl CompileActor {
pub fn query(&self, query: CompilerQueryRequest) -> anyhow::Result<CompilerQueryResponse> {
use CompilerQueryRequest::*;
assert!(query.fold_feature() != FoldRequestFeature::ContextFreeUnique);
match query {
CompilerQueryRequest::OnExport(OnExportRequest { path }) => {
Ok(CompilerQueryResponse::OnExport(self.on_export(path)?))
}
CompilerQueryRequest::OnSaveExport(OnSaveExportRequest { path }) => {
self.on_save_export(path)?;
Ok(CompilerQueryResponse::OnSaveExport(()))
}
Hover(req) => query_state!(self, Hover, req),
GotoDefinition(req) => query_world!(self, GotoDefinition, req),
GotoDeclaration(req) => query_world!(self, GotoDeclaration, req),
References(req) => query_world!(self, References, req),
InlayHint(req) => query_world!(self, InlayHint, req),
CodeLens(req) => query_world!(self, CodeLens, req),
Completion(req) => query_state!(self, Completion, req),
SignatureHelp(req) => query_world!(self, SignatureHelp, req),
Rename(req) => query_world!(self, Rename, req),
PrepareRename(req) => query_world!(self, PrepareRename, req),
Symbol(req) => query_world!(self, Symbol, req),
FoldingRange(..)
| SelectionRange(..)
| SemanticTokensDelta(..)
| DocumentSymbol(..)
| SemanticTokensFull(..) => unreachable!(),
}
}
fn on_export(&self, path: PathBuf) -> anyhow::Result<Option<PathBuf>> {
info!("CompileActor: on export: {}", path.display());
let (tx, rx) = oneshot::channel();
let task = Arc::new(Mutex::new(Some(tx)));
self.render_tx
.send(RenderActorRequest::DoExport(task))
.map_err(map_string_err("failed to send to sync_render"))?;
let res: Option<PathBuf> = utils::threaded_receive(rx)?;
info!("CompileActor: on export end: {path:?} as {res:?}");
Ok(res)
}
fn on_save_export(&self, path: PathBuf) -> anyhow::Result<()> {
info!("CompileActor: on save export: {}", path.display());
let _ = self.render_tx.send(RenderActorRequest::OnSaved(path));
Ok(())
}
fn steal_state<T: Send + Sync + 'static>(
&self,
f: impl FnOnce(&mut AnalysisContext, Option<VersionedDocument>) -> T + Send + Sync + 'static,
) -> anyhow::Result<T> {
let enc = self.position_encoding;
self.steal(move |compiler| {
let doc = compiler.success_doc();
let w = compiler.compiler.world_mut();
let Some(main) = w.main_id() else {
log::error!("TypstActor: main file is not set");
return Err(anyhow!("main file is not set"));
};
let Some(root) = w.entry.root() else {
log::error!("TypstActor: root is not set");
return Err(anyhow!("root 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")
})?;
let w = WrapWorld(w);
Ok(f(
&mut AnalysisContext::new(
&w,
Analysis {
root,
position_encoding: enc,
},
),
doc,
))
})?
}
fn steal_world<T: Send + Sync + 'static>(
&self,
f: impl FnOnce(&mut AnalysisContext) -> T + Send + Sync + 'static,
) -> anyhow::Result<T> {
let enc = self.position_encoding;
// let opts = match opts {
// OptsState::Exact(opts) => opts,
// OptsState::Rootless(opts) => {
// let root: ImmutPath = match utils::threaded_receive(root_rx) {
// Ok(Some(root)) => root,
// Ok(None) => {
// error!("TypstActor: failed to receive root path: root is
// none"); return CompileClient::faked();
// }
// Err(err) => {
// error!("TypstActor: failed to receive root path: {:#}", err);
// return CompileClient::faked();
// }
// };
// opts(root.as_ref().into())
// }
// };
// mut opts: CompileOnceOpts,
// let inputs = std::mem::take(&mut opts.inputs);
// w.set_inputs(Arc::new(Prehashed::new(inputs)));
self.steal(move |compiler| {
let w = compiler.compiler.world_mut();
let Some(main) = w.main_id() else {
log::error!("TypstActor: main file is not set");
return Err(anyhow!("main file is not set"));
};
let Some(root) = w.entry.root() else {
log::error!("TypstActor: root is not set");
return Err(anyhow!("root 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")
})?;
let w = WrapWorld(w);
Ok(f(&mut AnalysisContext::new(
&w,
Analysis {
root,
position_encoding: enc,
},
)))
})?
}
}
#[cfg(feature = "preview")]
mod preview_exts {
use std::path::Path;
use typst::layout::Position;
use typst::syntax::Span;
use typst_preview::{
CompileHost, DocToSrcJumpInfo, EditorServer, Location, MemoryFiles, MemoryFilesShort,
SourceFileServer,
};
use typst_ts_compiler::vfs::notify::FileChangeSet;
use typst_ts_compiler::vfs::notify::MemoryEvent;
use typst_ts_core::debug_loc::SourceSpanOffset;
use typst_ts_core::Error;
use super::CompileActor;
#[cfg(feature = "preview")]
impl SourceFileServer for CompileActor {
async fn resolve_source_span(
&mut self,
loc: Location,
) -> Result<Option<SourceSpanOffset>, Error> {
let Location::Src(src_loc) = loc;
self.inner().resolve_src_location(src_loc).await
}
async fn resolve_document_position(
&mut self,
loc: Location,
) -> Result<Option<Position>, 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)
.await
}
async fn resolve_source_location(
&mut self,
s: Span,
offset: Option<usize>,
) -> Result<Option<DocToSrcJumpInfo>, Error> {
Ok(self
.inner()
.resolve_span_and_offset(s, offset)
.await
.map_err(|err| {
log::error!("TypstActor: failed to resolve span and offset: {:#}", err);
})
.ok()
.flatten()
.map(|e| DocToSrcJumpInfo {
filepath: e.filepath,
start: e.start,
end: e.end,
}))
}
}
#[cfg(feature = "preview")]
impl EditorServer for CompileActor {
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>
(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(())
}
}
#[cfg(feature = "preview")]
impl CompileHost for CompileActor {}
}

View file

@ -42,7 +42,7 @@ use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
use std::{collections::HashMap, path::PathBuf}; use std::{collections::HashMap, path::PathBuf};
use actor::typst::CompileActor; use actor::typ_client::CompileClientActor;
use anyhow::Context; use anyhow::Context;
use crossbeam_channel::select; use crossbeam_channel::select;
use crossbeam_channel::Receiver; use crossbeam_channel::Receiver;
@ -324,9 +324,9 @@ pub struct TypstLanguageServer {
diag_tx: mpsc::UnboundedSender<(String, Option<DiagnosticsMap>)>, diag_tx: mpsc::UnboundedSender<(String, Option<DiagnosticsMap>)>,
memory_changes: HashMap<Arc<Path>, MemoryFileMeta>, memory_changes: HashMap<Arc<Path>, MemoryFileMeta>,
primary: Option<CompileActor>, primary: Option<CompileClientActor>,
pinning: bool, pinning: bool,
main: Option<CompileActor>, main: Option<CompileClientActor>,
font: Deferred<SharedFontResolver>, font: Deferred<SharedFontResolver>,
tokens_ctx: SemanticTokenContext, tokens_ctx: SemanticTokenContext,
} }
@ -369,7 +369,7 @@ impl TypstLanguageServer {
&self.const_config &self.const_config
} }
fn primary(&self) -> &CompileActor { fn primary(&self) -> &CompileClientActor {
self.primary.as_ref().expect("primary") self.primary.as_ref().expect("primary")
} }

View file

@ -1 +1,2 @@
pub mod package; pub mod package;
pub mod preview;

View file

@ -0,0 +1,125 @@
#[cfg(feature = "preview")]
pub use typst_preview::CompileStatus;
#[cfg(not(feature = "preview"))]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "kind", content = "data")]
pub enum CompileStatus {
Compiling,
CompileSuccess,
CompileError,
}
#[cfg(feature = "preview")]
pub use typst_preview::CompilationHandle;
#[cfg(not(feature = "preview"))]
pub trait CompilationHandle: Send + 'static {
fn status(&self, status: CompileStatus);
fn notify_compile(
&self,
res: Result<std::sync::Arc<typst_ts_core::TypstDocument>, CompileStatus>,
);
}
#[cfg(feature = "preview")]
mod preview_exts {
use std::path::Path;
use typst::layout::Position;
use typst::syntax::Span;
use typst_preview::{
CompileHost, DocToSrcJumpInfo, EditorServer, Location, MemoryFiles, MemoryFilesShort,
SourceFileServer,
};
use typst_ts_compiler::vfs::notify::FileChangeSet;
use typst_ts_compiler::vfs::notify::MemoryEvent;
use typst_ts_core::debug_loc::SourceSpanOffset;
use typst_ts_core::Error;
use crate::actor::typ_client::CompileClientActor;
impl SourceFileServer for CompileClientActor {
async fn resolve_source_span(
&mut self,
loc: Location,
) -> Result<Option<SourceSpanOffset>, Error> {
let Location::Src(src_loc) = loc;
self.inner().resolve_src_location(src_loc).await
}
async fn resolve_document_position(
&mut self,
loc: Location,
) -> Result<Option<Position>, 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)
.await
}
async fn resolve_source_location(
&mut self,
s: Span,
offset: Option<usize>,
) -> Result<Option<DocToSrcJumpInfo>, Error> {
Ok(self
.inner()
.resolve_span_and_offset(s, offset)
.await
.map_err(|err| {
log::error!("TypstActor: failed to resolve span and offset: {:#}", err);
})
.ok()
.flatten()
.map(|e| DocToSrcJumpInfo {
filepath: e.filepath,
start: e.start,
end: e.end,
}))
}
}
impl EditorServer for CompileClientActor {
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>
(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 CompileClientActor {}
}