1use std::{
2 fmt::{Debug, Display, Write},
3 hash::Hash,
4 ops::Range,
5 path::Path,
6};
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
10pub struct Position {
11 pub ln: Range<usize>,
12 pub col: Range<usize>,
13}
14pub struct Located<T> {
16 pub value: T,
17 pub pos: Position,
18}
19pub struct PathLocated<T> {
21 pub value: T,
22 pub pos: Position,
23 pub path: Box<Path>,
24}
25
26impl Position {
27 pub fn new(ln: Range<usize>, col: Range<usize>) -> Self {
28 Self { ln, col }
29 }
30 pub fn extend(&mut self, other: &Self) {
32 if self.ln.start > other.ln.start {
33 self.ln.start = other.ln.start;
34 }
35 if self.ln.end < other.ln.end {
36 self.ln.end = other.ln.end;
37 }
38 if self.col.start > other.col.start {
39 self.col.start = other.col.start;
40 }
41 if self.col.end < other.col.end {
42 self.col.end = other.col.end;
43 }
44 }
45}
46impl<T> Located<T> {
47 pub fn new(value: T, pos: Position) -> Self {
48 Self { value, pos }
49 }
50 pub fn new_default(value: T) -> Self {
52 Self {
53 value,
54 pos: Position::default(),
55 }
56 }
57 pub fn map<U, F: Fn(T) -> U>(self, f: F) -> Located<U> {
59 Located {
60 value: f(self.value),
61 pos: self.pos,
62 }
63 }
64 pub fn with_path(self, path: Box<Path>) -> PathLocated<T> {
65 PathLocated {
66 value: self.value,
67 pos: self.pos,
68 path,
69 }
70 }
71}
72impl<T: Default> Located<T> {
73 pub fn default_pos(pos: Position) -> Self {
75 Self {
76 value: T::default(),
77 pos,
78 }
79 }
80}
81impl<T: Default> Default for Located<T> {
82 fn default() -> Self {
83 Self {
84 value: T::default(),
85 pos: Position::default(),
86 }
87 }
88}
89impl<T: Debug> Debug for Located<T> {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 self.value.fmt(f)
92 }
93}
94impl<T: Display> Display for Located<T> {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 self.value.fmt(f)
97 }
98}
99impl<T: Clone> Clone for Located<T> {
100 fn clone(&self) -> Self {
101 Self {
102 value: self.value.clone(),
103 pos: self.pos.clone(),
104 }
105 }
106}
107impl<T: PartialEq> PartialEq for Located<T> {
108 fn eq(&self, other: &Self) -> bool {
110 self.value == other.value
111 }
112}
113impl<T: Eq> Eq for Located<T> {}
114impl<T: Hash> Hash for Located<T> {
115 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
116 self.value.hash(state);
117 self.pos.hash(state);
118 }
119}
120impl<T> PathLocated<T> {
121 pub fn new(value: T, pos: Position, path: Box<Path>) -> Self {
122 Self { value, pos, path }
123 }
124 pub fn map<U, F: Fn(T) -> U>(self, f: F) -> PathLocated<U> {
126 PathLocated {
127 value: f(self.value),
128 pos: self.pos,
129 path: self.path,
130 }
131 }
132}
133impl<T: Debug> Debug for PathLocated<T> {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 self.value.fmt(f)
136 }
137}
138impl<T: Display> Display for PathLocated<T> {
139 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140 self.value.fmt(f)
141 }
142}
143impl<T: Clone> Clone for PathLocated<T> {
144 fn clone(&self) -> Self {
145 Self {
146 value: self.value.clone(),
147 pos: self.pos.clone(),
148 path: self.path.clone(),
149 }
150 }
151}
152impl<T: PartialEq> PartialEq for PathLocated<T> {
153 fn eq(&self, other: &Self) -> bool {
155 self.value == other.value
156 }
157}
158impl<T: Eq> Eq for PathLocated<T> {}
159impl<T: Hash> Hash for PathLocated<T> {
160 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
161 self.value.hash(state);
162 self.pos.hash(state);
163 self.path.hash(state);
164 }
165}
166
167impl Position {
168 pub fn display(&self, f: &mut String, content: &str) -> std::fmt::Result {
169 let lines = content.lines().collect::<Vec<&str>>();
170 let Some(lines) = lines.get(self.ln.start..=self.ln.end) else {
171 writeln!(f, "... code snippet unavailable ...")?;
172 return Ok(());
173 };
174 if lines.is_empty() {
175 writeln!(f, "... code snippet unavailable ...")?;
176 return Ok(());
177 }
178 let tab = 4;
179 if lines.len() == 1 {
180 let line = lines[0];
181 let ln = self.ln.start;
182 writeln!(f, "{:>tab$}| {line}", ln + 1)?;
183 writeln!(
184 f,
185 "{:>tab$} {}",
186 "",
187 line.char_indices()
188 .map(|(col, _)| if self.col.start <= col && self.col.end > col {
189 '~'
190 } else {
191 ' '
192 })
193 .collect::<String>(),
194 )?;
195 } else {
196 let last_ln = lines.len() - 1;
197 for (ln, line) in lines.iter().copied().enumerate() {
198 writeln!(f, "{:>tab$}| {line}", ln + 1)?;
199 if ln == 0 {
200 writeln!(
201 f,
202 "{:>tab$} {}",
203 "",
204 line.char_indices()
205 .map(|(col, _)| if self.col.start <= col { '~' } else { ' ' })
206 .collect::<String>(),
207 )?;
208 } else if ln == last_ln {
209 writeln!(
210 f,
211 "{:>tab$} {}",
212 "",
213 line.char_indices()
214 .map(|(col, _)| if self.col.end > col { '~' } else { ' ' })
215 .collect::<String>(),
216 )?;
217 } else {
218 writeln!(f, "{:>tab$} {}", "", "~".repeat(line.len()),)?;
219 }
220 }
221 }
222 Ok(())
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 #[test]
230 fn test() {
231 let text = "hello man\n i like pizza";
232 let mut display = String::new();
233 Position::new(0..1, 1..5)
234 .display(&mut display, text)
235 .unwrap();
236 println!("{display}");
237 panic!();
238 }
239}