1use crate::LspShellState;
15use crate::diagnostics_follow_up::{
16 TIDE_REPUBLISH_LANE_CONFIG, workspace_republish_frontier_passed,
17};
18use crate::disk_cache::DiskDiagnosticsCacheSlotV0;
19use crate::lsp_output::ScheduledLspOutput;
20use crate::state::LspQuerySnapshotV0;
21use crate::tide::TideGateInputsV0;
22use serde_json::Value;
23use std::sync::Arc;
24use std::sync::atomic::{AtomicU64, Ordering};
25
26#[derive(Debug)]
27pub struct TideWorkspaceRepublishJobV0 {
28 snapshot: LspQuerySnapshotV0,
29 uris: Vec<String>,
30 pub generation: u64,
31 gen_watch: Arc<AtomicU64>,
32}
33
34impl TideWorkspaceRepublishJobV0 {
35 #[cfg(test)]
36 pub(crate) fn target_uris_for_test(&self) -> &[String] {
37 self.uris.as_slice()
38 }
39}
40
41#[derive(Debug)]
42pub struct TideWorkspaceRepublishItemV0 {
43 pub(crate) uri: String,
44 pub(crate) diagnostics: Value,
45 pub(crate) disk_cache_slot: Option<DiskDiagnosticsCacheSlotV0>,
46}
47
48#[derive(Debug)]
49pub struct TideWorkspaceRepublishResultV0 {
50 pub generation: u64,
51 pub items: Vec<TideWorkspaceRepublishItemV0>,
52 pub uncovered_uris: Vec<String>,
56 pub final_chunk: bool,
59}
60
61pub fn prepare_tide_workspace_republish_job(
65 state: &mut LspShellState,
66 idle: bool,
67) -> Option<TideWorkspaceRepublishJobV0> {
68 if !state.external_sif_refresh_deferred {
69 return None;
70 }
71 let inputs = TideGateInputsV0 {
72 frontier_passed: workspace_republish_frontier_passed(state),
73 idle,
74 };
75 let flush = state.tide_republish_lane.try_flush(
76 inputs,
77 state.tide_tick,
78 &TIDE_REPUBLISH_LANE_CONFIG,
79 )?;
80 state
81 .tide_republish_gen_watch
82 .store(flush.generation, Ordering::Relaxed);
83 let uris = crate::diagnostics_follow_up::tide_republish_target_uris(state, &flush.demand);
88 if uris.is_empty() {
89 state.tide_republish_lane.tide_completed(flush.generation);
90 return None;
91 }
92 crate::loop_trace!(
93 "republish-tide prepared gen={} targets={} demand={}",
94 flush.generation,
95 uris.len(),
96 match &flush.demand {
97 crate::tide::TideRepublishDemandV0::All => "all".to_string(),
98 crate::tide::TideRepublishDemandV0::Cone(seeds) => format!("cone({})", seeds.len()),
99 crate::tide::TideRepublishDemandV0::None => "none".to_string(),
100 }
101 );
102 Some(TideWorkspaceRepublishJobV0 {
103 snapshot: state.query_snapshot(),
104 uris,
105 generation: flush.generation,
106 gen_watch: Arc::clone(&state.tide_republish_gen_watch),
107 })
108}
109
110pub fn collect_tide_workspace_republish_streaming(
117 job: TideWorkspaceRepublishJobV0,
118 emit: &(dyn Fn(TideWorkspaceRepublishResultV0) -> bool + Sync),
119) {
120 let generation = job.generation;
127 let uncovered_fallback = job.uris.clone();
128 if std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
129 collect_tide_workspace_republish_streaming_inner(job, emit)
130 }))
131 .is_err()
132 {
133 let _ = emit(TideWorkspaceRepublishResultV0 {
134 generation,
135 items: Vec::new(),
136 uncovered_uris: uncovered_fallback,
137 final_chunk: true,
138 });
139 }
140}
141
142fn collect_tide_workspace_republish_streaming_inner(
143 job: TideWorkspaceRepublishJobV0,
144 emit: &(dyn Fn(TideWorkspaceRepublishResultV0) -> bool + Sync),
145) {
146 let covered = std::sync::Mutex::new(std::collections::BTreeSet::<usize>::new());
147 let sink =
148 |index: usize,
149 diagnostics: serde_json::Value,
150 disk_cache_slot: Option<crate::disk_cache::DiskDiagnosticsCacheSlotV0>| {
151 if let Ok(mut covered) = covered.lock() {
152 covered.insert(index);
153 }
154 let Some(uri) = job.uris.get(index) else {
155 return;
156 };
157 let _ = emit(TideWorkspaceRepublishResultV0 {
158 generation: job.generation,
159 items: vec![TideWorkspaceRepublishItemV0 {
160 uri: uri.clone(),
161 diagnostics,
162 disk_cache_slot,
163 }],
164 uncovered_uris: Vec::new(),
165 final_chunk: false,
166 });
167 };
168 let _ = crate::parallel_style_wave::resolved_parallel_style_wave_targets_from_read_view_with_abort_and_sink(
169 &job.snapshot,
170 job.uris.as_slice(),
171 crate::parallel_style_wave::PARALLEL_STYLE_WAVE_MIN_PARALLEL_TARGETS,
172 Some((job.gen_watch.as_ref(), job.generation)),
173 Some(&sink),
174 );
175 let covered = covered.into_inner().unwrap_or_default();
176 let uncovered_uris = job
177 .uris
178 .iter()
179 .enumerate()
180 .filter(|(index, _)| !covered.contains(index))
181 .map(|(_, uri)| uri.clone())
182 .collect::<Vec<_>>();
183 crate::loop_trace!(
184 "republish-tide collected gen={} covered={} uncovered={}",
185 job.generation,
186 covered.len(),
187 uncovered_uris.len()
188 );
189 let _ = emit(TideWorkspaceRepublishResultV0 {
190 generation: job.generation,
191 items: Vec::new(),
192 uncovered_uris,
193 final_chunk: true,
194 });
195}
196
197pub fn apply_tide_workspace_republish_item(
202 state: &mut LspShellState,
203 item: TideWorkspaceRepublishItemV0,
204) -> Vec<ScheduledLspOutput> {
205 if let Some(slot) = item.disk_cache_slot.as_ref() {
206 slot.store_write_behind(state, &item.diagnostics);
207 }
208 crate::diagnostics_scheduler::publish_tiered_diagnostics_notifications(
209 state,
210 item.uri.as_str(),
211 item.diagnostics,
212 )
213}
214
215pub fn complete_tide_workspace_republish(
218 state: &mut LspShellState,
219 generation: u64,
220 uncovered_uris: Vec<String>,
221) -> crate::LspDiagnosticsFollowUpEffectsV0 {
222 let current = state.tide_republish_lane.generation() == generation;
223 let source_scope_seeds = match state.tide_republish_lane.in_flight_demand() {
229 Some(crate::tide::TideRepublishDemandV0::Cone(seeds)) => Some(seeds.clone()),
230 _ => None,
231 };
232 state.tide_republish_lane.tide_completed(generation);
233 if !current {
234 return crate::LspDiagnosticsFollowUpEffectsV0::default();
235 }
236 let mut effects = crate::diagnostics_scheduler::DiagnosticsScheduleEffectsV0::default();
237 if !uncovered_uris.is_empty() {
238 crate::loop_trace!(
239 "republish-tide leftovers gen={} n={}",
240 generation,
241 uncovered_uris.len()
242 );
243 effects.extend(
244 crate::diagnostics_scheduler::run_diagnostics_schedule_effects(
245 state,
246 crate::diagnostics_scheduler::DiagnosticsScheduleEvent::WatchedFiles {
247 uris: uncovered_uris,
248 },
249 ),
250 );
251 }
252 let open_source_uris = crate::diagnostics_scheduler::open_document_uris_for_diagnostics(state)
261 .into_iter()
262 .filter(|uri| !crate::protocol::is_style_document_uri(uri.as_str()))
263 .collect::<Vec<_>>();
264 let open_source_uris = match source_scope_seeds {
269 Some(seeds) => {
270 let mut scoped = std::collections::BTreeSet::new();
271 for seed in seeds {
272 scoped.extend(
273 crate::diagnostics_scheduler::scoped_source_republish_uris_for_style_change(
274 state,
275 seed.as_str(),
276 open_source_uris.clone(),
277 ),
278 );
279 }
280 scoped.into_iter().collect::<Vec<_>>()
281 }
282 None => open_source_uris,
283 };
284 if !open_source_uris.is_empty() {
285 crate::loop_trace!(
286 "republish-tide source refresh gen={} n={}",
287 generation,
288 open_source_uris.len()
289 );
290 effects.extend(
291 crate::diagnostics_scheduler::diagnostics_effects_for_document_uris(
292 state,
293 open_source_uris,
294 true,
295 ),
296 );
297 }
298 crate::LspDiagnosticsFollowUpEffectsV0 {
299 outputs: effects.outputs,
300 deferred_diagnostics: effects.deferred_diagnostics,
301 }
302}