1use std::{borrow::Cow, cmp::Ordering, fmt::Display};
2
3pub enum SourceContextLine<'a> {
4 Context {
5 line: usize,
6 outside: Cow<'a, str>,
7 },
8 Start {
9 line: usize,
10 before: Cow<'a, str>,
11 inside: Cow<'a, str>,
12 },
13 End {
14 line: usize,
15 inside: Cow<'a, str>,
16 after: Cow<'a, str>,
17 },
18 StartAndEnd {
19 line: usize,
20 before: Cow<'a, str>,
21 inside: Cow<'a, str>,
22 after: Cow<'a, str>,
23 },
24 Inside {
25 line: usize,
26 inside: Cow<'a, str>,
27 },
28}
29
30impl Display for SourceContextLine<'_> {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 match self {
33 SourceContextLine::Context { line, outside } => {
34 writeln!(f, "{line:>6} | {outside}")
35 }
36 SourceContextLine::Start {
37 line,
38 before,
39 inside,
40 } => {
41 writeln!(
42 f,
43 " | {}v{}",
44 " ".repeat(before.len()),
45 "-".repeat(inside.len()),
46 )?;
47 writeln!(f, "{line:>6} + {before}{inside}")
48 }
49 SourceContextLine::End {
50 line,
51 inside,
52 after,
53 } => {
54 writeln!(f, "{line:>6} + {inside}{after}")?;
55 writeln!(f, " +{}^", "-".repeat(inside.len()))
56 }
57 SourceContextLine::StartAndEnd {
58 line,
59 before,
60 inside,
61 after,
62 } => {
63 if inside.len() >= 2 {
64 writeln!(
65 f,
66 " | {}v{}v",
67 " ".repeat(before.len()),
68 "-".repeat(inside.len() - 2)
69 )?;
70 } else {
71 writeln!(f, " | {}v", " ".repeat(before.len()))?;
72 }
73 writeln!(f, "{line:>6} + {before}{inside}{after}")?;
74 if inside.len() >= 2 {
75 writeln!(
76 f,
77 " | {}^{}^",
78 " ".repeat(before.len()),
79 "-".repeat(inside.len() - 2),
80 )
81 } else {
82 writeln!(f, " | {}^", " ".repeat(before.len()))
83 }
84 }
85 SourceContextLine::Inside { line, inside } => {
86 writeln!(f, "{line:>6} + {inside}")
87 }
88 }
89 }
90}
91
92pub struct SourceContextLines<'a>(pub Vec<SourceContextLine<'a>>);
93
94impl Display for SourceContextLines<'_> {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 for line in &self.0 {
97 write!(f, "{line}")?;
98 }
99 Ok(())
100 }
101}
102
103pub fn get_source_context<'a>(
106 lines: impl Iterator<Item = &'a str>,
107 start_line: u32,
108 start_column: u32,
109 end_line: u32,
110 end_column: u32,
111) -> SourceContextLines<'a> {
112 let mut result = Vec::new();
113 let context_start = (start_line.saturating_sub(4)) as usize;
114 let context_end = (end_line + 4) as usize;
115 for (i, l) in lines.enumerate().take(context_end + 1).skip(context_start) {
116 let n = i + 1;
117 let i = i as u32;
118 fn safe_split_at(s: &str, i: u32) -> (&str, &str) {
119 let i = i as usize;
120 if i < s.len() {
121 s.split_at(s.floor_char_boundary(i))
122 } else {
123 (s, "")
124 }
125 }
126 fn limit_len(s: &str) -> Cow<'_, str> {
127 if s.len() < 200 {
128 return Cow::Borrowed(s);
129 }
130 let (a, b) = s.split_at(s.floor_char_boundary(98));
131 let (_, c) = b.split_at(b.ceil_char_boundary(b.len() - 99));
132 Cow::Owned(format!("{a}...{c}"))
133 }
134 match (i.cmp(&start_line), i.cmp(&end_line)) {
135 (Ordering::Less, _) | (_, Ordering::Greater) => {
137 result.push(SourceContextLine::Context {
138 line: n,
139 outside: limit_len(l),
140 });
141 }
142 (Ordering::Equal, Ordering::Less) => {
144 let (before, inside) = safe_split_at(l, start_column);
145 let before = limit_len(before);
146 let inside = limit_len(inside);
147 result.push(SourceContextLine::Start {
148 line: n,
149 before,
150 inside,
151 });
152 }
153 (Ordering::Equal, Ordering::Equal) => {
155 let real_start = l.floor_char_boundary(start_column as usize) as u32;
156 let (before, temp) = safe_split_at(l, real_start);
157 let (inside, after) = safe_split_at(temp, end_column - real_start);
158 let before = limit_len(before);
159 let inside = limit_len(inside);
160 let after = limit_len(after);
161 result.push(SourceContextLine::StartAndEnd {
162 line: n,
163 before,
164 inside,
165 after,
166 });
167 }
168 (Ordering::Greater, Ordering::Equal) => {
170 let (inside, after) = safe_split_at(l, end_column);
171 let inside = limit_len(inside);
172 let after = limit_len(after);
173 result.push(SourceContextLine::End {
174 line: n,
175 inside,
176 after,
177 });
178 }
179 (Ordering::Greater, Ordering::Less) => {
181 result.push(SourceContextLine::Inside {
182 line: n,
183 inside: limit_len(l),
184 });
185 }
186 }
187 }
188 SourceContextLines(result)
189}