mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-08-04 02:08:17 +00:00
* fix: emit latest compiled artifact with correct signals * fix: bad guard
This commit is contained in:
parent
26fd50febf
commit
0c64bea89e
5 changed files with 125 additions and 42 deletions
|
@ -127,20 +127,12 @@ impl<F: CompilerFeat> CompiledArtifact<F> {
|
|||
pub enum CompileReport {
|
||||
Suspend,
|
||||
Stage(FileId, &'static str, tinymist_std::time::Time),
|
||||
CompileError(
|
||||
FileId,
|
||||
EcoVec<SourceDiagnostic>,
|
||||
tinymist_std::time::Duration,
|
||||
),
|
||||
ExportError(
|
||||
FileId,
|
||||
EcoVec<SourceDiagnostic>,
|
||||
tinymist_std::time::Duration,
|
||||
),
|
||||
CompileError(FileId, usize, tinymist_std::time::Duration),
|
||||
ExportError(FileId, usize, tinymist_std::time::Duration),
|
||||
CompileSuccess(
|
||||
FileId,
|
||||
// warnings, if not empty
|
||||
EcoVec<SourceDiagnostic>,
|
||||
usize,
|
||||
tinymist_std::time::Duration,
|
||||
),
|
||||
}
|
||||
|
@ -166,7 +158,7 @@ impl CompileReport {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn diagnostics(self) -> Option<EcoVec<SourceDiagnostic>> {
|
||||
pub fn diagnostics_size(self) -> Option<usize> {
|
||||
match self {
|
||||
Self::Suspend | Self::Stage(..) => None,
|
||||
Self::CompileError(_, diagnostics, ..)
|
||||
|
@ -193,13 +185,12 @@ impl fmt::Display for CompileReportMsg<'_> {
|
|||
Suspend => write!(f, "suspended"),
|
||||
Stage(_, stage, ..) => write!(f, "{input:?}: {stage} ..."),
|
||||
CompileSuccess(_, warnings, duration) => {
|
||||
if warnings.is_empty() {
|
||||
if *warnings == 0 {
|
||||
write!(f, "{input:?}: compilation succeeded in {duration:?}")
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{input:?}: compilation succeeded with {} warnings in {duration:?}",
|
||||
warnings.len()
|
||||
"{input:?}: compilation succeeded with {warnings} warnings in {duration:?}",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -217,7 +208,7 @@ pub trait CompileHandler<F: CompilerFeat, Ext>: Send + Sync + 'static {
|
|||
fn on_any_compile_reason(&self, state: &mut ProjectCompiler<F, Ext>);
|
||||
// todo: notify project specific compile
|
||||
/// Called when a compilation is done.
|
||||
fn notify_compile(&self, res: &CompiledArtifact<F>, rep: CompileReport);
|
||||
fn notify_compile(&self, res: &CompiledArtifact<F>);
|
||||
/// Called when a project is removed.
|
||||
fn notify_removed(&self, _id: &ProjectInsId) {}
|
||||
/// Called when the compilation status is changed.
|
||||
|
@ -231,7 +222,7 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: 'static> CompileHandler<F, Ex
|
|||
fn on_any_compile_reason(&self, _state: &mut ProjectCompiler<F, Ext>) {
|
||||
log::info!("ProjectHandle: no need to compile");
|
||||
}
|
||||
fn notify_compile(&self, _res: &CompiledArtifact<F>, _rep: CompileReport) {}
|
||||
fn notify_compile(&self, _res: &CompiledArtifact<F>) {}
|
||||
fn status(&self, _revision: usize, _id: &ProjectInsId, _rep: CompileReport) {}
|
||||
}
|
||||
|
||||
|
@ -280,6 +271,16 @@ pub struct CompileReasons {
|
|||
pub by_entry_update: bool,
|
||||
}
|
||||
|
||||
impl From<CompileReasons> for ExportSignal {
|
||||
fn from(value: CompileReasons) -> Self {
|
||||
Self {
|
||||
by_mem_events: value.by_memory_events,
|
||||
by_fs_events: value.by_fs_events,
|
||||
by_entry_update: value.by_entry_update,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CompileReasons {
|
||||
/// Merge two reasons.
|
||||
pub fn see(&mut self, reason: CompileReasons) {
|
||||
|
@ -795,13 +796,15 @@ impl<F: CompilerFeat, Ext: 'static> ProjectInsState<F, Ext> {
|
|||
|
||||
let elapsed = start.elapsed().unwrap_or_default();
|
||||
let rep = match &compiled.doc {
|
||||
Ok(..) => CompileReport::CompileSuccess(id, compiled.warnings.clone(), elapsed),
|
||||
Err(err) => CompileReport::CompileError(id, err.clone(), elapsed),
|
||||
Ok(..) => CompileReport::CompileSuccess(id, compiled.warnings.len(), elapsed),
|
||||
Err(err) => CompileReport::CompileError(id, err.len(), elapsed),
|
||||
};
|
||||
|
||||
// todo: we need to check revision for really concurrent compilation
|
||||
log_compile_report(&rep);
|
||||
h.notify_compile(&compiled, rep);
|
||||
|
||||
h.status(revision, &compiled.id, rep);
|
||||
h.notify_compile(&compiled);
|
||||
|
||||
compiled
|
||||
}
|
||||
|
|
|
@ -36,6 +36,15 @@ pub struct ExportSignal {
|
|||
pub by_entry_update: bool,
|
||||
}
|
||||
|
||||
impl ExportSignal {
|
||||
/// Merge two signals.
|
||||
pub fn merge(&mut self, other: ExportSignal) {
|
||||
self.by_mem_events |= other.by_mem_events;
|
||||
self.by_fs_events |= other.by_fs_events;
|
||||
self.by_entry_update |= other.by_entry_update;
|
||||
}
|
||||
}
|
||||
|
||||
/// A snapshot of the project and compilation state.
|
||||
pub struct CompileSnapshot<F: CompilerFeat> {
|
||||
/// The project id.
|
||||
|
|
|
@ -648,7 +648,7 @@ impl<F: CompilerFeat> World for CompilerWorld<F> {
|
|||
///
|
||||
/// The returned `Source` file's [id](Source::id) does not have to match the
|
||||
/// given `id`. Due to symlinks, two different file id's can point to the
|
||||
/// same on-disk file. Implementors can deduplicate and return the same
|
||||
/// same on-disk file. Implementers can deduplicate and return the same
|
||||
/// `Source` if they want to, but do not have to.
|
||||
fn source(&self, id: FileId) -> FileResult<Source> {
|
||||
static DETACH_SOURCE: LazyLock<Source> =
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
pub use tinymist_project::*;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::{num::NonZeroUsize, sync::Arc};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use reflexo::{hash::FxHashMap, path::unix_slash};
|
||||
|
@ -173,6 +173,7 @@ impl ServerState {
|
|||
stats: Arc::default(),
|
||||
}),
|
||||
|
||||
status_revision: Mutex::default(),
|
||||
notified_revision: Mutex::default(),
|
||||
});
|
||||
|
||||
|
@ -229,10 +230,64 @@ impl ServerState {
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct ProjectInsStateExt {
|
||||
pub notified_revision: usize,
|
||||
pub pending_reasons: CompileReasons,
|
||||
pub emitted_reasons: CompileReasons,
|
||||
pub is_compiling: bool,
|
||||
pub last_compilation: Option<LspCompiledArtifact>,
|
||||
}
|
||||
|
||||
impl ProjectInsStateExt {
|
||||
/// Remembers the last compilation. Emits the pending reasons during
|
||||
/// compilation if any.
|
||||
pub fn compiled(
|
||||
&mut self,
|
||||
revision: &NonZeroUsize,
|
||||
handler: &dyn CompileHandler<LspCompilerFeat, ProjectInsStateExt>,
|
||||
compilation: &LspCompiledArtifact,
|
||||
) {
|
||||
let rev = compilation.world.revision().get();
|
||||
if self.notified_revision >= rev {
|
||||
return;
|
||||
}
|
||||
self.notified_revision = rev;
|
||||
|
||||
self.is_compiling = false;
|
||||
self.last_compilation = Some(compilation.clone());
|
||||
|
||||
self.emit_pending_reasons(revision, handler);
|
||||
}
|
||||
|
||||
/// Emits the pending reasons if the latest compiled revision matches.
|
||||
pub fn emit_pending_reasons(
|
||||
&mut self,
|
||||
revision: &NonZeroUsize,
|
||||
handler: &dyn CompileHandler<LspCompilerFeat, ProjectInsStateExt>,
|
||||
) -> bool {
|
||||
let Some(last_compilation) = self.last_compilation.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let last_rev = last_compilation.world.revision();
|
||||
if last_rev != *revision {
|
||||
return false;
|
||||
}
|
||||
|
||||
let pending_reasons = self.pending_reasons.exclude(self.emitted_reasons);
|
||||
if !pending_reasons.any() {
|
||||
return false;
|
||||
}
|
||||
self.emitted_reasons.see(self.pending_reasons);
|
||||
let mut last_compilation = last_compilation.clone();
|
||||
last_compilation.snap.signal = pending_reasons.into();
|
||||
|
||||
handler.notify_compile(&last_compilation);
|
||||
self.pending_reasons = CompileReasons::default();
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProjectState {
|
||||
pub compiler: LspProjectCompiler,
|
||||
pub preview: ProjectPreviewState,
|
||||
|
@ -269,8 +324,8 @@ impl ProjectState {
|
|||
if let Interrupt::Compiled(compiled) = &intr {
|
||||
let proj = self.compiler.projects().find(|p| p.id == compiled.id);
|
||||
if let Some(proj) = proj {
|
||||
proj.ext.is_compiling = false;
|
||||
proj.ext.last_compilation = Some(compiled.clone());
|
||||
proj.ext
|
||||
.compiled(&proj.verse.revision, proj.handler.as_ref(), compiled);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -346,6 +401,7 @@ pub struct CompileHandlerImpl {
|
|||
pub(crate) editor_tx: EditorSender,
|
||||
pub(crate) client: Box<dyn ProjectClient>,
|
||||
|
||||
pub(crate) status_revision: Mutex<FxHashMap<ProjectInsId, usize>>,
|
||||
pub(crate) notified_revision: Mutex<FxHashMap<ProjectInsId, usize>>,
|
||||
}
|
||||
|
||||
|
@ -438,11 +494,27 @@ impl CompileHandler<LspCompilerFeat, ProjectInsStateExt> for CompileHandlerImpl
|
|||
s.verse.vfs().is_clean_compile(last_rev.get(), &deps)
|
||||
}
|
||||
{
|
||||
log::info!("Project: skip compilation for {id:?} due to harmless vfs changes");
|
||||
s.ext.pending_reasons.see(reason);
|
||||
s.reason = CompileReasons::default();
|
||||
|
||||
let pending_reasons = s.ext.pending_reasons.exclude(s.ext.emitted_reasons);
|
||||
let emitted = s
|
||||
.ext
|
||||
.emit_pending_reasons(&s.verse.revision, s.handler.as_ref());
|
||||
|
||||
if !emitted {
|
||||
log::info!("Project: skip compilation for {id:?} due to harmless vfs changes");
|
||||
} else {
|
||||
log::info!(
|
||||
"Project: emit compilation again for {id:?}, reason: {pending_reasons:?}"
|
||||
);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
s.ext.pending_reasons = CompileReasons::default();
|
||||
s.ext.emitted_reasons = reason;
|
||||
let Some(compile_fn) = s.may_compile(&c.handler) else {
|
||||
continue;
|
||||
};
|
||||
|
@ -454,6 +526,20 @@ impl CompileHandler<LspCompilerFeat, ProjectInsStateExt> for CompileHandlerImpl
|
|||
}
|
||||
|
||||
fn status(&self, revision: usize, id: &ProjectInsId, rep: CompileReport) {
|
||||
// todo: we need to manage the revision for fn status() as well
|
||||
{
|
||||
let mut n_revs = self.status_revision.lock();
|
||||
let n_rev = n_revs.entry(id.clone()).or_default();
|
||||
if *n_rev > revision {
|
||||
log::info!(
|
||||
"Project: outdated status for revision {} <= {n_rev}",
|
||||
revision,
|
||||
);
|
||||
return;
|
||||
}
|
||||
*n_rev = revision;
|
||||
}
|
||||
|
||||
// todo: seems to duplicate with CompileStatus
|
||||
let status = match rep {
|
||||
CompileReport::Suspend => {
|
||||
|
@ -505,8 +591,7 @@ impl CompileHandler<LspCompilerFeat, ProjectInsStateExt> for CompileHandlerImpl
|
|||
n_revs.remove(id);
|
||||
}
|
||||
|
||||
fn notify_compile(&self, snap: &LspCompiledArtifact, rep: CompileReport) {
|
||||
// todo: we need to manage the revision for fn status() as well
|
||||
fn notify_compile(&self, snap: &LspCompiledArtifact) {
|
||||
{
|
||||
let mut n_revs = self.notified_revision.lock();
|
||||
let n_rev = n_revs.entry(snap.id.clone()).or_default();
|
||||
|
@ -525,21 +610,6 @@ impl CompileHandler<LspCompilerFeat, ProjectInsStateExt> for CompileHandlerImpl
|
|||
self.client.interrupt(LspInterrupt::Compiled(snap.clone()));
|
||||
self.export.signal(snap);
|
||||
|
||||
self.editor_tx
|
||||
.send(EditorRequest::Status(CompileStatus {
|
||||
id: snap.id.clone(),
|
||||
path: rep
|
||||
.compiling_id()
|
||||
.map(|s| unix_slash(s.vpath().as_rooted_path()))
|
||||
.unwrap_or_default(),
|
||||
status: if snap.doc.is_ok() {
|
||||
CompileStatusEnum::CompileSuccess
|
||||
} else {
|
||||
CompileStatusEnum::CompileError
|
||||
},
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
#[cfg(feature = "preview")]
|
||||
if let Some(inner) = self.preview.get(&snap.id) {
|
||||
let snap = snap.clone();
|
||||
|
|
|
@ -651,6 +651,7 @@ pub async fn preview_main(args: PreviewCliArgs) -> Result<()> {
|
|||
client: Box::new(intr_tx.clone()),
|
||||
analysis: Arc::default(),
|
||||
|
||||
status_revision: Mutex::default(),
|
||||
notified_revision: Mutex::default(),
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue