Skip to main content

code_moniker_workspace/source/
content.rs

1use std::collections::{BTreeMap, BTreeSet};
2use std::path::{Path, PathBuf};
3use std::sync::{Arc, Mutex};
4use std::time::Duration;
5
6use code_moniker_core::core::code_graph::CodeGraph;
7use code_moniker_core::core::moniker::{Moniker, MonikerBuilder};
8use code_moniker_core::lang::Lang;
9use rustc_hash::FxHashMap;
10
11use crate::environment::{self, SourceFileSet, SourceRoot};
12use crate::path_util::lexical_path;
13use crate::snapshot::{ReferenceId, SourceId, SymbolId};
14
15use super::identity::LocalIdentityResolver;
16
17pub const MEMORY_SOURCE_ROOT: &str = "memory";
18pub const MEMORY_SOURCE_ROOT_LABEL: &str = "memory";
19const MEMORY_SOURCE_PATH_ROOT: &str = ".code-moniker-memory";
20
21pub fn is_memory_source_path(path: &Path) -> bool {
22	path.starts_with(MEMORY_SOURCE_PATH_ROOT)
23}
24
25#[derive(Clone, Default)]
26pub struct LocalResourceCache {
27	inner: Arc<Mutex<LocalResourceMaterial>>,
28}
29
30impl LocalResourceCache {
31	pub fn next_generation(&self) -> crate::snapshot::ResourceGeneration {
32		let mut inner = self.lock_material();
33		let generation = crate::snapshot::ResourceGeneration::new(inner.next_generation);
34		inner.next_generation += 1;
35		generation
36	}
37
38	fn lock_material(&self) -> std::sync::MutexGuard<'_, LocalResourceMaterial> {
39		self.inner
40			.lock()
41			.unwrap_or_else(|poisoned| poisoned.into_inner())
42	}
43
44	pub fn insert_sources(
45		&self,
46		generation: crate::snapshot::ResourceGeneration,
47		material: SourceCatalogMaterial,
48	) {
49		let mut inner = self.lock_material();
50		inner.sources.clear();
51		inner.sources.insert(generation.value(), material);
52	}
53
54	pub fn source_material(
55		&self,
56		generation: crate::snapshot::ResourceGeneration,
57	) -> Option<SourceCatalogMaterial> {
58		self.lock_material()
59			.sources
60			.get(&generation.value())
61			.cloned()
62	}
63
64	pub fn insert_index(
65		&self,
66		generation: crate::snapshot::ResourceGeneration,
67		material: CodeIndexMaterial,
68	) {
69		let mut inner = self.lock_material();
70		inner.indexes.clear();
71		inner.index_diffs.clear();
72		inner.indexes.insert(generation.value(), Arc::new(material));
73	}
74
75	pub fn insert_index_diff(
76		&self,
77		generation: crate::snapshot::ResourceGeneration,
78		previous_generation: crate::snapshot::ResourceGeneration,
79		diff: crate::code::CodeIndexGraphDiff,
80	) {
81		self.lock_material()
82			.index_diffs
83			.insert(generation.value(), (previous_generation, Arc::new(diff)));
84	}
85
86	pub fn index_diff(
87		&self,
88		generation: crate::snapshot::ResourceGeneration,
89	) -> Option<(
90		crate::snapshot::ResourceGeneration,
91		Arc<crate::code::CodeIndexGraphDiff>,
92	)> {
93		self.lock_material()
94			.index_diffs
95			.get(&generation.value())
96			.cloned()
97	}
98
99	pub fn index_material(
100		&self,
101		generation: crate::snapshot::ResourceGeneration,
102	) -> Option<Arc<CodeIndexMaterial>> {
103		self.lock_material()
104			.indexes
105			.get(&generation.value())
106			.cloned()
107	}
108
109	pub fn replace_memory_source_set(&self, source_set: MemorySourceSet) -> MemorySourceSetUpdate {
110		let mut inner = self.lock_material();
111		if inner.memory_source_sets.get(&source_set.srcset) == Some(&source_set) {
112			return MemorySourceSetUpdate {
113				srcset: source_set.srcset,
114				..Default::default()
115			};
116		}
117		let srcset = source_set.srcset.clone();
118		let next_paths = memory_source_paths(&source_set);
119		let previous = inner.memory_source_sets.insert(srcset.clone(), source_set);
120		let mut paths = previous
121			.as_ref()
122			.into_iter()
123			.flat_map(memory_source_paths)
124			.collect::<BTreeSet<_>>();
125		paths.extend(next_paths);
126		MemorySourceSetUpdate {
127			changed: true,
128			paths: paths.into_iter().collect(),
129			srcset,
130			previous,
131		}
132	}
133
134	pub fn remove_memory_source_set(&self, srcset: &str) -> MemorySourceSetUpdate {
135		let mut inner = self.lock_material();
136		let Some(previous) = inner.memory_source_sets.remove(srcset) else {
137			return MemorySourceSetUpdate {
138				srcset: srcset.to_string(),
139				..Default::default()
140			};
141		};
142		MemorySourceSetUpdate {
143			changed: true,
144			paths: memory_source_paths(&previous),
145			srcset: srcset.to_string(),
146			previous: Some(previous),
147		}
148	}
149
150	pub fn restore_memory_source_set(&self, srcset: String, previous: Option<MemorySourceSet>) {
151		let mut inner = self.lock_material();
152		match previous {
153			Some(previous) => {
154				inner.memory_source_sets.insert(srcset, previous);
155			}
156			None => {
157				inner.memory_source_sets.remove(&srcset);
158			}
159		}
160	}
161
162	pub fn memory_source_usage_after_replacing(
163		&self,
164		source_set: &MemorySourceSet,
165	) -> (usize, usize, usize) {
166		let inner = self.lock_material();
167		let mut source_sets = 0usize;
168		let mut documents = 0usize;
169		let mut bytes = 0usize;
170		for (srcset, active) in &inner.memory_source_sets {
171			if srcset == &source_set.srcset {
172				continue;
173			}
174			source_sets = source_sets.saturating_add(1);
175			documents = documents.saturating_add(active.documents.len());
176			bytes = bytes.saturating_add(active.size_bytes());
177		}
178		(
179			source_sets.saturating_add(1),
180			documents.saturating_add(source_set.documents.len()),
181			bytes.saturating_add(source_set.size_bytes()),
182		)
183	}
184
185	pub(crate) fn memory_source_sets(&self) -> BTreeMap<String, MemorySourceSet> {
186		self.lock_material().memory_source_sets.clone()
187	}
188}
189
190struct LocalResourceMaterial {
191	next_generation: u64,
192	sources: BTreeMap<u64, SourceCatalogMaterial>,
193	indexes: BTreeMap<u64, Arc<CodeIndexMaterial>>,
194	memory_source_sets: BTreeMap<String, MemorySourceSet>,
195	index_diffs: BTreeMap<
196		u64,
197		(
198			crate::snapshot::ResourceGeneration,
199			Arc<crate::code::CodeIndexGraphDiff>,
200		),
201	>,
202}
203
204impl Default for LocalResourceMaterial {
205	fn default() -> Self {
206		Self {
207			next_generation: 1,
208			sources: BTreeMap::new(),
209			indexes: BTreeMap::new(),
210			memory_source_sets: BTreeMap::new(),
211			index_diffs: BTreeMap::new(),
212		}
213	}
214}
215
216#[derive(Clone, Debug, Eq, PartialEq)]
217pub struct MemorySourceSet {
218	pub srcset: String,
219	pub revision: Option<String>,
220	pub documents: Vec<MemorySourceDocument>,
221}
222
223#[derive(Clone, Debug, Eq, PartialEq)]
224pub struct MemorySourceDocument {
225	pub uri: String,
226	pub lang: Lang,
227	pub content: String,
228}
229
230impl MemorySourceSet {
231	pub fn size_bytes(&self) -> usize {
232		self.srcset
233			.len()
234			.saturating_add(self.revision.as_ref().map_or(0, String::len))
235			.saturating_add(self.documents.iter().fold(0usize, |total, document| {
236				total
237					.saturating_add(document.uri.len())
238					.saturating_add(document.content.len())
239					.saturating_add(document.lang.tag().len())
240			}))
241	}
242}
243
244#[derive(Clone, Debug, Default, Eq, PartialEq)]
245pub struct MemorySourceSetUpdate {
246	pub changed: bool,
247	pub paths: Vec<PathBuf>,
248	pub srcset: String,
249	pub previous: Option<MemorySourceSet>,
250}
251
252pub(crate) fn memory_source_path(srcset: &str, uri: &str) -> PathBuf {
253	PathBuf::from(MEMORY_SOURCE_PATH_ROOT)
254		.join(srcset)
255		.join(hex_path_component(uri.as_bytes()))
256}
257
258fn hex_path_component(value: &[u8]) -> String {
259	const HEX: &[u8; 16] = b"0123456789abcdef";
260	let mut encoded = String::with_capacity(value.len() * 2);
261	for byte in value {
262		encoded.push(HEX[(byte >> 4) as usize] as char);
263		encoded.push(HEX[(byte & 0x0f) as usize] as char);
264	}
265	encoded
266}
267
268fn memory_source_paths(source_set: &MemorySourceSet) -> Vec<PathBuf> {
269	source_set
270		.documents
271		.iter()
272		.map(|document| memory_source_path(&source_set.srcset, &document.uri))
273		.collect()
274}
275
276#[derive(Clone)]
277pub struct SourceCatalogMaterial {
278	pub(crate) sources: SourceFileSet,
279	pub(crate) identity: LocalIdentityResolver,
280	pub(crate) memory_sources: BTreeMap<PathBuf, String>,
281	pub(crate) memory_slots: BTreeSet<PathBuf>,
282	pub(crate) memory_revisions: BTreeMap<String, Option<String>>,
283}
284
285impl SourceCatalogMaterial {
286	pub(crate) fn source_id_for_file(&self, file_idx: usize) -> Option<SourceId> {
287		let file = self.sources.files.get(file_idx)?;
288		Some(self.identity.source_id(file_idx, &file.rel_path))
289	}
290
291	pub fn source_uri_for_path(&self, path: &Path) -> Option<String> {
292		let file_idx = self.normalized_file_index(path)?;
293		let file = self.sources.files.get(file_idx)?;
294		let rel_path = file.rel_path.as_path();
295		Some(
296			match self
297				.is_memory_slot(&file.path)
298				.then_some(file.srcset.as_deref())
299				.flatten()
300			{
301				Some(srcset) => {
302					let rel_path = crate::path_util::portable_path(rel_path);
303					let moniker = MonikerBuilder::new()
304						.project(b".")
305						.segment(b"srcset", srcset.as_bytes())
306						.segment(b"file", rel_path.as_bytes())
307						.build();
308					self.identity.moniker_uri(&moniker)
309				}
310				None => self.identity.source_uri(rel_path),
311			},
312		)
313	}
314
315	#[allow(dead_code)]
316	pub(crate) fn resolve_source(&self, path: &Path) -> Option<ResolvedSourceResource> {
317		SourceResourceLookup::new(self).resolve(path)
318	}
319
320	pub(crate) fn normalized_file_index(&self, path: &Path) -> Option<usize> {
321		let normalized = normalize_path(path);
322		self.sources.files.iter().position(|file| {
323			normalize_path(&file.path) == normalized
324				|| normalize_path(&file.rel_path) == normalized
325				|| normalize_path(&file.anchor) == normalized
326		})
327	}
328
329	pub(crate) fn memory_source(&self, path: &Path) -> Option<&str> {
330		self.memory_sources.get(path).map(String::as_str)
331	}
332
333	pub(crate) fn is_memory_slot(&self, path: &Path) -> bool {
334		self.memory_slots.contains(path)
335	}
336
337	#[allow(dead_code)]
338	fn root_for_path(&self, path: &Path) -> Option<(usize, &SourceRoot)> {
339		self.sources
340			.roots
341			.iter()
342			.enumerate()
343			.filter_map(|(root_idx, root)| {
344				let absolute = absolute_path_against_root(&root.path, path);
345				let root_path = normalize_path(&root.path);
346				normalize_path(&absolute)
347					.starts_with(&root_path)
348					.then_some((root_idx, root, root_path.components().count()))
349			})
350			.max_by_key(|(_, _, depth)| *depth)
351			.map(|(root_idx, root, _)| (root_idx, root))
352	}
353}
354
355#[allow(dead_code)]
356struct SourceResourceLookup<'a> {
357	material: &'a SourceCatalogMaterial,
358}
359
360impl<'a> SourceResourceLookup<'a> {
361	fn new(material: &'a SourceCatalogMaterial) -> Self {
362		Self { material }
363	}
364
365	fn resolve(&self, path: &Path) -> Option<ResolvedSourceResource> {
366		self.indexed(path).or_else(|| self.lazy(path))
367	}
368
369	fn indexed(&self, path: &Path) -> Option<ResolvedSourceResource> {
370		let file_idx = self.match_indexed_file(path)?;
371		let file = self.material.sources.files.get(file_idx)?;
372		Some(ResolvedSourceResource {
373			source_root: file.source,
374			source_id: self.material.identity.source_id(file_idx, &file.rel_path),
375			source_uri: self.material.identity.source_uri(&file.rel_path),
376			path: file.path.clone(),
377			rel_path: file.rel_path.clone(),
378			anchor: file.anchor.clone(),
379			lang: file.lang,
380			eager_index: Some(file_idx),
381		})
382	}
383
384	fn match_indexed_file(&self, path: &Path) -> Option<usize> {
385		self.material
386			.sources
387			.files
388			.iter()
389			.enumerate()
390			.filter(|(_, file)| path.ends_with(&file.rel_path))
391			.max_by_key(|(_, file)| file.rel_path.components().count())
392			.map(|(file_idx, _)| file_idx)
393			.or_else(|| self.material.normalized_file_index(path))
394	}
395
396	fn lazy(&self, path: &Path) -> Option<ResolvedSourceResource> {
397		let (source_root, root) = self.material.root_for_path(path)?;
398		let abs_path = absolute_path_against_root(&root.path, path);
399		if !abs_path.is_file() {
400			return None;
401		}
402		let lang = environment::language_for_path(&abs_path).ok()?;
403		let rel = abs_path.strip_prefix(&root.path).ok()?.to_path_buf();
404		let rel_path = self.rel_path(root, &rel);
405		Some(ResolvedSourceResource {
406			source_root,
407			source_id: SourceId::at(u32::MAX as usize),
408			source_uri: self.material.identity.source_uri(&rel_path),
409			path: abs_path,
410			rel_path,
411			anchor: rel,
412			lang,
413			eager_index: None,
414		})
415	}
416
417	fn rel_path(&self, root: &SourceRoot, rel: &Path) -> PathBuf {
418		if self.material.sources.multi {
419			PathBuf::from(&root.label).join(rel)
420		} else {
421			rel.to_path_buf()
422		}
423	}
424}
425
426#[derive(Clone)]
427#[allow(dead_code)]
428pub struct ResolvedSourceResource {
429	pub(crate) source_root: usize,
430	pub(crate) source_id: SourceId,
431	pub(crate) source_uri: String,
432	pub(crate) path: PathBuf,
433	pub(crate) rel_path: PathBuf,
434	pub(crate) anchor: PathBuf,
435	pub(crate) lang: Lang,
436	pub(crate) eager_index: Option<usize>,
437}
438
439#[derive(Clone)]
440pub struct CodeIndexMaterial {
441	pub source_catalog: SourceCatalogMaterial,
442	pub files: Vec<Arc<IndexedSourceFile>>,
443	pub identity: LocalIdentityResolver,
444	pub symbols_by_moniker: FxHashMap<Moniker, SymbolId>,
445}
446
447impl CodeIndexMaterial {
448	pub fn source_set(&self) -> &SourceFileSet {
449		&self.source_catalog.sources
450	}
451
452	pub fn symbol_moniker(&self, symbol: &SymbolId) -> Option<&Moniker> {
453		let (file_idx, def_idx) = self.identity.symbol_location(symbol)?;
454		let graph = &self.files.get(file_idx)?.graph;
455		(def_idx < graph.def_count()).then(|| &graph.def_at(def_idx).moniker)
456	}
457
458	pub fn symbol_source(&self, symbol: &SymbolId) -> Option<SourceId> {
459		let (file_idx, def_idx) = self.identity.symbol_location(symbol)?;
460		let file = self.files.get(file_idx)?;
461		(def_idx < file.graph.def_count()).then(|| file.source_id)
462	}
463
464	pub fn symbol_exists(&self, symbol: &SymbolId) -> bool {
465		self.symbol_moniker(symbol).is_some()
466	}
467
468	pub fn reference_target(&self, reference: &ReferenceId) -> Option<&Moniker> {
469		let (file_idx, ref_idx) = self.identity.reference_location(reference)?;
470		let graph = &self.files.get(file_idx)?.graph;
471		(ref_idx < graph.ref_count()).then(|| &graph.ref_at(ref_idx).target)
472	}
473
474	pub fn symbols(&self) -> impl Iterator<Item = (SymbolId, &Moniker)> + '_ {
475		self.files.iter().enumerate().flat_map(|(file_idx, file)| {
476			file.graph.defs().enumerate().map(move |(def_idx, def)| {
477				(file.identity.symbol_id(file_idx, def_idx), &def.moniker)
478			})
479		})
480	}
481}
482
483#[derive(Clone)]
484pub struct IndexedSourceFile {
485	pub source_root: usize,
486	pub source_id: SourceId,
487	pub source_uri: String,
488	pub identity: LocalIdentityResolver,
489	pub path: PathBuf,
490	pub rel_path: PathBuf,
491	pub anchor: PathBuf,
492	pub lang: Lang,
493	pub graph: CodeGraph,
494	pub source: String,
495	pub extraction_cache: &'static str,
496	pub extraction_duration: Duration,
497}
498
499fn normalize_path(path: &Path) -> PathBuf {
500	lexical_path(path)
501}
502
503#[allow(dead_code)]
504fn absolute_path_against_root(root: &Path, path: &Path) -> PathBuf {
505	if path.is_absolute() {
506		normalize_path(path)
507	} else {
508		normalize_path(&root.join(path))
509	}
510}
511
512#[cfg(test)]
513mod tests {
514	use super::*;
515	use code_moniker_core::core::moniker::MonikerBuilder;
516	use code_moniker_core::lang::Lang;
517
518	#[test]
519	fn symbol_moniker_returns_none_for_out_of_range_symbol_id() {
520		let (material, root, _) = material_with_one_reference();
521
522		assert_eq!(material.symbol_moniker(&SymbolId::at(0, 0)), Some(&root));
523		assert!(material.symbol_moniker(&SymbolId::at(0, 999999)).is_none());
524	}
525
526	#[test]
527	fn reference_target_returns_none_for_out_of_range_reference_id() {
528		let (material, _, target) = material_with_one_reference();
529
530		assert_eq!(
531			material.reference_target(&ReferenceId::at(0, 0)),
532			Some(&target)
533		);
534		assert!(
535			material
536				.reference_target(&ReferenceId::at(0, 999999))
537				.is_none()
538		);
539	}
540
541	fn material_with_one_reference() -> (CodeIndexMaterial, Moniker, Moniker) {
542		let identity = LocalIdentityResolver::default();
543		let root = MonikerBuilder::new()
544			.project(b"app")
545			.segment(b"module", b"main")
546			.build();
547		let target = MonikerBuilder::new()
548			.project(b"app")
549			.segment(b"module", b"other")
550			.build();
551		let mut graph = CodeGraph::new(root.clone(), b"module");
552		graph
553			.add_ref(&root, target.clone(), b"calls", None)
554			.expect("test graph ref must be valid");
555		let rel_path = PathBuf::from("main.rs");
556		let file = IndexedSourceFile {
557			source_root: 0,
558			source_id: identity.source_id(0, &rel_path),
559			source_uri: identity.source_uri(&rel_path),
560			identity: identity.clone(),
561			path: rel_path.clone(),
562			rel_path: rel_path.clone(),
563			anchor: rel_path,
564			lang: Lang::Rs,
565			graph,
566			source: String::new(),
567			extraction_cache: "provided",
568			extraction_duration: Duration::ZERO,
569		};
570		let material = CodeIndexMaterial {
571			source_catalog: SourceCatalogMaterial {
572				sources: SourceFileSet {
573					roots: Vec::new(),
574					files: Vec::new(),
575					multi: false,
576				},
577				identity: identity.clone(),
578				memory_sources: BTreeMap::new(),
579				memory_slots: BTreeSet::new(),
580				memory_revisions: BTreeMap::new(),
581			},
582			files: vec![Arc::new(file)],
583			identity,
584			symbols_by_moniker: FxHashMap::default(),
585		};
586		(material, root, target)
587	}
588}