From 3e507d93051f0737809cae13918fe432b5313bf9 Mon Sep 17 00:00:00 2001 From: Hong Jiarong Date: Tue, 9 Dec 2025 18:07:13 +0800 Subject: [PATCH 1/8] feat: enhance diagnostics handling by introducing DiagKind and separating compiler/lint diagnostics --- crates/tinymist-query/src/check.rs | 20 ++++++--- crates/tinymist/src/actor/editor.rs | 63 ++++++++++++++++++++--------- crates/tinymist/src/project.rs | 52 ++++++++++++++++++------ 3 files changed, 100 insertions(+), 35 deletions(-) diff --git a/crates/tinymist-query/src/check.rs b/crates/tinymist-query/src/check.rs index 12c7b26f5..bc07edb57 100644 --- a/crates/tinymist-query/src/check.rs +++ b/crates/tinymist-query/src/check.rs @@ -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 { - 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 }) } } diff --git a/crates/tinymist/src/actor/editor.rs b/crates/tinymist/src/actor/editor.rs index 845416332..aca7acb1b 100644 --- a/crates/tinymist/src/actor/editor.rs +++ b/crates/tinymist/src/actor/editor.rs @@ -25,13 +25,34 @@ pub struct EditorActorConfig { pub enum EditorRequest { Config(EditorActorConfig), /// Publishes diagnostics to the editor. - Diag(ProjVersion, Option), + Diag(ProjVersion, DiagKind, Option), /// 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>>, - /// The map from project ID to the affected files. - affect_map: HashMap>, + /// The inner `HashMap` is indexed by the `(project, kind)` pair, allowing + /// multiple sources publishing diagnostics to the same file independently. + diagnostics: HashMap>>, + /// The map from `(project, kind)` to the affected files. + affect_map: HashMap>, /// 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) { + pub fn publish( + &mut self, + version: ProjVersion, + kind: DiagKind, + next_diag: Option, + ) { + 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>) { + fn publish_file(&mut self, key: &DiagKey, uri: Url, next: Option>) { 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 diff --git a/crates/tinymist/src/project.rs b/crates/tinymist/src/project.rs index 2eded4db2..9e8cefeee 100644 --- a/crates/tinymist/src/project.rs +++ b/crates/tinymist/src/project.rs @@ -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; @@ -520,9 +520,14 @@ impl ProjectClient for mpsc::UnboundedSender { impl CompileHandlerImpl { /// Pushes diagnostics to the editor. - fn push_diagnostics(&self, dv: ProjVersion, diagnostics: Option) { + fn push_diagnostics( + &self, + dv: ProjVersion, + kind: DiagKind, + diagnostics: Option, + ) { self.editor_tx - .send(EditorRequest::Diag(dv, diagnostics)) + .send(EditorRequest::Diag(dv, kind, diagnostics)) .log_error("failed to send diagnostics"); } @@ -535,7 +540,8 @@ impl CompileHandlerImpl { // todo: better way to remove diagnostics let valid = !art.world().entry_state().is_inactive(); if !valid { - self.push_diagnostics(dv, None); + self.push_diagnostics(dv.clone(), DiagKind::Compiler, None); + self.push_diagnostics(dv, DiagKind::Lint, None); return; } @@ -556,13 +562,12 @@ impl CompileHandlerImpl { log::trace!("notify diagnostics({dv:?}): {diagnostics:#?}"); - self.editor_tx - .send(EditorRequest::Diag(dv, Some(diagnostics))) - .log_error("failed to send diagnostics"); + self.push_diagnostics(dv, DiagKind::Compiler, Some(diagnostics)); } else { let snap = art.clone(); let editor_tx = self.editor_tx.clone(); let analysis = self.analysis.clone(); + let dv_clone = dv; spawn_cpu(move || { let mut ctx = analysis.enter(snap.graph.clone()); @@ -571,11 +576,32 @@ impl CompileHandlerImpl { return; }; - log::trace!("notify diagnostics({dv:?}): {diagnostics:#?}"); + log::trace!( + "notify lint diagnostics({:?}): {:#?}", + dv_clone.id, + diagnostics.lint + ); + log::trace!( + "notify compiler diagnostics({:?}): {:#?}", + dv_clone.id, + diagnostics.compiler + ); + let lint_version = dv_clone.clone(); editor_tx - .send(EditorRequest::Diag(dv, Some(diagnostics))) - .log_error("failed to send diagnostics"); + .send(EditorRequest::Diag( + lint_version, + DiagKind::Lint, + Some(diagnostics.lint), + )) + .log_error("failed to send lint diagnostics"); + editor_tx + .send(EditorRequest::Diag( + dv_clone, + DiagKind::Compiler, + Some(diagnostics.compiler), + )) + .log_error("failed to send compiler diagnostics"); }); } } @@ -691,7 +717,8 @@ impl CompileHandler for CompileHandlerImpl id: rep.id.clone(), revision, }; - self.push_diagnostics(dv, None); + self.push_diagnostics(dv.clone(), DiagKind::Compiler, None); + self.push_diagnostics(dv, DiagKind::Lint, None); } #[cfg(feature = "preview")] @@ -720,7 +747,8 @@ impl CompileHandler for CompileHandlerImpl // todo: race condition with notify_compile? // remove diagnostics - self.push_diagnostics(dv, None); + self.push_diagnostics(dv.clone(), DiagKind::Compiler, None); + self.push_diagnostics(dv, DiagKind::Lint, None); } fn notify_compile(&self, art: &LspCompiledArtifact) { From df5f56b4d22a9c2c686e124919ffacd0aa39f83f Mon Sep 17 00:00:00 2001 From: Hong Jiarong Date: Tue, 9 Dec 2025 19:55:50 +0800 Subject: [PATCH 2/8] feat: add clear_diagnostics method to CompileHandlerImpl for better diagnostic management --- crates/tinymist/src/project.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/tinymist/src/project.rs b/crates/tinymist/src/project.rs index 9e8cefeee..52ea61acc 100644 --- a/crates/tinymist/src/project.rs +++ b/crates/tinymist/src/project.rs @@ -519,6 +519,11 @@ impl ProjectClient for mpsc::UnboundedSender { } impl CompileHandlerImpl { + /// Clears both compiler and lint diagnostics for the given project version. + fn clear_diagnostics(&self, dv: ProjVersion) { + self.push_diagnostics(dv.clone(), DiagKind::Compiler, None); + self.push_diagnostics(dv, DiagKind::Lint, None); + } /// Pushes diagnostics to the editor. fn push_diagnostics( &self, @@ -540,8 +545,7 @@ impl CompileHandlerImpl { // todo: better way to remove diagnostics let valid = !art.world().entry_state().is_inactive(); if !valid { - self.push_diagnostics(dv.clone(), DiagKind::Compiler, None); - self.push_diagnostics(dv, DiagKind::Lint, None); + self.clear_diagnostics(dv); return; } @@ -717,8 +721,7 @@ impl CompileHandler for CompileHandlerImpl id: rep.id.clone(), revision, }; - self.push_diagnostics(dv.clone(), DiagKind::Compiler, None); - self.push_diagnostics(dv, DiagKind::Lint, None); + self.clear_diagnostics(dv); } #[cfg(feature = "preview")] @@ -747,8 +750,7 @@ impl CompileHandler for CompileHandlerImpl // todo: race condition with notify_compile? // remove diagnostics - self.push_diagnostics(dv.clone(), DiagKind::Compiler, None); - self.push_diagnostics(dv, DiagKind::Lint, None); + self.clear_diagnostics(dv); } fn notify_compile(&self, art: &LspCompiledArtifact) { From ca4d702beb4f5fdc45821a208b0691a50cb007dc Mon Sep 17 00:00:00 2001 From: Hong Jiarong Date: Tue, 9 Dec 2025 20:46:01 +0800 Subject: [PATCH 3/8] refactor: add hook scaffolding --- crates/tinymist/src/project.rs | 301 +++++++++++++++++++++------------ 1 file changed, 192 insertions(+), 109 deletions(-) diff --git a/crates/tinymist/src/project.rs b/crates/tinymist/src/project.rs index 52ea61acc..1fb4ec144 100644 --- a/crates/tinymist/src/project.rs +++ b/crates/tinymist/src/project.rs @@ -161,38 +161,49 @@ impl ServerState { // Create the compile handler for client consuming results. let periscope_args = config.periscope_args.clone(); + 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 + }), + local_packages: Arc::default(), + tokens_caches: Arc::default(), + workers: Default::default(), + caches: Default::default(), + analysis_rev_cache: Arc::default(), + stats: Arc::default(), + }); + + let lint_hook = LintHook::new(analysis.clone(), editor_tx.clone()); + + #[cfg(feature = "preview")] + let preview_hook = PreviewHook::new(preview); + + #[cfg(feature = "export")] + let export_hook = ExportHook::new(export.clone()); + let handle = Arc::new(CompileHandlerImpl { + analysis, + lint_hook, #[cfg(feature = "preview")] - preview, + preview_hook, is_standalone: false, #[cfg(feature = "export")] - export: export.clone(), + export_hook, 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 - }), - local_packages: Arc::default(), - tokens_caches: Arc::default(), - workers: Default::default(), - caches: Default::default(), - analysis_rev_cache: Arc::default(), - stats: Arc::default(), - }), status_revision: Mutex::default(), notified_revision: Mutex::default(), @@ -245,11 +256,11 @@ impl ServerState { ProjectState { compiler, #[cfg(feature = "preview")] - preview: handle.preview.clone(), + preview: handle.preview_state(), analysis: handle.analysis.clone(), stats: CompilerQueryStats::default(), #[cfg(feature = "export")] - export: handle.export.clone(), + export: handle.export_task(), } } } @@ -448,19 +459,155 @@ impl ProjectPreviewState { } } +fn push_editor_diagnostics( + editor_tx: &EditorSender, + dv: ProjVersion, + kind: DiagKind, + diagnostics: Option, +) { + editor_tx + .send(EditorRequest::Diag(dv, kind, diagnostics)) + .log_error("failed to send diagnostics"); +} + +struct LintHook { + analysis: Arc, + editor_tx: EditorSender, +} + +impl LintHook { + fn new(analysis: Arc, 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 { + let enc = self.analysis.position_encoding; + let diagnostics = + tinymist_query::convert_diagnostics(art.graph.clone(), art.diagnostics(), enc); + + log::trace!("notify diagnostics({dv:?}): {diagnostics:#?}"); + + push_editor_diagnostics(&self.editor_tx, dv, DiagKind::Compiler, Some(diagnostics)); + return; + } + + let snap = art.clone(); + let editor_tx = self.editor_tx.clone(); + let analysis = self.analysis.clone(); + let dv_clone = dv; + 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_clone.id, + diagnostics.lint + ); + log::trace!( + "notify compiler diagnostics({:?}): {:#?}", + dv_clone.id, + diagnostics.compiler + ); + + let lint_version = dv_clone.clone(); + editor_tx + .send(EditorRequest::Diag( + lint_version, + DiagKind::Lint, + Some(diagnostics.lint), + )) + .log_error("failed to send lint diagnostics"); + editor_tx + .send(EditorRequest::Diag( + dv_clone, + DiagKind::Compiler, + Some(diagnostics.compiler), + )) + .log_error("failed to send compiler diagnostics"); + }); + } +} + +#[cfg(feature = "preview")] +#[derive(Clone)] +struct PreviewHook { + state: ProjectPreviewState, +} + +#[cfg(feature = "preview")] +impl PreviewHook { + 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 state(&self) -> ProjectPreviewState { + self.state.clone() + } +} + +#[cfg(feature = "export")] +#[derive(Clone)] +struct ExportHook { + task: crate::task::ExportTask, +} + +#[cfg(feature = "export")] +impl ExportHook { + fn new(task: crate::task::ExportTask) -> Self { + Self { task } + } + + fn notify(&self, art: &LspCompiledArtifact, client: &Arc) { + self.task.signal(art, client); + } + + fn task(&self) -> crate::task::ExportTask { + self.task.clone() + } +} + /// The implementation of the compile handler. pub struct CompileHandlerImpl { /// The analysis data. pub(crate) analysis: Arc, + lint_hook: LintHook, #[cfg(feature = "preview")] - pub(crate) preview: ProjectPreviewState, + preview_hook: PreviewHook, /// 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, + export_hook: ExportHook, /// 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,21 +666,20 @@ impl ProjectClient for mpsc::UnboundedSender { } impl CompileHandlerImpl { + #[cfg(feature = "preview")] + fn preview_state(&self) -> ProjectPreviewState { + self.preview_hook.state() + } + + #[cfg(feature = "export")] + fn export_task(&self) -> crate::task::ExportTask { + self.export_hook.task() + } + /// Clears both compiler and lint diagnostics for the given project version. fn clear_diagnostics(&self, dv: ProjVersion) { - self.push_diagnostics(dv.clone(), DiagKind::Compiler, None); - self.push_diagnostics(dv, DiagKind::Lint, None); - } - /// Pushes diagnostics to the editor. - fn push_diagnostics( - &self, - dv: ProjVersion, - kind: DiagKind, - diagnostics: Option, - ) { - self.editor_tx - .send(EditorRequest::Diag(dv, kind, diagnostics)) - .log_error("failed to send diagnostics"); + push_editor_diagnostics(&self.editor_tx, dv.clone(), DiagKind::Compiler, None); + push_editor_diagnostics(&self.editor_tx, dv, DiagKind::Lint, None); } /// Notifies the diagnostics. @@ -549,65 +695,7 @@ impl CompileHandlerImpl { 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.push_diagnostics(dv, DiagKind::Compiler, Some(diagnostics)); - } else { - let snap = art.clone(); - let editor_tx = self.editor_tx.clone(); - let analysis = self.analysis.clone(); - let dv_clone = dv; - 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_clone.id, - diagnostics.lint - ); - log::trace!( - "notify compiler diagnostics({:?}): {:#?}", - dv_clone.id, - diagnostics.compiler - ); - - let lint_version = dv_clone.clone(); - editor_tx - .send(EditorRequest::Diag( - lint_version, - DiagKind::Lint, - Some(diagnostics.lint), - )) - .log_error("failed to send lint diagnostics"); - editor_tx - .send(EditorRequest::Diag( - dv_clone, - DiagKind::Compiler, - Some(diagnostics.compiler), - )) - .log_error("failed to send compiler diagnostics"); - }); - } + self.lint_hook.notify(dv, art); } } @@ -801,15 +889,10 @@ impl CompileHandler for CompileHandlerImpl } #[cfg(feature = "export")] - self.export.signal(art, &self.client); + self.export_hook.notify(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.preview_hook.notify(art); self.notify_diagnostics(art); } From 9d7f139ff853600d260e43a41e07babc6f6afcdf Mon Sep 17 00:00:00 2001 From: Hong Jiarong Date: Tue, 9 Dec 2025 21:05:36 +0800 Subject: [PATCH 4/8] feat: split lint hook from compiler diagnostics --- crates/tinymist/src/project.rs | 96 +++++++++++++++++------------ crates/tinymist/src/tool/project.rs | 33 +++++----- 2 files changed, 74 insertions(+), 55 deletions(-) diff --git a/crates/tinymist/src/project.rs b/crates/tinymist/src/project.rs index 1fb4ec144..e833e0482 100644 --- a/crates/tinymist/src/project.rs +++ b/crates/tinymist/src/project.rs @@ -185,29 +185,16 @@ impl ServerState { analysis_rev_cache: Arc::default(), stats: Arc::default(), }); - - let lint_hook = LintHook::new(analysis.clone(), editor_tx.clone()); - - #[cfg(feature = "preview")] - let preview_hook = PreviewHook::new(preview); - - #[cfg(feature = "export")] - let export_hook = ExportHook::new(export.clone()); - - let handle = Arc::new(CompileHandlerImpl { - analysis, - lint_hook, + let handle = CompileHandlerImpl::new( + analysis.clone(), + editor_tx.clone(), + Arc::new(client.clone().to_untyped()), + false, #[cfg(feature = "preview")] - preview_hook, - is_standalone: false, + preview, #[cfg(feature = "export")] - export_hook, - editor_tx: editor_tx.clone(), - client: Arc::new(client.clone().to_untyped()), - - status_revision: Mutex::default(), - notified_revision: Mutex::default(), - }); + export.clone(), + ); let export_target = config.export_target; let default_path = config.entry_resolver.resolve_default(); @@ -484,6 +471,23 @@ impl LintHook { } 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), + ); + let should_lint = art .snap .signal @@ -495,13 +499,6 @@ impl LintHook { ); 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:#?}"); - - push_editor_diagnostics(&self.editor_tx, dv, DiagKind::Compiler, Some(diagnostics)); return; } @@ -522,11 +519,6 @@ impl LintHook { dv_clone.id, diagnostics.lint ); - log::trace!( - "notify compiler diagnostics({:?}): {:#?}", - dv_clone.id, - diagnostics.compiler - ); let lint_version = dv_clone.clone(); editor_tx @@ -536,13 +528,6 @@ impl LintHook { Some(diagnostics.lint), )) .log_error("failed to send lint diagnostics"); - editor_tx - .send(EditorRequest::Diag( - dv_clone, - DiagKind::Compiler, - Some(diagnostics.compiler), - )) - .log_error("failed to send compiler diagnostics"); }); } } @@ -666,6 +651,35 @@ impl ProjectClient for mpsc::UnboundedSender { } impl CompileHandlerImpl { + pub(crate) fn new( + analysis: Arc, + editor_tx: EditorSender, + client: Arc, + is_standalone: bool, + #[cfg(feature = "preview")] preview: ProjectPreviewState, + #[cfg(feature = "export")] export: crate::task::ExportTask, + ) -> Arc { + let lint_hook = LintHook::new(analysis.clone(), editor_tx.clone()); + #[cfg(feature = "preview")] + let preview_hook = PreviewHook::new(preview); + #[cfg(feature = "export")] + let export_hook = ExportHook::new(export); + + Arc::new(Self { + analysis, + lint_hook, + #[cfg(feature = "preview")] + preview_hook, + is_standalone, + #[cfg(feature = "export")] + export_hook, + editor_tx, + client, + status_revision: Mutex::default(), + notified_revision: Mutex::default(), + }) + } + #[cfg(feature = "preview")] fn preview_state(&self) -> ProjectPreviewState { self.preview_hook.state() @@ -813,7 +827,7 @@ impl CompileHandler for CompileHandlerImpl } #[cfg(feature = "preview")] - if let Some(inner) = self.preview.get(&rep.id) { + if let Some(inner) = self.preview_state().get(&rep.id) { use tinymist_preview::CompileStatus; use tinymist_project::CompileStatusEnum::*; diff --git a/crates/tinymist/src/tool/project.rs b/crates/tinymist/src/tool/project.rs index fab2b69ab..a339de7d5 100644 --- a/crates/tinymist/src/tool/project.rs +++ b/crates/tinymist/src/tool/project.rs @@ -2,7 +2,6 @@ use std::sync::Arc; -use parking_lot::Mutex; use tinymist_query::analysis::Analysis; use tokio::sync::mpsc; @@ -76,20 +75,26 @@ 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()); + + // Create the actor + let compile_handle = CompileHandlerImpl::new( + analysis, + editor_tx.clone(), + Arc::new(intr_tx.clone()), + true, + #[cfg(feature = "preview")] + preview, + #[cfg(feature = "export")] + export_task, + ); let mut compiler = ProjectCompiler::new( verse, From 912eec62b2225e8509519b8f6f62d5afcbd3557c Mon Sep 17 00:00:00 2001 From: Hong Jiarong Date: Wed, 10 Dec 2025 16:06:36 +0800 Subject: [PATCH 5/8] feat: Separate diagnostic handling into a dedicated `DiagHook` for compiler diagnostics and a new `LintHook` for lint-specific diagnostics. --- crates/tinymist/src/project.rs | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/crates/tinymist/src/project.rs b/crates/tinymist/src/project.rs index e833e0482..98f7c753c 100644 --- a/crates/tinymist/src/project.rs +++ b/crates/tinymist/src/project.rs @@ -457,12 +457,12 @@ fn push_editor_diagnostics( .log_error("failed to send diagnostics"); } -struct LintHook { +struct DiagHook { analysis: Arc, editor_tx: EditorSender, } -impl LintHook { +impl DiagHook { fn new(analysis: Arc, editor_tx: EditorSender) -> Self { Self { analysis, @@ -487,7 +487,23 @@ impl LintHook { DiagKind::Compiler, Some(diagnostics), ); + } +} +struct LintHook { + analysis: Arc, + editor_tx: EditorSender, +} + +impl LintHook { + fn new(analysis: Arc, editor_tx: EditorSender) -> Self { + Self { + analysis, + editor_tx, + } + } + + fn notify(&self, dv: ProjVersion, art: &LspCompiledArtifact) { let should_lint = art .snap .signal @@ -505,7 +521,6 @@ impl LintHook { let snap = art.clone(); let editor_tx = self.editor_tx.clone(); let analysis = self.analysis.clone(); - let dv_clone = dv; spawn_cpu(move || { let mut ctx = analysis.enter(snap.graph.clone()); @@ -516,14 +531,13 @@ impl LintHook { log::trace!( "notify lint diagnostics({:?}): {:#?}", - dv_clone.id, + dv.id, diagnostics.lint ); - let lint_version = dv_clone.clone(); editor_tx .send(EditorRequest::Diag( - lint_version, + dv, DiagKind::Lint, Some(diagnostics.lint), )) @@ -583,6 +597,7 @@ impl ExportHook { pub struct CompileHandlerImpl { /// The analysis data. pub(crate) analysis: Arc, + diag_hook: DiagHook, lint_hook: LintHook, #[cfg(feature = "preview")] @@ -659,6 +674,7 @@ impl CompileHandlerImpl { #[cfg(feature = "preview")] preview: ProjectPreviewState, #[cfg(feature = "export")] export: crate::task::ExportTask, ) -> Arc { + let diag_hook = DiagHook::new(analysis.clone(), editor_tx.clone()); let lint_hook = LintHook::new(analysis.clone(), editor_tx.clone()); #[cfg(feature = "preview")] let preview_hook = PreviewHook::new(preview); @@ -667,6 +683,7 @@ impl CompileHandlerImpl { Arc::new(Self { analysis, + diag_hook, lint_hook, #[cfg(feature = "preview")] preview_hook, @@ -709,6 +726,7 @@ impl CompileHandlerImpl { return; } + self.diag_hook.notify(dv.clone(), art); self.lint_hook.notify(dv, art); } } From 168fdd6c5ae695c25b16e54c041f4b19f4e29e7b Mon Sep 17 00:00:00 2001 From: Hong Jiarong Date: Wed, 10 Dec 2025 16:26:46 +0800 Subject: [PATCH 6/8] refactor: Unify compile event handling with a new `CompileHook` trait, replacing individual handlers for diagnostics, linting, preview, and export. --- crates/tinymist/src/project.rs | 179 +++++++++++++++------------- crates/tinymist/src/task/export.rs | 20 ++-- crates/tinymist/src/tool/project.rs | 13 +- 3 files changed, 115 insertions(+), 97 deletions(-) diff --git a/crates/tinymist/src/project.rs b/crates/tinymist/src/project.rs index 98f7c753c..485d3f1b1 100644 --- a/crates/tinymist/src/project.rs +++ b/crates/tinymist/src/project.rs @@ -159,6 +159,9 @@ 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 analysis = Arc::new(Analysis { @@ -185,15 +188,21 @@ impl ServerState { analysis_rev_cache: Arc::default(), stats: Arc::default(), }); + + let mut hooks: Vec> = Vec::new(); + hooks.push(Box::new(DiagHook::new(analysis.clone(), editor_tx.clone()))); + hooks.push(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, - #[cfg(feature = "preview")] - preview, - #[cfg(feature = "export")] - export.clone(), + hooks, ); let export_target = config.export_target; @@ -243,11 +252,11 @@ impl ServerState { ProjectState { compiler, #[cfg(feature = "preview")] - preview: handle.preview_state(), + preview: preview_state, analysis: handle.analysis.clone(), stats: CompilerQueryStats::default(), #[cfg(feature = "export")] - export: handle.export_task(), + export, } } } @@ -457,13 +466,13 @@ fn push_editor_diagnostics( .log_error("failed to send diagnostics"); } -struct DiagHook { +pub struct DiagHook { analysis: Arc, editor_tx: EditorSender, } impl DiagHook { - fn new(analysis: Arc, editor_tx: EditorSender) -> Self { + pub fn new(analysis: Arc, editor_tx: EditorSender) -> Self { Self { analysis, editor_tx, @@ -490,13 +499,29 @@ impl DiagHook { } } -struct LintHook { +pub trait CompileHook { + fn notify(&self, dv: ProjVersion, art: &LspCompiledArtifact, client: &Arc); + fn status(&self, _revision: usize, _rep: &CompileReport) {} +} + +impl CompileHook for DiagHook { + fn notify(&self, dv: ProjVersion, art: &LspCompiledArtifact, _client: &Arc) { + if art.world().entry_state().is_inactive() { + push_editor_diagnostics(&self.editor_tx, dv.clone(), DiagKind::Compiler, None); + return; + } + + self.notify(dv, art); + } +} + +pub struct LintHook { analysis: Arc, editor_tx: EditorSender, } impl LintHook { - fn new(analysis: Arc, editor_tx: EditorSender) -> Self { + pub fn new(analysis: Arc, editor_tx: EditorSender) -> Self { Self { analysis, editor_tx, @@ -546,15 +571,26 @@ impl LintHook { } } +impl CompileHook for LintHook { + fn notify(&self, dv: ProjVersion, art: &LspCompiledArtifact, _client: &Arc) { + 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)] -struct PreviewHook { +pub struct PreviewHook { state: ProjectPreviewState, } #[cfg(feature = "preview")] impl PreviewHook { - fn new(state: ProjectPreviewState) -> Self { + pub fn new(state: ProjectPreviewState) -> Self { Self { state } } @@ -567,47 +603,73 @@ impl PreviewHook { } } + 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, + }); + } + } + fn state(&self) -> ProjectPreviewState { self.state.clone() } } +#[cfg(feature = "preview")] +impl CompileHook for PreviewHook { + fn notify( + &self, + _dv: ProjVersion, + art: &LspCompiledArtifact, + _client: &Arc, + ) { + self.notify(art); + } + + fn status(&self, revision: usize, rep: &CompileReport) { + self.status(revision, rep); + } +} + #[cfg(feature = "export")] #[derive(Clone)] -struct ExportHook { +pub struct ExportHook { task: crate::task::ExportTask, } #[cfg(feature = "export")] impl ExportHook { - fn new(task: crate::task::ExportTask) -> Self { + pub fn new(task: crate::task::ExportTask) -> Self { Self { task } } - fn notify(&self, art: &LspCompiledArtifact, client: &Arc) { - self.task.signal(art, client); - } - 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) { + self.task.signal(art, client); + } +} + /// The implementation of the compile handler. pub struct CompileHandlerImpl { /// The analysis data. pub(crate) analysis: Arc, - diag_hook: DiagHook, - lint_hook: LintHook, + hooks: Vec>, - #[cfg(feature = "preview")] - preview_hook: PreviewHook, /// Whether the compile server is running in standalone CLI (not as a /// language server). pub is_standalone: bool, - /// The export task. - #[cfg(feature = "export")] - export_hook: ExportHook, /// 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. @@ -671,63 +733,29 @@ impl CompileHandlerImpl { editor_tx: EditorSender, client: Arc, is_standalone: bool, - #[cfg(feature = "preview")] preview: ProjectPreviewState, - #[cfg(feature = "export")] export: crate::task::ExportTask, + hooks: Vec>, ) -> Arc { - let diag_hook = DiagHook::new(analysis.clone(), editor_tx.clone()); - let lint_hook = LintHook::new(analysis.clone(), editor_tx.clone()); - #[cfg(feature = "preview")] - let preview_hook = PreviewHook::new(preview); - #[cfg(feature = "export")] - let export_hook = ExportHook::new(export); - Arc::new(Self { analysis, - diag_hook, - lint_hook, - #[cfg(feature = "preview")] - preview_hook, is_standalone, - #[cfg(feature = "export")] - export_hook, editor_tx, client, status_revision: Mutex::default(), notified_revision: Mutex::default(), + hooks, }) } - #[cfg(feature = "preview")] - fn preview_state(&self) -> ProjectPreviewState { - self.preview_hook.state() - } - - #[cfg(feature = "export")] - fn export_task(&self) -> crate::task::ExportTask { - self.export_hook.task() - } - - /// Clears both compiler and lint diagnostics for the given project version. - fn clear_diagnostics(&self, dv: ProjVersion) { - push_editor_diagnostics(&self.editor_tx, dv.clone(), DiagKind::Compiler, None); - push_editor_diagnostics(&self.editor_tx, dv, DiagKind::Lint, None); - } - /// Notifies the diagnostics. fn notify_diagnostics(&self, art: &LspCompiledArtifact) { let dv = ProjVersion { 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.clear_diagnostics(dv); - return; - } - self.diag_hook.notify(dv.clone(), art); - self.lint_hook.notify(dv, art); + for hook in &self.hooks { + hook.notify(dv.clone(), art, &self.client); + } } } @@ -841,19 +869,13 @@ impl CompileHandler for CompileHandlerImpl id: rep.id.clone(), revision, }; - self.clear_diagnostics(dv); + 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_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, - }); + for hook in &self.hooks { + hook.status(revision, &rep); } self.editor_tx.send(EditorRequest::Status(rep)).unwrap(); @@ -870,7 +892,8 @@ impl CompileHandler for CompileHandlerImpl // todo: race condition with notify_compile? // remove diagnostics - self.clear_diagnostics(dv); + 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) { @@ -920,12 +943,6 @@ impl CompileHandler for CompileHandlerImpl .log_error("failed to print diagnostics"); } - #[cfg(feature = "export")] - self.export_hook.notify(art, &self.client); - - #[cfg(feature = "preview")] - self.preview_hook.notify(art); - self.notify_diagnostics(art); } } diff --git a/crates/tinymist/src/task/export.rs b/crates/tinymist/src/task/export.rs index b99b61c57..02f9b42b9 100644 --- a/crates/tinymist/src/task/export.rs +++ b/crates/tinymist/src/task/export.rs @@ -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, - ) { + pub(crate) fn signal(&self, art: &LspCompiledArtifact, client: &Arc) { 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( diff --git a/crates/tinymist/src/tool/project.rs b/crates/tinymist/src/tool/project.rs index a339de7d5..99a682c06 100644 --- a/crates/tinymist/src/tool/project.rs +++ b/crates/tinymist/src/tool/project.rs @@ -84,16 +84,21 @@ where let export_task = crate::task::ExportTask::new(handle, Some(editor_tx.clone()), opts.config.export()); + let mut hooks: Vec> = Vec::new(); + hooks.push(Box::new(DiagHook::new(analysis.clone(), editor_tx.clone()))); + hooks.push(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, - #[cfg(feature = "preview")] - preview, - #[cfg(feature = "export")] - export_task, + hooks, ); let mut compiler = ProjectCompiler::new( From 084706347340b223ab73799fbc88b3d3ad70e49b Mon Sep 17 00:00:00 2001 From: Hong Jiarong Date: Wed, 10 Dec 2025 20:59:41 +0800 Subject: [PATCH 7/8] docs: Add documentation comments to project hooks and refactor hook vector initialization. --- crates/tinymist/src/project.rs | 20 +++++++++++++++++--- crates/tinymist/src/tool/project.rs | 7 ++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/crates/tinymist/src/project.rs b/crates/tinymist/src/project.rs index 485d3f1b1..9d72bc3f6 100644 --- a/crates/tinymist/src/project.rs +++ b/crates/tinymist/src/project.rs @@ -189,9 +189,10 @@ impl ServerState { stats: Arc::default(), }); - let mut hooks: Vec> = Vec::new(); - hooks.push(Box::new(DiagHook::new(analysis.clone(), editor_tx.clone()))); - hooks.push(Box::new(LintHook::new(analysis.clone(), editor_tx.clone()))); + let mut hooks: Vec> = 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")] @@ -466,12 +467,14 @@ fn push_editor_diagnostics( .log_error("failed to send diagnostics"); } +/// A hook that handles diagnostics. pub struct DiagHook { analysis: Arc, editor_tx: EditorSender, } impl DiagHook { + /// Creates a new diagnostics hook. pub fn new(analysis: Arc, editor_tx: EditorSender) -> Self { Self { analysis, @@ -499,8 +502,11 @@ impl DiagHook { } } +/// 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); + /// Notifies the hook of a compilation status. fn status(&self, _revision: usize, _rep: &CompileReport) {} } @@ -515,12 +521,14 @@ impl CompileHook for DiagHook { } } +/// A hook that handles linting. pub struct LintHook { analysis: Arc, editor_tx: EditorSender, } impl LintHook { + /// Creates a new lint hook. pub fn new(analysis: Arc, editor_tx: EditorSender) -> Self { Self { analysis, @@ -584,12 +592,14 @@ impl CompileHook for LintHook { #[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 } } @@ -616,6 +626,7 @@ impl PreviewHook { } } + #[allow(dead_code)] fn state(&self) -> ProjectPreviewState { self.state.clone() } @@ -639,16 +650,19 @@ impl CompileHook for PreviewHook { #[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() } diff --git a/crates/tinymist/src/tool/project.rs b/crates/tinymist/src/tool/project.rs index 99a682c06..d7279cffa 100644 --- a/crates/tinymist/src/tool/project.rs +++ b/crates/tinymist/src/tool/project.rs @@ -84,9 +84,10 @@ where let export_task = crate::task::ExportTask::new(handle, Some(editor_tx.clone()), opts.config.export()); - let mut hooks: Vec> = Vec::new(); - hooks.push(Box::new(DiagHook::new(analysis.clone(), editor_tx.clone()))); - hooks.push(Box::new(LintHook::new(analysis.clone(), editor_tx.clone()))); + let mut hooks: Vec> = 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")] From 5dbcd4ebd7efc193e587a48b4394ce2e413b93e9 Mon Sep 17 00:00:00 2001 From: Hong Jiarong Date: Wed, 10 Dec 2025 21:13:28 +0800 Subject: [PATCH 8/8] style: suppress `unused_mut` warning for `hooks` vector initialization --- crates/tinymist/src/project.rs | 1 + crates/tinymist/src/tool/project.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/tinymist/src/project.rs b/crates/tinymist/src/project.rs index 9d72bc3f6..db89c1700 100644 --- a/crates/tinymist/src/project.rs +++ b/crates/tinymist/src/project.rs @@ -189,6 +189,7 @@ impl ServerState { stats: Arc::default(), }); + #[allow(unused_mut)] let mut hooks: Vec> = vec![ Box::new(DiagHook::new(analysis.clone(), editor_tx.clone())), Box::new(LintHook::new(analysis.clone(), editor_tx.clone())), diff --git a/crates/tinymist/src/tool/project.rs b/crates/tinymist/src/tool/project.rs index d7279cffa..9871056ba 100644 --- a/crates/tinymist/src/tool/project.rs +++ b/crates/tinymist/src/tool/project.rs @@ -84,6 +84,7 @@ where let export_task = crate::task::ExportTask::new(handle, Some(editor_tx.clone()), opts.config.export()); + #[allow(unused_mut)] let mut hooks: Vec> = vec![ Box::new(DiagHook::new(analysis.clone(), editor_tx.clone())), Box::new(LintHook::new(analysis.clone(), editor_tx.clone())),