surql_parser/upstream/syn/error/
render.rs1use super::{Location, MessageKind};
3use std::cmp::Ordering;
4use std::fmt;
5use std::ops::Range;
6#[derive(Clone, Debug)]
7pub struct RenderedError {
8 pub errors: Vec<String>,
9 pub snippets: Vec<Snippet>,
10}
11impl RenderedError {
12 pub fn offset_location(mut self, line: usize, col: usize) -> Self {
17 for s in self.snippets.iter_mut() {
18 if s.location.line == 1 {
19 s.location.column += col;
20 }
21 s.location.line += line;
22 }
23 self
24 }
25}
26impl fmt::Display for RenderedError {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 match self.errors.len().cmp(&1) {
29 Ordering::Equal => writeln!(f, "{}", self.errors[0])?,
30 Ordering::Greater => {
31 writeln!(f, "- {}", self.errors[0])?;
32 writeln!(f, "caused by:")?;
33 for e in &self.errors[2..] {
34 writeln!(f, " - {}", e)?
35 }
36 }
37 Ordering::Less => {}
38 }
39 for s in &self.snippets {
40 writeln!(f, "{s}")?;
41 }
42 Ok(())
43 }
44}
45#[derive(Clone, Copy, Eq, PartialEq, Debug)]
47pub enum Truncation {
48 None,
50 Start,
52 End,
54 Both,
56}
57#[derive(Clone, Debug)]
59pub struct Snippet {
60 source: String,
62 truncation: Truncation,
64 location: Location,
66 offset: usize,
68 length: usize,
70 label: Option<String>,
72 #[expect(dead_code)]
74 kind: MessageKind,
75}
76impl Snippet {
77 const MAX_SOURCE_DISPLAY_LEN: usize = 80;
79 const MAX_ERROR_LINE_OFFSET: usize = 50;
82 pub fn from_source_location(
83 source: &str,
84 location: Location,
85 explain: Option<&'static str>,
86 kind: MessageKind,
87 ) -> Self {
88 let line = source
89 .split('\n')
90 .nth(location.line - 1)
91 .expect("line exists in source");
92 let (line, truncation, offset) = Self::truncate_line(line, location.column - 1);
93 Snippet {
94 source: line.to_owned(),
95 truncation,
96 location,
97 offset,
98 length: 1,
99 label: explain.map(|x| x.into()),
100 kind,
101 }
102 }
103 pub fn from_source_location_range(
104 source: &str,
105 location: Range<Location>,
106 explain: Option<&str>,
107 kind: MessageKind,
108 ) -> Self {
109 let line = source
110 .split('\n')
111 .nth(location.start.line - 1)
112 .expect("line exists in source");
113 let (line, truncation, offset) = Self::truncate_line(line, location.start.column - 1);
114 let length = if location.start.line == location.end.line {
115 (location.end.column - location.start.column).max(1)
116 } else {
117 1
118 };
119 Snippet {
120 source: line.to_owned(),
121 truncation,
122 location: location.start,
123 offset,
124 length,
125 label: explain.map(|x| x.into()),
126 kind,
127 }
128 }
129 fn truncate_line(mut line: &str, target_col: usize) -> (&str, Truncation, usize) {
135 let mut offset = 0;
136 for (i, (idx, c)) in line.char_indices().enumerate() {
137 if i == target_col || !c.is_whitespace() {
138 line = &line[idx..];
139 offset = target_col - i;
140 break;
141 }
142 }
143 line = line.trim_end();
144 let mut truncation = Truncation::None;
145 if offset > Self::MAX_ERROR_LINE_OFFSET {
146 let too_much_offset = offset - 10;
147 let mut chars = line.chars();
148 for _ in 0..too_much_offset {
149 chars.next();
150 }
151 offset = 10;
152 line = chars.as_str();
153 truncation = Truncation::Start;
154 }
155 if line.chars().count() > Self::MAX_SOURCE_DISPLAY_LEN {
156 let mut size = Self::MAX_SOURCE_DISPLAY_LEN - 3;
157 if truncation == Truncation::Start {
158 truncation = Truncation::Both;
159 size -= 3;
160 } else {
161 truncation = Truncation::End
162 }
163 let truncate_index = line
164 .char_indices()
165 .nth(size)
166 .expect("character index exists")
167 .0;
168 line = &line[..truncate_index];
169 }
170 (line, truncation, offset)
171 }
172}
173impl fmt::Display for Snippet {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 let spacing = self.location.line.ilog10() as usize + 1;
176 for _ in 0..spacing {
177 f.write_str(" ")?;
178 }
179 writeln!(f, "--> [{}:{}]", self.location.line, self.location.column)?;
180 for _ in 0..spacing {
181 f.write_str(" ")?;
182 }
183 f.write_str(" |\n")?;
184 write!(f, "{:>spacing$} | ", self.location.line)?;
185 match self.truncation {
186 Truncation::None => {
187 writeln!(f, "{}", self.source)?;
188 }
189 Truncation::Start => {
190 writeln!(f, "...{}", self.source)?;
191 }
192 Truncation::End => {
193 writeln!(f, "{}...", self.source)?;
194 }
195 Truncation::Both => {
196 writeln!(f, "...{}...", self.source)?;
197 }
198 }
199 let error_offset = self.offset
200 + if matches!(self.truncation, Truncation::Start | Truncation::Both) {
201 3
202 } else {
203 0
204 };
205 for _ in 0..spacing {
206 f.write_str(" ")?;
207 }
208 f.write_str(" | ")?;
209 for _ in 0..error_offset {
210 f.write_str(" ")?;
211 }
212 for _ in 0..self.length {
213 write!(f, "^")?;
214 }
215 if let Some(ref explain) = self.label {
216 write!(f, " {explain}")?;
217 }
218 Ok(())
219 }
220}