solar_interface/source_map/
file.rs1use crate::{BytePos, CharPos, pos::RelativeBytePos};
2use std::{
3 fmt, io,
4 ops::{Range, RangeInclusive},
5 path::{Path, PathBuf},
6 sync::Arc,
7};
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub struct MultiByteChar {
12 pub pos: RelativeBytePos,
14 pub bytes: u8,
16}
17
18#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub enum FileName {
24 Real(PathBuf),
26 Stdin,
28 Custom(String),
30}
31
32impl PartialEq<Path> for FileName {
33 fn eq(&self, other: &Path) -> bool {
34 match self {
35 Self::Real(p) => p == other,
36 _ => false,
37 }
38 }
39}
40
41impl PartialEq<&Path> for FileName {
42 fn eq(&self, other: &&Path) -> bool {
43 match self {
44 Self::Real(p) => p == *other,
45 _ => false,
46 }
47 }
48}
49
50impl PartialEq<PathBuf> for FileName {
51 fn eq(&self, other: &PathBuf) -> bool {
52 match self {
53 Self::Real(p) => p == other,
54 _ => false,
55 }
56 }
57}
58
59impl From<PathBuf> for FileName {
60 fn from(p: PathBuf) -> Self {
61 Self::Real(p)
62 }
63}
64
65impl From<&PathBuf> for FileName {
66 fn from(p: &PathBuf) -> Self {
67 Self::Real(p.clone())
68 }
69}
70
71impl From<&Path> for FileName {
72 fn from(p: &Path) -> Self {
73 Self::Real(p.to_path_buf())
74 }
75}
76
77impl From<String> for FileName {
78 fn from(s: String) -> Self {
79 Self::Custom(s)
80 }
81}
82
83impl From<&Self> for FileName {
84 fn from(s: &Self) -> Self {
85 s.clone()
86 }
87}
88
89impl FileName {
90 pub fn parse(sm: &crate::SourceMap, s: &str) -> Self {
94 sm.parse_file_name(s)
95 }
96
97 pub fn real(path: impl Into<PathBuf>) -> Self {
99 Self::Real(path.into())
100 }
101
102 pub fn custom(s: impl Into<String>) -> Self {
104 Self::Custom(s.into())
105 }
106
107 #[inline]
109 pub fn display(&self) -> FileNameDisplay<'_> {
110 let base_path =
111 crate::SessionGlobals::try_with(|g| g.and_then(|g| g.source_map.base_path()));
112 FileNameDisplay { inner: self, base_path }
113 }
114
115 #[inline]
117 pub fn as_real(&self) -> Option<&Path> {
118 match self {
119 Self::Real(path) => Some(path),
120 _ => None,
121 }
122 }
123}
124
125pub struct FileNameDisplay<'a> {
129 pub(crate) inner: &'a FileName,
130 pub(crate) base_path: Option<PathBuf>,
131}
132
133impl fmt::Display for FileNameDisplay<'_> {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 match self.inner {
136 FileName::Real(path) => {
137 let path = if let Some(base_path) = &self.base_path
138 && let Ok(rpath) = path.strip_prefix(base_path)
139 {
140 rpath
141 } else {
142 path.as_path()
143 };
144 path.display().fmt(f)
145 }
146 FileName::Stdin => f.write_str("<stdin>"),
147 FileName::Custom(s) => write!(f, "<{s}>"),
148 }
149 }
150}
151
152#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
153pub(crate) struct SourceFileId(u64);
154
155impl SourceFileId {
156 pub(crate) fn new(filename: &FileName) -> Self {
157 use std::hash::{Hash, Hasher};
158 let mut hasher = solar_data_structures::map::FxHasher::with_seed(0);
159 filename.hash(&mut hasher);
160 Self(hasher.finish())
161 }
162}
163
164#[derive(Debug)]
166pub struct OffsetOverflowError(pub(crate) ());
167
168impl fmt::Display for OffsetOverflowError {
169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170 f.write_str("files larger than 4GiB are not supported")
171 }
172}
173
174impl std::error::Error for OffsetOverflowError {}
175
176impl From<OffsetOverflowError> for io::Error {
177 fn from(e: OffsetOverflowError) -> Self {
178 Self::new(io::ErrorKind::FileTooLarge, e)
179 }
180}
181
182#[derive(Clone, derive_more::Debug)]
184#[non_exhaustive]
185pub struct SourceFile {
186 pub name: FileName,
190 #[debug(skip)]
192 pub src: Arc<String>,
193 pub start_pos: BytePos,
195 pub source_len: RelativeBytePos,
197 #[debug(skip)]
199 lines: Vec<RelativeBytePos>,
200 #[debug(skip)]
202 pub multibyte_chars: Vec<MultiByteChar>,
203}
204
205impl PartialEq for SourceFile {
206 fn eq(&self, other: &Self) -> bool {
207 self.start_pos == other.start_pos
208 }
209}
210
211impl Eq for SourceFile {}
212
213impl std::hash::Hash for SourceFile {
214 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
215 self.start_pos.hash(state);
216 }
217}
218
219impl SourceFile {
220 pub(crate) fn new(
222 name: FileName,
223 id: SourceFileId,
224 mut src: String,
225 ) -> Result<Self, OffsetOverflowError> {
226 debug_assert_eq!(id, SourceFileId::new(&name));
232 let source_len = src.len();
233 let source_len = u32::try_from(source_len).map_err(|_| OffsetOverflowError(()))?;
234
235 let (lines, multibyte_chars) = super::analyze::analyze_source_file(&src);
236
237 src.shrink_to_fit();
238 Ok(Self {
239 name,
240 src: Arc::new(src),
241 start_pos: BytePos::from_u32(0),
242 source_len: RelativeBytePos::from_u32(source_len),
243 lines,
244 multibyte_chars,
245 })
246 }
247
248 pub fn lines(&self) -> &[RelativeBytePos] {
249 &self.lines
250 }
251
252 pub fn count_lines(&self) -> usize {
253 self.lines().len()
254 }
255
256 #[inline]
257 pub fn absolute_position(&self, pos: RelativeBytePos) -> BytePos {
258 BytePos::from_u32(pos.to_u32() + self.start_pos.to_u32())
259 }
260
261 #[inline]
262 pub fn relative_position(&self, pos: BytePos) -> RelativeBytePos {
263 RelativeBytePos::from_u32(pos.to_u32() - self.start_pos.to_u32())
264 }
265
266 #[inline]
267 pub fn end_position(&self) -> BytePos {
268 self.absolute_position(self.source_len)
269 }
270
271 pub fn lookup_line(&self, pos: RelativeBytePos) -> Option<usize> {
276 self.lines().partition_point(|x| x <= &pos).checked_sub(1)
277 }
278
279 pub fn line_bounds(&self, line_index: usize) -> Range<BytePos> {
280 if self.is_empty() {
281 return self.start_pos..self.start_pos;
282 }
283
284 let lines = self.lines();
285 assert!(line_index < lines.len());
286 if line_index == (lines.len() - 1) {
287 self.absolute_position(lines[line_index])..self.end_position()
288 } else {
289 self.absolute_position(lines[line_index])..self.absolute_position(lines[line_index + 1])
290 }
291 }
292
293 pub fn line_position(&self, line_number: usize) -> Option<usize> {
296 self.lines().get(line_number).map(|x| x.to_usize())
297 }
298
299 pub(crate) fn bytepos_to_file_charpos(&self, bpos: RelativeBytePos) -> CharPos {
301 let mut total_extra_bytes = 0;
303
304 for mbc in self.multibyte_chars.iter() {
305 if mbc.pos < bpos {
306 total_extra_bytes += mbc.bytes as u32 - 1;
309 assert!(bpos.to_u32() >= mbc.pos.to_u32() + mbc.bytes as u32);
312 } else {
313 break;
314 }
315 }
316
317 assert!(total_extra_bytes <= bpos.to_u32());
318 CharPos(bpos.to_usize() - total_extra_bytes as usize)
319 }
320
321 fn lookup_file_pos(&self, pos: RelativeBytePos) -> (usize, CharPos) {
324 let chpos = self.bytepos_to_file_charpos(pos);
325 match self.lookup_line(pos) {
326 Some(a) => {
327 let line = a + 1; let linebpos = self.lines()[a];
329 let linechpos = self.bytepos_to_file_charpos(linebpos);
330 let col = chpos - linechpos;
331 assert!(chpos >= linechpos);
332 (line, col)
333 }
334 None => (0, chpos),
335 }
336 }
337
338 pub fn lookup_file_pos_with_col_display(&self, pos: BytePos) -> (usize, CharPos, usize) {
341 let pos = self.relative_position(pos);
342 let (line, col_or_chpos) = self.lookup_file_pos(pos);
343 if line > 0 {
344 let Some(code) = self.get_line(line - 1) else {
345 debug!("couldn't find line {line} in {:?}", self.name);
353 return (line, col_or_chpos, col_or_chpos.0);
354 };
355 let display_col = code.chars().take(col_or_chpos.0).map(char_width).sum();
356 (line, col_or_chpos, display_col)
357 } else {
358 (0, col_or_chpos, col_or_chpos.0)
360 }
361 }
362
363 pub fn get_line(&self, line_number: usize) -> Option<&str> {
366 fn get_until_newline(src: &str, begin: usize) -> &str {
367 let slice = &src[begin..];
371 match slice.find('\n') {
372 Some(e) => &slice[..e],
373 None => slice,
374 }
375 }
376
377 let start = self.lines().get(line_number)?.to_usize();
378 Some(get_until_newline(&self.src, start))
379 }
380
381 pub fn get_lines(&self, range: RangeInclusive<usize>) -> Option<&str> {
384 fn get_until_newline(src: &str, start: usize, end: usize) -> &str {
385 match src[end..].find('\n') {
386 Some(e) => &src[start..end + e + 1],
387 None => &src[start..],
388 }
389 }
390
391 let (start, end) = range.into_inner();
392 let lines = self.lines();
393 let start = lines.get(start)?.to_usize();
394 let end = lines.get(end)?.to_usize();
395 Some(get_until_newline(&self.src, start, end))
396 }
397
398 #[inline]
403 pub fn contains(&self, byte_pos: BytePos) -> bool {
404 byte_pos >= self.start_pos && byte_pos <= self.end_position()
405 }
406
407 #[inline]
408 pub fn is_empty(&self) -> bool {
409 self.source_len.to_u32() == 0
410 }
411
412 pub fn original_relative_byte_pos(&self, pos: BytePos) -> RelativeBytePos {
415 let pos = self.relative_position(pos);
416 RelativeBytePos::from_u32(pos.0)
417 }
418}
419
420pub fn char_width(ch: char) -> usize {
421 match ch {
422 '\t' => 4,
423 _ => unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1),
424 }
425}