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
15pub struct KeepTrackOfSourceLine {
16 pub last_line_info: SourceFileLineInfo,
17 pub current_line: usize,
18}
19
20impl Default for KeepTrackOfSourceLine {
21 fn default() -> Self {
22 Self::new()
23 }
24}
25
26impl KeepTrackOfSourceLine {
27 #[must_use]
28 pub const fn new() -> Self {
29 Self {
30 last_line_info: SourceFileLineInfo {
31 row: usize::MAX,
32 file_id: usize::MAX,
33 },
34 current_line: usize::MAX,
35 }
36 }
37
38 pub fn check_if_new_line(&mut self, found: &SourceFileLineInfo) -> Option<(usize, usize)> {
39 if self.last_line_info.file_id != found.file_id || found.row != self.current_line {
40 self.last_line_info = found.clone();
41 self.current_line = self.last_line_info.row;
42 Some((self.last_line_info.row, self.last_line_info.row))
43 } else if found.row == self.current_line {
44 None
45 } else {
46 let line_start = self.current_line;
47 self.current_line = found.row;
48 Some((line_start, found.row))
49 }
50 }
51}
52
53#[derive(Eq, PartialEq, Clone)]
54pub struct SourceFileLineInfo {
55 pub row: usize,
56 pub file_id: usize,
57}
58
59#[derive(Debug)]
60pub struct FileInfo {
61 pub mount_name: String,
62 pub relative_path: PathBuf,
63 pub contents: String,
64 pub line_offsets: Box<[u16]>,
65}
66
67#[derive(Debug)]
68pub struct SourceMap {
69 pub mounts: SeqMap<String, PathBuf>,
70 pub cache: SeqMap<FileId, FileInfo>,
71 pub file_cache: SeqMap<(String, String), FileId>,
72 pub next_file_id: FileId,
73}
74
75#[derive(Debug)]
76pub struct RelativePath(pub String);
77
78impl SourceMap {
79 pub fn new(mounts: &SeqMap<String, PathBuf>) -> io::Result<Self> {
82 let mut canonical_mounts = SeqMap::new();
83 for (mount_name, base_path) in mounts {
84 let canon_path = base_path.canonicalize().map_err(|_| {
85 io::Error::new(
86 io::ErrorKind::InvalidData,
87 format!("could not canonicalize {base_path:?}"),
88 )
89 })?;
90
91 if !canon_path.is_dir() {
92 return Err(io::Error::new(
93 ErrorKind::NotFound,
94 format!("{canon_path:?} is not a directory"),
95 ));
96 }
97 canonical_mounts
98 .insert(mount_name.clone(), canon_path)
99 .map_err(|_| {
100 io::Error::new(io::ErrorKind::InvalidData, "could not insert mount")
101 })?;
102 }
103 Ok(Self {
104 mounts: canonical_mounts,
105 cache: SeqMap::new(),
106 file_cache: SeqMap::new(),
107 next_file_id: 1,
108 })
109 }
110
111 pub fn add_mount(&mut self, name: &str, path: &Path) -> io::Result<()> {
114 if !path.is_dir() {
115 return Err(io::Error::new(
116 ErrorKind::NotFound,
117 format!("{path:?} is not a directory"),
118 ));
119 }
120 self.mounts
121 .insert(name.to_string(), path.to_path_buf())
122 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "could not insert mount"))
123 }
124
125 #[must_use]
126 pub fn base_path(&self, name: &str) -> &Path {
127 self.mounts.get(&name.to_string()).map_or_else(
128 || {
129 panic!("could not find path {name}");
130 },
131 |found| found,
132 )
133 }
134
135 pub fn read_file(&mut self, path: &Path, mount_name: &str) -> io::Result<(FileId, String)> {
136 let found_base_path = self.base_path(mount_name);
137 let relative_path = diff_paths(path, found_base_path)
138 .unwrap_or_else(|| panic!("could not find relative path {path:?} {found_base_path:?}"));
139
140 let contents = fs::read_to_string(path)?;
141
142 let id = self.next_file_id;
143 self.next_file_id += 1;
144
145 self.add_manual(id, mount_name, &relative_path, &contents);
146
147 Ok((id, contents))
148 }
149
150 pub fn add_to_cache(
151 &mut self,
152 mount_name: &str,
153 relative_path: &Path,
154 contents: &str,
155 file_id: FileId,
156 ) {
157 self.add_manual(file_id, mount_name, relative_path, contents);
158 }
159
160 pub fn add_manual(
161 &mut self,
162 id: FileId,
163 mount_name: &str,
164 relative_path: &Path,
165 contents: &str,
166 ) {
167 let line_offsets = Self::compute_line_offsets(contents);
168
169 self.cache
170 .insert(
171 id,
172 FileInfo {
173 mount_name: mount_name.to_string(),
174 relative_path: relative_path.to_path_buf(),
175 contents: contents.to_string(),
176 line_offsets,
177 },
178 )
179 .expect("could not add file info");
180
181 self.file_cache
183 .insert(
184 (
185 mount_name.to_string(),
186 relative_path.to_str().unwrap().to_string(),
187 ),
188 id,
189 )
190 .expect("could not add to file cache");
191 }
192
193 pub fn add_manual_no_id(
194 &mut self,
195 mount_name: &str,
196 relative_path: &Path,
197 contents: &str,
198 ) -> FileId {
199 let line_offsets = Self::compute_line_offsets(contents);
200 let id = self.next_file_id;
201 self.next_file_id += 1;
202
203 self.cache
204 .insert(
205 id,
206 FileInfo {
207 mount_name: mount_name.to_string(),
208 relative_path: relative_path.to_path_buf(),
209 contents: contents.to_string(),
210 line_offsets,
211 },
212 )
213 .expect("could not add file info");
214
215 self.file_cache
217 .insert(
218 (
219 mount_name.to_string(),
220 relative_path.to_str().unwrap().to_string(),
221 ),
222 id,
223 )
224 .expect("could not add to file cache");
225
226 id
227 }
228
229 pub fn read_file_relative(
230 &mut self,
231 mount_name: &str,
232 relative_path: &str,
233 ) -> io::Result<(FileId, String)> {
234 if let Some(found_in_cache) = self
235 .file_cache
236 .get(&(mount_name.to_string(), relative_path.to_string()))
237 {
238 let contents = self.cache.get(found_in_cache).unwrap().contents.clone();
239 return Ok((*found_in_cache, contents));
240 }
241
242 let buf = self.to_file_system_path(mount_name, relative_path)?;
243 self.read_file(&buf, mount_name)
244 }
245
246 fn to_file_system_path(&self, mount_name: &str, relative_path: &str) -> io::Result<PathBuf> {
247 let base_path = self.base_path(mount_name).to_path_buf();
248 let mut path_buf = base_path;
249
250 path_buf.push(relative_path);
251
252 path_buf.canonicalize().map_err(|_| {
253 io::Error::other(format!(
254 "path is wrong mount:{mount_name} relative:{relative_path}",
255 ))
256 })
257 }
258
259 fn compute_line_offsets(contents: &str) -> Box<[u16]> {
260 let mut offsets = Vec::new();
261 offsets.push(0);
262
263 for (i, &byte) in contents.as_bytes().iter().enumerate() {
265 if byte == b'\n' {
266 let next_line_start = u16::try_from(i + 1).expect("too big file");
268 offsets.push(next_line_start);
269 }
270 }
271
272 let eof_offset = u16::try_from(contents.len()).expect("too big file");
275 if offsets.last().is_none_or(|&last| last != eof_offset) {
276 offsets.push(eof_offset);
277 }
278
279 offsets.into_boxed_slice()
280 }
281
282 #[must_use]
283 pub fn get_span_source(&self, file_id: FileId, offset: usize, length: usize) -> &str {
284 self.cache.get(&file_id).map_or_else(
285 || {
286 "ERROR"
287 },
289 |file_info| {
290 let start = offset;
291 let end = start + length;
292 &file_info.contents[start..end]
293 },
294 )
295 }
296
297 #[must_use]
298 pub fn get_source_line(&self, file_id: FileId, line_number: usize) -> Option<&str> {
299 let file_info = self.cache.get(&file_id)?;
300
301 if line_number == 0 || line_number >= file_info.line_offsets.len() {
303 return None;
304 }
305
306 let start_offset = file_info.line_offsets[line_number - 1] as usize;
307 let end_offset = file_info.line_offsets[line_number] as usize;
308
309 let line = &file_info.contents[start_offset..end_offset];
310
311 if line.ends_with('\n') {
314 Some(&line[..line.len() - 1])
315 } else {
316 Some(line)
317 }
318 }
319
320 #[must_use]
321 pub fn get_span_location_utf8(&self, file_id: FileId, offset: usize) -> (usize, usize) {
322 let file_info = self.cache.get(&file_id).expect("Invalid file_id in span");
323
324 let offset = offset as u16;
325
326 let line_idx = file_info
328 .line_offsets
329 .binary_search(&offset)
330 .unwrap_or_else(|insert_point| insert_point.saturating_sub(1));
331
332 let line_start = file_info.line_offsets[line_idx] as usize;
334 let octet_offset = offset as usize;
335
336 let line_text = &file_info.contents[line_start..octet_offset];
338
339 let column_character_offset = line_text.chars().count();
341
342 (line_idx + 1, column_character_offset + 1)
344 }
345
346 #[must_use]
347 pub fn fetch_relative_filename(&self, file_id: FileId) -> &str {
348 self.cache
349 .get(&file_id)
350 .unwrap()
351 .relative_path
352 .to_str()
353 .unwrap()
354 }
355
356 pub fn minimal_relative_path(target: &Path, current_dir: &Path) -> io::Result<PathBuf> {
357 let current_dir_components = current_dir.components().collect::<Vec<_>>();
358 let target_components = target.components().collect::<Vec<_>>();
359
360 let mut common_prefix_len = 0;
361 for i in 0..std::cmp::min(current_dir_components.len(), target_components.len()) {
362 if current_dir_components[i] == target_components[i] {
363 common_prefix_len += 1;
364 } else {
365 break;
366 }
367 }
368
369 let mut relative_path = PathBuf::new();
370
371 for _ in 0..(current_dir_components.len() - common_prefix_len) {
372 relative_path.push("..");
373 }
374
375 for component in &target_components[common_prefix_len..] {
376 relative_path.push(component);
377 }
378 Ok(relative_path)
379 }
380
381 pub fn get_relative_path_to(&self, file_id: FileId, current_dir: &Path) -> io::Result<PathBuf> {
382 let file_info = self.cache.get(&file_id).unwrap();
383 let mount_path = self.mounts.get(&file_info.mount_name).unwrap();
384
385 let absolute_path = mount_path.join(&file_info.relative_path);
386
387 Self::minimal_relative_path(&absolute_path, current_dir)
388 }
389
390 #[must_use]
391 pub fn get_text(&self, node: &Node) -> &str {
392 self.get_span_source(
393 node.span.file_id,
394 node.span.offset as usize,
395 node.span.length as usize,
396 )
397 }
398
399 #[must_use]
400 pub fn get_text_span(&self, span: &Span) -> &str {
401 self.get_span_source(span.file_id, span.offset as usize, span.length as usize)
402 }
403
404 #[must_use]
405 pub fn get_line(&self, span: &Span, current_dir: &Path) -> FileLineInfo {
406 let relative_file_name = self
407 .get_relative_path_to(span.file_id, current_dir)
408 .unwrap();
409 let (row, col) = self.get_span_location_utf8(span.file_id, span.offset as usize);
410 let line = self.get_source_line(span.file_id, row).unwrap();
411
412 FileLineInfo {
413 row,
414 col,
415 line: line.to_string(),
416 relative_file_name: relative_file_name.to_str().unwrap().to_string(),
417 }
418 }
419
420 pub fn set(&mut self, mount_name: &str, relative_path: &Path, contents: &str) -> FileId {
421 if let Some(&existing_file_id) = self.file_cache.get(&(
423 mount_name.to_string(),
424 relative_path.to_str().unwrap().to_string(),
425 )) {
426 let line_offsets = Self::compute_line_offsets(contents);
428
429 if let Some(file_info) = self.cache.get_mut(&existing_file_id) {
430 file_info.contents = contents.to_string();
431 file_info.line_offsets = line_offsets;
432 }
433
434 existing_file_id
435 } else {
436 self.add_manual_no_id(mount_name, relative_path, contents)
438 }
439 }
440}
441
442pub struct FileLineInfo {
443 pub row: usize,
444 pub col: usize,
445 pub line: String,
446 pub relative_file_name: String,
447}
448
449pub struct SourceLineInfo {
450 pub line: String,
451 pub relative_file_name: String,
452}
453
454pub trait SourceMapLookup: Debug {
455 fn get_text(&self, node: &Node) -> &str;
456 fn get_text_span(&self, span: &Span) -> &str;
457 fn get_line(&self, span: &Span) -> FileLineInfo;
458 fn get_relative_path(&self, file_id: FileId) -> String;
459 fn get_source_line(&self, file_id: FileId, row: usize) -> Option<&str>;
460}
461
462#[derive(Debug)]
463pub struct SourceMapWrapper<'a> {
464 pub source_map: &'a SourceMap,
465 pub current_dir: PathBuf,
466}
467
468impl SourceMapLookup for SourceMapWrapper<'_> {
469 fn get_text(&self, resolved_node: &Node) -> &str {
470 self.source_map.get_text(resolved_node)
471 }
472
473 fn get_text_span(&self, span: &Span) -> &str {
474 self.source_map.get_text_span(span)
475 }
476
477 fn get_line(&self, span: &Span) -> FileLineInfo {
478 self.source_map.get_line(span, &self.current_dir)
479 }
480
481 fn get_relative_path(&self, file_id: FileId) -> String {
482 self.source_map
483 .get_relative_path_to(file_id, &self.current_dir)
484 .unwrap()
485 .to_str()
486 .unwrap()
487 .to_string()
488 }
489
490 fn get_source_line(&self, file_id: FileId, line_number: usize) -> Option<&str> {
491 self.source_map.get_source_line(file_id, line_number)
492 }
493}