This commit is contained in:
Hong Jiarong 2025-12-16 19:35:13 +08:00 committed by GitHub
commit 6d398cdea9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 384 additions and 162 deletions

View file

@ -10,14 +10,24 @@ pub struct CheckRequest {
pub snap: LspCompiledArtifact,
}
/// The diagnostics emitted by a full check run.
#[derive(Debug, Clone, Default)]
pub struct DiagnosticsResult {
/// Diagnostics reported by the compiler.
pub compiler: DiagnosticsMap,
/// Diagnostics reported by lint passes.
pub lint: DiagnosticsMap,
}
impl SemanticRequest for CheckRequest {
type Response = DiagnosticsMap;
type Response = DiagnosticsResult;
fn request(self, ctx: &mut LocalContext) -> Option<Self::Response> {
let worker = DiagWorker::new(ctx);
let compiler_diags = self.snap.diagnostics();
let compiler_diags: Vec<_> = self.snap.diagnostics().cloned().collect();
let known_issues = KnownIssues::from_compiler_diagnostics(compiler_diags.iter());
let lint = DiagWorker::new(ctx).check(&known_issues).results;
let compiler = DiagWorker::new(ctx).convert_all(compiler_diags.iter());
let known_issues = KnownIssues::from_compiler_diagnostics(compiler_diags.clone());
Some(worker.check(&known_issues).convert_all(compiler_diags))
Some(DiagnosticsResult { compiler, lint })
}
}

View file

