1use std::collections::BTreeSet;
4use std::path::{Path, PathBuf};
5use std::sync::Arc;
6use std::time::Instant;
7
8use code_moniker_core::core::moniker::Moniker;
9use rayon::prelude::*;
10use rustc_hash::FxHashMap;
11
12use crate::code::{def_kind, is_navigable_def, last_name, ref_kind};
13use crate::lines::LineIndex;
14use crate::snapshot::{
15 CodeIndex, CodeIndexTimings, RecordTable, ReferenceId, ReferenceRecord, SourceCatalog,
16 SourceFileRecord, SourceId, SymbolId, SymbolRecord, WorkspaceCancellation, WorkspaceFailure,
17 WorkspaceResource, WorkspaceResult,
18};
19use crate::source::{
20 CodeIndexMaterial, IndexedSourceFile, LocalResourceCache, SourceCatalogMaterial,
21};
22
23use crate::source::LocalIdentityResolver;
24
25pub trait CodeIndexPort {
26 fn build_index(&mut self, catalog: &SourceCatalog) -> WorkspaceResult<CodeIndex>;
27 fn build_index_cancellable(
28 &mut self,
29 catalog: &SourceCatalog,
30 cancellation: &WorkspaceCancellation,
31 ) -> WorkspaceResult<CodeIndex> {
32 cancellation.check(WorkspaceResource::CodeIndex)?;
33 let index = self.build_index(catalog)?;
34 cancellation.check(WorkspaceResource::CodeIndex)?;
35 Ok(index)
36 }
37 fn refresh_paths(
38 &mut self,
39 current: &CodeIndex,
40 paths: &[PathBuf],
41 ) -> WorkspaceResult<CodeIndexRefresh>;
42 fn refresh_catalog_paths(
43 &mut self,
44 current: &CodeIndex,
45 catalog: &SourceCatalog,
46 paths: &[PathBuf],
47 ) -> WorkspaceResult<CodeIndexRefresh>;
48}
49
50#[derive(Clone, Debug, Eq, PartialEq)]
51pub struct CodeIndexRefresh {
52 pub index: CodeIndex,
53 pub changed_sources: Vec<SourceId>,
54 pub graph_diff: CodeIndexGraphDiff,
55}
56
57#[derive(Clone, Debug, Default, Eq, PartialEq)]
58pub struct CodeIndexGraphDiff {
59 pub added_symbols: Vec<SymbolId>,
60 pub modified_symbols: Vec<SymbolId>,
61 pub changed_symbols: Vec<SymbolId>,
62 pub removed_symbols: Vec<SymbolId>,
63 pub modified_symbol_identities: Vec<String>,
64 pub removed_symbol_identities: Vec<String>,
65 pub changed_references: Vec<ReferenceId>,
66 pub removed_references: Vec<ReferenceId>,
67 pub removed_reference_kinds: Vec<String>,
68 pub symbol_id_remaps: Vec<(SymbolId, SymbolId)>,
69 pub reference_id_remaps: Vec<(ReferenceId, ReferenceId)>,
70 pub unchanged_symbols: usize,
71 pub unchanged_references: usize,
72}
73
74impl CodeIndexGraphDiff {
75 pub fn changed_symbol_count(&self) -> usize {
76 self.changed_symbols.len() + self.removed_symbols.len()
77 }
78
79 pub fn changed_reference_count(&self) -> usize {
80 self.changed_references.len() + self.removed_references.len()
81 }
82}
83
84#[derive(Clone, Debug, Default, Eq, PartialEq)]
85pub struct LocalCodeIndexOptions {
86 pub cache_dir: Option<PathBuf>,
87}
88
89impl LocalCodeIndexOptions {
90 pub fn new(cache_dir: Option<PathBuf>) -> Self {
91 Self { cache_dir }
92 }
93}
94
95pub struct LocalCodeIndex {
96 options: LocalCodeIndexOptions,
97 cache: LocalResourceCache,
98}
99
100impl LocalCodeIndex {
101 pub fn new(options: LocalCodeIndexOptions, cache: LocalResourceCache) -> Self {
102 Self { options, cache }
103 }
104}
105
106impl CodeIndexPort for LocalCodeIndex {
107 fn build_index(&mut self, catalog: &SourceCatalog) -> WorkspaceResult<CodeIndex> {
108 build_local_code_index(
109 &self.cache,
110 &self.options,
111 catalog,
112 &WorkspaceCancellation::default(),
113 )
114 }
115
116 fn build_index_cancellable(
117 &mut self,
118 catalog: &SourceCatalog,
119 cancellation: &WorkspaceCancellation,
120 ) -> WorkspaceResult<CodeIndex> {
121 build_local_code_index(&self.cache, &self.options, catalog, cancellation)
122 }
123
124 fn refresh_paths(
125 &mut self,
126 current: &CodeIndex,
127 paths: &[PathBuf],
128 ) -> WorkspaceResult<CodeIndexRefresh> {
129 refresh_local_code_index(&self.cache, &self.options, current, None, paths)
130 }
131
132 fn refresh_catalog_paths(
133 &mut self,
134 current: &CodeIndex,
135 catalog: &SourceCatalog,
136 paths: &[PathBuf],
137 ) -> WorkspaceResult<CodeIndexRefresh> {
138 refresh_local_code_index(&self.cache, &self.options, current, Some(catalog), paths)
139 }
140}
141
142fn build_local_code_index(
143 cache: &LocalResourceCache,
144 options: &LocalCodeIndexOptions,
145 catalog: &SourceCatalog,
146 cancellation: &WorkspaceCancellation,
147) -> WorkspaceResult<CodeIndex> {
148 cancellation.check(WorkspaceResource::CodeIndex)?;
149 let total_timer = Instant::now();
150 let source_material = source_material(cache, catalog)?;
151 let generation = cache.next_generation();
152 let extract_timer = Instant::now();
153 let files = extract_source_files(&source_material, options.cache_dir.as_deref(), cancellation)?;
154 let extract_sources = extract_timer.elapsed();
155 cancellation.check(WorkspaceResource::CodeIndex)?;
156 let semantic_timer = Instant::now();
157 let (symbols, references, material) =
158 build_semantic_index(source_material, files, cancellation)?;
159 let semantic_index = semantic_timer.elapsed();
160 let mut sources = source_records(&material.files);
161 let identity_scheme = material.identity.scheme().to_string();
162 cache.insert_index(generation, material);
163 sources.shrink_to_fit();
164 Ok(CodeIndex {
165 generation,
166 catalog_generation: catalog.generation,
167 identity_scheme,
168 sources,
169 symbols,
170 references,
171 timings: CodeIndexTimings {
172 extract_sources,
173 semantic_index,
174 total: total_timer.elapsed(),
175 },
176 })
177}
178
179fn refresh_local_code_index(
180 cache: &LocalResourceCache,
181 options: &LocalCodeIndexOptions,
182 current: &CodeIndex,
183 extended_catalog: Option<&SourceCatalog>,
184 paths: &[PathBuf],
185) -> WorkspaceResult<CodeIndexRefresh> {
186 let total_timer = Instant::now();
187 let current_material = cache.index_material(current.generation).ok_or_else(|| {
188 WorkspaceFailure::new(
189 WorkspaceResource::CodeIndex,
190 "code index material is unavailable",
191 )
192 })?;
193 let source_catalog = match extended_catalog {
194 Some(catalog) => cache.source_material(catalog.generation).ok_or_else(|| {
195 WorkspaceFailure::new(
196 WorkspaceResource::CodeIndex,
197 "extended source catalog material is unavailable",
198 )
199 })?,
200 None => current_material.source_catalog.clone(),
201 };
202 let mut files = current_material.files.clone();
203 let mut changed_sources = Vec::new();
204 let mut changed_file_indexes = BTreeSet::new();
205 let extract_timer = Instant::now();
206 refresh_retired_slots(RetiredSlotRefresh {
207 previous_catalog: ¤t_material.source_catalog,
208 source_catalog: &source_catalog,
209 cache_dir: options.cache_dir.as_deref(),
210 files: &mut files,
211 changed_sources: &mut changed_sources,
212 changed_file_indexes: &mut changed_file_indexes,
213 })?;
214 for file_idx in files.len()..source_catalog.sources.files.len() {
215 let file = &source_catalog.sources.files[file_idx];
216 let indexed = extract_source_file(
217 &source_catalog,
218 file_idx,
219 &file.path.clone(),
220 options.cache_dir.as_deref(),
221 )?;
222 push_unique_source(&mut changed_sources, indexed.source_id);
223 changed_file_indexes.insert(file_idx);
224 files.push(Arc::new(indexed));
225 }
226 for path in paths {
227 let Some(source) = source_catalog.resolve_source(path) else {
228 continue;
229 };
230 let Some(file_idx) = source.eager_index else {
231 continue;
232 };
233 if changed_file_indexes.contains(&file_idx) {
234 continue;
235 }
236 let indexed = extract_source_file(
237 &source_catalog,
238 file_idx,
239 &source.path,
240 options.cache_dir.as_deref(),
241 )?;
242 if let Some(slot) = files.get_mut(file_idx) {
243 push_unique_source(&mut changed_sources, indexed.source_id);
244 changed_file_indexes.insert(file_idx);
245 *slot = Arc::new(indexed);
246 }
247 }
248 let extract_sources = extract_timer.elapsed();
249 if changed_sources.is_empty() {
250 return Ok(CodeIndexRefresh {
251 index: current.clone(),
252 changed_sources,
253 graph_diff: CodeIndexGraphDiff::default(),
254 });
255 }
256 let semantic_timer = Instant::now();
257 let material = material_from_files(source_catalog, files, &WorkspaceCancellation::default())?;
258 let sources = source_records(&material.files);
259 let graph_diff = graph_diff(current_material.as_ref(), &material, &changed_file_indexes);
260 let mut symbols = current.symbols.clone();
261 let mut references = current.references.clone();
262 for file_idx in &changed_file_indexes {
263 let (file_symbols, file_references) =
264 records_for_file(*file_idx, &material.files[*file_idx]);
265 symbols.replace(*file_idx, Arc::from(file_symbols));
266 references.replace(*file_idx, Arc::from(file_references));
267 }
268 let semantic_index = semantic_timer.elapsed();
269 let generation = cache.next_generation();
270 let identity_scheme = material.identity.scheme().to_string();
271 cache.insert_index(generation, material);
272 Ok(CodeIndexRefresh {
273 index: CodeIndex {
274 generation,
275 catalog_generation: extended_catalog
276 .map(|catalog| catalog.generation)
277 .unwrap_or(current.catalog_generation),
278 identity_scheme,
279 sources,
280 symbols,
281 references,
282 timings: CodeIndexTimings {
283 extract_sources,
284 semantic_index,
285 total: total_timer.elapsed(),
286 },
287 },
288 changed_sources,
289 graph_diff,
290 })
291}
292
293struct RetiredSlotRefresh<'a> {
294 previous_catalog: &'a SourceCatalogMaterial,
295 source_catalog: &'a SourceCatalogMaterial,
296 cache_dir: Option<&'a Path>,
297 files: &'a mut Vec<Arc<IndexedSourceFile>>,
298 changed_sources: &'a mut Vec<SourceId>,
299 changed_file_indexes: &'a mut BTreeSet<usize>,
300}
301
302fn refresh_retired_slots(refresh: RetiredSlotRefresh<'_>) -> WorkspaceResult<()> {
303 let slots = refresh
304 .files
305 .len()
306 .min(refresh.source_catalog.sources.files.len());
307 for file_idx in 0..slots {
308 let was_retired = refresh.previous_catalog.sources.files[file_idx].retired;
309 let is_retired = refresh.source_catalog.sources.files[file_idx].retired;
310 if was_retired == is_retired {
311 continue;
312 }
313 let indexed = if is_retired {
314 tombstone_file(&refresh.files[file_idx])
315 } else {
316 extract_source_file(
317 refresh.source_catalog,
318 file_idx,
319 &refresh.source_catalog.sources.files[file_idx].path.clone(),
320 refresh.cache_dir,
321 )?
322 };
323 push_unique_source(refresh.changed_sources, indexed.source_id);
324 refresh.changed_file_indexes.insert(file_idx);
325 refresh.files[file_idx] = Arc::new(indexed);
326 }
327 Ok(())
328}
329
330fn tombstone_file(previous: &IndexedSourceFile) -> IndexedSourceFile {
331 IndexedSourceFile {
332 source_root: previous.source_root,
333 source_id: previous.source_id,
334 source_uri: previous.source_uri.clone(),
335 identity: previous.identity.clone(),
336 path: previous.path.clone(),
337 rel_path: previous.rel_path.clone(),
338 anchor: previous.anchor.clone(),
339 lang: previous.lang,
340 graph: code_moniker_core::core::code_graph::CodeGraph::from_records(Vec::new(), Vec::new()),
341 source: String::new(),
342 }
343}
344
345fn source_material(
346 cache: &LocalResourceCache,
347 catalog: &SourceCatalog,
348) -> WorkspaceResult<SourceCatalogMaterial> {
349 cache.source_material(catalog.generation).ok_or_else(|| {
350 WorkspaceFailure::new(
351 WorkspaceResource::CodeIndex,
352 "source catalog material is unavailable",
353 )
354 })
355}
356
357fn extract_source_files(
358 source_material: &SourceCatalogMaterial,
359 cache_dir: Option<&std::path::Path>,
360 cancellation: &WorkspaceCancellation,
361) -> WorkspaceResult<Vec<Arc<IndexedSourceFile>>> {
362 source_material
363 .sources
364 .files
365 .par_iter()
366 .enumerate()
367 .map(|(file_idx, file)| {
368 cancellation.check(WorkspaceResource::CodeIndex)?;
369 extract_source_file(source_material, file_idx, &file.path, cache_dir).map(Arc::new)
370 })
371 .collect()
372}
373
374fn extract_source_file(
375 source_material: &SourceCatalogMaterial,
376 file_idx: usize,
377 path: &Path,
378 cache_dir: Option<&Path>,
379) -> WorkspaceResult<IndexedSourceFile> {
380 let file = source_material.sources.files.get(file_idx).ok_or_else(|| {
381 WorkspaceFailure::new(
382 WorkspaceResource::CodeIndex,
383 format!("source file index {file_idx} is unavailable"),
384 )
385 })?;
386 let ctx = &source_material.sources.roots[file.source].ctx;
387 let (graph, extracted_source) = crate::cache::load_or_extract_workspace_result(
388 path,
389 &file.anchor,
390 file.lang,
391 cache_dir,
392 ctx,
393 )
394 .map_err(|err| {
395 WorkspaceFailure::new(
396 WorkspaceResource::CodeIndex,
397 format!("cannot extract {}: {err}", path.display()),
398 )
399 })?;
400 let source = match extracted_source {
401 Some(source) => source,
402 None => crate::cache::read_source_lossy(path).map_err(|err| {
403 WorkspaceFailure::new(
404 WorkspaceResource::CodeIndex,
405 format!("cannot read {}: {err}", path.display()),
406 )
407 })?,
408 };
409 Ok(IndexedSourceFile {
410 source_root: file.source,
411 source_id: source_material
412 .source_id_for_file(file_idx)
413 .ok_or_else(|| {
414 WorkspaceFailure::new(
415 WorkspaceResource::CodeIndex,
416 format!("source id is unavailable for {}", file.rel_path.display()),
417 )
418 })?,
419 source_uri: source_material
420 .source_uri_for_path(&file.path)
421 .ok_or_else(|| {
422 WorkspaceFailure::new(
423 WorkspaceResource::CodeIndex,
424 format!("source uri is unavailable for {}", file.path.display()),
425 )
426 })?,
427 identity: source_material.identity.clone(),
428 path: file.path.clone(),
429 rel_path: file.rel_path.clone(),
430 anchor: file.anchor.clone(),
431 lang: file.lang,
432 graph,
433 source,
434 })
435}
436
437fn build_semantic_index(
438 source_material: SourceCatalogMaterial,
439 files: Vec<Arc<IndexedSourceFile>>,
440 cancellation: &WorkspaceCancellation,
441) -> WorkspaceResult<(
442 RecordTable<SymbolRecord>,
443 RecordTable<ReferenceRecord>,
444 CodeIndexMaterial,
445)> {
446 let mut symbol_shards = Vec::with_capacity(files.len());
447 let mut reference_shards = Vec::with_capacity(files.len());
448 for (file_idx, file) in files.iter().enumerate() {
449 cancellation.check(WorkspaceResource::CodeIndex)?;
450 let (symbols, references) = records_for_file(file_idx, file);
451 symbol_shards.push(Arc::from(symbols));
452 reference_shards.push(Arc::from(references));
453 }
454 let material = material_from_files(source_material, files, cancellation)?;
455 Ok((
456 RecordTable::from_shards(symbol_shards),
457 RecordTable::from_shards(reference_shards),
458 material,
459 ))
460}
461
462fn material_from_files(
463 source_material: SourceCatalogMaterial,
464 mut files: Vec<Arc<IndexedSourceFile>>,
465 cancellation: &WorkspaceCancellation,
466) -> WorkspaceResult<CodeIndexMaterial> {
467 let symbol_count = files.iter().map(|file| file.graph.def_count()).sum();
468 let mut symbols_by_moniker = rustc_hash::FxHashMap::default();
469 symbols_by_moniker.reserve(symbol_count);
470 for (file_idx, file) in files.iter().enumerate() {
471 cancellation.check(WorkspaceResource::CodeIndex)?;
472 for (def_idx, def) in file.graph.defs().enumerate() {
473 symbols_by_moniker.insert(
474 def.moniker.clone(),
475 file.graph_identity().symbol_id(file_idx, def_idx),
476 );
477 }
478 }
479 symbols_by_moniker.shrink_to_fit();
480 files.shrink_to_fit();
481 let identity = source_material.identity.clone();
482 Ok(CodeIndexMaterial {
483 source_catalog: source_material,
484 files,
485 identity,
486 symbols_by_moniker,
487 })
488}
489
490fn graph_diff(
491 previous: &CodeIndexMaterial,
492 next: &CodeIndexMaterial,
493 changed_files: &BTreeSet<usize>,
494) -> CodeIndexGraphDiff {
495 let mut diff = CodeIndexGraphDiff::default();
496 for file_idx in changed_files {
497 let Some(next_file) = next.files.get(*file_idx) else {
498 continue;
499 };
500 let (previous_symbols, previous_references) = match previous.files.get(*file_idx) {
501 Some(previous_file) => records_for_file(*file_idx, previous_file),
502 None => (Vec::new(), Vec::new()),
503 };
504 let (next_symbols, next_references) = records_for_file(*file_idx, next_file);
505 diff_symbols(&previous_symbols, &next_symbols, &mut diff);
506 diff_references(
507 &previous_references,
508 previous,
509 &next_references,
510 next,
511 &mut diff,
512 );
513 }
514 diff
515}
516
517fn records_for_file(
518 file_idx: usize,
519 file: &IndexedSourceFile,
520) -> (Vec<SymbolRecord>, Vec<ReferenceRecord>) {
521 let line_index = LineIndex::new(&file.source);
522 let mut symbols = Vec::with_capacity(file.graph.def_count());
523 collect_symbols(file_idx, file, &line_index, &mut symbols);
524 let mut reference_identity_pool = TargetIdentityPool::default();
525 let mut references = Vec::with_capacity(file.graph.ref_count());
526 collect_references(
527 file_idx,
528 file,
529 &line_index,
530 &mut references,
531 &mut reference_identity_pool,
532 );
533 (symbols, references)
534}
535
536fn diff_symbols(previous: &[SymbolRecord], next: &[SymbolRecord], diff: &mut CodeIndexGraphDiff) {
537 let mut next_by_key = symbol_record_indexes(next);
538 for previous_symbol in previous {
539 let key = symbol_key(previous_symbol);
540 let Some(next_idx) = pop_index(&mut next_by_key, &key) else {
541 diff.removed_symbols.push(previous_symbol.id);
542 diff.removed_symbol_identities
543 .push(previous_symbol.identity.to_string());
544 continue;
545 };
546 let next_symbol = &next[next_idx];
547 if symbol_linkage_fields_changed(previous_symbol, next_symbol) {
548 diff.modified_symbols.push(next_symbol.id);
549 diff.modified_symbol_identities
550 .push(next_symbol.identity.to_string());
551 diff.changed_symbols.push(next_symbol.id);
552 continue;
553 }
554 if previous_symbol.id != next_symbol.id {
555 diff.symbol_id_remaps
556 .push((previous_symbol.id, next_symbol.id));
557 }
558 diff.unchanged_symbols += 1;
559 }
560 for indexes in next_by_key.into_values() {
561 for idx in indexes {
562 diff.added_symbols.push(next[idx].id);
563 diff.changed_symbols.push(next[idx].id);
564 }
565 }
566}
567
568fn diff_references(
569 previous: &[ReferenceRecord],
570 previous_material: &CodeIndexMaterial,
571 next: &[ReferenceRecord],
572 next_material: &CodeIndexMaterial,
573 diff: &mut CodeIndexGraphDiff,
574) {
575 let mut next_by_key = reference_record_indexes(next, next_material);
576 for previous_reference in previous {
577 let Some(key) = reference_key(previous_reference, previous_material) else {
578 diff.removed_references.push(previous_reference.id);
579 diff.removed_reference_kinds
580 .push(previous_reference.kind.clone());
581 continue;
582 };
583 let Some(next_idx) = pop_index(&mut next_by_key, &key) else {
584 diff.removed_references.push(previous_reference.id);
585 diff.removed_reference_kinds
586 .push(previous_reference.kind.clone());
587 continue;
588 };
589 let next_reference = &next[next_idx];
590 if previous_reference.id != next_reference.id {
591 diff.reference_id_remaps
592 .push((previous_reference.id, next_reference.id));
593 }
594 diff.unchanged_references += 1;
595 }
596 for indexes in next_by_key.into_values() {
597 for idx in indexes {
598 diff.changed_references.push(next[idx].id);
599 }
600 }
601}
602
603fn symbol_record_indexes(records: &[SymbolRecord]) -> FxHashMap<Arc<str>, Vec<usize>> {
604 let mut by_key = FxHashMap::<Arc<str>, Vec<usize>>::default();
605 for (idx, record) in records.iter().enumerate() {
606 by_key.entry(symbol_key(record)).or_default().push(idx);
607 }
608 by_key
609}
610
611fn reference_record_indexes(
612 records: &[ReferenceRecord],
613 material: &CodeIndexMaterial,
614) -> FxHashMap<ReferenceKey, Vec<usize>> {
615 let mut by_key = FxHashMap::<ReferenceKey, Vec<usize>>::default();
616 for (idx, record) in records.iter().enumerate() {
617 if let Some(key) = reference_key(record, material) {
618 by_key.entry(key).or_default().push(idx);
619 }
620 }
621 by_key
622}
623
624fn pop_index<K: Eq + std::hash::Hash>(
625 by_key: &mut FxHashMap<K, Vec<usize>>,
626 key: &K,
627) -> Option<usize> {
628 let indexes = by_key.get_mut(key)?;
629 let idx = indexes.remove(0);
630 if indexes.is_empty() {
631 by_key.remove(key);
632 }
633 Some(idx)
634}
635
636fn symbol_key(symbol: &SymbolRecord) -> Arc<str> {
637 Arc::clone(&symbol.identity)
638}
639
640fn symbol_linkage_fields_changed(previous: &SymbolRecord, next: &SymbolRecord) -> bool {
641 previous.identity != next.identity
642 || previous.name != next.name
643 || previous.kind != next.kind
644 || previous.visibility != next.visibility
645 || previous.signature != next.signature
646 || previous.call_name != next.call_name
647 || previous.call_arity != next.call_arity
648 || previous.navigable != next.navigable
649}
650
651#[derive(Clone, Debug, Eq, Hash, PartialEq)]
652struct ReferenceKey {
653 source_symbol_identity: String,
654 target_identity: String,
655 kind: String,
656 call_name: Option<String>,
657 call_arity: Option<usize>,
658 confidence: Option<String>,
659 receiver: Option<String>,
660 alias: Option<String>,
661}
662
663fn reference_key(
664 reference: &ReferenceRecord,
665 material: &CodeIndexMaterial,
666) -> Option<ReferenceKey> {
667 let source_symbol_identity = material
668 .symbol_moniker(&reference.source_symbol)
669 .map(|moniker| material.identity.moniker_uri(moniker))?;
670 Some(ReferenceKey {
671 source_symbol_identity,
672 target_identity: reference.target_identity.to_string(),
673 kind: reference.kind.clone(),
674 call_name: reference.call_name.clone(),
675 call_arity: reference.call_arity,
676 confidence: reference.confidence.clone(),
677 receiver: reference.receiver.clone(),
678 alias: reference.alias.clone(),
679 })
680}
681
682fn push_unique_source(sources: &mut Vec<SourceId>, source: SourceId) {
683 if !sources.iter().any(|existing| existing == &source) {
684 sources.push(source);
685 }
686}
687
688fn collect_symbols(
689 file_idx: usize,
690 file: &IndexedSourceFile,
691 line_index: &LineIndex,
692 symbols: &mut Vec<SymbolRecord>,
693) {
694 for (def_idx, def) in file.graph.defs().enumerate() {
695 let id = file.graph_identity().symbol_id(file_idx, def_idx);
696 let parent = def
697 .parent
698 .map(|parent_idx| file.graph_identity().symbol_id(file_idx, parent_idx));
699 symbols.push(SymbolRecord {
700 id,
701 source: file.source_id,
702 identity: Arc::from(file.graph_identity().moniker_uri(&def.moniker)),
703 name: last_name(&def.moniker),
704 kind: def_kind(def),
705 visibility: def_visibility(def),
706 signature: String::from_utf8_lossy(&def.signature).to_string(),
707 call_name: (!def.call_name.is_empty())
708 .then(|| String::from_utf8_lossy(&def.call_name).to_string()),
709 call_arity: def.call_arity,
710 navigable: is_navigable_def(file.lang, def),
711 line_range: def
712 .position
713 .map(|(start, end)| line_index.line_range(start, end)),
714 parent,
715 });
716 }
717}
718
719fn def_visibility(def: &code_moniker_core::core::code_graph::DefRecord) -> String {
720 std::str::from_utf8(&def.visibility)
721 .unwrap_or("")
722 .to_string()
723}
724
725fn collect_references(
726 file_idx: usize,
727 file: &IndexedSourceFile,
728 line_index: &LineIndex,
729 references: &mut Vec<ReferenceRecord>,
730 reference_identity_pool: &mut TargetIdentityPool,
731) {
732 for (ref_idx, reference) in file.graph.refs().enumerate() {
733 let id = file.graph_identity().reference_id(file_idx, ref_idx);
734 let source_symbol = file.graph_identity().symbol_id(file_idx, reference.source);
735 let target_identity =
736 reference_identity_pool.intern(file.graph_identity(), &reference.target);
737 references.push(
738 ReferenceRecord::new(
739 id,
740 file.source_id,
741 source_symbol,
742 target_identity,
743 ref_kind(reference),
744 reference
745 .position
746 .map(|(start, end)| line_index.line_range(start, end)),
747 )
748 .with_call_metadata(ref_attr(&reference.call_name), reference.call_arity)
749 .with_metadata(
750 ref_attr(&reference.confidence),
751 ref_attr(&reference.receiver_hint),
752 ref_attr(&reference.alias),
753 ),
754 );
755 }
756}
757
758#[derive(Default)]
759struct TargetIdentityPool {
760 values: rustc_hash::FxHashMap<Moniker, Arc<str>>,
761}
762
763impl TargetIdentityPool {
764 fn intern(&mut self, identity: &LocalIdentityResolver, target: &Moniker) -> Arc<str> {
765 if let Some(existing) = self.values.get(target) {
766 return Arc::clone(existing);
767 }
768 let shared = Arc::<str>::from(identity.moniker_uri(target));
769 self.values.insert(target.clone(), Arc::clone(&shared));
770 shared
771 }
772}
773
774fn source_records(files: &[Arc<IndexedSourceFile>]) -> Vec<SourceFileRecord> {
775 files
776 .iter()
777 .map(|file| SourceFileRecord {
778 id: file.source_id,
779 uri: file.source_uri.clone(),
780 source_root: file.source_root,
781 path: file.path.display().to_string(),
782 rel_path: file.rel_path.display().to_string(),
783 anchor: file.anchor.display().to_string(),
784 language: file.lang.tag().to_string(),
785 text: String::new(),
786 })
787 .collect()
788}
789
790fn ref_attr(bytes: &[u8]) -> Option<String> {
791 if bytes.is_empty() {
792 return None;
793 }
794 std::str::from_utf8(bytes)
795 .ok()
796 .filter(|value| !value.is_empty())
797 .map(ToOwned::to_owned)
798}
799
800trait IndexedSourceIdentity {
801 fn graph_identity(&self) -> &LocalIdentityResolver;
802}
803
804impl IndexedSourceIdentity for IndexedSourceFile {
805 fn graph_identity(&self) -> &LocalIdentityResolver {
806 &self.identity
807 }
808}