1use pathdiff::diff_paths;
6use seq_map::SeqMap;
7use source_map_node::{Node, Span};
8use std::fmt::Debug;
9use std::io::ErrorKind;
10use std::path::{Path, PathBuf};
11use std::{fs, io};
12pub mod prelude;
13pub type FileId = u16;
14
15#[derive(Debug)]
16pub struct FileInfo {
17 pub mount_name: String,
18 pub relative_path: PathBuf,
19 pub contents: String,
20 pub line_offsets: Box<[u16]>,
21}
22
23#[derive(Debug)]
24pub struct SourceMap {
25 pub mounts: SeqMap<String, PathBuf>,
26 pub cache: SeqMap<FileId, FileInfo>,
27 pub file_cache: SeqMap<(String, String), FileId>,
28 pub next_file_id: FileId,
29}
30
31#[derive(Debug)]
32pub struct RelativePath(pub String);
33
34impl SourceMap {
35 pub fn new(mounts: &SeqMap<String, PathBuf>) -> io::Result<Self> {
38 let mut canonical_mounts = SeqMap::new();
39 for (mount_name, base_path) in mounts {
40 let canon_path = base_path.canonicalize().map_err(|_| {
41 io::Error::new(
42 io::ErrorKind::InvalidData,
43 format!("could not canonicalize {base_path:?}"),
44 )
45 })?;
46
47 if !canon_path.is_dir() {
48 return Err(io::Error::new(
49 ErrorKind::NotFound,
50 format!("{canon_path:?} is not a directory"),
51 ));
52 }
53 canonical_mounts
54 .insert(mount_name.clone(), canon_path)
55 .map_err(|_| {
56 io::Error::new(io::ErrorKind::InvalidData, "could not insert mount")
57 })?;
58 }
59 Ok(Self {
60 mounts: canonical_mounts,
61 cache: SeqMap::new(),
62 file_cache: SeqMap::new(),
63 next_file_id: 1,
64 })
65 }
66
67 pub fn add_mount(&mut self, name: &str, path: &Path) -> io::Result<()> {
70 if !path.is_dir() {
71 return Err(io::Error::new(
72 ErrorKind::NotFound,
73 format!("{path:?} is not a directory"),
74 ));
75 }
76 self.mounts
77 .insert(name.to_string(), path.to_path_buf())
78 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "could not insert mount"))
79 }
80
81 #[must_use]
82 pub fn base_path(&self, name: &str) -> &Path {
83 self.mounts.get(&name.to_string()).map_or_else(
84 || {
85 panic!("could not find path {name}");
86 },
87 |found| found,
88 )
89 }
90
91 pub fn read_file(&mut self, path: &Path, mount_name: &str) -> io::Result<(FileId, String)> {
92 let found_base_path = self.base_path(mount_name);
93 let relative_path = diff_paths(path, found_base_path)
94 .unwrap_or_else(|| panic!("could not find relative path {path:?} {found_base_path:?}"));
95
96 let contents = fs::read_to_string(path)?;
97
98 let id = self.next_file_id;
99 self.next_file_id += 1;
100
101 self.add_manual(id, mount_name, &relative_path, &contents);
102
103 Ok((id, contents))
104 }
105
106 pub fn add_to_cache(
107 &mut self,
108 mount_name: &str,
109 relative_path: &Path,
110 contents: &str,
111 file_id: FileId,
112 ) {
113 self.add_manual(file_id, mount_name, relative_path, contents);
114 self.file_cache
115 .insert(
116 (
117 mount_name.to_string(),
118 relative_path.to_str().unwrap().to_string(),
119 ),
120 file_id,
121 )
122 .unwrap();
123 }
124
125 pub fn add_manual(
126 &mut self,
127 id: FileId,
128 mount_name: &str,
129 relative_path: &Path,
130 contents: &str,
131 ) {
132 let line_offsets = Self::compute_line_offsets(contents);
133
134 self.cache
135 .insert(
136 id,
137 FileInfo {
138 mount_name: mount_name.to_string(),
139 relative_path: relative_path.to_path_buf(),
140 contents: contents.to_string(),
141 line_offsets,
142 },
143 )
144 .expect("could not add file info");
145 }
146
147 pub fn add_manual_no_id(
148 &mut self,
149 mount_name: &str,
150 relative_path: &Path,
151 contents: &str,
152 ) -> FileId {
153 let line_offsets = Self::compute_line_offsets(contents);
154 let id = self.next_file_id;
155 self.next_file_id += 1;
156
157 self.cache
158 .insert(
159 id,
160 FileInfo {
161 mount_name: mount_name.to_string(),
162 relative_path: relative_path.to_path_buf(),
163 contents: contents.to_string(),
164 line_offsets,
165 },
166 )
167 .expect("could not add file info");
168 id
169 }
170
171 pub fn read_file_relative(
172 &mut self,
173 mount_name: &str,
174 relative_path: &str,
175 ) -> io::Result<(FileId, String)> {
176 if let Some(found_in_cache) = self
177 .file_cache
178 .get(&(mount_name.to_string(), relative_path.to_string()))
179 {
180 let contents = self.cache.get(found_in_cache).unwrap().contents.clone();
181 return Ok((found_in_cache.clone(), contents));
182 }
183
184 let buf = self.to_file_system_path(mount_name, relative_path)?;
185 self.read_file(&buf, mount_name)
186 }
187
188 fn to_file_system_path(&self, mount_name: &str, relative_path: &str) -> io::Result<PathBuf> {
189 let base_path = self.base_path(mount_name).to_path_buf();
190 let mut path_buf = base_path;
191
192 path_buf.push(relative_path);
193
194 path_buf.canonicalize().map_err(|_| {
195 io::Error::new(
196 ErrorKind::Other,
197 format!("path is wrong mount:{mount_name} relative:{relative_path}",),
198 )
199 })
200 }
201
202 fn compute_line_offsets(contents: &str) -> Box<[u16]> {
203 let mut offsets = Vec::new();
204 offsets.push(0);
205 for (i, &byte) in contents.as_bytes().iter().enumerate() {
206 if byte == b'\n' {
207 let next_line_start = u16::try_from(i + 1).expect("too big file");
209 offsets.push(next_line_start);
210 }
211 }
212 offsets.into_boxed_slice()
213 }
214
215 #[must_use]
216 pub fn get_span_source(&self, file_id: FileId, offset: usize, length: usize) -> &str {
217 self.cache.get(&file_id).map_or_else(
218 || {
219 "ERROR"
220 },
222 |file_info| {
223 let start = offset;
224 let end = start + length;
225 &file_info.contents[start..end]
226 },
227 )
228 }
229
230 #[must_use]
231 pub fn get_source_line(&self, file_id: FileId, line_number: usize) -> Option<&str> {
232 let file_info = self.cache.get(&file_id)?;
233
234 let start_offset = file_info.line_offsets[line_number - 1] as usize;
235 let end_offset = file_info.line_offsets[line_number] as usize;
236 Some(&file_info.contents[start_offset..end_offset - 1])
237 }
238
239 #[must_use]
240 pub fn get_span_location_utf8(&self, file_id: FileId, offset: usize) -> (usize, usize) {
241 let file_info = self.cache.get(&file_id).expect("Invalid file_id in span");
242
243 let offset = offset as u16;
244
245 let line_idx = file_info
247 .line_offsets
248 .binary_search(&offset)
249 .unwrap_or_else(|insert_point| insert_point.saturating_sub(1));
250
251 let line_start = file_info.line_offsets[line_idx] as usize;
253 let octet_offset = offset as usize;
254
255 let line_text = &file_info.contents[line_start..octet_offset];
257
258 let column_character_offset = line_text.chars().count();
260
261 (line_idx + 1, column_character_offset + 1)
263 }
264
265 #[must_use]
266 pub fn fetch_relative_filename(&self, file_id: FileId) -> &str {
267 self.cache
268 .get(&file_id)
269 .unwrap()
270 .relative_path
271 .to_str()
272 .unwrap()
273 }
274
275 pub fn minimal_relative_path(target: &Path, current_dir: &Path) -> io::Result<PathBuf> {
276 let current_dir_components = current_dir.components().collect::<Vec<_>>();
277 let target_components = target.components().collect::<Vec<_>>();
278
279 let mut common_prefix_len = 0;
280 for i in 0..std::cmp::min(current_dir_components.len(), target_components.len()) {
281 if current_dir_components[i] == target_components[i] {
282 common_prefix_len += 1;
283 } else {
284 break;
285 }
286 }
287
288 let mut relative_path = PathBuf::new();
289
290 for _ in 0..(current_dir_components.len() - common_prefix_len) {
291 relative_path.push("..");
292 }
293
294 for component in &target_components[common_prefix_len..] {
295 relative_path.push(component);
296 }
297 Ok(relative_path)
298 }
299
300 pub fn get_relative_path_to(&self, file_id: FileId, current_dir: &Path) -> io::Result<PathBuf> {
301 let file_info = self.cache.get(&file_id).unwrap();
302 let mount_path = self.mounts.get(&file_info.mount_name).unwrap();
303
304 let absolute_path = mount_path.join(&file_info.relative_path);
305
306 Self::minimal_relative_path(&absolute_path, current_dir)
307 }
308
309 fn get_text(&self, node: &Node) -> &str {
310 self.get_span_source(
311 node.span.file_id,
312 node.span.offset as usize,
313 node.span.length as usize,
314 )
315 }
316
317 fn get_text_span(&self, span: &Span) -> &str {
318 self.get_span_source(span.file_id, span.offset as usize, span.length as usize)
319 }
320
321 fn get_line(&self, span: &Span, current_dir: &Path) -> FileLineInfo {
322 let relative_file_name = self
323 .get_relative_path_to(span.file_id, current_dir)
324 .unwrap();
325 let (row, col) = self.get_span_location_utf8(span.file_id, span.offset as usize);
326 let line = self.get_source_line(span.file_id, row).unwrap();
327
328 FileLineInfo {
329 row,
330 col,
331 line: line.to_string(),
332 relative_file_name: relative_file_name.to_str().unwrap().to_string(),
333 }
334 }
335}
336
337pub struct FileLineInfo {
338 pub row: usize,
339 pub col: usize,
340 pub line: String,
341 pub relative_file_name: String,
342}
343
344pub trait SourceMapLookup: Debug {
345 fn get_text(&self, node: &Node) -> &str;
346 fn get_text_span(&self, span: &Span) -> &str;
347 fn get_line(&self, span: &Span) -> FileLineInfo;
348}
349
350#[derive(Debug)]
351pub struct SourceMapWrapper<'a> {
352 pub source_map: &'a SourceMap,
353 pub current_dir: PathBuf,
354}
355
356impl SourceMapLookup for SourceMapWrapper<'_> {
357 fn get_text(&self, resolved_node: &Node) -> &str {
358 self.source_map.get_text(resolved_node)
359 }
360
361 fn get_text_span(&self, span: &Span) -> &str {
362 self.source_map.get_text_span(span)
363 }
364
365 fn get_line(&self, span: &Span) -> FileLineInfo {
366 self.source_map.get_line(span, &self.current_dir)
367 }
368}