code_moniker_workspace/source/
content.rs1use 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}