necessist_core/
source_file.rs1use 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 #[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
110impl 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}