@ -25,13 +25,34 @@ pub struct EditorActorConfig {
pub enum EditorRequest {
Config(EditorActorConfig),
/// Publishes diagnostics to the editor.
Diag(ProjVersion, Option<DiagnosticsMap>),
Diag(ProjVersion, DiagKind, Option<DiagnosticsMap>),
/// Updates compile status to the editor.
Status(CompileReport),
/// Updastes words count status to the editor.
WordCount(ProjectInsId, WordsCount),
}
/// The kind of diagnostics published to the editor.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DiagKind {
/// Diagnostics reported by the Typst compiler.
Compiler,
/// Diagnostics reported by Tinymist's lint engine.
Lint,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct DiagKey {
pub project: ProjectInsId,
pub kind: DiagKind,
}
impl DiagKey {
fn new(project: ProjectInsId, kind: DiagKind) -> Self {
Self { project, kind }
}
}
/// The actor maintaining output to the editor, including diagnostics and
/// compile status.
pub struct EditorActor {
@ -44,11 +65,11 @@ pub struct EditorActor {
/// Accumulated diagnostics per file.
/// The outer `HashMap` is indexed by the file's URL.
/// The inner `HashMap` is indexed by the project ID, allowing multiple
/// projects publishing diagnostics to the same file independently.
diagnostics: HashMap<Url, HashMap<ProjectInsId, EcoVec<Diagnostic>>>,
/// The map from project ID to the affected files.
affect_map: HashMap<ProjectInsId, Vec<Url>>,
/// The inner `HashMap` is indexed by the `(project, kind)` pair, allowing
/// multiple sources publishing diagnostics to the same file independently.
diagnostics: HashMap<Url, HashMap<DiagKey, EcoVec<Diagnostic>>>,
/// The map from `(project, kind)` to the affected files.
affect_map: HashMap<DiagKey, Vec<Url>>,
/// The local state.
status: StatusAll,
@ -100,13 +121,13 @@ impl EditorActor {
log::info!("received config request: {config:?}");
self.config = config;
}
EditorRequest::Diag(version, diagnostics) => {
EditorRequest::Diag(version, kind, diagnostics) => {
log::debug!(
"received diagnostics from {version:?}: diag({:?})",
diagnostics.as_ref().map(|files| files.len())
);
self.publish(version.id, diagnostics);
self.publish(version, kind, diagnostics);
}
EditorRequest::Status(compile_status) => {
log::trace!("received status request: {compile_status:?}");
@ -137,12 +158,18 @@ impl EditorActor {
}
/// Publishes diagnostics of a project to the editor.
pub fn publish(&mut self, id: ProjectInsId, next_diag: Option<DiagnosticsMap>) {
pub fn publish(
&mut self,
version: ProjVersion,
kind: DiagKind,
next_diag: Option<DiagnosticsMap>,
) {
let key = DiagKey::new(version.id.clone(), kind);
let affected = match next_diag.as_ref() {
Some(next_diag) => self
.affect_map
.insert(id.clone(), next_diag.keys().cloned().collect()),
None => self.affect_map.remove(&id),
.insert(key.clone(), next_diag.keys().cloned().collect()),
None => self.affect_map.remove(&key),
};
// Gets sources which had some diagnostic published last time, but not this
@ -155,24 +182,24 @@ impl EditorActor {
// Gets sources that affected by this group in last round but not this time
for uri in affected.into_iter().flatten() {
if !next_diag.as_ref().is_some_and(|e| e.contains_key(&uri)) {
self.publish_file(&id, uri, None)
self.publish_file(&key, uri, None)
}
}
// Gets touched updates
for (uri, next) in next_diag.into_iter().flatten() {
self.publish_file(&id, uri, Some(next))
self.publish_file(&key, uri, Some(next))
}
}
/// Publishes diagnostics of a file to the editor.
fn publish_file(&mut self, id: &ProjectInsId, uri: Url, next: Option<EcoVec<Diagnostic>>) {
fn publish_file(&mut self, key: &DiagKey, uri: Url, next: Option<EcoVec<Diagnostic>>) {
let mut diagnostics = EcoVec::new();
// Gets the diagnostics from other groups
let path_diags = self.diagnostics.entry(uri.clone()).or_default();
for (existing_id, diags) in path_diags.iter() {
if existing_id != id {
for (existing_key, diags) in path_diags.iter() {
if existing_key != key {
diagnostics.push(diags.clone());
}
}
@ -184,8 +211,8 @@ impl EditorActor {
// Updates the diagnostics for this group
match next {
Some(next) => path_diags.insert(id.clone(), next),
None => path_diags.remove(id),
Some(next) => path_diags.insert(key.clone(), next),
None => path_diags.remove(key),
};
// Publishes the diagnostics

View file

@ -38,7 +38,7 @@ use tokio::sync::mpsc;
use typst::{diag::FileResult, foundations::Bytes, layout::Position as TypstPosition};
use super::ServerState;
use crate::actor::editor::{EditorRequest, ProjVersion};
use crate::actor::editor::{DiagKind, EditorRequest, ProjVersion};
use crate::stats::{CompilerQueryStats, QueryStatGuard};
#[cfg(feature = "export")]
use crate::task::ExportUserConfig;
@ -159,45 +159,54 @@ impl ServerState {
config.export(),
);
#[cfg(feature = "preview")]
let preview_state = preview.clone();
// Create the compile handler for client consuming results.
let periscope_args = config.periscope_args.clone();
let handle = Arc::new(CompileHandlerImpl {
#[cfg(feature = "preview")]
preview,
is_standalone: false,
#[cfg(feature = "export")]
export: export.clone(),
editor_tx: editor_tx.clone(),
client: Arc::new(client.clone().to_untyped()),
analysis: Arc::new(Analysis {
position_encoding: const_config.position_encoding,
allow_overlapping_token: const_config.tokens_overlapping_token_support,
allow_multiline_token: const_config.tokens_multiline_token_support,
remove_html: !config.support_html_in_markdown,
support_client_codelens: true,
extended_code_action: config.extended_code_action,
completion_feat: config.completion.clone(),
color_theme: match config.color_theme.as_deref() {
Some("dark") => tinymist_query::ColorTheme::Dark,
_ => tinymist_query::ColorTheme::Light,
},
lint: config.lint.when().clone(),
periscope: periscope_args.map(|args| {
let r = TypstPeriscopeProvider(PeriscopeRenderer::new(args));
Arc::new(r) as Arc<dyn PeriscopeProvider + Send + Sync>
}),
local_packages: Arc::default(),
tokens_caches: Arc::default(),
workers: Default::default(),
caches: Default::default(),
analysis_rev_cache: Arc::default(),
stats: Arc::default(),
let analysis = Arc::new(Analysis {
position_encoding: const_config.position_encoding,
allow_overlapping_token: const_config.tokens_overlapping_token_support,
allow_multiline_token: const_config.tokens_multiline_token_support,
remove_html: !config.support_html_in_markdown,
support_client_codelens: true,
extended_code_action: config.extended_code_action,
completion_feat: config.completion.clone(),
color_theme: match config.color_theme.as_deref() {
Some("dark") => tinymist_query::ColorTheme::Dark,
_ => tinymist_query::ColorTheme::Light,
},
lint: config.lint.when().clone(),
periscope: periscope_args.map(|args| {
let r = TypstPeriscopeProvider(PeriscopeRenderer::new(args));
Arc::new(r) as Arc<dyn PeriscopeProvider + Send + Sync>
}),
status_revision: Mutex::default(),
notified_revision: Mutex::default(),
local_packages: Arc::default(),
tokens_caches: Arc::default(),
workers: Default::default(),
caches: Default::default(),
analysis_rev_cache: Arc::default(),
stats: Arc::default(),
});
#[allow(unused_mut)]
let mut hooks: Vec<Box<dyn CompileHook + Send + Sync>> = vec![
Box::new(DiagHook::new(analysis.clone(), editor_tx.clone())),
Box::new(LintHook::new(analysis.clone(), editor_tx.clone())),
];
#[cfg(feature = "preview")]
hooks.push(Box::new(PreviewHook::new(preview)));
#[cfg(feature = "export")]
hooks.push(Box::new(ExportHook::new(export.clone())));
let handle = CompileHandlerImpl::new(
analysis.clone(),
editor_tx.clone(),
Arc::new(client.clone().to_untyped()),
false,
hooks,
);
let export_target = config.export_target;
let default_path = config.entry_resolver.resolve_default();
let entry = config.entry_resolver.resolve(default_path);
@ -245,11 +254,11 @@ impl ServerState {
ProjectState {
compiler,
#[cfg(feature = "preview")]
preview: handle.preview.clone(),
preview: preview_state,
analysis: handle.analysis.clone(),
stats: CompilerQueryStats::default(),
#[cfg(feature = "export")]
export: handle.export.clone(),
export,
}
}
}
@ -448,19 +457,234 @@ impl ProjectPreviewState {
}
}
fn push_editor_diagnostics(
editor_tx: &EditorSender,
dv: ProjVersion,
kind: DiagKind,
diagnostics: Option<DiagnosticsMap>,
) {
editor_tx
.send(EditorRequest::Diag(dv, kind, diagnostics))
.log_error("failed to send diagnostics");
}
/// A hook that handles diagnostics.
pub struct DiagHook {
analysis: Arc<Analysis>,
editor_tx: EditorSender,
}
impl DiagHook {
/// Creates a new diagnostics hook.
pub fn new(analysis: Arc<Analysis>, editor_tx: EditorSender) -> Self {
Self {
analysis,
editor_tx,
}
}
fn notify(&self, dv: ProjVersion, art: &LspCompiledArtifact) {
let enc = self.analysis.position_encoding;
let diagnostics =
tinymist_query::convert_diagnostics(art.graph.clone(), art.diagnostics(), enc);
log::trace!(
"notify compiler diagnostics({:?}): {:#?}",
dv.id,
diagnostics
);
push_editor_diagnostics(
&self.editor_tx,
dv.clone(),
DiagKind::Compiler,
Some(diagnostics),
);
}
}
/// A hook that handles compilation events.
pub trait CompileHook {
/// Notifies the hook of a compilation result.
fn notify(&self, dv: ProjVersion, art: &LspCompiledArtifact, client: &Arc<dyn ProjectClient>);
/// Notifies the hook of a compilation status.
fn status(&self, _revision: usize, _rep: &CompileReport) {}
}
impl CompileHook for DiagHook {
fn notify(&self, dv: ProjVersion, art: &LspCompiledArtifact, _client: &Arc<dyn ProjectClient>) {
if art.world().entry_state().is_inactive() {
push_editor_diagnostics(&self.editor_tx, dv.clone(), DiagKind::Compiler, None);
return;
}
self.notify(dv, art);
}
}
/// A hook that handles linting.
pub struct LintHook {
analysis: Arc<Analysis>,
editor_tx: EditorSender,
}
impl LintHook {
/// Creates a new lint hook.
pub fn new(analysis: Arc<Analysis>, editor_tx: EditorSender) -> Self {
Self {
analysis,
editor_tx,
}
}
fn notify(&self, dv: ProjVersion, art: &LspCompiledArtifact) {
let should_lint = art
.snap
.signal
.should_run_task_dyn(&self.analysis.lint, art.doc.as_ref())
.unwrap_or_default();
log::debug!(
"Project: should_lint: {should_lint:?}, signal: {:?}",
art.snap.signal
);
if !should_lint {
return;
}
let snap = art.clone();
let editor_tx = self.editor_tx.clone();
let analysis = self.analysis.clone();
spawn_cpu(move || {
let mut ctx = analysis.enter(snap.graph.clone());
// todo: check all errors in this file
let Some(diagnostics) = CheckRequest { snap }.request(&mut ctx) else {
return;
};
log::trace!(
"notify lint diagnostics({:?}): {:#?}",
dv.id,
diagnostics.lint
);
editor_tx
.send(EditorRequest::Diag(
dv,
DiagKind::Lint,
Some(diagnostics.lint),
))
.log_error("failed to send lint diagnostics");
});
}
}
impl CompileHook for LintHook {
fn notify(&self, dv: ProjVersion, art: &LspCompiledArtifact, _client: &Arc<dyn ProjectClient>) {
if art.world().entry_state().is_inactive() {
push_editor_diagnostics(&self.editor_tx, dv.clone(), DiagKind::Lint, None);
return;
}
self.notify(dv, art);
}
}
#[cfg(feature = "preview")]
#[derive(Clone)]
/// A hook that handles preview.
pub struct PreviewHook {
state: ProjectPreviewState,
}
#[cfg(feature = "preview")]
impl PreviewHook {
/// Creates a new preview hook.
pub fn new(state: ProjectPreviewState) -> Self {
Self { state }
}
fn notify(&self, art: &LspCompiledArtifact) {
if let Some(inner) = self.state.get(art.id()) {
let art = art.clone();
inner.notify_compile(Arc::new(crate::tool::preview::PreviewCompileView { art }));
} else {
log::debug!("Project: no preview for {:?}", art.id());
}
}
fn status(&self, _revision: usize, rep: &CompileReport) {
if let Some(inner) = self.state.get(&rep.id) {
use tinymist_preview::CompileStatus;
use tinymist_project::CompileStatusEnum::*;
inner.status(match &rep.status {
Compiling => CompileStatus::Compiling,
Suspend | CompileSuccess { .. } => CompileStatus::CompileSuccess,
ExportError { .. } | CompileError { .. } => CompileStatus::CompileError,
});
}
}
#[allow(dead_code)]
fn state(&self) -> ProjectPreviewState {
self.state.clone()
}
}
#[cfg(feature = "preview")]
impl CompileHook for PreviewHook {
fn notify(
&self,
_dv: ProjVersion,
art: &LspCompiledArtifact,
_client: &Arc<dyn ProjectClient>,
) {
self.notify(art);
}
fn status(&self, revision: usize, rep: &CompileReport) {
self.status(revision, rep);
}
}
#[cfg(feature = "export")]
#[derive(Clone)]
/// A hook that handles export.
pub struct ExportHook {
task: crate::task::ExportTask,
}
#[cfg(feature = "export")]
impl ExportHook {
/// Creates a new export hook.
pub fn new(task: crate::task::ExportTask) -> Self {
Self { task }
}
#[allow(dead_code)]
fn task(&self) -> crate::task::ExportTask {
self.task.clone()
}
}
#[cfg(feature = "export")]
impl CompileHook for ExportHook {
fn notify(&self, _dv: ProjVersion, art: &LspCompiledArtifact, client: &Arc<dyn ProjectClient>) {
self.task.signal(art, client);
}
}
/// The implementation of the compile handler.
pub struct CompileHandlerImpl {
/// The analysis data.
pub(crate) analysis: Arc<Analysis>,
hooks: Vec<Box<dyn CompileHook + Send + Sync>>,
#[cfg(feature = "preview")]
pub(crate) preview: ProjectPreviewState,
/// Whether the compile server is running in standalone CLI (not as a
/// language server).
pub is_standalone: bool,
/// The export task.
#[cfg(feature = "export")]
pub(crate) export: crate::task::ExportTask,
/// The editor sender, used to send editor requests to the editor.
pub(crate) editor_tx: EditorSender,
/// The client used to send events back to the server itself or the clients.
@ -519,11 +743,22 @@ impl ProjectClient for mpsc::UnboundedSender<LspInterrupt> {
}
impl CompileHandlerImpl {
/// Pushes diagnostics to the editor.
fn push_diagnostics(&self, dv: ProjVersion, diagnostics: Option<DiagnosticsMap>) {
self.editor_tx
.send(EditorRequest::Diag(dv, diagnostics))
.log_error("failed to send diagnostics");
pub(crate) fn new(
analysis: Arc<Analysis>,
editor_tx: EditorSender,
client: Arc<dyn ProjectClient>,
is_standalone: bool,
hooks: Vec<Box<dyn CompileHook + Send + Sync>>,
) -> Arc<Self> {
Arc::new(Self {
analysis,
is_standalone,
editor_tx,
client,
status_revision: Mutex::default(),
notified_revision: Mutex::default(),
hooks,
})
}
/// Notifies the diagnostics.
@ -532,51 +767,9 @@ impl CompileHandlerImpl {
id: art.id().clone(),
revision: art.world().revision().get(),
};
// todo: better way to remove diagnostics
let valid = !art.world().entry_state().is_inactive();
if !valid {
self.push_diagnostics(dv, None);
return;
}
let should_lint = art
.snap
.signal
.should_run_task_dyn(&self.analysis.lint, art.doc.as_ref())
.unwrap_or_default();
log::debug!(
"Project: should_lint: {should_lint:?}, signal: {:?}",
art.snap.signal
);
if !should_lint {
let enc = self.analysis.position_encoding;
let diagnostics =
tinymist_query::convert_diagnostics(art.graph.clone(), art.diagnostics(), enc);
log::trace!("notify diagnostics({dv:?}): {diagnostics:#?}");
self.editor_tx
.send(EditorRequest::Diag(dv, Some(diagnostics)))
.log_error("failed to send diagnostics");
} else {
let snap = art.clone();
let editor_tx = self.editor_tx.clone();
let analysis = self.analysis.clone();
spawn_cpu(move || {
let mut ctx = analysis.enter(snap.graph.clone());
// todo: check all errors in this file
let Some(diagnostics) = CheckRequest { snap }.request(&mut ctx) else {
return;
};
log::trace!("notify diagnostics({dv:?}): {diagnostics:#?}");
editor_tx
.send(EditorRequest::Diag(dv, Some(diagnostics)))
.log_error("failed to send diagnostics");
});
for hook in &self.hooks {
hook.notify(dv.clone(), art, &self.client);
}
}
}
@ -691,19 +884,13 @@ impl CompileHandler<LspCompilerFeat, ProjectInsStateExt> for CompileHandlerImpl
id: rep.id.clone(),
revision,
};
self.push_diagnostics(dv, None);
push_editor_diagnostics(&self.editor_tx, dv.clone(), DiagKind::Compiler, None);
push_editor_diagnostics(&self.editor_tx, dv, DiagKind::Lint, None);
}
#[cfg(feature = "preview")]
if let Some(inner) = self.preview.get(&rep.id) {
use tinymist_preview::CompileStatus;
use tinymist_project::CompileStatusEnum::*;
inner.status(match &rep.status {
Compiling => CompileStatus::Compiling,
Suspend | CompileSuccess { .. } => CompileStatus::CompileSuccess,
ExportError { .. } | CompileError { .. } => CompileStatus::CompileError,
});
for hook in &self.hooks {
hook.status(revision, &rep);
}
self.editor_tx.send(EditorRequest::Status(rep)).unwrap();
@ -720,7 +907,8 @@ impl CompileHandler<LspCompilerFeat, ProjectInsStateExt> for CompileHandlerImpl
// todo: race condition with notify_compile?
// remove diagnostics
self.push_diagnostics(dv, None);
push_editor_diagnostics(&self.editor_tx, dv.clone(), DiagKind::Compiler, None);
push_editor_diagnostics(&self.editor_tx, dv, DiagKind::Lint, None);
}
fn notify_compile(&self, art: &LspCompiledArtifact) {
@ -770,17 +958,6 @@ impl CompileHandler<LspCompilerFeat, ProjectInsStateExt> for CompileHandlerImpl
.log_error("failed to print diagnostics");
}
#[cfg(feature = "export")]
self.export.signal(art, &self.client);
#[cfg(feature = "preview")]
if let Some(inner) = self.preview.get(art.id()) {
let art = art.clone();
inner.notify_compile(Arc::new(crate::tool::preview::PreviewCompileView { art }));
} else {
log::debug!("Project: no preview for {:?}", art.id());
}
self.notify_diagnostics(art);
}
}

View file

@ -16,7 +16,7 @@ use tinymist_std::path::PathClean;
use tinymist_std::typst::TypstDocument;
use tinymist_task::{
output_template, DocumentQuery, ExportMarkdownTask, ExportPngTask, ExportSvgTask, ExportTarget,
ImageOutput, PdfExport, PngExport, SvgExport, TextExport,
ExportTask as ProjectExportTask, ImageOutput, PdfExport, PngExport, SvgExport, TextExport,
};
use tokio::sync::mpsc;
use typlite::{Format, Typlite};
@ -27,16 +27,16 @@ use parking_lot::Mutex;
use rayon::Scope;
use super::SyncTaskFactory;
use crate::actor::editor::EditorRequest;
use crate::lsp::query::QueryFuture;
use crate::project::{
update_lock, ApplyProjectTask, CompiledArtifact, DevEvent, DevExportEvent, EntryReader,
ExportHtmlTask, ExportPdfTask, ExportTask as ProjectExportTask, ExportTeXTask, ExportTextTask,
LspCompiledArtifact, LspComputeGraph, ProjectClient, ProjectTask, TaskWhen,
PROJECT_ROUTE_USER_ACTION_PRIORITY,
ExportHtmlTask, ExportPdfTask, ExportTeXTask, ExportTextTask, LspCompiledArtifact,
LspComputeGraph, ProjectClient, ProjectTask, TaskWhen, PROJECT_ROUTE_USER_ACTION_PRIORITY,
};
use crate::tool::word_count;
use crate::world::TaskInputs;
use crate::ServerState;
use crate::{actor::editor::EditorRequest, tool::word_count};
impl ServerState {
/// Exports the current document.
@ -159,15 +159,11 @@ impl ExportTask {
self.factory.mutate(|data| *data = config);
}
pub(crate) fn signal(
&self,
snap: &LspCompiledArtifact,
client: &std::sync::Arc<dyn ProjectClient + 'static>,
) {
pub(crate) fn signal(&self, art: &LspCompiledArtifact, client: &Arc<dyn ProjectClient>) {
let config = self.factory.task();
self.signal_export(snap, &config, client);
self.signal_count_word(snap, &config);
self.signal_export(art, &config, client);
self.signal_count_word(art, &config);
}
fn signal_export(

View file

@ -2,7 +2,6 @@
use std::sync::Arc;
use parking_lot::Mutex;
use tinymist_query::analysis::Analysis;
use tokio::sync::mpsc;
@ -76,20 +75,33 @@ where
log::warn!("Project: system watcher is not enabled, file changes will not be watched");
}
// Create the actor
let compile_handle = Arc::new(CompileHandlerImpl {
#[cfg(feature = "preview")]
preview: opts.preview,
is_standalone: true,
#[cfg(feature = "export")]
export: crate::task::ExportTask::new(handle, Some(editor_tx.clone()), opts.config.export()),
editor_tx,
client: Arc::new(intr_tx.clone()),
let analysis = opts.analysis.clone();
analysis: opts.analysis,
status_revision: Mutex::default(),
notified_revision: Mutex::default(),
});
#[cfg(feature = "preview")]
let preview = opts.preview;
#[cfg(feature = "export")]
let export_task =
crate::task::ExportTask::new(handle, Some(editor_tx.clone()), opts.config.export());
#[allow(unused_mut)]
let mut hooks: Vec<Box<dyn CompileHook + Send + Sync>> = vec![
Box::new(DiagHook::new(analysis.clone(), editor_tx.clone())),
Box::new(LintHook::new(analysis.clone(), editor_tx.clone())),
];
#[cfg(feature = "preview")]
hooks.push(Box::new(PreviewHook::new(preview)));
#[cfg(feature = "export")]
hooks.push(Box::new(ExportHook::new(export_task)));
// Create the actor
let compile_handle = CompileHandlerImpl::new(
analysis,
editor_tx.clone(),
Arc::new(intr_tx.clone()),
true,
hooks,
);
let mut compiler = ProjectCompiler::new(
verse,