Skip to main content

code_moniker_workspace/source/
content.rs

1use std::collections::BTreeMap;
2use std::path::{Path, PathBuf};
3use std::sync::{Arc, Mutex};
4
5use code_moniker_core::core::code_graph::CodeGraph;
6use code_moniker_core::core::moniker::Moniker;
7use code_moniker_core::lang::Lang;
8use rustc_hash::FxHashMap;
9
10use crate::environment::{self, SourceFileSet, SourceRoot};
11use crate::path_util::lexical_path;
12use crate::snapshot::{ReferenceId, SourceId, SymbolId};
13
14use super::identity::LocalIdentityResolver;
15
16#[derive(Clone, Default)]
17pub struct LocalResourceCache {
18	inner: Arc<Mutex<LocalResourceMaterial>>,
19}
20
21impl LocalResourceCache {
22	pub fn next_generation(&self) -> crate::snapshot::ResourceGeneration {
23		let mut inner = self.lock_material();
24		let generation = crate::snapshot::ResourceGeneration::new(inner.next_generation);
25		inner.next_generation += 1;
26		generation
27	}
28
29	fn lock_material(&self) -> std::sync::MutexGuard<'_, LocalResourceMaterial> {
30		self.inner
31			.lock()
32			.unwrap_or_else(|poisoned| poisoned.into_inner())
33	}
34
35	pub fn insert_sources(
36		&self,
37		generation: crate::snapshot::ResourceGeneration,
38		material: SourceCatalogMaterial,
39	) {
40		let mut inner = self.lock_material();
41		inner.sources.clear();
42		inner.sources.insert(generation.value(), material);
43	}
44
45	pub fn source_material(
46		&self,
47		generation: crate::snapshot::ResourceGeneration,
48	) -> Option<SourceCatalogMaterial> {
49		self.lock_material()
50			.sources
51			.get(&generation.value())
52			.cloned()
53	}
54
55	pub fn insert_index(
56		&self,
57		generation: crate::snapshot::ResourceGeneration,
58		material: CodeIndexMaterial,
59	) {
60		let mut inner = self.lock_material();
61		inner.indexes.clear();
62		inner.indexes.insert(generation.value(), Arc::new(material));
63	}
64
65	pub fn index_material(
66		&self,
67		generation: crate::snapshot::ResourceGeneration,
68	) -> Option<Arc<CodeIndexMaterial>> {
69		self.lock_material()
70			.indexes
71			.get(&generation.value())
72			.cloned()
73	}
74}
75
76struct LocalResourceMaterial {
77	next_generation: u64,
78	sources: BTreeMap<u64, SourceCatalogMaterial>,
79	indexes: BTreeMap<u64, Arc<CodeIndexMaterial>>,
80}
81
82impl Default for LocalResourceMaterial {
83	fn default() -> Self {
84		Self {
85			next_generation: 1,
86			sources: BTreeMap::new(),
87			indexes: BTreeMap::new(),
88		}
89	}
90}
91
92#[derive(Clone)]
93pub struct SourceCatalogMaterial {
94	pub(crate) sources: SourceFileSet,
95	pub(crate) identity: LocalIdentityResolver,
96}
97
98impl SourceCatalogMaterial {
99	pub(crate) fn source_id_for_file(&self, file_idx: usize) -> Option<SourceId> {
100		let file = self.sources.files.get(file_idx)?;
101		Some(self.identity.source_id(file_idx, &file.rel_path))
102	}
103
104	pub fn source_uri_for_path(&self, path: &Path) -> Option<String> {
105		self.source_rel_path(path)
106			.map(|rel_path| self.identity.source_uri(rel_path))
107	}
108
109	#[allow(dead_code)]
110	pub(crate) fn resolve_source(&self, path: &Path) -> Option<ResolvedSourceResource> {
111		SourceResourceLookup::new(self).resolve(path)
112	}
113
114	fn source_rel_path(&self, path: &Path) -> Option<&Path> {
115		self.normalized_file_index(path)
116			.map(|file_idx| self.sources.files[file_idx].rel_path.as_path())
117	}
118
119	pub(crate) fn normalized_file_index(&self, path: &Path) -> Option<usize> {
120		let normalized = normalize_path(path);
121		self.sources.files.iter().position(|file| {
122			normalize_path(&file.path) == normalized
123				|| normalize_path(&file.rel_path) == normalized
124				|| normalize_path(&file.anchor) == normalized
125		})
126	}
127
128	#[allow(dead_code)]
129	fn root_for_path(&self, path: &Path) -> Option<(usize, &SourceRoot)> {
130		self.sources
131			.roots
132			.iter()
133			.enumerate()
134			.filter_map(|(root_idx, root)| {
135				let absolute = absolute_path_against_root(&root.path, path);
136				let root_path = normalize_path(&root.path);
137				normalize_path(&absolute)
138					.starts_with(&root_path)
139					.then_some((root_idx, root, root_path.components().count()))
140			})
141			.max_by_key(|(_, _, depth)| *depth)
142			.map(|(root_idx, root, _)| (root_idx, root))
143	}
144}
145
146#[allow(dead_code)]
147struct SourceResourceLookup<'a> {
148	material: &'a SourceCatalogMaterial,
149}
150
151impl<'a> SourceResourceLookup<'a> {
152	fn new(material: &'a SourceCatalogMaterial) -> Self {
153		Self { material }
154	}
155
156	fn resolve(&self, path: &Path) -> Option<ResolvedSourceResource> {
157		self.indexed(path).or_else(|| self.lazy(path))
158	}
159
160	fn indexed(&self, path: &Path) -> Option<ResolvedSourceResource> {
161		let file_idx = self.match_indexed_file(path)?;
162		let file = self.material.sources.files.get(file_idx)?;
163		Some(ResolvedSourceResource {
164			source_root: file.source,
165			source_id: self.material.identity.source_id(file_idx, &file.rel_path),
166			source_uri: self.material.identity.source_uri(&file.rel_path),
167			path: file.path.clone(),
168			rel_path: file.rel_path.clone(),
169			anchor: file.anchor.clone(),
170			lang: file.lang,
171			eager_index: Some(file_idx),
172		})
173	}
174
175	fn match_indexed_file(&self, path: &Path) -> Option<usize> {
176		self.material
177			.sources
178			.files
179			.iter()
180			.enumerate()
181			.filter(|(_, file)| path.ends_with(&file.rel_path))
182			.max_by_key(|(_, file)| file.rel_path.components().count())
183			.map(|(file_idx, _)| file_idx)
184			.or_else(|| self.material.normalized_file_index(path))
185	}
186
187	fn lazy(&self, path: &Path) -> Option<ResolvedSourceResource> {
188		let (source_root, root) = self.material.root_for_path(path)?;
189		let abs_path = absolute_path_against_root(&root.path, path);
190		if !abs_path.is_file() {
191			return None;
192		}
193		let lang = environment::language_for_path(&abs_path).ok()?;
194		let rel = abs_path.strip_prefix(&root.path).ok()?.to_path_buf();
195		let rel_path = self.rel_path(root, &rel);
196		Some(ResolvedSourceResource {
197			source_root,
198			source_id: SourceId::at(u32::MAX as usize),
199			source_uri: self.material.identity.source_uri(&rel_path),
200			path: abs_path,
201			rel_path,
202			anchor: rel,
203			lang,
204			eager_index: None,
205		})
206	}
207
208	fn rel_path(&self, root: &SourceRoot, rel: &Path) -> PathBuf {
209		if self.material.sources.multi {
210			PathBuf::from(&root.label).join(rel)
211		} else {
212			rel.to_path_buf()
213		}
214	}
215}
216
217#[derive(Clone)]
218#[allow(dead_code)]
219pub struct ResolvedSourceResource {
220	pub(crate) source_root: usize,
221	pub(crate) source_id: SourceId,
222	pub(crate) source_uri: String,
223	pub(crate) path: PathBuf,
224	pub(crate) rel_path: PathBuf,
225	pub(crate) anchor: PathBuf,
226	pub(crate) lang: Lang,
227	pub(crate) eager_index: Option<usize>,
228}
229
230#[derive(Clone)]
231pub struct CodeIndexMaterial {
232	pub source_catalog: SourceCatalogMaterial,
233	pub files: Vec<Arc<IndexedSourceFile>>,
234	pub identity: LocalIdentityResolver,
235	pub symbols_by_moniker: FxHashMap<Moniker, SymbolId>,
236}
237
238impl CodeIndexMaterial {
239	pub fn symbol_moniker(&self, symbol: &SymbolId) -> Option<&Moniker> {
240		let (file_idx, def_idx) = self.identity.symbol_location(symbol)?;
241		let graph = &self.files.get(file_idx)?.graph;
242		(def_idx < graph.def_count()).then(|| &graph.def_at(def_idx).moniker)
243	}
244
245	pub fn symbol_source(&self, symbol: &SymbolId) -> Option<SourceId> {
246		let (file_idx, def_idx) = self.identity.symbol_location(symbol)?;
247		let file = self.files.get(file_idx)?;
248		(def_idx < file.graph.def_count()).then(|| file.source_id)
249	}
250
251	pub fn symbol_exists(&self, symbol: &SymbolId) -> bool {
252		self.symbol_moniker(symbol).is_some()
253	}
254
255	pub fn reference_target(&self, reference: &ReferenceId) -> Option<&Moniker> {
256		let (file_idx, ref_idx) = self.identity.reference_location(reference)?;
257		let graph = &self.files.get(file_idx)?.graph;
258		(ref_idx < graph.ref_count()).then(|| &graph.ref_at(ref_idx).target)
259	}
260
261	pub fn symbols(&self) -> impl Iterator<Item = (SymbolId, &Moniker)> + '_ {
262		self.files.iter().enumerate().flat_map(|(file_idx, file)| {
263			file.graph.defs().enumerate().map(move |(def_idx, def)| {
264				(file.identity.symbol_id(file_idx, def_idx), &def.moniker)
265			})
266		})
267	}
268}
269
270#[derive(Clone)]
271pub struct IndexedSourceFile {
272	pub source_root: usize,
273	pub source_id: SourceId,
274	pub source_uri: String,
275	pub identity: LocalIdentityResolver,
276	pub path: PathBuf,
277	pub rel_path: PathBuf,
278	pub anchor: PathBuf,
279	pub lang: Lang,
280	pub graph: CodeGraph,
281	pub source: String,
282}
283
284fn normalize_path(path: &Path) -> PathBuf {
285	lexical_path(path)
286}
287
288#[allow(dead_code)]
289fn absolute_path_against_root(root: &Path, path: &Path) -> PathBuf {
290	if path.is_absolute() {
291		normalize_path(path)
292	} else {
293		normalize_path(&root.join(path))
294	}
295}
296
297#[cfg(test)]
298mod tests {
299	use super::*;
300	use code_moniker_core::core::moniker::MonikerBuilder;
301	use code_moniker_core::lang::Lang;
302
303	#[test]
304	fn symbol_moniker_returns_none_for_out_of_range_symbol_id() {
305		let (material, root, _) = material_with_one_reference();
306
307		assert_eq!(material.symbol_moniker(&SymbolId::at(0, 0)), Some(&root));
308		assert!(material.symbol_moniker(&SymbolId::at(0, 999999)).is_none());
309	}
310
311	#[test]
312	fn reference_target_returns_none_for_out_of_range_reference_id() {
313		let (material, _, target) = material_with_one_reference();
314
315		assert_eq!(
316			material.reference_target(&ReferenceId::at(0, 0)),
317			Some(&target)
318		);
319		assert!(
320			material
321				.reference_target(&ReferenceId::at(0, 999999))
322				.is_none()
323		);
324	}
325
326	fn material_with_one_reference() -> (CodeIndexMaterial, Moniker, Moniker) {
327		let identity = LocalIdentityResolver::default();
328		let root = MonikerBuilder::new()
329			.project(b"app")
330			.segment(b"module", b"main")
331			.build();
332		let target = MonikerBuilder::new()
333			.project(b"app")
334			.segment(b"module", b"other")
335			.build();
336		let mut graph = CodeGraph::new(root.clone(), b"module");
337		graph
338			.add_ref(&root, target.clone(), b"calls", None)
339			.expect("test graph ref must be valid");
340		let rel_path = PathBuf::from("main.rs");
341		let file = IndexedSourceFile {
342			source_root: 0,
343			source_id: identity.source_id(0, &rel_path),
344			source_uri: identity.source_uri(&rel_path),
345			identity: identity.clone(),
346			path: rel_path.clone(),
347			rel_path: rel_path.clone(),
348			anchor: rel_path,
349			lang: Lang::Rs,
350			graph,
351			source: String::new(),
352		};
353		let material = CodeIndexMaterial {
354			source_catalog: SourceCatalogMaterial {
355				sources: SourceFileSet {
356					roots: Vec::new(),
357					files: Vec::new(),
358					multi: false,
359				},
360				identity: identity.clone(),
361			},
362			files: vec![Arc::new(file)],
363			identity,
364			symbols_by_moniker: FxHashMap::default(),
365		};
366		(material, root, target)
367	}
368}