1use std::sync::Arc;
2use std::sync::atomic::{AtomicBool, Ordering};
3use std::time::Duration;
4
5use super::records::RecordTable;
6
7#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
8pub struct ResourceGeneration(u64);
9
10impl ResourceGeneration {
11 pub fn new(value: u64) -> Self {
12 Self(value)
13 }
14
15 pub fn value(self) -> u64 {
16 self.0
17 }
18}
19
20#[derive(Clone, Debug)]
21pub struct WorkspaceRequest {
22 pub label: String,
23 pub catalog: CatalogRequest,
24 cancellation: WorkspaceCancellation,
25}
26
27impl PartialEq for WorkspaceRequest {
28 fn eq(&self, other: &Self) -> bool {
29 self.label == other.label && self.catalog == other.catalog
30 }
31}
32
33impl Eq for WorkspaceRequest {}
34
35#[derive(Clone, Debug, Default)]
36pub struct WorkspaceCancellation(Arc<AtomicBool>);
37
38impl WorkspaceCancellation {
39 pub fn cancel(&self) {
40 self.0.store(true, Ordering::Release);
41 }
42
43 pub fn is_cancelled(&self) -> bool {
44 self.0.load(Ordering::Acquire)
45 }
46
47 pub fn check(&self, resource: WorkspaceResource) -> WorkspaceResult<()> {
48 if self.is_cancelled() {
49 return Err(WorkspaceFailure::new(resource, "workspace build cancelled"));
50 }
51 Ok(())
52 }
53}
54
55#[derive(Clone, Copy, Debug, Eq, PartialEq)]
56pub enum CatalogRequest {
57 Refresh,
58 ReuseCurrent,
59}
60
61impl WorkspaceRequest {
62 pub fn new(label: impl Into<String>) -> Self {
63 Self {
64 label: label.into(),
65 catalog: CatalogRequest::Refresh,
66 cancellation: WorkspaceCancellation::default(),
67 }
68 }
69
70 pub fn with_cancellation(mut self, cancellation: WorkspaceCancellation) -> Self {
71 self.cancellation = cancellation;
72 self
73 }
74
75 pub fn cancellation(&self) -> &WorkspaceCancellation {
76 &self.cancellation
77 }
78
79 pub fn reuse_current_catalog(mut self) -> Self {
80 self.catalog = CatalogRequest::ReuseCurrent;
81 self
82 }
83
84 pub fn should_reuse_current_catalog(&self) -> bool {
85 self.catalog == CatalogRequest::ReuseCurrent
86 }
87}
88
89#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
90pub struct SourceId {
91 file: u32,
92}
93
94impl SourceId {
95 pub fn at(file: usize) -> Self {
96 Self { file: file as u32 }
97 }
98
99 pub fn parse(value: &str) -> Option<Self> {
100 let rest = value.strip_prefix("source:")?;
101 let file = rest.split(':').next()?;
102 Some(Self {
103 file: file.parse().ok()?,
104 })
105 }
106
107 pub fn file(self) -> usize {
108 self.file as usize
109 }
110}
111
112impl std::fmt::Display for SourceId {
113 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 write!(formatter, "source:{}", self.file)
115 }
116}
117
118#[derive(Clone, Debug, Eq, PartialEq)]
119pub struct SourceUnit {
120 pub id: SourceId,
121 pub display_name: String,
122 pub language: Option<String>,
123}
124
125impl SourceUnit {
126 pub fn new(id: SourceId, display_name: impl Into<String>) -> Self {
127 Self {
128 id,
129 display_name: display_name.into(),
130 language: None,
131 }
132 }
133
134 pub fn with_language(
135 id: SourceId,
136 display_name: impl Into<String>,
137 language: impl Into<String>,
138 ) -> Self {
139 Self {
140 id,
141 display_name: display_name.into(),
142 language: Some(language.into()),
143 }
144 }
145}
146
147#[derive(Clone, Debug, Eq, PartialEq)]
148pub struct SourceCatalog {
149 pub generation: ResourceGeneration,
150 pub sources: Vec<SourceUnit>,
151}
152
153impl SourceCatalog {
154 pub fn new(generation: ResourceGeneration, mut sources: Vec<SourceUnit>) -> Self {
155 sources.shrink_to_fit();
156 Self {
157 generation,
158 sources,
159 }
160 }
161}
162
163#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
164pub struct SymbolId {
165 file: u32,
166 def: u32,
167}
168
169impl SymbolId {
170 pub fn at(file: usize, def: usize) -> Self {
171 Self {
172 file: file as u32,
173 def: def as u32,
174 }
175 }
176
177 pub fn parse(value: &str) -> Option<Self> {
178 let rest = value.strip_prefix("symbol:")?;
179 let (file, def) = rest.split_once(':')?;
180 Some(Self {
181 file: file.parse().ok()?,
182 def: def.parse().ok()?,
183 })
184 }
185
186 pub fn file(self) -> usize {
187 self.file as usize
188 }
189
190 pub fn def(self) -> usize {
191 self.def as usize
192 }
193}
194
195impl std::fmt::Display for SymbolId {
196 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197 write!(formatter, "symbol:{}:{}", self.file, self.def)
198 }
199}
200
201#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
202pub struct SymbolLocation {
203 pub file: usize,
204 pub symbol: usize,
205}
206
207#[derive(Clone, Debug, Eq, PartialEq)]
208pub struct SymbolRecord {
209 pub id: SymbolId,
210 pub source: SourceId,
211 pub identity: Arc<str>,
212 pub name: String,
213 pub kind: String,
214 pub visibility: String,
215 pub signature: String,
216 pub call_name: Option<String>,
217 pub call_arity: Option<usize>,
218 pub navigable: bool,
219 pub line_range: Option<(u32, u32)>,
220 pub parent: Option<SymbolId>,
221}
222
223impl SymbolRecord {
224 pub fn new(
225 id: SymbolId,
226 source: SourceId,
227 name: impl Into<String>,
228 kind: impl Into<String>,
229 ) -> Self {
230 Self {
231 identity: Arc::from(id.to_string()),
232 id,
233 source,
234 name: name.into(),
235 kind: kind.into(),
236 visibility: String::new(),
237 signature: String::new(),
238 call_name: None,
239 call_arity: None,
240 navigable: true,
241 line_range: None,
242 parent: None,
243 }
244 }
245}
246
247#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
248pub struct ReferenceId {
249 file: u32,
250 reference: u32,
251}
252
253impl ReferenceId {
254 pub fn at(file: usize, reference: usize) -> Self {
255 Self {
256 file: file as u32,
257 reference: reference as u32,
258 }
259 }
260
261 pub fn parse(value: &str) -> Option<Self> {
262 let rest = value.strip_prefix("reference:")?;
263 let (file, reference) = rest.split_once(':')?;
264 Some(Self {
265 file: file.parse().ok()?,
266 reference: reference.parse().ok()?,
267 })
268 }
269
270 pub fn file(self) -> usize {
271 self.file as usize
272 }
273
274 pub fn reference(self) -> usize {
275 self.reference as usize
276 }
277}
278
279impl std::fmt::Display for ReferenceId {
280 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281 write!(formatter, "reference:{}:{}", self.file, self.reference)
282 }
283}
284
285#[derive(Clone, Debug, Eq, PartialEq)]
286pub struct ReferenceRecord {
287 pub id: ReferenceId,
288 pub source: SourceId,
289 pub source_symbol: SymbolId,
290 pub target_identity: Arc<str>,
291 pub kind: String,
292 pub call_name: Option<String>,
293 pub call_arity: Option<usize>,
294 pub confidence: Option<String>,
295 pub receiver: Option<String>,
296 pub alias: Option<String>,
297 pub line_range: Option<(u32, u32)>,
298}
299
300impl ReferenceRecord {
301 pub fn new(
302 id: ReferenceId,
303 source: SourceId,
304 source_symbol: SymbolId,
305 target_identity: impl Into<Arc<str>>,
306 kind: impl Into<String>,
307 line_range: Option<(u32, u32)>,
308 ) -> Self {
309 Self {
310 id,
311 source,
312 source_symbol,
313 target_identity: target_identity.into(),
314 kind: kind.into(),
315 call_name: None,
316 call_arity: None,
317 confidence: None,
318 receiver: None,
319 alias: None,
320 line_range,
321 }
322 }
323
324 pub fn with_call_metadata(
325 mut self,
326 call_name: Option<String>,
327 call_arity: Option<usize>,
328 ) -> Self {
329 self.call_name = call_name;
330 self.call_arity = call_arity;
331 self
332 }
333
334 pub fn with_metadata(
335 mut self,
336 confidence: Option<String>,
337 receiver: Option<String>,
338 alias: Option<String>,
339 ) -> Self {
340 self.confidence = confidence;
341 self.receiver = receiver;
342 self.alias = alias;
343 self
344 }
345}
346
347#[derive(Clone, Debug, Eq, PartialEq)]
348pub struct SourceFileRecord {
349 pub id: SourceId,
350 pub uri: String,
351 pub source_root: usize,
352 pub path: String,
353 pub rel_path: String,
354 pub anchor: String,
355 pub language: String,
356 pub text: String,
357}
358
359#[derive(Clone, Debug, Eq, PartialEq)]
360pub struct CodeIndex {
361 pub generation: ResourceGeneration,
362 pub catalog_generation: ResourceGeneration,
363 pub identity_scheme: String,
364 pub sources: Vec<SourceFileRecord>,
365 pub symbols: RecordTable<SymbolRecord>,
366 pub references: RecordTable<ReferenceRecord>,
367 pub timings: CodeIndexTimings,
368}
369
370#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
371pub struct CodeIndexTimings {
372 pub extract_sources: Duration,
373 pub semantic_index: Duration,
374 pub total: Duration,
375}
376
377impl CodeIndex {
378 pub fn new(
379 generation: ResourceGeneration,
380 catalog_generation: ResourceGeneration,
381 symbols: Vec<SymbolRecord>,
382 ) -> Self {
383 Self::with_references(generation, catalog_generation, symbols, Vec::new())
384 }
385
386 pub fn with_references(
387 generation: ResourceGeneration,
388 catalog_generation: ResourceGeneration,
389 symbols: Vec<SymbolRecord>,
390 references: Vec<ReferenceRecord>,
391 ) -> Self {
392 Self {
393 generation,
394 catalog_generation,
395 identity_scheme: crate::DEFAULT_IDENTITY_SCHEME.to_string(),
396 sources: Vec::new(),
397 symbols: RecordTable::from_records(symbols),
398 references: RecordTable::from_records(references),
399 timings: CodeIndexTimings::default(),
400 }
401 }
402}
403
404#[derive(Clone, Debug, Eq, PartialEq)]
405pub struct LinkageEdge {
406 pub reference: ReferenceId,
407 pub target: SymbolId,
408 pub evidence: ResolutionEvidence,
409}
410
411impl LinkageEdge {
412 pub fn new(reference: ReferenceId, target: SymbolId) -> Self {
413 Self::with_evidence(reference, target, ResolutionEvidence::ExactBinding)
414 }
415
416 pub fn with_evidence(
417 reference: ReferenceId,
418 target: SymbolId,
419 evidence: ResolutionEvidence,
420 ) -> Self {
421 Self {
422 reference,
423 target,
424 evidence,
425 }
426 }
427}
428
429#[derive(Clone, Copy, Debug, Eq, PartialEq)]
430pub enum ResolutionEvidence {
431 ExactBinding,
432 LocalBinding,
433 GlobalBinding,
434 TypeConstraint,
435 Mro,
436 Injected,
437 NameMatch,
438}
439
440impl ResolutionEvidence {
441 pub fn as_str(self) -> &'static str {
442 match self {
443 Self::ExactBinding => "exact_binding",
444 Self::LocalBinding => "local_binding",
445 Self::GlobalBinding => "global_binding",
446 Self::TypeConstraint => "type_constraint",
447 Self::Mro => "mro",
448 Self::Injected => "injected",
449 Self::NameMatch => "name_match",
450 }
451 }
452
453 pub fn rank(self) -> u8 {
454 match self {
455 Self::ExactBinding => 100,
456 Self::LocalBinding | Self::TypeConstraint => 90,
457 Self::Mro => 85,
458 Self::GlobalBinding | Self::Injected => 80,
459 Self::NameMatch => 10,
460 }
461 }
462}
463
464#[derive(Clone, Copy, Debug, Eq, PartialEq)]
465pub enum CandidateReason {
466 WeakNameMatch,
467 MultipleTargets,
468 AmbiguousLookup,
469}
470
471impl CandidateReason {
472 pub fn as_str(self) -> &'static str {
473 match self {
474 Self::WeakNameMatch => "weak_name_match",
475 Self::MultipleTargets => "multiple_targets",
476 Self::AmbiguousLookup => "ambiguous_lookup",
477 }
478 }
479}
480
481#[derive(Clone, Copy, Debug, Eq, PartialEq)]
482pub enum CandidateScope {
483 Local,
484 Global,
485 Builtin,
486 Injected,
487 Unknown,
488}
489
490impl CandidateScope {
491 pub fn as_str(self) -> &'static str {
492 match self {
493 Self::Local => "local",
494 Self::Global => "global",
495 Self::Builtin => "builtin",
496 Self::Injected => "injected",
497 Self::Unknown => "unknown",
498 }
499 }
500}
501
502#[derive(Clone, Debug, Eq, PartialEq)]
503pub struct CandidateReference {
504 pub reference: ReferenceId,
505 pub targets: Vec<SymbolId>,
506 pub reason: CandidateReason,
507 pub scope: CandidateScope,
508 pub evidence: ResolutionEvidence,
509}
510
511impl CandidateReference {
512 pub fn new(
513 reference: ReferenceId,
514 targets: Vec<SymbolId>,
515 reason: CandidateReason,
516 scope: CandidateScope,
517 evidence: ResolutionEvidence,
518 ) -> Self {
519 Self {
520 reference,
521 targets,
522 reason,
523 scope,
524 evidence,
525 }
526 }
527}
528
529#[derive(Clone, Copy, Debug, Eq, PartialEq)]
530pub enum DynamicReason {
531 DynamicAttribute,
532 DescriptorOrFrameworkInjected,
533 DuckTypedCandidateSet,
534 MixinContract,
535 ExternalDependencyUnindexed,
536 RuntimeImport,
537 RuntimeMutation,
538 PreprocessorExpansion,
539 InsufficientLocalFacts,
540}
541
542impl DynamicReason {
543 pub fn as_str(self) -> &'static str {
544 match self {
545 Self::DynamicAttribute => "dynamic_attribute",
546 Self::DescriptorOrFrameworkInjected => "descriptor_or_framework_injected",
547 Self::DuckTypedCandidateSet => "duck_typed_candidate_set",
548 Self::MixinContract => "mixin_contract",
549 Self::ExternalDependencyUnindexed => "external_dependency_unindexed",
550 Self::RuntimeImport => "runtime_import",
551 Self::RuntimeMutation => "runtime_mutation",
552 Self::PreprocessorExpansion => "preprocessor_expansion",
553 Self::InsufficientLocalFacts => "insufficient_local_facts",
554 }
555 }
556}
557
558#[derive(Clone, Debug, Eq, PartialEq)]
559pub struct DynamicReference {
560 pub reference: ReferenceId,
561 pub target_identity: Arc<str>,
562 pub reason: DynamicReason,
563 pub candidates: Vec<SymbolId>,
564}
565
566impl DynamicReference {
567 pub fn new(
568 reference: ReferenceId,
569 target_identity: impl Into<Arc<str>>,
570 reason: DynamicReason,
571 candidates: Vec<SymbolId>,
572 ) -> Self {
573 Self {
574 reference,
575 target_identity: target_identity.into(),
576 reason,
577 candidates,
578 }
579 }
580}
581
582#[derive(Clone, Copy, Debug, Eq, PartialEq)]
583pub enum ExternalReferenceOrigin {
584 Sdk,
585 Dependency,
586 Injected,
587 UnknownExternal,
588}
589
590impl ExternalReferenceOrigin {
591 pub fn label(self) -> &'static str {
592 match self {
593 Self::Sdk => "sdk",
594 Self::Dependency => "dependency",
595 Self::Injected => "injected",
596 Self::UnknownExternal => "unknown_external",
597 }
598 }
599}
600
601#[derive(Clone, Debug, Eq, PartialEq)]
602pub struct ExternalReference {
603 pub reference: ReferenceId,
604 pub target_identity: Arc<str>,
605 pub origin: ExternalReferenceOrigin,
606}
607
608impl ExternalReference {
609 pub fn new(
610 reference: ReferenceId,
611 target_identity: impl Into<Arc<str>>,
612 origin: ExternalReferenceOrigin,
613 ) -> Self {
614 Self {
615 reference,
616 target_identity: target_identity.into(),
617 origin,
618 }
619 }
620}
621
622#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
626pub enum UnresolvedReason {
627 ManifestBlocked,
628 Visibility,
629 LanguageBoundary,
630 MissingQuery,
631 NoCandidate,
632 Ambiguous,
633 UnsupportedLanguageRule,
634 IncompleteExtractorMetadata,
635}
636
637impl UnresolvedReason {
638 pub fn as_str(&self) -> &'static str {
639 match self {
640 Self::ManifestBlocked => "manifest_blocked",
641 Self::Visibility => "visibility",
642 Self::LanguageBoundary => "language_boundary",
643 Self::MissingQuery => "missing_query",
644 Self::NoCandidate => "no_candidate",
645 Self::Ambiguous => "ambiguous",
646 Self::UnsupportedLanguageRule => "unsupported_language_rule",
647 Self::IncompleteExtractorMetadata => "incomplete_extractor_metadata",
648 }
649 }
650}
651
652#[derive(Clone, Debug, Eq, PartialEq)]
653pub struct UnresolvedReference {
654 pub reference: ReferenceId,
655 pub target_identity: Arc<str>,
656 pub reason: UnresolvedReason,
657}
658
659impl UnresolvedReference {
660 pub fn new(
661 reference: ReferenceId,
662 target_identity: impl Into<Arc<str>>,
663 reason: UnresolvedReason,
664 ) -> Self {
665 Self {
666 reference,
667 target_identity: target_identity.into(),
668 reason,
669 }
670 }
671}
672
673#[derive(Clone, Debug, Eq, PartialEq)]
674pub struct LinkageSnapshot {
675 pub generation: ResourceGeneration,
676 pub index_generation: ResourceGeneration,
677 pub resolved_refs: usize,
678 pub candidate_refs: usize,
679 pub external_refs: usize,
680 pub dynamic_refs: usize,
681 pub blocked_refs: usize,
682 pub manifest_blocked_refs: usize,
684 pub unresolved_refs: usize,
685 pub ambiguous_refs: usize,
688 pub resolved: Vec<LinkageEdge>,
689 pub candidates: Vec<CandidateReference>,
690 pub external: Vec<ExternalReference>,
691 pub dynamic: Vec<DynamicReference>,
692 pub blocked: Vec<UnresolvedReference>,
693 pub manifest_blocked: Vec<UnresolvedReference>,
695 pub unresolved: Vec<UnresolvedReference>,
696 pub read_index: LinkageReadIndexHandle,
697}
698
699#[derive(Debug)]
700pub struct LinkageReadIndex {
701 pub(crate) incoming: rustc_hash::FxHashMap<SymbolId, Vec<ReferenceId>>,
702 pub(crate) targets: rustc_hash::FxHashMap<ReferenceId, SymbolId>,
703}
704
705impl LinkageReadIndex {
706 pub fn from_edges(edges: &[LinkageEdge]) -> Self {
707 let mut incoming = rustc_hash::FxHashMap::<SymbolId, Vec<ReferenceId>>::default();
708 let mut targets = rustc_hash::FxHashMap::<ReferenceId, SymbolId>::default();
709 for edge in edges {
710 let LinkageEdge {
711 reference, target, ..
712 } = edge.clone();
713 targets.entry(reference).or_insert(target);
714 incoming.entry(target).or_default().push(reference);
715 }
716 Self { incoming, targets }
717 }
718
719 pub fn incoming(&self, symbol: &SymbolId) -> &[ReferenceId] {
720 self.incoming.get(symbol).map(Vec::as_slice).unwrap_or(&[])
721 }
722
723 pub fn resolved_target(&self, reference: &ReferenceId) -> Option<&SymbolId> {
724 self.targets.get(reference)
725 }
726}
727
728#[derive(Clone, Debug, Default)]
729pub struct LinkageReadIndexHandle(Option<Arc<LinkageReadIndex>>);
730
731impl LinkageReadIndexHandle {
732 pub fn from_edges(edges: &[LinkageEdge]) -> Self {
733 Self(Some(Arc::new(LinkageReadIndex::from_edges(edges))))
734 }
735
736 pub fn get(&self) -> Option<&LinkageReadIndex> {
737 self.0.as_deref()
738 }
739}
740
741impl PartialEq for LinkageReadIndexHandle {
742 fn eq(&self, _other: &Self) -> bool {
743 true
744 }
745}
746
747impl Eq for LinkageReadIndexHandle {}
748
749impl LinkageSnapshot {
750 pub fn new(
751 generation: ResourceGeneration,
752 index_generation: ResourceGeneration,
753 resolved_refs: usize,
754 unresolved_refs: usize,
755 ) -> Self {
756 Self {
757 generation,
758 index_generation,
759 resolved_refs,
760 candidate_refs: 0,
761 external_refs: 0,
762 dynamic_refs: 0,
763 blocked_refs: 0,
764 manifest_blocked_refs: 0,
765 unresolved_refs,
766 ambiguous_refs: 0,
767 resolved: Vec::new(),
768 candidates: Vec::new(),
769 external: Vec::new(),
770 dynamic: Vec::new(),
771 blocked: Vec::new(),
772 manifest_blocked: Vec::new(),
773 unresolved: Vec::new(),
774 read_index: LinkageReadIndexHandle::default(),
775 }
776 }
777
778 pub fn with_refs(
779 generation: ResourceGeneration,
780 index_generation: ResourceGeneration,
781 mut resolved: Vec<LinkageEdge>,
782 mut unresolved: Vec<UnresolvedReference>,
783 ) -> Self {
784 resolved.shrink_to_fit();
785 unresolved.shrink_to_fit();
786 let read_index = LinkageReadIndexHandle::from_edges(&resolved);
787 Self {
788 generation,
789 index_generation,
790 resolved_refs: resolved.len(),
791 candidate_refs: 0,
792 external_refs: 0,
793 dynamic_refs: 0,
794 blocked_refs: 0,
795 manifest_blocked_refs: 0,
796 unresolved_refs: unresolved.len(),
797 ambiguous_refs: 0,
798 resolved,
799 candidates: Vec::new(),
800 external: Vec::new(),
801 dynamic: Vec::new(),
802 blocked: Vec::new(),
803 manifest_blocked: Vec::new(),
804 unresolved,
805 read_index,
806 }
807 }
808}
809
810#[derive(Clone, Debug, Eq, PartialEq)]
811pub struct ChangeOverlay {
812 pub generation: ResourceGeneration,
813 pub catalog_generation: ResourceGeneration,
814 pub index_generation: ResourceGeneration,
815 pub scope: String,
816 pub resources: Vec<ChangeResource>,
817 pub diagnostics: Vec<String>,
818 pub changed_symbols: Vec<SymbolId>,
819 pub changes: Vec<ChangeRecord>,
820 pub semantic: Option<std::sync::Arc<crate::changes::semantic::review::SemanticReview>>,
821}
822
823pub struct ChangeOverlayReport {
824 pub generation: ResourceGeneration,
825 pub catalog_generation: ResourceGeneration,
826 pub index_generation: ResourceGeneration,
827 pub scope: String,
828 pub resources: Vec<ChangeResource>,
829 pub diagnostics: Vec<String>,
830 pub changes: Vec<ChangeRecord>,
831}
832
833impl ChangeOverlay {
834 pub fn new(
835 generation: ResourceGeneration,
836 catalog_generation: ResourceGeneration,
837 index_generation: ResourceGeneration,
838 mut changed_symbols: Vec<SymbolId>,
839 ) -> Self {
840 changed_symbols.shrink_to_fit();
841 Self {
842 generation,
843 catalog_generation,
844 index_generation,
845 scope: "HEAD..worktree".to_string(),
846 resources: Vec::new(),
847 diagnostics: Vec::new(),
848 changed_symbols,
849 changes: Vec::new(),
850 semantic: None,
851 }
852 }
853
854 pub fn with_records(
855 generation: ResourceGeneration,
856 catalog_generation: ResourceGeneration,
857 index_generation: ResourceGeneration,
858 mut changes: Vec<ChangeRecord>,
859 ) -> Self {
860 changes.shrink_to_fit();
861 let changed_symbols = changes.iter().filter_map(|change| change.symbol).fold(
862 Vec::new(),
863 |mut out, symbol| {
864 if !out.contains(&symbol) {
865 out.push(symbol);
866 }
867 out
868 },
869 );
870 let mut changed_symbols = changed_symbols;
871 changed_symbols.shrink_to_fit();
872 Self {
873 generation,
874 catalog_generation,
875 index_generation,
876 scope: "HEAD..worktree".to_string(),
877 resources: Vec::new(),
878 diagnostics: Vec::new(),
879 changed_symbols,
880 changes,
881 semantic: None,
882 }
883 }
884
885 pub fn from_report(report: ChangeOverlayReport) -> Self {
886 let mut resources = report.resources;
887 let mut diagnostics = report.diagnostics;
888 resources.shrink_to_fit();
889 diagnostics.shrink_to_fit();
890 let mut overlay = Self::with_records(
891 report.generation,
892 report.catalog_generation,
893 report.index_generation,
894 report.changes,
895 );
896 overlay.scope = report.scope;
897 overlay.resources = resources;
898 overlay.diagnostics = diagnostics;
899 overlay
900 }
901}
902
903#[derive(Clone, Debug, Eq, PartialEq)]
904pub struct ChangeResource {
905 pub available: bool,
906 pub label: String,
907 pub message: String,
908}
909
910#[derive(Clone, Copy, Debug, Eq, PartialEq)]
911pub enum ChangeStatus {
912 Added,
913 Modified,
914 Removed,
915}
916
917impl ChangeStatus {
918 pub fn label(self) -> &'static str {
919 match self {
920 Self::Added => "added",
921 Self::Modified => "modified",
922 Self::Removed => "removed",
923 }
924 }
925}
926
927#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
928pub struct ChangeId(String);
929
930impl ChangeId {
931 pub fn new(value: impl Into<String>) -> Self {
932 Self(value.into())
933 }
934
935 pub fn as_str(&self) -> &str {
936 &self.0
937 }
938}
939
940#[derive(Clone, Debug, Eq, PartialEq)]
941pub struct ChangeRecord {
942 pub id: ChangeId,
943 pub status: ChangeStatus,
944 pub source: Option<SourceId>,
945 pub source_uri: Option<String>,
946 pub symbol: Option<SymbolId>,
947 pub identity: String,
948 pub language: String,
949 pub file_path: String,
950 pub name: String,
951 pub kind: String,
952 pub line_range: Option<(u32, u32)>,
953 pub hunk_count: usize,
954}
955
956#[derive(Clone, Debug, Eq, PartialEq)]
957pub struct ChangeRecordCoreFields {
958 pub id: ChangeId,
959 pub status: ChangeStatus,
960 pub identity: String,
961 pub language: String,
962 pub file_path: String,
963 pub name: String,
964 pub kind: String,
965 pub line_range: Option<(u32, u32)>,
966 pub hunk_count: usize,
967}
968
969impl ChangeRecord {
970 pub fn new(fields: ChangeRecordCoreFields) -> Self {
971 Self {
972 id: fields.id,
973 status: fields.status,
974 source: None,
975 source_uri: None,
976 symbol: None,
977 identity: fields.identity,
978 language: fields.language,
979 file_path: fields.file_path,
980 name: fields.name,
981 kind: fields.kind,
982 line_range: fields.line_range,
983 hunk_count: fields.hunk_count,
984 }
985 }
986
987 pub fn with_source(mut self, source: SourceId, source_uri: impl Into<String>) -> Self {
988 self.source = Some(source);
989 self.source_uri = Some(source_uri.into());
990 self
991 }
992
993 pub fn with_symbol(mut self, symbol: SymbolId) -> Self {
994 self.symbol = Some(symbol);
995 self
996 }
997}
998
999#[derive(Clone, Debug, Eq, PartialEq)]
1000pub struct WorkspaceSnapshot {
1001 pub generation: ResourceGeneration,
1002 pub catalog: SourceCatalog,
1003 pub index: CodeIndex,
1004 pub linkage: LinkageSnapshot,
1005 pub changes: ChangeOverlay,
1006 pub timings: WorkspaceTimings,
1007}
1008
1009#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1010pub struct WorkspaceTimings {
1011 pub source_catalog: Duration,
1012 pub extract_sources: Duration,
1013 pub semantic_index: Duration,
1014 pub code_index: Duration,
1015 pub linkage: Duration,
1016 pub change_overlay: Duration,
1017 pub total: Duration,
1018}
1019
1020#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1021pub enum WorkspaceResource {
1022 SourceCatalog,
1023 CodeIndex,
1024 LinkageSnapshot,
1025 ChangeOverlay,
1026}
1027
1028#[derive(Clone, Debug, Eq, PartialEq)]
1029pub struct WorkspaceFailure {
1030 pub resource: WorkspaceResource,
1031 pub message: String,
1032}
1033
1034impl WorkspaceFailure {
1035 pub fn new(resource: WorkspaceResource, message: impl Into<String>) -> Self {
1036 Self {
1037 resource,
1038 message: message.into(),
1039 }
1040 }
1041}
1042
1043pub type WorkspaceResult<T> = Result<T, WorkspaceFailure>;
1044
1045#[derive(Clone, Debug, Eq, PartialEq)]
1046pub enum WorkspaceTransition {
1047 Ready {
1048 generation: ResourceGeneration,
1049 },
1050 Failed {
1051 failure: WorkspaceFailure,
1052 preserved_generation: Option<ResourceGeneration>,
1053 },
1054}