1use std::{
4 fmt,
5 path::{Path, PathBuf},
6};
7
8use text_size::{TextRange, TextSize};
9
10use super::FileId;
11
12#[derive(Debug, Clone, PartialEq, Eq, derive_more::From)]
14pub enum FileOverride {
15 None,
17 Number(u32),
19 Path(String),
21}
22
23impl FileOverride {
24 pub fn is_none(&self) -> bool {
26 matches!(self, Self::None)
27 }
28}
29
30impl Default for FileOverride {
31 fn default() -> Self {
32 Self::None
33 }
34}
35
36impl fmt::Display for FileOverride {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 match self {
39 FileOverride::None => Ok(()),
40 FileOverride::Number(number) => write!(f, "{}", number),
41 FileOverride::Path(path) => write!(f, "{}", path),
42 }
43 }
44}
45
46pub trait Resolver {
48 fn resolve(&self, offset: TextSize) -> (u32, u32);
54}
55
56impl<'s> Resolver for &'s str {
57 fn resolve(&self, offset: TextSize) -> (u32, u32) {
58 let offset: usize = offset.into();
59 let offset = if offset >= self.len() {
60 self.len().max(1) - 1
61 } else {
62 offset
63 };
64
65 let line_start = line_span::find_line_start(self, offset);
67
68 let line_index = self
70 .bytes()
71 .take(line_start)
72 .filter(|c| *c == b'\n')
73 .count();
74
75 let pos_index = offset - line_start;
77
78 (line_index as _, pos_index as _)
79 }
80}
81
82pub trait HasFileNumber {
84 fn current_file(&self) -> FileId;
86}
87
88pub trait FileIdResolver {
90 fn resolve(&self, file_id: FileId) -> Option<&Path>;
92}
93
94#[derive(Default)]
96pub struct LocatedBuilder {
97 pos: TextRange,
99 current_file: Option<FileId>,
101 path: Option<PathBuf>,
103 file_override: FileOverride,
105 line_number: u32,
107 column: u32,
109}
110
111impl LocatedBuilder {
112 pub fn new() -> Self {
114 Self::default()
115 }
116
117 pub fn pos(self, pos: impl Into<TextRange>) -> Self {
119 Self {
120 pos: pos.into(),
121 ..self
122 }
123 }
124
125 pub fn current_file(self, file: impl Into<FileId>) -> Self {
127 Self {
128 current_file: Some(file.into()),
129 ..self
130 }
131 }
132
133 pub fn path(self, path: impl Into<PathBuf>) -> Self {
135 Self {
136 path: Some(path.into()),
137 ..self
138 }
139 }
140
141 pub fn file_override(self, file_override: impl Into<FileOverride>) -> Self {
143 Self {
144 file_override: file_override.into(),
145 ..self
146 }
147 }
148
149 pub fn line_number(self, line_number: u32) -> Self {
151 Self {
152 line_number,
153 ..self
154 }
155 }
156
157 pub fn column(self, column: u32) -> Self {
159 Self { column, ..self }
160 }
161
162 pub fn resolve(self, resolver: &impl Resolver) -> Self {
164 let (line, col) = resolver.resolve(self.pos.start());
165 Self {
166 line_number: line,
167 column: col,
168 ..self
169 }
170 }
171
172 pub fn resolve_file(self, resolver: &(impl Resolver + HasFileNumber)) -> Self {
175 self.resolve(resolver).current_file(resolver.current_file())
176 }
177
178 pub fn resolve_path(self, resolver: &impl FileIdResolver) -> Self {
180 Self {
181 path: self
182 .current_file
183 .and_then(|current_file| resolver.resolve(current_file).map(Path::to_owned)),
184 ..self
185 }
186 }
187
188 pub fn finish<E>(self, inner: E) -> Located<E> {
190 Located {
191 inner,
192 pos: self.pos,
193 current_file: self.current_file,
194 path: self.path,
195 file_override: self.file_override,
196 line_number: self.line_number,
197 column: self.column,
198 }
199 }
200}
201
202#[derive(Debug)]
204pub struct Located<E> {
205 inner: E,
207 pos: TextRange,
209 current_file: Option<FileId>,
211 path: Option<PathBuf>,
213 file_override: FileOverride,
215 line_number: u32,
217 column: u32,
219}
220
221impl<E> Located<E> {
222 pub fn builder() -> LocatedBuilder {
224 LocatedBuilder::default()
225 }
226
227 pub fn map<F>(self, f: impl FnOnce(E) -> F) -> Located<F> {
233 Located {
234 inner: f(self.inner),
235 pos: self.pos,
236 current_file: self.current_file,
237 path: self.path,
238 file_override: self.file_override,
239 line_number: self.line_number,
240 column: self.column,
241 }
242 }
243
244 pub fn inner(&self) -> &E {
246 &self.inner
247 }
248
249 pub fn into_inner(self) -> E {
251 self.inner
252 }
253
254 pub fn current_file(&self) -> Option<FileId> {
256 self.current_file
257 }
258
259 pub fn set_current_file(&mut self, current_file: FileId) {
261 self.current_file = Some(current_file);
262 }
263
264 pub fn pos(&self) -> TextRange {
266 self.pos
267 }
268
269 pub fn line(&self) -> u32 {
271 self.line_number
272 }
273
274 pub fn col(&self) -> u32 {
276 self.column
277 }
278}
279
280impl<E: Clone> Clone for Located<E> {
281 fn clone(&self) -> Self {
282 Self {
283 inner: self.inner.clone(),
284 pos: self.pos,
285 current_file: self.current_file,
286 path: self.path.clone(),
287 file_override: self.file_override.clone(),
288 line_number: self.line_number,
289 column: self.column,
290 }
291 }
292}
293
294impl<E: PartialEq> PartialEq for Located<E> {
295 fn eq(&self, other: &Self) -> bool {
296 self.inner == other.inner
297 && self.pos == other.pos
298 && self.current_file == other.current_file
299 && self.path == other.path
300 && self.file_override == other.file_override
301 && self.line_number == other.line_number
302 && self.column == other.column
303 }
304}
305
306impl<E: Eq> Eq for Located<E> {}
307
308impl<E: std::error::Error> std::error::Error for Located<E> {}
309
310impl<E: std::fmt::Display> std::fmt::Display for Located<E> {
311 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312 if self.file_override.is_none() {
313 if let Some(path) = self.path.as_ref() {
314 write!(f, "{}:", path.display())?;
315 } else if let Some(current_file) = self.current_file {
316 write!(f, "{}:", current_file)?;
317 }
318 } else {
319 write!(f, "{}:", self.file_override)?;
320 }
321
322 write!(
323 f,
324 "{}:{}: {}",
325 self.line_number + 1,
326 self.column + 1,
327 self.inner
328 )
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::Resolver;
335 use std::convert::TryInto;
336
337 #[test]
338 fn resolved_position() {
339 let s = r#"
340Hello,
341World"#;
342
343 let offset = s.find('r').unwrap().try_into().unwrap();
344 let resolved = s.resolve(offset);
345
346 assert_eq!(resolved.0, 2);
347 assert_eq!(resolved.1, 2);
348 }
349
350 #[test]
351 fn resolved_position_last_char() {
352 let s = r#"
353Hello,
354World"#;
355
356 let offset = s.find('d').unwrap().try_into().unwrap();
357 let resolved = s.resolve(offset);
358
359 assert_eq!(resolved.0, 2);
360 assert_eq!(resolved.1, 4);
361 }
362
363 #[test]
364 fn resolved_position_out_of_bounds() {
365 let offset = 1.into();
366 assert_eq!("".resolve(offset).0, 0);
367 }
368}