1use crate::error::RexactError;
4
5pub fn locate(chars: &[char], offset: usize) -> (usize, usize) {
8 let end = offset.min(chars.len());
9 let mut line = 1;
10 let mut col = 1;
11 for &c in &chars[..end] {
12 if c == '\n' {
13 line += 1;
14 col = 1;
15 } else {
16 col += 1;
17 }
18 }
19 (line, col)
20}
21
22impl RexactError {
23 pub fn render(&self, source: &str) -> String {
26 let mut out = self.to_string();
27 if let Some((line, col)) = self.position {
28 if let Some(text) = source.split('\n').nth(line - 1) {
29 out.push_str(&format!("\n {text}\n {}^", " ".repeat(col - 1)));
30 }
31 }
32 out
33 }
34}
35
36#[cfg(test)]
37mod tests {
38 use super::*;
39 use crate::error::ErrorCode;
40
41 #[test]
42 fn locate_counts_scalars_and_lines() {
43 let c: Vec<char> = "ab\nc😀d".chars().collect();
44 assert_eq!(locate(&c, 0), (1, 1));
45 assert_eq!(locate(&c, 2), (1, 3));
46 assert_eq!(locate(&c, 3), (2, 1));
47 assert_eq!(locate(&c, 5), (2, 3)); assert_eq!(locate(&c, 99), (2, 4)); }
50
51 #[test]
52 fn caret_sits_under_the_column() {
53 let mut e = RexactError::new(ErrorCode::P007);
54 e.position = Some((1, 3));
55 let r = e.render("ab[");
56 assert!(r.ends_with("\n ab[\n ^"), "{r}");
57 }
58}