use crate::diagnostics_scheduler;
use crate::lsp_output::{LspDeferredDiagnosticsDispatchV0, ScheduledLspOutput};
use crate::protocol::is_style_document_uri;
use crate::tide::{TideGateInputsV0, TideLaneConfigV0, TideRepublishDemandV0};
use crate::{LspDocumentOrigin, LspExternalSifRefreshResultV0, LspShellState};
use std::collections::BTreeSet;
pub(crate) const TIDE_REPUBLISH_LANE_CONFIG: TideLaneConfigV0 = TideLaneConfigV0 {
aging_bound_ticks: 2_000,
};
#[cfg(test)]
pub(crate) mod warmup_wave_count_probe {
use std::cell::Cell;
thread_local! {
static FOLLOW_UP_WAVE_COUNT: Cell<usize> = const { Cell::new(0) };
}
pub(crate) fn record() {
FOLLOW_UP_WAVE_COUNT.with(|count| count.set(count.get().saturating_add(1)));
}
pub(crate) fn reset() {
FOLLOW_UP_WAVE_COUNT.with(|count| count.set(0));
}
pub(crate) fn read() -> usize {
FOLLOW_UP_WAVE_COUNT.with(Cell::get)
}
}
#[derive(Debug, Default)]
pub struct LspDiagnosticsFollowUpEffectsV0 {
pub outputs: Vec<ScheduledLspOutput>,
pub deferred_diagnostics: Vec<LspDeferredDiagnosticsDispatchV0>,
}
pub fn external_sif_refresh_follow_up_diagnostics_effects(
state: &mut LspShellState,
) -> LspDiagnosticsFollowUpEffectsV0 {
let uris = tide_republish_target_uris(state, &TideRepublishDemandV0::All);
if uris.is_empty() {
return LspDiagnosticsFollowUpEffectsV0::default();
}
#[cfg(test)]
warmup_wave_count_probe::record();
crate::loop_trace!(
"sif-follow-up fired for {} style docs (deferred)",
uris.len()
);
let effects = diagnostics_scheduler::run_diagnostics_schedule_effects(
state,
diagnostics_scheduler::DiagnosticsScheduleEvent::WatchedFiles { uris },
);
LspDiagnosticsFollowUpEffectsV0 {
outputs: effects.outputs,
deferred_diagnostics: effects.deferred_diagnostics,
}
}
pub fn apply_external_sif_refresh_result_follow_up_diagnostics_effects(
state: &mut LspShellState,
result: LspExternalSifRefreshResultV0,
) -> LspDiagnosticsFollowUpEffectsV0 {
crate::apply_deferred_external_sif_refresh_result(state, result);
tide_workspace_republish_flush_effects(state)
}
pub(crate) fn workspace_republish_frontier_passed(state: &LspShellState) -> bool {
!state.tide_sif_lane.has_demand()
&& !state.tide_sif_lane.in_flight()
&& state.workspace_index_pending_file_count == 0
}
pub(crate) fn tide_republish_target_uris(
state: &LspShellState,
demand: &TideRepublishDemandV0,
) -> Vec<String> {
let cone = match demand {
TideRepublishDemandV0::None => return Vec::new(),
TideRepublishDemandV0::All => None,
TideRepublishDemandV0::Cone(seeds) => match republish_cone_paths(state, seeds) {
Some(cone) => Some(cone),
None => {
crate::loop_trace!(
"republish-cone widened to all: {} seeds, no committed scope",
seeds.len()
);
None
}
},
};
let mut open = Vec::new();
let mut unopened = Vec::new();
for document in state.documents.values() {
if document.origin != LspDocumentOrigin::Local
|| !is_style_document_uri(document.uri.as_str())
{
continue;
}
if let Some(cone) = cone.as_ref()
&& !diagnostics_scheduler::file_uri_set_contains_equivalent(cone, document.uri.as_str())
{
continue;
}
if state.has_open_document_uri(document.uri.as_str()) {
open.push(document.uri.clone());
} else {
unopened.push(document.uri.clone());
}
}
open.extend(unopened);
open
}
fn republish_cone_paths(
state: &LspShellState,
seeds: &BTreeSet<String>,
) -> Option<BTreeSet<String>> {
diagnostics_scheduler::with_reverse_dependency_index(state, |index| {
let mut cone =
diagnostics_scheduler::reverse_dependency_closure_for_lsp_paths(index, seeds);
cone.extend(seeds.iter().cloned());
cone
})
}
pub fn tide_workspace_republish_flush_effects(
state: &mut LspShellState,
) -> LspDiagnosticsFollowUpEffectsV0 {
let inputs = TideGateInputsV0 {
frontier_passed: workspace_republish_frontier_passed(state),
idle: true,
};
let Some(flush) =
state
.tide_republish_lane
.try_flush(inputs, state.tide_tick, &TIDE_REPUBLISH_LANE_CONFIG)
else {
return LspDiagnosticsFollowUpEffectsV0::default();
};
let uris = tide_republish_target_uris(state, &flush.demand);
let effects = if uris.is_empty() {
LspDiagnosticsFollowUpEffectsV0::default()
} else {
#[cfg(test)]
warmup_wave_count_probe::record();
crate::loop_trace!(
"sif-follow-up fired for {} style docs (deferred)",
uris.len()
);
let effects = diagnostics_scheduler::run_diagnostics_schedule_effects(
state,
diagnostics_scheduler::DiagnosticsScheduleEvent::WatchedFiles { uris },
);
LspDiagnosticsFollowUpEffectsV0 {
outputs: effects.outputs,
deferred_diagnostics: effects.deferred_diagnostics,
}
};
state.tide_republish_lane.tide_completed(flush.generation);
effects
}