Skip to main content

necessist_core/
source_file.rs

1use crate::{
2    LineColumn, Rewriter, Span, offset_calculator::OffsetCalculator,
3    to_console_string::ToConsoleString, util,
4};
5use anyhow::Result;
6use std::{
7    cell::RefCell,
8    collections::HashMap,
9    fs::read_to_string,
10    ops::Deref,
11    path::{Path, PathBuf},
12    rc::Rc,
13};
14
15thread_local! {
16    static ROOT: RefCell<Option<Rc<PathBuf>>> = const { RefCell::new(None) };
17    static SOURCE_FILES: RefCell<HashMap<PathBuf, SourceFile>> = RefCell::new(HashMap::new());
18}
19
20#[derive(Clone, Eq, PartialEq)]
21pub struct SourceFile {
22    inner: Rc<Inner>,
23}
24
25struct Inner {
26    root: Rc<PathBuf>,
27    path: PathBuf,
28    contents: &'static str,
29    offset_calculator: Rc<RefCell<OffsetCalculator<'static>>>,
30}
31
32impl Eq for Inner {}
33
34impl PartialEq for Inner {
35    fn eq(&self, other: &Self) -> bool {
36        self.root.eq(&other.root) && self.path.eq(&other.path)
37    }
38}
39
40impl SourceFile {
41    pub fn new(root: Rc<PathBuf>, path: PathBuf) -> Result<Self> {
42        ROOT.with(|root_prev| {
43            let mut root_prev = root_prev.borrow_mut();
44
45            if let Some(root_prev) = root_prev.as_ref() {
46                assert_eq!(*root_prev, root);
47            } else {
48                assert!(root.is_absolute());
49                *root_prev = Some(root.clone());
50            }
51        });
52
53        assert!(path.starts_with(&*root));
54
55        SOURCE_FILES.with(|source_files| {
56            let mut source_files = source_files.borrow_mut();
57
58            if let Some(source_file) = source_files.get(&path) {
59                Ok(source_file.clone())
60            } else {
61                let contents = read_to_string(&path)?;
62                let leaked = Box::leak(contents.into_boxed_str());
63                let source_file = Self {
64                    inner: Rc::new(Inner {
65                        root,
66                        path: path.clone(),
67                        contents: leaked,
68                        offset_calculator: Rc::new(RefCell::new(OffsetCalculator::new(leaked))),
69                    }),
70                };
71                source_files.insert(path, source_file.clone());
72                Ok(source_file)
73            }
74        })
75    }
76
77    fn relative_path(&self) -> &Path {
78        #[allow(clippy::unwrap_used)]
79        util::strip_prefix(&self.inner.path, &self.inner.root).unwrap()
80    }
81
82    // smoelius: Leaking the file contents is a hack.
83    #[must_use]
84    pub fn contents(&self) -> &'static str {
85        self.inner.contents
86    }
87
88    #[must_use]
89    pub fn offset_calculator(&self) -> Rc<RefCell<OffsetCalculator<'static>>> {
90        self.inner.offset_calculator.clone()
91    }
92
93    pub fn insert(&self, rewriter: &mut Rewriter, line_column: LineColumn, insertion: &str) {
94        let span = Span {
95            source_file: self.clone(),
96            start: line_column,
97            end: line_column,
98        };
99
100        let _: String = rewriter.rewrite(&span, insertion);
101    }
102}
103
104impl std::fmt::Debug for SourceFile {
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        <_ as std::fmt::Debug>::fmt(&self.inner.path, f)
107    }
108}
109
110/// Gives the path relative to the project root
111impl std::fmt::Display for SourceFile {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        write!(f, "{}", self.relative_path().to_string_lossy())
114    }
115}
116
117impl ToConsoleString for SourceFile {
118    fn to_console_string(&self) -> String {
119        util::strip_current_dir(self).to_string_lossy().to_string()
120    }
121}
122
123impl Ord for SourceFile {
124    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
125        self.relative_path().cmp(other.relative_path())
126    }
127}
128
129impl PartialOrd for SourceFile {
130    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
131        Some(self.cmp(other))
132    }
133}
134
135impl AsRef<Path> for SourceFile {
136    fn as_ref(&self) -> &Path {
137        &self.inner.path
138    }
139}
140
141impl Deref for SourceFile {
142    type Target = Path;
143    fn deref(&self) -> &Self::Target {
144        #[allow(clippy::explicit_deref_methods)]
145        self.inner.path.deref()
146    }
147}