gent/errors/
reporter.rs

1//! Error formatting with source context
2
3use super::GentError;
4
5/// Formats errors with source context
6pub struct ErrorReporter<'a> {
7    source: &'a str,
8    filename: &'a str,
9    pub use_colors: bool,
10}
11
12impl<'a> ErrorReporter<'a> {
13    /// Create a new error reporter
14    pub fn new(source: &'a str, filename: &'a str) -> Self {
15        Self {
16            source,
17            filename,
18            use_colors: atty::is(atty::Stream::Stderr),
19        }
20    }
21
22    /// Calculate line and column from byte offset
23    fn line_col(&self, offset: usize) -> (usize, usize) {
24        let mut line = 1;
25        let mut col = 1;
26        for (i, ch) in self.source.char_indices() {
27            if i >= offset {
28                break;
29            }
30            if ch == '\n' {
31                line += 1;
32                col = 1;
33            } else {
34                col += 1;
35            }
36        }
37        (line, col)
38    }
39
40    /// Get the source line containing the given offset
41    fn get_line(&self, offset: usize) -> &str {
42        let start = self.source[..offset]
43            .rfind('\n')
44            .map(|i| i + 1)
45            .unwrap_or(0);
46        let end = self.source[offset..]
47            .find('\n')
48            .map(|i| offset + i)
49            .unwrap_or(self.source.len());
50        &self.source[start..end]
51    }
52
53    /// Format an error with source context
54    pub fn format(&self, error: &GentError) -> String {
55        let mut output = String::new();
56
57        // Error header
58        let error_msg = error.to_string();
59        if self.use_colors {
60            output.push_str(&format!("\x1b[31merror:\x1b[0m {}\n", error_msg));
61        } else {
62            output.push_str(&format!("error: {}\n", error_msg));
63        }
64
65        // Source location if available
66        if let Some(span) = error.span() {
67            let (line, col) = self.line_col(span.start);
68            let source_line = self.get_line(span.start);
69            let caret_count = (span.end - span.start).max(1);
70
71            // Location line
72            if self.use_colors {
73                output.push_str(&format!(
74                    "  \x1b[36m-->\x1b[0m {}:\x1b[34m{}:{}\x1b[0m\n",
75                    self.filename, line, col
76                ));
77            } else {
78                output.push_str(&format!("  --> {}:{}:{}\n", self.filename, line, col));
79            }
80
81            // Gutter and source line
82            let line_num_width = line.to_string().len();
83            output.push_str(&format!("{:width$} |\n", "", width = line_num_width + 1));
84
85            if self.use_colors {
86                output.push_str(&format!(
87                    "\x1b[34m{:>width$}\x1b[0m | {}\n",
88                    line,
89                    source_line,
90                    width = line_num_width
91                ));
92            } else {
93                output.push_str(&format!(
94                    "{:>width$} | {}\n",
95                    line,
96                    source_line,
97                    width = line_num_width
98                ));
99            }
100
101            // Caret line
102            let padding = col - 1;
103            let carets = "^".repeat(caret_count);
104            if self.use_colors {
105                output.push_str(&format!(
106                    "{:width$} | {:padding$}\x1b[31m{}\x1b[0m\n",
107                    "",
108                    "",
109                    carets,
110                    width = line_num_width + 1,
111                    padding = padding
112                ));
113            } else {
114                output.push_str(&format!(
115                    "{:width$} | {:padding$}{}\n",
116                    "",
117                    "",
118                    carets,
119                    width = line_num_width + 1,
120                    padding = padding
121                ));
122            }
123        }
124
125        output
126    }
127}