1use std::sync::Arc;
2use std::time::Duration;
3
4use super::records::RecordTable;
5
6#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
7pub struct ResourceGeneration(u64);
8
9impl ResourceGeneration {
10 pub fn new(value: u64) -> Self {
11 Self(value)
12 }
13
14 pub fn value(self) -> u64 {
15 self.0
16 }
17}
18
19#[derive(Clone, Debug, Eq, PartialEq)]
20pub struct WorkspaceRequest {
21 pub label: String,
22 pub catalog: CatalogRequest,
23}
24
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub enum CatalogRequest {
27 Refresh,
28 ReuseCurrent,
29}
30
31impl WorkspaceRequest {
32 pub fn new(label: impl Into<String>) -> Self {
33 Self {
34 label: label.into(),
35 catalog: CatalogRequest::Refresh,
36 }
37 }
38
39 pub fn reuse_current_catalog(mut self) -> Self {
40 self.catalog = CatalogRequest::ReuseCurrent;
41 self
42 }
43
44 pub fn should_reuse_current_catalog(&self) -> bool {
45 self.catalog == CatalogRequest::ReuseCurrent
46 }
47}
48
49#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
50pub struct SourceId {
51 file: u32,
52}
53
54impl SourceId {
55 pub fn at(file: usize) -> Self {
56 Self { file: file as u32 }
57 }
58
59 pub fn parse(value: &str) -> Option<Self> {
60 let rest = value.strip_prefix("source:")?;
61 let file = rest.split(':').next()?;
62 Some(Self {
63 file: file.parse().ok()?,
64 })
65 }
66
67 pub fn file(self) -> usize {
68 self.file as usize
69 }
70}
71
72impl std::fmt::Display for SourceId {
73 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 write!(formatter, "source:{}", self.file)
75 }
76}
77
78#[derive(Clone, Debug, Eq, PartialEq)]
79pub struct SourceUnit {
80 pub id: SourceId,
81 pub display_name: String,
82 pub language: Option<String>,
83}
84
85impl SourceUnit {
86 pub fn new(id: SourceId, display_name: impl Into<String>) -> Self {
87 Self {
88 id,
89 display_name: display_name.into(),
90 language: None,
91 }
92 }
93
94 pub fn with_language(
95 id: SourceId,
96 display_name: impl Into<String>,
97 language: impl Into<String>,
98 ) -> Self {
99 Self {
100 id,
101 display_name: display_name.into(),
102 language: Some(language.into()),
103 }
104 }
105}
106
107#[derive(Clone, Debug, Eq, PartialEq)]
108pub struct SourceCatalog {
109 pub generation: ResourceGeneration,
110 pub sources: Vec<SourceUnit>,
111}
112
113impl SourceCatalog {
114 pub fn new(generation: ResourceGeneration, mut sources: Vec<SourceUnit>) -> Self {
115 sources.shrink_to_fit();
116 Self {
117 generation,
118 sources,
119 }
120 }
121}
122
123#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
124pub struct SymbolId {
125 file: u32,
126 def: u32,
127}
128
129impl SymbolId {
130 pub fn at(file: usize, def: usize) -> Self {
131 Self {
132 file: file as u32,
133 def: def as u32,
134 }
135 }
136
137 pub fn parse(value: &str) -> Option<Self> {
138 let rest = value.strip_prefix("symbol:")?;
139 let (file, def) = rest.split_once(':')?;
140 Some(Self {
141 file: file.parse().ok()?,
142 def: def.parse().ok()?,
143 })
144 }
145
146 pub fn file(self) -> usize {
147 self.file as usize
148 }
149
150 pub fn def(self) -> usize {
151 self.def as usize
152 }
153}
154
155impl std::fmt::Display for SymbolId {
156 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 write!(formatter, "symbol:{}:{}", self.file, self.def)
158 }
159}
160
161#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
162pub struct SymbolLocation {
163 pub file: usize,
164 pub symbol: usize,
165}
166
167#[derive(Clone, Debug, Eq, PartialEq)]
168pub struct SymbolRecord {
169 pub id: SymbolId,
170 pub source: SourceId,
171 pub identity: Arc<str>,
172 pub name: String,
173 pub kind: String,
174 pub visibility: String,
175 pub signature: String,
176 pub navigable: bool,
177 pub line_range: Option<(u32, u32)>,
178 pub parent: Option<SymbolId>,
179}
180
181impl SymbolRecord {
182 pub fn new(
183 id: SymbolId,
184 source: SourceId,
185 name: impl Into<String>,
186 kind: impl Into<String>,
187 ) -> Self {
188 Self {
189 identity: Arc::from(id.to_string()),
190 id,
191 source,
192 name: name.into(),
193 kind: kind.into(),
194 visibility: String::new(),
195 signature: String::new(),
196 navigable: true,
197 line_range: None,
198 parent: None,
199 }
200 }
201}
202
203#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
204pub struct ReferenceId {
205 file: u32,
206 reference: u32,
207}
208
209impl ReferenceId {
210 pub fn at(file: usize, reference: usize) -> Self {
211 Self {
212 file: file as u32,
213 reference: reference as u32,
214 }
215 }
216
217 pub fn parse(value: &str) -> Option<Self> {
218 let rest = value.strip_prefix("reference:")?;
219 let (file, reference) = rest.split_once(':')?;
220 Some(Self {
221 file: file.parse().ok()?,
222 reference: reference.parse().ok()?,
223 })
224 }
225
226 pub fn file(self) -> usize {
227 self.file as usize
228 }
229
230 pub fn reference(self) -> usize {
231 self.reference as usize
232 }
233}
234
235impl std::fmt::Display for ReferenceId {
236 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237 write!(formatter, "reference:{}:{}", self.file, self.reference)
238 }
239}
240
241#[derive(Clone, Debug, Eq, PartialEq)]
242pub struct ReferenceRecord {
243 pub id: ReferenceId,
244 pub source: SourceId,
245 pub source_symbol: SymbolId,
246 pub target_identity: Arc<str>,
247 pub kind: String,
248 pub call_name: Option<String>,
249 pub call_arity: Option<usize>,
250 pub confidence: Option<String>,
251 pub receiver: Option<String>,
252 pub alias: Option<String>,
253 pub line_range: Option<(u32, u32)>,
254}
255
256impl ReferenceRecord {
257 pub fn new(
258 id: ReferenceId,
259 source: SourceId,
260 source_symbol: SymbolId,
261 target_identity: impl Into<Arc<str>>,
262 kind: impl Into<String>,
263 line_range: Option<(u32, u32)>,
264 ) -> Self {
265 Self {
266 id,
267 source,
268 source_symbol,
269 target_identity: target_identity.into(),
270 kind: kind.into(),
271 call_name: None,
272 call_arity: None,
273 confidence: None,
274 receiver: None,
275 alias: None,
276 line_range,
277 }
278 }
279
280 pub fn with_call_metadata(
281 mut self,
282 call_name: Option<String>,
283 call_arity: Option<usize>,
284 ) -> Self {
285 self.call_name = call_name;
286 self.call_arity = call_arity;
287 self
288 }
289
290 pub fn with_metadata(
291 mut self,
292 confidence: Option<String>,
293 receiver: Option<String>,
294 alias: Option<String>,
295 ) -> Self {
296 self.confidence = confidence;
297 self.receiver = receiver;
298 self.alias = alias;
299 self
300 }
301}
302
303#[derive(Clone, Debug, Eq, PartialEq)]
304pub struct SourceFileRecord {
305 pub id: SourceId,
306 pub uri: String,
307 pub source_root: usize,
308 pub path: String,
309 pub rel_path: String,
310 pub anchor: String,
311 pub language: String,
312 pub text: String,
313}
314
315#[derive(Clone, Debug, Eq, PartialEq)]
316pub struct CodeIndex {
317 pub generation: ResourceGeneration,
318 pub catalog_generation: ResourceGeneration,
319 pub identity_scheme: String,
320 pub sources: Vec<SourceFileRecord>,
321 pub symbols: RecordTable<SymbolRecord>,
322 pub references: RecordTable<ReferenceRecord>,
323 pub timings: CodeIndexTimings,
324}
325
326#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
327pub struct CodeIndexTimings {
328 pub extract_sources: Duration,
329 pub semantic_index: Duration,
330 pub total: Duration,
331}
332
333impl CodeIndex {
334 pub fn new(
335 generation: ResourceGeneration,
336 catalog_generation: ResourceGeneration,
337 symbols: Vec<SymbolRecord>,
338 ) -> Self {
339 Self::with_references(generation, catalog_generation, symbols, Vec::new())
340 }
341
342 pub fn with_references(
343 generation: ResourceGeneration,
344 catalog_generation: ResourceGeneration,
345 symbols: Vec<SymbolRecord>,
346 references: Vec<ReferenceRecord>,
347 ) -> Self {
348 Self {
349 generation,
350 catalog_generation,
351 identity_scheme: crate::DEFAULT_IDENTITY_SCHEME.to_string(),
352 sources: Vec::new(),
353 symbols: RecordTable::from_records(symbols),
354 references: RecordTable::from_records(references),
355 timings: CodeIndexTimings::default(),
356 }
357 }
358}
359
360#[derive(Clone, Debug, Eq, PartialEq)]
361pub struct LinkageEdge {
362 pub reference: ReferenceId,
363 pub target: SymbolId,
364}
365
366impl LinkageEdge {
367 pub fn new(reference: ReferenceId, target: SymbolId) -> Self {
368 Self { reference, target }
369 }
370}
371
372#[derive(Clone, Copy, Debug, Eq, PartialEq)]
373pub enum ExternalReferenceOrigin {
374 Dependency,
375 Injected,
376 UnknownExternal,
377}
378
379impl ExternalReferenceOrigin {
380 pub fn label(self) -> &'static str {
381 match self {
382 Self::Dependency => "dependency",
383 Self::Injected => "injected",
384 Self::UnknownExternal => "unknown_external",
385 }
386 }
387}
388
389#[derive(Clone, Debug, Eq, PartialEq)]
390pub struct ExternalReference {
391 pub reference: ReferenceId,
392 pub target_identity: Arc<str>,
393 pub origin: ExternalReferenceOrigin,
394}
395
396impl ExternalReference {
397 pub fn new(
398 reference: ReferenceId,
399 target_identity: impl Into<Arc<str>>,
400 origin: ExternalReferenceOrigin,
401 ) -> Self {
402 Self {
403 reference,
404 target_identity: target_identity.into(),
405 origin,
406 }
407 }
408}
409
410#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
414pub enum UnresolvedReason {
415 ManifestBlocked,
416 Visibility,
417 LanguageBoundary,
418 MissingQuery,
419 NoCandidate,
420 Ambiguous,
421 UnsupportedLanguageRule,
422 IncompleteExtractorMetadata,
423}
424
425impl UnresolvedReason {
426 pub fn as_str(&self) -> &'static str {
427 match self {
428 Self::ManifestBlocked => "manifest_blocked",
429 Self::Visibility => "visibility",
430 Self::LanguageBoundary => "language_boundary",
431 Self::MissingQuery => "missing_query",
432 Self::NoCandidate => "no_candidate",
433 Self::Ambiguous => "ambiguous",
434 Self::UnsupportedLanguageRule => "unsupported_language_rule",
435 Self::IncompleteExtractorMetadata => "incomplete_extractor_metadata",
436 }
437 }
438}
439
440#[derive(Clone, Debug, Eq, PartialEq)]
441pub struct UnresolvedReference {
442 pub reference: ReferenceId,
443 pub target_identity: Arc<str>,
444 pub reason: UnresolvedReason,
445}
446
447impl UnresolvedReference {
448 pub fn new(
449 reference: ReferenceId,
450 target_identity: impl Into<Arc<str>>,
451 reason: UnresolvedReason,
452 ) -> Self {
453 Self {
454 reference,
455 target_identity: target_identity.into(),
456 reason,
457 }
458 }
459}
460
461#[derive(Clone, Debug, Eq, PartialEq)]
462pub struct LinkageSnapshot {
463 pub generation: ResourceGeneration,
464 pub index_generation: ResourceGeneration,
465 pub resolved_refs: usize,
466 pub external_refs: usize,
467 pub manifest_blocked_refs: usize,
468 pub unresolved_refs: usize,
469 pub ambiguous_refs: usize,
470 pub resolved: Vec<LinkageEdge>,
471 pub external: Vec<ExternalReference>,
472 pub manifest_blocked: Vec<UnresolvedReference>,
473 pub unresolved: Vec<UnresolvedReference>,
474 pub read_index: LinkageReadIndexHandle,
475}
476
477#[derive(Debug)]
478pub struct LinkageReadIndex {
479 pub(crate) incoming: rustc_hash::FxHashMap<SymbolId, Vec<ReferenceId>>,
480 pub(crate) targets: rustc_hash::FxHashMap<ReferenceId, SymbolId>,
481}
482
483impl LinkageReadIndex {
484 pub fn from_edges(edges: &[LinkageEdge]) -> Self {
485 let mut incoming = rustc_hash::FxHashMap::<SymbolId, Vec<ReferenceId>>::default();
486 let mut targets = rustc_hash::FxHashMap::<ReferenceId, SymbolId>::default();
487 for edge in edges {
488 let LinkageEdge { reference, target } = edge.clone();
489 targets.entry(reference).or_insert(target);
490 incoming.entry(target).or_default().push(reference);
491 }
492 Self { incoming, targets }
493 }
494
495 pub fn incoming(&self, symbol: &SymbolId) -> &[ReferenceId] {
496 self.incoming.get(symbol).map(Vec::as_slice).unwrap_or(&[])
497 }
498
499 pub fn resolved_target(&self, reference: &ReferenceId) -> Option<&SymbolId> {
500 self.targets.get(reference)
501 }
502}
503
504#[derive(Clone, Debug, Default)]
505pub struct LinkageReadIndexHandle(Option<Arc<LinkageReadIndex>>);
506
507impl LinkageReadIndexHandle {
508 pub fn from_edges(edges: &[LinkageEdge]) -> Self {
509 Self(Some(Arc::new(LinkageReadIndex::from_edges(edges))))
510 }
511
512 pub fn get(&self) -> Option<&LinkageReadIndex> {
513 self.0.as_deref()
514 }
515}
516
517impl PartialEq for LinkageReadIndexHandle {
518 fn eq(&self, _other: &Self) -> bool {
519 true
520 }
521}
522
523impl Eq for LinkageReadIndexHandle {}
524
525impl LinkageSnapshot {
526 pub fn new(
527 generation: ResourceGeneration,
528 index_generation: ResourceGeneration,
529 resolved_refs: usize,
530 unresolved_refs: usize,
531 ) -> Self {
532 Self {
533 generation,
534 index_generation,
535 resolved_refs,
536 external_refs: 0,
537 manifest_blocked_refs: 0,
538 unresolved_refs,
539 ambiguous_refs: 0,
540 resolved: Vec::new(),
541 external: Vec::new(),
542 manifest_blocked: Vec::new(),
543 unresolved: Vec::new(),
544 read_index: LinkageReadIndexHandle::default(),
545 }
546 }
547
548 pub fn with_refs(
549 generation: ResourceGeneration,
550 index_generation: ResourceGeneration,
551 mut resolved: Vec<LinkageEdge>,
552 mut unresolved: Vec<UnresolvedReference>,
553 ) -> Self {
554 resolved.shrink_to_fit();
555 unresolved.shrink_to_fit();
556 let read_index = LinkageReadIndexHandle::from_edges(&resolved);
557 Self {
558 generation,
559 index_generation,
560 resolved_refs: resolved.len(),
561 external_refs: 0,
562 manifest_blocked_refs: 0,
563 unresolved_refs: unresolved.len(),
564 ambiguous_refs: 0,
565 resolved,
566 external: Vec::new(),
567 manifest_blocked: Vec::new(),
568 unresolved,
569 read_index,
570 }
571 }
572}
573
574#[derive(Clone, Debug, Eq, PartialEq)]
575pub struct ChangeOverlay {
576 pub generation: ResourceGeneration,
577 pub catalog_generation: ResourceGeneration,
578 pub index_generation: ResourceGeneration,
579 pub scope: String,
580 pub resources: Vec<ChangeResource>,
581 pub diagnostics: Vec<String>,
582 pub changed_symbols: Vec<SymbolId>,
583 pub changes: Vec<ChangeRecord>,
584 pub semantic: Option<std::sync::Arc<crate::changes::semantic::review::SemanticReview>>,
585}
586
587pub struct ChangeOverlayReport {
588 pub generation: ResourceGeneration,
589 pub catalog_generation: ResourceGeneration,
590 pub index_generation: ResourceGeneration,
591 pub scope: String,
592 pub resources: Vec<ChangeResource>,
593 pub diagnostics: Vec<String>,
594 pub changes: Vec<ChangeRecord>,
595}
596
597impl ChangeOverlay {
598 pub fn new(
599 generation: ResourceGeneration,
600 catalog_generation: ResourceGeneration,
601 index_generation: ResourceGeneration,
602 mut changed_symbols: Vec<SymbolId>,
603 ) -> Self {
604 changed_symbols.shrink_to_fit();
605 Self {
606 generation,
607 catalog_generation,
608 index_generation,
609 scope: "HEAD..worktree".to_string(),
610 resources: Vec::new(),
611 diagnostics: Vec::new(),
612 changed_symbols,
613 changes: Vec::new(),
614 semantic: None,
615 }
616 }
617
618 pub fn with_records(
619 generation: ResourceGeneration,
620 catalog_generation: ResourceGeneration,
621 index_generation: ResourceGeneration,
622 mut changes: Vec<ChangeRecord>,
623 ) -> Self {
624 changes.shrink_to_fit();
625 let changed_symbols = changes.iter().filter_map(|change| change.symbol).fold(
626 Vec::new(),
627 |mut out, symbol| {
628 if !out.contains(&symbol) {
629 out.push(symbol);
630 }
631 out
632 },
633 );
634 let mut changed_symbols = changed_symbols;
635 changed_symbols.shrink_to_fit();
636 Self {
637 generation,
638 catalog_generation,
639 index_generation,
640 scope: "HEAD..worktree".to_string(),
641 resources: Vec::new(),
642 diagnostics: Vec::new(),
643 changed_symbols,
644 changes,
645 semantic: None,
646 }
647 }
648
649 pub fn from_report(report: ChangeOverlayReport) -> Self {
650 let mut resources = report.resources;
651 let mut diagnostics = report.diagnostics;
652 resources.shrink_to_fit();
653 diagnostics.shrink_to_fit();
654 let mut overlay = Self::with_records(
655 report.generation,
656 report.catalog_generation,
657 report.index_generation,
658 report.changes,
659 );
660 overlay.scope = report.scope;
661 overlay.resources = resources;
662 overlay.diagnostics = diagnostics;
663 overlay
664 }
665}
666
667#[derive(Clone, Debug, Eq, PartialEq)]
668pub struct ChangeResource {
669 pub available: bool,
670 pub label: String,
671 pub message: String,
672}
673
674#[derive(Clone, Copy, Debug, Eq, PartialEq)]
675pub enum ChangeStatus {
676 Added,
677 Modified,
678 Removed,
679}
680
681impl ChangeStatus {
682 pub fn label(self) -> &'static str {
683 match self {
684 Self::Added => "added",
685 Self::Modified => "modified",
686 Self::Removed => "removed",
687 }
688 }
689}
690
691#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
692pub struct ChangeId(String);
693
694impl ChangeId {
695 pub fn new(value: impl Into<String>) -> Self {
696 Self(value.into())
697 }
698
699 pub fn as_str(&self) -> &str {
700 &self.0
701 }
702}
703
704#[derive(Clone, Debug, Eq, PartialEq)]
705pub struct ChangeRecord {
706 pub id: ChangeId,
707 pub status: ChangeStatus,
708 pub source: Option<SourceId>,
709 pub source_uri: Option<String>,
710 pub symbol: Option<SymbolId>,
711 pub identity: String,
712 pub language: String,
713 pub file_path: String,
714 pub name: String,
715 pub kind: String,
716 pub line_range: Option<(u32, u32)>,
717 pub hunk_count: usize,
718}
719
720#[derive(Clone, Debug, Eq, PartialEq)]
721pub struct ChangeRecordCoreFields {
722 pub id: ChangeId,
723 pub status: ChangeStatus,
724 pub identity: String,
725 pub language: String,
726 pub file_path: String,
727 pub name: String,
728 pub kind: String,
729 pub line_range: Option<(u32, u32)>,
730 pub hunk_count: usize,
731}
732
733impl ChangeRecord {
734 pub fn new(fields: ChangeRecordCoreFields) -> Self {
735 Self {
736 id: fields.id,
737 status: fields.status,
738 source: None,
739 source_uri: None,
740 symbol: None,
741 identity: fields.identity,
742 language: fields.language,
743 file_path: fields.file_path,
744 name: fields.name,
745 kind: fields.kind,
746 line_range: fields.line_range,
747 hunk_count: fields.hunk_count,
748 }
749 }
750
751 pub fn with_source(mut self, source: SourceId, source_uri: impl Into<String>) -> Self {
752 self.source = Some(source);
753 self.source_uri = Some(source_uri.into());
754 self
755 }
756
757 pub fn with_symbol(mut self, symbol: SymbolId) -> Self {
758 self.symbol = Some(symbol);
759 self
760 }
761}
762
763#[derive(Clone, Debug, Eq, PartialEq)]
764pub struct WorkspaceSnapshot {
765 pub generation: ResourceGeneration,
766 pub catalog: SourceCatalog,
767 pub index: CodeIndex,
768 pub linkage: LinkageSnapshot,
769 pub changes: ChangeOverlay,
770 pub timings: WorkspaceTimings,
771}
772
773#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
774pub struct WorkspaceTimings {
775 pub source_catalog: Duration,
776 pub extract_sources: Duration,
777 pub semantic_index: Duration,
778 pub code_index: Duration,
779 pub linkage: Duration,
780 pub change_overlay: Duration,
781 pub total: Duration,
782}
783
784#[derive(Clone, Copy, Debug, Eq, PartialEq)]
785pub enum WorkspaceResource {
786 SourceCatalog,
787 CodeIndex,
788 LinkageSnapshot,
789 ChangeOverlay,
790}
791
792#[derive(Clone, Debug, Eq, PartialEq)]
793pub struct WorkspaceFailure {
794 pub resource: WorkspaceResource,
795 pub message: String,
796}
797
798impl WorkspaceFailure {
799 pub fn new(resource: WorkspaceResource, message: impl Into<String>) -> Self {
800 Self {
801 resource,
802 message: message.into(),
803 }
804 }
805}
806
807pub type WorkspaceResult<T> = Result<T, WorkspaceFailure>;
808
809#[derive(Clone, Debug, Eq, PartialEq)]
810pub enum WorkspaceTransition {
811 Ready {
812 generation: ResourceGeneration,
813 },
814 Failed {
815 failure: WorkspaceFailure,
816 preserved_generation: Option<ResourceGeneration>,
817 },
818}