1use crate::protocol::{file_uri_to_path, is_style_document_uri, normalize_path};
2use crate::tide::{
3 TideDisownCauseV0, TideFootprintStampV0, TideFootprintV0, TideGateInputsV0, TideInputKindV0,
4 TideLaneConfigV0, TideRepublishDemandV0, TideSifDemandV0,
5};
6use crate::{LspShellState, LspTextDocumentState};
7use omena_query::{
8 OmenaQueryBridgeExternalSifTrustedResolutionV1, OmenaQueryExternalSifInputV0,
9 OmenaQueryExternalSifStorageV0, OmenaQueryExternalSifTrustV1,
10 OmenaQueryStyleResolutionInputsV0, OmenaQueryStyleSourceInputV0,
11 resolve_omena_query_bridge_external_sifs_for_seed_pairs_with_cache_storage_and_trust,
12 resolve_omena_query_bridge_external_sifs_for_seed_pairs_with_trust,
13 resolve_omena_query_bridge_external_sifs_for_style_sources_with_cache_storage_and_trust,
14 resolve_omena_query_bridge_external_sifs_for_style_sources_with_trust,
15};
16use omena_sif::OMENA_SIF_SHARD_VERDICT_DIR_V1;
17use std::{
18 collections::{BTreeMap, BTreeSet},
19 path::{Path, PathBuf},
20};
21
22#[derive(Debug, Clone)]
23pub struct LspExternalSifRefreshDocumentV0 {
24 pub uri: String,
25 pub workspace_folder_uri: Option<String>,
26 pub text: String,
27}
28
29#[derive(Debug, Clone)]
30pub struct LspExternalSifRefreshJobV0 {
31 pub stamp: TideFootprintStampV0,
32 pub generation: u64,
33 pub lockfiles: Vec<PathBuf>,
34 pub documents: Vec<LspExternalSifRefreshDocumentV0>,
35 pub package_manifests: Vec<omena_query::OmenaQueryStylePackageManifestV0>,
36 pub resolution_inputs_by_workspace_uri:
37 std::collections::BTreeMap<String, OmenaQueryStyleResolutionInputsV0>,
38}
39
40#[derive(Debug, Clone, Default)]
41pub struct LspExternalSifRefreshCacheStorageV0 {
42 by_workspace_uri: BTreeMap<String, OmenaQueryExternalSifStorageV0>,
43 by_document_uri: BTreeMap<String, OmenaQueryExternalSifStorageV0>,
44}
45
46#[derive(Debug, Clone)]
47pub struct LspExternalSifRefreshResultV0 {
48 pub stamp: TideFootprintStampV0,
49 pub generation: u64,
50 pub external_sifs: Vec<OmenaQueryExternalSifInputV0>,
51 pub bridge_external_sif_urls: BTreeSet<String>,
52 pub lock_read_count: usize,
57 pub bridge_generation_count: usize,
58 pub trust_records: Vec<OmenaQueryExternalSifTrustV1>,
59}
60
61pub(crate) const EXTERNAL_SIF_FOOTPRINT: TideFootprintV0 = TideFootprintV0::of(&[
67 TideInputKindV0::DocumentSet,
68 TideInputKindV0::LockfileFingerprint,
69 TideInputKindV0::PackageManifest,
70 TideInputKindV0::ResolutionSettings,
71]);
72
73pub(crate) const TIDE_SETTLE_LANE_CONFIG: TideLaneConfigV0 = TideLaneConfigV0 {
76 aging_bound_ticks: u64::MAX,
77};
78
79pub(crate) fn refresh_external_sifs_for_state(state: &mut LspShellState) {
80 if state.external_sif_refresh_deferred {
81 crate::loop_trace!("sif-demand reason=state-refresh");
82 let tick = state.tide_tick;
83 state
84 .tide_sif_lane
85 .deposit(TideSifDemandV0::refresh(), tick);
86 return;
87 }
88 refresh_external_sifs_for_state_immediate(state);
89}
90
91fn refresh_external_sifs_for_state_immediate(state: &mut LspShellState) {
92 let bridge_result = resolve_in_process_external_sifs_for_lsp(state, &BTreeSet::new());
96 state.external_sif_bridge_generation_count = state
97 .external_sif_bridge_generation_count
98 .saturating_add(bridge_result.resolution.generation_count);
99 let external_sifs = bridge_result.resolution.external_sifs;
100
101 let trust_records = external_sif_trust_record_map(bridge_result.trust_records);
102 if state.resolution.external_sifs != external_sifs
103 || state.resolution.external_sif_trust_records != trust_records
104 {
105 state.resolution.external_sifs = external_sifs;
106 state.resolution.external_sif_trust_records = trust_records;
107 invalidate_external_sif_dependents(state);
108 }
109 state.resolution.bridge_external_sif_urls =
110 bridge_result.resolution.bridge_urls.into_iter().collect();
111}
112
113pub(crate) fn refresh_external_sifs_for_bridge_source_delta(
114 state: &mut LspShellState,
115 affected_document_uris: &[String],
116 previous_sources: &[String],
117 next_sources: &[String],
118) {
119 if state.external_sif_refresh_deferred {
120 let previous_set = previous_sources.iter().collect::<BTreeSet<_>>();
125 let next_set = next_sources.iter().collect::<BTreeSet<_>>();
126 if previous_set == next_set {
127 crate::loop_trace!(
128 "sif-demand SKIPPED reason=bridge-delta-equal len={}",
129 next_set.len()
130 );
131 return;
132 }
133 crate::loop_trace!(
134 "sif-demand reason=bridge-delta prev={} next={}",
135 previous_sources.len(),
136 next_sources.len()
137 );
138 state.tide_ledger.advance(&[TideInputKindV0::DocumentSet]);
142 let affected_file_ids = affected_document_uris
143 .iter()
144 .filter_map(|uri| state.document_file_id(uri))
145 .collect::<Vec<_>>();
146 state.tide_reopen_republish_window(TideDisownCauseV0::for_file_ids(
147 TideInputKindV0::DocumentSet,
148 affected_file_ids,
149 ));
150 let tick = state.tide_tick;
151 state
152 .tide_sif_lane
153 .deposit(TideSifDemandV0::refresh(), tick);
154 return;
155 }
156 let previous_sources = previous_sources.iter().cloned().collect::<BTreeSet<_>>();
157 let next_sources = next_sources.iter().cloned().collect::<BTreeSet<_>>();
158 if previous_sources == next_sources {
159 return;
160 }
161 if previous_sources
162 .iter()
163 .chain(next_sources.iter())
164 .any(|source| !source.starts_with("file://"))
165 {
166 refresh_external_sifs_for_state(state);
167 return;
168 }
169
170 let active_bridge_sources = active_bridge_sources_from_documents(state);
171 let mut changed = false;
172 let mut remove_urls = BTreeSet::new();
173 for source in previous_sources.difference(&next_sources) {
174 if active_bridge_sources.contains(source) {
175 continue;
176 }
177 collect_bridge_sif_urls_for_sources(
178 state,
179 std::iter::once(source.as_str()),
180 &BTreeSet::new(),
181 )
182 .into_iter()
183 .for_each(|url| {
184 remove_urls.insert(url);
185 });
186 }
187
188 if !remove_urls.is_empty() {
189 let before_len = state.resolution.external_sifs.len();
190 state.resolution.external_sifs.retain(|input| {
191 !state
192 .resolution
193 .bridge_external_sif_urls
194 .contains(input.canonical_url.as_str())
195 || !remove_urls.contains(input.canonical_url.as_str())
196 });
197 state
198 .resolution
199 .bridge_external_sif_urls
200 .retain(|url| !remove_urls.contains(url.as_str()));
201 changed |= before_len != state.resolution.external_sifs.len();
202 let live_canonical_urls = state
203 .resolution
204 .external_sifs
205 .iter()
206 .map(|input| input.sif.canonical_url.clone())
207 .collect::<BTreeSet<_>>();
208 let before_trust_len = state.resolution.external_sif_trust_records.len();
209 state
210 .resolution
211 .external_sif_trust_records
212 .retain(|url, _| live_canonical_urls.contains(url));
213 changed |= before_trust_len != state.resolution.external_sif_trust_records.len();
214 }
215
216 let mut covered = covered_external_sif_urls(state.resolution.external_sifs.as_slice());
217 for source in next_sources.difference(&previous_sources) {
218 if state
219 .resolution
220 .bridge_external_sif_urls
221 .contains(source.as_str())
222 {
223 continue;
224 }
225 let bridge_result = resolve_bridge_external_sifs_for_sources(
226 state,
227 std::iter::once(source.as_str()),
228 &covered,
229 );
230 let before_len = state.resolution.external_sifs.len();
231 extend_unique_external_sifs(
232 &mut state.resolution.external_sifs,
233 &mut covered,
234 bridge_result.resolution.external_sifs,
235 );
236 state
237 .resolution
238 .bridge_external_sif_urls
239 .extend(bridge_result.resolution.bridge_urls);
240 changed |= before_len != state.resolution.external_sifs.len();
241 for trust_record in bridge_result.trust_records {
242 changed |= state
243 .resolution
244 .external_sif_trust_records
245 .insert(trust_record.canonical_url.clone(), trust_record.clone())
246 .as_ref()
247 != Some(&trust_record);
248 }
249 state.external_sif_bridge_generation_count = state
250 .external_sif_bridge_generation_count
251 .saturating_add(bridge_result.resolution.generation_count);
252 }
253
254 if changed {
255 invalidate_external_sif_dependents(state);
256 }
257}
258
259pub(crate) fn bridge_sources_for_style_uris(
260 state: &LspShellState,
261 style_uris: &[String],
262) -> Vec<String> {
263 let mut sources = BTreeSet::new();
264 for uri in style_uris {
265 let Some(document) = state.document(uri.as_str()) else {
266 continue;
267 };
268 collect_bridge_sources_from_style_document(document, &mut sources);
269 }
270 sources.into_iter().collect()
271}
272
273pub fn enable_deferred_external_sif_refresh(state: &mut LspShellState) {
274 state.external_sif_refresh_deferred = true;
275 let tick = state.tide_tick;
276 state
277 .tide_sif_lane
278 .deposit(TideSifDemandV0::refresh(), tick);
279}
280
281pub fn prepare_deferred_external_sif_refresh_job(
282 state: &mut LspShellState,
283) -> Option<LspExternalSifRefreshJobV0> {
284 if !state.external_sif_refresh_deferred {
285 return None;
286 }
287 let inputs = TideGateInputsV0 {
291 frontier_passed: state.workspace_index_pending_file_count == 0,
292 idle: true,
293 };
294 let flush = state
295 .tide_sif_lane
296 .try_flush(inputs, state.tide_tick, &TIDE_SETTLE_LANE_CONFIG)?;
297 crate::loop_trace!(
298 "sif-job-prepared gen={} epoch={} docs={}",
299 flush.generation,
300 state.tide_ledger.epoch(),
301 state
302 .documents
303 .values()
304 .filter(|d| is_style_document_uri(d.uri.as_str()))
305 .count()
306 );
307 Some(LspExternalSifRefreshJobV0 {
308 stamp: state.tide_ledger.stamp(EXTERNAL_SIF_FOOTPRINT),
309 generation: flush.generation,
310 lockfiles: workspace_lockfiles(state),
311 documents: state
312 .documents
313 .values()
314 .map(AsRef::as_ref)
315 .filter(|document| is_style_document_uri(document.uri.as_str()))
316 .map(|document| LspExternalSifRefreshDocumentV0 {
317 uri: document.uri.clone(),
318 workspace_folder_uri: document.workspace_folder_uri.clone(),
319 text: document.text.clone(),
320 })
321 .collect(),
322 package_manifests: state.resolution.package_manifests.clone(),
323 resolution_inputs_by_workspace_uri: state
324 .resolution
325 .workspace_style_resolution_inputs
326 .clone(),
327 })
328}
329
330pub fn prepare_deferred_external_sif_refresh_cache_storage(
331 state: &LspShellState,
332) -> LspExternalSifRefreshCacheStorageV0 {
333 let mut by_workspace_uri = BTreeMap::new();
334 for folder in state.workspace_runtime_registry.folder_snapshots() {
335 if let Some(storage) =
336 bridge_cache_storage_for_workspace_uri(state, Some(folder.uri.as_str()))
337 {
338 by_workspace_uri.insert(folder.uri, storage);
339 }
340 }
341 let by_document_uri = state
342 .documents
343 .values()
344 .map(AsRef::as_ref)
345 .filter(|document| {
346 document.workspace_folder_uri.is_none() && is_style_document_uri(document.uri.as_str())
347 })
348 .filter_map(|document| {
349 bridge_cache_storage_for_document(state, None, document.uri.as_str())
350 .map(|storage| (document.uri.clone(), storage))
351 })
352 .collect();
353 LspExternalSifRefreshCacheStorageV0 {
354 by_workspace_uri,
355 by_document_uri,
356 }
357}
358
359pub fn collect_deferred_external_sif_refresh(
360 job: LspExternalSifRefreshJobV0,
361) -> LspExternalSifRefreshResultV0 {
362 collect_deferred_external_sif_refresh_with_cache_storage(
363 job,
364 LspExternalSifRefreshCacheStorageV0::default(),
365 )
366}
367
368pub fn collect_deferred_external_sif_refresh_with_cache_storage(
369 job: LspExternalSifRefreshJobV0,
370 cache_storage: LspExternalSifRefreshCacheStorageV0,
371) -> LspExternalSifRefreshResultV0 {
372 let LspExternalSifRefreshJobV0 {
373 stamp,
374 generation,
375 lockfiles: excluded_lockfiles,
376 documents,
377 package_manifests,
378 resolution_inputs_by_workspace_uri,
379 } = job;
380 drop(excluded_lockfiles);
381 let bridge_result = resolve_external_sifs_for_refresh_documents(
382 documents.as_slice(),
383 &[],
384 package_manifests.as_slice(),
385 &resolution_inputs_by_workspace_uri,
386 Some(&cache_storage),
387 );
388 let external_sifs = bridge_result.resolution.external_sifs;
389
390 LspExternalSifRefreshResultV0 {
391 stamp,
392 generation,
393 external_sifs,
394 bridge_external_sif_urls: bridge_result.resolution.bridge_urls.into_iter().collect(),
395 lock_read_count: 0,
396 bridge_generation_count: bridge_result.resolution.generation_count,
397 trust_records: bridge_result.trust_records,
398 }
399}
400
401pub fn apply_deferred_external_sif_refresh_result(
402 state: &mut LspShellState,
403 result: LspExternalSifRefreshResultV0,
404) -> bool {
405 if !state.tide_ledger.is_current(&result.stamp) {
406 crate::loop_trace!(
407 "sif-apply DISCARDED gen={} stamp_epoch={} ledger_epoch={}",
408 result.generation,
409 result.stamp.epoch,
410 state.tide_ledger.epoch()
411 );
412 state.tide_sif_lane.tide_completed(result.generation);
415 return false;
416 }
417 state.external_sif_bridge_generation_count = state
418 .external_sif_bridge_generation_count
419 .saturating_add(result.bridge_generation_count);
420 let trust_records = external_sif_trust_record_map(result.trust_records);
421 let changed = state.resolution.external_sifs != result.external_sifs
422 || state.resolution.external_sif_trust_records != trust_records;
423 crate::loop_trace!(
424 "sif-apply gen={} changed={} sifs {}->{}",
425 result.generation,
426 changed,
427 state.resolution.external_sifs.len(),
428 result.external_sifs.len()
429 );
430 if changed {
431 let demand =
435 republish_demand_for_external_sif_delta(state, result.external_sifs.as_slice());
436 state.resolution.external_sifs = result.external_sifs;
437 state.resolution.external_sif_trust_records = trust_records;
438 invalidate_external_sif_dependents(state);
439 state.tide_reopen_republish_window(TideDisownCauseV0::for_republish_demand(
442 TideInputKindV0::DocumentSet,
443 demand.clone(),
444 ));
445 let tick = state.tide_tick;
446 state.tide_republish_lane.deposit(demand, tick);
447 }
448 state.resolution.bridge_external_sif_urls = result.bridge_external_sif_urls;
449 state.tide_sif_lane.tide_completed(result.generation);
450 changed
451}
452
453#[cfg(feature = "salsa-style-diagnostics")]
460pub(crate) fn republish_demand_for_external_sif_delta(
461 state: &LspShellState,
462 next_external_sifs: &[OmenaQueryExternalSifInputV0],
463) -> TideRepublishDemandV0 {
464 let previous: BTreeMap<&str, &OmenaQueryExternalSifInputV0> = state
465 .resolution
466 .external_sifs
467 .iter()
468 .map(|input| (input.canonical_url.as_str(), input))
469 .collect();
470 let next: BTreeMap<&str, &OmenaQueryExternalSifInputV0> = next_external_sifs
471 .iter()
472 .map(|input| (input.canonical_url.as_str(), input))
473 .collect();
474 let mut changed_urls = BTreeSet::new();
475 for (url, input) in &next {
476 if previous.get(url).is_none_or(|prev| prev != input) {
477 changed_urls.insert(*url);
478 }
479 }
480 for url in previous.keys() {
481 if !next.contains_key(url) {
482 changed_urls.insert(*url);
483 }
484 }
485 if changed_urls.is_empty() {
486 return TideRepublishDemandV0::None;
487 }
488 let memo_slot = state.reverse_dependency_index_memo.borrow();
489 let Some(memo) = memo_slot.as_ref() else {
490 crate::loop_trace!(
491 "republish-demand all: {} changed sif urls, no reverse index",
492 changed_urls.len()
493 );
494 return TideRepublishDemandV0::All;
495 };
496 let corpus_input_mark = state
501 .tide_ledger
502 .mark(TideInputKindV0::DocumentText)
503 .max(state.tide_ledger.mark(TideInputKindV0::DocumentSet));
504 if memo.ledger_epoch < corpus_input_mark {
505 crate::loop_trace!(
506 "republish-demand all: reverse index stale (memo epoch {} < corpus mark {})",
507 memo.ledger_epoch,
508 corpus_input_mark
509 );
510 return TideRepublishDemandV0::All;
511 }
512 let mut seeds: BTreeSet<crate::LspFileId> = BTreeSet::new();
513 for url in &changed_urls {
514 let resolved_alias = next
518 .get(url)
519 .or_else(|| previous.get(url))
520 .map(|input| input.sif.canonical_url.as_str());
521 let dependents = memo
522 .index
523 .rev
524 .get(*url)
525 .or_else(|| resolved_alias.and_then(|alias| memo.index.rev.get(alias)));
526 let Some(dependents) = dependents else {
527 crate::loop_trace!("republish-demand all: unattributed sif url {url}");
528 return TideRepublishDemandV0::All;
529 };
530 for dependent in dependents {
531 let Some(file_id) = state.document_file_id(dependent) else {
532 crate::loop_trace!(
533 "republish-demand all: dependent has no admitted file id {dependent}"
534 );
535 return TideRepublishDemandV0::All;
536 };
537 seeds.insert(file_id);
538 }
539 }
540 crate::loop_trace!(
541 "republish-demand cone seeds={} changed_urls={}",
542 seeds.len(),
543 changed_urls.len()
544 );
545 TideRepublishDemandV0::cone(seeds)
546}
547
548#[cfg(not(feature = "salsa-style-diagnostics"))]
549pub(crate) fn republish_demand_for_external_sif_delta(
550 _state: &LspShellState,
551 _next_external_sifs: &[OmenaQueryExternalSifInputV0],
552) -> TideRepublishDemandV0 {
553 TideRepublishDemandV0::All
554}
555
556fn workspace_lockfiles(state: &LspShellState) -> Vec<PathBuf> {
557 let mut lockfiles = BTreeSet::new();
558 for folder in state.workspace_runtime_registry.folder_snapshots() {
559 let Some(root) = file_uri_to_path(folder.uri.as_str()).map(normalize_path) else {
560 continue;
561 };
562 if let Some(lockfile) = discover_omena_lockfile_for_workspace_root(root.as_path()) {
563 lockfiles.insert(lockfile);
564 }
565 }
566 lockfiles.into_iter().collect()
567}
568
569fn discover_omena_lockfile_for_workspace_root(root: &Path) -> Option<PathBuf> {
570 let mut current = Some(root);
571 while let Some(directory) = current {
572 let candidate = directory.join("omena.lock");
573 if candidate.exists() {
574 return Some(normalize_path(candidate));
575 }
576 current = directory.parent();
577 }
578 None
579}
580
581fn extend_unique_external_sifs(
582 output: &mut Vec<OmenaQueryExternalSifInputV0>,
583 covered: &mut BTreeSet<String>,
584 candidates: Vec<OmenaQueryExternalSifInputV0>,
585) {
586 for candidate in candidates {
587 if covered.insert(candidate.canonical_url.clone()) {
588 covered.insert(candidate.sif.canonical_url.clone());
589 output.push(candidate);
590 }
591 }
592}
593
594pub(crate) fn external_sif_trust_record_map(
595 records: Vec<OmenaQueryExternalSifTrustV1>,
596) -> BTreeMap<String, OmenaQueryExternalSifTrustV1> {
597 records
598 .into_iter()
599 .map(|record| (record.canonical_url.clone(), record))
600 .collect()
601}
602
603fn deduplicate_external_sif_trust_records(records: &mut Vec<OmenaQueryExternalSifTrustV1>) {
604 *records = external_sif_trust_record_map(std::mem::take(records))
605 .into_values()
606 .collect();
607}
608
609fn resolve_in_process_external_sifs_for_lsp(
610 state: &LspShellState,
611 existing_covered: &BTreeSet<String>,
612) -> OmenaQueryBridgeExternalSifTrustedResolutionV1 {
613 let mut existing_inputs = state
614 .resolution
615 .external_sifs
616 .iter()
617 .filter(|input| {
618 existing_covered.contains(input.canonical_url.as_str())
619 || existing_covered.contains(input.sif.canonical_url.as_str())
620 })
621 .cloned()
622 .collect::<Vec<_>>();
623 let mut combined = OmenaQueryBridgeExternalSifTrustedResolutionV1::default();
624 let mut bridge_urls = BTreeSet::new();
625
626 for document in state.documents.values().map(AsRef::as_ref) {
627 if !is_style_document_uri(document.uri.as_str()) {
628 continue;
629 }
630 let source = OmenaQueryStyleSourceInputV0 {
631 style_path: document.uri.clone(),
632 style_source: document.text.clone(),
633 };
634 let resolution_inputs =
635 resolution_inputs_for_document(state, document.workspace_folder_uri.as_deref());
636 let cache_storage = bridge_cache_storage_for_document(
637 state,
638 document.workspace_folder_uri.as_deref(),
639 document.uri.as_str(),
640 );
641 let result = if let Some(cache_storage) = cache_storage.as_ref() {
642 resolve_omena_query_bridge_external_sifs_for_style_sources_with_cache_storage_and_trust(
643 std::slice::from_ref(&source),
644 existing_inputs.as_slice(),
645 &resolution_inputs,
646 cache_storage,
647 )
648 } else {
649 resolve_omena_query_bridge_external_sifs_for_style_sources_with_trust(
650 std::slice::from_ref(&source),
651 existing_inputs.as_slice(),
652 &resolution_inputs,
653 )
654 };
655 combined.resolution.generation_count = combined
656 .resolution
657 .generation_count
658 .saturating_add(result.resolution.generation_count);
659 bridge_urls.extend(result.resolution.bridge_urls);
660 combined.trust_records.extend(result.trust_records);
661 for external_sif in result.resolution.external_sifs {
662 existing_inputs.push(external_sif.clone());
663 combined.resolution.external_sifs.push(external_sif);
664 }
665 }
666
667 combined.resolution.bridge_urls = bridge_urls.into_iter().collect();
668 deduplicate_external_sif_trust_records(&mut combined.trust_records);
669 combined
670}
671
672fn resolve_external_sifs_for_refresh_documents(
673 documents: &[LspExternalSifRefreshDocumentV0],
674 existing_external_sifs: &[OmenaQueryExternalSifInputV0],
675 package_manifests: &[omena_query::OmenaQueryStylePackageManifestV0],
676 resolution_inputs_by_workspace_uri: &std::collections::BTreeMap<
677 String,
678 OmenaQueryStyleResolutionInputsV0,
679 >,
680 cache_storage: Option<&LspExternalSifRefreshCacheStorageV0>,
681) -> OmenaQueryBridgeExternalSifTrustedResolutionV1 {
682 let mut existing_inputs = existing_external_sifs.to_vec();
683 let mut combined = OmenaQueryBridgeExternalSifTrustedResolutionV1::default();
684 let mut bridge_urls = BTreeSet::new();
685
686 for document in documents {
687 let source = OmenaQueryStyleSourceInputV0 {
688 style_path: document.uri.clone(),
689 style_source: document.text.clone(),
690 };
691 let resolution_inputs = document
692 .workspace_folder_uri
693 .as_deref()
694 .and_then(|uri| resolution_inputs_by_workspace_uri.get(uri))
695 .cloned()
696 .unwrap_or_else(|| OmenaQueryStyleResolutionInputsV0 {
697 package_manifests: package_manifests.to_vec(),
698 ..OmenaQueryStyleResolutionInputsV0::default()
699 });
700 let document_cache_storage = cache_storage.and_then(|storage| {
701 if let Some(workspace_uri) = document.workspace_folder_uri.as_ref() {
702 storage.by_workspace_uri.get(workspace_uri)
703 } else {
704 storage.by_document_uri.get(document.uri.as_str())
705 }
706 });
707 let result = if let Some(document_cache_storage) = document_cache_storage {
708 resolve_omena_query_bridge_external_sifs_for_style_sources_with_cache_storage_and_trust(
709 std::slice::from_ref(&source),
710 existing_inputs.as_slice(),
711 &resolution_inputs,
712 document_cache_storage,
713 )
714 } else {
715 resolve_omena_query_bridge_external_sifs_for_style_sources_with_trust(
716 std::slice::from_ref(&source),
717 existing_inputs.as_slice(),
718 &resolution_inputs,
719 )
720 };
721 combined.resolution.generation_count = combined
722 .resolution
723 .generation_count
724 .saturating_add(result.resolution.generation_count);
725 bridge_urls.extend(result.resolution.bridge_urls);
726 combined.trust_records.extend(result.trust_records);
727 for external_sif in result.resolution.external_sifs {
728 existing_inputs.push(external_sif.clone());
729 combined.resolution.external_sifs.push(external_sif);
730 }
731 }
732
733 combined.resolution.bridge_urls = bridge_urls.into_iter().collect();
734 deduplicate_external_sif_trust_records(&mut combined.trust_records);
735 combined
736}
737
738fn resolve_bridge_external_sifs_for_sources<'a>(
739 state: &LspShellState,
740 sources: impl Iterator<Item = &'a str>,
741 existing_covered: &BTreeSet<String>,
742) -> OmenaQueryBridgeExternalSifTrustedResolutionV1 {
743 let mut combined = OmenaQueryBridgeExternalSifTrustedResolutionV1::default();
744 let mut covered = existing_covered.clone();
745 let mut bridge_urls = BTreeSet::new();
746 for source in sources
747 .filter(|source| source.starts_with("file://") && !existing_covered.contains(*source))
748 {
749 let owner = state.workspace_runtime_registry.resolve_owner_uri(source);
750 let cache_storage = bridge_cache_storage_for_document(state, owner.as_deref(), source);
751 let result = if let Some(cache_storage) = cache_storage.as_ref() {
752 resolve_omena_query_bridge_external_sifs_for_seed_pairs_with_cache_storage_and_trust(
753 std::iter::once((source.to_string(), source.to_string())),
754 &[],
755 &OmenaQueryStyleResolutionInputsV0::default(),
756 cache_storage,
757 )
758 } else {
759 resolve_omena_query_bridge_external_sifs_for_seed_pairs_with_trust(
760 std::iter::once((source.to_string(), source.to_string())),
761 &[],
762 &OmenaQueryStyleResolutionInputsV0::default(),
763 )
764 };
765 combined.resolution.generation_count = combined
766 .resolution
767 .generation_count
768 .saturating_add(result.resolution.generation_count);
769 bridge_urls.extend(result.resolution.bridge_urls);
770 combined.trust_records.extend(result.trust_records);
771 extend_unique_external_sifs(
772 &mut combined.resolution.external_sifs,
773 &mut covered,
774 result.resolution.external_sifs,
775 );
776 }
777 combined.resolution.bridge_urls = bridge_urls.into_iter().collect();
778 deduplicate_external_sif_trust_records(&mut combined.trust_records);
779 combined
780}
781
782fn collect_bridge_sif_urls_for_sources<'a>(
783 state: &LspShellState,
784 sources: impl Iterator<Item = &'a str>,
785 existing_covered: &BTreeSet<String>,
786) -> BTreeSet<String> {
787 resolve_bridge_external_sifs_for_sources(state, sources, existing_covered)
788 .resolution
789 .bridge_urls
790 .into_iter()
791 .collect()
792}
793
794fn bridge_cache_storage_for_workspace_uri(
795 state: &LspShellState,
796 workspace_folder_uri: Option<&str>,
797) -> Option<OmenaQueryExternalSifStorageV0> {
798 let workspace_folder_uri = workspace_folder_uri?;
799 let workspace_root = file_uri_to_path(workspace_folder_uri)?;
800 let workspace_cache_root = crate::cache_root::resolved_bridge_workspace_cache_root(
801 &state.resolution.cache_storage,
802 workspace_folder_uri,
803 workspace_root.as_path(),
804 )?;
805 Some(
806 OmenaQueryExternalSifStorageV0::from_workspace_cache_root_and_identity(
807 workspace_cache_root,
808 workspace_folder_uri,
809 )
810 .with_recorded_verdict_dir(
811 workspace_root
812 .join(".cache")
813 .join("omena")
814 .join(OMENA_SIF_SHARD_VERDICT_DIR_V1),
815 ),
816 )
817}
818
819pub(crate) fn bridge_cache_storage_for_document(
820 state: &LspShellState,
821 workspace_folder_uri: Option<&str>,
822 document_uri: &str,
823) -> Option<OmenaQueryExternalSifStorageV0> {
824 if let Some(workspace_folder_uri) = workspace_folder_uri {
825 return bridge_cache_storage_for_workspace_uri(state, Some(workspace_folder_uri));
826 }
827 let document_path = file_uri_to_path(document_uri)?;
828 let document_root = document_path.parent()?;
829 let workspace_identity = document_root.to_string_lossy();
830 let workspace_cache_root = crate::cache_root::resolved_bridge_workspace_cache_root(
831 &state.resolution.cache_storage,
832 workspace_identity.as_ref(),
833 document_root,
834 )?;
835 Some(
836 OmenaQueryExternalSifStorageV0::from_workspace_cache_root_and_identity(
837 workspace_cache_root,
838 workspace_identity,
839 )
840 .with_recorded_verdict_dir(
841 document_root
842 .join(".cache")
843 .join("omena")
844 .join(OMENA_SIF_SHARD_VERDICT_DIR_V1),
845 ),
846 )
847}
848
849fn resolution_inputs_for_document(
850 state: &LspShellState,
851 workspace_folder_uri: Option<&str>,
852) -> OmenaQueryStyleResolutionInputsV0 {
853 workspace_folder_uri
854 .and_then(|uri| {
855 state
856 .resolution
857 .workspace_style_resolution_inputs
858 .get(uri)
859 .cloned()
860 })
861 .unwrap_or_else(|| OmenaQueryStyleResolutionInputsV0 {
862 package_manifests: state.resolution.package_manifests.clone(),
863 ..OmenaQueryStyleResolutionInputsV0::default()
864 })
865}
866
867fn active_bridge_sources_from_documents(state: &LspShellState) -> BTreeSet<String> {
868 let mut sources = BTreeSet::new();
869 for document in state.documents.values() {
870 collect_bridge_sources_from_style_document(document, &mut sources);
871 }
872 sources
873}
874
875fn collect_bridge_sources_from_style_document(
876 document: &LspTextDocumentState,
877 sources: &mut BTreeSet<String>,
878) {
879 let Some(summary) = document.style_summary.as_ref() else {
880 return;
881 };
882 let edge_sources = summary
883 .sass_module_use_sources
884 .iter()
885 .map(String::as_str)
886 .chain(
887 summary
888 .sass_module_forward_sources
889 .iter()
890 .map(String::as_str),
891 );
892 for edge_source in edge_sources {
893 if edge_source.starts_with("file://") {
894 sources.insert(edge_source.to_string());
895 }
896 }
897}
898
899fn covered_external_sif_urls(inputs: &[OmenaQueryExternalSifInputV0]) -> BTreeSet<String> {
900 inputs
901 .iter()
902 .flat_map(|input| [input.canonical_url.clone(), input.sif.canonical_url.clone()])
903 .collect()
904}
905
906fn invalidate_external_sif_dependents(state: &mut LspShellState) {
907 *state.workspace_occurrence_index_memo_lock() = None;
908 if let Ok(mut memo) = state.cascade_narrowing_substrate_memo.lock() {
909 *memo = None;
910 }
911}