mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 13:13:43 +00:00
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:
parent
90484cb8e6
commit
e649ad308f
9 changed files with 711 additions and 761 deletions
|
@ -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()
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
476
crates/tinymist/src/actor/typ_client.rs
Normal file
476
crates/tinymist/src/actor/typ_client.rs
Normal 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))?
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
}
|
}
|
|
@ -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 {}
|
|
||||||
}
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
pub mod package;
|
pub mod package;
|
||||||
|
pub mod preview;
|
||||||
|
|
125
crates/tinymist/src/tools/preview.rs
Normal file
125
crates/tinymist/src/tools/preview.rs
Normal 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 {}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue